Skip to content

Instantly share code, notes, and snippets.

@fattredd
Last active May 18, 2020 23:46
Show Gist options
  • Save fattredd/8b41590dfaf4bd819fa92dea0df216d2 to your computer and use it in GitHub Desktop.
Save fattredd/8b41590dfaf4bd819fa92dea0df216d2 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
This is a little script to download every song from every playlist
if your Google Play Music account. Songs are organized as follows:
<playlist>/<artist>/<album>/<song>.mp3
I Highly recomend putting this file in your %USER%\Music folder
before running.
Please note that this will ONLY work if you have a subscription.
Requirements:
- gmusicapi
- requests
For further documentation on what I'm using here, check out:
http://unofficial-google-music-api.readthedocs.io/en/latest/reference/mobileclient.html
'''
from gmusicapi import Mobileclient
import requests
import sys, os, unicodedata
reload(sys)
sys.setdefaultencoding('ISO-8859-1')
# Account settings
username = ""
password = "" # App-specific passwords work here too
# Playlist settings
## Export as...
m3u = True
winamp = False
rootPath = "" # Playlists can require abs. paths. Default is current dir
if rootPath == "":
rootPath = os.path.realpath('.')
# Here thar be dragons
# Start with some declarations
def dlSong(id, name):
url = mc.get_stream_url(id, device_id=device_id)
r = requests.get(url, stream=True)
with open(name, "wb") as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
class Playlist(object):
def __init__(self, name):
name = self.clean(name)
self.name = name
self.path = name
self.songs = []
def __repr__(self):
return "{}: {} songs".format(self.name, len(self.songs))
def clean(self, string):
badchars = "<>:\"/|?*"
string = unicode(string)
string = unicodedata.normalize('NFKD', string).encode('ascii','ignore')
for i in badchars:
string = string.replace(i,"")
return string
def addSong(self, song):
self.songs.append(song)
def makePath(self, song):
path = os.path.join(rootPath, song.artist, song.album)
song.path = self.clean(path)
try:
os.makedirs(song.path)
except:
pass
def songPath(self, song):
self.makePath(song)
return os.path.join(song.path, song.title + ".mp3")
class Song(object):
def __init__(self, tid, title, artist, album, length):
self.tid = self.clean(tid)
self.title = self.clean(title)
self.artist = self.clean(artist)
self.album = self.clean(album)
self.length = length
def __repr__(self):
return "{} - {}".format(self.artist, self.title)
def clean(self, string):
badchars = "<>:\"/|?*"
string = unicode(string)
string = unicodedata.normalize('NFKD', string).encode('ascii','ignore')
for i in badchars:
string = string.replace(i,"")
return string
# Login
mc = Mobileclient()
mc.__init__(debug_logging=False, validate=True, verify_ssl=True)
mc.login(username, password, mc.FROM_MAC_ADDRESS)
# Pick a device_id for downloading later
device_id = mc.get_registered_devices()[0]['id'][2:].encode('ascii','ignore')
# Grab all playlists, and sort them into a structure
playlists = mc.get_all_user_playlist_contents()
print len(playlists), "found."
master = []
for ply in playlists:
name = ply['name']
curPlaylist = Playlist(name)
tracks = ply['tracks']
for song in tracks:
if song['source'] == u"2": # If song is not custom upload
tid = song['trackId']
title = song['track']['title']
artist = song['track']['artist']
album = song['track']['album']
length = int(song['track']['durationMillis']) / 1000
newSong = Song(tid, title, artist, album, length)
curPlaylist.addSong(newSong)
master.append(curPlaylist)
# Step through the playlists and download songs
for playlist in master:
print "Grabbing", playlist
for song in playlist.songs:
path = playlist.songPath(song)
if not os.path.isfile(path): # Skip existing songs
dlSong(song.tid, path)
# Deal with playlists
if m3u:
for playlist in master:
fname = playlist.name + ".m3u"
with open(fname, "w+") as f:
f.write("#EXTM3U\n")
for song in playlist.songs:
meta = "#EXTINF:{},{}".format(song.length, song)
path = os.path.join(rootPath, playlist.songPath(song))
f.write(meta + "\n")
f.write(path + "\n")
if winamp:
for playlist in master:
fname = playlist.name + ".pls"
with open(fname, "w+") as f:
f.write("[playlist]")
for i, song in enumerate(playlist.songs):
path = os.path.join(rootPath, playlist.songPath(song))
f.write("File{}={}\n".format(i, path))
f.write("Title{}={}\n".format(i, song.title))
f.write("Length{}={}\n".format(i, song.length))
f.write("NumberOfEntries={}".format(len(playlist.songs)))
f.write("Version=2")
@Walkman100
Copy link

Hi, I've made a few fixes and changes to your script, you can find it here: https://github.com/Walkman100/GPMplaylistDL

  • most importantly, fixed the device_id pick - it has to be an android or iOS ID - see simon-weber/gmusicapi#590 (comment) and on
  • fixed cleaning the path after it is generated - currently, your script will download to homeuserMusicArtistAlbum/Track.mp3 in the current directory because it's stripping characters after generating the full path (see Walkman100/GPMplaylistDL@8d87491)
  • added more options at the top: show songs as they are downloaded, quiet mode, and a character to replace invalid chars with
  • added a python3 version of the script - no more Unicode BS yay

@fattredd
Copy link
Author

I only just saw this haha. Great stuff! thank you!

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