def main(): log.setLevel(logging.INFO) logging.getLogger('gmusicapi').setLevel(logging.INFO) cred_path = os.path.join(os.path.expanduser('~'), '.gmusic-sync') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' 'be found either. Try creating %s and specifying your ' 'username/password there. Make sure to chmod 600.' % cred_path) if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( 'Config file is not protected. Please run: ' 'chmod 600 %s' % cred_path) config = ConfigParser.ConfigParser() config.read(cred_path) src_user = config.get('src','username') src_pass = config.get('src','password') src_device = config.get('src','deviceid') dst_user = config.get('dst','username') dst_pass = config.get('dst','password') dst_device = config.get('dst','deviceid') if not src_user or not src_pass or not dst_user or not dst_pass: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not src_device or not dst_device: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) parser = argparse.ArgumentParser(description='gmusic-sync', add_help=False) parser.add_argument('-a', '--add-songs', help='Add songs from the src account to the dst account', action='store_true', dest='add') parser.add_argument('-h', '--heuristics', help='Use heuristics to try and find missing songs. Songs must match artist and title to be considered a match.', action='store_true', dest='heuristics') parser.add_argument('-hs', '--strict-heuristics', help='Songs must match artist, album, and title to be considered a match.', action='store_true', dest='strict_heuristics') args = parser.parse_args() api = Mobileclient() api.login(src_user, src_pass, src_device) library = api.get_all_songs() api2 = Mobileclient() api2.login(dst_user, dst_pass, dst_device) library2 = api2.get_all_songs() aa_songs2 = [] aa_diff = [] for track in library2: try: if track['storeId'].startswith('T'): aa_songs2.append(track['storeId']) except KeyError: continue for track in library: try: if track['storeId'].startswith('T') : if track['storeId'] not in aa_songs2: #aa_diff.append(track) if args.add: api2.add_aa_track(track['storeId']) time.sleep(1) # sleep so Google doesn't banhammer you for spamming their servers except KeyError: continue except: #Track wasn't found in the dst database. See if we can match to an existing song or something #else from the store if args.heuristics: if heuristic_search(library2, track, args.strict_heuristics) is not None: continue print 'ERROR ADDING: ' + track['title'] + ' - ' + track['artist'] + ' (' + track['album'] + ')' + ' (' + track['storeId'] + ')' aa_diff.append(track) continue print '----------------- TRACKS NOT IN DST --------------------' for track in aa_diff: print track['title'] + ' - ' + track['artist'] + ' (' + track['album'] + ')'
api.login("google-username", "google-password") def find_songs(artist, title, album): pos_results = api.search_all_access(title)["song_hits"] def_results = filter(lambda res : ((res["track"]["albumArtist"] == artist) and (res["track"]["album"] == album)), pos_results) return def_results def known_songids(): songs = api.get_all_songs() return map(lambda song: song["nid"] if ("nid" in song) else song["id"], songs) rdio_tracks = json.loads(open("tracks.json").read()) known_songs = known_songids() for track in rdio_tracks: songs = find_songs(track["artist"], track["title"], track["album"]) if ( len(songs) > 0) : song = songs[0] if (not (song["track"]["nid"] in known_songs)): api.add_aa_track(song["track"]["nid"]) print u"+ {} by {} from {}".format(track["title"], track["artist"], track["album"]) else: print u"Could not find {} by {} from {} in Play music".format(track["title"], track["artist"], track["album"])
class MusicSync(object): def __init__(self, email=None, password=None): self.mm = Musicmanager() self.wc = Webclient() self.mc = Mobileclient() if not email: email = raw_input("Email: ") if not password: password = getpass() self.email = email self.password = password self.logged_in = self.auth() print "Fetching playlists from Google..." self.playlists = self.wc.get_all_playlist_ids(auto=False) print "Got %d playlists." % len(self.playlists["user"]) print "" def auth(self): self.logged_in = self.wc.login(self.email, self.password) self.logged_in = self.mc.login(self.email, self.password) if not self.logged_in: print "Login failed..." exit() print "" print "Logged in as %s" % self.email print "" if not os.path.isfile(OAUTH_FILEPATH): print "First time login. Please follow the instructions below:" self.mm.perform_oauth() self.logged_in = self.mm.login() if not self.logged_in: print "OAuth failed... try deleting your %s file and trying again." % OAUTH_FILEPATH exit() print "Authenticated" print "" def add_rhapsody_playlist(self, filename, remove_missing=False): filename = self.get_platform_path(filename) os.chdir(os.path.dirname(filename)) # playlist_title = os.path.splitext(os.path.basename(filename))[0] print "Synching File: %s" % filename print "Parsing Songs from %s" % filename pc_songs = self.get_songs_from_file(filename) # print (pc_songs) print "%d songs in local file: %s" % (len(pc_songs), filename) # Sanity check max 1000 songs per playlist if len(pc_songs) > MAX_SONGS_IN_PLAYLIST: print " Google music doesn't allow more than %d songs in a playlist..." % MAX_SONGS_IN_PLAYLIST print " Will only attempt to sync the first %d songs." % MAX_SONGS_IN_PLAYLIST del pc_songs[MAX_SONGS_IN_PLAYLIST:] existing_files = 0 added_files = 0 failed_files = 0 removed_files = 0 fatal_count = 0 for song in pc_songs: playlist_title = song["playlist"] if playlist_title not in self.playlists["user"]: self.playlists["user"][playlist_title] = [self.mc.create_playlist(playlist_title)] time.sleep(0.7) print "Starting Playlist Sync with Google music..." for song in pc_songs: # print song plid = "" print "--------------------------------" print "" print "Playlist: %s" % song["playlist"] print "Artist: %s" % song["artist"] print "Song: %s" % song["title"] print "Album: %s" % song["album"] playlist_title = song["playlist"] plid = self.playlists["user"][playlist_title][0] goog_songs = self.wc.get_playlist_songs(plid) if self.song_already_in_list(song, goog_songs): existing_files += 1 print "Result: Song Already Added" continue print "Total %d songs in Google playlist: %s" % (len(goog_songs), playlist_title) print "%s - %s, didn't exist...Will try to add..." % (song["artist"], song["title"]) print "" print "--------------------------------" results = self.mc.search_all_access(song["title"], max_results=50) nid = self.filter_search_results(results, song) print "AA nId: %s " % nid if nid: song_id = self.mc.add_aa_track(nid) added = self.wc.add_songs_to_playlist(plid, song_id) print "Playlist UUid: %s" % plid print "Song ID: %s" % song_id time.sleep(0.3) # Don't spam the server too fast... print "Result: done adding to playlist" added_files += 1 continue else: query = "%s %s" % (song["artist"], song["title"].split(" ")[0]) print "Query %s" % query results = self.mc.search_all_access(query, max_results=50) nid = self.filter_search_results(results, song) if nid: song_id = self.mc.add_aa_track(nid) added = self.wc.add_songs_to_playlist(plid, song_id) print "Playlist UUid: %s" % plid print "Song ID: %s" % song_id time.sleep(0.3) # Don't spam the server too fast... print " -- done adding to playlist" added_files += 1 continue print "Result: NID Blank, Song not Found in All Access" print "" print "---" print "%d songs unmodified" % existing_files print "%d songs added" % added_files print "%d songs failed" % failed_files print "%d songs removed" % removed_files def get_songs_from_file(self, filename): songs = [] f = codecs.open(filename, encoding="utf-8") for line in f: line = line.rstrip().replace(u"\ufeff", u"") if line == "" or line[0] == "#": continue la = line.split("\\") regex_filter = "[^A-Za-z0-9\,\-\.\ \(\)'\!\?\$\/ \& \:]" artist = re.sub(regex_filter, "", la[1]) playlist = re.sub(regex_filter, "", la[0]) album = re.sub(regex_filter, "", la[2]) title = re.sub(regex_filter, "", la[3]) # print "Filtered Strings:" # print "Artist: %s" % artist # print "Playlist: %s" % playlist # print "Song: %s" % title # print "Album: %s" % album dt = {"playlist": playlist, "artist": artist, "album": album, "title": title} # print (dt) songs.append(dt) f.close() return songs def get_songs_from_playlist(self, filename): songs = [] f = codecs.open(filename, encoding="utf-8") for line in f: line = line.rstrip().replace(u"\ufeff", u"") if line == "" or line[0] == "#": continue path = os.path.abspath(self.get_platform_path(line)) # if not os.path.exists(path): # print "File not found: %s" % line # continue songs.append(path) f.close() return songs def get_files_from_playlist(self, filename): files = [] f = codecs.open(filename, encoding="utf-8") for line in f: line = line.rstrip().replace(u"\ufeff", u"") if line == "" or line[0] == "#": continue path = os.path.abspath(self.get_platform_path(line)) if not os.path.exists(path): print "File not found: %s" % line continue files.append(path) f.close() return files def song_already_in_list(self, song, goog_songs): # tag = self.get_id3_tag(filename) i = 0 while i < len(goog_songs): # print goog_songs if self.tag_compare(goog_songs[i], song): goog_songs.pop(i) return True i += 1 return False def file_already_in_list(self, filename, goog_songs): tag = self.get_id3_tag(filename) i = 0 while i < len(goog_songs): if self.tag_compare(goog_songs[i], tag): goog_songs.pop(i) return True i += 1 return False def get_id3_tag(self, filename): data = mutagen.File(filename, easy=True) r = {} if "title" not in data: title = os.path.splitext(os.path.basename(filename))[0] print "Found song with no ID3 title, setting using filename:" print " %s" % title print " (please note - the id3 format used (v2.4) is invisible to windows)" data["title"] = [title] data.save() r["title"] = data["title"][0] r["track"] = int(data["tracknumber"][0].split("/")[0]) if "tracknumber" in data else 0 # If there is no track, try and get a track number off the front of the file... since thats # what google seems to do... # Not sure how google expects it to be formatted, for now this is a best guess if r["track"] == 0: m = re.match("(\d+) ", os.path.basename(filename)) if m: r["track"] = int(m.group(0)) r["artist"] = data["artist"][0] if "artist" in data else "" r["album"] = data["album"][0] if "album" in data else "" return r def find_song(self, filename, plid): tag = self.get_id3_tag(filename) print "Song Tag: %s " % tag print "Filename: %s" % filename ws_plids = [] ws_plids.append(plid) print (ws_plids) playlists = self.wc.get_all_playlist_ids() print (playlists) results = self.wc.get_playlist_songs(ws_plids) # NOTE - dianostic print here to check results if you're creating duplicates print results print "%s ][ %s ][ %s ][ %s" % (tag["title"], tag["artist"], tag["album"], tag["track"]) for r in results: if self.tag_compare(r, tag): # TODO: add rough time check to make sure its "close" return r return None def filter_search_results(self, results, song): # Try Exact Matching for g_song in results["song_hits"]: if self.tag_compare(g_song["track"], song): return g_song["track"]["nid"] elif self.song_compare(g_song["track"], song, "artist"): # try just the artist return g_song["track"]["nid"] elif self.song_compare(g_song["track"], song, "part-song"): # try part of song and artist return g_song["track"]["nid"] return None def song_compare(self, g_song, tag, type): if "track" not in g_song: g_song["track"] = 0 title_parts = tag["title"].split("(") # removing shit like (featuring wiz) tp = title_parts[0].split(" ") # First word maybe if "artist" in type: return g_song["artist"].lower() == tag["artist"].lower() if "part-song" in type: return g_song["title"].find(tp[0]) and g_song["artist"].lower() == tag["artist"].lower() return None def tag_compare(self, g_song, tag): if "track" not in g_song: g_song["track"] = 0 return ( g_song["title"].split(" ")[0].lower() == tag["title"].split(" ")[0].lower() and g_song["artist"].lower() == tag["artist"].lower() ) def delete_song(self, sid): self.wc.delete_songs(sid) print "Deleted song by id [%s]" % sid def get_platform_path(self, full_path): # Try to avoid messing with the path if possible if os.sep == "/" and "\\" not in full_path: return full_path if os.sep == "\\" and "\\" in full_path: return full_path if "\\" not in full_path: return full_path return os.path.normpath(full_path.replace("\\", "/"))
class GMusic(object): def __init__(self): self.authenticated = False self.all_access = False self.library_loaded = False self.all_songs = [] self.letters = {} self.artists = {} self.albums = {} self.genres = {} self.tracks_by_letter = {} self.tracks_by_artist = {} self.tracks_by_album = {} self.tracks_by_genre = {} self._device = None self._webclient = Webclient(debug_logging=False) self._mobileclient = Mobileclient(debug_logging=False) self._playlists = [] self._playlist_contents = [] self._stations = [] def _get_device_id(self): if self.authenticated: devices = self._webclient.get_registered_devices() for dev in devices: if dev['type'] == 'PHONE': self._device = dev['id'][2:] break elif dev['type'] == 'IOS': self._device = dev['id'] break def _set_all_access(self): settings = self._webclient._make_call(webclient.GetSettings, '') self.all_access = True if 'isSubscription' in settings[ 'settings'] and settings['settings'][ 'isSubscription'] == True else False def _set_all_songs(self): if len(self.all_songs) == 0: try: self.all_songs = self._mobileclient.get_all_songs() except NotLoggedIn: if self.authenticate(): self.all_songs = self._mobileclient.get_all_songs() else: return [] else: return self.all_songs def authenticate(self, email, password): try: mcauthenticated = self._mobileclient.login(email, password) except AlreadyLoggedIn: mcauthenticated = True try: wcauthenticated = self._webclient.login(email, password) except AlreadyLoggedIn: wcauthenticated = True self.authenticated = mcauthenticated and wcauthenticated self._set_all_access() self._get_device_id() return self.authenticated def load_data(self): self._set_all_songs() for song in self.all_songs: thumb = None letter = song['title'][0] artist = song['artist'] album = song['album'] genre = song['genre'] if 'genre' in song else '(None)' if letter not in self.tracks_by_letter: self.tracks_by_letter[letter] = [] self.letters[letter] = None if artist not in self.tracks_by_artist: self.tracks_by_artist[artist] = [] self.artists[artist] = None if album not in self.tracks_by_album: self.tracks_by_album[album] = [] self.albums[album] = None if genre not in self.tracks_by_genre: self.tracks_by_genre[genre] = [] self.genres[genre] = None track = {'artist': artist, 'album': album} if 'title' in song: track['title'] = song['title'] if 'album' in song: track['album'] = song['album'] if 'artist' in song: track['artist'] = song['artist'] if 'durationMillis' in song: track['durationMillis'] = song['durationMillis'] if 'id' in song: track['id'] = song['id'] if 'trackNumber' in song: track['trackType'] = song['trackNumber'] if 'storeId' in song: track['storeId'] = song['storeId'] if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] thumb = song['albumArtRef'][0]['url'] self.letters[letter] = thumb self.artists[artist] = thumb self.albums[album] = thumb self.genres[genre] = thumb self.tracks_by_letter[letter].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.tracks_by_artist[artist].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.tracks_by_album[album].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.tracks_by_genre[genre].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.library_loaded = True def get_tracks_for_type(self, type, name): type = type.lower() if type == 'artists': return self.tracks_by_artist[name] elif type == 'albums': return self.tracks_by_album[name] elif type == 'genres': return self.tracks_by_genre[name] elif type == 'songs by letter': return self.tracks_by_letter[name] else: return {} def get_song(self, id): return [x for x in self.all_songs if x['id'] == id][0] def get_all_playlists(self): if len(self._playlists) == 0: try: self._playlists = self._mobileclient.get_all_playlists() except NotLoggedIn: if self.authenticate(): self._playlists = self._mobileclient.get_all_playlists() else: return [] return self._playlists def get_all_user_playlist_contents(self, id): tracks = [] if len(self._playlist_contents) == 0: try: self._playlist_contents = self._mobileclient.get_all_user_playlist_contents( ) except NotLoggedIn: if self.authenticate(): self._playlist_contents = self._mobileclient.get_all_user_playlist_contents( ) else: return [] for playlist in self._playlist_contents: if id == playlist['id']: tracks = playlist['tracks'] break return tracks def get_shared_playlist_contents(self, token): playlist = [] try: playlist = self._mobileclient.get_shared_playlist_contents(token) except NotLoggedIn: if self.authenticate(): playlist = self._mobileclient.get_shared_playlist_contents( token) else: return [] return playlist def get_all_stations(self): if len(self._stations) == 0: try: self._stations = self._mobileclient.get_all_stations() except NotLoggedIn: if self.authenticate(): self._stations = self._mobileclient.get_all_stations() else: return [] return self._stations def get_station_tracks(self, id, num_tracks=200): tracks = [] try: tracks = self._mobileclient.get_station_tracks(id, num_tracks) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.get_station_tracks(id, num_tracks) else: return [] return tracks def get_genres(self): genres = [] try: genres = self._mobileclient.get_genres() except NotLoggedIn: if self.authenticate(): genres = self._mobileclient.get_genres() else: return [] return genres def create_station(self, name, id): station = None try: station = self._mobileclient.create_station(name=name, genre_id=id) except NotLoggedIn: if self.authenticate(): station = self._mobileclient.create_station(name=name, genre_id=id) else: return [] return station def search_all_access(self, query, max_results=50): results = None try: results = self._mobileclient.search_all_access(query, max_results) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.search_all_access( query, max_results) else: return [] return results def get_artist_info(self, id, include_albums=True, max_top_tracks=5, max_rel_artist=5): results = None try: results = self._mobileclient.get_artist_info( id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_artist_info( id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) else: return [] return results def get_album_info(self, id, include_tracks=True): results = None try: results = self._mobileclient.get_album_info( id, include_tracks=include_tracks) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_album_info( id, include_tracks=include_tracks) else: return [] return results def add_aa_track(self, id): track = None try: track = self._mobileclient.add_aa_track(id) except NotLoggedIn: if self.authenticate(): track = self._mobileclient.add_aa_track(id) else: return None return track def add_songs_to_playlist(self, playlist_id, song_ids): tracks = None try: tracks = self._mobileclient.add_songs_to_playlist( playlist_id, song_ids) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.add_songs_to_playlist( playlist_id, song_ids) else: return None return tracks def get_stream_url(self, id): try: stream_url = self._mobileclient.get_stream_url(id, self._device) except NotLoggedIn: if self.authenticate(): stream_url = self._mobileclient.get_stream_url( id, self._device) else: return '' except CallFailure: raise CallFailure('Could not play song with id: ' + id, 'get_stream_url') return stream_url
# import tracks if migration_type == 'all' or migration_type == 'tracks': log.info('Importing ' + str(len(all_tracks)) + ' All Access tracks to ' + import_username) for i, track in enumerate(all_tracks, start=1): track_id = get_aa_id(track) track_artist = track.get('artist') track_title = track.get('title') if i % 100 == 0: log.info('Importing track ' + str(i) + ' of ' + str(len(all_tracks))) if not simulate: try: import_api.add_aa_track(track_id) except CallFailure as e: log.error('Add failed for track ' + track_artist + ' - ' + track_title) log.debug('ID of failed track is ' + track_id) continue # create consolidated list of library and thumbs up tracks with a rating if migration_type == 'all' or migration_type == 'ratings': merged_tracks = merge_track_lists(all_tracks, thumbs_up_tracks) rated_tracks = [t for t in merged_tracks if t.get('rating', 0) > 0] log.info('Importing ' + str(len(rated_tracks)) + ' track ratings to ' + import_username) # set rating on tracks
class GMusicClient(ContentConsumer): """Element in charge of interfacing with GMusicApi Client""" def __init__(self, data_cache): self.client = Mobileclient() self.data_cache = data_cache def login(self): """Use data/unlocked/credentials.json to log in""" mac = Mobileclient.FROM_MAC_ADDRESS try: credentials = json.load(open("data/unlocked/credentials.json", "r")) result = self.client.login(credentials["username"], credentials["password"], mac) if result == False: print("\n\033[93mLogin failed.") print("Please double check that data/unlocked/credentials.json has correct information.\033[m\n") os._exit(1) except: print("\n\033[93mdata/unlocked/credentials.json is not valid.") print("You may need to delete and regnerate the credentials file.\033[m\n") exit(1) def load_my_library(self): """Load user's songs, playlists, and stations""" track_load = threading.Thread(target=self.load_tracks) radio_load = threading.Thread(target=self.load_radios) playlist_load = threading.Thread(target=self.load_playlists) track_load.start() radio_load.start() playlist_load.start() track_load.join() radio_load.join() playlist_load.join() def load_playlists(self): playlists = self.client.get_all_user_playlist_contents() playlists.reverse() self.data_cache.playlists = playlists def load_tracks(self): self.data_cache.tracks = [t for t in self.client.get_all_songs() if "nid" in t] def scrape_song(self, track): return track def load_radios(self): radios = self.client.get_all_stations() radios.reverse() self.data_cache.radios = radios def get_radio_contents(self, radio_id): tracks = self.client.get_station_tracks(radio_id) return tracks def get_radio_list(self, name): return [r for r in self.data_cache.radios if name in r["name"]] def filter(self, element, field, filter_by): return [e for e in element if filter_by in e[field]] def get_playlist_list(self, name): return self.filter(self.data_cache.playlists, "name", name) def search_all_access(self, query): return self.client.search_all_access(query) def create_radio(self, seed_type, id, name): """Create a radio""" # This is a weird way to do this, but there's no other choice ids = {"track": None, "album": None, "artist": None} seed_id_name = self.get_type_name(seed_type) ids[seed_id_name] = id return self.client.create_station( name=name, track_id=ids["track"], album_id=ids["album"], artist_id=ids["artist"] ) def search_items_all_access(self, type, query): """Searches Albums, Artists, and Songs; uses metaprogramming""" index_arguments = self.get_index_arguments(type) items = self.search_all_access(query)["{0}_hits".format(type[:-1])] return [self.format_item(item, type, index_arguments) for item in items] def get_sub_items(self, type_from, search_type, from_id): """Here type_from refers to artist or album we're indexing against""" args = self.get_index_arguments(search_type) if type_from == "playlist": return self.get_playlist_contents(from_id) else: # Get the appropriate search method and execute it search_method_name = "get_{0}_info".format(type_from) search_method = getattr(self.client, search_method_name) try: items = search_method(from_id, True)[args["type"] + "s"] # True here includes subelements except: # User passed in a bad id or something return # Now return appropriately return [self.format_subitems(t, args) for t in items if args["id"] in t] def get_playlist_contents(self, from_id): """Playlist exclusive stuff""" shareToken = [t for t in self.data_cache.playlists if t["id"] == from_id][0]["shareToken"] contents = self.client.get_shared_playlist_contents(shareToken) return [self.format_playlist_contents(t) for t in contents if "track" in t] def get_suggested(self): """Returns a list of tracks that the user might be interested in""" items = sorted(self.client.get_promoted_songs(), key=lambda y: y["title"]) return [self.format_suggested(t) for t in items if "storeId" in t] def get_information_about(self, from_type, from_id): """Gets specific information about an id (depending on the type)""" if "artist" in from_type.lower(): return self.client.get_artist_info(from_id, include_albums=False) if "album" in from_type.lower(): return self.client.get_album_info(from_id, include_tracks=False) return self.client.get_track_info(from_id) def get_stream_url(self, nid): return self.client.get_stream_url(nid) def lookup(self, nid): return self.client.get_track_info(nid) def add_track_to_library(self, nid): self.client.add_aa_track(nid) def add_to_playlist(self, playlist_id, nid): self.client.add_songs_to_playlist(playlist_id, nid) playlist_load = threading.Thread(target=self.load_playlists) playlist_load.daemon = True playlist_load.start() def format_suggested(self, t): return (t["title"], t["storeId"], "Play", t["album"]) def format_playlist_contents(self, t): return (t["track"]["title"], t["trackId"], "Play", t["track"]["album"]) def format_subitems(self, t, args): return (t[args["name"]], t[args["id"]], args["command"], t[args["alt"]])
log.debug('API logout for ' + export_username) # import tracks if migration_type == 'all' or migration_type == 'tracks': log.info('Importing ' + str(len(all_tracks)) + ' All Access tracks to ' + import_username) for i, track in enumerate(all_tracks, start=1): track_id = get_aa_id(track) track_artist = track.get('artist') track_title = track.get('title') if i % 100 == 0: log.info('Importing track ' + str(i) + ' of ' + str(len(all_tracks))) if not simulate: try: import_api.add_aa_track(track_id) except CallFailure as e: log.error('Add failed for track ' + track_artist + ' - ' + track_title) log.debug('ID of failed track is ' + track_id) continue # create consolidated list of library and thumbs up tracks with a rating if migration_type == 'all' or migration_type == 'ratings': merged_tracks = merge_track_lists(all_tracks, thumbs_up_tracks) rated_tracks = [t for t in merged_tracks if t.get('rating', 0) > 0] log.info('Importing ' + str(len(rated_tracks)) + ' track ratings to ' + import_username) # set rating on tracks for i, track in enumerate(rated_tracks, start=1): track_id = get_aa_id(track)
class GMusic(object): def __init__(self): self.authenticated = False self.all_access = False self.library_loaded = False self.all_songs = [] self.letters = {} self.artists = {} self.albums = {} self.genres = {} self.tracks_by_letter = {} self.tracks_by_artist = {} self.tracks_by_album = {} self.tracks_by_genre = {} self._device = None self._webclient = Webclient(debug_logging=False) self._mobileclient = Mobileclient(debug_logging=False) self._playlists = [] self._playlist_contents = [] self._stations = [] def _get_device_id(self): if self.authenticated: devices = self._webclient.get_registered_devices() for dev in devices: if dev['type'] == 'PHONE': self._device = dev['id'][2:] break elif dev['type'] == 'IOS': self._device = dev['id'] break def _set_all_access(self): settings = self._webclient._make_call(webclient.GetSettings, '') self.all_access = True if 'isSubscription' in settings['settings'] and settings['settings']['isSubscription'] == True else False def _set_all_songs(self): if len(self.all_songs) == 0: try: self.all_songs = self._mobileclient.get_all_songs() except NotLoggedIn: if self.authenticate(): self.all_songs = self._mobileclient.get_all_songs() else: return [] else: return self.all_songs def authenticate(self, email, password): try: mcauthenticated = self._mobileclient.login(email, password) except AlreadyLoggedIn: mcauthenticated = True try: wcauthenticated = self._webclient.login(email, password) except AlreadyLoggedIn: wcauthenticated = True self.authenticated = mcauthenticated and wcauthenticated self._set_all_access() self._get_device_id() return self.authenticated def load_data(self): self._set_all_songs() for song in self.all_songs: thumb = None letter = song['title'][0] artist = song['artist'] album = song['album'] genre = song['genre'] if 'genre' in song else '(None)' if letter not in self.tracks_by_letter: self.tracks_by_letter[letter] = [] self.letters[letter] = None if artist not in self.tracks_by_artist: self.tracks_by_artist[artist] = [] self.artists[artist] = None if album not in self.tracks_by_album: self.tracks_by_album[album] = [] self.albums[album] = None if genre not in self.tracks_by_genre: self.tracks_by_genre[genre] = [] self.genres[genre] = None track = {'artist': artist, 'album': album} if 'title' in song: track['title'] = song['title'] if 'album' in song: track['album'] = song['album'] if 'artist' in song: track['artist'] = song['artist'] if 'durationMillis' in song: track['durationMillis'] = song['durationMillis'] if 'id' in song: track['id'] = song['id'] if 'trackNumber' in song: track['trackType'] = song['trackNumber'] if 'storeId' in song: track['storeId'] = song['storeId'] if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] thumb = song['albumArtRef'][0]['url'] self.letters[letter] = thumb self.artists[artist] = thumb self.albums[album] = thumb self.genres[genre] = thumb self.tracks_by_letter[letter].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_artist[artist].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_album[album].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_genre[genre].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.library_loaded = True def get_tracks_for_type(self, type, name): type = type.lower() if type == 'artists': return self.tracks_by_artist[name] elif type == 'albums': return self.tracks_by_album[name] elif type == 'genres': return self.tracks_by_genre[name] elif type == 'songs by letter': return self.tracks_by_letter[name] else: return {} def get_song(self, id): return [x for x in self.all_songs if x['id'] == id][0] def get_all_playlists(self): if len(self._playlists) == 0: try: self._playlists = self._mobileclient.get_all_playlists() except NotLoggedIn: if self.authenticate(): self._playlists = self._mobileclient.get_all_playlists() else: return [] return self._playlists def get_all_user_playlist_contents(self, id): tracks = [] if len(self._playlist_contents) == 0: try: self._playlist_contents = self._mobileclient.get_all_user_playlist_contents() except NotLoggedIn: if self.authenticate(): self._playlist_contents = self._mobileclient.get_all_user_playlist_contents() else: return [] for playlist in self._playlist_contents: if id == playlist['id']: tracks = playlist['tracks'] break return tracks def get_shared_playlist_contents(self, token): playlist = [] try: playlist = self._mobileclient.get_shared_playlist_contents(token) except NotLoggedIn: if self.authenticate(): playlist = self._mobileclient.get_shared_playlist_contents(token) else: return [] return playlist def get_all_stations(self): if len(self._stations) == 0: try: self._stations = self._mobileclient.get_all_stations() except NotLoggedIn: if self.authenticate(): self._stations = self._mobileclient.get_all_stations() else: return [] return self._stations def get_station_tracks(self, id, num_tracks=200): tracks = [] try: tracks = self._mobileclient.get_station_tracks(id, num_tracks) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.get_station_tracks(id, num_tracks) else: return [] return tracks def get_genres(self): genres = [] try: genres = self._mobileclient.get_genres() except NotLoggedIn: if self.authenticate(): genres = self._mobileclient.get_genres() else: return [] return genres def create_station(self, name, id): station = None try: station = self._mobileclient.create_station(name=name, genre_id=id) except NotLoggedIn: if self.authenticate(): station = self._mobileclient.create_station(name=name, genre_id=id) else: return [] return station def search_all_access(self, query, max_results=50): results = None try: results = self._mobileclient.search_all_access(query, max_results) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.search_all_access(query, max_results) else: return [] return results def get_artist_info(self, id, include_albums=True, max_top_tracks=5, max_rel_artist=5): results = None try: results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) else: return [] return results def get_album_info(self, id, include_tracks=True): results = None try: results = self._mobileclient.get_album_info(id, include_tracks=include_tracks) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_album_info(id, include_tracks=include_tracks) else: return [] return results def add_aa_track(self, id): track = None try: track = self._mobileclient.add_aa_track(id) except NotLoggedIn: if self.authenticate(): track = self._mobileclient.add_aa_track(id) else: return None return track def add_songs_to_playlist(self, playlist_id, song_ids): tracks = None try: tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids) else: return None return tracks def get_stream_url(self, id): try: stream_url = self._mobileclient.get_stream_url(id, self._device) except NotLoggedIn: if self.authenticate(): stream_url = self._mobileclient.get_stream_url(id, self._device) else: return '' except CallFailure: raise CallFailure('Could not play song with id: ' + id, 'get_stream_url') return stream_url def reset_library(self): self.library_loaded = False self.all_songs[:] = [] self._playlists[:] = [] self._playlist_contents[:] = [] self._stations[:] = [] self.letters.clear() self.artists.clear() self.albums.clear() self.genres.clear() self.tracks_by_letter.clear() self.tracks_by_artist.clear() self.tracks_by_album.clear() self.tracks_by_genre.clear()
def main(): log.setLevel(logging.INFO) logging.getLogger('gmusicapi').setLevel(logging.INFO) cred_path = os.path.join(os.path.expanduser('~'), '.gmusic-sync') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' 'be found either. Try creating %s and specifying your ' 'username/password there. Make sure to chmod 600.' % cred_path) if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( 'Config file is not protected. Please run: ' 'chmod 600 %s' % cred_path) config = ConfigParser.ConfigParser() config.read(cred_path) src_user = config.get('src','username') src_pass = config.get('src','password') src_device = config.get('src','deviceid') dst_user = config.get('dst','username') dst_pass = config.get('dst','password') dst_device = config.get('dst','deviceid') if not src_user or not src_pass or not dst_user or not dst_pass: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not src_device or not dst_device: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) parser = argparse.ArgumentParser(description='gmusic-sync', add_help=False) parser.add_argument('-hs', '--strict-heuristics', help='Songs must match artist, album, and title to be considered a match.', action='store_true', dest='strict_heuristics') parser.add_argument('-l', '--list', help='List playlists on the src account', action='store_true', dest='lst') parser.add_argument('-p', '--playlist', help='Playlist ID from src account to transfer', dest='playlist') args = parser.parse_args() api = Mobileclient() api.login(src_user, src_pass, src_device) playlists = api.get_all_playlists() if args.lst: for playlist in playlists: print playlist['name'] + ' (' + playlist['id'] + ') ' exit() library = api.get_all_songs() api2 = Mobileclient() api2.login(dst_user, dst_pass, dst_device) library2 = api2.get_all_songs() if args.playlist is None: print 'Error: no playlist selected' all_playlist_entries = api.get_all_user_playlist_contents() selected_playlist_entries = [] dst_playlist_id = None for entry in all_playlist_entries: if entry['id'] == args.playlist: selected_playlist_entries = entry['tracks'] dst_playlist_id = api2.create_playlist(entry['name']) if dst_playlist_id is None: print 'Error creating new playlist' exit() playlist_tracks = [] for ptrack in selected_playlist_entries: track_found = False for track in library: if ptrack['trackId'] == track['id']: playlist_tracks.append(track) track_found = True break try: if ptrack['trackId'] == track['storeId']: playlist_tracks.append(track) track_found = True break except: pass if not track_found: print 'ERROR: could not find playlist entry ' + str(ptrack) api.add_aa_track(ptrack['trackId']) if len(playlist_tracks) != len(selected_playlist_entries): print 'Error: could not locate all playlist tracks in src library' exit() failed_tracks = [] for track in playlist_tracks: try: if track['storeId'].startswith('T'): #It's a store track: does it exist in the target store? #Perform a store lookup: this will raise an exception if the track #Is not in the target store store_track = api2.get_track_info(track['storeId']) #If we got here, we're good to go for adding the track to the playlist retval = api2.add_songs_to_playlist(dst_playlist_id, track['storeId']) if track['storeId'] not in retval: print 'Error adding ' + track['title'] + ' - ' + track['artist'] + ' (' + track['album'] + ')' else: dst_track = heuristic_search(library2, track, args.strict_heuristics) if dst_track is not None: api2.add_songs_to_playlist(dst_playlist_id, dst_track['id']) else: failed_tracks.append(track) except: #Not a store track: do heuristics lookup dst_track = heuristic_search(library2, track, args.strict_heuristics) if dst_track is not None: api2.add_songs_to_playlist(dst_playlist_id, dst_track['id']) else: failed_tracks.append(track) continue print '----------------- FAILED TRACKS --------------------' for track in failed_tracks: print track['title'] + ' - ' + track['artist'] + ' (' + track['album'] + ')'
class GMusicClient(ContentConsumer): '''Element in charge of interfacing with GMusicApi Client''' def __init__(self, data_cache): self.client = Mobileclient() self.data_cache = data_cache def login(self): '''Use data/unlocked/credentials.json to log in''' mac = Mobileclient.FROM_MAC_ADDRESS credentials = json.load(open('data/unlocked/credentials.json', 'r')) self.client.login(credentials['username'], credentials['password'], mac) def load_my_library(self): '''Load user's songs, playlists, and stations''' track_load = threading.Thread(target=self.load_tracks) radio_load = threading.Thread(target=self.load_radios) playlist_load = threading.Thread(target=self.load_playlists) track_load.start() radio_load.start() playlist_load.start() track_load.join() radio_load.join() playlist_load.join() def load_playlists(self): playlists = self.client.get_all_user_playlist_contents() playlists.reverse() self.data_cache.playlists = playlists def load_tracks(self): self.data_cache.tracks = [t for t in self.client.get_all_songs() if 'nid' in t] def scrape_song(self, track): return track def load_radios(self): radios = self.client.get_all_stations() radios.reverse() self.data_cache.radios = radios def get_radio_contents(self, radio_id): tracks = self.client.get_station_tracks(radio_id) return tracks def get_radio_list(self, name): return [r for r in self.data_cache.radios if name in r['name']] def filter(self, element, field, filter_by): return [e for e in element if filter_by in e[field]] def get_playlist_list(self, name): return self.filter(self.data_cache.playlists, 'name', name) def search_all_access(self, query): return self.client.search_all_access(query) def create_radio(self, seed_type, id, name): '''Create a radio''' # This is a weird way to do this, but there's no other choice ids = {"track": None, "album": None, "artist": None} seed_id_name = self.get_type_name(seed_type) ids[seed_id_name] = id return self.client.create_station(name=name, track_id=ids['track'], album_id=ids['album'], artist_id=ids['artist']) def search_items_all_access(self, type, query): '''Searches Albums, Artists, and Songs; uses metaprogramming''' index_arguments = self.get_index_arguments(type) items = self.search_all_access(query)['{0}_hits'.format(type[:-1])] return [self.format_item(item, type, index_arguments) for item in items] def get_sub_items(self, type_from, search_type, from_id): '''Here type_from refers to artist or album we're indexing against''' args = self.get_index_arguments(search_type) if type_from == 'playlist': return self.get_playlist_contents(from_id) else: # Get the appropriate search method and execute it search_method_name = 'get_{0}_info'.format(type_from) search_method = getattr(self.client, search_method_name) try: items = search_method(from_id, True)[args['type']+'s'] # True here includes subelements except: # User passed in a bad id or something return # Now return appropriately return [self.format_subitems(t, args) for t in items if args['id'] in t] def get_playlist_contents(self, from_id): '''Playlist exclusive stuff''' shareToken = [t for t in self.data_cache.playlists \ if t['id'] == from_id][0]['shareToken'] contents = self.client.get_shared_playlist_contents(shareToken) return [self.format_playlist_contents(t) for t in contents if 'track' in t] def get_suggested(self): '''Returns a list of tracks that the user might be interested in''' items = sorted(self.client.get_promoted_songs(), key=lambda y: y['title']) return [self.format_suggested(t) for t in items if 'storeId' in t] def get_information_about(self, from_type, from_id): '''Gets specific information about an id (depending on the type)''' if 'artist' in from_type.lower(): return self.client.get_artist_info(from_id, include_albums=False) if 'album' in from_type.lower(): return self.client.get_album_info(from_id, include_tracks=False) return self.client.get_track_info(from_id) def get_stream_url(self, nid): return self.client.get_stream_url(nid) def lookup(self, nid): return self.client.get_track_info(nid) def add_track_to_library(self, nid): self.client.add_aa_track(nid) def add_to_playlist(self, playlist_id, nid): self.client.add_songs_to_playlist(playlist_id, nid) playlist_load = threading.Thread(target=self.load_playlists) playlist_load.daemon = True playlist_load.start() def format_suggested(self, t): return (t['title'], t['storeId'], 'Play', t['album']) def format_playlist_contents(self, t): return (t['track']['title'], t['trackId'], 'Play', t['track']['album']) def format_subitems(self, t, args): return (t[args['name']], t[args['id']], args['command'], t[args['alt']])
def main(): log.setLevel(logging.INFO) logging.getLogger('gmusicapi').setLevel(logging.INFO) cred_path = os.path.join(os.path.expanduser('~'), '.gmusic-sync') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' 'be found either. Try creating %s and specifying your ' 'username/password there. Make sure to chmod 600.' % cred_path) if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( 'Config file is not protected. Please run: ' 'chmod 600 %s' % cred_path) config = ConfigParser.ConfigParser() config.read(cred_path) src_user = config.get('src','username') src_pass = config.get('src','password') src_device = config.get('src','deviceid') dst_user = config.get('dst','username') dst_pass = config.get('dst','password') dst_device = config.get('dst','deviceid') if not src_user or not src_pass or not dst_user or not dst_pass: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not src_device or not dst_device: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) parser = argparse.ArgumentParser(description='gmusic-sync', add_help=False) parser.add_argument('-d', '--dst', help='Perform operation on the dst account', action='store_true', dest='dst') parser.add_argument('-l', '--list', help='List playlists on the src account', action='store_true', dest='lst') parser.add_argument('-p', '--playlist', help='Playlist ID from src account to transfer', dest='playlist') args = parser.parse_args() api = Mobileclient() if args.dst: api.login(dst_user, dst_pass, dst_device) else: api.login(src_user, src_pass, src_device) playlists = api.get_all_playlists() if args.lst: for playlist in playlists: print playlist['name'] + ' (' + playlist['id'] + ') ' exit() library = api.get_all_songs() if args.playlist is None: print 'Error: no playlist selected' all_playlist_entries = api.get_all_user_playlist_contents() selected_playlist_entries = [] for entry in all_playlist_entries: if entry['id'] == args.playlist: selected_playlist_entries = entry['tracks'] playlist_tracks = [] for ptrack in selected_playlist_entries: track_found = False for track in library: if ptrack['trackId'] == track['id']: playlist_tracks.append(track) track_found = True break try: if ptrack['trackId'] == track['storeId']: playlist_tracks.append(track) track_found = True break except: pass if not track_found: print 'ERROR: could not find playlist entry ' + str(ptrack) api.add_aa_track(ptrack['trackId']) if len(playlist_tracks) != len(selected_playlist_entries): print 'Error: could not locate all playlist tracks in src library' exit() failed_tracks = [] playlist_tracks_reversed = [] for track in playlist_tracks: playlist_tracks_reversed.insert(0, track) for track in playlist_tracks_reversed: track['rating'] = '5' res = api.change_song_metadata(track) if len(res) != 1: raise Exception('Could not change track metadata!') time.sleep(1)
from gmusicapi import Mobileclient from getpass import getpass import json # fill this in email = '' api = Mobileclient() logged_in = api.login(email, getpass()) if logged_in: for track in json.load(open('dump.json')): title = track['title'] artist = track['artist'] query = u"{title!s} - {artist!s}".format(**locals()) results = api.search_all_access(query)['song_hits'] if len(results) > 0: storeId = results[0]['track']['storeId'] api.add_aa_track(storeId) else: print u"Google Music lacks: {query!s}".format(**locals()) else: print "failed to log in"
class GoogleMusicClient(object): def __init__(self, username, password): self._username = username self._password = password self._apiClient = None def initConnection(self): self._apiClient = Mobileclient(debug_logging=False) if not self._apiClient.login(self._username, self._password): raise RuntimeError("Could not connect %s to Google Music." % \ self._username) def addPlaylist(self, playlist): plid = self._apiClient.create_playlist(playlist.name) tids = [] for track in playlist.tracks: aaid = self._getAllAccessTrackId(track) if aaid: try: tid = self._apiClient.add_aa_track(aaid) if tid: tids.append(tid) else: LOGGER.warning( "Could not add track %s to library.", str(track)) except: LOGGER.error("Could not add track %s to library.", str(track)) continue else: LOGGER.warning("Track %s not found.", str(track)) self._apiClient.add_songs_to_playlist(plid, tids) def addAlbum(self, album): aaid = self._getAllAccessAlbumId(album) if aaid: albumInfo = self._apiClient.get_album_info(aaid, include_tracks=True) for track in albumInfo["tracks"]: try: self._apiClient.add_aa_track(track[Track.GM_ID_KEY]) except: LOGGER.error("Could not add track %s to library.", str(track)) continue else: LOGGER.warning("Album %s not found.", str(album)) def _getAllAccessFirstResult(self, resource): queryStr = re.sub("[-:\(\)\",]","", "%s %s" % (resource.name, resource.artist)) queryStr = re.sub("\s+", "+", queryStr) searchResults = self._apiClient.search_all_access(queryStr) gmusicResources = searchResults.get(resource.GM_HITS_KEY) if gmusicResources: firstResult = gmusicResources[0][resource.GM_NAME] return firstResult[resource.GM_ID_KEY] else: return None def _getAllAccessTrackId(self, track): return self._getAllAccessFirstResult(track) def _getAllAccessAlbumId(self, album): return self._getAllAccessFirstResult(album)