// 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); |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
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! |
This comment has been minimized.
This comment has been minimized.
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. Also, I just noticed that the deduplication logic has a bug on the first pass. I'll fix that as well. |
This comment has been minimized.
This comment has been minimized.
There seems to be no way to link to the diff of a Gist revision (?), so this is the best I can do:
|
This comment has been minimized.
This comment has been minimized.
This is excellent! |
This comment has been minimized.
This comment has been minimized.
Is this the right place to open an issue? |
This comment has been minimized.
This comment has been minimized.
@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. |
This comment has been minimized.
This comment has been minimized.
Thank you for this! |
This comment has been minimized.
This comment has been minimized.
You are a god |
This comment has been minimized.
This comment has been minimized.
This is super! |
This comment has been minimized.
This comment has been minimized.
Never mind, it's there in the CSV export. |
This comment has been minimized.
This comment has been minimized.
The iron-scrolling-like breaks this badly |
This comment has been minimized.
This comment has been minimized.
This doesn't work anymore. I guess it needs few fixes |
This comment has been minimized.
This comment has been minimized.
It still works for me...? If you have more details please send me the error message/screenshot. |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
This worked well for me. |
This comment has been minimized.
This comment has been minimized.
Worked like a charm, thank you very much. Was also able to export playlists by executing the script while viewing them. |
This comment has been minimized.
This comment has been minimized.
I just tried this on Chrome 57.0.2987.133 (64-bit) - Windows 10 and got this error:
|
This comment has been minimized.
This comment has been minimized.
@itm1960 Just worked for me. Windows 10 / Chrome 57.0.2987.133 (64-bit) |
This comment has been minimized.
This comment has been minimized.
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) |
This comment has been minimized.
This comment has been minimized.
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!" |
This comment has been minimized.
This comment has been minimized.
Wow. Just googled for a luck and here it is. Thanks! |
This comment has been minimized.
This comment has been minimized.
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 |
This comment has been minimized.
This comment has been minimized.
How can I skip saving the album name? |
This comment has been minimized.
This comment has been minimized.
Thank you for this, lastfm is the past now for me! |
This comment has been minimized.
This comment has been minimized.
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). |
This comment has been minimized.
This comment has been minimized.
Alternatively, you can get the information from the db created by the google music. ` ` |
This comment has been minimized.
This comment has been minimized.
Can anyone make this into a Chrome Extension? |
This comment has been minimized.
This comment has been minimized.
Ok, I made it into an extension for Chrome/Opera After you have loaded the extension, you need to REFRESH the google music page (F5) |
This comment has been minimized.
This comment has been minimized.
Here's the easy copy paste version freshly tested :
|
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
This worked for me, thanks! http://www.playlist-converter.net was then able to (mostly) successfully import the playlist's into Spotify. |
This comment has been minimized.
This comment has been minimized.
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 : 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 |
This comment has been minimized.
This comment has been minimized.
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) |
This comment has been minimized.
This comment has been minimized.
Works greatI Sure do appreciate it! |
This comment has been minimized.
This comment has been minimized.
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? |
This comment has been minimized.
This comment has been minimized.
Thx it works! |
This comment has been minimized.
This comment has been minimized.
Just waned to say thanks! This has been tremendously helpfu! |
This comment has been minimized.
This comment has been minimized.
Thanks a lot for this snippet! It worked marvellously for me! |
This comment has been minimized.
This comment has been minimized.
This is fantastic, thank you so much! Is it possible to capture the Date Added column in a Last Added auto-playlist? |
This comment has been minimized.
This comment has been minimized.
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.
|
This comment has been minimized.
This comment has been minimized.
Is there a similar way to do this with YouTube Music? |
This comment has been minimized.
This comment has been minimized.
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 ? |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
@acdcsteve That's disappointing... my Google Takeout for Google Play Music contains a Do your exported playlists maybe contain the song's youtube links? Then you could easily write a script that downloads them all for you. |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
Thank you for this script. It still works great as of today. |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
@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 |
This comment has been minimized.
This comment has been minimized.
Oh, I didn't know it could include the actual music tracks, I'll give that a whirl, thank you :) |
This comment has been minimized.
Suggest to change
your variant gives me
Yelawolf - Love Story - !
Best Friend (feat. Eminem)
proposed is plain
Yelawolf - Love Story - Best Friend (feat. Eminem)