Exporting Google PlayMusic playlists to Spotify using Python libraries for their web APIs

[TL;DR: in case you are familiar enough with python and know enough about enough, and just need to get it done: heres the github repo: Github: PlayMusic2Spotify. read the readme and its simple enough to figure out quicker than reading this post. ]



I had been using Google play Music for quite some time now, in spite of it unavoidably badgering me to consume more not-personalised content despite me having not shown any interest towards "Dilbar Dilbar" etc., Nonetheless, it's relatively cheap, at 99rs/month (1.5$ USD) one can hardly complain. 
 Consequently I ended up assorting some rather enjoyable playlists on it, either manually or indirectly using the commendable "Start Radio" feature of Play Music, like Spotify's "Play Shuffle" but better.

This was a problem when I finally decided to make the switch to Spotify, if only there was a way to port my library and playlists...


I couldn't really find any service online that does this, not free of charge at least,  thus after some considerable procrastination I decided to write some scripts to get the job done. Here's how I did it:


 
If you have some basic familiarity with programming in Python then you shall be able to follow the following tutorial with relative ease and have your playlists/library transferred in about 15-30mins, if not you could still try, I've done my best to make this as accessible as possible.

Step 1: Setup

install python 3.0 the required python packages

Here's how to get python.
How to get pip on windows (used to get python packages).

You can google the same for mac if you're using an apple PC. If you're using linux {masterrace} you probably can just skip these steps anyway.
 Here's how to use pip.

I had to install the following  packages for this task:

1) Spotipy : the official Python library for the Spotify Web API.
        PypI: https://pypi.org/project/spotipy/
        Docs: https://spotipy.readthedocs.io/en/latest/


