2

I want to print a list of songs (with artist, album, and duration) from my YouTube Music account.

There is no easy way to do this from the app. Doing print-screens as I page through a long list of songs is not tenable.

I would be happy with an export of data to a standard format (plain text, CSV, XML, etc.) that I can manipulate myself.

Any suggestions?

1 Answer 1

3

I do not recommend playing music from the same tab that you're doing this process in as some of the time it seemed to cause problems for me.

Instructions:

  1. Go to your Playlists page.
  2. Press F12 to open your console.
  3. Paste in the JavaScript code below into your console.
  4. Surprisingly important: Close your console. I've found that YouTube Music works wayyy slower with the console open for some reason.
  5. Click on a playlist that you want to save to text.
  6. If the playlist is longer than 100 songs, scroll to the bottom. Continue until the last batch is loaded (YouTube Music seems to load batches of 100 songs, so if there are 317 songs in your playlist you'll need to scroll to the bottom and wait for it to load 3 times).
  7. Once all songs have loaded, navigate back to the playlists page (same as in step 1.) using the menu or your browsers back button.
  8. Repeat steps 5-7 for all playlists you want to save to text.
  9. Once you've done this for all the playlists you want to save to text, open your console (F12) and either type JSON.stringify(tracklistObj, null, '\t') (change the '\t' to ' ' if you want minimal indentation) or tracklistObj if you just want the JavaScript object to manipulate it your own way. If you want it sorted, run the command Object.values(tracklistObj).forEach(a => a.sort()) before calling the JSON.stringify command.

Be careful to not refresh the page before you've completed all that you want to do or else you'll have to restart from step 1.

// Setup
var tracklistObj = {},
    currentPlaylist,
    checkIntervalTime = 100,
    lastTime;

function processString(str) {
    str = str.trim();
    if (!stripFileExtension) return str;

    return str.replaceAll(".mp3", "");
}

function convertDurationtoSeconds(duration) {
    const hasHours = duration.split(':').length - 1 === 2;
    if (hasHours) {
        const [hours, minutes, seconds] = duration.split(':');
        return 360 * Number(hours) + 60 + Number(minutes) + Number(seconds);
    } else {
        const [minutes, seconds] = duration.split(':');
        return 60 * Number(minutes) + Number(seconds);
    }
};

// Process the visible tracks
function getVisibleTracks() {
    var playlistTrackContainers = document.querySelectorAll('#header + #contents > ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer');
    for(var i = 0; i < playlistTrackContainers.length ; i++) { 
        var trackContainer = playlistTrackContainers[i];

        var title = trackContainer.querySelector('.title-column');
        if(title !== null) {
            title = processString(title.textContent);
        } else return;

        var artist = trackContainer.querySelector('.secondary-flex-columns > yt-formatted-string:first-child');
        if(artist !== null) {
            artist = processString(artist.textContent);
        } else return;

        var duration = trackContainer.children[trackContainer.children.length - 1];
        if(duration !== null) {
            duration = processString(duration.textContent);
            if (durationInSeconds) {
                duration = convertDurationtoSeconds(duration);
            }
        }

        var album = trackContainer.querySelector('.secondary-flex-columns > yt-formatted-string:nth-child(2)');
        if(album !== null)
            album = processString(album.textContent);

        // Add it if it doesn't exist already
        var key = artist + " - " + title;
        if(tracklistObj[currentPlaylist] && !tracklistObj[currentPlaylist].includes(key)) {
            tracklistObj[currentPlaylist].push(key);

            if(printTracksToConsole) {
                console.log(key);
            }
        }
    }
}

// Listen for page changes
window.history.pushState = new Proxy(window.history.pushState, {
    apply: (target, thisArg, argArray) => {
        currentPlaylist = null; 

        var doneLoading = setInterval(function() {
            var playListName = document.querySelector('ytmusic-responsive-header-renderer .title');
            if(playListName != null) {
                currentPlaylist = playListName.innerText;
                if(tracklistObj[currentPlaylist] === undefined) {
                    tracklistObj[currentPlaylist] = [];
                }

                console.log("===================================");
                console.log("Adding to playlist " + currentPlaylist);

                getVisibleTracks();

                clearInterval(doneLoading);
            }
        }, 100);

        return target.apply(thisArg, argArray);
  },
});

// Check for new tracks every so often
setInterval(function() {
    getVisibleTracks();
}, checkIntervalTime);

// Whether or not to print the tracks obtained to the console
var printTracksToConsole = false;
var stripFileExtension = true;
var durationInSeconds = false;

You can also print out track names to the console as you go by changing printTracksToConsole to true (you should do this between steps 2 and 3).

By default this script will strip any file format off of the end of the track names. Set stripFileExtension to false if you don't want to do this.

You can also convert the duration to seconds by setting durationInSeconds to true.

Note that currently it's setup only to give Artist - Track name, but you can easily edit the line that has tracklistObj[currentPlaylist].push(artist + " - " + title); with album or duration, and/or whatever formatting you want (including CSV format if you so please). Do this before step 3.

Example output (all YouTube Music playlists I currently have) with default settings. It took about 5 minutes in total to navigate to each of the 52 playlists, scroll down them, and then convert the result to text.


Feel free to modify the above script. For example, to output a .m3u file, I changed key to:

var key = '#EXTINF:' + duration + ', ' + artist + ' - ' + title + '\n' + String.raw`E:\music` + '\\' + title + '.mp3\n\n';

Then set durationInSeconds to true, then did console.log(tracklistObj['My Playlist'].join('')). I copy-pasted this into a new file and put #EXTM3U at the top, then saved as My Playlist.m3u! Make sure to change the file path in the key variable to the path that you need.

3
  • Heads up I had to remove var duration = info[3].querySelector('a'); if (duration !== null) duration = duration.textContent; as it was throwing error
    – drez90
    Feb 3, 2021 at 20:08
  • @d_rez90 Hmm. It seems to work fine to me. You could add some additional checking to make sure info[3] exists (or whatever it's throwing the error at). Feb 3, 2021 at 20:32
  • Strange. Not sure why it didn't work for me but the duration wasn't important to me so got rid of it. Upvoted ya anyways, thanks.
    – drez90
    Feb 3, 2021 at 20:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.