Instantly share code, notes, and snippets.

Embed
What would you like to do?
(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 <jeremie@miserez.org>
//
// 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);
@erapid

This comment has been minimized.

erapid commented Jan 29, 2016

Suggest to change

  • allsongs[i].title
  • allsongs[i].title.replace(/[\n\r!]/g, '').trim()

your variant gives me
Yelawolf - Love Story - !
Best Friend (feat. Eminem)
proposed is plain
Yelawolf - Love Story - Best Friend (feat. Eminem)

@jordam

This comment has been minimized.

jordam commented Feb 13, 2016

This script wasnt detecting when the page was scrolled to the bottom, it worked but it relied on the three second timeout.

Setting scrollDiv = document.querySelector("#mainContainer"); on #160 worked better for me.

I hope you dont mind, I modified the code so that it could be imported as a library and used it for the gplay exporting part of Portify.JS

@daviddgz

This comment has been minimized.

daviddgz commented Apr 13, 2016

Thank you very much for this script, I have been using it for months to track all my collection and see when the songs dissappear, and this is actually quite common.

Also, I noticed that from today that it doesn't work as Google play changed a little bit the layout. Would you be able to fix it? Thanks!

@jmiserez

This comment has been minimized.

Owner

jmiserez commented May 9, 2016

For some reason I get no notification when someone comments on here, so I'm sorry I didn't see your comments until today. I think this might be a Gist/Github bug? EDIT: Yes it's a Github bug.

@erapid: Thanks, done in Rev 19. Probably could do the same for artist/album/etc. if necessary.
@jordam: Thanks, replaced with your suggestion in Rev 19. Using the code: Sure, I see you even linked back to here in g-scrape.js so that's great.
@daviddgz: There is an issue with the copy() function it seems, but the scraping itself still seems to work. Run songsToText("all", false, false); once after the scraping is done, and it should work. I'll fix this issue though once I find what broke.

Also, I just noticed that the deduplication logic has a bug on the first pass. I'll fix that as well.

@jmiserez

This comment has been minimized.

Owner

jmiserez commented May 9, 2016

There seems to be no way to link to the diff of a Gist revision (?), so this is the best I can do:

  • Rev 20: Deduplication issue fixed, diff:
Line 187:
-            seen[curr.id] = true;
+            seen[curr.dataid] = true;
  • Rev 24: Scroll to top before starting to scrape.
Line 136:
+  document.querySelector("#mainContainer").scrollTop = 0; //scroll to top
  • Rev 28: @davidggz "Fix" issue with script not working anymore: The copy() function only works on the developer console and not inside functions, it might be related to this bug. So as a workaround, you can either i) type in "copy(outText);" on the console manually (which works), or ii) copy the printed log output. This might be fixed in a future version of Chrome, then the copy should work automatically again.
  • Rev 29: Fix CSV export (escape quotes and commas).
  • Rev 30: Fix missing ratings and playcount.

As comment notifications don't work for Gists (as mentioned above) when people comment here, feel free to send me an email to jeremie@miserez.org or tweet me at @jmiserez after leaving a comment.

@danhanly

This comment has been minimized.

danhanly commented May 18, 2016

This is excellent!

@phpmoli

This comment has been minimized.

phpmoli commented May 19, 2016

Is this the right place to open an issue?
Possible bug: exported data has a letter 'E' in front of the song titles in case the song is marked 'explicit'.

@jmiserez

This comment has been minimized.

Owner

jmiserez commented May 23, 2016

@phpmoli: Yes this seems to be a bug, I'll see if I can fix it. All "explicit" songs have the string "E " (an E followed by two spaces) prepended to the song title.

@christophetd

This comment has been minimized.

christophetd commented Sep 11, 2016

Thank you for this!

@jacklaplante

This comment has been minimized.

jacklaplante commented Sep 29, 2016

You are a god

@chorfu

This comment has been minimized.

chorfu commented Nov 17, 2016

This is super!

@roman-ku

This comment has been minimized.

roman-ku commented Nov 25, 2016

Can you add track duration as an export option? It helps me match up songs.

Never mind, it's there in the CSV export.

@DaniGuardiola

This comment has been minimized.

DaniGuardiola commented Jan 13, 2017

The iron-scrolling-like breaks this badly

@btwarog

This comment has been minimized.

btwarog commented Jan 23, 2017

This doesn't work anymore. I guess it needs few fixes

@jmiserez

This comment has been minimized.

Owner

jmiserez commented Feb 24, 2017

It still works for me...? If you have more details please send me the error message/screenshot via email (jeremie@miserez.org).

@tomkrizan

This comment has been minimized.

tomkrizan commented Mar 1, 2017

This works well enough for me. There's some formatting issues but I think that's because of my library being weird (from uploading to GMusic years ago and having some old beta features enabled, like 5 star ratings). Thanks a bunch, I've tried other things in the past but none worked as well as this did.

@halsafar

This comment has been minimized.

halsafar commented Mar 21, 2017

This worked well for me.

@potaito

This comment has been minimized.

potaito commented Mar 31, 2017

