class gmObject: def __init__(self): self.mc = Mobileclient() self.wc = Webclient() self.mm = Musicmanager() def login(self, username, password): error.e = 0 if not self.mc.login(username, password): gmtPrintV("gmObject.login: Wrong username or password (or no internet connection)") error.e = error.LOGIN_FAILED return if not self.wc.login(username, password): gmtPrintV("gmObject.login: Wrong username or password (or no internet connection)") error.e = error.LOGIN_FAILED return if not self.mm.login(config.cred_path): gmtPrintV("gmObject.login: Wrong credentials (or no internet connection)") error.e = error.LOGIN_FAILED return def logout(self): error.e = 0 try: self.mc.logout() self.wc.logout() self.mm.logout() except: gmtPrintV("gmObject.logout: Logout failed") error.e = error.LOGOUT_FAILED
def get_data(self): mobileapi = Mobileclient() mobileapi.login(setting.GUSER, setting.GPASS) library = mobileapi.get_all_songs() mobileapi.logout() return library
class GMusicAPI(): def __init__(self, username=None, encrypted_pass=None): self._api = Mobileclient() self.logged_in = False if username and encrypted_pass: self.login(username, encrypted_pass) def login(self, username, encrypted_pass): self.logged_in = self._api.login(username, decrypt(encrypted_pass), Mobileclient.FROM_MAC_ADDRESS) def logout(self): self._api.logout() self.logged_in = False def clear_playlist(self, playlist_name): playlists = self._api.get_all_user_playlist_contents() playlist = [playlist for playlist in playlists if playlist['name'] == playlist_name][0] entry_ids = [entry['id'] for entry in playlist['tracks']] removed = self._api.remove_entries_from_playlist(entry_ids) return len(removed) def search(self, *args): """ Returns the best-fitting track dict for the given information. :param args: Strings which can be artist, song title, album etc. :return: """ query = sanitise_query(' '.join(args)) result = self._api.search(query) song_results = result['song_hits'] if not song_results: warnings.warn('Warning: query {} returned no song hits.'.format(query)) return None tracks = [song_result['track'] for song_result in song_results[:5]] for track in tracks: if not is_tribute(track, query): return track warnings.warn('Warning: query {} returned no non-tribute song hits.'.format(query)) return None def get_playlist_id(self, playlist_name): for playlist in self._api.get_all_playlists(): if playlist['name'] == playlist_name: return playlist['id'] raise ValueError("Playlist '{}' not found".format(playlist_name)) def add_songs(self, playlist_name, tracks): playlist_id = self.get_playlist_id(playlist_name) track_ids = [track['nid'] for track in tracks if track] self._api.add_songs_to_playlist(playlist_id, track_ids)
class AudioStream: __username = configuration.get('google_username') __password = configuration.get('google_password') __track_prefetch = 15 __client = None __playlist = [] def __init__(self, station_id = 'IFL'): self.__client = Mobileclient() self.__client.login(self.__username, self.__password, Mobileclient.FROM_MAC_ADDRESS) self.__playlist = self.__fetchTrackIDs(station_id) def __del__(self): if self.__client: self.__client.logout() def __fetchTrackIDs(self, station_id): if not self.__client or not self.__client.is_authenticated(): logger.error("Client is not authenticated!") return [] tracklist = self.__client.get_station_tracks(station_id, num_tracks=self.__track_prefetch) logger.info("Received tracks: %r" % json.dumps(tracklist)) # Filter out explicit tracks, where non-explicit is explicitType=2 tracklist = [track for track in tracklist if not 'explicitType' in track or track['explicitType'] == "2"] logger.info("Non-explicit tracks: %r" % json.dumps(tracklist)) # Fetch both song IDs and Nautilus (old) IDs songids = [track['id'] for track in tracklist if 'id' in track] nautids = [track['nid'] for track in tracklist if 'nid' in track] return songids + nautids def pop(self): while self.__playlist: track_id = self.__playlist.pop() try: stream_url = self.__client.get_stream_url(track_id, quality='low') return stream_url except(exceptions.CallFailure): logger.warning("Failed to fetch Stream URL for ID %s" % track_id) raise IndexError("pop from empty list") def reverse(self): # Reverse just returns itself, since the playlist is already chaos return self def __len__(self): return len(self.__playlist)
def main(): parser = argparse.ArgumentParser(description='Sync iTunes Playlists to Google Play Music.') parser.add_argument('itunes_music_library', type=str, help='Path to iTunes Music Library.xml') parser.add_argument('google_music_manager_db', type=str, help='Path to Google Music Manager ServerDatabase.db') parser.add_argument('--verbose', action='store_true', default=False, help='Print verbose output') parser.add_argument('playlists', type=str, nargs='*', metavar='playlist', help='Names of playlists to sync') args = parser.parse_args() global verbose verbose = args.verbose lib = pyItunes.Library(args.itunes_music_library) known_itunes_playlists = lib.getPlaylistNames() if args.playlists: itunes_playlists = args.playlists not_found = set(itunes_playlists) - set(known_itunes_playlists) if not_found: print('''Error: these playlists aren't in your iTunes Library: %s ''' % (sorted(not_found), )) return 1 else: itunes_playlists = known_itunes_playlists server_db = sqlite3.connect(args.google_music_manager_db) api = None username, password = open(os.path.join(os.path.dirname(__file__), 'auth.txt'), 'r').read().splitlines() try: api = Mobileclient() if not api.login(username, password): print('Error: unable to login', file=sys.stderr) return 1 all_google_playlists = api.get_all_user_playlist_contents() google_playlists = {p['name']: p for p in all_google_playlists} for name in itunes_playlists: sync_playlist(api, server_db, lib.getPlaylist(name), google_playlists) finally: if api: api.logout() return 0
def get_albums_from_playlist(config): login, password, playlist_name, android_id = map(config.get, ('login', 'password', 'playlist', 'android_id')) api = Mobileclient() if not android_id: android_id = Mobileclient.FROM_MAC_ADDRESS try: api.login(login, password, android_id) all_playlists = api.get_all_user_playlist_contents() matched_playlist = next(playlist for playlist in all_playlists if playlist['name'].lower() == playlist_name.lower()) album_list = {(entry['track']['albumArtist'], entry['track']['album']) for entry in matched_playlist['tracks'] if 'track' in entry} return album_list except StopIteration: sys.exit('playlist not found.') except NotLoggedIn: sys.exit('wrong username or password.') finally: api.logout()
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.mc.get_all_user_playlist_contents() #self.playlists = self.mc.get_all_playlists() #self.playlists = self.wc.get_all_playlist_ids(auto=False) self.all_songs = self.mc.get_all_songs() #print "Got %d playlists." % len(self.playlists['user']) print "Got %d playlists containing %d songs." % (len(self.playlists), len(self.all_songs)) print "" def auth(self): self.logged_in = self.mc.login(self.email, self.password) #self.logged_in = self.wc.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 sync_playlist(self, filename, remove_missing): #def sync_playlist(self, filename, remove_missing=False): filename = self.get_platform_path(filename) os.chdir(os.path.dirname(filename)) title = os.path.splitext(os.path.basename(filename))[0] print "Syncing playlist: %s" % filename #if title not in self.playlists['user']: #print " didn't exist... creating..." #self.playlists['user'][title] = [self.wc.create_playlist(title)] print "" plid = "" for pl in self.playlists: if pl['name'] == title: plid = pl['id'] goog_songs = pl['tracks'] if plid == "": print " didn't exist... creating..." plid = self.mc.create_playlist(self, title) #plid = self.playlists['user'][title][0] #goog_songs = self.wc.get_playlist_songs(plid) print "%d songs already in Google Music playlist" % len(goog_songs) pc_songs = self.get_files_from_playlist(filename) print "%d songs in local playlist" % len(pc_songs) print "" # 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 fn in pc_songs: if self.file_already_in_list(fn, goog_songs, self.all_songs): existing_files += 1 continue print "" print "Adding: %s" % os.path.basename(fn).encode('cp1252') #print "Adding: %s" % os.path.basename(fn) #online = False online = self.find_song(fn, goog_songs, self.all_songs) #online = self.find_song(fn) song_id = None if online: song_id = online['id'] print " already uploaded [%s]" % song_id else: attempts = 0 result = [] while not result and attempts < MAX_UPLOAD_ATTEMPTS_PER_FILE: print " uploading... (may take a while)" attempts += 1 try: result = self.mm.upload(fn) except (BadStatusLine, CannotSendRequest): # Bail out if we're getting too many disconnects if fatal_count >= MAX_CONNECTION_ERRORS_BEFORE_QUIT: print "" print "Too many disconnections - quitting. Please try running the script again." print "" exit() print "Connection Error -- Reattempting login" fatal_count += 1 self.wc.logout() self.mc.logout() self.mm.logout() result = [] time.sleep(STANDARD_SLEEP) except: result = [] time.sleep(STANDARD_SLEEP) try: if result[0]: song_id = result[0].itervalues().next() else: song_id = result[1].itervalues().next() print " upload complete [%s]" % song_id except: print " upload failed - skipping" tag = self.get_id3_tag(fn) print " failed song:\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) if not song_id: failed_files += 1 continue added = self.mc.add_songs_to_playlist(plid, song_id) time.sleep(.3) # Don't spam the server too fast... print " done adding to playlist" added_files += 1 if remove_missing: for g in goog_songs: for s in self.all_songs: if g['trackId'] == s['id']: print "" print "Removing: %s" % s['title'].encode('cp1252') self.mc.remove_entries_from_playlist(g['id']) #self.wc.remove_songs_from_playlist(plid, s.id) time.sleep(.3) # Don't spam the server too fast... removed_files += 1 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_files_from_playlist(self, filename): files = [] f = codecs.open(filename, encoding='cp1252') #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 file_already_in_list(self, filename, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) i = 0 while i < len(goog_songs): for s in all_songs: if goog_songs[i]['trackId'] == s['id']: if self.tag_compare(s, tag): print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) 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, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) #results = self.wc.search(tag['title']) # NOTE - diagnostic print here to check results if you're creating duplicates #print results['song_hits'] #for r in goog_songs: #for r in results['song_hits']: for s in all_songs: #if r['trackId'] == s['id']: if self.tag_compare(s, tag): # TODO: add rough time check to make sure its "close" print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) return s return None def tag_compare(self, g_song, tag): # If a google result has no track, google doesn't return a field for it if 'title' not in g_song: g_song['title'] = "" if 'artist' not in g_song: g_song['artist'] = "" if 'album' not in g_song: g_song['album'] = "" if 'track' not in g_song: g_song['track'] = 0 if (g_song['title'].lower() == tag['title'].lower() and g_song['artist'].lower() == tag['artist'].lower()) or\ (g_song['album'].lower() == tag['album'].lower() and g_song['title'].lower() == tag['title'].lower()) or\ (g_song['artist'].lower() == tag['artist'].lower() and g_song['album'].lower() == tag['album'].lower() and g_song['track'] == tag['track']): print "Partial match\t%s\t%s\t%s" % (g_song['title'].encode('cp1252'), g_song['artist'].encode('cp1252'), g_song['album'].encode('cp1252')) return g_song['title'].lower() == tag['title'].lower() and\ g_song['artist'].lower() == tag['artist'].lower() and\ g_song['album'].lower() == tag['album'].lower() #and\ #g_song['track'] == tag['track'] def delete_song(self, sid): self.mc.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 GoogleMusicController(object): def __init__(self): self.device_id = os.environ['GOOGLE_MUSIC_DEVICE_ID'] self.client = Mobileclient(debug_logging=False) # TODO: change this to relative path from run location self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS, 'iota/auth.json') self.player_data = Manager().dict() self.player = None self.player_pid = None self.playlist = Playlist('Now Playing') def _get_entity(self, name: str, type: str, extra_filter=lambda _: True): results = self.search(name).__dict__[type] if len(results) == 0: return None results = list(filter(extra_filter, results)) if len(results) == 0: return None # We will trust Google's ability to filter search results... :P return results[0] def _get_song(self, name: str, artist: str = '', album: str = '') -> Song: if artist != '' and album != '': return self._get_entity( name, 'songs', lambda x: x.artist.lower() == artist and x.album .lower() == album) if artist != '': return self._get_entity(name, 'songs', lambda x: x.artist.lower() == artist) if album != '': return self._get_entity(name, 'songs', lambda x: x.album.lower() == album) return self._get_entity(name, 'songs') def _get_album(self, name: str, artist: str = '') -> Album: if artist != '': return self._get_entity(name, 'albums', lambda x: x.artist == artist) return self._get_entity(name, 'albums') def _get_artist(self, name: str) -> Artist: return self._get_entity(name, 'artists') def _get_playlist(self, name: str) -> Playlist: playlists = self.client.get_all_user_playlist_contents() matched_playlists = [] for p in playlists: p_name = p['name'].lower() if p_name == name or p_name == name.replace(' ', ''): matched_playlists.append(p) if len(matched_playlists) > 0: found = matched_playlists[0] self.playlist = Playlist(found['name']) [ self.playlist.add_song(track['track']) for track in found['tracks'] ] return self.playlist return None def play_song(self, name: str, callback_at_end, artist: str = '', album: str = ''): song = self._get_song(name, artist, album) if song is None: return f'I couldn\'t find a song called {name} by {artist}' return self.play_playlist('Now Playing', callback_at_end, song_list=[song]) def play_playlist( self, name: str, callback_at_end, song_list=[], start=0, shuffle=False, ) -> str: if song_list == []: self.playlist = self._get_playlist(name) if self.playlist is None: return f'I couldn\'t find a playlist called {name}' else: self.playlist = Playlist(name) self.playlist.set_list(song_list) if shuffle: self.playlist.shuffle() # Embed this so we don't have to pass a bunch of context out def get_url(id): # we need to logout and log back in to allow rapid requesting # of stream_urls -- they expire after a minute, and can't be # re-requested before then without an SSLError...thanks Google. self.client.logout() self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS, 'auth.json') return self.client.get_stream_url(id, device_id=self.device_id) # Spawn a subprocess for the player self.player = Process(target=spawn_player, args=(get_url, self.playlist, self.player_data, callback_at_end, start)) self.player.start() self.player_pid = self.player.pid return None def pause_song(self): if 'pid' in self.player_data.keys(): psutil.Process(self.player_data['pid']).send_signal(signal.SIGSTOP) def resume_song(self): if 'pid' in self.player_data.keys(): psutil.Process(self.player_data['pid']).send_signal(signal.SIGCONT) def stop_player(self): if 'pid' in self.player_data.keys(): psutil.Process(self.player_data['pid']).send_signal(signal.SIGSTOP) # self.player.terminate() def next_song(self) -> str: if 'pid' in self.player_data.keys(): psutil.Process(self.player_data['pid']).send_signal(signal.SIGTERM) def previous_song(self) -> str: if 'index' not in self.player_data.keys(): return 'Could not start the playlist, missing index' idx = self.player_data['index'] idx = idx - 1 if idx > 0 else 0 if not self.player_data['done']: self.stop_player() self.play_playlist(self.playlist.name.lower(), self.playlist.songs, start=idx) return '' def start_over(self): return '' def search(self, query: str, max_results: int = 100) -> SearchResults: results = self.client.search(query, max_results) return SearchResults(results)
class BasePlayer(object): def __init__(self, *, email=None, password=None, interval=3, width=50, shuffle=True, repeat=True, loop=False): self.api = Mobileclient() self.vlc_media_player = vlc.MediaPlayer() self.interval = abs(interval) self.width = int(abs(width)) self.shuffle = shuffle self.repeat = repeat self.loop = loop if email is not None and password is not None: self._logged_in = False self.api_login(email, password) else: self._logged_in = False def api_login(self, email, password): attempts = 0 while not self._logged_in and attempts < 3: self._logged_in = self.api.login(email, password, Mobileclient.FROM_MAC_ADDRESS) attempts += 1 def close(self): if self._logged_in: self.api.logout() def prepare(self): if (not self._logged_in) or (not self.api.is_authenticated()): raise LoginFailure else: return True def start(self): try: self._run_player() except (KeyboardInterrupt, PlayerExitException): self.close() print('\nGood bye') finally: return True def get_tracks(self): # This method returns list of tracks raise NotImplementedError def _loop_index(self, index, cmd, length): if self.repeat: index = loop_index(index, cmd, length) else: index += 1 return index def _run_player(self): while True: tracks = self.get_tracks() if self.shuffle: random.shuffle(tracks) i = 0 ns = 0 while i < len(tracks): try: track_id = choose_track_id(tracks[i]) except KeyError: i = self._loop_index(index=i, cmd='f', length=len(tracks)) continue except StoredTrackError: ns += 1 i = loop_index(index=i, cmd='f', length=len(tracks)) warnings.warn('Track is not in the store.\n') if ns >= len(tracks): warnings.warn('All tracks are not in the store.\n') break else: continue cmd = self._play_track(track_id) if cmd == 's': break i = self._loop_index(index=i, cmd=cmd, length=len(tracks)) def _play_track(self, track_id): self.prepare() try: info = self.api.get_track_info(track_id) url = self.api.get_stream_url(track_id) except CallFailure as e: warnings.warn(str(e)) return 'f' tmp = tempfile.NamedTemporaryFile(delete=False) def close_player(): self.vlc_media_player.stop() tmp.close() os.remove(tmp.name) try: tmp.write(urllib.request.urlopen(url).read()) self.vlc_media_player.set_mrl(tmp.name) self.vlc_media_player.play() paused = False duration = int(info['durationMillis']) while True: clear_screen() print_track_info(info) current = self.vlc_media_player.get_time() remain = (duration - current) / 1000 timeout = min(remain, self.interval) print_bar(current, duration, remain, self.width) print_command_list() if paused: cmd = input('PAUSED\n>>') else: try: cmd = inputimeout(timeout=timeout, prompt='>>') except TimeoutOccurred: if remain > self.interval: continue if self.loop: cmd = 'r' else: cmd = 'f' if is_next(cmd): close_player() return cmd elif is_quit(cmd): raise PlayerExitException elif cmd == 'p': paused = not paused self.vlc_media_player.pause() except BaseException: close_player() raise
category_b_playlist = [p for p in playlists if p['name'] == secret.CATEGORY_B][0] # Splitting up songs by whether they are categorized or not all_songs_set = get_trackids_set(big_playlist) cat_a_songs_set = get_trackids_set(category_a_playlist) cat_b_songs_set = get_trackids_set(category_b_playlist) all_categorized_songs_set = cat_a_songs_set | cat_b_songs_set uncategorized_songs_set = all_songs_set - all_categorized_songs_set # Remove songs from uncategorized that have been categorized already print("Removing newly categorized songs from the uncategorized playlist named", secret.UNCATEGORIZED) uncategorized_playlist_songs_set = get_trackids_set(uncategorized_playlist) already_categorized_songs_set = all_categorized_songs_set & uncategorized_playlist_songs_set already_categorized_songs_ids = [song['id'] for song in uncategorized_playlist['tracks'] if song['trackId'] in already_categorized_songs_set] api.remove_entries_from_playlist(already_categorized_songs_ids) print("Removed", len(already_categorized_songs_ids), "songs from the uncategorized playlist named", secret.UNCATEGORIZED) # Add songs that are not categorized, to uncategorized # First, don't re-add songs that are still uncategorized print("Adding uncategorized songs to playlist named", secret.UNCATEGORIZED) newly_uncategorized_songs_set = uncategorized_songs_set - uncategorized_playlist_songs_set api.add_songs_to_playlist(uncategorized_playlist['id'], list(newly_uncategorized_songs_set)) print("Success! Added", len(newly_uncategorized_songs_set), "songs to playlist named", secret.UNCATEGORIZED) api.logout() print("Logged out")
if not mc.is_subscribed: print("This user is not subscribed") print("Google Play Music empties playlists while a user is unsubscribed") print("Therefore playlists transferred over will be empty\n") songs = mc.get_all_songs() playlists = mc.get_all_user_playlist_contents() #radios = mc.get_all_stations() #podcasts = mc.get_all_podcast_series() print("I've found " + str(len(songs)) + " songs in your library") print("I've found " + str(len(playlists)) + " user playlists in your library") #print("I've found " + str(len(radios)) + " radios in your library") #print("I've found " + str(len(podcasts)) + " podcasts in your library") mc.logout() print("Logged Out\n") #Logging into new account print("Log into the Play Music account that you want to transfer songs to") while 1: login(mc) if not mc.is_subscribed: print("This account is not subscribed") print("Please log in with a subscribed account\n") mc.logout() continue break #Adding songs
export_thumbs_up = export_api.get_thumbs_up_songs() # strip out any tracks that are not available on All Access thumbs_up_tracks = [t for t in export_thumbs_up if track_has_aa_data(t)] if migration_type == 'all' or migration_type == 'playlists': log.info('Retrieving playlists from ' + export_username) export_playlists = export_api.get_all_user_playlist_contents() playlists = [p for p in export_playlists if not p.get('deleted')] if migration_type == 'all' or migration_type == 'stations': log.info('Retrieving stations from ' + export_username) export_stations = export_api.get_all_stations() radio_stations = [s for s in export_stations if not s.get('deleted')] log.info('Export complete') export_api.logout() 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:
class MobileClientWrapper: def __init__(self, config): self.client = Mobileclient(debug_logging=False) login = self.client.login(config.user_name, config.password, Mobileclient.FROM_MAC_ADDRESS) if not login: raise ConnectionError( 'MobileClientWrapper - Login Error Please Check Google Play Username and Password' ) def logout(self): self.client.logout() def get_all_playlist_content(self): """ :return: list of all Playlist content as dictionaries. """ return self.client.get_all_user_playlist_contents() def create_playlist(self, new_playlist_name, description_text, public_bool): """ Creates a Playlist with given information and returns its id :param new_playlist_name: name to give new PlayList :param description_text: description text of new Playlist :param public_bool: True/False value to specify public sharing on new Playlist :return: playlist id """ return self.client.create_playlist(new_playlist_name, description=description_text, public=public_bool) def delete_playlist(self, play_list_id): """ Delete a Playlist with given Id :param play_list_id: playlist ID """ self.client.delete_playlist(play_list_id) def add_songs_to_playlist(self, play_list_id, song_ids): """ Adds given song(s) to given Playlist. :param play_list_id: id of the target Playlist :param song_ids: id(s) of the target Song to add :return: list of Playlist Entry ids added """ return self.client.add_songs_to_playlist(play_list_id, song_ids) def get_track_info(self, store_track_id): """ Returns information on a store track :param store_track_id: target TrackId """ return self.client.get_track_info(store_track_id) def search(self, search_query): """ Searches based on searchQuery :param search_query: query to run through music library :return: dictionary of hits """ return self.client.search(search_query, max_results=10)
class GPMClient(object): """ Google Play Music client. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" #------------------------------------------------------------------------------ def __init__(self, email, password, device_id): self.__api = Mobileclient() self.logged_in = False self.__device_id = device_id attempts = 0 while not self.logged_in and attempts < 3: self.logged_in = self.__api.login(email, password, device_id) attempts += 1 self.all_tracks = dict() self.playlists = dict() self.library = dict() #------------------------------------------------------------------------------ def logout(self): self.__api.logout() #------------------------------------------------------------------------------ def update_local_lib(self): songs = self.__api.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Get main library song_map = dict() for song in songs: if "rating" in song and song["rating"] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song["id"] song_artist = song["artist"] song_album = song["album"] song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = dict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): for album in self.library[artist].keys(): if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album # Get all playlists plists = self.__api.get_all_user_playlist_contents() for plist in plists: plist_name = plist["name"] self.playlists[plist_name] = list() for track in plist["tracks"]: if track["trackId"] not in song_map: song = song_map[track["trackId"]] = track["track"] song["id"] = track["trackId"] else: song = song_map[track["trackId"]] self.playlists[plist_name].append(song) #------------------------------------------------------------------------------ def get_stream_url(self, song): return self.__api.get_stream_url(song["id"], self.__device_id) #------------------------------------------------------------------------------ def rate_song(self, song, rating): try: song["rating"] = rating song_list = [song] self.__api.change_song_metadata(song_list) print "Gave a Thumbs Up to {0} by {1} on Google Play.".format( song["title"].encode("utf-8"), song["artist"].encode("utf-8")) except RuntimeError: print "Error giving a Thumbs Up on Google Play."
class GoogleMusicDownloader: LONG_FILE_DUR = 600000 FILE_NAME_RE = re.compile(r'[\\/:"*?<>|]+') def __init__(self): self.client = Mobileclient() logged_in = self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS) if path.exists(Mobileclient.OAUTH_FILEPATH) else False if not logged_in: print('No oauth credentials found, please authenticate your account') self.client.perform_oauth(open_browser=True) self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS) else: print('Logged in!') def __ask(self, text): inpt = input(text + ' (y/n): ').lower() return inpt.startswith('y') def __update_metadata(self, path = str(), info = dict()): with open(path, 'r+b') as mp3f: mp3 = MP3(mp3f) id3 = ID3() track_num = info.get('trackNumber', 1) id3.add(TRCK(encoding=3, text=[track_num if track_num > 0 else 1])) id3.add(TIT2(encoding=3, text=info['title'])) id3.add(TPE1(encoding=3, text=[info.get('artist', None)])) id3.add(TCOM(encoding=3, text=[info.get('composer', None)])) id3.add(TCON(encoding=3, text=[info.get('genre', None)])) id3.add(TAL(encoding=3, text=[info.get('album', None)])) year = info.get('year', 0) if year > 0: id3.add(TYER(encoding=3, text=[year])) if 'albumArtRef' in info and len(info['albumArtRef']) > 0: img_url = info['albumArtRef'][0]['url'] if img_url: req = requests.get(img_url, allow_redirects=True) id3.add(APIC(encoding=3, mime='image/jpeg', type=3, data=req.content)) mp3.tags = id3 mp3.save(mp3f) def __kill(self): self.client.logout() exit() def download_all_songs(self): print('Loading music library...') library = self.client.get_all_songs() print(len(library), 'tracks detected.') if len(library) == 0: self.__kill() current_path = path.dirname(path.realpath(__file__)) include_long_files = self.__ask('Also download long files? (10+ min)') if not self.__ask('Begin downloading?'): self.__kill() long_skipped_count = 0 errors_count = 0 successful_count = 0 song_num = 0 folder_path = path.join(current_path, 'downloads') if not path.exists(folder_path): mkdir(folder_path) for song in library: song_num += 1 song_id = song['id'] if song['id'] else song['storeId'] song_name = song['artist'] + ' - ' + song['title'] mp3_path = path.join(folder_path, self.FILE_NAME_RE.sub(' ', song_name) + '.mp3') song_name = '%d. %s' % (song_num, song_name) # song name with index number only for display if path.exists(mp3_path): print('Track', song_name, 'already exists! Updating metadata...') self.__update_metadata(mp3_path, song) continue if not include_long_files and int(song.get('durationMillis', 0)) >= self.LONG_FILE_DUR: long_skipped_count += 1 continue song_url = self.client.get_stream_url(song_id) if not song_url: print('Warning:', song_name, 'url is empty! Skip...') errors_count += 1 continue req = requests.get(song_url, allow_redirects=True, stream=True) if not req.ok: print(song_name, 'download error!') errors_count += 1 req.raise_for_status() continue total_size = int(req.headers.get('content-length')) with open(mp3_path, 'wb') as mp3f: with tqdm(total=total_size, unit='B', unit_scale=True, desc=song_name + '.mp3') as pbar: for chunk in req.iter_content(1024): if chunk: mp3f.write(chunk) mp3f.flush() pbar.update(len(chunk)) successful_count += 1 print('Filling metadata for', song_name) self.__update_metadata(mp3_path, song) status_text = 'Process complete! Downloaded: {downloaded}; ' if not include_long_files: status_text += 'Long files skipped: {long_skipped}; ' status_text += 'Errors count: {errors}' print(status_text.format(downloaded=successful_count, long_skipped=long_skipped_count, errors=errors_count)) self.client.logout()
class GoogleMusicLogin(): def __init__(self): import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning from requests.packages.urllib3.exceptions import InsecurePlatformWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) self.gmusicapi = Mobileclient(debug_logging=False, validate=False, verify_ssl=False) def checkCookie(self): # Remove cookie data if it is older then 7 days if utils.addon.getSetting('cookie-date') != None and len(utils.addon.getSetting('cookie-date')) > 0: import time if (datetime.now() - datetime(*time.strptime(utils.addon.getSetting('cookie-date'), '%Y-%m-%d %H:%M:%S.%f')[0:6])).days >= 7: self.clearCookie() def checkCredentials(self): if not utils.addon.getSetting('username'): utils.addon.openSettings() if utils.addon.getSetting('password') and utils.addon.getSetting('password') != '**encoded**': import base64 utils.addon.setSetting('encpassword',base64.b64encode(utils.addon.getSetting('password'))) utils.addon.setSetting('password','**encoded**') def getApi(self): return self.gmusicapi def getStreamUrl(self,song_id): # retrieve registered device device_id = self.getDevice() # retrieve stream quality from settings quality = { '0':'hi','1':'med','2':'low' } [utils.addon.getSetting('quality')] utils.log("getStreamUrl songid: %s device: %s quality: %s"%(song_id, device_id, quality)) return self.gmusicapi.get_stream_url(song_id, device_id, quality) def getDevice(self): return utils.addon.getSetting('device_id') def initDevice(self): device_id = self.getDevice() if not device_id: utils.log('Trying to fetch the device_id') self.login(True) try: devices = self.gmusicapi.get_registered_devices() if len(devices) == 10: utils.log("WARNING: 10 devices already registered!") utils.log(repr(devices)) for device in devices: if device["type"] in ("ANDROID","PHONE","IOS"): device_id = str(device["id"]) break except: pass if device_id: if device_id.lower().startswith('0x'): device_id = device_id[2:] utils.addon.setSetting('device_id', device_id) utils.log('Found device_id: '+device_id) else: #utils.log('No device found, using default.') #device_id = "333c60412226c96f" raise Exception('No devices found, registered mobile device required!') def clearCookie(self): utils.addon.setSetting('logged_in-mobile', "") utils.addon.setSetting('authtoken-mobile', "") utils.addon.setSetting('device_id', "") def logout(self): self.gmusicapi.logout() def login(self, nocache=False): if not utils.addon.getSetting('logged_in-mobile') or nocache: import base64 utils.log('Logging in') self.checkCredentials() username = utils.addon.getSetting('username') password = base64.b64decode(utils.addon.getSetting('encpassword')) try: self.gmusicapi.login(username, password, utils.addon.getSetting('device_id')) if not self.gmusicapi.is_authenticated(): self.gmusicapi.login(username, password, Mobileclient.FROM_MAC_ADDRESS) except Exception as e: utils.log(repr(e)) if not self.gmusicapi.is_authenticated(): utils.log("Login failed") utils.addon.setSetting('logged_in-mobile', "") self.language = utils.addon.getLocalizedString dialog = xbmcgui.Dialog() dialog.ok(self.language(30101), self.language(30102)) #utils.addon.openSettings() raise else: utils.log("Login succeeded") utils.addon.setSetting('logged_in-mobile', "1") utils.addon.setSetting('authtoken-mobile', self.gmusicapi.session._authtoken) utils.addon.setSetting('cookie-date', str(datetime.now())) else: utils.log("Loading auth from cache") self.gmusicapi.session._authtoken = utils.addon.getSetting('authtoken-mobile') self.gmusicapi.session.is_authenticated = True
''' Created on Jan 12, 2015 @author: snoecker ''' if __name__ == '__main__': pass from gmusicapi import Mobileclient, Api import login api = Mobileclient() GAcct=login.readGoogleLogin() if GAcct: logged_in = api.login(GAcct.GUserName, GAcct.GPassword) # logged_in is True if login was successful if logged_in == True: print "logged in" song_list= api.get_all_songs(True) for songs in song_list: print "size of list" + str(len(songs)) logged_out= api.logout() print "logged out " + str(logged_out)
class Main: def __init__(self): GPIO.setmode(GPIO.BOARD) self.display = LCDDisplay.LCDThread() self.display.spool_string_value("GPM-Connecting..", "Please wait ") self.mus = Mobileclient() fd = open('Secret.txt', 'r') data = fd.read().split("\n") fd.close() self.mus.login(data[0], data[1], Mobileclient.FROM_MAC_ADDRESS) self.Lib = self.mus.get_all_songs() self.playlists = self.mus.get_all_user_playlist_contents() tplay = self.playlists self.playlists = {} self.index = 0 for pl in tplay: out = self.append_song_to_playlist(pl, songs=self.Lib) self.playlists[out["name"]] = out self.lists_w_id = [{'quit': 0}] for item in self.playlists.values(): self.lists_w_id.append({item["name"]: item["id"]}) self.display.spool_string_value("Ready ", "Awaiting input ") self.button_set_up() self.display.spool_string_value( self.lists_w_id[self.index].items()[0][0][:16]) self.player = MusicPlayer.PlayerThread(self.playlists, self.display, self, self.Lib) self.working = True try: while self.working: time.sleep(10) except KeyboardInterrupt: pass finally: self.display.spool_string_value("Stopping...", "Have a nice day") self.end_clear() def get_songs(self): return self.Lib def get_core(self): return self.mus # noinspection PyUnusedLocal def previous_button(self, channel): # button left, pin 21 self.index -= 1 if self.index < 0: self.index = len(self.lists_w_id) - 1 self.display.spool_string_value( self.lists_w_id[self.index].items()[0][0][:16]) # noinspection PyUnusedLocal def next_button(self, channel): # button right, pin 2 self.index += 1 if self.index >= len(self.lists_w_id): self.index = 0 self.display.spool_string_value( self.lists_w_id[self.index].items()[0][0][:16]) # noinspection PyUnusedLocal def accept_button(self, channel): # button down, pin 24 if self.lists_w_id[self.index].items()[0][0] == "quit": self.working = False else: self.player.stop_music() self.player.set_playlist_to_play( self.lists_w_id[self.index].items()[0][1]) # noinspection PyUnusedLocal def random_button(self, channel): # button up, pin 19 self.player.stop_music() self.player.play_random() def button_set_up(self): GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # up GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # left GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # right GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # down GPIO.add_event_detect(19, GPIO.RISING, callback=self.random_button, bouncetime=300) GPIO.add_event_detect(21, GPIO.RISING, callback=self.previous_button, bouncetime=300) GPIO.add_event_detect(23, GPIO.RISING, callback=self.next_button, bouncetime=300) GPIO.add_event_detect(24, GPIO.RISING, callback=self.accept_button, bouncetime=300) def end_clear(self): self.display.stop() self.player.stop() self.mus.logout() GPIO.cleanup() # noinspection PyMethodMayBeStatic def append_song_to_playlist(self, playlist, mus_entity=None, songs=None): if not songs: songs = mus_entity.get_all_songs() out_list = [] for track in playlist['tracks']: if track['source'] == 2: out_list.append(track) else: for song in songs: if song['id'] == track['trackId']: out_list.append(song) playlist['songs'] = out_list return playlist
class GoogleMusicApi: def __init__(self): self._api = Mobileclient() def login(self, username, password, device_id): try: return self._api.login(username, password, device_id) except AlreadyLoggedIn: Logger.debug('API: Already logged in') return True def relogin(self, username, password, device_id): try: return self._api.login(username, password, device_id) except AlreadyLoggedIn: self._api.logout() return self._api.login(username, password, device_id) def logout(self): return self._api.logout() def get_registered_mobile_devices(self): devices = self._api.get_registered_devices() mobile_devices = [] for device in devices: if device['type'] == "ANDROID": # TODO: Add iOS mobile_devices.append({ 'name': device['friendlyName'], 'id': device['id'][2:] }) return mobile_devices def get_stream_url(self, track_id, quality): return self._api.get_stream_url(song_id=track_id, quality=quality) def get_library(self): return self._api.get_all_songs() def get_album_info(self, album_id): return self._api.get_album_info(album_id) def search(self, query, max_results=25): # TODO: make number of results configurable / add to settings try: return self._api.search_all_access(query, max_results) # TODO: remove when gmusicapi 9.0.1 is stable except AttributeError: # develop version of gmusicapi is installed return self._api.search(query, max_results) def get_station_tracks(self, title, seed, num_tracks=25, recently_played_ids=None): # TODO: make number of results configurable / add to settings # TODO: check for existing stations, so we don't always create new ones (maybe not necessary: stations created with same seed have the same id seed_type = seed['type'] seed = seed['seed'] station_id = '' Logger.debug('Station: Creating station (Title: {}, Seed: {}, Type:{}'.format(title, seed, seed_type)) if seed_type == 'track': station_id = self.create_station(title, track_id=seed) elif seed_type == 'artist': station_id = self.create_station(title, artist_id=seed) elif seed_type == 'album': station_id = self.create_station(title, album_id=seed) elif seed_type == 'genre': station_id = self.create_station(title, genre_id=seed) elif seed_type == 'curated': Logger.debug("Station: CuratedStationId seed, don't know what to do :(") else: Logger.error("Station: Unknown seed, don't know what to do :(") if station_id: Logger.debug('Station: ID is ' + station_id) station_tracks = self._api.get_station_tracks(station_id, num_tracks, recently_played_ids) Logger.debug('Station: Station has {} tracks'.format(len(station_tracks))) return station_tracks else: Logger.warning("Station: Could not retrieve station ID") return [] def get_feeling_lucky_station_tracks(self, num_tracks=25, recently_played_ids=None): # TODO: make number of results configurable / add to settings return self._api.get_station_tracks('IFL', num_tracks, recently_played_ids) def create_station(self, name, track_id=None, artist_id=None, album_id=None, genre_id=None, playlist_token=None): return self._api.create_station(name, track_id=track_id, artist_id=artist_id, album_id=album_id, genre_id=genre_id, playlist_token=playlist_token) def increment_track_playcount(self, track_id): self._api.increment_song_playcount(track_id)
class GMusicSession(object): def __init__(self): super(GMusicSession, self).__init__() logger.info('Mopidy uses Google Music') self.api = Mobileclient() def login(self, username, password, deviceid): if self.api.is_authenticated(): self.api.logout() try: self.api.login(username, password) except CallFailure as error: logger.error(u'Failed to login as "%s": %s', username, error) if self.api.is_authenticated(): if deviceid is None: self.deviceid = self.get_deviceid(username, password) else: self.deviceid = deviceid else: return False def logout(self): if self.api.is_authenticated(): return self.api.logout() else: return True def get_all_songs(self): if self.api.is_authenticated(): return self.api.get_all_songs() else: return {} def get_stream_url(self, song_id): if self.api.is_authenticated(): try: return self.api.get_stream_url(song_id, self.deviceid) except CallFailure as error: logger.error(u'Failed to lookup "%s": %s', song_id, error) def get_all_playlist_contents(self): if self.api.is_authenticated(): return self.api.get_all_user_playlist_contents() else: return {} def get_shared_playlist_contents(self, shareToken): if self.api.is_authenticated(): return self.api.get_shared_playlist_contents(shareToken) else: return {} def get_all_playlists(self): if self.api.is_authenticated(): return self.api.get_all_playlists() else: return {} def get_deviceid(self, username, password): logger.warning(u'No mobile device ID configured. ' u'Trying to detect one.') webapi = Webclient(validate=False) webapi.login(username, password) devices = webapi.get_registered_devices() deviceid = None for device in devices: if device['type'] == 'PHONE' and device['id'][0:2] == u'0x': # Omit the '0x' prefix deviceid = device['id'][2:] break webapi.logout() if deviceid is None: logger.error(u'No valid mobile device ID found. ' u'Playing songs will not work.') else: logger.info(u'Using mobile device ID %s', deviceid) return deviceid def get_track_info(self, store_track_id): if self.api.is_authenticated(): try: return self.api.get_track_info(store_track_id) except CallFailure as error: logger.error(u'Failed to get All Access track info: %s', error) def get_album_info(self, albumid, include_tracks=True): if self.api.is_authenticated(): try: return self.api.get_album_info(albumid, include_tracks) except CallFailure as error: logger.error(u'Failed to get All Access album info: %s', error)
def main(): global player global api parser = OptionParser() parser.add_option("-p", "--playlist", dest="playlist", help="Playlist (Name or ID)") parser.add_option("-t", "--track", dest="track", help="Track (Name or ID)") parser.add_option("-l", "--listen", action="store_true", dest="listen", help="Start listening") parser.add_option("-c", "--continue", action="store_true", dest="cont", help="Continue playlist after track") parser.add_option("-s", "--shuffle", action="store_true", dest="shuffle", help="Randomize playlist") (opts, args) = parser.parse_args() config = ConfigParser.RawConfigParser() directory = os.path.dirname(os.path.realpath(sys.argv[0])) config.read(directory + '/.gmusicpy') username = config.get('gmusic', 'username') password = config.get('gmusic', 'password') api = Mobileclient() api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) if not api.is_authenticated(): print "Bad username/password" return id = 0 try: if(opts.playlist): playlists = api.get_all_user_playlist_contents() playlist = findPlaylist(opts.playlist, playlists) if(playlist is None): print 'Playlist not found' return if(opts.track): item = findTrack(opts.track, playlist) if(item is None): print 'Track not found' return track = item['track'] track['trackId'] = item['trackId'] tracklist.append(track) else: for item in playlist['tracks']: track = item['track'] track['trackId'] = item['trackId'] tracklist.append(track) if(opts.shuffle): shuffle(tracklist) if(opts.listen): track = tracklist.pop(0) printTrack("", track) url = api.get_stream_url(track['trackId']) player = play(url) else: for track in tracklist: printTrack(id, track) id = id + 1 else: playlists = api.get_all_playlists() for playlist in playlists: print str(id) + ' ' + playlist['name'] id = id + 1 while(True): if player == None: break if isinstance(player, subprocess.Popen) and player.poll() != None: if(len(tracklist) > 0): track = tracklist.pop(0) printTrack("", track) url = api.get_stream_url(track['trackId']) player = play(url) else: break; sleep(0.2) finally: if isinstance(player, subprocess.Popen): player.terminate() api.logout()
print "##### {} of {} Songs Found. #####".format(len(songs), len(rows)) log("### Missing Songs ###") for row in missing: log(decode(row['title']) + " could not be found.") log("### Fetching Playlists ###") playlists = api.get_all_user_playlist_contents() playlist = [p for p in playlists if p['name'] == gmusic_playlist_name] song_ids = [s['id'] for s in songs] if len(playlist) == 0: # Playlist not found, create a new one. playlist_id = api.create_playlist(u'foobar2000') else: playlist_id = playlist[0]['id'] # Prevent duplicated songs in existing playlist for track in playlist[0]['tracks']: if track['trackId'] in song_ids: song_ids.remove(track['trackId']) log("Importing to Playlist ID: " + playlist_id) api.add_songs_to_playlist(playlist_id, song_ids) log("### Import Completed ###") logfile.close() api.logout()
class tizgmusicproxy(object): """A class for accessing a Google Music account to retrieve song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" def __init__(self, email, password, device_id): self.__api = Mobileclient() self.logged_in = False self.__device_id = device_id self.queue = list() self.queue_index = -1 self.play_mode = 0 self.now_playing_song = None attempts = 0 while not self.logged_in and attempts < 3: self.logged_in = self.__api.login(email, password) attempts += 1 self.playlists = CaseInsensitiveDict() self.library = CaseInsensitiveDict() def logout(self): self.__api.logout() def update_local_lib(self): songs = self.__api.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Get main library song_map = dict() for song in songs: if "rating" in song and song["rating"] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song["id"] song_artist = song["artist"] song_album = song["album"] song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if not (song_artist in self.library): self.library[song_artist] = dict() self.library[song_artist][self.all_songs_album_title] = list() if not (song_album in self.library[song_artist]): self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : {0}".format(artist.encode("utf-8"))) for album in self.library[artist].keys(): logging.info(" Album : {0}".format(album.encode("utf-8"))) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k["title"]) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get("trackNumber", 0)) self.library[artist][album] = sorted_album # Get all playlists plists = self.__api.get_all_user_playlist_contents() for plist in plists: plist_name = plist["name"] self.playlists[plist_name] = list() for track in plist["tracks"]: try: song = song_map[track["trackId"]] self.playlists[plist_name].append(song) except IndexError: pass def current_song_title_and_artist(self): logging.info("current_song_title_and_artist") song = self.now_playing_song if song is not None: title = self.now_playing_song["title"] artist = self.now_playing_song["artist"] logging.info("Now playing {0} by {1}".format(title.encode("utf-8"), artist.encode("utf-8"))) return artist.encode("utf-8"), title.encode("utf-8") else: return "", "" def current_song_album_and_duration(self): logging.info("current_song_album_and_duration") song = self.now_playing_song if song is not None: album = self.now_playing_song["album"] duration = self.now_playing_song["durationMillis"] logging.info("album {0} duration {1}".format(album.encode("utf-8"), duration.encode("utf-8"))) return album.encode("utf-8"), int(duration) else: return "", 0 def current_song_track_number_and_total_tracks(self): logging.info("current_song_track_number_and_total_tracks") song = self.now_playing_song if song is not None: track = self.now_playing_song["trackNumber"] total = self.now_playing_song["totalTrackCount"] logging.info("track number {0} total tracks {1}".format(track, total)) return track, total else: logging.info("current_song_track_number_and_total_tracks : not found") return 0, 0 def clear_queue(self): self.queue = list() self.queue_index = -1 def enqueue_artist(self, arg): try: artist = self.library[arg] count = 0 for album in artist: for song in artist[album]: self.queue.append(song) count += 1 logging.info("Added {0} tracks by {1} to queue".format(count, arg)) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def enqueue_album(self, arg): try: for artist in self.library: for album in self.library[artist]: logging.info("enqueue album : {0} | {1}".format(artist.encode("utf-8"), album.encode("utf-8"))) if album.lower() == arg.lower(): count = 0 for song in self.library[artist][album]: self.queue.append(song) count += 1 logging.info( "Added {0} tracks from {1} by " "{2} to queue".format(count, album.encode("utf-8"), artist.encode("utf-8")) ) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def enqueue_playlist(self, arg): try: playlist = self.playlists[arg] count = 0 for song in playlist: self.queue.append(song) count += 1 logging.info("Added {0} tracks from {1} to queue".format(count, arg)) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def next_url(self): logging.info("next_url") if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): next_song = self.queue[self.queue_index] return self.__get_song_url(next_song) else: self.queue_index = -1 return self.next_url() else: return "" def prev_url(self): if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): prev_song = self.queue[self.queue_index] return self.__get_song_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return "" def __get_song_url(self, song): song_url = self.__api.get_stream_url(song["id"], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve song url!") raise
class tizgmusicproxy(object): """A class for logging into a Google Play Music account and retrieving song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" def __init__(self, email, password, device_id): self.__gmusic = Mobileclient() self.__email = email self.__device_id = device_id self.logged_in = False self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_song = None userdir = os.path.expanduser('~') tizconfig = os.path.join(userdir, ".config/tizonia/." + email + ".auth_token") auth_token = "" if os.path.isfile(tizconfig): with open(tizconfig, "r") as f: auth_token = pickle.load(f) if auth_token: # 'Keep track of the auth token' workaround. See: # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198 print_msg("[Google Play Music] [Authenticating] : " \ "'with cached auth token'") self.__gmusic.android_id = device_id self.__gmusic.session._authtoken = auth_token self.__gmusic.session.is_authenticated = True try: self.__gmusic.get_registered_devices() except CallFailure: # The token has expired. Reset the client object print_wrn("[Google Play Music] [Authenticating] : " \ "'auth token expired'") self.__gmusic = Mobileclient() auth_token = "" if not auth_token: attempts = 0 print_nfo("[Google Play Music] [Authenticating] : " \ "'with user credentials'") while not self.logged_in and attempts < 3: self.logged_in = self.__gmusic.login(email, password, device_id) attempts += 1 with open(tizconfig, "a+") as f: f.truncate() pickle.dump(self.__gmusic.session._authtoken, f) self.library = CaseInsensitiveDict() self.song_map = CaseInsensitiveDict() self.playlists = CaseInsensitiveDict() self.stations = CaseInsensitiveDict() def logout(self): """ Reset the session to an unauthenticated, default state. """ self.__gmusic.logout() def set_play_mode(self, mode): """ Set the playback mode. :param mode: curren tvalid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def current_song_title_and_artist(self): """ Retrieve the current track's title and artist name. """ logging.info("current_song_title_and_artist") song = self.now_playing_song if song: title = to_ascii(self.now_playing_song.get('title')) artist = to_ascii(self.now_playing_song.get('artist')) logging.info("Now playing %s by %s", title, artist) return artist, title else: return '', '' def current_song_album_and_duration(self): """ Retrieve the current track's album and duration. """ logging.info("current_song_album_and_duration") song = self.now_playing_song if song: album = to_ascii(self.now_playing_song.get('album')) duration = to_ascii \ (self.now_playing_song.get('durationMillis')) logging.info("album %s duration %s", album, duration) return album, int(duration) else: return '', 0 def current_track_and_album_total(self): """Return the current track number and the total number of tracks in the album, if known. """ logging.info("current_track_and_album_total") song = self.now_playing_song track = 0 total = 0 if song: try: track = self.now_playing_song['trackNumber'] total = self.now_playing_song['totalTrackCount'] logging.info("track number %s total tracks %s", track, total) except KeyError: logging.info("trackNumber or totalTrackCount : not found") else: logging.info("current_song_track_number_" "and_total_tracks : not found") return track, total def current_song_year(self): """ Return the current track's year of publication. """ logging.info("current_song_year") song = self.now_playing_song year = 0 if song: try: year = song['year'] logging.info("track year %s", year) except KeyError: logging.info("year : not found") else: logging.info("current_song_year : not found") return year def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def enqueue_artist(self, arg): """ Search the user's library for tracks from the given artist and adds them to the playback queue. :param arg: an artist """ try: self.__update_local_library() artist = None if arg not in self.library.keys(): for name, art in self.library.iteritems(): if arg.lower() in name.lower(): artist = art print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ name.encode('utf-8'))) break if not artist: # Play some random artist from the library random.seed() artist = random.choice(self.library.keys()) artist = self.library[artist] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: artist = self.library[arg] tracks_added = 0 for album in artist: tracks_added += self.__enqueue_tracks(artist[album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(artist))) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) def enqueue_album(self, arg): """ Search the user's library for albums with a given name and adds them to the playback queue. """ try: self.__update_local_library() album = None artist = None tentative_album = None tentative_artist = None for library_artist in self.library: for artist_album in self.library[library_artist]: print_nfo("[Google Play Music] [Album] '{0}'." \ .format(to_ascii(artist_album))) if not album: if arg.lower() == artist_album.lower(): album = artist_album artist = library_artist break if not tentative_album: if arg.lower() in artist_album.lower(): tentative_album = artist_album tentative_artist = library_artist if album: break if not album and tentative_album: album = tentative_album artist = tentative_artist print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album.encode('utf-8'))) if not album: # Play some random album from the library random.seed() artist = random.choice(self.library.keys()) album = random.choice(self.library[artist].keys()) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not album: raise KeyError("Album not found : {0}".format(arg)) self.__enqueue_tracks(self.library[artist][album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(album))) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) def enqueue_playlist(self, arg): """Search the user's library for playlists with a given name and adds the tracks of the first match to the playback queue. Requires Unlimited subscription. """ try: self.__update_local_library() self.__update_playlists() self.__update_playlists_unlimited() playlist = None playlist_name = None for name, plist in self.playlists.items(): print_nfo("[Google Play Music] [Playlist] '{0}'." \ .format(to_ascii(name))) if arg not in self.playlists.keys(): for name, plist in self.playlists.iteritems(): if arg.lower() in name.lower(): playlist = plist playlist_name = name print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ to_ascii(name))) break if not playlist: # Play some random playlist from the library random.seed() playlist_name = random.choice(self.playlists.keys()) playlist = self.playlists[playlist_name] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: playlist_name = arg playlist = self.playlists[arg] self.__enqueue_tracks(playlist) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(playlist_name))) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) def enqueue_station_unlimited(self, arg): """Search the user's library for a station with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ try: # First try to find a suitable station in the user's library self.__enqueue_user_station_unlimited(arg) if not len(self.queue): # If no suitable station is found in the user's library, then # search google play unlimited for a potential match. self.__enqueue_station_unlimited(arg) if not len(self.queue): raise KeyError except KeyError: raise KeyError("Station not found : {0}".format(arg)) def enqueue_genre_unlimited(self, arg): """Search Unlimited for a genre with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \ .format(self.__email)) try: all_genres = list() root_genres = self.__gmusic.get_genres() second_tier_genres = list() for root_genre in root_genres: second_tier_genres += self.__gmusic.get_genres(root_genre['id']) all_genres += root_genres all_genres += second_tier_genres for genre in all_genres: print_nfo("[Google Play Music] [Genre] '{0}'." \ .format(to_ascii(genre['name']))) genre = dict() if arg not in all_genres: genre = next((g for g in all_genres \ if arg.lower() in to_ascii(g['name']).lower()), \ None) tracks_added = 0 while not tracks_added: if not genre and len(all_genres): # Play some random genre from the search results random.seed() genre = random.choice(all_genres) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) genre_name = genre['name'] genre_id = genre['id'] station_id = self.__gmusic.create_station(genre_name, \ None, None, None, genre_id) num_tracks = 200 tracks = self.__gmusic.get_station_tracks(station_id, num_tracks) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", tracks_added, genre_name) if not tracks_added: # This will produce another iteration in the loop genre = None print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(genre['name']))) self.__update_play_queue_order() except KeyError: raise KeyError("Genre not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_situation_unlimited(self, arg): """Search Unlimited for a situation with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_situation_unlimited(arg) if not len(self.queue): raise KeyError logging.info("Added %d tracks from %s to queue", \ len(self.queue), arg) except KeyError: raise KeyError("Situation not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_artist_unlimited(self, arg): """Search Unlimited for an artist and adds the artist's 200 top tracks to the playback queue. Requires Unlimited subscription. """ try: artist = self.__gmusic_search(arg, 'artist') include_albums = False max_top_tracks = 200 max_rel_artist = 0 artist_tracks = dict() if artist: artist_tracks = self.__gmusic.get_artist_info \ (artist['artist']['artistId'], include_albums, max_top_tracks, max_rel_artist)['topTracks'] if not artist_tracks: raise KeyError tracks_added = self.__enqueue_tracks(artist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_album_unlimited(self, arg): """Search Unlimited for an album and add its tracks to the playback queue. Requires Unlimited subscription. """ try: album = self.__gmusic_search(arg, 'album') album_tracks = dict() if album: album_tracks = self.__gmusic.get_album_info \ (album['album']['albumId'])['tracks'] if not album_tracks: raise KeyError print_wrn("[Google Play Music] Playing '{0}'." \ .format((album['album']['name']).encode('utf-8'))) tracks_added = self.__enqueue_tracks(album_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_tracks_unlimited(self, arg): """ Search Unlimited for a track name and adds all the matching tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) try: max_results = 200 track_hits = self.__gmusic.search(arg, max_results)['song_hits'] if not len(track_hits): # Do another search with an empty string track_hits = self.__gmusic.search("", max_results)['song_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) tracks = list() for hit in track_hits: tracks.append(hit['track']) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_promoted_tracks_unlimited(self): """ Retrieve the url of the next track in the playback queue. """ try: tracks = self.__gmusic.get_promoted_songs() count = 0 for track in tracks: store_track = self.__gmusic.get_track_info(track['storeId']) if u'id' not in store_track.keys(): store_track[u'id'] = store_track['nid'] self.queue.append(store_track) count += 1 if count == 0: print_wrn("[Google Play Music] Operation requires " \ "an Unlimited subscription.") logging.info("Added %d Unlimited promoted tracks to queue", \ count) self.__update_play_queue_order() except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def next_url(self): """ Retrieve the url of the next track in the playback queue. """ if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(next_song) else: self.queue_index = -1 return self.next_url() else: return '' def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, song): """ Retrieve a song url """ song_url = self.__gmusic.get_stream_url(song['id'], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve the song url!") raise def __update_local_library(self): """ Retrieve the songs and albums from the user's library """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) songs = self.__gmusic.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Retrieve the user's song library for song in songs: if "rating" in song and song['rating'] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song['id'] song_artist = song['artist'] song_album = song['album'] self.song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = CaseInsensitiveDict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : %s", to_ascii(artist)) for album in self.library[artist].keys(): logging.info(" Album : %s", to_ascii(album)) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album def __update_stations_unlimited(self): """ Retrieve stations (Unlimited) """ self.stations.clear() stations = self.__gmusic.get_all_stations() self.stations[u"I'm Feeling Lucky"] = 'IFL' for station in stations: station_name = station['name'] logging.info("station name : %s", to_ascii(station_name)) self.stations[station_name] = station['id'] def __enqueue_user_station_unlimited(self, arg): """ Enqueue a user station (Unlimited) """ print_msg("[Google Play Music] [Station search "\ "in user's library] : '{0}'. " \ .format(self.__email)) self.__update_stations_unlimited() station_name = arg station_id = None for name, st_id in self.stations.iteritems(): print_nfo("[Google Play Music] [Station] '{0}'." \ .format(to_ascii(name))) if arg not in self.stations.keys(): for name, st_id in self.stations.iteritems(): if arg.lower() in name.lower(): station_id = st_id station_name = name break else: station_id = self.stations[arg] num_tracks = 200 tracks = list() if station_id: try: tracks = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if arg != station_name: print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", tracks_added, arg) self.__update_play_queue_order() else: print_wrn("[Google Play Music] '{0}' has no tracks. " \ .format(station_name)) if not len(self.queue): print_wrn("[Google Play Music] '{0}' " \ "not found in the user's library. " \ .format(arg.encode('utf-8'))) def __enqueue_station_unlimited(self, arg, max_results=200, quiet=False): """Search for a station and enqueue all of its tracks (Unlimited) """ if not quiet: print_msg("[Google Play Music] [Station search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: station_name = arg station_id = None station = self.__gmusic_search(arg, 'station', max_results, quiet) if station: station = station['station'] station_name = station['name'] seed = station['seed'] seed_type = seed['seedType'] track_id = seed['trackId'] if seed_type == u'2' else None artist_id = seed['artistId'] if seed_type == u'3' else None album_id = seed['albumId'] if seed_type == u'4' else None genre_id = seed['genreId'] if seed_type == u'5' else None playlist_token = seed['playlistShareToken'] if seed_type == u'8' else None curated_station_id = seed['curatedStationId'] if seed_type == u'9' else None num_tracks = max_results tracks = list() try: station_id \ = self.__gmusic.create_station(station_name, \ track_id, \ artist_id, \ album_id, \ genre_id, \ playlist_token, \ curated_station_id) tracks \ = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if not quiet: print_wrn("[Google Play Music] [Station] : '{0}'." \ .format(station_name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg.encode('utf-8')) self.__update_play_queue_order() except KeyError: raise KeyError("Station not found : {0}".format(arg)) def __enqueue_situation_unlimited(self, arg): """Search for a situation and enqueue all of its tracks (Unlimited) """ print_msg("[Google Play Music] [Situation search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: situation_hits = self.__gmusic.search(arg)['situation_hits'] if not len(situation_hits): # Do another search with an empty string situation_hits = self.__gmusic.search("")['situation_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) situation = next((hit for hit in situation_hits \ if 'best_result' in hit.keys()), None) num_tracks = 200 if not situation and len(situation_hits): max_results = num_tracks / len(situation_hits) for hit in situation_hits: situation = hit['situation'] print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \ .format((hit['situation']['title']).encode('utf-8'), (hit['situation']['description']).encode('utf-8'))) self.__enqueue_station_unlimited(situation['title'], max_results, True) if not situation: raise KeyError except KeyError: raise KeyError("Situation not found : {0}".format(arg)) def __enqueue_tracks(self, tracks): """ Add tracks to the playback queue """ count = 0 for track in tracks: if u'id' not in track.keys(): track[u'id'] = track['nid'] self.queue.append(track) count += 1 return count def __update_playlists(self): """ Retrieve the user's playlists """ plists = self.__gmusic.get_all_user_playlist_contents() for plist in plists: plist_name = plist['name'] logging.info("playlist name : %s", to_ascii(plist_name)) tracks = plist['tracks'] tracks.sort(key=itemgetter('creationTimestamp')) self.playlists[plist_name] = list() for track in tracks: try: song = self.song_map[track['trackId']] self.playlists[plist_name].append(song) except IndexError: pass def __update_playlists_unlimited(self): """ Retrieve shared playlists (Unlimited) """ plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \ if p.get('type') == 'SHARED'] for plist in plists_subscribed_to: share_tok = plist['shareToken'] playlist_items \ = self.__gmusic.get_shared_playlist_contents(share_tok) plist_name = plist['name'] logging.info("shared playlist name : %s", to_ascii(plist_name)) self.playlists[plist_name] = list() for item in playlist_items: try: song = item['track'] song['id'] = item['trackId'] self.playlists[plist_name].append(song) except IndexError: pass def __gmusic_search(self, query, query_type, max_results=200, quiet=False): """ Search Google Play (Unlimited) """ search_results = self.__gmusic.search(query, max_results)[query_type + '_hits'] result = next((hit for hit in search_results \ if 'best_result' in hit.keys()), None) if not result and len(search_results): secondary_hit = None for hit in search_results: if not quiet: print_nfo("[Google Play Music] [{0}] '{1}'." \ .format(query_type.capitalize(), (hit[query_type]['name']).encode('utf-8'))) if query.lower() == \ to_ascii(hit[query_type]['name']).lower(): result = hit break if query.lower() in \ to_ascii(hit[query_type]['name']).lower(): secondary_hit = hit if not result and secondary_hit: result = secondary_hit if not result and not len(search_results): # Do another search with an empty string search_results = self.__gmusic.search("")[query_type + '_hits'] if not result and len(search_results): # Play some random result from the search results random.seed() result = random.choice(search_results) if not quiet: print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(query.encode('utf-8'))) return result
def play_song(song): if Mobileclient.is_authenticated(gpm): mm = Musicmanager() mm.login('/home/pi/oauth.cred') if Musicmanager.is_authenticated(mm): song_dict = mm.get_purchased_songs() song_pattern = re.compile( r'(?:.)*\s?(' + re.escape(song) + r')\s?(?:.)*', re.IGNORECASE) btn = OnButtonPress() btn.start() for song in song_dict: m = re.match(song_pattern, song['title']) print(m) if re.match(song_pattern, song['title']) is not None: print('Song found!') song_id = song['id'] (filename, audio) = mm.download_song(song_id) # get rid of non-ascii characters in file name filename = filename.encode('ascii', errors='ignore') # check if song is already downloaded # path will look something like: # /home/pi/Music/02 - Raindrop Prelude.mp3 # forces filename to be a string filename = filename.decode('ascii') path = song_location + filename try: if os.path.isfile(path): print('Song is already downloaded...') print(path) print('Playing song.') vlc_instance = vlc.Instance() p = vlc_instance.media_player_new() media = vlc_instance.media_new(path) p.set_media(media) events = p.event_manager() events.event_attach( vlc.EventType.MediaPlayerEndReached, SongFinished) p.play() p.audio_set_volume(58) while finish == 0: duration = p.get_time() / 1000 (m, s) = divmod(duration, 60) print('Current song is: ', path) print('Length:', '%02d:%02d' % (m, s)) time.sleep(5) p.stop() break else: with open(path, 'wb') as f: f.write(audio) print('Song has been added to: ' + path) print('Playing song.') vlc_instance = vlc.Instance() p = vlc_instance.media_player_new() media = vlc_instance.media_new(path) p.set_media(media) events = p.event_manager() events.event_attach( vlc.EventType.MediaPlayerEndReached, SongFinished) p.play() p.audio_set_volume(58) while finish == 0: duration = p.get_time() / 1000 (m, s) = divmod(duration, 60) print('Current song is: ', path) print('Length:', '%02d:%02d' % (m, s)) time.sleep(5) p.stop() break except (OSError, IOError): print('An error has occurred.') break else: print('Song not found yet.') else: print('Looks like you need to authenticate.') mm.perform_oauth('/home/pi/oauth.cred') print('Logging out.') Mobileclient.logout(gpm) mm.logout() else: print('Mobileclient could not authenticate.') Mobileclient.logout(gpm)
class tizgmusicproxy(object): """A class for logging into a Google Play Music account and retrieving song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" # pylint: disable=too-many-instance-attributes,too-many-public-methods def __init__(self, email, password, device_id): self.__gmusic = Mobileclient() self.__email = email self.__device_id = device_id self.logged_in = False self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_song = None userdir = os.path.expanduser('~') tizconfig = os.path.join(userdir, ".config/tizonia/." + email + ".auth_token") auth_token = "" if os.path.isfile(tizconfig): with open(tizconfig, "r") as f: auth_token = pickle.load(f) if auth_token: # 'Keep track of the auth token' workaround. See: # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198 print_msg("[Google Play Music] [Authenticating] : " \ "'with cached auth token'") self.__gmusic.android_id = device_id self.__gmusic.session._authtoken = auth_token self.__gmusic.session.is_authenticated = True try: self.__gmusic.get_registered_devices() except CallFailure: # The token has expired. Reset the client object print_wrn("[Google Play Music] [Authenticating] : " \ "'auth token expired'") self.__gmusic = Mobileclient() auth_token = "" if not auth_token: attempts = 0 print_nfo("[Google Play Music] [Authenticating] : " \ "'with user credentials'") while not self.logged_in and attempts < 3: self.logged_in = self.__gmusic.login(email, password, device_id) attempts += 1 with open(tizconfig, "a+") as f: f.truncate() pickle.dump(self.__gmusic.session._authtoken, f) self.library = CaseInsensitiveDict() self.song_map = CaseInsensitiveDict() self.playlists = CaseInsensitiveDict() self.stations = CaseInsensitiveDict() def logout(self): """ Reset the session to an unauthenticated, default state. """ self.__gmusic.logout() def set_play_mode(self, mode): """ Set the playback mode. :param mode: curren tvalid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def current_song_title_and_artist(self): """ Retrieve the current track's title and artist name. """ logging.info("current_song_title_and_artist") song = self.now_playing_song if song: title = to_ascii(self.now_playing_song.get('title')) artist = to_ascii(self.now_playing_song.get('artist')) logging.info("Now playing %s by %s", title, artist) return artist, title else: return '', '' def current_song_album_and_duration(self): """ Retrieve the current track's album and duration. """ logging.info("current_song_album_and_duration") song = self.now_playing_song if song: album = to_ascii(self.now_playing_song.get('album')) duration = to_ascii \ (self.now_playing_song.get('durationMillis')) logging.info("album %s duration %s", album, duration) return album, int(duration) else: return '', 0 def current_track_and_album_total(self): """Return the current track number and the total number of tracks in the album, if known. """ logging.info("current_track_and_album_total") song = self.now_playing_song track = 0 total = 0 if song: try: track = self.now_playing_song['trackNumber'] total = self.now_playing_song['totalTrackCount'] logging.info("track number %s total tracks %s", track, total) except KeyError: logging.info("trackNumber or totalTrackCount : not found") else: logging.info("current_song_track_number_" "and_total_tracks : not found") return track, total def current_song_year(self): """ Return the current track's year of publication. """ logging.info("current_song_year") song = self.now_playing_song year = 0 if song: try: year = song['year'] logging.info("track year %s", year) except KeyError: logging.info("year : not found") else: logging.info("current_song_year : not found") return year def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def enqueue_tracks(self, arg): """ Search the user's library for tracks and add them to the playback queue. :param arg: a track search term """ try: songs = self.__gmusic.get_all_songs() track_hits = list() for song in songs: song_title = song['title'] if arg.lower() in song_title.lower(): track_hits.append(song) print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) if not len(track_hits): print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) random.seed() track_hits = random.sample(songs, MAX_TRACKS) for hit in track_hits: song_title = hit['title'] print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) if not len(track_hits): raise KeyError tracks_added = self.__enqueue_tracks(track_hits) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Track not found : {0}".format(arg)) def enqueue_artist(self, arg): """ Search the user's library for tracks from the given artist and add them to the playback queue. :param arg: an artist """ try: self.__update_local_library() artist = None artist_dict = None if arg not in self.library.keys(): for name, art in self.library.iteritems(): if arg.lower() in name.lower(): artist = name artist_dict = art if arg.lower() != name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ name.encode('utf-8'))) break if not artist: # Play some random artist from the library random.seed() artist = random.choice(self.library.keys()) artist_dict = self.library[artist] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: artist = arg artist_dict = self.library[arg] tracks_added = 0 for album in artist_dict: tracks_added += self.__enqueue_tracks(artist_dict[album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(artist))) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) def enqueue_album(self, arg): """ Search the user's library for albums with a given name and add them to the playback queue. """ try: self.__update_local_library() album = None artist = None tentative_album = None tentative_artist = None for library_artist in self.library: for artist_album in self.library[library_artist]: print_nfo("[Google Play Music] [Album] '{0}'." \ .format(to_ascii(artist_album))) if not album: if arg.lower() == artist_album.lower(): album = artist_album artist = library_artist break if not tentative_album: if arg.lower() in artist_album.lower(): tentative_album = artist_album tentative_artist = library_artist if album: break if not album and tentative_album: album = tentative_album artist = tentative_artist print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album.encode('utf-8'))) if not album: # Play some random album from the library random.seed() artist = random.choice(self.library.keys()) album = random.choice(self.library[artist].keys()) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not album: raise KeyError("Album not found : {0}".format(arg)) self.__enqueue_tracks(self.library[artist][album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(album))) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) def enqueue_playlist(self, arg): """Search the user's library for playlists with a given name and add the tracks of the first match to the playback queue. Requires Unlimited subscription. """ try: self.__update_local_library() self.__update_playlists() self.__update_playlists_unlimited() playlist = None playlist_name = None for name, plist in self.playlists.items(): print_nfo("[Google Play Music] [Playlist] '{0}'." \ .format(to_ascii(name))) if arg not in self.playlists.keys(): for name, plist in self.playlists.iteritems(): if arg.lower() in name.lower(): playlist = plist playlist_name = name if arg.lower() != name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ to_ascii(name))) break else: playlist_name = arg playlist = self.playlists[arg] random.seed() x = 0 while (not playlist or not len(playlist)) and x < 3: x += 1 # Play some random playlist from the library playlist_name = random.choice(self.playlists.keys()) playlist = self.playlists[playlist_name] print_wrn("[Google Play Music] '{0}' not found or found empty. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not len(playlist): raise KeyError self.__enqueue_tracks(playlist) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(playlist_name))) self.__update_play_queue_order() except KeyError: raise KeyError( "Playlist not found or found empty : {0}".format(arg)) def enqueue_podcast(self, arg): """Search Google Play Music for a podcast series and add its tracks to the playback queue (). Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving podcasts] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_podcast(arg) if not len(self.queue): raise KeyError logging.info("Added %d episodes from '%s' to queue", \ len(self.queue), arg) self.__update_play_queue_order() except KeyError: raise KeyError("Podcast not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_station_unlimited(self, arg): """Search the user's library for a station with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ try: # First try to find a suitable station in the user's library self.__enqueue_user_station_unlimited(arg) if not len(self.queue): # If no suitable station is found in the user's library, then # search google play unlimited for a potential match. self.__enqueue_station_unlimited(arg) if not len(self.queue): raise KeyError except KeyError: raise KeyError("Station not found : {0}".format(arg)) def enqueue_genre_unlimited(self, arg): """Search Unlimited for a genre with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \ .format(self.__email)) try: all_genres = list() root_genres = self.__gmusic.get_genres() second_tier_genres = list() for root_genre in root_genres: second_tier_genres += self.__gmusic.get_genres( root_genre['id']) all_genres += root_genres all_genres += second_tier_genres for genre in all_genres: print_nfo("[Google Play Music] [Genre] '{0}'." \ .format(to_ascii(genre['name']))) genre = dict() if arg not in all_genres: genre = next((g for g in all_genres \ if arg.lower() in to_ascii(g['name']).lower()), \ None) tracks_added = 0 while not tracks_added: if not genre and len(all_genres): # Play some random genre from the search results random.seed() genre = random.choice(all_genres) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) genre_name = genre['name'] genre_id = genre['id'] station_id = self.__gmusic.create_station(genre_name, \ None, None, None, genre_id) num_tracks = MAX_TRACKS tracks = self.__gmusic.get_station_tracks( station_id, num_tracks) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", tracks_added, genre_name) if not tracks_added: # This will produce another iteration in the loop genre = None print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(genre['name']))) self.__update_play_queue_order() except KeyError: raise KeyError("Genre not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_situation_unlimited(self, arg): """Search Unlimited for a situation with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_situation_unlimited(arg) if not len(self.queue): raise KeyError logging.info("Added %d tracks from %s to queue", \ len(self.queue), arg) except KeyError: raise KeyError("Situation not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_artist_unlimited(self, arg): """Search Unlimited for an artist and add the artist's 200 top tracks to the playback queue. Requires Unlimited subscription. """ try: artist = self.__gmusic_search(arg, 'artist') include_albums = False max_top_tracks = MAX_TRACKS max_rel_artist = 0 artist_tracks = dict() if artist: artist_tracks = self.__gmusic.get_artist_info \ (artist['artist']['artistId'], include_albums, max_top_tracks, max_rel_artist)['topTracks'] if not artist_tracks: raise KeyError for track in artist_tracks: song_title = track['title'] print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) tracks_added = self.__enqueue_tracks(artist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_album_unlimited(self, arg): """Search Unlimited for an album and add its tracks to the playback queue. Requires Unlimited subscription. """ try: album = self.__gmusic_search(arg, 'album') album_tracks = dict() if album: album_tracks = self.__gmusic.get_album_info \ (album['album']['albumId'])['tracks'] if not album_tracks: raise KeyError print_wrn("[Google Play Music] Playing '{0}'." \ .format((album['album']['name']).encode('utf-8'))) tracks_added = self.__enqueue_tracks(album_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_tracks_unlimited(self, arg): """ Search Unlimited for a track name and add all the matching tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) try: max_results = MAX_TRACKS track_hits = self.__gmusic.search(arg, max_results)['song_hits'] if not len(track_hits): # Do another search with an empty string track_hits = self.__gmusic.search("", max_results)['song_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) tracks = list() for hit in track_hits: tracks.append(hit['track']) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_playlist_unlimited(self, arg): """Search Unlimited for a playlist name and add all its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving playlists] : '{0}'. " \ .format(self.__email)) try: playlist_tracks = list() playlist_hits = self.__gmusic_search(arg, 'playlist') if playlist_hits: playlist = playlist_hits['playlist'] playlist_contents = self.__gmusic.get_shared_playlist_contents( playlist['shareToken']) else: raise KeyError print_nfo("[Google Play Music] [Playlist] '{}'." \ .format(playlist['name']).encode('utf-8')) for item in playlist_contents: print_nfo("[Google Play Music] [Playlist Track] '{} by {} (Album: {}, {})'." \ .format((item['track']['title']).encode('utf-8'), (item['track']['artist']).encode('utf-8'), (item['track']['album']).encode('utf-8'), (item['track']['year']))) track = item['track'] playlist_tracks.append(track) if not playlist_tracks: raise KeyError tracks_added = self.__enqueue_tracks(playlist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_promoted_tracks_unlimited(self): """ Retrieve the url of the next track in the playback queue. """ try: tracks = self.__gmusic.get_promoted_songs() count = 0 for track in tracks: store_track = self.__gmusic.get_track_info(track['storeId']) if u'id' not in store_track.keys(): store_track[u'id'] = store_track['storeId'] self.queue.append(store_track) count += 1 if count == 0: print_wrn("[Google Play Music] Operation requires " \ "an Unlimited subscription.") logging.info("Added %d Unlimited promoted tracks to queue", \ count) self.__update_play_queue_order() except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def next_url(self): """ Retrieve the url of the next track in the playback queue. """ if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(next_song) else: self.queue_index = -1 return self.next_url() else: return '' def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, song): """ Retrieve a song url """ if song.get('episodeId'): song_url = self.__gmusic.get_podcast_episode_stream_url( song['episodeId'], self.__device_id) else: song_url = self.__gmusic.get_stream_url(song['id'], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve the song url!") raise def __update_local_library(self): """ Retrieve the songs and albums from the user's library """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) songs = self.__gmusic.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Retrieve the user's song library for song in songs: if "rating" in song and song['rating'] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song['id'] song_artist = song['artist'] song_album = song['album'] self.song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = CaseInsensitiveDict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : %s", to_ascii(artist)) for album in self.library[artist].keys(): logging.info(" Album : %s", to_ascii(album)) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted( self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album def __update_stations_unlimited(self): """ Retrieve stations (Unlimited) """ self.stations.clear() stations = self.__gmusic.get_all_stations() self.stations[u"I'm Feeling Lucky"] = 'IFL' for station in stations: station_name = station['name'] logging.info("station name : %s", to_ascii(station_name)) self.stations[station_name] = station['id'] def __enqueue_user_station_unlimited(self, arg): """ Enqueue a user station (Unlimited) """ print_msg("[Google Play Music] [Station search "\ "in user's library] : '{0}'. " \ .format(self.__email)) self.__update_stations_unlimited() station_name = arg station_id = None for name, st_id in self.stations.iteritems(): print_nfo("[Google Play Music] [Station] '{0}'." \ .format(to_ascii(name))) if arg not in self.stations.keys(): for name, st_id in self.stations.iteritems(): if arg.lower() in name.lower(): station_id = st_id station_name = name break else: station_id = self.stations[arg] num_tracks = MAX_TRACKS tracks = list() if station_id: try: tracks = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if arg.lower() != station_name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", tracks_added, arg) self.__update_play_queue_order() else: print_wrn("[Google Play Music] '{0}' has no tracks. " \ .format(station_name)) if not len(self.queue): print_wrn("[Google Play Music] '{0}' " \ "not found in the user's library. " \ .format(arg.encode('utf-8'))) def __enqueue_station_unlimited(self, arg, max_results=MAX_TRACKS, quiet=False): """Search for a station and enqueue all of its tracks (Unlimited) """ if not quiet: print_msg("[Google Play Music] [Station search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: station_name = arg station_id = None station = self.__gmusic_search(arg, 'station', max_results, quiet) if station: station = station['station'] station_name = station['name'] seed = station['seed'] seed_type = seed['seedType'] track_id = seed['trackId'] if seed_type == u'2' else None artist_id = seed['artistId'] if seed_type == u'3' else None album_id = seed['albumId'] if seed_type == u'4' else None genre_id = seed['genreId'] if seed_type == u'5' else None playlist_token = seed[ 'playlistShareToken'] if seed_type == u'8' else None curated_station_id = seed[ 'curatedStationId'] if seed_type == u'9' else None num_tracks = max_results tracks = list() try: station_id \ = self.__gmusic.create_station(station_name, \ track_id, \ artist_id, \ album_id, \ genre_id, \ playlist_token, \ curated_station_id) tracks \ = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if not quiet: print_wrn("[Google Play Music] [Station] : '{0}'." \ .format(station_name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg.encode('utf-8')) self.__update_play_queue_order() except KeyError: raise KeyError("Station not found : {0}".format(arg)) def __enqueue_situation_unlimited(self, arg): """Search for a situation and enqueue all of its tracks (Unlimited) """ print_msg("[Google Play Music] [Situation search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: situation_hits = self.__gmusic.search(arg)['situation_hits'] # If the search didn't return results, just do another search with # an empty string if not len(situation_hits): situation_hits = self.__gmusic.search("")['situation_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) # Try to find a "best result", if one exists situation = next((hit for hit in situation_hits \ if 'best_result' in hit.keys() \ and hit['best_result'] == True), None) num_tracks = MAX_TRACKS # If there is no best result, then get a selection of tracks from # each situation. At least we'll play some music. if not situation and len(situation_hits): max_results = num_tracks / len(situation_hits) for hit in situation_hits: situation = hit['situation'] print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \ .format((hit['situation']['title']).encode('utf-8'), (hit['situation']['description']).encode('utf-8'))) self.__enqueue_station_unlimited(situation['title'], max_results, True) elif situation: # There is at list one sitution, enqueue its tracks. situation = situation['situation'] max_results = num_tracks self.__enqueue_station_unlimited(situation['title'], max_results, True) if not situation: raise KeyError except KeyError: raise KeyError("Situation not found : {0}".format(arg)) def __enqueue_podcast(self, arg): """Search for a podcast series and enqueue all of its tracks. """ print_msg("[Google Play Music] [Podcast search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: podcast_hits = self.__gmusic_search(arg, 'podcast', 10, quiet=False) if not podcast_hits: print_wrn( "[Google Play Music] [Podcast] 'Search returned zero results'." ) print_wrn( "[Google Play Music] [Podcast] 'Are you in a supported region " "(currently only US and Canada) ?'") # Use the first podcast retrieved. At least we'll play something. podcast = dict() if podcast_hits and len(podcast_hits): podcast = podcast_hits['series'] episodes_added = 0 if podcast: # There is a podcast, enqueue its episodes. print_nfo("[Google Play Music] [Podcast] 'Playing '{0}' by {1}'." \ .format((podcast['title']).encode('utf-8'), (podcast['author']).encode('utf-8'))) print_nfo("[Google Play Music] [Podcast] '{0}'." \ .format((podcast['description'][0:150]).encode('utf-8'))) series = self.__gmusic.get_podcast_series_info( podcast['seriesId']) episodes = series['episodes'] for episode in episodes: print_nfo("[Google Play Music] [Podcast Episode] '{0} : {1}'." \ .format((episode['title']).encode('utf-8'), (episode['description'][0:80]).encode('utf-8'))) episodes_added = self.__enqueue_tracks(episodes) if not podcast or not episodes_added: raise KeyError except KeyError: raise KeyError( "Podcast not found or no episodes found: {0}".format(arg)) def __enqueue_tracks(self, tracks): """ Add tracks to the playback queue """ count = 0 for track in tracks: if u'id' not in track.keys() and track.get('storeId'): track[u'id'] = track['storeId'] self.queue.append(track) count += 1 return count def __update_playlists(self): """ Retrieve the user's playlists """ plists = self.__gmusic.get_all_user_playlist_contents() for plist in plists: plist_name = plist['name'] logging.info("playlist name : %s", to_ascii(plist_name)) tracks = plist['tracks'] tracks.sort(key=itemgetter('creationTimestamp')) self.playlists[plist_name] = list() for track in tracks: try: song = self.song_map[track['trackId']] self.playlists[plist_name].append(song) except IndexError: pass def __update_playlists_unlimited(self): """ Retrieve shared playlists (Unlimited) """ plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \ if p.get('type') == 'SHARED'] for plist in plists_subscribed_to: share_tok = plist['shareToken'] playlist_items \ = self.__gmusic.get_shared_playlist_contents(share_tok) plist_name = plist['name'] logging.info("shared playlist name : %s", to_ascii(plist_name)) self.playlists[plist_name] = list() for item in playlist_items: try: song = item['track'] song['id'] = item['trackId'] self.playlists[plist_name].append(song) except IndexError: pass def __gmusic_search(self, query, query_type, max_results=MAX_TRACKS, quiet=False): """ Search Google Play (Unlimited) """ search_results = self.__gmusic.search(query, max_results)[query_type + '_hits'] # This is a workaround. Some podcast results come without these two # keys in the dictionary if query_type == "podcast" and len(search_results) \ and not search_results[0].get('navigational_result'): for res in search_results: res[u'best_result'] = False res[u'navigational_result'] = False res[query_type] = res['series'] result = '' if query_type != "playlist": result = next((hit for hit in search_results \ if 'best_result' in hit.keys() \ and hit['best_result'] == True), None) if not result and len(search_results): secondary_hit = None for hit in search_results: name = '' if hit[query_type].get('name'): name = hit[query_type].get('name') elif hit[query_type].get('title'): name = hit[query_type].get('title') if not quiet: print_nfo("[Google Play Music] [{0}] '{1}'." \ .format(query_type.capitalize(), (name).encode('utf-8'))) if query.lower() == \ to_ascii(name).lower(): result = hit break if query.lower() in \ to_ascii(name).lower(): secondary_hit = hit if not result and secondary_hit: result = secondary_hit if not result and not len(search_results): # Do another search with an empty string search_results = self.__gmusic.search("")[query_type + '_hits'] if not result and len(search_results): # Play some random result from the search results random.seed() result = random.choice(search_results) if not quiet: print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(query.encode('utf-8'))) return result
class CBMMusicManager(): # Please have this be an absolute path SONG_DIR = "/home/pi/Desktop/songs" def __init__(self): self.api = None self.player = None def start(self): """ Starts the MobileClient """ self.api = Mobileclient() def stop(self): """ Deletes MobileClient and sets self.api to default(None) """ del self.api self.api = None def logon(self, email, password): """ Logs onto google music as a mobile client. Returns true is sucessful. :param email: Email of the Google user :param password: Pass of the google user :return: Bool if connection was successful """ if self.api is None: raise errors.MobileClientNotInitError( "The Client has not been init therefor it cannot logon.", 1000) try: res = self.api.login(email, password, self.api.FROM_MAC_ADDRESS) except AlreadyLoggedIn as e: self.api.logout() res = self.api.login(email, password, self.api.FROM_MAC_ADDRESS) del email del password return res def logout(self): """ logs out of google music mobile client. :return: if it was succesful """ return self.api.logout() def is_authenticated(self): if not self.api.is_authenticated(): raise errors.SessionNotActive( "The session is no longer active. Either it timedout or you have not logged in", 1001) def search_song(self, query): """ Searchs for the given query and return the song results Will check for authentication. [{ 'track': { 'album': 'Work Out', 'albumArtRef': [{ 'aspectRatio': '1', 'autogen': False, 'kind': 'sj#imageRef', 'url': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fFdxj5w...' }], 'albumArtist': 'J.Cole', 'albumAvailableForPurchase': True, 'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y', 'artist': 'J Cole', 'artistId': ['Ajgnxme45wcqqv44vykrleifpji', 'Ampniqsqcwxk7btbgh5ycujij5i'], 'composer': '', 'discNumber': 1, 'durationMillis': '234000', 'estimatedSize': '9368582', 'explicitType': '1', 'genre': 'Pop', 'kind': 'sj#track', 'nid': 'Tq3nsmzeumhilpegkimjcnbr6aq', 'primaryVideo': { 'id': '6PN78PS_QsM', 'kind': 'sj#video', 'thumbnails': [{ 'height': 180, 'url': 'https://i.ytimg.com/vi/6PN78PS_QsM/mqdefault.jpg', 'width': 320 }] }, 'storeId': 'Tq3nsmzeumhilpegkimjcnbr6aq', 'title': 'Work Out', 'trackAvailableForPurchase': True, 'trackAvailableForSubscription': True, 'trackNumber': 1, 'trackType': '7', 'year': 2011 }, 'type': '1' }] :param query: The song query :return: [list] all the song hits """ self.is_authenticated() res = self.api.search(query) songs = res['song_hits'] return songs def search_album(self, query): """ Searchs for the given query and returns the album results. Will check for authenitcation. e.g return: [{ 'album': { 'albumArtRef': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fF...', 'albumArtist': 'J.Cole', 'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y', 'artist': 'J.Cole', 'artistId': ['Ajgnxme45wcqqv44vykrleifpji'], 'description_attribution': { 'kind': 'sj#attribution', 'license_title': 'Creative Commons Attribution CC-BY', 'license_url': 'http://creativecommons.org/licenses/by/4.0/legalcode', 'source_title': 'Freebase', 'source_url': '' }, 'explicitType': '1', 'kind': 'sj#album', 'name': 'Work Out', 'year': 2011 }, 'type': '3' }] :param query: [string] The album query :return: [list] A list of all the album hits """ self.is_authenticated() res = self.api.search(query) albums = res['album_hits'] return albums def get_album_info(self, album_id): """ Returns information about an album e.g return: { 'kind': 'sj#album', 'name': 'Circle', 'artist': 'Amorphis', 'albumArtRef': 'http://lh6.ggpht.com/...', 'tracks': [ # if `include_tracks` is True { 'album': 'Circle', 'kind': 'sj#track', 'storeId': 'T5zb7luo2vkroozmj57g2nljdsy', # can be used as a song id 'artist': 'Amorphis', 'albumArtRef': [ { 'url': 'http://lh6.ggpht.com/...' }], 'title': 'Shades of Grey', 'nid': 'T5zb7luo2vkroozmj57g2nljdsy', 'estimatedSize': '13115591', 'albumId': 'Bfr2onjv7g7tm4rzosewnnwxxyy', 'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'], 'albumArtist': 'Amorphis', 'durationMillis': '327000', 'composer': '', 'genre': 'Metal', 'trackNumber': 1, 'discNumber': 1, 'trackAvailableForPurchase': True, 'trackType': '7', 'albumAvailableForPurchase': True }, # ... ], 'albumId': 'Bfr2onjv7g7tm4rzosewnnwxxyy', 'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'], 'albumArtist': 'Amorphis', 'year': 2013 } :param album_id: The albumId :return: Dictionary in the format above """ self.is_authenticated() return self.api.get_album_info(album_id) def get_song_info(self, song_id): """ Returns information about a song e.g return { 'album': 'Best Of', 'kind': 'sj#track', 'storeId': 'Te2qokfjmhqxw4bnkswbfphzs4m', 'artist': 'Amorphis', 'albumArtRef': [ { 'url': 'http://lh5.ggpht.com/...' }], 'title': 'Hopeless Days', 'nid': 'Te2qokfjmhqxw4bnkswbfphzs4m', 'estimatedSize': '12325643', 'albumId': 'Bsbjjc24a5xutbutvbvg3h4y2k4', 'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'], 'albumArtist': 'Amorphis', 'durationMillis': '308000', 'composer': '', 'genre': 'Metal', 'trackNumber': 2, 'discNumber': 1, 'trackAvailableForPurchase': True, 'trackType': '7', 'albumAvailableForPurchase': True } :param song_id: The songds storeId :return: A dict with the above information """ self.is_authenticated() return self.api.get_track_info(song_id) def get_song_url(self, song_id): self.is_authenticated() res = self.api.get_stream_url(song_id) return res def download_song(self, song_id): """ Download the song from the storeId. :param song_id: the 'storeId' of the specific song """ url = self.get_song_url(song_id) song_file_path = "{}/{}.mp3".format(CBMMusicManager.SONG_DIR, song_id) if os.path.isfile(song_file_path): raise errors.SongAlreadyDownloadedException( "The song '{}' has already been downloaded and cached".format( song_file_path), 8002) # This need to not use subprocessing command = ['wget', url, '-O', song_file_path] res = check_output(command) lines = res.decode().split('\n') error_lines = [line for line in lines if 'failed' in line] if len(error_lines) > 0: # We have an error raise errors.CannotDownloadSongError( "Could not download the given song. {}".format( str(error_lines)), 1003)