Instantly share code, notes, and snippets.

Embed
What would you like to do?
Unportify helps you export your Google Play Music playlists.
/*
Unportify is a script that exports your Google Play music to text.
Copyright (C) 2016 Arnau Villoslada
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Copy of License:
https://www.gnu.org/licenses/gpl-3.0.html
*/
/*
HOW TO USE (Chrome):
1. Go to Google Play Music
2. Once loaded, right click anywhere in the page and left click on "Inspect" (or press CTRL + SHIFT + I)
3. Go to the "Console" tab.
4. There's a little dropdown, select "top".
5. Paste this script next to the ">" and press enter.
6. You can now close the console. (Click the X at the right or press CTRL + SHIFT + I).
7. Go to the playlist you want to export.
8. Press 0 to start the export process.
If pressing 0 does nothing, reload the page and make sure you you inject the code on "top" (step #4).
9. Follow on-screen instructions.
10. Once it's done, a file containing your playlist will download, paste it here:
https://labs.villoslada.me/unportify.
NOTE: If the number of catched songs is lower than the number of songs in your playlist,
you might have duplicates, if not, try lowering the scroll speed.
*/
/* Pre-start variables */
unportify_url = 'https://labs.villoslada.me/unportify';
message = {};
message['startup-1'] = 'Unportify v1.4.3 injected, you can now close this window.';
message['startup-2'] = 'Press 0 at any time to start exporting your playlist.';
message['albums'] = 'Would you like to also export the albums? \n(Not needed for Unportify importer)';
message['scroll'] = 'How fast would you like me to scroll? (Leave at default if you\'re not sure).\n\
Lower if it\'s not catching the entire playlist', '100';
message['scroll-error'] = 'Please, only use numbers.\nDefault should work fine on a average speed Internet connection';
message['scroll-startup'] = 'I\'m going to start scrolling, I\'ll warn you when I\'m done.\nPlease, don\'t switch tabs.';
message['open-unportify'] = 'Would you like to import this to Spotify? \n(Opens ' + unportify_url + ')';
console.log(message['startup-1']);
alert(message['startup-2']);
// Sets the starting variables
function startingVariables() {
// JSON containing playlist
playlist_string = '{"tracks": [';
// Google Music uses a main container to scroll instead of the browser window, get it.
main_container = document.querySelector("#mainContainer");
// Ask if the user would like to export albums
export_albums = confirm(message['albums']);
// This function keeps asking for the scroll speed until a number is given
function askAutoScrollSpeed() {
auto_scroll_speed = prompt(message['scroll'], '100');
if (isNaN(parseInt(auto_scroll_speed))) {
alert(message['scroll-error']);
askAutoScrollSpeed();
}
}
askAutoScrollSpeed();
// Scroll margin. Used to make sure the script detects that the bottom of the page has been reached.
scroll_margin = 50;
alert(message['scroll-startup']);
// Set the scroll speed to the user's desired speed
auto_scroll_speed = parseInt(auto_scroll_speed);
// Go to the top of the playlist
main_container.scrollTop = 0;
// Set the starting auto scroll position
auto_scroll_pos = main_container.scrollTop;
}
// Gets all of the loaded songs from the playlist
function getSongs() {
var i, j, playlist_div = document.querySelectorAll('.song-row');
for (i = 0, j = playlist_div.length; i < j; i++) {
var track = playlist_div[i];
var title = encodeURI(track.querySelector('[data-col="title"] span.column-content').textContent);
var artist = encodeURI(track.querySelector('[data-col="artist"] span.column-content').textContent);
var album = encodeURI(track.querySelector('[data-col="album"] span.column-content').textContent);
// Clean possible characters that could mess up the JSON
title = title.replace(/"/g, '\\"');
artist = artist.replace(/"/g, '\\"');
album = album.replace(/"/g, '\\"');
// Checks if the album needs to be exported
if (export_albums) {
var song = ('{"artist": "' + artist + '", "title": "' + title + '", "album": "' + album + '"},');
} else {
var song = ('{"artist": "' + artist + '", "title": "' + title + '"},');
}
// Only add if the song isn't already added the list
if (playlist_string.includes(song) != true) {
playlist_string += song;
}
// Will contain the last processed song
last = track;
}
}
// As Trump would say, we will do many many things, and it will be great.
function scrollAction() {
var last_song_pos = last.offsetTop;
var window_scroll_pos = main_container.scrollTop;
// Checks if we reached the last loaded song
if(window_scroll_pos >= last_song_pos) {
getSongs();
}
var limit = main_container.scrollHeight - main_container.offsetHeight - scroll_margin;
// Checks if we arrived at the bottom of the playlist
if (window_scroll_pos >= limit) {
// Stops the page scroll
clearTimeout(scroll_delay);
// Get the playlist name
var playlist_title = document.querySelector('h2[slot="title"]').textContent;
// Remove theh last ,
playlist_string = playlist_string.replace(/.$/, '');
// Close JSON.
playlist_string += '], "playlist": "' + playlist_title + '", "exported_by": "Unportify"}';
// Convert to human readable JSON
playlist_string = JSON.stringify(JSON.parse(playlist_string), null, 4);
// Exports the playlist
alert('Catched ' + (playlist_string.match(/"artist":/g) || []).length + ' songs.');
var blob = new Blob([playlist_string], {type: 'text/plain;charset=utf-8'});
saveAs(blob, playlist_title + '.txt');
if (confirm(message['open-unportify'])) {
window.open(unportify_url, '_blank');
}
// Awaits for the user to press the start key again
window.onkeypress = function(event){startExport(event)}
}
}
// This funcion makes the page scroll automatically.
function pageScroll() {
main_container.scrollTop = auto_scroll_pos;
scroll_delay = setTimeout(pageScroll,10);
scrollAction();
auto_scroll_pos = auto_scroll_pos + auto_scroll_speed;
}
// Starts everything when the user presses the start key.
function startExport(event) {
if (event.keyCode == 48) {
window.onkeypress = null;
startingVariables();
getSongs();
pageScroll();
}
}
window.onkeypress = function(event){startExport(event)};
/* Everything below is not my code */
// Used to save the playlists in a file.
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/constructor/i.test(e.HTMLElement),f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},d="application/octet-stream",s=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,s)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(i){u(i)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,s){if(!s){t=p(t)}var v=this,w=t.type,m=w===d,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&a)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;i(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define([],function(){return saveAs})}
@rwagner00

This comment has been minimized.

rwagner00 commented Apr 11, 2017

The script works correctly and catches the right number of songs, but whenever I attempt to import the data I get the error "Wrong Format".

Github won't let me attach the text file for some reason, but you can view it at https://pastebin.com/aaJG3yfx.

Any thoughts on this one?

@TheDegree0

This comment has been minimized.

Owner

TheDegree0 commented Apr 15, 2017

@rwagner00 There's some format problems with the file that's generated by this script, to fix this, copy it from a source that removes the formatting, like pastebin. When copying from there, I was able to import your playlist: https://open.spotify.com/user/arnauvp/playlist/4AHDzaX2OookmrmGb1EZEl

Tracks that were not found: https://pastebin.com/qYy8HLdP

Unfortunately I cannot keep updating this script since my Google Play Music subscription has expired (I'm using Spotify). Any edits to the script are welcome!

@tadly

This comment has been minimized.

tadly commented Apr 26, 2017

@TheDegree0
"location" of the playlist title seems to has changed.
var playlist_title = document.querySelector('[slot="title"]').textContent;
That's working for me, thought I'd let you know

@ralphhogaboom

This comment has been minimized.

ralphhogaboom commented Jun 27, 2017

This is fantastic - works perfectly. Bravo!

@TheDegree0

This comment has been minimized.

Owner

TheDegree0 commented Jul 30, 2017

@tadly Hey, thank you, updated the gist.
@ralphhogaboom I'm glad it helped!

@se-bastiaan

This comment has been minimized.

se-bastiaan commented Aug 1, 2017

Awesome script. I'm possibly migrating from Play Music to Spotify and just used the latest version to make an export.
Since I wanted to migrate everything I have using the songs tab I just changed line 145 a bit:

var playlist_title = "Songs"
if (document.querySelector('h2[slot="title"]') != null) {
    playlist_title = document.querySelector('h2[slot="title"]').textContent;
}

Just leaving this as note for anyone who wants to do the same :)

@sebhaugeto

This comment has been minimized.

sebhaugeto commented Sep 7, 2017

It worked flawlessly! Thanks man, you spared me a lot of time

@thisismydesign

This comment has been minimized.

thisismydesign commented Oct 8, 2017

Thanks for making this.

As @se-bastiaan pointed out it doesn't work on this link: https://play.google.com/music/listen?u=0#/all

For future reference this is the error:
TypeError: Cannot read property 'textContent' of null at scrollAction (<anonymous>:145:72) at pageScroll (<anonymous>:175:5) at c (https://play-music.gstatic.com/fe/5fe97bf3f03d8f910c21eadc88b3532e/listen.js:1169:261)

The fix by @se-bastiaan also works. Would you mind incorporating it into your script?

@x3rAx

This comment has been minimized.

x3rAx commented Nov 11, 2017

For some reason it does not work when I'm pressing "0" but calling

startExport({keyCode: 48})

in the console window works fine ;)


I don't know why but when pressing "0", it seems that no KeyboardEvent is passed to the startExport() method. When adding a console.log() to the startExport() method, I can see KeyboardEvents for other keys like "u". But "p" for example is opening the playlists menu in Google Music and does not output a log message. I think Google is grabbing and handling the "0" before Unportify can do.

@x3rAx

This comment has been minimized.

x3rAx commented Nov 11, 2017

Oh and by the way, MANY THANKS FOR YOUR EFFORT! ♥

@Diolor

This comment has been minimized.

Diolor commented Dec 6, 2017

Ctrl + 0 also works

@TENsaga

This comment has been minimized.

TENsaga commented Jan 12, 2018

@TheDegree0 thank you so much for putting this together, wanted to switch a few years ago but was dreading rebuilding some of these playlists.

@x3rAx thank you, that worked or me.

@StanfordLin

This comment has been minimized.

StanfordLin commented Feb 13, 2018

Thank you,

For some reason Ctrl + 0 worked but just pressing 0 didn't haha

@mikee1999

This comment has been minimized.

mikee1999 commented Feb 27, 2018

Does it still work having some issues.

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