Created
January 20, 2017 00:03
-
-
Save nukemberg/ad704a7bd101c82a9848d46f595664d7 to your computer and use it in GitHub Desktop.
Deezer playlist export -> Google Music import
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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