def main(): args, config = init() if not os.path.isdir(config['PROG']['DownloadPath']): os.mkdir(config['PROG']['DownloadPath']) sc_download( config['SOUNDCLOUD']['PlaylistUrl'], config['PROG']['DownloadPath'] ) mc = Mobileclient() mc.login(config['GMUSIC']['User'], config['GMUSIC']['Password'], Mobileclient.FROM_MAC_ADDRESS) mm = Musicmanager() if not (os.path.exists(gmclients.OAUTH_FILEPATH) and mm.login(gmclients.OAUTH_FILEPATH)): mm.perform_oauth(gmclients.OAUTH_FILEPATH, open_browser=True) if not mm.login(gmclients.OAUTH_FILEPATH): sys.stderr.write('Musicmanager could not authenticate') if config['GMUSIC']['TargetPlaylist'] not in set([item['name'] for item in mc.get_all_playlists() if not item['deleted']]): mc.create_playlist(name=config['GMUSIC']['TargetPlaylist'], description='Tracks synchronized using {}'.format(__prog__), public=False) playlist_id, current_members = gm_get_current_pl_member(mc, config['GMUSIC']['TargetPlaylist']) for track in os.listdir(config['PROG']['DownloadPath']): print('Uploading {}'.format(track)) uploaded_id = gm_extract_id(mm.upload('{}{}'.format(config['PROG']['DownloadPath'], track))) mc.add_songs_to_playlist(playlist_id, uploaded_id)
def __get_all_playlists(gpm_client: Mobileclient) -> None: playlists = gpm_client.get_all_playlists() playlist_dict = {} for playlist in playlists: playlist_dict.update({playlist['name']: playlist['id']}) pass
class GMusicApp: def __init__(self): self.base_dir = os.path.dirname(os.path.realpath(__file__)) self.cred_filename = os.path.join(self.base_dir, 'credentials.json') self.api = Mobileclient() @staticmethod def get_device_id(): dev_id = os.getenv('DEVICE_ID') if not dev_id: raise Exception('Please fill the DEVICE_ID field in .env file') elif not re.match('^[a-f0-9]*$', dev_id): raise Exception('DEVICE_ID is incorrect') return dev_id def auth(self): try: device_id = self.get_device_id() except Exception as e: raise e if not os.path.exists(self.cred_filename): self.api.perform_oauth(self.cred_filename, False) return self.api.oauth_login(device_id, self.cred_filename) def run(self): playlists = self.api.get_all_playlists() print(playlists)
def google_playlists(x): api = Mobileclient() api.login('*****@*****.**', 'rtqjkpidxwxddpur', Mobileclient.FROM_MAC_ADDRESS) all_playlists = api.get_all_playlists(incremental=False, include_deleted=False) dj_list = list(set(x['host'])) for k, dj in enumerate(dj_list): # pull out subset of a given Dj subset = x.loc[x['host']==(dj)] print("\n Analyzing "+dj+" Playlists...\n") # pull out subset of dj subset for a given month for i in np.arange(1, 12, 1): print('Now loading Month: '+str(i)) artists = np.load(dj+'/'+'2015-'+str(i)+'-artists.npy') if len(artists) == 0: break titles = np.load(dj+'/'+'2015-'+str(i)+'-titles.npy') # playlist_exists = False # playlist_name = 'KCRW DJ '+host+', Tracks of 2015-'+str(i) # print("Searching for playlist named: " + playlist_name) # for playlist in all_playlists: # if playlist['name'] == playlist_name: # playlist_exists = True # playlist_id = playlist['id'] # print("Playlist exists, adding new songs to playlist: "+playlist_name) # if not playlist_exists: # playlist_id = api.create_playlist(playlist_name, description=None, public=False) # print("Playlist is new, creating playlist and adding new songs to: "+playlist_name) search_google(api, artists, titles)
def google_playlists(x): api = Mobileclient() api.login('*****@*****.**', 'rtqjkpidxwxddpur', Mobileclient.FROM_MAC_ADDRESS) all_playlists = api.get_all_playlists(incremental=False, include_deleted=False) dj_list = list(set(x['host'])) for k, dj in enumerate(dj_list): # pull out subset of a given Dj subset = x.loc[x['host'] == (dj)] print("\n Analyzing " + dj + " Playlists...\n") # pull out subset of dj subset for a given month for i in np.arange(1, 12, 1): print('Now loading Month: ' + str(i)) artists = np.load(dj + '/' + '2015-' + str(i) + '-artists.npy') if len(artists) == 0: break titles = np.load(dj + '/' + '2015-' + str(i) + '-titles.npy') # playlist_exists = False # playlist_name = 'KCRW DJ '+host+', Tracks of 2015-'+str(i) # print("Searching for playlist named: " + playlist_name) # for playlist in all_playlists: # if playlist['name'] == playlist_name: # playlist_exists = True # playlist_id = playlist['id'] # print("Playlist exists, adding new songs to playlist: "+playlist_name) # if not playlist_exists: # playlist_id = api.create_playlist(playlist_name, description=None, public=False) # print("Playlist is new, creating playlist and adding new songs to: "+playlist_name) search_google(api, artists, titles)
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 GMusicWS: def __init__(self, user, password, playlistName): self.playlistName = playlistName self.api = Mobileclient() print("Logging into MobileClient API") self.api.login(user, password, "android_id") #insert unique android_id here def mapUnknownTracks(self, db): playlist = db.unmappedTracks() for track in playlist: searchstr = track.artist + " " + track.song print("Searching for %s" % (searchstr)) try: result = self.api.search_all_access(searchstr, max_results=1) print("Found " + result['song_hits'][0]['track']['artist'] + " - " + result['song_hits'][0]['track']['title']) nid = result['song_hits'][0]['track']['nid'] db.storemapping(track.song, track.artist, nid) except: print("Error parsing result: " + str(result)) time.sleep(1) def maintain(self, tracks): print("Searching for playlist %s" % (self.playlistName)) found = False searchres = self.api.get_all_playlists() for list in searchres: if list['name'] == self.playlistName: found = True pid = list['id'] if not found: print("Not found - creating") pid = self.api.create_playlist(self.playlistName) print("Playlist id is %s" % (pid)) print("Getting current contents") playlists = self.api.get_all_user_playlist_contents() currentEntries = [] for playlist in playlists: if playlist['name'] == self.playlistName: for entry in playlist['tracks']: currentEntries.append(entry['id']) print("Removing songs") self.api.remove_entries_from_playlist(currentEntries) print("Adding songs") self.api.add_songs_to_playlist(pid, tracks)
class GMusicPodcastSyncDestination(PodcastSyncDestination): def __init__(self): super(GMusicPodcastSyncDestination, self).__init__() self.serviceIdentifier = "GOO" self.serviceName = "Google Music" self.musicManager = Musicmanager() self.mobileClient = Mobileclient() oAuthFile = "gMusic.oauth" if not os.path.isfile(oAuthFile): if not self.musicManager.perform_oauth(oAuthFile, True): print "Failed to authenticate Music Manager." raise PodcastSyncDestinationException("Authentication Failure.") else: try: self.musicManagerauthenticated = self.musicManager.login(oAuthFile) except gmusicapi.exceptions.AlreadyLoggedIn: pass username = raw_input("Enter Google Username: "******"Enter Google Password: "******"Authentication Failure.") # perform a push task. should return a PodFastPodcastPushedEpisode instance def pushEpisode(self, podcastSyncTaskEpisodePush): (uploaded, matched, not_uploaded) = self.musicManager.upload([podcastSyncTaskEpisodePush.localFilename]) songGoogleMusicID = "" if not_uploaded: # If the track was not uploaded, it may have been uploaded in the past. p = re.compile("ALREADY_EXISTS\\((.*)\\)") m = p.findall(not_uploaded[podcastSyncTaskEpisodePush.localFilename]) songGoogleMusicID = m[0] else: songGoogleMusicID = uploaded[podcastSyncTaskEpisodePush.localFilename] print "Track uploaded Successfully. ID:" + songGoogleMusicID gmusicPlayLists = self.mobileClient.get_all_playlists() playListExists = False gmusicPlaylistID = "" for gmusicPlayList in gmusicPlayLists: if gmusicPlayList["name"] == podcastSyncTaskEpisodePush.playlistName: playListExists = True gmusicPlaylistID = gmusicPlayList["id"] break if not playListExists: print "Creating playlist..." gmusicPlaylistID = self.mobileClient.create_playlist(podcastSyncTaskEpisodePush.playlistName) addedEntries = self.mobileClient.add_songs_to_playlist(gmusicPlaylistID, [songGoogleMusicID]) print "Moved track to playlist." return songGoogleMusicID # Pull (deletes) an episode from the destination returns true on success, False on faiilure def pullEpisode(self, podcastSyncTaskEpisodePull): self.mobileClient.delete_songs([podcastSyncTaskEpisodePull.syncDestinationID]) # TODO: Error check here. return True
class GMusicWS: def __init__(self, user, password, playlistName): self.playlistName = playlistName self.api = Mobileclient() print ("Logging into MobileClient API") self.api.login(user, password,"android_id") #insert unique android_id here def mapUnknownTracks(self, db): playlist = db.unmappedTracks() for track in playlist: searchstr = track.artist + " " + track.song print ("Searching for %s" % (searchstr)) try: result = self.api.search_all_access(searchstr, max_results=1) print ("Found " + result['song_hits'][0]['track']['artist'] + " - " + result['song_hits'][0]['track']['title']) nid = result['song_hits'][0]['track']['nid'] db.storemapping(track.song, track.artist, nid) except: print ("Error parsing result: " + str(result)) time.sleep(1) def maintain(self, tracks): print ("Searching for playlist %s" % (self.playlistName)) found = False searchres = self.api.get_all_playlists() for list in searchres: if list['name'] == self.playlistName: found = True pid = list['id'] if not found: print ("Not found - creating") pid = self.api.create_playlist(self.playlistName) print ("Playlist id is %s" % (pid)) print ("Getting current contents") playlists = self.api.get_all_user_playlist_contents() currentEntries = [] for playlist in playlists: if playlist['name'] == self.playlistName: for entry in playlist['tracks']: currentEntries.append(entry['id']) print ("Removing songs") self.api.remove_entries_from_playlist(currentEntries) print ("Adding songs") self.api.add_songs_to_playlist(pid, tracks)
def download_songs(self): api = Musicmanager() ip = urllib2.urlopen('http://ip.42.pl/raw').read() #Obtain your public IP address mac_binary = str(get_mac()) #Obtain binary MAC address temp = mac_binary.replace(':', '').replace('-', '').replace('.', '').upper() mac_h3x = temp[:2] + ":" + ":".join([temp[i] + temp[i+1] for i in range(2,12,2)]) #Convert MAC from 48bit int to hexadecimal string user = pwd.getpwuid(os.getuid())[0] #Get your system's username hostname = '<' + ip + '>' + '' + '(gmusicapi-{2.0.0})' Musicmanager.perform_oauth(storage_filepath='/home/' + user + '/.config/gmusicapi/oauth.cred', open_browser=False) api.login(oauth_credentials='/home/' + user + '/.config/gmusicapi/oauth.cred', uploader_id=mac_h3x, uploader_name=hostname) gmusicapi.clients.Musicmanager(debug_logging=True, validate=True) playlist_id = raw_input("insert id: ") api_ = Mobileclient() api_.login(self.email, self.password) playlist_method = api_.get_all_playlists()
def create_playlist(playlist_name): if Mobileclient.is_authenticated(gpm): # the following prints all playlist names all_playlists = Mobileclient.get_all_playlists(gpm) for playlist in all_playlists: # print(playlist) temp = set() temp.add(playlist['name']) # print(temp) return gpm.create_playlist(playlist_name) else: print("Mobileclient could not authenticate.") Mobileclient.logout(gpm)
def add_song_to_playlist(song, playlist_name): if Mobileclient.is_authenticated(gpm): # get_all_playlists() returns a dict object filled with # information about all of a user's playlists, and # each individual playlist is a dict as well. playlists = Mobileclient.get_all_playlists(gpm) # print playlists # one can get all playlist names by just accessing the # 'name' key of each playlist dict, as well as # compare the playlist name to a user given name playlist_pattern = re.compile(r'(?:.)*\s?(' + re.escape(playlist_name) + r')\s?(?:.)*', re.IGNORECASE) found = False for i in playlists: # print(i['name']) if re.match(playlist_pattern, i['name']): found = True print("Playlist found!") search = gpm.search(song, 1) for track in search['song_hits']: temp = dict(track['track']) # print(temp) gpm.add_songs_to_playlist(i['id'], temp['storeId']) print("Song " + temp['title'] + " was found, and placed in playlist: " + playlist_name) break if not found: i = create_playlist(playlist_name) print(playlist_name + ' was not found, but it was created.') search = gpm.search(song, 1) for track in search['song_hits']: temp = dict(track['track']) gpm.add_songs_to_playlist(i, temp['storeId']) print("Song " + temp['title'] + " was found, and placed in playlist: " + playlist_name) 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" 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
from gmusicapi import Mobileclient from pprint import pprint api = Mobileclient() api.login('', '', Mobileclient.FROM_MAC_ADDRESS) playlist_name = 'ToListenTo' answer=api.search_all_access('', max_results=1) sweet_track_ids = [] artist_id=answer['artist_hits'][0]['artist']['artistId'] response = api.get_artist_info(artist_id, include_albums=False, max_top_tracks=3, max_rel_artist=0) for song in response['topTracks']: sweet_track_ids.append(song['nid']) playlists = api.get_all_playlists() playlist_id = None for playlist in playlists: if playlist_name in playlist['name']: playlist_id = playlist['id'] if not playlist_id: playlist_id = api.create_playlist(playlist_name) api.add_songs_to_playlist(playlist_id, sweet_track_ids)
from gmusicapi import Mobileclient # google play musicのプレイリストを順に削除 mc = Mobileclient() mc.oauth_login(mc.FROM_MAC_ADDRESS) # google play musicのプレイリストを取得 gPlaylists = mc.get_all_playlists() for gPlaylist in gPlaylists: mc.delete_playlist(gPlaylist['id']) print("deleted playlistName = {p}".format(p=gPlaylist['name']))
class Gopma(): def __init__(self, action=None): print "Initialising GOPMA." config = ConfigParser.ConfigParser() config.read('config.ini') email = config.get('login', 'email') password = config.get('login', 'password') try: auth_token = config.get('login', 'auth_token') except: auth_token = False print "No auth token could be found" print "Logging into Google Play Music as", email logged_in = False bad_auth = False while not logged_in: if not auth_token or bad_auth: self.api = Mobileclient() login = self.api.login(email, password, Mobileclient.FROM_MAC_ADDRESS) if not login: print "Login failed, check your credentials." sys.exit() # Save the auth token for later with open('config.ini', 'w+') as f: config.set('login', 'auth_token', self.api.session._authtoken) config.write(f) f.close() print "Saved auth token for later." logged_in = True else: print "Found an auth token, trying it." self.api = Mobileclient() self.api.session._authtoken = auth_token self.api.session.is_authenticated = True try: # Test the auth token self.api.get_registered_devices() logged_in = True except: # Failed print "Bad auth token, manually signing in." bad_auth = True print "Successfully logged in as", email if action != 'reset_genres': print "Loading data." self.playlists = self.api.get_all_playlists() self.content = self.api.get_all_user_playlist_contents() self.root_genres, self.child_genres = self.load_genres() print "Data successfully loaded." def create_or_retrieve_playlists(self, playlists): """ Helper function to create or retrieve playlist IDs for a given agg_lists Input: List of playlist names Output: Dict of playlist names and IDs """ if type(playlists) is not list: print "Stop passing non-lists to this function." sys.exit() agg_lists = [ p for p in self.content if p.get('type') == 'USER_GENERATED' and p.get('name') in playlists ] # Get all playlist IDs agg_playlists = {} existing_playlists = [playlist['name'] for playlist in agg_lists] for name in playlists: if name not in existing_playlists: print "Playlist not found, creating", name agg_playlists[name] = self.api.create_playlist(name) self.api.edit_playlist(agg_playlists[name], public=True) else: print "Playlist found", name + ", retrieving ID." playlist_id = [ p['id'] for p in agg_lists if p.get('name') == name ][0] agg_playlists[name] = playlist_id # self.api.edit_playlist(agg_playlists[name], public=True) return agg_playlists def load_genres(self, reset=False): """ Load all genres """ # Get the root genres if os.path.isfile(ROOT_GENRE_FILE): print "Found a root genres file." if reset: root_genres = self.api.get_genres() with open(ROOT_GENRE_FILE, 'w') as fp: pickle.dump(root_genres, fp) print "Root genres have been reset." else: with open(ROOT_GENRE_FILE) as fp: root_genres = pickle.load(fp) else: print "Couldn't find a root genres file, retrieving data." root_genres = self.api.get_genres() with open(ROOT_GENRE_FILE, 'w') as fp: pickle.dump(root_genres, fp) print "Root genres file created." # Get the child genres if os.path.isfile(CHILD_GENRE_FILE): print "Found a child genres file." if reset: child_genres = {} for genre in root_genres: children = self.api.get_genres(genre['id']) child_names = [] for child in children: child_names.append(child['name']) child_genres[genre['id']] = child_names with open(CHILD_GENRE_FILE, 'w') as fp: pickle.dump(child_genres, fp) print "Child genres have been reset." else: with open(CHILD_GENRE_FILE) as fp: child_genres = pickle.load(fp) else: print "Couldn't find a child genres file, retrieving data." child_genres = {} for genre in root_genres: children = self.api.get_genres(genre['id']) child_names = [] for child in children: child_names.append(child['name']) child_genres[genre['id']] = child_names with open(CHILD_GENRE_FILE, 'w') as fp: pickle.dump(child_genres, fp) print "Child genres file created." return root_genres, child_genres def delete_empty_playlists(self): """ Delete ALL empty playlists. Be careful with this. """ playlists = self.content for playlist in playlists: if len(playlist['tracks'] ) == 0 and playlist['name'] != AGGREGATE_PLAYLIST_NAME: self.api.delete_playlist(playlist['id']) print "Deleted", playlist['name'] def create_playlists(self): """ Create all needed playlists """ print "Creating/updating playlists." self.create_or_retrieve_playlists( [AGGREGATE_PLAYLIST_NAME, SHARED_PLAYLIST_NAME]) self.create_or_retrieve_playlists( [PLAYLIST_PREFIX + genre for genre in GENRE_PLAYLISTS.values()]) def get_playlist_urls(self): """ Get all gopma playlist URLS """ urls = {} for playlist in self.playlists: if PLAYLIST_PREFIX in playlist['name'] and playlist[ 'type'] == 'USER_GENERATED': urls[playlist[ 'name']] = "https://play.google.com/music/playlist/" + playlist[ 'shareToken'] return urls def get_playlist_id(self, name): """ Get the playlist ID for a given playlist name """ playlist = [p for p in self.playlists if p.get('name') == name][0] return playlist['id'] def get_share_token(self, playlist_id): """ Get the share token for a given playlist ID """ playlist = [p for p in self.playlists if p.get('id') == playlist_id] return playlist[0]['shareToken'] def get_playlist_tracks(self, playlist_id): """ Get the tracks for a specified playlist id """ return [p for p in self.content if p.get('id') == playlist_id][0]['tracks'] def get_parent_genre_id(self, genre_name): """ Get the parent id for a given genre name """ # Check the root genres first for genre in self.root_genres: if genre_name == genre['name']: return genre['id'] # Check children genres for gid, genres in self.child_genres.items(): for genre in genres: if genre == genre_name: return gid def wipe_all_playlists(self): """ Wipe all Gopma playlists """ for playlist in self.playlists: if PLAYLIST_PREFIX in playlist[ 'name'] and SHARED_PLAYLIST_NAME not in playlist['name']: print "Wiping playlist: ", playlist['name'] self.wipe_playlist(playlist['id']) def wipe_playlist(self, playlist_id): """ Wipe a given playlist """ playlist_tracks = self.get_playlist_tracks(playlist_id) song_ids = [track['id'] for track in playlist_tracks] self.api.remove_entries_from_playlist(song_ids) def reset_daily_playlists(self): """ Reset the daily playlists """ # Get playlists agg_playlists = self.create_or_retrieve_playlists([TODAY, YESTERDAY]) yest_id = agg_playlists[YESTERDAY] today_id = agg_playlists[TODAY] # Wipe yesterday print "Wiping yesterday's playlist." self.wipe_playlist(yest_id) # Copy today to yesterday print "Copying", TODAY, "to", YESTERDAY today_tracks = self.get_playlist_tracks(today_id) self.api.add_songs_to_playlist(yest_id, [t['trackId'] for t in today_tracks]) # Wipe today print "Wiping today's playlist." self.wipe_playlist(today_id) def update_group_playlist(self): """ Update the big group aggregate and the daily playlist with any new shared songs """ # Get the aggregate playlist songs agg_token = self.get_share_token( self.get_playlist_id(AGGREGATE_PLAYLIST_NAME)) agg_playlists = [ p for p in self.playlists if p.get('type') == 'USER_GENERATED' and p.get('shareToken') == agg_token ] agg_id = agg_playlists[0]['id'] # Get tracks agg_tracks = self.api.get_shared_playlist_contents(agg_token) agg_tracks_ids = [track['trackId'] for track in agg_tracks] print "Updating group playlists." # Get the playlists we want to update with shared_lists = [ p for p in self.playlists if p.get('name') == SHARED_PLAYLIST_NAME ] for playlist in shared_lists: shared_tracks = self.api.get_shared_playlist_contents( playlist['shareToken']) print "\nRetrieving from", playlist[ 'name'], "by", playlist['ownerName'] + ":" # Add songs to aggregate playlist if len(shared_tracks) == 0: print "<< Playlist is empty. >>" else: no_new = True for track in shared_tracks: if track['trackId'] not in agg_tracks_ids: # Add to giant aggregate playlist self.api.add_songs_to_playlist(agg_id, track['trackId']) # Add to daily playlist self.api.add_songs_to_playlist( self.get_playlist_id(TODAY), track['trackId']) # Add to genre relevant playlist self.api.add_songs_to_playlist( self.get_playlist_id( PLAYLIST_PREFIX + GENRE_PLAYLISTS[self.get_parent_genre_id( track['track']['genre'])]), track['trackId']) title = track['track']['title'].encode( 'ascii', 'ignore') artist = track['track']['artist'].encode( 'ascii', 'ignore') print "+", title, "by", artist, "has been added." no_new = False if no_new: print "<< There are no new tracks to be added from this playlist. >>" print "Finished updating group playlists." def update_songs(self): """ Update the database with song information """ # Connect to the database config = ConfigParser.ConfigParser() config.read('config.ini') dbname = config.get('database', 'dbname') dbuser = config.get('database', 'user') dbhost = config.get('database', 'host') dbpass = config.get('database', 'password') try: conn = psycopg2.connect("dbname=" + dbname + " user="******" host=" + dbhost + " password="******"<< Could not connect to the db. >>" print e sys.exit() # Update songs for c in self.content: if c.get('type') == 'USER_GENERATED' and c.get( 'name') == AGGREGATE_PLAYLIST_NAME: songs = c.get('tracks') for s in songs: # Song details details = s['track'] # Date song was added date_added = datetime.fromtimestamp( int(s.get('creationTimestamp')) / 1000000) # Values to save values = [ str(s.get('trackId')), str(details.get('title').encode('UTF-8', 'ignore')), str(details.get('artist').encode('UTF-8', 'ignore')), str(details.get('album').encode('UTF-8', 'ignore')), str(details.get('genre')), date_added ] # SQL query insert_song = "INSERT INTO playlists_song VALUES (%s, %s, %s, %s, %s, %s) ON CONFLICT (tid) DO NOTHING;" # Save to DB self.commit_changes(conn, cur, insert_song, values) print "Songs successfully updated." # Close connection cur.close() conn.close() def commit_changes(self, conn, cur, query, values): try: # Commit our changes made cur.execute(query, values) conn.commit() except psycopg2.Error as exc: print exc sys.exit()
class GmusicStore(BackendStore): # this *must* be set. Because the (most used) MediaServer Coherence also # allows other kind of Backends (like remote lights). implements = ['MediaServer'] def __init__(self, server, *args, **kwargs): # first we initialize our heritage BackendStore.__init__(self, server, **kwargs) reload(sys) sys.setdefaultencoding('utf-8') # When a Backend is initialized, the configuration is given as keyword # arguments to the initialization. We receive it here as a dictionary # and allow some values to be set: # the name of the MediaServer as it appears in the network self.name = kwargs.get('name', 'Google Music') # timeout between updates in hours: self.refresh = int(kwargs.get('refresh', 1)) * (60 * 60) # the UPnP device that's hosting that backend, that's already done # in the BackendStore.__init__, just left here the sake of completeness self.server = server # initialize our containers self.container = GmusicContainer(None, ROOT_ID, "Google Music") self.container.tracks = GmusicContainer(self.container, TRACKS_ID, "Tracks") self.container.albums = GmusicContainer(self.container, ALBUM_ID, "Albums") self.container.playlists = GmusicContainer(self.container, PLAYLIST_ID, "Playlists") self.container.children.append(self.container.tracks) self.container.children.append(self.container.albums) self.container.children.append(self.container.playlists) # but as we also have to return them on 'get_by_id', we have our local # store of items per id: self.tracks = {} self.albums = {} self.artists = {} self.playlists = {} # we tell that if an XBox sends a request for images we'll # map the WMC id of that request to our local one # Todo: What the hell is going on here? # self.wmc_mapping = {'16': 0} self.username = kwargs.get('username', '') self.password = kwargs.get('password', '') self.device_id = kwargs.get('device_id', '') self.api = Mobileclient() if not self.login(): self.info("Could not login") return # and trigger an update of the data dfr = self.update_data() # So, even though the initialize is kind of done, Coherence does not yet # announce our Media Server. # Coherence does wait for signal send by us that we are ready now. # And we don't want that to happen as long as we don't have succeeded # in fetching some first data, so we delay this signaling after the update is done: dfr.addCallback(self.init_completed) dfr.addCallback(self.queue_update) def login(self): return self.api.login(self.username, self.password, self.device_id) def get_by_id(self, id): print("asked for", id, type(id)) # what ever we are asked for, we want to return the container only if isinstance(id, basestring): id = id.split('@', 1) id = id[0] if id == str(ROOT_ID): return self.container if id == str(ALBUM_ID): return self.container.albums if id == str(TRACKS_ID): return self.container.tracks if id == str(PLAYLIST_ID): return self.container.playlists if id in self.albums: self.info("id in albums:", id) album = self.albums.get(id, None) return album if id in self.playlists: self.info("id in playlists:", id) playlist = self.playlists.get(id, None) return playlist return self.tracks.get(id, None) def upnp_init(self): # after the signal was triggered, this method is called by coherence and # from now on self.server is existing and we can do # the necessary setup here # that allows us to specify our server options in more detail. # here we define what kind of media content we do provide # mostly needed to make some naughty DLNA devices behave # will probably move into Coherence internals one day self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo', ['http-get:*:audio/mpeg:*', 'http-get:*:application/ogg:*', ] ) # and as it was done after we fetched the data the first time # we want to take care about the server wide updates as well self._update_container() def _update_container(self, result=None): # we need to inform Coherence about these changes # again this is something that will probably move # into Coherence internals one day if self.server: self.server.content_directory_server.set_variable(0, 'SystemUpdateID', self.update_id) value = (ROOT_ID, self.container.update_id) self.server.content_directory_server.set_variable(0, 'ContainerUpdateIDs', value) return result def update_loop(self): # in the loop we want to call update_data dfr = self.update_data() # after it was done we want to take care of updating # the container dfr.addCallback(self._update_container) # in ANY case queue an update of the data dfr.addBoth(self.queue_update) # finally clean the tempfiles print("Cleaning tempfiles") for file_id, file_tmp in cache.iteritems(): if file_id not in recent_cache: os.close(file_tmp[0]) os.remove(file_tmp[1]) cache.clear() cache.update(recent_cache) recent_cache.clear() def get_data(self): subscribed_to_playlists = [p for p in self.api.get_all_playlists() if p.get('type') == 'SHARED'] for playlist in subscribed_to_playlists: playlist["tracks"] = self.api.get_shared_playlist_contents(playlist.get("shareToken", "")) return (self.api.get_all_songs(), self.api.get_all_user_playlist_contents(), subscribed_to_playlists) def update_data(self): # trigger an update of the data self.info("Updating data") dfr = task.deferLater(reactor, 0, self.get_data) # then parse the data into our models dfr.addCallback(self.parse_data) # self.parse_data(dfr) self.info("Finished update") return dfr def parse_library(self, songs): for song in songs: try: song_id = song.get("storeId", song.get("nid", 0)) title = song.get("title", "") artist_id = song.get("artistId", [0])[0] album_id = song.get("albumId", 0) album_name = song.get("album", "") duration = song.get("durationMillis", 0) album_art_uri = song.get("albumArtRef", [{"url":""}])[0].get("url", "") track_no = song.get("trackNumber", "0") disc_no = song.get("discNumber", "0") artist = song.get("artist", "") album_artist = song.get("albumArtist", artist) if album_id in self.albums: album = self.albums[album_id] else: album = GmusicAlbum(ALBUM_ID, self, album_id, album_name, artist_id, album_artist, album_art_uri) self.container.albums.children.append(album) self.albums[album_id] = album track = GmusicTrack(TRACKS_ID, self, song_id, title, artist, album_name, artist_id, album_id, track_no, duration, album_art_uri) album.tracks[int(str(disc_no) + str('{:0>10}'.format(track_no)))] = track self.container.tracks.children.append(track) self.tracks[song_id] = track # i = i + 1 except Exception as e: print(e) print(song) traceback.print_exc() try: self.container.albums.children.sort(key=lambda x: (x.artist, x.title)) except Exception as e: print("Failed to sort albums") print(e) traceback.print_exc() def parse_playlist_tracks(self, tracks): tracklist = [] for track in tracks: try: song = track.get("track", {}) song_id = song.get("storeId", song.get("nid", 0)) if song_id in self.tracks: tracklist.append(self.tracks[song_id]) else: title = song.get("title", "") artist_id = song.get("artistId", [0])[0] album_id = song.get("albumId", 0) album_name = song.get("album", "") duration = song.get("durationMillis", 0) album_art_uri = song.get("albumArtRef", [{"url":""}])[0].get("url", "") track_no = song.get("trackNumber", "0") artist = song.get("artist", "") track_container = GmusicTrack(TRACKS_ID, self, song_id, title, artist, album_name, artist_id, album_id, track_no, duration, album_art_uri) tracklist.append(track_container) self.tracks[song_id] = track_container except Exception as e: print(e) print(track) traceback.print_exc() return tracklist def parse_playlists(self, playlists): for playlist in playlists: try: playlist_id = playlist.get("id", playlist.get("shareToken", "DEBUG: NO_ID_TOKEN")) playlist_owner = playlist.get("ownerName", "DEBUG: NO_AUTHOR") playlist_owner_image = playlist.get("ownerProfilePhotoUrl", "DEBUG: NO_AUTHOR_IMAGE") playlist_name = playlist.get("name", "DEBUG: NO_NAME") playlist_tracks = playlist.get("tracks", {}) playlist_container = GmusicPlaylist(PLAYLIST_ID, self, playlist_id, playlist_name, playlist_owner, playlist_owner_image) self.container.playlists.children.append(playlist_container) self.playlists[playlist_id] = playlist_container playlist_container.tracks = self.parse_playlist_tracks(playlist_tracks) except Exception as e: print(e) print(playlist) traceback.print_exc() def parse_data(self, data): self.info("Parsing data") # reset the storage self.container.tracks.children = [] self.container.albums.children = [] self.container.playlists.children = [] self.tracks = {} self.albums = {} self.playlists = {} self.parse_library(data[0]) self.parse_playlists(data[1]) self.parse_playlists(data[2]) self.info("Finished parsing") # and increase the container update id and the system update id # so that the clients can refresh with the new data # Todo: Does this actually do anyting? self.container.update_id += 1 self.update_id += 1 def queue_update(self, error_or_failure): # We use the reactor to queue another updating of our data print error_or_failure reactor.callLater(self.refresh, self.update_loop)
songs_ids = [] for line in options.file.readlines(): line = line.strip().decode('utf-8') if not line: continue log.info('Searching for %s', line) result = cached_search(cache, api, line) songs = result['song_hits'] songs = filter_songs(songs, line) if len(songs): songs_ids.append(songs[0]['track']['storeId']) continue log.debug(songs) for pl in api.get_all_playlists(): if pl['name'] == options.playlist: api.delete_playlist(pl['id']) playlist_id = api.create_playlist(options.playlist) try: api.add_songs_to_playlist(playlist_id, songs_ids) except Exception, _: api.delete_playlist(playlist_id)
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.get('name') tracks = plist.get('tracks') if plist_name and tracks: logging.info("playlist name : %s", to_ascii(plist_name)) tracks.sort(key=itemgetter('creationTimestamp')) self.playlists[plist_name] = list() for track in tracks: song_id = track.get('trackId') if song_id: song = self.song_map.get(song_id) if song: self.playlists[plist_name].append(song) 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 FetchSongs(object): """ Class to manage updating and creating of playlists and songs in the Google Play Music service for a specific Play Music account """ def __init__(self): self.songs = [] self.nids = [] os.environ['REQUESTS_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt' self.api = Mobileclient() self.api.login('xxxxxx', 'xxxxx', Mobileclient.FROM_MAC_ADDRESS) def add_songs_to_gmusic_playlist(self, playlist_id, song_ids): """ Add songs to the GMUSIC API playlist using song ids :param playlist_id: str playlist id :param song_ids: list of strings :returns: None """ return self.api.add_songs_to_playlist(playlist_id, song_ids) def search_for_songs(self, artist, title): """ Search for songs in the GMUSIC API :param artist: string :param title: string :return song_nid: string """ song_nid = None search_content = self.api.search(''.join([artist, ' ', title])) for each_song in search_content['song_hits']: song_detail = box.Box(each_song['track']) if artist.casefold() in song_detail.artist.casefold( ) and title.casefold() in song_detail.title.casefold(): song_nid = song_detail.storeId break return song_nid def get_playlists_length(self, api_content): """ Get size of GMUSIC playlists in terms of number of songs :param api_content: dict :returns playlist_sizes: dict """ playlist_sizes = {} api_playlists = self.api.get_all_playlists() for api_playlist in api_playlists: for content_playlist in api_content: if content_playlist['id'] == api_playlist['id']: playlist_sizes[api_playlist['id']] = len( content_playlist['tracks']) return playlist_sizes def get_available_playlists(playlist_dict): """ Get available playlist ids and associated number of songs :param playlist_dict: dict :return list_id, number_of_songs: tuple of list_id and number of songs """ available_lists = {k for (k, v) in playlist_dict.items() if v <= 800} if available_lists: for list_id, number_of_songs in available_lists.items(): yield (list_id, number_of_songs) else: return None @classmethod def create_gmusic_playlist(self, name): """ Create GMUSIC playlist :param name: string name of playlist :return: string success or fail""" return self.api.create_playlist(name)
class GPMPlGen: def __init__(self, config): self.config = config logging.basicConfig(level=config.log_level) self.logger = logging.getLogger(__name__) # Client self.client = Mobileclient(debug_logging=False) gmusicapi_logger = logging.getLogger('gmusicapi.utils') gmusicapi_logger.setLevel(logging.INFO) # Logging in self.logger.info('Logging in...') try: android_id = config.client_id if android_id is None: android_id = Mobileclient.FROM_MAC_ADDRESS success = self.client.oauth_login(android_id) if not success: self.client.perform_oauth() success = self.client.oauth_login(android_id) if not success: raise GPMPlGenException("Could not log in") except Exception as e: raise GPMPlGenException("Could not log in", e) # Setting up writer if config.dry_run: self.logger.info('Running in DRY-RUN mode; setting up mock client...') self.writer_client = ClientMock() else: self.writer_client = self.client # Local database self.db = LibraryDb(config.local_db) # Internal stuff self.timestamp = time.time() def _get_all_songs(self): self.logger.info("Downloading all tracks from library") try: library_from_gpm = self.client.get_all_songs(incremental=False) except Exception as e: raise GPMPlGenException("Could not download library", e) self.logger.info("Loaded %d songs" % (len(library_from_gpm))) return library_from_gpm def _get_all_playlists_songs(self, static_playlists): self.logger.info("Downloading all tracks from playlists") try: playlists_with_contents = self.client.get_all_user_playlist_contents() self.logger.info("Loaded %d playlists" % (len(playlists_with_contents))) except Exception as e: raise GPMPlGenException("Could not download playlist contents", e) songs_from_static_playlists = [] static_playlists_ids = map((lambda p: p['id']), static_playlists) for p in playlists_with_contents: if p['id'] in static_playlists_ids: if 'tracks' in p: for t in p['tracks']: if 'track' in t: track = t['track'] track['id'] = t['id'] else: track = t songs_from_static_playlists.append(track) self.logger.info("Loaded %d songs" % (len(songs_from_static_playlists))) return songs_from_static_playlists def store_songs_in_db(self): library_from_gpm = self._get_all_songs() self.db.ingest_track_list(library_from_gpm, LibraryDb.LIBRARY_TABLE) def store_playlists_in_db(self, get_songs=True): self.logger.info("Downloading all generated playlists from library") (generated_playlists, other_playlists) = self._get_all_playlists() self.logger.info("Loading %d generated playlists" % (len(generated_playlists))) self.db.ingest_generated_playlists(generated_playlists) if not get_songs: return self.logger.info("Loading %d static playlists" % (len(other_playlists))) tracks = self._get_all_playlists_songs(other_playlists) self.db.ingest_track_list(tracks, LibraryDb.STATIC_PL_TABLE) def retrieve_library(self, get_songs=True): if self.db.cache_mode: if self.config.write_to_db: self.logger.info("Storing into cache") self.store_songs_in_db() self.store_playlists_in_db() self.db.consolidate_all_tracks() else: self.logger.info("Using cache") return # Get songs if get_songs: self.store_songs_in_db() # Get playlists and potentially their contents self.store_playlists_in_db(get_songs) # Generate full track list if get_songs: self.db.consolidate_all_tracks() def _get_all_playlists(self): self.logger.info('Getting playlists') playlists_from_gpm = [] try: playlists_from_gpm = self.client.get_all_playlists() except Exception as e: raise GPMPlGenException("Could not download playlists", e) generated_playlists = [] static_playlists = [] for pl in playlists_from_gpm: if Playlist.is_generated_by_gpmplgen(pl): generated_playlists.append(pl) else: static_playlists.append(pl) return (generated_playlists, static_playlists) def delete_playlists(self, playlists): # FIXME: replace with playlist.delete for pl in playlists: self.logger.info('Deleting %s: %s' % (pl.id, pl.name)) self.writer_client.delete_playlist(pl.id) def cleanup_all_generated_playlists(self): self.delete_playlists(self.db.get_generated_playlists()) def generate_playlist(self, playlist_type, playlistConfig): generator = PlaylistGenerator(self.config.playlist_prefix, self.timestamp, self.db) try: generator_results = generator.generate(playlist_type, playlistConfig) except PlaylistGeneratorError as e: self.logger.error(e) return self.delete_playlists(generator_results.get_playlists_to_delete()) playlists_to_create = generator_results.get_playlists_to_create() created = 0 try: for pl in playlists_to_create: pl.create_in_gpm(self.writer_client) created = created + 1 except GPMPlGenException as e: self.logger.error("Error talking to Google Play Music; attempting to clean-up") for pl in playlists_to_create: pl.delete_in_gpm(self.writer_client) self.logger.debug(e.parent_exception) raise e # FIXME: maybe not? return created
# GPLAY_USERNAME= (no quotes) # GPLAY_PW= (no quotes) load_dotenv(find_dotenv()) # Set up Google Play Music Mobile Client mc = Mobileclient() # Retrieve Google Play Music email and password from environment variables gplay_email = os.environ['GPLAY_USERNAME'] gplay_password = os.environ['GPLAY_PW'] logged_in = mc.login(gplay_email, gplay_password, Mobileclient.FROM_MAC_ADDRESS) if logged_in: # Save list of current playlists to not add duplicates current_playlists = [d['name'] for d in mc.get_all_playlists()] if len(sys.argv) > 1: username = sys.argv[1] else: print "usage: python music_conversion.py [spotify_username]" sys.exit() scope = 'user-library-read' token = util.prompt_for_user_token(username, scope) if token: sp = spotipy.Spotify(auth=token) playlists = sp.user_playlists(username) for playlist in playlists['items']: if playlist['owner']['id'] == username and playlist['name'] not in current_playlists: print
from cmenu import Menu api = Mobileclient(debug_logging = False) if not args.username: args.username = raw_input('Username: '******'Password:'******'Logging in uwer %s.', args.username) if not api.login(args.username, args.password, ''.join([choice('1234567890abcdef') for x in xrange(16)]) if args.random_id else api.FROM_MAC_ADDRESS): quit('Login failed.') m = Menu('Select source to download') m.add_entry('Library', api.get_all_songs) for p in api.get_all_playlists(): m.add_entry('%s playlist' % p.get('name', 'Untitled'), lambda token = p['shareToken']: [x['track'] for x in api.get_shared_playlist_contents(token)]) res = m.get_selection() if res: logging.debug('Getting source contents.') program_start = time() logging.info('Started the download at %s.', ctime(program_start)) total = 0.0 # The average download time. try: for i, r in enumerate(res()): logging.debug('Result = %s', r) artist = valid_filename(r.get('artist', 'Unknown Artist')) album = valid_filename(r.get('album', 'Unknown Album')) number = r.get('trackNumber', 0) if number < 10: number = '0%s' % number
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()
# Need to get a valid device ID from the exception text when using an invalid ID. deviceId = 'deviceId' while True: try: mc.oauth_login(deviceId) break except InvalidDeviceId as exc: deviceId = str(exc).split('\n')[1].replace('* ', '') else: # First time login - do OAuth in browser mc.perform_oauth(open_browser=True) # Find the playlist if it exists, or create it print('Finding playlist "{0}"'.format(playlistName)) existingPlaylist = list( filter(lambda p: p['name'] == playlistName, mc.get_all_playlists())) if len(existingPlaylist) > 0: playlistId = existingPlaylist[0]['id'] else: playlistId = mc.create_playlist(playlistName) print('Fetching data from server') # Get all songs in library allTracks = mc.get_all_songs() # Get contents of our playlist playlist = list( filter(lambda p: p['id'] == playlistId, mc.get_all_user_playlist_contents()))[0]
class Session(object): def __init__(self): self.api = None self.user = None self.lib_albums = {} self.lib_artists = {} self.lib_tracks = {} self.lib_updatetime = 0 self.sitdata = [] self.sitbyid = {} self.sitdataupdtime = 0 def dmpdata(self, who, data): print("%s: %s" % (who, json.dumps(data, indent=4)), file=sys.stderr) def login(self, username, password, deviceid=None): self.api = Mobileclient(debug_logging=False) if deviceid is None: logged_in = self.api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) else: logged_in = self.api.login(username, password, deviceid) # print("Logged in: %s" % logged_in) # data = self.api.get_registered_devices() # print("registered: %s" % data) # isauth = self.api.is_authenticated() # print("Auth ok: %s" % isauth) return logged_in def _get_user_library(self): now = time.time() if now - self.lib_updatetime < 300: return data = self.api.get_all_songs() # self.dmpdata("all_songs", data) self.lib_updatetime = now tracks = [_parse_track(t) for t in data] self.lib_tracks = dict([(t.id, t) for t in tracks]) for track in tracks: # We would like to use the album id here, but gmusic # associates the tracks with any compilations after # uploading (does not use the metadata apparently), so # that we can't (we would end up with multiple # albums). OTOH, the album name is correct (so seems to # come from the metadata). What we should do is test the # album ids for one album with a matching title, but we're # not sure to succeed. So at this point, the album id we # end up storing could be for a different albums, and we # should have a special library-local get_album_tracks self.lib_albums[track.album.name] = track.album self.lib_artists[track.artist.id] = track.artist def get_user_albums(self): self._get_user_library() return self.lib_albums.values() def get_user_artists(self): self._get_user_library() return self.lib_artists.values() def get_user_playlists(self): pldata = self.api.get_all_playlists() # self.dmpdata("playlists", pldata) return [_parse_playlist(pl) for pl in pldata] def get_user_playlist_tracks(self, playlist_id): self._get_user_library() data = self.api.get_all_user_playlist_contents() # self.dmpdata("user_playlist_content", data) trkl = [item["tracks"] for item in data if item["id"] == playlist_id] if not trkl: return [] try: return [self.lib_tracks[track["trackId"]] for track in trkl[0]] except: return [] def create_station_for_genre(self, genre_id): id = self.api.create_station("station" + genre_id, genre_id=genre_id) return id def get_user_stations(self): data = self.api.get_all_stations() # parse_playlist works fine for stations stations = [_parse_playlist(d) for d in data] return stations def delete_user_station(self, id): self.api.delete_stations(id) # not working right now def listen_now(self): print("api.get_listen_now_items()", file=sys.stderr) ret = {"albums": [], "stations": []} try: data = self.api.get_listen_now_items() except Exception as err: print("api.get_listen_now_items failed: %s" % err, file=sys.stderr) data = None # listen_now entries are not like normal albums or stations, # and need special parsing. I could not make obvious sense of # the station-like listen_now entries, so left them aside for # now. Maybe should use create_station on the artist id? if data: ret["albums"] = [_parse_ln_album(a["album"]) for a in data if "album" in a] # ret['stations'] = [_parse_ln_station(d['radio_station']) \ # for d in data if 'radio_station' in d] else: print("listen_now: no items returned !", file=sys.stderr) print( "get_listen_now_items: returning %d albums and %d stations" % (len(ret["albums"]), len(ret["stations"])), file=sys.stderr, ) return ret def get_situation_content(self, id=None): ret = {"situations": [], "stations": []} now = time.time() if id is None and now - self.sitdataupdtime > 300: self.sitbyid = {} self.sitdata = self.api.get_listen_now_situations() self.sitdataupdtime = now # Root is special, it's a list of situations if id is None: ret["situations"] = [self._parse_situation(s) for s in self.sitdata] return ret # not root if id not in self.sitbyid: print("get_situation_content: %s unknown" % id, file=sys.stderr) return ret situation = self.sitbyid[id] # self.dmpdata("situation", situation) if "situations" in situation: ret["situations"] = [self._parse_situation(s) for s in situation["situations"]] if "stations" in situation: ret["stations"] = [_parse_situation_station(s) for s in situation["stations"]] return ret def _parse_situation(self, data): self.sitbyid[data["id"]] = data return Playlist(id=data["id"], name=data["title"]) def create_curated_and_get_tracks(self, id): sid = self.api.create_station("station" + id, curated_station_id=id) print("create_curated: sid %s" % sid, file=sys.stderr) tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)] # print("curated tracks: %s"%tracks, file=sys.stderr) self.api.delete_stations(sid) return tracks def get_station_tracks(self, id): return [_parse_track(t) for t in self.api.get_station_tracks(id)] def get_media_url(self, song_id, quality=u"med"): url = self.api.get_stream_url(song_id, quality=quality) print("get_media_url got: %s" % url, file=sys.stderr) return url def get_album_tracks(self, album_id): data = self.api.get_album_info(album_id, include_tracks=True) album = _parse_album(data) return [_parse_track(t, album) for t in data["tracks"]] def get_promoted_tracks(self): data = self.api.get_promoted_songs() # self.dmpdata("promoted_tracks", data) return [_parse_track(t) for t in data] def get_genres(self, parent=None): data = self.api.get_genres(parent_genre_id=parent) return [_parse_genre(g) for g in data] def get_artist_info(self, artist_id, doRelated=False): ret = {"albums": [], "toptracks": [], "related": []} # Happens,some library tracks have no artistId entry if artist_id is None or artist_id == "None": print("get_artist_albums: artist_id is None", file=sys.stderr) return ret else: print("get_artist_albums: artist_id %s" % artist_id, file=sys.stderr) maxrel = 20 if doRelated else 0 maxtop = 0 if doRelated else 10 incalbs = False if doRelated else True data = self.api.get_artist_info(artist_id, include_albums=incalbs, max_top_tracks=maxtop, max_rel_artist=maxrel) # self.dmpdata("artist_info", data) if "albums" in data: ret["albums"] = [_parse_album(alb) for alb in data["albums"]] if "topTracks" in data: ret["toptracks"] = [_parse_track(t) for t in data["topTracks"]] if "related_artists" in data: ret["related"] = [_parse_artist(a) for a in data["related_artists"]] return ret def get_artist_related(self, artist_id): data = self.get_artist_info(artist_id, doRelated=True) return data["related"] def search(self, query): data = self.api.search(query, max_results=50) # self.dmpdata("Search", data) tr = [_parse_track(i["track"]) for i in data["song_hits"]] print("track ok", file=sys.stderr) ar = [_parse_artist(i["artist"]) for i in data["artist_hits"]] print("artist ok", file=sys.stderr) al = [_parse_album(i["album"]) for i in data["album_hits"]] print("album ok", file=sys.stderr) # self.dmpdata("Search playlists", data['playlist_hits']) try: pl = [_parse_splaylist(i) for i in data["playlist_hits"]] except: pl = [] print("playlist ok", file=sys.stderr) return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
class GMusic(object): def __init__(self): self.authenticated = False self.all_access = False self._device = None self._webclient = Webclient(debug_logging=False) self._mobileclient = Mobileclient(debug_logging=False) self._playlists = [] self._playlist_contents = [] self._all_songs = [] self._all_artists = {} self._all_albums = {} self._all_genres = {} self._stations = [] def _get_device_id(self): if self.authenticated: devices = self._webclient.get_registered_devices() for dev in devices: if dev['type'] == 'PHONE': self._device = dev['id'][2:] break 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 authenticate(self, email, password): try: mcauthenticated = self._mobileclient.login(email, password) except AlreadyLoggedIn: mcauthenticated = True try: wcauthenticated = self._webclient.login(email, password) except AlreadyLoggedIn: wcauthenticated = True self.authenticated = mcauthenticated and wcauthenticated self._get_device_id() self._set_all_access() return self.authenticated def get_all_songs(self, id=None): 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 [] if id: return [x for x in self._all_songs if x['id'] == id][0] else: return self._all_songs def get_all_artists(self): if not self._all_artists: songs = self.get_all_songs() for song in songs: artist = song['artist'] thumb = None if artist not in self._all_artists: self._all_artists[artist] = [] track = {'title': song['title'], 'album': song['album'], 'artist': artist, 'durationMillis': song['durationMillis'], 'trackType': song['trackNumber'], 'id': song['id']} if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] if 'artistArtRef' in song: thumb = song['artistArtRef'][0]['url'] if 'storeId' in song: track['storeId'] = song['storeId'] self._all_artists[artist].append({'track': track, 'thumb': thumb, 'id': song['id']}) return self._all_artists def get_all_albums(self): if not self._all_albums: songs = self.get_all_songs() for song in songs: album = song['album'] thumb = None if album not in self._all_albums: self._all_albums[album] = [] track = {'title': song['title'], 'album': album, 'artist': song['artist'], 'durationMillis': song['durationMillis'], 'trackType': song['trackNumber'], 'id': song['id']} if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] thumb = song['albumArtRef'][0]['url'] if 'storeId' in song: track['storeId'] = song['storeId'] self._all_albums[album].append({'track': track, 'thumb': thumb, 'id': song['id']}) return self._all_albums def get_all_genres(self): if not self._all_genres: songs = self.get_all_songs() for song in songs: genre = song['genre'] if genre not in self._all_genres: self._all_genres[genre] = [] track = {'title': song['title'], 'album': song['album'], 'artist': song['artist'], 'durationMillis': song['durationMillis'], 'trackType': song['trackNumber'], 'id': song['id']} if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] if 'storeId' in song: track['storeId'] = song['storeId'] self._all_genres[genre].append({'track': track, 'id': song['id']}) return self._all_genres 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 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
class Session(object): def __init__(self): self.api = None self.user = None self.lib_albums = {} self.lib_artists = {} self.lib_tracks = {} self.lib_playlists = {} self.lib_updatetime = 0 self.sitdata = [] self.sitbyid = {} self.sitdataupdtime = 0 def dmpdata(self, who, data): uplog("%s: %s" % (who, json.dumps(data, indent=4))) # Look for an Android device id in the registered devices. def find_device_id(self, data): for entry in data: if "type" in entry and entry["type"] == u"ANDROID": # Get rid of 0x id = entry["id"][2:] uplog("Using deviceid %s" % id) return id return None def login(self, username, password, deviceid=None): self.api = Mobileclient(debug_logging=False) if deviceid is None: logged_in = self.api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) if logged_in: # Try to re-login with a valid deviceid data = self.api.get_registered_devices() #self.dmpdata("registered devices", data) deviceid = self.find_device_id(data) if deviceid: logged_in = self.login(username, password, deviceid) else: logged_in = self.api.login(username, password, deviceid) isauth = self.api.is_authenticated() #uplog("login: Logged in: %s. Auth ok: %s" % (logged_in, isauth)) return logged_in def _get_user_library(self): now = time.time() if now - self.lib_updatetime < 300: return if self.lib_updatetime == 0: data = self.api.get_all_songs() #self.dmpdata("all_songs", data) else: data = self.api.get_all_songs(updated_after=datetime.datetime.fromtimestamp(self.lib_updatetime)) #self.dmpdata("all_songs_since_update", data) self.lib_updatetime = now tracks = [_parse_track(t) for t in data] self.lib_tracks.update(dict([(t.id, t) for t in tracks])) for track in tracks: # We would like to use the album id here, but gmusic # associates the tracks with any compilations after # uploading (does not use the metadata apparently), so # that we can't (we would end up with multiple # albums). OTOH, the album name is correct (so seems to # come from the metadata). What we should do is test the # album ids for one album with a matching title, but we're # not sure to succeed. So at this point, the album id we # end up storing could be for a different albums, and we # should have a special library-local get_album_tracks self.lib_albums[track.album.name] = track.album self.lib_artists[track.artist.id] = track.artist def get_user_albums(self): self._get_user_library() return self.lib_albums.values() def get_user_artists(self): self._get_user_library() return self.lib_artists.values() def get_user_playlists(self): pldata = self.api.get_all_playlists() #self.dmpdata("playlists", pldata) return [_parse_playlist(pl) for pl in pldata] def get_user_playlist_tracks(self, playlist_id): self._get_user_library() # Unfortunately gmusic does not offer incremental updates for # playlists. This means we must download all playlist data any # time we want an update. Playlists include track information # for gmusic songs, so if a user has a lot of playlists this # can take some time. # For now, we only load the playlists one time for performance purposes if len(self.lib_playlists) == 0: data = self.api.get_all_user_playlist_contents() self.lib_playlists = dict([(pl['id'], pl) for pl in data]) tracks = [] if playlist_id in self.lib_playlists: for entry in self.lib_playlists[playlist_id]['tracks']: if entry['deleted']: continue if entry['source'] == u'1': tracks.append(self.lib_tracks[entry['trackId']]) elif 'track' in entry: tracks.append(_parse_track(entry['track']) ) return tracks def create_station_for_genre(self, genre_id): id = self.api.create_station("station"+genre_id, genre_id=genre_id) return id def get_user_stations(self): data = self.api.get_all_stations() # parse_playlist works fine for stations stations = [_parse_playlist(d) for d in data] return stations def delete_user_station(self, id): self.api.delete_stations(id) # not working right now def listen_now(self): print("api.get_listen_now_items()", file=sys.stderr) ret = {'albums' : [], 'stations' : []} try: data = self.api.get_listen_now_items() except Exception as err: print("api.get_listen_now_items failed: %s" % err, file=sys.stderr) data = None # listen_now entries are not like normal albums or stations, # and need special parsing. I could not make obvious sense of # the station-like listen_now entries, so left them aside for # now. Maybe should use create_station on the artist id? if data: ret['albums'] = [_parse_ln_album(a['album']) \ for a in data if 'album' in a] #ret['stations'] = [_parse_ln_station(d['radio_station']) \ # for d in data if 'radio_station' in d] else: print("listen_now: no items returned !", file=sys.stderr) print("get_listen_now_items: returning %d albums and %d stations" %\ (len(ret['albums']), len(ret['stations'])), file=sys.stderr) return ret def get_situation_content(self, id = None): ret = {'situations' : [], 'stations' : []} now = time.time() if id is None and now - self.sitdataupdtime > 300: self.sitbyid = {} self.sitdata = self.api.get_listen_now_situations() self.sitdataupdtime = now # Root is special, it's a list of situations if id is None: ret['situations'] = [self._parse_situation(s) \ for s in self.sitdata] return ret # not root if id not in self.sitbyid: print("get_situation_content: %s unknown" % id, file=sys.stderr) return ret situation = self.sitbyid[id] #self.dmpdata("situation", situation) if 'situations' in situation: ret['situations'] = [self._parse_situation(s) \ for s in situation['situations']] if 'stations' in situation: ret['stations'] = [_parse_situation_station(s) \ for s in situation['stations']] return ret def _parse_situation(self, data): self.sitbyid[data['id']] = data return Playlist(id=data['id'], name=data['title']) def create_curated_and_get_tracks(self, id): sid = self.api.create_station("station"+id, curated_station_id=id) print("create_curated: sid %s"%sid, file=sys.stderr) tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)] #print("curated tracks: %s"%tracks, file=sys.stderr) self.api.delete_stations(sid) return tracks def get_station_tracks(self, id): return [_parse_track(t) for t in self.api.get_station_tracks(id)] def get_media_url(self, song_id, quality=u'med'): url = self.api.get_stream_url(song_id, quality=quality) print("get_media_url got: %s" % url, file=sys.stderr) return url def get_album_tracks(self, album_id): data = self.api.get_album_info(album_id, include_tracks=True) album = _parse_album(data) return [_parse_track(t, album) for t in data['tracks']] def get_promoted_tracks(self): data = self.api.get_promoted_songs() #self.dmpdata("promoted_tracks", data) return [_parse_track(t) for t in data] def get_genres(self, parent=None): data = self.api.get_genres(parent_genre_id=parent) return [_parse_genre(g) for g in data] def get_artist_info(self, artist_id, doRelated=False): ret = {"albums" : [], "toptracks" : [], "related" : []} # Happens,some library tracks have no artistId entry if artist_id is None or artist_id == 'None': uplog("get_artist_albums: artist_id is None") return ret else: uplog("get_artist_albums: artist_id %s" % artist_id) maxrel = 20 if doRelated else 0 maxtop = 0 if doRelated else 10 incalbs = False if doRelated else True data = self.api.get_artist_info(artist_id, include_albums=incalbs, max_top_tracks=maxtop, max_rel_artist=maxrel) #self.dmpdata("artist_info", data) if 'albums' in data: ret["albums"] = [_parse_album(alb) for alb in data['albums']] if 'topTracks' in data: ret["toptracks"] = [_parse_track(t) for t in data['topTracks']] if 'related_artists' in data: ret["related"] = [_parse_artist(a) for a in data['related_artists']] return ret def get_artist_related(self, artist_id): data = self.get_artist_info(artist_id, doRelated=True) return data["related"] def search(self, query): data = self.api.search(query, max_results=50) #self.dmpdata("Search", data) tr = [_parse_track(i['track']) for i in data['song_hits']] ar = [_parse_artist(i['artist']) for i in data['artist_hits']] al = [_parse_album(i['album']) for i in data['album_hits']] #self.dmpdata("Search playlists", data['playlist_hits']) try: pl = [_parse_splaylist(i) for i in data['playlist_hits']] except: pl = [] return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
class GoogleMusic: __metaclass__ = Singleton SKIP_ARTIST = ['vox freaks'] def __init__(self): self.gmusicapi = Mobileclient(debug_logging=False) logged_in = self.gmusicapi.login( email=Config.GOOGLE_MUSIC_USER_NAME, password=Config.GOOGLE_MUSIC_APP_PASSWORD, locale='en_US', android_id=Mobileclient.FROM_MAC_ADDRESS) if not logged_in: raise Exception('Unable to log in to GoogleMusic') def get_lib_from_gmusic(self): lib = self.gmusicapi.get_all_songs() lib_list = [] song = None for song in lib: song = Song(artist=song['artist'], title=song['title']) lib_list.append(song) return lib_list def delete_playlist_if_exists(self, name): all_playlists = self.gmusicapi.get_all_playlists() for playlist in all_playlists: if playlist['name'] == name: self.gmusicapi.delete_playlist(playlist['id']) def create_playlist(self, name, description, public=True): return self.gmusicapi.create_playlist(name=name, description=description, public=public) def add_songs_to_playlist(self, playlist_id, song_ids=None, song_df=None): if (song_ids is None and song_df is None): raise ValueError('Need song_ids or song_dfs to add to playlist') if song_df is not None and (not song_df.empty): song_ids = song_df.google_music_store_id.dropna().tolist() return self.gmusicapi.add_songs_to_playlist(playlist_id, song_ids) def gmusic_constrained_search(self, song, query, strict): song_hits = query['song_hits'] for result in song_hits: track = result['track'] if track['albumArtist'].lower() in SKIP_ARTIST: continue if song.remix: if "remix" not in track['title'].lower(): continue else: if "remix" in track['title'].lower(): continue if strict: full_title = "{} - {}".format(track['albumArtist'], track['title']) score = difflib.SequenceMatcher(None, song.full_title.lower(), full_title.lower()).ratio() if score < 0.6: continue return track return None def search_song(self, song, strict=False): try: first_query = self.gmusicapi.search(song.full_title) first_result = self.gmusic_constrained_search( song, first_query, strict) if first_result is None: second_query = self.gmusicapi.search(song.full_title_stripped) first_result = self.gmusic_constrained_search( song, second_query, strict) if first_result is None: logger.warning( 'No satisfactory result found in Google Music for {}'. format(song.full_title)) return first_result except Exception as e: logger.debug('Exception: {}'.format(e)) logger.info(u'Skipped {}'.format(song.title)) return None def get_store_id(self, result): store_id = None if result: if result.has_key('storeId'): store_id = result['storeId'] return store_id def get_google_rating(self, result): rating = None if result: if result.has_key('rating'): return result['rating'] return rating def update_playlist(self, playlist, public=True, exclude_0_rating=True): #Delete Playlist if present. logger.info(u'Updating the playlist {} in GoogleMusic'.format( playlist.name)) self.delete_playlist_if_exists(playlist.name) #Create Playlist playlist_id = self.create_playlist(name=playlist.name, description=playlist.description, public=public) if exclude_0_rating: playlist.song_df = playlist.song_df[ playlist.song_df['google_music_rating'] != '1'] self.add_songs_to_playlist(playlist_id=playlist_id, song_df=playlist.song_df)
def main(): log.setLevel(logging.INFO) logging.getLogger('gmusicapi').setLevel(logging.INFO) cred_path = os.path.join(os.path.expanduser('~'), '.gmusic-sync') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' 'be found either. Try creating %s and specifying your ' 'username/password there. Make sure to chmod 600.' % cred_path) if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( 'Config file is not protected. Please run: ' 'chmod 600 %s' % cred_path) config = ConfigParser.ConfigParser() config.read(cred_path) src_user = config.get('src','username') src_pass = config.get('src','password') src_device = config.get('src','deviceid') dst_user = config.get('dst','username') dst_pass = config.get('dst','password') dst_device = config.get('dst','deviceid') if not src_user or not src_pass or not dst_user or not dst_pass: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not src_device or not dst_device: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) parser = argparse.ArgumentParser(description='gmusic-sync', add_help=False) parser.add_argument('-d', '--dst', help='Perform operation on the dst account', action='store_true', dest='dst') parser.add_argument('-l', '--list', help='List playlists on the src account', action='store_true', dest='lst') parser.add_argument('-p', '--playlist', help='Playlist ID from src account to transfer', dest='playlist') args = parser.parse_args() api = Mobileclient() if args.dst: api.login(dst_user, dst_pass, dst_device) else: api.login(src_user, src_pass, src_device) playlists = api.get_all_playlists() if args.lst: for playlist in playlists: print playlist['name'] + ' (' + playlist['id'] + ') ' exit() library = api.get_all_songs() if args.playlist is None: print 'Error: no playlist selected' all_playlist_entries = api.get_all_user_playlist_contents() selected_playlist_entries = [] for entry in all_playlist_entries: if entry['id'] == args.playlist: selected_playlist_entries = entry['tracks'] playlist_tracks = [] for ptrack in selected_playlist_entries: track_found = False for track in library: if ptrack['trackId'] == track['id']: playlist_tracks.append(track) track_found = True break try: if ptrack['trackId'] == track['storeId']: playlist_tracks.append(track) track_found = True break except: pass if not track_found: print 'ERROR: could not find playlist entry ' + str(ptrack) api.add_aa_track(ptrack['trackId']) if len(playlist_tracks) != len(selected_playlist_entries): print 'Error: could not locate all playlist tracks in src library' exit() failed_tracks = [] playlist_tracks_reversed = [] for track in playlist_tracks: playlist_tracks_reversed.insert(0, track) for track in playlist_tracks_reversed: track['rating'] = '5' res = api.change_song_metadata(track) if len(res) != 1: raise Exception('Could not change track metadata!') time.sleep(1)
class GMusic(object): def __init__(self): self.authenticated = False self.all_access = False self.library_loaded = False self.all_songs = [] self.letters = {} self.artists = {} self.albums = {} self.genres = {} self.tracks_by_letter = {} self.tracks_by_artist = {} self.tracks_by_album = {} self.tracks_by_genre = {} self._device = None self._webclient = Webclient(debug_logging=False) self._mobileclient = Mobileclient(debug_logging=False) self._playlists = [] self._playlist_contents = [] self._stations = [] def _get_device_id(self): if self.authenticated: devices = self._webclient.get_registered_devices() for dev in devices: if dev['type'] == 'PHONE': self._device = dev['id'][2:] break elif dev['type'] == 'IOS': self._device = dev['id'] break def _set_all_access(self): settings = self._webclient._make_call(webclient.GetSettings, '') self.all_access = True if 'isSubscription' in settings['settings'] and settings['settings']['isSubscription'] == True else False def _set_all_songs(self): if len(self.all_songs) == 0: try: self.all_songs = self._mobileclient.get_all_songs() except NotLoggedIn: if self.authenticate(): self.all_songs = self._mobileclient.get_all_songs() else: return [] else: return self.all_songs def authenticate(self, email, password): try: mcauthenticated = self._mobileclient.login(email, password) except AlreadyLoggedIn: mcauthenticated = True try: wcauthenticated = self._webclient.login(email, password) except AlreadyLoggedIn: wcauthenticated = True self.authenticated = mcauthenticated and wcauthenticated self._set_all_access() self._get_device_id() return self.authenticated def load_data(self): self._set_all_songs() for song in self.all_songs: thumb = None letter = song['title'][0] artist = song['artist'] album = song['album'] genre = song['genre'] if 'genre' in song else '(None)' if letter not in self.tracks_by_letter: self.tracks_by_letter[letter] = [] self.letters[letter] = None if artist not in self.tracks_by_artist: self.tracks_by_artist[artist] = [] self.artists[artist] = None if album not in self.tracks_by_album: self.tracks_by_album[album] = [] self.albums[album] = None if genre not in self.tracks_by_genre: self.tracks_by_genre[genre] = [] self.genres[genre] = None track = {'artist': artist, 'album': album} if 'title' in song: track['title'] = song['title'] if 'album' in song: track['album'] = song['album'] if 'artist' in song: track['artist'] = song['artist'] if 'durationMillis' in song: track['durationMillis'] = song['durationMillis'] if 'id' in song: track['id'] = song['id'] if 'trackNumber' in song: track['trackType'] = song['trackNumber'] if 'storeId' in song: track['storeId'] = song['storeId'] if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] thumb = song['albumArtRef'][0]['url'] self.letters[letter] = thumb self.artists[artist] = thumb self.albums[album] = thumb self.genres[genre] = thumb self.tracks_by_letter[letter].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_artist[artist].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_album[album].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.tracks_by_genre[genre].append({'track': track, 'thumb': thumb, 'id': song['id']}) self.library_loaded = True def get_tracks_for_type(self, type, name): type = type.lower() if type == 'artists': return self.tracks_by_artist[name] elif type == 'albums': return self.tracks_by_album[name] elif type == 'genres': return self.tracks_by_genre[name] elif type == 'songs by letter': return self.tracks_by_letter[name] else: return {} def get_song(self, id): return [x for x in self.all_songs if x['id'] == id][0] def get_all_playlists(self): if len(self._playlists) == 0: try: self._playlists = self._mobileclient.get_all_playlists() except NotLoggedIn: if self.authenticate(): self._playlists = self._mobileclient.get_all_playlists() else: return [] return self._playlists def get_all_user_playlist_contents(self, id): tracks = [] if len(self._playlist_contents) == 0: try: self._playlist_contents = self._mobileclient.get_all_user_playlist_contents() except NotLoggedIn: if self.authenticate(): self._playlist_contents = self._mobileclient.get_all_user_playlist_contents() else: return [] for playlist in self._playlist_contents: if id == playlist['id']: tracks = playlist['tracks'] break return tracks def get_shared_playlist_contents(self, token): playlist = [] try: playlist = self._mobileclient.get_shared_playlist_contents(token) except NotLoggedIn: if self.authenticate(): playlist = self._mobileclient.get_shared_playlist_contents(token) else: return [] return playlist def get_all_stations(self): if len(self._stations) == 0: try: self._stations = self._mobileclient.get_all_stations() except NotLoggedIn: if self.authenticate(): self._stations = self._mobileclient.get_all_stations() else: return [] return self._stations def get_station_tracks(self, id, num_tracks=200): tracks = [] try: tracks = self._mobileclient.get_station_tracks(id, num_tracks) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.get_station_tracks(id, num_tracks) else: return [] return tracks def get_genres(self): genres = [] try: genres = self._mobileclient.get_genres() except NotLoggedIn: if self.authenticate(): genres = self._mobileclient.get_genres() else: return [] return genres def create_station(self, name, id): station = None try: station = self._mobileclient.create_station(name=name, genre_id=id) except NotLoggedIn: if self.authenticate(): station = self._mobileclient.create_station(name=name, genre_id=id) else: return [] return station def search_all_access(self, query, max_results=50): results = None try: results = self._mobileclient.search_all_access(query, max_results) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.search_all_access(query, max_results) else: return [] return results def get_artist_info(self, id, include_albums=True, max_top_tracks=5, max_rel_artist=5): results = None try: results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) else: return [] return results def get_album_info(self, id, include_tracks=True): results = None try: results = self._mobileclient.get_album_info(id, include_tracks=include_tracks) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_album_info(id, include_tracks=include_tracks) else: return [] return results def add_aa_track(self, id): track = None try: track = self._mobileclient.add_aa_track(id) except NotLoggedIn: if self.authenticate(): track = self._mobileclient.add_aa_track(id) else: return None return track def add_songs_to_playlist(self, playlist_id, song_ids): tracks = None try: tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids) else: return None return tracks def get_stream_url(self, id): try: stream_url = self._mobileclient.get_stream_url(id, self._device) except NotLoggedIn: if self.authenticate(): stream_url = self._mobileclient.get_stream_url(id, self._device) else: return '' except CallFailure: raise CallFailure('Could not play song with id: ' + id, 'get_stream_url') return stream_url def reset_library(self): self.library_loaded = False self.all_songs[:] = [] self._playlists[:] = [] self._playlist_contents[:] = [] self._stations[:] = [] self.letters.clear() self.artists.clear() self.albums.clear() self.genres.clear() self.tracks_by_letter.clear() self.tracks_by_artist.clear() self.tracks_by_album.clear() self.tracks_by_genre.clear()
pickle.dump(self,file) file.close() def dictToSong(dict): return Song(dict) if(len(sys.argv) < 1): print "please enter the name of the playlist" exit() googlePlay = Mobileclient() logged_in = googlePlay.login('*****@*****.**', '1944_D-d@y',Mobileclient.FROM_MAC_ADDRESS) if(not logged_in): print "failed to login to google play" exit() print "Choose a playlist: " playlists = googlePlay.get_all_playlists() for playlist in playlists: print playlist["name"] list = Playlist() list.fetchFromGooglePlay(googlePlay) list.pushToSpotify() #for song in list.songs: #print song.name #list.fetchFromGooglePlay(googlePlay) list.outToFile() #list.pushToSpotify()
class GMusicDownloader(threading.Thread): api = None library = list() filtered_library = list() download_workers = list() max_threads = None config_error = False loggedin = False playlists = None fetchedlists = None # type: list filecreationlock = threading.Lock() trackqueue = Queue() threadqueue = Queue() player = None current_displayed_content_type = "Track" def __init__(self, queue: Queue): super().__init__() self.communicationqueue = queue self.api = Mobileclient() config = configparser.ConfigParser() config.read("config.ini") self.load_settings(config) if not self.config_error: self.threaded_api_query(self.login) for i in range(self.max_threads): self.threadqueue.put("I'm a thread") def login(self): if not self.loggedin: self.api.login(self.username, self.password, Mobileclient.FROM_MAC_ADDRESS) print("logged in") self.library = self.api.get_all_songs() print("songs fetched") self.communicationqueue.put({ "login": self.username, "library": self.library }) self.loggedin = True def get_directory_path(self, track: dict, and_create=False): artist = self.slugify(track["artist"]) album = self.slugify(track["album"]) artist_path = os.path.join(self.music_directory, artist) album_path = os.path.join(artist_path, album) if and_create: if not os.path.exists(artist_path): os.makedirs(artist_path) if not os.path.exists(album_path): os.makedirs(album_path) return album_path def get_file_path(self, track: dict, directory_path: str = None): if directory_path is not None: return os.path.join(directory_path, self.slugify(track["title"]) + self.file_type) else: return os.path.join(self.get_directory_path(track), self.slugify(track["title"]) + self.file_type) def threaded_stream_downloads(self, tracklist: list): for i in range(self.max_threads): threading.Thread(target=self.__downloadworker).start() for track in tracklist: self.trackqueue.put(track) # stop threads when they're done for i in range(self.max_threads): self.trackqueue.put(None) def __downloadworker(self): while True: permission = self.threadqueue.get(block=True) track = self.trackqueue.get() if track is None: self.threadqueue.put(permission) break self.communicationqueue.put({"downloading": track}) self.stream_download(track) self.threadqueue.put(permission) self.trackqueue.task_done() def stream_download(self, track: dict): track_title = track["title"] self.filecreationlock.acquire() directory_path = self.get_directory_path(track, and_create=True) self.filecreationlock.release() file_path = self.get_file_path(track, directory_path) if not os.path.exists(file_path): dl = 0 track_url = self.api.get_stream_url(track['id']) response = requests.get(track_url, stream=True) # total_length = int(response.headers.get('content-length')) with open(file_path, "wb") as songfile: for chunk in response.iter_content(chunk_size=self.chunk_size): songfile.write(chunk) dl += len(chunk) print(track_title, " done.") # next(filter(lambda t: t == track, self.filtered_library)) else: print(track_title + " already exists, skipping") self.add_tags(file_path, track) self.communicationqueue.put({"download complete": track}) def search_library(self, search_term: str): if search_term == "*": self.filtered_library = self.library else: self.filtered_library = list( filter(lambda t: search_term in t["artist"], self.library)) @staticmethod def threaded_api_query(worker: types.FunctionType, *args): threading.Thread(target=worker, args=(*args, )).start() def search_worker_thread(self, searchstring: str): search_results = self.api.search(self.slugify(searchstring)) def parse_song_hit(song_hit): # couldn't fit it into a lambda :'( track = song_hit["track"] track["id"] = track["storeId"] return track self.filtered_library = list( map(parse_song_hit, search_results["song_hits"])) self.communicationqueue.put({"search results": True}) @staticmethod def slugify(value): """ Normalizes string, removes non-alpha characters """ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('utf-8') value = re.sub('[^\w\s-]', '', value).strip() return value def check_filtered_tracks_for_download(self): for track in self.filtered_library: if "saved" not in track: if os.path.exists(self.get_file_path(track)): track["saved"] = "√" else: track["saved"] = "" def sort(self, sort: str, is_reversed: bool, content_type: str): if content_type == "Track": self.filtered_library = sorted(self.filtered_library, key=lambda k: k[sort], reverse=is_reversed) elif content_type == "Playlist": def lazy_hack_for_playlist_sort(playlist: dict): if sort == "title": return playlist["name"] if sort == "artist": return playlist["ownerName"] if sort == "album": return playlist['type'] self.playlists = sorted(self.playlists, key=lazy_hack_for_playlist_sort, reverse=is_reversed) def load_settings(self, config: configparser.ConfigParser): try: account = config["Account"] self.username = account["username"] self.password = account["password"] settings = config["Settings"] self.music_directory = settings["music_directory"] self.file_type = "." + settings["file_type"] self.chunk_size = settings.getint("chunk_size") self.max_threads = settings.getint("download_threads", 5) self.config_error = False except KeyError as e: self.communicationqueue.put({ "error": { "title": "Configuration Error GMusicDownloader", "body": "Could not find " + e.args[0] + " in preferences, please update prefs and try again" } }) self.config_error = True def add_tags(self, filepath: str, track: dict): try: tags = EasyID3(filepath) except ID3NoHeaderError: tags = mutagen.File(filepath, easy=True) tags.add_tags() tags["tracknumber"] = str( track["trackNumber"]).encode("utf-8").decode("utf-8") tags["title"] = track["title"] tags["artist"] = track["artist"] tags["album"] = track["album"] tags["discnumber"] = str( track["discNumber"]).encode("utf-8").decode("utf-8") tags["genre"] = track["genre"] tags["composer"] = track["composer"] tags["albumartist"] = track["albumArtist"] if "beatsPerMinute" in track and not track["beatsPerMinute"] == 0: tags["bpm"] = str( track["beatsPerMinute"]).encode("utf-8").decode("utf-8") # TODO store Year. will have to use standard ID3 instead of easy tags.save(v2_version=3) def open_playlists(self): if self.playlists is None: self.playlists = self.api.get_all_playlists() if not "lastAdded" in map(lambda p: p["id"], self.playlists): self.add_automatic_playlists() self.communicationqueue.put({"playlists loaded": self.playlists}) def add_automatic_playlists(self): last_added = { "id": 'lastAdded', "tracks": sorted(self.library, key=lambda t: t['creationTimestamp'], reverse=True), "name": "Last Added", "ownerName": "System", "type": "Automatic" } self.playlists.append(last_added) thumbs_up = { "id": 'thumbsup', "tracks": filter(lambda t: t['rating'] > 3, self.library), "name": "Thumbs Up", "ownerName": "System", "type": "Automatic" } self.playlists.append(thumbs_up) def fetch_all_playlists_and_return_one_with_iid(self, iid: str): # TODO make this work for non user owned playlists. should use get_shared_playlist_contents for those. if self.fetchedlists is None: self.fetchedlists = self.api.get_all_user_playlist_contents( ) # type: list # noinspection PyTypeChecker for playlist in self.fetchedlists: existing_playlist = next( filter(lambda p: p["id"] == playlist["id"], self.playlists)) existing_playlist["tracks"] = playlist["tracks"] # noinspection PyTypeChecker for playlist in self.playlists: if playlist["id"] == iid: playlist_tracks = playlist["tracks"] if playlist["type"] == "Automatic": self.filtered_library = playlist_tracks else: self.filtered_library = self.songs_from_playlist( playlist_tracks) self.communicationqueue.put({"search results": True}) return self.current_displayed_content_type = "Playlist" self.communicationqueue.put( {"error": { "title": "Could not fetch playlist", "body": ":(" }}) def songs_from_playlist(self, playlist): tracks = list() for id_dict in playlist: if "track" in id_dict: track = id_dict["track"] else: track = next( filter(lambda t: t["id"] == id_dict['trackId'], self.library), None) if "id" not in track: track["id"] = track["storeId"] tracks.append(track) return tracks def play_song(self, trackid): try: import vlc if isinstance(trackid, dict): print('playing track', trackid['title']) trackid = trackid["id"] if trackid is None and self.player is not None: self.player.pause() return url = self.api.get_stream_url(trackid) if self.player is None: self.player = vlc.MediaPlayer(url) # type: vlc.MediaPlayer else: # TODO: this is terrible and I should fix it. Lucky it works! self.player = vlc.MediaPlayer(url) self.player.play() self.player.event_manager().event_attach( vlc.EventType.MediaPlayerEndReached, self.song_complete, trackid) except ImportError: self.communicationqueue.put({ "error": { "title": "Could not import VLC", "body": "Please make sure you have 64-bit VLC installed" } }) @callbackmethod def song_complete(self, event, trackId): if self.current_displayed_content_type == "Track": libiterator = iter(self.filtered_library) for track in libiterator: if track["id"] == trackId: self.play_song(next(libiterator, None)) return
class GMusic(object): def __init__(self): self.authenticated = False self.all_access = False self.library_loaded = False self.all_songs = [] self.letters = {} self.artists = {} self.albums = {} self.genres = {} self.tracks_by_letter = {} self.tracks_by_artist = {} self.tracks_by_album = {} self.tracks_by_genre = {} self._device = None self._webclient = Webclient(debug_logging=False) self._mobileclient = Mobileclient(debug_logging=False) self._playlists = [] self._playlist_contents = [] self._stations = [] def _get_device_id(self): if self.authenticated: devices = self._webclient.get_registered_devices() for dev in devices: if dev['type'] == 'PHONE': self._device = dev['id'][2:] break elif dev['type'] == 'IOS': self._device = dev['id'] break def _set_all_access(self): settings = self._webclient._make_call(webclient.GetSettings, '') self.all_access = True if 'isSubscription' in settings[ 'settings'] and settings['settings'][ 'isSubscription'] == True else False def _set_all_songs(self): if len(self.all_songs) == 0: try: self.all_songs = self._mobileclient.get_all_songs() except NotLoggedIn: if self.authenticate(): self.all_songs = self._mobileclient.get_all_songs() else: return [] else: return self.all_songs def authenticate(self, email, password): try: mcauthenticated = self._mobileclient.login(email, password) except AlreadyLoggedIn: mcauthenticated = True try: wcauthenticated = self._webclient.login(email, password) except AlreadyLoggedIn: wcauthenticated = True self.authenticated = mcauthenticated and wcauthenticated self._set_all_access() self._get_device_id() return self.authenticated def load_data(self): self._set_all_songs() for song in self.all_songs: thumb = None letter = song['title'][0] artist = song['artist'] album = song['album'] genre = song['genre'] if 'genre' in song else '(None)' if letter not in self.tracks_by_letter: self.tracks_by_letter[letter] = [] self.letters[letter] = None if artist not in self.tracks_by_artist: self.tracks_by_artist[artist] = [] self.artists[artist] = None if album not in self.tracks_by_album: self.tracks_by_album[album] = [] self.albums[album] = None if genre not in self.tracks_by_genre: self.tracks_by_genre[genre] = [] self.genres[genre] = None track = {'artist': artist, 'album': album} if 'title' in song: track['title'] = song['title'] if 'album' in song: track['album'] = song['album'] if 'artist' in song: track['artist'] = song['artist'] if 'durationMillis' in song: track['durationMillis'] = song['durationMillis'] if 'id' in song: track['id'] = song['id'] if 'trackNumber' in song: track['trackType'] = song['trackNumber'] if 'storeId' in song: track['storeId'] = song['storeId'] if 'albumArtRef' in song: track['albumArtRef'] = song['albumArtRef'] thumb = song['albumArtRef'][0]['url'] self.letters[letter] = thumb self.artists[artist] = thumb self.albums[album] = thumb self.genres[genre] = thumb self.tracks_by_letter[letter].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.tracks_by_artist[artist].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.tracks_by_album[album].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.tracks_by_genre[genre].append({ 'track': track, 'thumb': thumb, 'id': song['id'] }) self.library_loaded = True def get_tracks_for_type(self, type, name): type = type.lower() if type == 'artists': return self.tracks_by_artist[name] elif type == 'albums': return self.tracks_by_album[name] elif type == 'genres': return self.tracks_by_genre[name] elif type == 'songs by letter': return self.tracks_by_letter[name] else: return {} def get_song(self, id): return [x for x in self.all_songs if x['id'] == id][0] def get_all_playlists(self): if len(self._playlists) == 0: try: self._playlists = self._mobileclient.get_all_playlists() except NotLoggedIn: if self.authenticate(): self._playlists = self._mobileclient.get_all_playlists() else: return [] return self._playlists def get_all_user_playlist_contents(self, id): tracks = [] if len(self._playlist_contents) == 0: try: self._playlist_contents = self._mobileclient.get_all_user_playlist_contents( ) except NotLoggedIn: if self.authenticate(): self._playlist_contents = self._mobileclient.get_all_user_playlist_contents( ) else: return [] for playlist in self._playlist_contents: if id == playlist['id']: tracks = playlist['tracks'] break return tracks def get_shared_playlist_contents(self, token): playlist = [] try: playlist = self._mobileclient.get_shared_playlist_contents(token) except NotLoggedIn: if self.authenticate(): playlist = self._mobileclient.get_shared_playlist_contents( token) else: return [] return playlist def get_all_stations(self): if len(self._stations) == 0: try: self._stations = self._mobileclient.get_all_stations() except NotLoggedIn: if self.authenticate(): self._stations = self._mobileclient.get_all_stations() else: return [] return self._stations def get_station_tracks(self, id, num_tracks=200): tracks = [] try: tracks = self._mobileclient.get_station_tracks(id, num_tracks) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.get_station_tracks(id, num_tracks) else: return [] return tracks def get_genres(self): genres = [] try: genres = self._mobileclient.get_genres() except NotLoggedIn: if self.authenticate(): genres = self._mobileclient.get_genres() else: return [] return genres def create_station(self, name, id): station = None try: station = self._mobileclient.create_station(name=name, genre_id=id) except NotLoggedIn: if self.authenticate(): station = self._mobileclient.create_station(name=name, genre_id=id) else: return [] return station def search_all_access(self, query, max_results=50): results = None try: results = self._mobileclient.search_all_access(query, max_results) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.search_all_access( query, max_results) else: return [] return results def get_artist_info(self, id, include_albums=True, max_top_tracks=5, max_rel_artist=5): results = None try: results = self._mobileclient.get_artist_info( id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_artist_info( id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist) else: return [] return results def get_album_info(self, id, include_tracks=True): results = None try: results = self._mobileclient.get_album_info( id, include_tracks=include_tracks) except NotLoggedIn: if self.authenticate(): results = self._mobileclient.get_album_info( id, include_tracks=include_tracks) else: return [] return results def add_aa_track(self, id): track = None try: track = self._mobileclient.add_aa_track(id) except NotLoggedIn: if self.authenticate(): track = self._mobileclient.add_aa_track(id) else: return None return track def add_songs_to_playlist(self, playlist_id, song_ids): tracks = None try: tracks = self._mobileclient.add_songs_to_playlist( playlist_id, song_ids) except NotLoggedIn: if self.authenticate(): tracks = self._mobileclient.add_songs_to_playlist( playlist_id, song_ids) else: return None return tracks def get_stream_url(self, id): try: stream_url = self._mobileclient.get_stream_url(id, self._device) except NotLoggedIn: if self.authenticate(): stream_url = self._mobileclient.get_stream_url( id, self._device) else: return '' except CallFailure: raise CallFailure('Could not play song with id: ' + id, 'get_stream_url') return stream_url
#Add to the path dictionary playlistFilePaths.append((os.path.join(currDir, iFile)).replace('\\','/')) #Get the file name #path = tkFileDialog.askopenfilename() #Create the mobile client #No debug logging, validate, and verify SSL api = Mobileclient(False,True,True) #Login #api.perform_oauth('C:\\Users\\Joshv\\Josh\\Programming\\Python\\googleMusic\\gMusicDeviceID.txt') api.oauth_login('b83fce595c6fd82bcfcfdd8e2e542d79f3c176bb0974c4bb34da2df10d95cec1') #Retrieve all playlists playlists = api.get_all_playlists(False, False) #Retrive the contents of all playlists playlistContents = api.get_all_user_playlist_contents() #Get the entire song library library = api.get_all_songs(False, False) #Loop through all of the local playlists for playlistFilePath in playlistFilePaths: #Create the array for the paths artists, tracks in the local playlist paths = [] lArtists = [] lTracks = [] #Create arrays for the artists and tracks in the gmusic playlist
JOIN `gpm_albums` USING (`gpm_albumid`) WHERE `gpm_playlists`.`name` LIKE 'Background %' AND `gpm_playlists`.`deleted` = 0 AND `gpm_playlist_entries`.`deleted` = 0 ORDER BY RAND() LIMIT 950) AS `t` ORDER BY `album_artist` ASC, `year` ASC, `album` ASC, `title` ASC """ cursor.execute(sql) trackRows = cursor.fetchall() playlistName = "BG Rand " + datetime.now().strftime("%Y-%m %B") # Check if the playlist exists # If it does, abort unless --replace is specified, in which case delete the old list playlists = gpm.get_all_playlists() for playlist in playlists: if playlist["deleted"]: continue if playlist["name"] == playlistName: if args.replace: logger.info("Deleting existing playlist") gpm.delete_playlist(playlist["id"]) else: logger.error("Playlist already exists: " + playlistName) exit(1) logger.info("Creating playlist: " + playlistName) playlistId = gpm.create_playlist(playlistName, description="Autogenerated by RPG", public=False) logger.info("Adding " + str(len(trackRows)) + " tracks to playlist")
from datetime import date import fileinput if len(sys.argv) > 1: pw = "" for line in fileinput.input(): pw = line api = Mobileclient() logged_in = api.login('*****@*****.**', pw, Mobileclient.FROM_MAC_ADDRESS) if logged_in: playlists = api.get_all_playlists() names = [] for playlist in playlists: names.append(playlist['name']) for name in sorted(names): print(name) api.logout() else: print( "Please include the path to a file containing a Google Music password." )
def transfer_gpm_to_ytm(): authenticated = do_login() print(f'Authentication status: {authenticated}') if not authenticated: return client.get_all_songs() client.get_all_playlists() if __name__ == '__main__': print('Logging in...') if do_login(): tracks = client.get_all_songs() ids = [ '648b82b7-015f-3823-bf21-3d31d7dc822d', '02a9b776-8606-37bd-8a92-c723e558c8d5' ] pEntry = None for track in tracks: if track['id'] in ids or track.get( 'storeId', None) in ids or track.get('nid', None) in ids: pEntry = track if pEntry is not None: print('Playlist Entry') print(dumps(pEntry, indent=4)) print(dumps(tracks[0], indent=4)) print(dumps(client.get_all_playlists()[0], indent=4)) print(dumps(client.get_all_user_playlist_contents()[0], indent=4))
def reverseplaylist(playlist_name='', repeat=False, quick=False): mc = Mobileclient() mc.__init__() # Find location of this script dir_path = os.path.dirname(os.path.realpath(__file__)) # Check whether this is the first time the program has been run by searching the directory for the log file file_list = os.listdir(dir_path) # No log file means not run if '.gmusiclog.txt' not in file_list: print( '\n' + 'This is the first time this program has been run in this folder.' + '\n' + 'performing initial authentication.' + '\n') # Autorepeat cannot be true without logfile, override if true repeat = False # Start with a bogus device ID to determine real id from error message devID = 'ffffffff' mc.perform_oauth() try: mc.oauth_login(devID) except Exception as e: error = str(e) IDpos = error.find('*') nextIDpos = error.find('\n', IDpos + 1, len(error)) devID = error[IDpos + 2:nextIDpos] # Perform login mc.oauth_login(devID) # Write authentication stuff to logfile with open(dir_path + '/.gmusiclog.txt', 'w') as log: log.write(devID + '\n') x = datetime.datetime.now() timeString = (str(x.day) + '/' + str(x.month) + '/' + str(x.year) + ' ' + str(x.hour) + ':' + str(x.minute) + ':' + str(x.second) + '.' + str(x.microsecond)) log.write('Initial authentication performed at ' + timeString + '\n') # Log file exists, we will check whether the user has requested autorepeat else: print( '\n' + 'This is not the first time this program has been run in this folder' + '\n' + 'performing login.' + '\n') # Open the logfile to read device id and previous playlists with open(dir_path + '/.gmusiclog.txt', 'r') as log: # Get device ID devID = log.readline().strip() # Look for the playlist name from the bottom of the list contents = log.read() # Perform login mc.oauth_login(devID) playlistLocation = contents.rfind('PLAYLIST') if playlistLocation != -1: # Define end of playlist to make defining desired playlist a little cleaner endOfPlaylist = contents.find('\n', playlistLocation, len(contents)) desired_playlist = contents[playlistLocation + 10:endOfPlaylist] with open(dir_path + '/.gmusiclog.txt', 'a+') as log: # Write authentication stuff to logfile x = datetime.datetime.now() timeString = (str(x.day) + '/' + str(x.month) + '/' + str(x.year) + ' ' + str(x.hour) + ':' + str(x.minute) + ':' + str(x.second) + '.' + str(x.microsecond)) log.write('Login performed at ' + timeString + '\n') # Get user input for desired playlist if autorepeat is not enabled if repeat == False and playlist_name == '': desired_playlist = input( 'Name of the playlist being reversed (case sensetive): ') elif playlist_name != '': # If a name is given this overrides all else desired_playlist = playlist_name else: print('Autorepeat enabled, reversing playlist: ' + desired_playlist + '\n') # Establish the name of the reversed playlist reversed_playlist = desired_playlist + 'REVERSED' # Check to see whether the desired and reversed playlists exist yet allPlaylists = mc.get_all_playlists() desired_playlist_index = -1 reversed_playlist_index = -1 for n in allPlaylists: if n['name'] == reversed_playlist: reversed_playlist_index = n reversedID = n['id'] elif n['name'] == desired_playlist: desired_playlist_index = n desiredID = n['id'] # Desired playlist exists, so we check to see if it has also been reversed if desired_playlist_index != -1: # We cache the playlist name so that it can be automatically re-reversed next time with open(dir_path + '/.gmusiclog.txt', 'a+') as log: log.write('PLAYLIST: ' + desired_playlist + '\n') # Desired playlist has been reversed, we can either delete the old one before proceeding # or perform a quick update if reversed_playlist_index != -1: print('The ' + desired_playlist + ' playlist has been reversed.') # Determine whether to do a quick update or not if quick == True: print('performing quick update') quick_update(mc, desiredID, reversedID) return else: print('Performing full update\n' + 'Deleting the old playlist...\n') mc.delete_playlist(reversedID) # Desired playlist has not been reversed, create the reverse else: print('The ' + desired_playlist + ' playlist has not been reversed, creating the reverse now.') # If we have got this far, the reversed playlist doesn't exist print('Generating reversed song list...') reversedID, id_list = create_new(mc, desired_playlist) print('Adding songs to the playlist...') mc.add_songs_to_playlist(reversedID, id_list) print('Done!') # No such playlist exists else: print( 'No playlist by the name of ' + desired_playlist + ' found. Did you spell it correctly? A reminder here that the playlist name is case sensetive.' )
def auth(): global wc, mc if os.path.isfile("email.txt"): f = open("email.txt", "r") email = f.read() else: f = open("email.txt", "w") email = emailBox() email.connect("delete-event", Gtk.main_quit) email.show_all() Gtk.main() f.write(email.email) f.close() email = email.email # Runs the dialog to take the user password pwdb = passwordBox() pwdb.connect("delete-event", Gtk.main_quit) pwdb.show_all() Gtk.main() wc.login(email, pwdb.pwd) mc.login(email, pwdb.pwd, get_mac()) mc = Mobileclient(debug_logging=True, validate=True, verify_ssl=True) wc = Webclient(debug_logging=True, validate=True, verify_ssl=True) auth() playlist_list = mc.get_all_playlists(incremental=False, include_deleted=False) print([x.get("name") for x in playlist_list])
class MusicPlayer: def __init__(self, device_id, token): # Gmusic API initialization print("initializing client") self.api = Mobileclient() print("logging in") self.api.oauth_login(device_id, token) print("loading all songs") self.all_songs = self.api.get_all_songs() print("loading playlists") self.all_playlists = self.api.get_all_user_playlist_contents() self.all_playlist_names = {} for playlist in self.all_playlists: self.all_playlist_names[playlist["name"]] = playlist["id"] # VLC initialization self.track_file = vlc.MediaPlayer() self.track_list = [] self.titles = [] self.track_number = 0 self.playlists = [] self.current_time = -1 self.max_time = -1 # Get playlists, songs from the first playlist, and load the first song self.get_playlists() self.get_songs_from_playlist(self.playlists[0]) self.song = self.track_list[self.track_number]["trackId"] self.load_track() # GUI initialization print("creating window") self.song = "" self.player_layout = [ [sg.Text("I love you Mom!", size=(15, 1), font=("Helvetica", 25))], [sg.Listbox(values=self.playlists, size=(30, 20), bind_return_key=True, key="_playlists_"), # sg.Image(), sg.Listbox(values=self.titles, size=(30, 20), bind_return_key=True, key="_Tracks_")], [sg.Text("Click Play or select song", key="_SongName_", enable_events=True)], [sg.Text("Volume:"), sg.Slider(range=(0, 100), orientation="h", size=(20, 15), default_value=self.track_file.audio_get_volume(), key="_volume_"), sg.Button("Play"), sg.Button("Pause"), sg.Button("Next")] ] self.title = "Music Player" self.window = sg.Window(self.title).Layout(self.player_layout) def get_playlists(self): data = self.api.get_all_playlists() self.playlists = [] for playlist in data: if not playlist['deleted']: self.playlists.append(playlist['name']) print(self.playlists) def change_playlists(self, name): for pos, title in enumerate(self.playlists): if title == name: self.get_songs_from_playlist(self.playlists[pos]) def get_songs_from_playlist(self, name): print("Obtaining track list") tracks = [] if name in self.all_playlist_names: for playlist in self.all_playlists: if playlist["name"] == name: for track in playlist["tracks"]: tracks.append(track) break self.track_list = tracks self.get_playlist_song_titles() def get_playlist_song_titles(self): print("Getting playlist song titles") titles = [] for song in self.track_list: if song["source"] == "2": titles.append(song["track"]["title"]) else: for track in self.all_songs: if track["id"] == song["trackId"]: print("match found") titles.append(track["title"]) else: print("No match found") print(titles) self.titles = titles def get_song_position_from_title(self, title): for pos, name in enumerate(self.titles): if name == title: return pos else: print("Couldn't find song in tracks") def download_song(self): print("downloading song") url = self.api.get_stream_url(self.song) doc = requests.get(url) with open("song.mp3", "wb") as f: f.write(doc.content) def load_track(self): self.track_file = vlc.MediaPlayer("song.mp3") print("Time:", self.track_file.get_length()) def play(self): self.track_file.play() self.window.FindElement("_SongName_").Update(value=self.titles[self.track_number]) def stop(self): self.track_file.stop() def pause(self): self.track_file.pause() def next(self): self.track_number += 1 self.song = self.track_list[self.track_number]["trackId"] self.window.FindElement("_Tracks_").SetValue(self.titles[self.track_number]) self.download_song() self.track_file.stop() self.load_track() self.track_file.play() self.max_time = self.track_file.get_time() def run(self): print("launching program") while True: self.current_time = self.track_file.get_time() if self.max_time == -1: self.max_time = self.track_file.get_length() elif self.max_time == 0: self.max_time = -1 else: current = timedelta(milliseconds=self.current_time) max = timedelta(milliseconds=self.max_time) # print("Current", current, "Max", max) # print(int((self.current_time / self.max_time) * 100)) if (self.current_time + 500) > self.max_time: self.next() event, values = self.window.Read(timeout=100) if event is not None: if event == "Play": self.play() elif event == "Stop": self.stop() elif event == "Pause": self.pause() elif event == "Next": self.next() elif event == "_Tracks_": self.track_number = self.get_song_position_from_title(values[event][0]) self.song = self.track_list[self.track_number]["trackId"] self.download_song() self.track_file.stop() self.load_track() self.play() elif event == "_playlists_": print(values[event][0]) self.change_playlists(values[event][0]) self.window.FindElement("_Tracks_").Update(self.titles) elif event == "_volume_": print("Volume", event, values) else: self.track_file.audio_set_volume(values["_volume_"]) if event == "Quit" or values is None: break
print( "Could not connect to Play Music, authentication failure. Exiting...") exit(1) #Ensure the device/account actually has a google play subscription and is able to play/download/etc content if mc.is_subscribed: print("This account DOES have a Play Music subscription!") else: print("This account deos NOT have a Play Music subscription! Exiting...") exit(1) #Get a list of the songs already in the account's library so we can compare songs we're looking for to it and ensure we don't add multiples of songs owned_songs = mc.get_all_songs() #Get a list of all playlists the user has so we can be sure not to try a name for ours that's already taken users_playlists = mc.get_all_playlists(incremental=False) #Create a name for the playlist, utilizing a root name so that we can add numbers to the end appropriately in the event that a playlist with our chosen name already exists playlist_root_name = "Nostalgia Playlist " + str(billboard_date.year) playlist_name = playlist_root_name #We're going to want to check to make sure that this name isn't already used for a playlist users_playlists_names = [] for user_playlist in users_playlists: users_playlists_names.append(user_playlist["name"]) #Start this number at 2 since that's most likely what a human would do (playlist, playlist2, playlist3, etc) appendable_num = 2 while playlist_name in users_playlists_names: playlist_name = playlist_root_name + " " + str(appendable_num) appendable_num = appendable_num + 1
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_user_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)
class TheGoogs: DEFAULT_CREDS_DIR = "~/gmusic/.oauth" DEFAULT_MOBILE_DEVICE_ID = "342e914abacc484d" # Galaxy Tab DEFAULT_MANAGER_MAC_ADDRESS = "A2:C2:E2:CC:C7:37" # Made-up def __init__(self, creds_dir=DEFAULT_CREDS_DIR): self.creds_dir = os.path.expanduser(creds_dir) logger.info("Creating TheGoogs from creds at {}".format( self.creds_dir)) self.mobile_creds = os.path.join(self.creds_dir, "mobile.creds") self.manager_creds = os.path.join(self.creds_dir, "manager.creds") self.mobile = Mobileclient() self.manager = Musicmanager() logger.debug("Logging in") self.mobile.oauth_login(device_id=self.DEFAULT_MOBILE_DEVICE_ID, oauth_credentials=self.mobile_creds) self.manager.login(uploader_id=self.DEFAULT_MANAGER_MAC_ADDRESS, oauth_credentials=self.manager_creds) def get_libdata(self): logger.info("Fetching libdata ...") logger.info("... fetching registered devices") registered_devices = self.mobile.get_registered_devices() logger.info("... fetching all songs") library = self.mobile.get_all_songs() logger.info("... fetching playlist metadata") playlists = self.mobile.get_all_playlists() logger.info("... fetching playlist contents") playlist_contents = self.mobile.get_all_user_playlist_contents() logger.info("... fetching uploaded songs") uploaded_songs = self.manager.get_uploaded_songs() logger.info("... fetching purchased songs") purchased_songs = self.manager.get_purchased_songs() return Libdata(timestamp=datetime.utcnow(), registered_devices=registered_devices, all_songs=library, playlist_metadata=playlists, playlists=playlist_contents, uploaded_songs=uploaded_songs, purchased_songs=purchased_songs) def get_streamed_song(self, id): logger.info("Downloading streamed song id {}".format(id)) stream_url = self.mobile.get_stream_url(id) response = urllib.request.urlopen(stream_url) return response.read() def get_uploaded_song(self, id): logger.info("Downloading uploaded song id {}".format(id)) suggested_filename, data = self.manager.download_song(id) return data
def main(): log.setLevel(logging.INFO) logging.getLogger('gmusicapi').setLevel(logging.INFO) cred_path = os.path.join(os.path.expanduser('~'), '.gmusic-sync') if not os.path.isfile(cred_path): raise NoCredentialException( 'No username/password was specified. No config file could ' 'be found either. Try creating %s and specifying your ' 'username/password there. Make sure to chmod 600.' % cred_path) if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'): raise NoCredentialException( 'Config file is not protected. Please run: ' 'chmod 600 %s' % cred_path) config = ConfigParser.ConfigParser() config.read(cred_path) src_user = config.get('src','username') src_pass = config.get('src','password') src_device = config.get('src','deviceid') dst_user = config.get('dst','username') dst_pass = config.get('dst','password') dst_device = config.get('dst','deviceid') if not src_user or not src_pass or not dst_user or not dst_pass: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not src_device or not dst_device: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) parser = argparse.ArgumentParser(description='gmusic-sync', add_help=False) parser.add_argument('-hs', '--strict-heuristics', help='Songs must match artist, album, and title to be considered a match.', action='store_true', dest='strict_heuristics') parser.add_argument('-l', '--list', help='List playlists on the src account', action='store_true', dest='lst') parser.add_argument('-p', '--playlist', help='Playlist ID from src account to transfer', dest='playlist') args = parser.parse_args() api = Mobileclient() api.login(src_user, src_pass, src_device) playlists = api.get_all_playlists() if args.lst: for playlist in playlists: print playlist['name'] + ' (' + playlist['id'] + ') ' exit() library = api.get_all_songs() api2 = Mobileclient() api2.login(dst_user, dst_pass, dst_device) library2 = api2.get_all_songs() if args.playlist is None: print 'Error: no playlist selected' all_playlist_entries = api.get_all_user_playlist_contents() selected_playlist_entries = [] dst_playlist_id = None for entry in all_playlist_entries: if entry['id'] == args.playlist: selected_playlist_entries = entry['tracks'] dst_playlist_id = api2.create_playlist(entry['name']) if dst_playlist_id is None: print 'Error creating new playlist' exit() playlist_tracks = [] for ptrack in selected_playlist_entries: track_found = False for track in library: if ptrack['trackId'] == track['id']: playlist_tracks.append(track) track_found = True break try: if ptrack['trackId'] == track['storeId']: playlist_tracks.append(track) track_found = True break except: pass if not track_found: print 'ERROR: could not find playlist entry ' + str(ptrack) api.add_aa_track(ptrack['trackId']) if len(playlist_tracks) != len(selected_playlist_entries): print 'Error: could not locate all playlist tracks in src library' exit() failed_tracks = [] for track in playlist_tracks: try: if track['storeId'].startswith('T'): #It's a store track: does it exist in the target store? #Perform a store lookup: this will raise an exception if the track #Is not in the target store store_track = api2.get_track_info(track['storeId']) #If we got here, we're good to go for adding the track to the playlist retval = api2.add_songs_to_playlist(dst_playlist_id, track['storeId']) if track['storeId'] not in retval: print 'Error adding ' + track['title'] + ' - ' + track['artist'] + ' (' + track['album'] + ')' else: dst_track = heuristic_search(library2, track, args.strict_heuristics) if dst_track is not None: api2.add_songs_to_playlist(dst_playlist_id, dst_track['id']) else: failed_tracks.append(track) except: #Not a store track: do heuristics lookup dst_track = heuristic_search(library2, track, args.strict_heuristics) if dst_track is not None: api2.add_songs_to_playlist(dst_playlist_id, dst_track['id']) else: failed_tracks.append(track) continue print '----------------- FAILED TRACKS --------------------' for track in failed_tracks: print track['title'] + ' - ' + track['artist'] + ' (' + track['album'] + ')'
class Gmusic(object): """Class to handle Google Music-related functionality""" def __init__(self, bot): """ init """ self.bot = bot self.mob = Mobileclient() def login(self, username, password, android_id=Mobileclient.FROM_MAC_ADDRESS): """ login method """ self.mob.login(username, password, android_id) return self.mob.is_authenticated() def search(self, searchterms): """ search for stuff """ hits = self.mob.search("{0}".format(searchterms)) return hits def create_playlist(self, name, song_ids, public=True): """ create new playlist named 'name', containing songs with 'song_id' """ playlist_id = self.mob.create_playlist(name, description="Bot Playlist", public=public) self.mob.add_songs_to_playlist(playlist_id, song_ids) return playlist_id def _make_playlist_share_link(self, share_token): base_share_url = "https://play.google.com/music/playlist" return "{}/{}".format(base_share_url, share_token) def share_playlist(self, playlist_id): try: [share_token] = [ plist['shareToken'] for plist in self.mob.get_all_playlists() if plist['id'] == playlist_id ] return self._make_playlist_share_link(share_token) except ValueError: return "Cannot find playlist" def get_best_song_match(self, artist, title): hits = self.search("{0} {1}".format(artist, title)) tracks = self.filter_to_song_minimum_info(self.get_songs(hits)) similarities = [(similarity(track['artist'], artist, track['title'], title), track) for track in tracks] sorted_tracks = sorted(similarities, key=lambda k: k[0]) best_track = None if len(sorted_tracks) > 0: best_track = sorted_tracks[0][1] return best_track def get_best_album_match(self, artist, album): hits = self.search("{0} {1}".format(artist, album)) albums = self.get_albums(hits) similarities = [(similarity(a['artist'], artist, a['album'], album), a) for a in albums] sorted_albums = sorted(similarities, key=lambda k: k[0]) if len(sorted_albums) == 0: return [] best_album = sorted_albums[0][1] album_info = self.mob.get_album_info(best_album['albumId']) store_ids = [t['storeId'] for t in album_info['tracks']] print("Store ids in best_album_match: {0}".format(store_ids)) return store_ids def format_best_match(self, artist, title): track = self.get_best_song_match(artist, title) share_base_url = 'https://play.google.com/music/m/' return "{0} {1} {2} - {3}{4}".format(track['artist'], track['album'], track['title'], share_base_url, track['storeId']) def get_albums(self, results): albums = [album.get('album', None) for album in results['album_hits']] album_details = [{ 'artist': a['artist'], 'album': a['name'], 'albumId': a['albumId'] } for a in albums] return album_details def get_songs(self, results): return [song.get('track', None) for song in results['song_hits']] def filter_to_song_minimum_info(self, results): return [{ 'artist': song.get('artist', None), 'album': song.get('album', None), 'title': song.get('title', None), 'storeId': song.get('storeId', None) } for song in results] def convert_spotify_embed_to_gmusic(self, url): s_list = SpotifyPlaylist(url) title = s_list.title best_matches = [ self.get_best_song_match(i.artist, i.track) for i in s_list.items ] filtered_matches = [i for i in best_matches if i is not None] store_ids = [i.get('storeId') for i in filtered_matches] new_plist = self.create_playlist(title, store_ids) return self.share_playlist(new_plist) def convert_hbih_to_gmusic(self, url): hbih_list = HBIHPlaylist(url) title = hbih_list.title store_ids = [] for item in hbih_list.items: album_store_ids = self.get_best_album_match(item[0], item[1]) print("Adding store ids: {0}".format(album_store_ids)) store_ids.extend(album_store_ids) store_id_set = IndexedSet(store_ids) no_dupes_store_ids = list(store_id_set) new_plist = self.create_playlist(title, no_dupes_store_ids[0:1000]) return self.share_playlist(new_plist) def create_playlist_from_song_names(self, artist, songs): year = datetime.datetime.now().year title = "{} setlist ({})".format(artist, year) best_matches = [self.get_best_song_match(artist, s) for s in songs] filtered_matches = [i for i in best_matches if i is not None] store_ids = [i.get('storeId') for i in filtered_matches] new_plist = self.create_playlist(title, store_ids) return self.share_playlist(new_plist) def get_newest_playlists(self, count=5): """ return 'count' newest playlists """ all_plists = self.mob.get_all_playlists() sorted_plists = sorted(all_plists, key=lambda k: k['lastModifiedTimestamp'], reverse=True) if count > 0: newest_plists = sorted_plists[:count] else: newest_plists = sorted_plists info = [{ 'name': p['name'], 'share': self._make_playlist_share_link(p['shareToken']) } for p in newest_plists] return info def get_all_playlists(self): """ return all playlists """ return self.get_newest_playlists(0) # 0 = return everything def find_playlists(self, searchterm): """ find all playlists that have a name containing 'searchterm' """ all_plists = self.get_all_playlists() all_matches = all_plists all_terms = searchterm.split(' ') for term in all_terms: all_matches = [ p for p in all_matches if p['name'].lower().find(term.lower()) != -1 ] return all_matches
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)