class GoogleMusic(Source): def __init__(self, library, username, password): Source.__init__(self, library, SourceType.GOOGLE) self.GOOGLE_DEVICE_ID = None print username print password self.client = Mobileclient() logged_in = self.client.login(username, password, Mobileclient.FROM_MAC_ADDRESS) print "Google logged in:", logged_in DList = self.client.get_registered_devices() self.GOOGLE_DEVICE_ID = DList[0]["id"] if self.GOOGLE_DEVICE_ID[:2] == '0x': self.GOOGLE_DEVICE_ID = self.GOOGLE_DEVICE_ID[2:] print self.GOOGLE_DEVICE_ID #>testing # self.get_stream_URL("47b9d52c-9d66-3ff2-94d4-3ae55c0d2acc") def get_stream_URL(self, song_id): return self.client.get_stream_url(song_id, self.GOOGLE_DEVICE_ID) def sync(self): gmusic_tracks = self.client.get_all_songs() for track in gmusic_tracks: art = '' try: art = track['albumArtRef'][0]['url'] except KeyError: art = '' self.library.insert_track(track['title'], track['album'], track['artist'], self._source, str(track['id']), track['trackNumber'], art)
def download_album(username, password, artist_name, album_name): api = Mobileclient() api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) library = api.get_all_songs() songs = [s for s in library if s['albumArtist'] == artist_name and s['album'] == album_name] if len(songs) == 0: print('Error: Album not found', file=sys.stderr) return device_id = api.get_registered_devices()[0]['id'].replace('0x', '') dname = slugify(unicode(album_name)) os.mkdir(dname) # download songs for song in tqdm(songs, desc='Downloading'): fname = slugify(song['title']) mpg_name = os.path.join(dname, fname + '.mpg') mp3_name = os.path.join(dname, fname + '.mp3') url = api.get_stream_url(song['id'], device_id=device_id) response = requests.get(url) # first save as MPEG video with open(mpg_name, 'wb') as fout: for chunk in response.iter_content(chunk_size=128): fout.write(chunk) # call FFMPEG to convert to MP3 os.system(' '.join([FFMPEG_CMD] + FFMPEG_ARGS).format( input=mpg_name, output=mp3_name, title=song['title'], artist=song['albumArtist'], album=song['album'], track=song['trackNumber'])) os.remove(mpg_name) # download album art art_name = os.path.join(dname, dname + '.png') album_info = api.get_album_info(songs[0]['albumId'], include_tracks=False) response = requests.get(album_info['albumArtRef']) t = magic.from_buffer(response.content, mime=True) if t == 'image/jpeg': ext = '.jpg' elif t == 'image/png': ext = '.png' else: print('Unknown MIME type: {}'.format(t), file=sys.stderr) ext = '.wat' with open(os.path.join(dname, dname + ext), 'wb') as fout: fout.write(response.content)
def get_device_id(api: gmusicapi.Mobileclient): devices = api.get_registered_devices() # TODO: sort devices by the last accessed time, grab the most recent for device in devices: if device['type'] != 'ANDROID': continue return device['id'].lstrip('0x') raise Exception('could not find an android device id')
def __init__(self, hass, config): """Initialize the books authors.""" global G_GM_MOBILE_CLIENT_API self.hass = hass self.all_gm_tracks = [] self.selected_books = [] from gmusicapi import Mobileclient G_GM_MOBILE_CLIENT_API = Mobileclient() G_GM_MOBILE_CLIENT_API.login(GM_USER, GM_PASS, GM_DEV_KEY) if not G_GM_MOBILE_CLIENT_API.is_authenticated(): _LOGGER.error("Failed to log in, check gmusicapi") return False else: _LOGGER.info("OK - we are in Gmusic") _LOGGER.info("devices: " + str(G_GM_MOBILE_CLIENT_API.get_registered_devices()))
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
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 print_all_my_devices(api: Mobileclient): for device in api.get_registered_devices(): print('{') for k in device: print('\t{}: {}'.format(k, device[k])) print('}')
class GMusic(object): def __init__(self): self.authenticated = False self.all_access = False self.library_loaded = False self.all_songs = [] self.letters = {} self.artists = {} self.albums = {} self.genres = {} self.tracks_by_letter = {} self.tracks_by_artist = {} self.tracks_by_album = {} self.tracks_by_genre = {} self._device = None #self._webclient = Webclient(debug_logging=True) self._mobileclient = Mobileclient(debug_logging=False) self._playlists = [] self._playlist_contents = [] self._stations = [] def _get_device_id(self): if self.authenticated: devices = self._mobileclient.get_registered_devices() for dev in devices: if dev['type'] == 'PHONE': self._device = dev['id'][2:] break elif dev['type'] == 'IOS': self._device = dev['id'] break def _set_all_access(self): #settings = self._webclient._make_call(webclient.GetSettings, '') self.all_access = True #if 'isSubscription' in settings['settings'] and settings['settings']['isSubscription'] == True else False def _set_all_songs(self): if len(self.all_songs) == 0: try: self.all_songs = self._mobileclient.get_all_songs() except NotLoggedIn: if self.authenticate(): self.all_songs = self._mobileclient.get_all_songs() else: return [] else: return self.all_songs def authenticate(self, email, password): try: mcauthenticated = self._mobileclient.login(email, password, Mobileclient.FROM_MAC_ADDRESS) except AlreadyLoggedIn: mcauthenticated = True #try: # wcauthenticated = self._webclient.login(email, password) #except AlreadyLoggedIn: # wcauthenticated = True self.authenticated = mcauthenticated #and wcauthenticated self._set_all_access() self._get_device_id() return self.authenticated def load_data(self): self._set_all_songs() for song in self.all_songs: thumb = None letter = song['title'][0] artist = song['artist'] album = song['album'] genre = song['genre'] if 'genre' in song else '(None)' if letter not in self.tracks_by_letter: self.tracks_by_letter[letter] = [] self.letters[letter] = None if artist not in self.tracks_by_artist: self.tracks_by_artist[artist] = [] self.artists[artist] = None if album not in self.tracks_by_album: self.tracks_by_album[album] = [] self.albums[album] = None if genre not in self.tracks_by_genre: self.tracks_by_genre[genre] = [] self.genres[genre] = None track = {'artist': artist, 'album': album} if 'title' in song: track['title'] = song['title'] if 'album' in song: track['album'] = song['album'] if 'artist' in song: track['artist'] = song['artist'] if 'durationMillis' in song: track['durationMillis'] = song['durationMillis'] if 'id' in song: track['id'] = song['id'] if 'trackNumber' in song: track['trackType'] = song['trackNumber'] if 'storeId' in song: track['storeId'] = song['storeId'] if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] thumb = song['albumArtRef'][0]['url'] self.letters[letter] = thumb self.artists[artist] = thumb self.albums[album] = thumb self.genres[genre] = thumb self.tracks_by_letter[letter].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_artist[artist].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_album[album].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_genre[genre].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.library_loaded = True def get_tracks_for_type(self, type, name): type = type.lower() if type == 'artists': return self.tracks_by_artist[name] elif type == 'albums': return self.tracks_by_album[name] elif type == 'genres': return self.tracks_by_genre[name] elif type == 'songs by letter': return self.tracks_by_letter[name] else: return {} def get_song(self, id): return [x for x in self.all_songs if x['id'] == id][0] def get_all_playlists(self): if len(self._playlists) == 0: try: self._playlists = self._mobileclient.get_all_playlists() except NotLoggedIn: if self.authenticate(): self._playlists = self._mobileclient.get_all_playlists() else: return [] return self._playlists def get_all_user_playlist_contents(self, id): tracks = [] if len(self._playlist_contents) == 0: try: self._playlist_contents = self._mobileclient.get_all_user_playlist_contents() except NotLoggedIn: if self.authenticate(): self._playlist_contents = self._mobileclient.get_all_user_playlist_contents() else: return [] for playlist in self._playlist_contents: if id == playlist['id']: tracks = playlist['tracks'] break return tracks def get_shared_playlist_contents(self, token): playlist = [] try: playlist = self._mobileclient.get_shared_playlist_contents(token) except NotLoggedIn: if self.authenticate(): playlist = self._mobileclient.get_shared_playlist_contents(token) else: return [] return playlist def get_all_stations(self): if len(self._stations) == 0: try: self._stations = self._mobileclient.get_all_stations() except NotLoggedIn: if self.authenticate(): self._stations = self._mobileclient.get_all_stations() else: return [] return self._stations def get_station_tracks(self, id, num_tracks=200): tracks = [] try: tracks = self._mobileclient.get_station_tracks(id, num_tracks) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.get_station_tracks(id, num_tracks) else: return [] return tracks def get_genres(self): genres = [] try: genres = self._mobileclient.get_genres() except NotLoggedIn: if self.authenticate(): genres = self._mobileclient.get_genres() else: return [] return genres def create_station(self, name, id): station = None try: station = self._mobileclient.create_station(name=name, genre_id=id) except NotLoggedIn: if self.authenticate(): station = self._mobileclient.create_station(name=name, genre_id=id) else: return [] return station def search_all_access(self, query, max_results=50): results = None try: results = self._mobileclient.search_all_access(query, max_results) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.search_all_access(query, max_results) else: return [] return results def get_artist_info(self, id, include_albums=True, max_top_tracks=5, max_rel_artist=5): results = None try: results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) else: return [] return results def get_album_info(self, id, include_tracks=True): results = None try: results = self._mobileclient.get_album_info(id, include_tracks=include_tracks) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_album_info(id, include_tracks=include_tracks) else: return [] return results def add_aa_track(self, id): track = None try: track = self._mobileclient.add_aa_track(id) except NotLoggedIn: if self.authenticate(): track = self._mobileclient.add_aa_track(id) else: return None return track def add_songs_to_playlist(self, playlist_id, song_ids): tracks = None try: tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids) else: return None return tracks def get_stream_url(self, id): try: stream_url = self._mobileclient.get_stream_url(id, self._device) except NotLoggedIn: if self.authenticate(): stream_url = self._mobileclient.get_stream_url(id, self._device) else: return '' except CallFailure: raise CallFailure('Could not play song with id: ' + id, 'get_stream_url') return stream_url def reset_library(self): self.library_loaded = False self.all_songs[:] = [] self._playlists[:] = [] self._playlist_contents[:] = [] self._stations[:] = [] self.letters.clear() self.artists.clear() self.albums.clear() self.genres.clear() self.tracks_by_letter.clear() self.tracks_by_artist.clear() self.tracks_by_album.clear() self.tracks_by_genre.clear()
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)