Skip to content

Instantly share code, notes, and snippets.

@nukemberg
Created January 20, 2017 00:03
Show Gist options
  • Save nukemberg/ad704a7bd101c82a9848d46f595664d7 to your computer and use it in GitHub Desktop.
Save nukemberg/ad704a7bd101c82a9848d46f595664d7 to your computer and use it in GitHub Desktop.
Deezer playlist export -> Google Music import
#!/usr/bin/env python3
from gmusicapi.clients import Mobileclient
import click
import requests
import re
from itertools import tee, filterfalse
from pprint import pprint
from concurrent.futures import ThreadPoolExecutor
GMUSIC_CREDENTIALS = {
'email': '',
'password': ''
}
def partition(pred, iterable):
'Use a predicate to partition entries into false entries and true entries'
# partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)
def find(predicate, iterable, default=None):
'Find the first item in an iterable for which a predicate is true, return `default` if nothing found'
return next((item for item in iterable if predicate(item)), default)
def read_deezer_playlist(playlist_id):
res = requests.get('https://api.deezer.com/playlist/' + str(playlist_id))
return [track for track in res.json()['tracks']['data']]
def normalize(x):
'Clean up a string'
x = re.sub('\s?[fF]eat(\.|uring)\s.*$', '', x)
x = re.sub('[.\',;_"-]*', '', x) # remove commas, dots, quotes, etc
x = re.sub('\s+', ' ', x) # remove spaces, tabs etc
return x.strip().lower() # convert to lowercase
def best_match(results, track_name, artist_name, album_name):
def match(x, y):
# we don't use == but rather in-string because there may be prefixes or suffixes
return normalize(x) in normalize(y)
def match_song(song):
return match(artist_name, song['track']['artist']) and \
match(track_name, song['track']['title']) and \
match(album_name, song['track']['album']) \
return find(match_song, results)
def search_gmusic(gmusic, track_name, artist_name, album_name):
'Search Google Music a matching track and try to select a "matching" result'
res = gmusic.search('{} {} {}'.format(artist_name, album_name, track_name))
try:
if len(res['song_hits']) == 0: # no results found
return None
else: # Google may tag a certain result with "best_result"
best_result = find(lambda t: 'best_result' in t and t['best_result'], res['song_hits'])
if best_result:
return best_result
else: # otherwise, use our own matching logic
return best_match(res['song_hits'], track_name, artist_name, album_name)
except Exception:
pprint('--exception---', res['song_hits'])
raise
def populate_playlist(gmusic, name, songs):
'Create a Google Music playlist and populate it with songs'
playlist_id = gmusic.create_playlist(name, 'import from Deezer')
gmusic.add_songs_to_playlist(playlist_id, [song['track']['nid'] for song in songs])
@click.group()
def cli():
pass
@cli.command()
@click.option('--deezer-playlist-id', '-d', help='Deezer playlist ID', type=int, required=True)
@click.option('--playlist-name', '-p', type=str, required=True, help='Google Music playlist name')
def deezer(deezer_playlist_id, playlist_name):
gmusic = Mobileclient()
gmusic.login(GMUSIC_CREDENTIALS['email'], GMUSIC_CREDENTIALS['password'], Mobileclient.FROM_MAC_ADDRESS)
deezer_playlist = read_deezer_playlist(deezer_playlist_id)
def _search_track(track):
'Run search on a track and return a pair (original_track, found_track)'
return (track, search_gmusic(gmusic, track['title_short'], track['artist']['name'], track['album']['title']))
# the following bit runs in parallel because search can be slow
with ThreadPoolExecutor(max_workers=6) as executor:
results = executor.map(_search_track, deezer_playlist)
not_found, found = partition(lambda t: t[1], results)
populate_playlist(gmusic, playlist_name, (t[1] for t in found))
# print not found tracks
not_found = list(not_found) # not found is a lazy iterator, convert to list for printing
print('===not found=== ({}/{})'.format(len(not_found), len(deezer_playlist)))
pprint([{'artist': t[0]['artist']['name'], 'album': t[0]['album']['title'], 'title': t[0]['title_short']} for t in not_found])
@cli.command()
@click.argument('artist')
@click.argument('album')
@click.argument('title')
def search(artist, album, title):
gmusic = Mobileclient()
gmusic.login(GMUSIC_CREDENTIALS['email'], GMUSIC_CREDENTIALS['password'], Mobileclient.FROM_MAC_ADDRESS)
res = gmusic.search('{} {} {}'.format(artist, album, title))
pprint(res)
if __name__ == '__main__':
cli()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment