Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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)
// Jeremie Miserez <jeremie@miserez.org>, 2015
//
// 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/73791/77056
var allsongs = []
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");
}
var outText = "";
if (csv) {
if (style == "all") {
//extra line
outText = "index,artist,album,title,duration,playcount,rating" + "\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 = "";
if (!likedonly || (likedonly && allsongs[i].rating >= 5)){
if (csv) {
if (style == "all") {
//extra line
curr = allsongs[i].artist + ",";
curr += allsongs[i].album + ",";
curr += allsongs[i].title + ",";
curr += allsongs[i].duration + ",";
curr += allsongs[i].index + ",";
curr += allsongs[i].playcount + ",";
curr += allsongs[i].rating;
} else if (style == "artist") {
curr = allsongs[i].artist;
} else if (style == "artistsong") {
curr = allsongs[i].artist + ",";
curr += allsongs[i].title;
} else if (style == "artistalbum") {
curr = allsongs[i].artist + ",";
curr += allsongs[i].album;
} else if (style == "artistalbumsong") {
curr = allsongs[i].artist + "\t";
curr += allsongs[i].album + "\t";
curr += allsongs[i].title;
} else {
console.log("style not defined");
}
} else {
if (style == "all"){
curr = "";
curr += (allsongs[i].index.length > 0 ? allsongs[i].index + ". " : "");
curr += allsongs[i].artist + " - ";
curr += allsongs[i].album + " - ";
curr += allsongs[i].title;
} 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 + " - " + allsongs[i].title;
} else if (style == "artistalbumsong"){
curr = allsongs[i].artist + " - " + allsongs[i].album + " - " + allsongs[i].title;
} 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);
}
}
}
copy(outText);
console.log("Done! " + numEntries + " lines copied to clipboard. Used " + numEntries + " songs 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 = "";
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") == "playcount" ? 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("div#music-content");
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("all", 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].textContent : ""),
}
if (!seen.hasOwnProperty(curr.dataid)){ // hashset
total.push(curr);
seen[curr.id] = 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);
@mikeyiss
Copy link

mikeyiss commented Jul 28, 2018

Getting the following error. How do I fix?

listen.js:1181 Uncaught gx {message: “Error in protected function: copy is not defined”, Osb: true, cause: ReferenceError: copy is not defined
at songsToText (:87:1)
at :130:1
…, stack: “ReferenceError: copy is not defined↵ at songsTo…0d8e628d55fb15f8053919494d29a/listen.js:1180:471)“}

@zhenyanevis
Copy link

zhenyanevis commented Dec 1, 2019

Thank you a lot! You made my day!

@slumtrimpet
Copy link

slumtrimpet commented Apr 3, 2020

Try https://musconv.com/ for export of the Google Music across many music platforms.

Actually don't this company spams the world with single-use throw away accounts to push their paid service.

@iozhukau
Copy link

iozhukau commented May 17, 2020

Thanks so much for the script!!! It best solution!!!

Maybe you can replace allsongs[i].{spmething} on some like that:

getSongAttribute(i,'artist')

var getSongAttribute = function(index, attribute) {
  return allsongs[index][attribute].replace('\n','').replace('\r','').trim()
}

Because now there are escape characters and it brokes csv.

@diegogteixeira
Copy link

diegogteixeira commented Jul 12, 2020

Thank you very much!

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