Worked like a charm, thank you very much. Was also able to export playlists by executing the script while viewing them.

@itm1960

This comment has been minimized.

itm1960 commented Apr 15, 2017

I just tried this on Chrome 57.0.2987.133 (64-bit) - Windows 10 and got this error:

Uncaught TypeError: Cannot set property 'scrollTop' of null
    at scrapeSongs (<anonymous>:103:54)
    at <anonymous>:177:1
scrapeSongs @ VM1962:103
(anonymous) @ VM1962:177
@ifgx

This comment has been minimized.

ifgx commented May 17, 2017

@itm1960 Just worked for me. Windows 10 / Chrome 57.0.2987.133 (64-bit)

@brk3

This comment has been minimized.

brk3 commented May 26, 2017

Thanks for this, works great. Here's a small helper script I wrote to use the output of this to import albums into Spotify (https://github.com/brk3/gmusic-to-spotify)

@GoCleanYourRoom

This comment has been minimized.

GoCleanYourRoom commented Jun 6, 2017

This is fantastic. Thank you!

I am running into one problem--it looks like album names that end in an exclamation mark, are signifying the end of the list. e.g. This playlist has 50 songs, but will only export the first 36 to .CSV, presumably because the 37th song is from an album called "Climbing!"

https://play.google.com/music/listen#/pl/AMaBXykaDHfmXvAKqSyLc4z0PJxkIx5pZ7U-pjlx5iJaCxhczFfNTNX_DJ0oEMT8SNYs8pfvCKc5-KKj4qQX-uc_geXbifdTzA%3D%3D

@glebavladimir

This comment has been minimized.

glebavladimir commented Jun 17, 2017

Wow. Just googled for a luck and here it is. Thanks!

@RZetko

This comment has been minimized.

RZetko commented Jul 14, 2017

I just made very simple csv exporter to Tidal if anyone is interested. Everything needed is mentioned in comments in file. https://gist.github.com/RZetko/71801a20188e842ef03bed3b6d7a297f

@zkazy

This comment has been minimized.

zkazy commented Jul 20, 2017

How can I skip saving the album name?

@bladegery

This comment has been minimized.

bladegery commented Sep 29, 2017

Thank you for this, lastfm is the past now for me!

@jmiserez

This comment has been minimized.

Owner

jmiserez commented Dec 5, 2017

NOTE: I removed 2 comments linking to commercial websites. This page is not the right place for ads/spam (both users had no GitHub contributions).

@jrussi

This comment has been minimized.

jrussi commented Jan 24, 2018

Alternatively, you can get the information from the db created by the google music.
Hit F12 and paste the code in the console.

`
var allData = [];
var allDataOrg = {};
var dbName = "music_09372034525924891548";
var request = indexedDB.open(dbName, 6);
request.onsuccess = function(){
var db = request.result;
var transaction = db.transaction(["tracks"]);
var objectStore = transaction.objectStore("tracks");
var req2 = objectStore.getAllKeys();
req2.onsuccess = function(){
var keys = req2.result;
for (var ii=0;ii<keys.length;ii++){
getData(keys[ii],db);
}
}
}
//
function getData(key,db){
var transaction = db.transaction(["tracks"]);
var objectStore = transaction.objectStore("tracks");
var request2 = objectStore.get(key);
request2.onsuccess = function() {
xml=JSON.parse(request2.result);
var keys = Object.keys(xml);
for (var ii=0;ii<keys.length;ii++){
var title = xml[keys[ii]][1];
var album = xml[keys[ii]][4];
var ano = xml[keys[ii]][18];
var artist1 = xml[keys[ii]][3];
var artist2 = xml[keys[ii]][5];
var length = xml[keys[ii]][13]/100;
var trackNumber = xml[keys[ii]][14];
//console.log(title + "\t" + album + "\t" + ano + "\t" + artist1 + "\t" + artist2 + "\t" + length + "\t" + trackNumber);
console.log(album + "\t" + ano + "\t" + trackNumber + "\t" + title);
window.allData.push({"artist": artist1 + "; " +artist2, "album": album, "ano": ano, "track": trackNumber + " " + title});
var pt1 = artist1 + "- " +artist2 + "- " + album;
var pt2 = trackNumber + ". " + title;
if (typeof(window.allDataOrg[pt1]) == "undefined"){
window.allDataOrg[pt1] = [];
}
window.allDataOrg[pt1].push(pt2);
}
}
}
//
window.allDataOrg

`

@TorATB

This comment has been minimized.

TorATB commented Mar 5, 2018

Can anyone make this into a Chrome Extension?

@TorATB

This comment has been minimized.

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

This comment has been minimized.

montamal commented Mar 22, 2018

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

This comment has been minimized.

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

This comment has been minimized.

jollybully commented May 23, 2018

This worked for me, thanks!

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

@jean-frederic

This comment has been minimized.

jean-frederic commented Jun 19, 2018

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

This comment has been minimized.

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

This comment has been minimized.

mahonta commented Aug 24, 2018

Works greatI Sure do appreciate it!

@CyberRen

This comment has been minimized.

CyberRen commented Oct 20, 2018

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!

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