2) gmusicapi : an unofficial API for Google play music  
(and the probable cause for why this blog/code is outdated if you're reading this sometime in the future.)

Step 2: Getting the Playlists'/Library's info from Google Play Music

 Now the first step is pretty straight forward, we need to query the gmusicAPI and get the list of songs in each playlist, after we authenticate ofcourse.

Here's the code, save this as a something.py file and run it (google how to run a python script or how to python if you need to), it will create a folder called "My playlists" with some files in it in the same directory as this file. more on those files later. 

Note: if your playlist names have some non-ascii (non-english perhaps?) characters, you may want to remove the last of the three "print" statements from the code.

from gmusicapi import Mobileclient as Mc
import csv
import os

api=Mc()
api.oauth_login(api.FROM_MAC_ADDRESS,api.perform_oauth())

lib=api.get_all_songs()
plsts=api.get_all_user_playlist_contents()
thumbs=[]
for i in lib:
    if ('rating' in i.keys()) and (i['rating']=='5'):
        thumbs.append(i)

if 'My playlists' not in os.listdir():
    os.makedirs('My playlists')

def writeP2F(P,F):
    songs=[]
    for i in P:
        songs.append([i['artist'],i['title'],i['album'],i['year']])
    csv.writer(F).writerows(songs)

print("\n\t\t...writing \"Thumbs Up\"...")
with open("My playlists/Thumbs Up",'w', encoding='utf-8') as f:
    writeP2F(thumbs,f)
    f.close()

print("\n\t\t...writing \"All Songs\" (\\\"Last Added\")...")
with open("My playlists/All Songs",'w', encoding='utf-8') as f:
    writeP2F(lib,f)
    f.close()

for plst in plsts:
    print("\n\t\t...writing \""+plst['name']+"\"...")
    tracks=[]
    for i in plst['tracks']:
        if i['source']=='2':
            tracks.append(i['track'])
    with open("My playlists/"+plst['name'], 'w', encoding='utf-8') as f:
        writeP2F(tracks,f)
        f.close()

Explaination:

api.oauth_login(api.FROM_MAC_ADDRESS,api.perform_oauth())

The above line does the authentication, it will give you a google.something link to open up in a browser tab to authenticate you.
All you need to do is copy the code displayed on the screen after performing the authorization and paste it back in the terminal / commandline(?)


lib=api.get_all_songs()
plsts=api.get_all_user_playlist_contents()
 The above two lines are pretty self explanatory

Lib gets a list of songs, each song is a dictionary object containing various details about it. You can find more on this in the documentation of the package.

I am storing all songs that have a '5' rating in thumbs[ ] list to recreate  the 'thumbs up' playlist that's auto generated by GooglePlayMusic as it cant be queried from the API directly. Refer to the docs of the gmusicapi for more clarification on this.

The writeP2F(P,F) function takes a list of songs and a file as parameters passed to it and writes the name me of the song, the artist, the album and the year in the file in a "My playlists" folder for each song in a comma seperated format.

this is done for all the playlists.

Step 3: Register an "app" on Spotify

go to the Dashboard page at the Spotify Developer website and, if necessary, log in. Accept the latest Developer Terms of Service to complete your account set up.

go to dashboard->applications    and click CREATE A CLIENT ID.
Enter Application Name and Application Description and then click CREATE. Your application is registered, and the app view opens.
Now Find your Client ID and Client Secret and copy paste them somewhere as you'll need them later.

This Client ID and Client Secret is used for any communication made with the spotify api as it then knows the requests are being made on behalf of whom.

Step 4: Recreating the playlists on spotify


Heres the code, add your own username, Client ID and Client Secret in it by replacing mine in the 6th, 7th  8th lines respectively. Then save it and run it.
Note: if your playlist names have some non-ascii (non-english perhaps?) characters, you may want to remove the last of the three "print" statements from the code.


import spotipy
import spotipy.util
import sys
import glob
import csv

user='31og2zbmvpbvzge26z36ctv2bqz4' #put your username here, remove mine
cliId='52fae4c7265a4f0fbfaf15291107c982' #put your client id here
cliSc='28281faedc0249d2a90123502be951be' #put your client secret here


def authenticate():
    sp=spotipy.Spotify(spotipy.util.prompt_for_user_token(
        user,
        client_id=cliId,
        client_secret=cliSc,
        scope='playlist-modify-private',
        redirect_uri='http://google.com/'))
    return sp
#print(sp.me())

def getupto1000(q,t,sp): #returns a list of 1000 results for a search query q of type t
    off=0
    try:
        res=sp.search(q=q,type=t,limit=50,offset=off)
    except:
        sp=authenticate()
        res=sp.search(q=q,type=t,limit=50,offset=off)
    list_res=(res[t+'s']['items'])
    while(res[t+'s']['next']!=None and off<950):
        off=off+50
        try:
            res=sp.search(q=q,type=t,limit=50,offset=off)
        except:
            sp=authenticate()
            res=sp.search(q=q,type=t,limit=50,offset=off)
        list_res=list_res+res[t+'s']['items']
    return list_res

def getSpotifySongIdsFromCSVPlaylist(Playlist,sp): # "Playlist" argument= path/name of csv file containing songs of a certain playlist
    songs_list=[]
    with open(Playlist,'r', encoding='utf-8') as f:
            songs_list=list(csv.reader(f))
            f.close()
    if songs_list==[]:
        print("FILE EMPTY---------------")
        return []
    songs_found=[]
    no_matches=0
    multiple_matches=0
    exact_match=0
    counter=0
    for song in songs_list:
        counter +=1
        print("\r#"+str(counter))
        #print("\nSONGS FOUND IN THIS PLAYLIST SO FAR:"+str(len(songs_found)))
        #print("==============Finding a match for:\n\t\""+song[1]+"\" by \""+song[0]+"\"")
        try:
            res_sngs=getupto1000(q=song[1],t='track',sp=sp) #results when searching for tracks by song name
            res_albms=getupto1000(q=song[2],t='album',sp=sp) #results when searching for albums by album name
            res_artsts=[getupto1000(q=i,t='artist',sp=sp) for i in song[0].split(' & ')] #results when searching for artists by artist name
        except:
            print("EXCEPTION:No match for: \""+song[1]+"\"\t FROM \""+song[2]+"\"\t BY \""+song[0])
            sp=authenticate()
            continue
        #check for each song in res_sngs, if there exists a song that is from an ablum that is in res_albms,
        # .. if found then check if any artist from that is in res_artists, if so then add that song to songs_found.
        full_match=[]
        nameNalbum_match=[]
        nameNartist_match=[]
        for sng in res_sngs:
            if sng['album']['id'] in [albm['id'] for albm in res_albms]:
                nameNalbum_match.append(sng['id'])
                for sng_artst in [x['id'] for x in sng['artists']]:
                    if sng_artst in [x['id'] for i in res_artsts for x in i]:
                        full_match.append(sng['id'])
        #print("\nFull Match:"+str(len(full_match))+"\tAlbum Match:"+str(len(nameNalbum_match)))
        if(len(full_match)==1):
            exact_match+=1
        else:
            multiple_matches+=1
        if full_match!=[] and len(full_match)<10:
            songs_found=songs_found+full_match
            continue
        if nameNalbum_match!=[] and len(nameNalbum_match)<4:
            songs_found=songs_found+nameNalbum_match
            continue
        for sng in res_sngs:
            for sng_artst in [x['id'] for x in sng['artists']]:
                if sng_artst in [x['id'] for i in res_artsts for x in i]:
                    nameNartist_match.append(sng['id'])
        #print("\nArtist Match:"+str(len(nameNartist_match)))
        if nameNartist_match!=[] and len(nameNartist_match)<4:
            songs_found=songs_found+nameNartist_match
            continue
        if res_sngs!=[] and len(res_sngs)<=2:
            songs_found=songs_found+res_sngs
            continue
        #print("No match for: \""+song[1]+"\"\t FROM \""+song[2]+"\"\t BY \""+song[0])
        multiple_matches-=1
        no_matches+=1
    return {'no_matches':no_matches,'multiple_matches':multiple_matches,'exact_match':exact_match,'res': list(set(songs_found))}

def uploadPlaylist2Spotify(playlist,songs,sp):
    try:
        plst_id=sp.user_playlist_create(user,name=playlist.split('/',1)[1]+" (IMPORTD4mGPM)",public=False)['id']
    except:
        try:
            sp=authenticate()
            plst_id=sp.user_playlist_create(user,name=playlist.split('/',1)[1],public=False)['id']
        except:
            return
    off=0
    L=len(songs)
    while(off<L):
        try:
            sp.user_playlist_add_tracks(user,plst_id,songs[off:((off+100) if (off+100)<L else L)])
        except:
            pass
        off=off+100

sp=authenticate()
playlists=[x for x in glob.glob('My playlists/**')]
for playlist in playlists:
    print("###############CURRENT PLAYLIST="+playlist.split('/',1)[1]+"###############")
    recreate = getSpotifySongIdsFromCSVPlaylist(playlist,sp)
    n=recreate['no_matches']
    e=recreate['multiple_matches']
    m=recreate['exact_match']
    songs_found=recreate['res']
    print("Total Songs in Playlist:"+str(n+e+m)+"\nExact matches found for:"+str(e)+" songs\nNo matches found for:"+str(n)+" songs")
    uploadPlaylist2Spotify(playlist=playlist,songs=songs_found,sp=sp)


Explaination:

Heres the slighly tricky bit.

Now we need to find a match for each song for each playlist based on the information we collected earlier.

I do this by making the script do for each song of each playlist the following: 
Querying the song name, the artists, and the album and storing these results.
then checking if there is any song that comes up in each search, ie if any song is present in each result, ie the name, artists and album all match, if so then add this song(s) as a match to be added to the playlist.
if not then add the song(s) for which the name and artists matched,  else name and album else just name (as long as it's less than 3 supposed "matches" for that song).
if no song satisfies this criteria, no match is found for that song.

The variable and function nomenclature is pretty explanatory as well and you shall have no trouble following it.

After running this script, all the playlists are recreated on spotify with an accuracy of more than 80% which is close enough I suppose. The false matches aren't entirely arbitrary anyway. 












Do drop a comment if there is any clarification needed for the codes of if you encountered any issues while following this, i'll try to get back asap. :) (y)


Comments

  1. William Hill Betting Locations | Mapyro
    Find William Hill sports https://jancasino.com/review/merit-casino/ betting 출장마사지 locations in Maryland, casino-roll.com West Virginia, sol.edu.kg Indiana, Pennsylvania, South Dakota, nba매니아 West Virginia and more. BetRivers.com.

    ReplyDelete

Post a Comment

Popular posts from this blog

Basic Statistical Analysis of CGPA Data of VIT Students using Python