Skip to content

Instantly share code, notes, and snippets.

@jmiserez
Last active December 20, 2023 01:45
Show Gist options
  • Save jmiserez/c9a9a0f41e867e5ebb75 to your computer and use it in GitHub Desktop.
Save jmiserez/c9a9a0f41e867e5ebb75 to your computer and use it in GitHub Desktop.
(fixed/updated 2016-05-10) Export your Google Music Library and Playlists (Google Play Music All Access) (see http://webapps.stackexchange.com/questions/50311/print-playlist-from-google-play-music for more)
// Copyright 2016 Jeremie Miserez <[email protected]>
//
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// A little bit of Javascript to let you export your Google Music library, playlists, and album track lists :)
//
// I posted this as an answer here: http://webapps.stackexchange.com/questions/50311/print-playlist-from-google-play-music
//
// 1. Go to: https://play.google.com/music/listen#/all (or your playlist)
//
// 2. Open a developer console (F12 for Chrome). Paste
// code below into the console.
//
// 3. All scraped songs are stored in the `allsongs` object
// and a text version of the list is copied to the clipboard. I recommend running
// `songsToText("all",true)` afterwards to get the full CSV information.
//
// 4. If you would like the output in a text format, can call
// the songsToText() function. You can select a style, choose
// the format, and if only liked/thumbed up songs should be exported.
// The resulting list will then be pasted into the clipboard.
// Styles are ` all`, `artist`, `artistalbum`, `artistsong`,
// `artistalbumsong`.
// CSV will result in a CSV file and can be left out (defaults to false).
// Likedonly can be left out (defaults to
// false) or set to true, and will filter all songs with
// ratings greater or equal to 5.
// E.g:
// - `songsToText("all",true,false)` will export all songs in csv format.
// - `songsToText("all",true,true)` will export only liked songs in csv format.
// - `songsToText("artistsong",false,false)` will export all songs as text.
//
// 5. You can then paste the data anywhere you like, for
// example http://www.ivyishere.org/ if you want to add the
// songs or albums to your Spotify account. To make Ivy
// recognize full albums, use the "artistalbum" style. For
// songs, use the "artistsong" style.
// see my answer here for questions: http://webapps.stackexchange.com/a/73792/77056
var allsongs = []
var outText = "";
var songsToText = function(style, csv, likedonly){
if (style === undefined){
console.log("style is undefined.");
return;
}
var csv = csv || false; // defaults to false
var likedonly = likedonly || false; // defaults to false
if (likedonly) {
console.log("Only selecting liked songs");
}
if (style == "all" && !csv){
console.log("Duration, ratings, and playcount will only be exported with the CSV flag");
}
outText = "";
if (csv) {
if (style == "all") {
//extra line
outText = "artist,album,title,duration,playcount,rating,rating_interpretation" + "\n";
} else if (style == "artist") {
} else if (style == "artistsong") {
} else if (style == "artistalbum") {
} else if (style == "artistalbumsong") {
} else {
console.log("style not defined");
}
}
var numEntries = 0;
var seen = {};
for (var i = 0; i < allsongs.length; i++) {
var curr = "";
var properTitle = allsongs[i].title.replace(/[\n\r!]/g, '').trim();
if (!likedonly || (likedonly && allsongs[i].rating >= 5)){
if (csv) {
if (style == "all") {
//extra line
curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + allsongs[i].album.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + properTitle.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + allsongs[i].duration.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + allsongs[i].playcount.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + allsongs[i].rating.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + allsongs[i].rating_interpretation.replace(/"/g, '""').trim() + '"';
} else if (style == "artist") {
curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"';
} else if (style == "artistsong") {
curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + properTitle.replace(/"/g, '""').trim() + '"';
} else if (style == "artistalbum") {
curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + allsongs[i].album.replace(/"/g, '""').trim() + '"';
} else if (style == "artistalbumsong") {
curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + allsongs[i].album.replace(/"/g, '""').trim() + '"' + ",";
curr += '"' + properTitle.replace(/"/g, '""').trim() + '"';
} else {
console.log("style not defined");
}
} else {
if (style == "all"){
curr = allsongs[i].artist + " - " + allsongs[i].album + " - " + properTitle + " [[playcount: " + allsongs[i].playcount + ", rating: " + allsongs[i].rating_interpretation + "]]" ;
} else if (style == "artist"){
curr = allsongs[i].artist;
} else if (style == "artistalbum"){
curr = allsongs[i].artist + " - " + allsongs[i].album;
} else if (style == "artistsong"){
curr = allsongs[i].artist + " - " + properTitle;
} else if (style == "artistalbumsong"){
curr = allsongs[i].artist + " - " + allsongs[i].album + " - " + properTitle;
} else {
console.log("style not defined");
}
}
if (!seen.hasOwnProperty(curr)){ // hashset
outText = outText + curr + "\n";
numEntries++;
seen[curr] = true;
} else {
//console.log("Skipping (duplicate) " + curr);
}
}
}
console.log("=============================================================");
console.log(outText);
console.log("=============================================================");
try {
copy(outText);
console.log("copy(outText) to clipboard succeeded.");
} catch (e) {
console.log(e);
console.log("copy(outText) to clipboard failed, please type copy(outText) on the console or copy the log output above.");
}
console.log("Done! " + numEntries + " lines in output. Used " + numEntries + " unique entries out of " + allsongs.length + ".");
};
var scrapeSongs = function(){
var intervalms = 1; //in ms
var timeoutms = 3000; //in ms
var retries = timeoutms / intervalms;
var total = [];
var seen = {};
var topId = "";
document.querySelector("#mainContainer").scrollTop = 0; //scroll to top
var interval = setInterval(function(){
var songs = document.querySelectorAll("table.song-table tbody tr.song-row");
if (songs.length > 0) {
// detect order
var colNames = {
index: -1,
title: -1,
duration: -1,
artist: -1,
album: -1,
playcount: -1,
rating: -1
};
for (var i = 0; i < songs[0].childNodes.length; i++) {
colNames.index = songs[0].childNodes[i].getAttribute("data-col") == "index" ? i : colNames.index;
colNames.title = songs[0].childNodes[i].getAttribute("data-col") == "title" ? i : colNames.title;
colNames.duration = songs[0].childNodes[i].getAttribute("data-col") == "duration" ? i : colNames.duration;
colNames.artist = songs[0].childNodes[i].getAttribute("data-col") == "artist" ? i : colNames.artist;
colNames.album = songs[0].childNodes[i].getAttribute("data-col") == "album" ? i : colNames.album;
colNames.playcount = songs[0].childNodes[i].getAttribute("data-col") == "play-count" ? i : colNames.playcount;
colNames.rating = songs[0].childNodes[i].getAttribute("data-col") == "rating" ? i : colNames.rating;
}
// check if page has updated/scrolled
var currId = songs[0].getAttribute("data-id");
if (currId == topId){ // page has not yet changed
retries--;
scrollDiv = document.querySelector("#mainContainer");
isAtBottom = scrollDiv.scrollTop == (scrollDiv.scrollHeight - scrollDiv.offsetHeight)
if (isAtBottom || retries <= 0) {
clearInterval(interval); //done
allsongs = total;
console.log("Got " + total.length + " songs and stored them in the allsongs variable.");
console.log("Calling songsToText with style all, csv flag true, likedonly false: songsToText(\"all\", false).");
songsToText("artistalbumsong", false, false);
}
} else {
retries = timeoutms / intervalms;
topId = currId;
// read page
for (var i = 0; i < songs.length; i++) {
var curr = {
dataid: songs[i].getAttribute("data-id"),
index: (colNames.index != -1 ? songs[i].childNodes[colNames.index].textContent : ""),
title: (colNames.title != -1 ? songs[i].childNodes[colNames.title].textContent : ""),
duration: (colNames.duration != -1 ? songs[i].childNodes[colNames.duration].textContent : ""),
artist: (colNames.artist != -1 ? songs[i].childNodes[colNames.artist].textContent : ""),
album: (colNames.album != -1 ? songs[i].childNodes[colNames.album].textContent : ""),
playcount: (colNames.playcount != -1 ? songs[i].childNodes[colNames.playcount].textContent : ""),
rating: (colNames.rating != -1 ? songs[i].childNodes[colNames.rating].getAttribute("data-rating") : ""),
rating_interpretation: "",
}
if(curr.rating == "undefined") {
curr.rating_interpretation = "never-rated"
}
if(curr.rating == "0") {
curr.rating_interpretation = "not-rated"
}
if(curr.rating == "1") {
curr.rating_interpretation = "thumbs-down"
}
if(curr.rating == "5") {
curr.rating_interpretation = "thumbs-up"
}
if (!seen.hasOwnProperty(curr.dataid)){ // hashset
total.push(curr);
seen[curr.dataid] = true;
}
}
songs[songs.length-1].scrollIntoView(true); // go to next page
}
}
}, intervalms);
};
scrapeSongs();
// for the full CSV version you can now call songsToText("all", true);
@TorATB
Copy link

TorATB commented Mar 7, 2018

Ok, I made it into an extension for Chrome/Opera
Unpacked Extension, compressed to zip file
Packed Extension to crx

After you have loaded the extension, you need to REFRESH the google music page (F5)
It does NOT work if you do not refresh the page...

@montamal
Copy link

Here's the easy copy paste version freshly tested :

var allsongs = []
var outText = "";

var songsToText = function(style, csv, likedonly){
  if (style === undefined){
    console.log("style is undefined.");
    return;
  }
  var csv = csv || false; // defaults to false
  var likedonly = likedonly || false; // defaults to false
  if (likedonly) {
    console.log("Only selecting liked songs");
  }
  if (style == "all" && !csv){
    console.log("Duration, ratings, and playcount will only be exported with the CSV flag");
  }
  outText = "";
  if (csv) {
    if (style == "all") {
      //extra line
      outText = "artist,album,title,duration,playcount,rating,rating_interpretation" + "\n";
    } else if (style == "artist") {
    } else if (style == "artistsong") {
    } else if (style == "artistalbum") {
    } else if (style == "artistalbumsong") {
    } else {
      console.log("style not defined");
    }
  }
  var numEntries = 0;
  var seen = {};
  for (var i = 0; i < allsongs.length; i++) {
    var curr = "";
    var properTitle = allsongs[i].title.replace(/[\n\r!]/g, '').trim();
    if (!likedonly || (likedonly && allsongs[i].rating >= 5)){
      if (csv) {
        if (style == "all") {
          //extra line
          curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + allsongs[i].album.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + properTitle.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + allsongs[i].duration.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + allsongs[i].playcount.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + allsongs[i].rating.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + allsongs[i].rating_interpretation.replace(/"/g, '""').trim() + '"';
        } else if (style == "artist") {
          curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"';
        } else if (style == "artistsong") {
          curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + properTitle.replace(/"/g, '""').trim() + '"';
        } else if (style == "artistalbum") {
          curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + allsongs[i].album.replace(/"/g, '""').trim() + '"';
        } else if (style == "artistalbumsong") {
          curr += '"' + allsongs[i].artist.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + allsongs[i].album.replace(/"/g, '""').trim() + '"' + ",";
          curr += '"' + properTitle.replace(/"/g, '""').trim() + '"';
        } else {
          console.log("style not defined");
        }
      } else {
        if (style == "all"){
          curr = allsongs[i].artist + " - " + allsongs[i].album + " - " + properTitle + " [[playcount: " + allsongs[i].playcount + ", rating: " + allsongs[i].rating_interpretation + "]]" ;
        } else if (style == "artist"){
          curr = allsongs[i].artist;
        } else if (style == "artistalbum"){
          curr = allsongs[i].artist + " - " + allsongs[i].album;
        } else if (style == "artistsong"){
          curr = allsongs[i].artist + " - " + properTitle;
        } else if (style == "artistalbumsong"){
          curr = allsongs[i].artist + " - " + allsongs[i].album + " - " + properTitle;
        } else {
          console.log("style not defined");
        }
      }
      if (!seen.hasOwnProperty(curr)){ // hashset
        outText = outText + curr + "\n";
        numEntries++;
        seen[curr] = true;
      } else {
        //console.log("Skipping (duplicate) " + curr);
      }
    }
  }
  console.log("=============================================================");
  console.log(outText);
  console.log("=============================================================");
  try {
    copy(outText);
    console.log("copy(outText) to clipboard succeeded.");
  } catch (e) {
    console.log(e);
    console.log("copy(outText) to clipboard failed, please type copy(outText) on the //console or copy the log output above.");
  }
  console.log("Done! " + numEntries + " lines in output. Used " + numEntries + " unique entries out of " + allsongs.length + ".");
  saveText('Google-Playlist.csv', outText);
};

function saveText(filename, text) {
	var tempElem = document.createElement('a');
	tempElem.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
	tempElem.setAttribute('download', filename);
	tempElem.click();
}

var scrapeSongs = function(){
  var intervalms = 1; //in ms
  var timeoutms = 3000; //in ms
  var retries = timeoutms / intervalms;
  var total = [];
  var seen = {};
  var topId = "";
  document.querySelector("#mainContainer").scrollTop = 0; //scroll to top
  var interval = setInterval(function(){
    var songs = document.querySelectorAll("table.song-table tbody tr.song-row");
    if (songs.length > 0) {
      // detect order
      var colNames = {
        index: -1,
        title: -1,
        duration: -1,
        artist: -1,
        album: -1,
        playcount: -1,
        rating: -1
        };
      for (var i = 0; i < songs[0].childNodes.length; i++) {
        colNames.index = songs[0].childNodes[i].getAttribute("data-col") == "index" ? i : colNames.index;
        colNames.title = songs[0].childNodes[i].getAttribute("data-col") == "title" ? i : colNames.title;
        colNames.duration = songs[0].childNodes[i].getAttribute("data-col") == "duration" ? i : colNames.duration;
        colNames.artist = songs[0].childNodes[i].getAttribute("data-col") == "artist" ? i : colNames.artist;
        colNames.album = songs[0].childNodes[i].getAttribute("data-col") == "album" ? i : colNames.album;
        colNames.playcount = songs[0].childNodes[i].getAttribute("data-col") == "play-count" ? i : colNames.playcount;
        colNames.rating = songs[0].childNodes[i].getAttribute("data-col") == "rating" ? i : colNames.rating;
      }
      // check if page has updated/scrolled
      var currId = songs[0].getAttribute("data-id");
      if (currId == topId){ // page has not yet changed
        retries--;
        scrollDiv = document.querySelector("#mainContainer");
        isAtBottom = scrollDiv.scrollTop == (scrollDiv.scrollHeight - scrollDiv.offsetHeight)
        if (isAtBottom || retries <= 0) {
          clearInterval(interval); //done
          allsongs = total;
          console.log("Got " + total.length + " songs and stored them in the allsongs variable.");
          console.log("Calling songsToText with style all, csv flag true, likedonly false: songsToText(\"all\", false).");
		  
          //songsToText("artistalbumsong", false, false);
		  songsToText("all", true);
        }
      } else {
        retries = timeoutms / intervalms;
        topId = currId;
        // read page
        for (var i = 0; i < songs.length; i++) {
          var curr = {
            dataid: songs[i].getAttribute("data-id"),
            index: (colNames.index != -1 ? songs[i].childNodes[colNames.index].textContent : ""),
            title: (colNames.title != -1 ? songs[i].childNodes[colNames.title].textContent : ""),
            duration: (colNames.duration != -1 ? songs[i].childNodes[colNames.duration].textContent : ""),
            artist: (colNames.artist != -1 ? songs[i].childNodes[colNames.artist].textContent : ""),
            album: (colNames.album != -1 ? songs[i].childNodes[colNames.album].textContent : ""),
            playcount: (colNames.playcount != -1 ? songs[i].childNodes[colNames.playcount].textContent : ""),
            rating: (colNames.rating != -1 ? songs[i].childNodes[colNames.rating].getAttribute("data-rating") : ""),
            rating_interpretation: "",
            }
          if(curr.rating == "undefined") {
            curr.rating_interpretation = "never-rated"
          }
          if(curr.rating == "0") {
            curr.rating_interpretation = "not-rated"
          }
          if(curr.rating == "1") {
            curr.rating_interpretation = "thumbs-down"
          }
          if(curr.rating == "5") {
            curr.rating_interpretation = "thumbs-up"
          }
          if (!seen.hasOwnProperty(curr.dataid)){ // hashset
            total.push(curr);
            seen[curr.dataid] = true;
          }
        }
        songs[songs.length-1].scrollIntoView(true); // go to next page
      }
    }
  }, intervalms);
};
scrapeSongs();
// for the full CSV version you can now call songsToText("all", true);

    sendResponse({data: data, success: true});

@blalond
Copy link

blalond commented Apr 7, 2018

Thanks @montamal - yours worked pasted in the chrome console when showing Songs tab on Google Play Music Library. I then had to do "copy(outText)" in the console and it worked.

@jollybully
Copy link

This worked for me, thanks!

http://www.playlist-converter.net was then able to (mostly) successfully import the playlist's into Spotify.

@jean-frederic
Copy link

Hello,

I've just found your script and it's nice, work 98% of the time.

Im able to reproduce an issue on that playlist :
https://play.google.com/music/playlist/AMaBXymMImtCEDzu3Bt-qIezJNpMeWEbeazynS7P6tWPVWE_bSqskegbj08BA5CDSzDwAwwnf7v0apVzw9bMGP_CyNQUaQPfyw%3D%3D

The song #55 Wagon Wheel doesnt get into the final CSV.

I added that song alone in a new playlist (empty) and I get it inside CSV.

Anyone else facing that kind of issue ?

Thanks

@titooo7
Copy link

titooo7 commented Aug 16, 2018

Could someone make a similar code to be applied to the our whole google play music library?

I'd be more than happy if I could get all the artists names of my google play music library (I don't really know the song names now)

@mahonta
Copy link

mahonta commented Aug 24, 2018

Works greatI Sure do appreciate it!

@CyberRen
Copy link

Hello! I used the code last month and it worked perfectly! However I tried to do it again and its not getting the last 8 songs in my Library? :/ Its seems like it scrolled down all the way. Do you have any advice?
Thank you for putting in the work for this!

@RexFack
Copy link

RexFack commented Dec 27, 2018

Thx it works!
27.12.2018

@jnutterdev
Copy link

Just waned to say thanks! This has been tremendously helpfu!

@Armitage35
Copy link

Thanks a lot for this snippet! It worked marvellously for me!

@crichardDFC
Copy link

This is fantastic, thank you so much! Is it possible to capture the Date Added column in a Last Added auto-playlist?

@gmp96c
Copy link

gmp96c commented Jun 12, 2020

For getting a csv of all tracks and plays for use with the universal scrobbler thing I ran this after the main code and it worked well.

allsongs.reduce((acc,cur)=>{
let plays = cur.playcount == '' ?0:parseInt(cur.playcount);
let current = [];
while(plays>0){
current.push(`"${cur.artist}","${cur.title.trim()}","${cur.album}"`);
plays--;
}
return [...acc, ...current];
},[]).join('\n');

@acdcsteve
Copy link

Is there a similar way to do this with YouTube Music?

@lucidBrot
Copy link

lucidBrot commented Aug 15, 2020

I have created a python script that takes the export from google takeout (i.e. from google play music) and your local files as well as the exported files, and finds matches between the songs in the playlists and the music files on your computer. You end up with two m3u playlist files per playlist: One using relative paths and one using absolute paths.

https://github.com/lucidBrot/migrate-from-google-play-music

I do not know, but I can imagine that you could export from YouTube Music in the same way using takeout.google.com @acdcsteve ?
If not, have you seen this stackexchange answer by the creator of this gist's original source code?

@acdcsteve
Copy link

acdcsteve commented Aug 15, 2020

Hey thanks lucidBrot, however when I did a Google Takeout, it only had all the playlists from YouTube Music, I couldn't find anything containing my entire library. I also believe there's a limit in the number of songs per playlist, so I wouldn't be able to get my 5,000 song collection into one playlist.
Also, that link will help with exporting playlists, however, I don't want to export playlists, I want to export my entire library, like how the code in the original post does.

@lucidBrot
Copy link

@acdcsteve That's disappointing... my Google Takeout for Google Play Music contains a Tracks folder contains 4285 music files in my case.

Do your exported playlists maybe contain the song's youtube links? Then you could easily write a script that downloads them all for you.

@acdcsteve
Copy link

acdcsteve commented Aug 15, 2020

@acdcsteve That's disappointing... my Google Takeout for Google Play Music contains a Tracks folder contains 4285 music files in my case.

Do your exported playlists maybe contain the song's youtube links? Then you could easily write a script that downloads them all for you.

Indeed it will lucidBrot, but my question is concerning the YouTube Music section of Google Takeout, as I assume the Google Play Music section of Google Takeout will be removed in the near future.
Also, I'm not interested in exporting my YouTube Music playlists - I'm trying to find a way to get a list of my entire uploaded music library in YouTube Music (for the future - obviously I can get it through Google Play Music for now via the original post).

Copy link

ghost commented Sep 12, 2020

Thank you for this script. It still works great as of today.

@jessicah
Copy link

Is there a way to modify this to automatically download all tracks? Seems I missed the window on downloading my library :-/ I see that can still download individual tracks from Google Play Music for now, would be nice to back this up, YouTube Music sucks.

@lucidBrot
Copy link

@jessicah are you saying that https://takeout.google.com/ is no longer an option? Just to be sure, because it does still show Google Play Music for me

@jessicah
Copy link

Oh, I didn't know it could include the actual music tracks, I'll give that a whirl, thank you :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment