#!/usr/bin/env python3 from gmusicapi import Mobileclient import argparse import sys import requests import json import urllib.parse creds_path = sys.path[0] + '/mobileclient.cred' def authenticate(api): creds = api.perform_oauth(storage_filepath=creds_path, open_browser=False) def uploadLibrary(mudbase_api, mudbase_user, mudbase_password, tracks): # First, attempt to login and start a session. s = requests.Session() response = s.post(mudbase_api + '/login?username=' + urllib.parse.quote(mudbase_user) + '&password=' + urllib.parse.quote(mudbase_password)) if response.status_code != 200: print("Unable to log in to MuDBase API.") # Helpers def getArtistStoreIds(track): if 'artistId' in track: return [track['artistId'][0]] return [] def getTrackStoreIds(track): if 'storeId' in track: return [track['storeId']] return [] # Create GPM import tag gpmTagIdResponse = s.post(mudbase_api + '/tag', json={ 'name': 'GPM Import', 'mbApi_typename': 'tag', 'parentId': None, }).json() gpmTagId = gpmTagIdResponse['id'] print(f"Created tag \"GPM Import\", response: {gpmTagIdResponse}") # Create the root genre tag genreRootResponse = s.post(mudbase_api + '/tag', json={ 'name': 'Genre', 'mbApi_typename': 'tag', 'parentId': None, }).json() genreRootTagId = genreRootResponse['id'] print(f"Created tag \"Genre\", response: {genreRootResponse}") # For keeping track what we have already created. storedArtists = dict() storedAlbums = dict() storedGenreTags = dict() for track in tracks: # TODO: check if these items already exist # Determine artist properties. artist = { 'mbApi_typename': 'artist', 'name': track['artist'], 'storeLinks': ['https://play.google.com/music/m' + id for id in getArtistStoreIds(track)], 'tagIds': [gpmTagId] } if 'artist' in track else None # Upload artist if not already done artistId = None if artist: for key, value in storedArtists.items(): if value == artist: artistId = key break if not artistId: response = s.post(mudbase_api + '/artist', json=artist).json() artistId = response['id'] print( f"Created artist \"{artist['name']}\", response: {response}") storedArtists[artistId] = artist # Determine album properties. album = { 'mbApi_typename': 'album', 'name': track['album'], 'tagIds': [gpmTagId], 'artistIds': [artistId], } if 'album' in track else None # Determine genre properties. genre = { 'mbApi_typename': 'tag', 'name': track['genre'], 'parentId': genreRootTagId } if 'genre' in track else None # Upload album if not already done albumId = None if album: for key, value in storedAlbums.items(): if value == album: albumId = key break if not albumId: response = s.post(mudbase_api + '/album', json=album).json() albumId = response['id'] print( f"Created album \"{album['name']}\", response: {response}") storedAlbums[albumId] = album # Upload genre if not already done genreTagId = None if genre: for key, value in storedGenreTags.items(): if value == genre: genreTagId = key break if not genreTagId: response = s.post(mudbase_api + '/tag', json=genre).json() genreTagId = response['id'] print( f"Created genre tag \"Genre / {genre['name']}\", response: {response}") storedGenreTags[genreTagId] = genre # Upload the track itself tagIds = [gpmTagId] if genreTagId: tagIds.append(genreTagId) _track = { 'mbApi_typename': 'track', 'name': track['title'], 'artistIds': [artistId] if artistId != None else [], 'albumId': albumId if albumId else None, 'tagIds': tagIds, 'storeLinks': ['https://play.google.com/music/m/' + id for id in getTrackStoreIds(track)], } response = s.post(mudbase_api + '/track', json=_track).json() print( f"Created track \"{track['title']}\" with artist ID {artistId}, album ID {albumId}, response: {response}") def getData(api): return { "tracks": api.get_all_tracks(), "playlists": api.get_all_user_playlist_contents() } def getTracks(data): # Get tracks from library tracks = [] # data['tracks'] # Append tracks from playlists for playlist in data['playlists']: for track in playlist['tracks']: if 'track' in track: tracks.append(track['track']) # Uniquify by using a dict. After all, same track may appear in # multiple playlists. def sI(track): return track['artist'] + '-' + \ track['title'] if 'artist' in track and 'title' in track else 'z' return list(dict((sI(track), track) for track in tracks).values()) api = Mobileclient() parser = argparse.ArgumentParser( description="Import Google Music library into MudBase.") parser.add_argument( '--authenticate', help="Generate credentials for authentication", action="store_true") parser.add_argument('--store-to', help="Store GPM library to JSON for later upload", action='store', dest='store_to') parser.add_argument('--load-from', help="Load GPM library from JSON for upload", action='store', dest='load_from') parser.add_argument('--mudbase_api', help="Address for the Mudbase back-end API to upload to", action='store', dest='mudbase_api') parser.add_argument('--mudbase_user', help="Username for the Mudbase API", action='store', dest="mudbase_user") parser.add_argument('--mudbase_password', help="Password for the Mudbase API", action='store', dest="mudbase_password") args = parser.parse_args() if args.authenticate: authenticate(api) data = None # Determine whether we need to log in to GPM and get tracks if args.store_to or (not args.load_from and args.mudbase_api): api.oauth_login(Mobileclient.FROM_MAC_ADDRESS, oauth_credentials=creds_path) data = getData(api) # Determine whether to save to a file if args.store_to: with open(args.store_to, 'w') as outfile: json.dump(data, outfile, sort_keys=True, indent=2) # Determine whether to load from a file if args.load_from: with open(args.load_from, 'r') as f: data = json.load(f) tracks = getTracks(data) print(f"Found {len(tracks)} tracks.") if args.mudbase_api: api.oauth_login(Mobileclient.FROM_MAC_ADDRESS, oauth_credentials=creds_path) uploadLibrary(args.mudbase_api, args.mudbase_user, args.mudbase_password, tracks)