def _combine_play_counts(self, api: Mobileclient) -> None: """Combine play counts on to keep song.""" discard_songs = self.get_discard_songs() extra_plays = sum(int(song["playCount"]) for song in discard_songs) if not extra_plays: return keep_song = self.get_keep_song() timestamps = [int(song["recentTimestamp"]) for song in self.group] most_recent_timestamp = max(timestamps) playtime = datetime.fromtimestamp(most_recent_timestamp / 10**6) api.increment_song_playcount(keep_song["id"], extra_plays, playtime=playtime)
class GMusicWrapper(object): def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login( username, password, getenv('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def log(self, log_str): self.logger.debug(log_str) def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return [x[query_type] for x in results[hits_key]] def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.log('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.log('Fetching library...') def get_artist(self, name): """ Fetches information about an artist given its name """ search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) def get_latest_album(self, artist_name=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True) for index, val in enumerate(sorted_list): album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: return album_info return False def get_album_by_artist(self, artist_name, album_id=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] random.shuffle(album_info) for index, val in enumerate(album_info): album = self._api.get_album_info( album_id=album_info[index]['albumId'], include_tracks=True) if album['albumId'] != album_id and len(album['tracks']) >= 5: return album return False def get_song(self, name, artist_name=None, album_name=None): if artist_name: name = "%s %s" % (artist_name, name) elif album_name: name = "%s %s" % (album_name, name) search = self._search("song", name) if len(search) == 0: return False if album_name: for i in range(0, len(search) - 1): if album_name in search[i]['album']: return search[i] return search[0] def get_promoted_songs(self): return self._api.get_promoted_songs() def get_station(self, title, track_id=None, artist_id=None, album_id=None): if artist_id is not None: if album_id is not None: if track_id is not None: return self._api.create_station(title, track_id=track_id) return self._api.create_station(title, album_id=album_id) return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/alexa/stream/%s" % (getenv('APP_URL'), song_id) def get_thumbnail(self, artist_art): return artist_art.replace("http://", "https://") def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if 'storeId' in track: return track, track['storeId'] elif 'trackId' in track: return self.library[track['trackId']], track['trackId'] else: return None, None def get_artist_album_list(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return "Unable to find the artist you requested." artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list_text = "Here's the album listing for %s: " % artist_name counter = 0 for index, val in enumerate(artist_info['albums']): if counter > 25: # alexa will time out after 10 seconds if the list takes too long to iterate through break album_info = self._api.get_album_info( album_id=artist_info['albums'][index]['albumId'], include_tracks=True) if len(album_info['tracks']) > 5: counter += 1 album_list_text += ( artist_info['albums'][index]['name']) + ", " return album_list_text def get_latest_artist_albums(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list = artist_info['albums'] sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True) speech_text = 'The latest albums by %s are ' % artist_name counter = 0 for index, val in enumerate(sorted_list): if counter > 5: break else: album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: counter += 1 album_name = sorted_list[index]['name'] album_year = sorted_list[index]['year'] speech_text += '%s, released in %d, ' % (album_name, album_year) return speech_text def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70): # Give each match a score based on its similarity to the requested # name self.log('Fetching library...') request_name = request_name.lower() + artist_name.lower() scored_matches = [] for i, match in enumerate(all_matches): try: name = match['name'].lower() except (KeyError, TypeError): i = match name = all_matches[match]['title'].lower() if artist_name != "": name += all_matches[match]['artist'].lower() scored_matches.append({ 'index': i, 'name': name, 'score': fuzz.ratio(name, request_name) }) sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True) top_scoring = sorted_matches[0] self.log('Fetching library...') best_match = all_matches[top_scoring['index']] # Make sure we have a decent match (the score is n where 0 <= n <= 100) if top_scoring['score'] < minimum_score: return None return best_match def get_genres(self, parent_genre_id=None): return self._api.get_genres(parent_genre_id) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) @classmethod def generate_api(cls, **kwargs): return cls(getenv('GOOGLE_EMAIL'), getenv('GOOGLE_PASSWORD'), **kwargs)
class GMusicWrapper(object): def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login( username, password, environ.get('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def populate_library( self ): #TODO: Use this as a function to refresh the library with Alexa via voice commands. # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return [x[query_type] for x in results[hits_key]] def _search_library_for_first(self, query_type, query): #try searching the library instead of the api for trackid, trackdata in self.library.items(): if query_type in trackdata: if query.lower() in trackdata[query_type].lower(): return trackdata return None def _search_library(self, query_type, query): #try searching the library instead of the api found = [] for trackid, trackdata in self.library.items(): if query_type in trackdata: if query.lower() in trackdata[query_type].lower(): found.append(trackdata) if not found: return None return found def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.logger.debug('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.logger.debug('Done! Discovered %d tracks.' % len(self.library)) def get_artist(self, name): """ Fetches information about an artist given its name """ search = self._search("artist", name) if len(search) == 0: search_lib = self._search_library("artist", name) if search_lib is not None: self.logger.debug(search_lib) return search_lib return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: search_lib = self._search_library("album", name) if search_lib is not None: self.logger.debug(search_lib) return search_lib return False return self._api.get_album_info(search[0]['albumId']) def get_latest_album(self, artist_name=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True) for index, val in enumerate(sorted_list): album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: return album_info return False def get_album_by_artist(self, artist_name, album_id=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] random.shuffle(album_info) for index, val in enumerate(album_info): album = self._api.get_album_info( album_id=album_info[index]['albumId'], include_tracks=True) if album['albumId'] != album_id and len(album['tracks']) >= 5: return album return False def get_song(self, song_name, artist_name=None, album_name=None): if artist_name: name = "%s %s" % (artist_name, song_name) elif album_name: name = "%s %s" % (album_name, song_name) self.logger.debug("get_song() : name: %s" % (name)) search = self._search("song", name) self.logger.debug("result length: %d" % len(search)) if len(search) == 0: search_lib = self._search_library_for_first("title", name) if search_lib is not None: return search_lib return False if album_name: for i in range(0, len(search) - 1): if album_name in search[i]['album']: return search[i] return search[0] def get_station(self, title, track_id=None, artist_id=None, album_id=None): if artist_id is not None: if album_id is not None: if track_id is not None: return self._api.create_station(title, track_id=track_id) return self._api.create_station(title, album_id=album_id) return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/alexa/stream/%s" % (environ['APP_URL'], song_id) def get_thumbnail(self, artist_art): # return artist_art.replace("http://", "https://") //OLD artistArtKey = 'artistArtRef' albumArtKey = 'albumArtRef' if artist_art is None: return self.default_thumbnail() elif artistArtKey in artist_art: artist_art = artist_art[artistArtKey] elif albumArtKey in artist_art: artist_art = artist_art[albumArtKey] else: return self.default_thumbnail() if type(artist_art) is list: if type(artist_art[0]) is dict: artUrl = artist_art[0]['url'] elif type(artist_art) is dict: artUrl = artist_art['url'] else: artUrl = artist_art return self.urlReplaceWithSecureHttps(artUrl) def urlReplaceWithSecureHttps(self, url): return url.replace("http://", "https://") def default_thumbnail(self): return 'https://lh3.googleusercontent.com/gdBHEk-u3YRDtuCU3iDTQ52nZd1t4GPmldYaT26Jh6EhXgp1mlhQiuLFl4eXDAXzDig5' def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if 'trackId' in track: return (self.library[track['trackId']], track['trackId']) if self.use_library_first(): #Using free version track id first if 'id' in track: return (track, track['id']) if 'storeId' in track: return track, track['storeId'] return (None, None) def get_artist_album_list(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return "Unable to find the artist you requested." artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list_text = "Here's the album listing for %s: " % artist_name counter = 0 for index, val in enumerate(artist_info['albums']): if counter > 25: # alexa will time out after 10 seconds if the list takes too long to iterate through break album_info = self._api.get_album_info( album_id=artist_info['albums'][index]['albumId'], include_tracks=True) if len(album_info['tracks']) > 5: counter += 1 album_list_text += ( artist_info['albums'][index]['name']) + ", " return album_list_text def get_latest_artist_albums(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list = artist_info['albums'] sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True) speech_text = 'The latest albums by %s are ' % artist_name counter = 0 for index, val in enumerate(sorted_list): if counter > 5: break else: album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: counter += 1 album_name = sorted_list[index]['name'] album_year = sorted_list[index]['year'] speech_text += '%s, released in %d, ' % (album_name, album_year) return speech_text def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70): # Give each match a score based on its similarity to the requested # name self.logger.debug("The artist name is " + str(artist_name)) request_name = request_name.lower() + artist_name.lower() scored_matches = [] for i, match in enumerate(all_matches): try: name = match['name'].lower() except (KeyError, TypeError): i = match name = all_matches[match]['title'].lower() if artist_name != "": name += all_matches[match]['artist'].lower() scored_matches.append({ 'index': i, 'name': name, 'score': fuzz.ratio(name, request_name) }) sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True) top_scoring = sorted_matches[0] self.logger.debug("The top scoring match was: " + str(top_scoring)) best_match = all_matches[top_scoring['index']] # Make sure the score is at least the min score value if top_scoring['score'] < minimum_score: return None return best_match def get_genres(self, parent_genre_id=None): return self._api.get_genres(parent_genre_id) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) def use_library_first(self): return environ['USE_LIBRARY_FIRST'].lower() == 'true' @classmethod def generate_api(cls, **kwargs): return cls(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'], **kwargs)
class GMusicWrapper(object): def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login(username, password, getenv('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") try: assert literal_eval(getenv("DEBUG_FORCE_LIBRARY", "False")) self.use_store = False except (AssertionError, ValueError): # AssertionError if it's False, ValueError if it's not set / not set to a proper boolean string self.use_store = self._api.is_subscribed # Populate our library self.start_indexing() def start_indexing(self): self.library = {} self.albums = set([]) self.artists = set([]) self.indexing_thread = threading.Thread( target=self.index_library ) self.indexing_thread.start() def log(self, log_str): if self.logger != None: self.logger.debug(log_str) def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return [x[query_type] for x in results[hits_key]] def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.log('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.albums.add(track['album']) self.artists.add(track['artist']) self.log('Fetching library complete.') def get_artist(self, name): """ Fetches information about an artist given its name """ if self.use_store: search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) else: search = {} search['topTracks'] = [] # Find the best artist we have, and then match songs to that artist likely_artist, score = process.extractOne(name, self.artists) if score < 70: return False for song_id, song in self.library.items(): if 'artist' in song and song['artist'].lower() == likely_artist.lower() and 'artistId' in song: if not search['topTracks']: # First entry # Copy artist details from the first song into the general artist response try: search['artistArtRef'] = song['artistArtRef'][0]['url'] except KeyError: pass search['name'] = song['artist'] search['artistId'] = song['artistId'] search['topTracks'].append(song) random.shuffle(search['topTracks']) # This is all music, not top, but the user probably would prefer it shuffled. if not search['topTracks']: return False return search def get_album(self, name, artist_name=None): if self.use_store: if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) else: search = {} search['tracks'] = [] if artist_name: artist_name, score = process.extractOne(artist_name, self.artists) if score < 70: return False name, score = process.extractOne(name, self.albums) if score < 70: return False for song_id, song in self.library.items(): if 'album' in song and song['album'].lower() == name.lower(): if not artist_name or ('artist' in song and song['artist'].lower() == artist_name.lower()): if not search['tracks']: # First entry search['albumArtist'] = song['albumArtist'] search['name'] = song['album'] try: search['albumId'] = song['albumId'] except KeyError: pass search['tracks'].append(song) if not search['tracks']: return False return search def get_latest_album(self, artist_name=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True) for index, val in enumerate(sorted_list): album_info = self._api.get_album_info(album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: return album_info return False def get_album_by_artist(self, artist_name, album_id=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] random.shuffle(album_info) for index, val in enumerate(album_info): album = self._api.get_album_info(album_id=album_info[index]['albumId'], include_tracks=True) if album['albumId'] != album_id and len(album['tracks']) >= 5: return album return False def get_song(self, name, artist_name=None, album_name=None): if self.use_store: if artist_name: name = "%s %s" % (artist_name, name) elif album_name: name = "%s %s" % (album_name, name) search = self._search("song", name) if len(search) == 0: return False if album_name: for i in range(0, len(search) - 1): if album_name in search[i]['album']: return search[i] return search[0] else: search = {} if not name: return False if artist_name: artist_name, score = process.extractOne(artist_name, self.artists) if score < 70: return False if album_name: album_name, score = process.extractOne(album_name, self.albums) if score < 70: return False possible_songs = {song_id: song['title'] for song_id, song in self.library.items() if (not artist_name or ('artist' in song and song['artist'].lower() == artist_name.lower())) and (not album_name or ('album' in song and song['album'].lower() == album_name.lower()))} song, score, song_id = process.extractOne(name.lower(), possible_songs) if score < 70: return False else: return self.library[song_id] def get_promoted_songs(self): return self._api.get_promoted_songs() def get_station(self, title, track_id=None, artist_id=None, album_id=None): if artist_id is not None: if album_id is not None: if track_id is not None: return self._api.create_station(title, track_id=track_id) return self._api.create_station(title, album_id=album_id) return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/alexa/stream/%s" % (getenv('APP_URL'), song_id) def get_thumbnail(self, artist_art): return artist_art.replace("http://", "https://") def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if self.use_store and 'storeId' in track: return track, track['storeId'] elif 'id' in track: return self.library[track['id']], track['id'] elif 'trackId' in track: return self.library[track['trackId']], track['trackId'] else: return None, None def get_artist_album_list(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return "Unable to find the artist you requested." artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list_text = "Here's the album listing for %s: " % artist_name counter = 0 for index, val in enumerate(artist_info['albums']): if counter > 25: # alexa will time out after 10 seconds if the list takes too long to iterate through break album_info = self._api.get_album_info(album_id=artist_info['albums'][index]['albumId'], include_tracks=True) if len(album_info['tracks']) > 5: counter += 1 album_list_text += (artist_info['albums'][index]['name']) + ", " return album_list_text def get_latest_artist_albums(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list = artist_info['albums'] sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True) speech_text = 'The latest albums by %s are ' % artist_name counter = 0 for index, val in enumerate(sorted_list): if counter > 5: break else: album_info = self._api.get_album_info(album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: counter += 1 album_name = sorted_list[index]['name'] album_year = sorted_list[index]['year'] speech_text += '%s, released in %d, ' % (album_name, album_year) return speech_text def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70): # Give each match a score based on its similarity to the requested # name self.log('Finding closest match...') best_match = None request_name = request_name.lower() + artist_name.lower() scored_matches = [] for i, match in enumerate(all_matches): try: name = match['name'].lower() except (KeyError, TypeError): i = match name = all_matches[match]['title'].lower() if artist_name != "": name += all_matches[match]['artist'].lower() scored_matches.append({ 'index': i, 'name': name, 'score': fuzz.ratio(name, request_name) }) sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True) try: top_scoring = sorted_matches[0] # Make sure we have a decent match (the score is n where 0 <= n <= 100) if top_scoring['score'] >= minimum_score: best_match = all_matches[top_scoring['index']] except IndexError: pass self.log('Found %s...' % best_match) return best_match def get_genres(self, parent_genre_id=None): return self._api.get_genres(parent_genre_id) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) @classmethod def generate_api(cls, **kwargs): return cls(getenv('GOOGLE_EMAIL'), getenv('GOOGLE_PASSWORD'), **kwargs)
class GooglePlayMusicPlayer(object): def __init__(self, account, password, shuffle=False, loop=0, volume=50, muted=False, library_update=False, debug=False): self._api = Mobileclient(debug_logging=False) self._vlc = Instance() self._library_songs = None self._library_playlists = None self._queue_trackDict = [] self._queue_index = -99 # -1 = finished playing queue, -99 = empty queue, -2 = fresh start self._queue_history_backward = [] self._queue_history_forward = [] self._queue_shuffle_on = shuffle self._queue_loop_mode = loop # 0 = loop off, 1 = loop all, 2 = repeat one self._player = None self._player_state = 'stopped' # stopped, playing, or paused self._player_volume = volume self._player_volume_muted = muted self._command_dict = available_commands self._id_text = '[GooglePlayMusicPlayer] ' self._is_playing_before_pausing_for_command = False self._last_executed_command = 'stop' self._debug = debug self._api.login(account, password, Mobileclient.FROM_MAC_ADDRESS) self._library_load_data(library_update) ### LIBRARY Functions ### def _library_load_data(self, force_online=False): if (not force_online) and os.path.isfile('songs.json'): # Load from file if self._debug is True: print(self._id_text + 'Found local song data.') with open('songs.json') as input_file: self._library_songs = json.load(input_file) else: self._library_songs = self._api.get_all_songs() # Save to file with open('songs.json', 'w') as output_file: json.dump(self._library_songs, output_file) if (not force_online) and os.path.isfile('playlists.json'): # Load from file if self._debug is True: print(self._id_text + 'Found local playlist data.') with open('playlists.json') as input_file: self._library_playlists = json.load(input_file) else: self._library_playlists = self._api.get_all_user_playlist_contents( ) # Save to file with open('playlists.json', 'w') as output_file: json.dump(self._library_playlists, output_file) def _library_get_song_details(self, track_id): for song_dict in self._library_songs: if track_id == song_dict['id']: return song_dict['artist'] + " - " + song_dict['title'] return "Unknown Artist - Unknown Title" ### End LIBRARY Functions ### ### QUEUE Functions ### def _queue_load_playlist(self, playlist): playlist_name = playlist.strip().lower() self._queue_reset() for playlist_dict in self._library_playlists: actual_playlist_name = playlist_dict['name'].strip().lower() if playlist_name in actual_playlist_name: if self._debug is True: print(self._id_text + "Found match...", playlist_dict['name']) for track_dict in playlist_dict['tracks']: self._queue_trackDict.append(track_dict) if len(self._queue_trackDict) != 0: self._queue_index = -2 return playlist_dict['name'] else: if self._debug is True: print(self._id_text + "Found...", playlist_dict['name']) if self._debug is True: print(self._id_text + "Nothing matches... Playlist was not loaded...") return None def _queue_reset(self): self._controller_stop() self._queue_trackDict = [] self._queue_index = -99 self._queue_history_backward = [] self._queue_history_forward = [] def _queue_reset_index(self): if self._queue_shuffle_on is True: self._queue_index = self._queue_random_index() else: self._queue_index = 0 def _queue_reset_history(self): self._queue_history_backward = [] self._queue_history_forward = [] def _queue_random_index(self): while True: random_number = random.randrange(0, len(self._queue_trackDict)) if (self._queue_index != random_number) or (len( self._queue_trackDict) == 1): break return random_number def _queue_next(self): if len(self._queue_trackDict) == 0: self._queue_index = -99 return if self._queue_loop_mode == 2: # repeat one return if self._queue_shuffle_on is True: self._queue_history_backward.append(self._queue_index) if len(self._queue_history_forward) > 0: self._queue_index = self._queue_history_forward.pop() else: self._queue_index = self._queue_random_index() else: self._queue_index += 1 if (self._queue_index >= len( self._queue_trackDict)) and (self._queue_loop_mode == 0): self._queue_index = -1 else: self._queue_index %= len(self._queue_trackDict) def _queue_previous(self): if len(self._queue_trackDict) == 0: self._queue_index = -99 return if self._queue_shuffle_on is True: if len(self._queue_history_backward) > 0: self._queue_history_forward.append(self._queue_index) self._queue_index = self._queue_history_backward.pop() else: self._queue_index = max(0, self._queue_index - 1) def _queue_get(self): if self._queue_index == -2: self._queue_reset_index() return self._queue_get() if self._queue_index == -1: self._queue_reset_index() return None if (self._queue_index < 0) or (self._queue_index >= len( self._queue_trackDict)): return None return self._queue_trackDict[self._queue_index] ### End QUEUE Functions ### ### PLAYER & CONTROLLER Functions ### def _controller_play_song(self, song_dict): stream_url = self._api.get_stream_url(song_dict['trackId']) self._player = self._vlc.media_player_new() media = self._vlc.media_new(stream_url) self._player.set_media(media) if self._player_volume_muted is True: self._player.audio_set_volume(0) else: self._player.audio_set_volume(self._player_volume) self._player.play() self._player_state = 'playing' self._player.event_manager().event_attach( EventType.MediaPlayerEndReached, self._controller_finish_and_play_next) song_info_string = self._library_get_song_details(song_dict['trackId']) print(self._id_text + "Playing... " + song_info_string + " (" + song_dict['id'] + ")") def _controller_play(self): if self._player_state == 'stopped': song_dict = self._queue_get() if song_dict == None: return self._controller_play_song(song_dict) elif self._player_state == 'playing': return elif self._player_state == 'paused': self._player.set_pause(0) self._player_state = 'playing' def _controller_pause(self): if self._player_state == 'playing': self._player.set_pause(1) self._player_state = 'paused' def _controller_stop(self): if self._player_state in ['playing', 'paused']: self._player.stop() self._player_state = 'stopped' def _controller_finish_and_play_next(self, event): self._player_state = 'stopped' self._api.increment_song_playcount( self._queue_trackDict[self._queue_index]['id']) self._controller_next() self._controller_play() def _controller_next(self): saved_player_state = self._player_state self._controller_stop() self._queue_next() if saved_player_state != 'stopped': self._controller_play() def _controller_previous(self): saved_player_state = self._player_state self._controller_stop() self._queue_previous() if saved_player_state != 'stopped': self._controller_play() def _controller_shuffle(self, mode): # mode False = off, mode True = on self._queue_shuffle_on = mode self._queue_reset_history() def _controller_loop( self, mode): # mode 0 = loop off, mode 1 = loop all, mode 2 = repeat one self._queue_loop_mode = mode def _controller_volume_adjust(self, direction, amount): if direction is 'up': self._player_volume = min(self._player_volume + amount, 100) else: self._player_volume = max(0, self._player_volume - amount) if self._player is not None: self._player.audio_set_volume(self._player_volume) self._player_volume_muted = False def _controller_volume_mute(self): if self._player_volume_muted is False: if self._player is not None: self._player.audio_set_volume(0) self._player_volume_muted = True def _controller_volume_unmute(self): if self._player_volume_muted is True: if self._player is not None: self._player.audio_set_volume(self._player_volume) self._player_volume_muted = False ### End PLAYER & CONTROL Functions ### def load_playlist(self, playlist_name): self._last_executed_command = 'load' return self._queue_load_playlist(playlist_name) def play(self): self._last_executed_command = 'play' self._controller_play() def pause(self): self._last_executed_command = 'pause' self._controller_pause() def stop(self): self._last_executed_command = 'stop' self._controller_stop() def next(self): self._last_executed_command = 'next' self._controller_next() def previous(self): self._last_executed_command = 'previous' self._controller_previous() def shuffle(self, command): self._last_executed_command = 'shuffle' if command in [0, 1]: self._controller_shuffle(True if command is 1 else False) elif command is 2: return self._queue_shuffle_on def loop(self, command): self._last_executed_command = 'loop' if command in [0, 1, 2]: self._controller_loop(command) elif command is 3: return self._queue_loop_mode def volume_adjust(self, direction, amount): self._last_executed_command = 'volume' if direction in ['up', 'down']: self._controller_volume_adjust(direction, amount) def mute(self): self._last_executed_command = 'mute' self._controller_volume_mute() def unmute(self): self._last_executed_command = 'unmute' self._controller_volume_unmute() def get_command_list(self): return self._command_dict.keys() def pause_for_command(self): if self._player_state is 'playing': self._is_playing_before_pausing_for_command = True self._controller_pause() else: self._is_playing_before_pausing_for_command = False def resume_after_command(self, force): if self._is_playing_before_pausing_for_command is True: if (force is True) or (self._last_executed_command not in ['play', 'pause', 'stop']): self._controller_play() # from google assistant voice command control (import this file from google AIY) def run_command(self, action): if 'play' in action: playlist = action.replace("play", "").strip() if self.load_playlist(playlist) is None: return ('I am not able to find ' + playlist) self.play() elif self._command_dict[action] == 'play': self.play() elif self._command_dict[action] == 'pause': self.pause() elif self._command_dict[action] == 'stop': self.stop() elif self._command_dict[action] == 'next': self.next() elif self._command_dict[action] == 'previous': self.previous() elif self._command_dict[action] == 'shuffle off': self.shuffle(0) elif self._command_dict[action] == 'shuffle on': self.shuffle(1) elif self._command_dict[action] == 'shuffle status': s = 'on' if self.shuffle(2) is True else 'off' return ('Shuffle is ' + s) elif self._command_dict[action] == 'looping off': self.loop(0) elif self._command_dict[action] == 'looping all': self.loop(1) elif self._command_dict[action] == 'looping one': self.loop(2) elif self._command_dict[action] == 'looping status': s = self.loop(3) msg = 'is off' if s is 0 else ( 'all songs' if s is 1 else 'this song') return ('looping ' + msg) elif self._command_dict[action] == 'volume up': self.volume_adjust('up', 10) elif self._command_dict[action] == 'volume down': self.volume_adjust('down', 10) elif self._command_dict[action] == 'mute': self.mute() elif self._command_dict[action] == 'unmute': self.unmute() else: # probably won't get here print(self._id_text + 'Not a valid action...') return ('I don\'t know what to do.')
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)
from gmusicapi import Mobileclient mc = Mobileclient() username = input("U: ") password = input("P: ") androidid = mc.FROM_MAC_ADDRESS mc.login(username, password, androidid) print("Getting Songs... (This may take some time)") library = mc.get_all_songs() notFound = True while notFound: songName = input("Name of song to change: ") song = [track for track in library if track['title'] == songName] if song is None: print("Song not found.") else: notFound = False songID = song[0]['id'] playCount = eval(input("New play count: ")) mc.increment_song_playcount(songID, plays=playCount) print("Done!")
class GMusicWrapper: def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login( username, password, environ.get('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return map(lambda x: x[query_type], results[hits_key]) def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.logger.debug('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.logger.debug('Done! Discovered %d tracks.' % len(self.library)) def get_artist(self, name): """ Fetches information about an artist given its name """ search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) def get_song(self, name, artist_name=None): if artist_name: name = "%s %s" % (artist_name, name) search = self._search("song", name) if len(search) == 0: return False return search[0] def get_station(self, title, artist_id=None): if artist_id != None: return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/stream/%s" % (environ['APP_URL'], song_id) def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if 'storeId' in track: return (track, track['storeId']) elif 'trackId' in track: return (self.library[track['trackId']], track['trackId']) else: return (None, None) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) @classmethod def generate_api(cls, **kwargs): return cls(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'], **kwargs)