Skip to content

Instantly share code, notes, and snippets.

@avillp
Last active November 12, 2023 15:22
Show Gist options
  • Save avillp/29137b5abf7be44dd46e29ad8ff39e2e to your computer and use it in GitHub Desktop.
Save avillp/29137b5abf7be44dd46e29ad8ff39e2e to your computer and use it in GitHub Desktop.
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})}
@tadly
Copy link

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
Copy link

This is fantastic - works perfectly. Bravo!

@avillp
Copy link
Author

avillp commented Jul 30, 2017

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

@se-bastiaan
Copy link

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
Copy link

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

@thisismydesign
Copy link

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
Copy link

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
Copy link

x3rAx commented Nov 11, 2017

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

@Diolor
Copy link

Diolor commented Dec 6, 2017

Ctrl + 0 also works

@TENsaga
Copy link

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
Copy link

Thank you,

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

@mikee1999
Copy link

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