class GoogleMusicHelper(object): def __init__(self, email=None, password=None): self.google_music_client = Mobileclient() if email and password: self.login(email, password) def login(self, email, password): if self.google_music_client.login(email, password, Mobileclient.FROM_MAC_ADDRESS): return "Logged in to Google" return "Error logging in" def add_song_by_name_to_google_library(self, song="", artist=""): results = self.google_music_client.search(query=song + " " + artist, max_results=1) if results: track = results["song_hits"][0]["track"] return self.google_music_client.add_store_tracks( track.get("storeId") or track.get("nid")) def list_playlists(self): return self.google_music_client.get_all_user_playlist_contents() def sync_playlists_with_library(self, password=None, username=None): if self.google_music_client.login(username, password, Mobileclient.FROM_MAC_ADDRESS): all_tracks = [] for playlist in self.google_music_client.get_all_user_playlist_contents( ): for track in playlist["tracks"]: all_tracks.append(track["track"]) playlist_store_ids = [track["storeId"] for track in all_tracks] all_songs = self.google_music_client.get_all_songs( incremental=False) print all_songs[0] added_store_ids = [] for song in all_songs: store_id = None if song.get("nid"): store_id = song["nid"] elif song.get("storeId"): store_id = song["storeId"] added_store_ids.append(store_id) new_store_ids = set(playlist_store_ids) - set(added_store_ids) new_tracks = [ track for track in all_tracks if track["storeId"] not in added_store_ids ] for storeId in new_store_ids: for track in new_tracks: if track["storeId"] == storeId: break print track['title'] + " by " + track["artist"] print self.google_music_client.add_store_tracks(storeId)
def get_gmusic_playlists(username, password): api = Mobileclient() print(username + ":" + password) logged_in = api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) if not logged_in: print("Login failed.") if api.is_authenticated(): playlists = api.get_all_user_playlist_contents() output_dict = {} for playlist in playlists: name = playlist["name"] tracks = playlist["tracks"] for track in tracks: track = track["track"] artist = track["artist"] title = track["title"] if name in output_dict: output_dict[name].append((artist, title)) else: output_dict[name] = [(artist, title)] return output_dict return None
def gm_get_current_pl_member(client: Mobileclient, playlist: str) -> (str, list): playlist = list(filter( lambda x: x['deleted'] == False and x['name'] == playlist, client.get_all_user_playlist_contents() ))[0] member_track_ids = set([item['trackId'] for item in playlist['tracks']]) return playlist['id'], [item for item in client.get_all_songs() if item['id'] in member_track_ids]
class GMusicClient: def __init__(self): """ This connects to the google music server by requesting credentials. """ self.api = Mobileclient() # username = input('Type your Google Play Music email below.\n--> ') self.username = os.getenv('GOOGLE_USERNAME') dir_path = os.path.dirname(os.path.realpath(__file__)) + '/.cache-gmusic-' + ''.join(filter(str.isalpha, self.username)) # Check if already authenticated if(not os.path.isfile(dir_path)): self.api.perform_oauth(open_browser=True, storage_filepath=dir_path) # Attempt to log in; if fail, get new token. try: self.api.oauth_login(Mobileclient.FROM_MAC_ADDRESS, oauth_credentials=dir_path) except: self.api.perform_oauth(open_browser=True, storage_filepath=dir_path) self.api.oauth_login(Mobileclient.FROM_MAC_ADDRESS, oauth_credentials=dir_path) print('Connected to GMusic') def get_playlists(self): """ Gets all the playlists in Google Play Music. Some may not actually have any music, but they will be processed anyways. """ playlists_cache_path = os.path.dirname(os.path.realpath(__file__)) + '/.cache-playlists_cache-' + ''.join(filter(str.isalpha, self.username)) if (os.path.isfile(playlists_cache_path)): with open(playlists_cache_path, 'rb') as playlists_cache_file: playlists = pickle.load(playlists_cache_file) else: print('Requesting Google playlists') playlistsG = self.api.get_all_user_playlist_contents() print('Received Google playlists, we have', len(playlistsG), 'playlists') playlists = Playlists(playlistsG) with open(playlists_cache_path, 'wb') as playlists_cache_file: pickle.dump(playlists, playlists_cache_file) return playlists def get_all_songs(self): """ Gets the entire Google library for adding to the """ lib_cache_path = os.path.dirname(os.path.realpath(__file__)) + '/.cache-lib_cache-' + ''.join(filter(str.isalpha, self.username)) if (os.path.isfile(lib_cache_path)): with open(lib_cache_path, 'rb') as lib_cache_file: library = pickle.load(lib_cache_file) else: print('Requesting Google library') librarySongs = self.api.get_all_songs() print('Received Google library, we have', len(librarySongs), 'songs') library = MusicLibrary(librarySongs) with open(lib_cache_path, 'wb') as lib_cache_file: pickle.dump(library, lib_cache_file) return library
class Plugin: name = 'gmusic' def __init__(self, username, password): self.client = Mobileclient() self.client.login(username, password, Mobileclient.FROM_MAC_ADDRESS) # self.webclient = Webclient() # self.webclient.login(username, password) def get_tracks(self, artist=None, album=None): """ Fetches tracks from api. If no filter is defined, it will get user tracks """ return TrackList(self.client.get_all_songs()) def get_playlists(self): """ Get playlists and radios """ playlists = [] for playlist in self.client.get_all_user_playlist_contents(): tracks = TrackList([ self.client.get_track_info(x['trackId']) for x in playlist['tracks'] ]) playlists.append(PlayList(playlist['name'], tracks)) return playlists def stream(self, track): def _stream(url): inp = requests.get(url, stream=True) chunk_size = 1024 for chunk in inp.iter_content(chunk_size): if not chunk: continue yield chunk song_id = track.uri.split(':')[-1] return _stream(self.client.get_stream_url(song_id)) def search(self, keywords, matches): results = self.client.search(keywords) if matches == 'artist': return {'artists': results.get('artist_hits', [])} elif matches == 'album': return {'albums': results.get('album_hits', [])} elif matches == 'tracks': return {'tracks': results.get('song_hits', [])} elif matches == 'all': return { 'artists': results.get('artist_hits', []), 'albums': results.get('album_hits', []), 'tracks': results.get('song_hits', []) }
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)
def get_google_playlists(): print("Retreiving playlists from Google Music.") playlists_path = f"{state_dir}/playlists.json" if os.path.exists(playlists_path) and not force_fetch: with open(playlists_path, 'r') as infile: return json.load(infile) print("Could not find saved favorites playlist, or force_fetch is True") credentials_path = f"{state_dir}/gmusic_credentials.json" mm = Mobileclient() if not os.path.exists(credentials_path): mm.perform_oauth(credentials_path, open_browser=True) mm.oauth_login(google_device_id, oauth_credentials=credentials_path) if mm.is_authenticated(): print("Authenticated sucessfully!") else: print("Could not authenticate :(") raise SystemExit(1) playlists = mm.get_all_user_playlist_contents() playlist_names = [p['name'] for p in playlists] print(f'Found playlists: {playlist_names}') clean_playlists = [] for p in playlists: playlist = { 'name': p['name'], 'tracks': [], } for track in p['tracks']: t = extract_google_track(track) if t is not None: playlist['tracks'].append(t) if len(playlist['tracks']) == 0: print(f"No tracks found in {p['name']}") else: clean_playlists.append(playlist) pprint(clean_playlists) if len(clean_playlists) == 0: print(f"No playlists with tracks found") raise SystemExit(1) with open(playlists_path, 'w') as outfile: json.dump(clean_playlists, outfile) return clean_playlists
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 get_online_library(): api = Mobileclient() logged_in = api.login("*****@*****.**", "yesilly12twat", Mobileclient.FROM_MAC_ADDRESS) if logged_in == False: sys.exit("Couldn't log in.") if not api.is_authenticated: sys.exit("Couldn't log in. Wrong credentials") library = { "tracks": api.get_all_songs(), "playlists": api.get_all_user_playlist_contents(), } return library
class Plugin: name = 'gmusic' def __init__(self, username, password): self.client = Mobileclient() self.client.login(username, password, Mobileclient.FROM_MAC_ADDRESS) # self.webclient = Webclient() # self.webclient.login(username, password) def get_tracks(self, artist=None, album=None): """ Fetches tracks from api. If no filter is defined, it will get user tracks """ return TrackList(self.client.get_all_songs()) def get_playlists(self): """ Get playlists and radios """ playlists = [] for playlist in self.client.get_all_user_playlist_contents(): tracks = TrackList([self.client.get_track_info(x['trackId']) for x in playlist['tracks']]) playlists.append(PlayList(playlist['name'], tracks)) return playlists def stream(self, track): def _stream(url): inp = requests.get(url, stream=True) chunk_size = 1024 for chunk in inp.iter_content(chunk_size): if not chunk: continue yield chunk song_id = track.uri.split(':')[-1] return _stream(self.client.get_stream_url(song_id)) def search(self, keywords, matches): results = self.client.search(keywords) if matches == 'artist': return {'artists': results.get('artist_hits', [])} elif matches == 'album': return {'albums': results.get('album_hits', [])} elif matches == 'tracks': return {'tracks': results.get('song_hits', [])} elif matches == 'all': return {'artists': results.get('artist_hits', []), 'albums': results.get('album_hits', []), 'tracks': results.get('song_hits', [])}
def main(): parser = argparse.ArgumentParser(description='Sync iTunes Playlists to Google Play Music.') parser.add_argument('itunes_music_library', type=str, help='Path to iTunes Music Library.xml') parser.add_argument('google_music_manager_db', type=str, help='Path to Google Music Manager ServerDatabase.db') parser.add_argument('--verbose', action='store_true', default=False, help='Print verbose output') parser.add_argument('playlists', type=str, nargs='*', metavar='playlist', help='Names of playlists to sync') args = parser.parse_args() global verbose verbose = args.verbose lib = pyItunes.Library(args.itunes_music_library) known_itunes_playlists = lib.getPlaylistNames() if args.playlists: itunes_playlists = args.playlists not_found = set(itunes_playlists) - set(known_itunes_playlists) if not_found: print('''Error: these playlists aren't in your iTunes Library: %s ''' % (sorted(not_found), )) return 1 else: itunes_playlists = known_itunes_playlists server_db = sqlite3.connect(args.google_music_manager_db) api = None username, password = open(os.path.join(os.path.dirname(__file__), 'auth.txt'), 'r').read().splitlines() try: api = Mobileclient() if not api.login(username, password): print('Error: unable to login', file=sys.stderr) return 1 all_google_playlists = api.get_all_user_playlist_contents() google_playlists = {p['name']: p for p in all_google_playlists} for name in itunes_playlists: sync_playlist(api, server_db, lib.getPlaylist(name), google_playlists) finally: if api: api.logout() return 0
class GoogleMusicClient(object): def __init__(self, email, password, device_id): self.api = Mobileclient() self.api.login(email, password) self.device_id = device_id self.playlists = [] self.songs = {} self.player = Player() def update_library(self): self.songs = {song.get('nid'): song for song in self.api.get_all_songs()} self.playlists = [ Playlist(name=playlist['name'].encode('utf-8'), tracks=[track['trackId'] for track in playlist['tracks']]) for playlist in self.api.get_all_user_playlist_contents() ] def play(self, item_type, item_index): if item_type == 'playlist': click.echo('playing {} {}'.format(item_type, self.playlists[int(item_index)].name)) def queue(self, item_type, item_index): if item_type == 'playlist': for song in self.playlists[int(item_index)]: self.player.enqueue(song) def show(self, item_type, item_index): if item_type == 'playlist': playlist = self.playlists[int(item_index)] click.echo('showing {} {}'.format(item_type, playlist.name)) for song in playlist.tracks: click.echo(self.songs[song]) elif item_type == 'queue': for song in self.player.queue: click.echo(song) def list(self, item_type): if item_type == 'playlist': for i, playlist in enumerate(self.playlists): click.echo("[{}]\t{} ({})".format(i, playlist.name, len(playlist.tracks))) elif item_type == 'songs': click.echo(self.songs)
def get_albums_from_playlist(config): login, password, playlist_name, android_id = map(config.get, ('login', 'password', 'playlist', 'android_id')) api = Mobileclient() if not android_id: android_id = Mobileclient.FROM_MAC_ADDRESS try: api.login(login, password, android_id) all_playlists = api.get_all_user_playlist_contents() matched_playlist = next(playlist for playlist in all_playlists if playlist['name'].lower() == playlist_name.lower()) album_list = {(entry['track']['albumArtist'], entry['track']['album']) for entry in matched_playlist['tracks'] if 'track' in entry} return album_list except StopIteration: sys.exit('playlist not found.') except NotLoggedIn: sys.exit('wrong username or password.') finally: api.logout()
def analyse(self, device_id, artists_number): """The main method that analysis Google Play Music account, and returns results of parsing concert.ua :param device_id: str :param artists_number: int :return: results: list """ api = Mobileclient() try: artists_number = int(artists_number) device_id = str(device_id) except ValueError: return -1 api.oauth_login(str(device_id)) main_playlist = Playlist() library = api.get_all_songs() for song in library: main_playlist.append(song) playlists = api.get_all_user_playlist_contents() for playlist in playlists: for song in playlist['tracks']: main_playlist.append(song) top = main_playlist.top_n(artists_number) artists = [] for artist_id in top: artist = api.get_artist_info(include_albums=False, artist_id=artist_id, max_rel_artist=0, max_top_tracks=0) artists.append(artist['name']) results = [] for artist in artists: parse_result = Parser().parse(artist) if parse_result: results.append(parse_result) return results
class GmSession: def __init__(self): self.session = Mobileclient() self.device_id = gmusic_device_id self.cred_path = gmusic_cred_path self.playlist_id = gmusic_playlist_id def login(self): self.session.oauth_login(device_id=self.device_id, oauth_credentials=self.cred_path) def logout(self): self.session.logout() def search(self, artist, song): search_string = f'{artist.lower()}' + f', {song.lower()}' results = self.session.search(search_string, max_results=20) if len(results['song_hits']) > 0: first_result = results['song_hits'][0]['track'] if 'storeId' in first_result.keys(): return first_result['storeId'] elif 'id' in first_result.keys(): print('bad id') return first_result['id'] elif 'nid' in first_result.keys(): print('bad id') return results['song_hits'][0]['track']['nid'] else: print('No songs found...') def add_to_playlist(self, song_list): playlists = self.session.get_all_user_playlist_contents() for playlist in playlists: if playlist['id'] == self.playlist_id: to_remove = [] for track in playlist['tracks']: to_remove.append(track['id']) print('Adding new songs...') res = self.session.add_songs_to_playlist( self.playlist_id, song_list) print('Removing previous songs...') out = self.session.remove_entries_from_playlist(to_remove) print('Finished')
def login_google(): """ Log into Google and retrieve user library and playlists """ g = Mobileclient() logged_in = g.login(config.auth['GOOGLE_EMAIL'], config.auth['GOOGLE_PASSWORD'], Mobileclient.FROM_MAC_ADDRESS) if not g.is_authenticated(): log.error("Invalid Google email/password; exiting.") sys.exit(1) log.info("Retrieving Google Music playlists") g.playlists = g.get_all_user_playlist_contents() log.info("Retrieving Google Music library") g.library = get_google_library(g) return g
class GMusicClient: def __init__(self): """ This connects to the google music server by requesting credentials. """ self.api = Mobileclient() username = input('Type your Google Play Music email below.\n--> ') dir_path = os.path.dirname( os.path.realpath(__file__)) + '/.cache-gmusic-' + ''.join( filter(str.isalpha, username)) # Check if already authenticated if (not os.path.isfile(dir_path)): self.api.perform_oauth(open_browser=True, storage_filepath=dir_path) # Attempt to log in; if fail, get new token. try: self.api.oauth_login(Mobileclient.FROM_MAC_ADDRESS) except: self.api.perform_oauth(open_browser=True, storage_filepath=dir_path) self.api.oauth_login(Mobileclient.FROM_MAC_ADDRESS) print('Connected to GMusic') def get_playlists(self): """ Gets all the playlists in Google Play Music. Some may not actually have any music, but they will be processed anyways. """ return Playlists(self.api.get_all_user_playlist_contents()) def get_all(self): """ Gets the entire Google library for adding to the """ print('Requesting Google library') library = self.api.get_all_songs() print('Received Google library, we have', len(library), 'songs') return MusicLibrary(library)
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 Player(object): def __init__(self, device_id): self.api = Mobileclient() self.api.logger.setLevel(logging.INFO) #print(utils.log_filepath) options = ["--aout=alsa", "-I dummy", "--fullscreen"] self.vlc = Instance(options) self.player = None self.loaded_tracks = [] self.playing = False self.repeat = Repeat.none self.random = False self.song_index = 0 self.now_playing_title = "" self.now_playing_artist = "" self.now_playing_playlist = "" # 取得したjsonの生データ self.song_library = [] self.playlist_library = [] # 整頓した楽曲ライブラリ self.songs = [] self.albums = [] self.playlists = [] self.artists = [] # play musicログイン if not os.path.exists(CREDENTIAL_FILE): self.api.perform_oauth(CREDENTIAL_FILE) self.api.oauth_login(device_id, CREDENTIAL_FILE) # 曲一覧読み込み if os.path.isfile(JSON_DIR + "songs.json"): # Load from file print("Found songs data.") with open(JSON_DIR + 'songs.json') as input_file: self.song_library = json.load(input_file) else: self.song_library = self.api.get_all_songs() # Save to file with open(JSON_DIR + 'songs.json', 'w') as output_file: json.dump(self.song_library, output_file) self.create_songs() self.create_albums() self.create_artists() # プレイリスト読み込み if os.path.isfile(JSON_DIR + "playlists.json"): # Load from file print("Found playlist data.") with open(JSON_DIR + 'playlists.json') as input_file: self.playlist_library = json.load(input_file) else: self.playlist_library = self.api.get_all_user_playlist_contents() # Save to file with open(JSON_DIR + 'playlists.json', 'w') as output_file: json.dump(self.playlist_library, output_file) #プレイリスト名編集 self.create_playlists() # 定時ライブラリ更新処理 t = threading.Timer(RELOAD_LIB_TIME, self.auto_reload) t.start() def auto_reload(self): while True: if not self.playing: break time.sleep(60) self.reload_library() print("[ music list auto reloaded ]") t = threading.Timer(RELOAD_LIB_TIME, self.auto_reload) t.start() def reload_library(self): # 曲一覧読み込み self.song_library = self.api.get_all_songs() # Save to file with open(JSON_DIR + 'songs.json', 'w') as output_file: json.dump(self.song_library, output_file) self.create_songs() self.create_albums() self.create_artists() # プレイリスト読み込み self.playlist_library = self.api.get_all_user_playlist_contents() # Save to file with open(JSON_DIR + 'playlists.json', 'w') as output_file: json.dump(self.playlist_library, output_file) #プレイリスト名編集 self.create_playlists() def create_songs(self): self.songs = [] # 曲名編集 for index, song in enumerate(self.song_library): self.songs.append({}) self.songs[index].update({"original_name": song['title']}) self.songs[index].update( {"name": cir.convert_into_romaji(song['title'])}) self.songs[index].update({"artist": song['artist']}) self.songs[index].update({"trackId": song['id']}) self.songs[index].update({"source": 1}) #print(self.songs[index]) #sleep(0.1) print("[ create_songs finished ]") def create_playlists(self): self.playlists = [] #プレイリスト名編集 for index, playlist in enumerate(self.playlist_library): self.playlists.append({}) self.playlists[index].update({"original_name": playlist['name']}) self.playlists[index].update( {"name": cir.convert_into_romaji(playlist['name'])}) self.playlists[index].update({"tracks": playlist['tracks']}) print(self.playlists[index]['name']) print("[ create_playlists finished ]") def create_albums(self): self.albums = [] # アルバムリスト作成 for song in self.song_library: album_found = False track = {} for index, album in enumerate(self.albums): # アルバムがすでに登録されていた場合 if album['original_name'] == song['album']: album_found = True track.update({"trackId": song['id']}) track.update({"source": 1}) track.update({"trackNumber": song['trackNumber']}) self.albums[index]['tracks'].append(track) #print(self.albums[index]) break if album_found: continue #新規アルバム作成 albums_len = len(self.albums) self.albums.append({}) self.albums[albums_len].update({"original_name": song['album']}) self.albums[albums_len].update( {"name": cir.convert_into_romaji(song['album'])}) track.update({"trackId": song['id']}) track.update({"source": 1}) track.update({"trackNumber": song['trackNumber']}) self.albums[albums_len].update({"tracks": [track]}) #print(self.albums[albums_len]) # tracknumberでソート for album in self.albums: album['tracks'] = sorted(album['tracks'], key=lambda x: x['trackNumber']) print(album["name"]) print("[ create_albums finished ]") def create_artists(self): self.artists = [] # アーティストリスト作成 for song in self.song_library: artist_found = False track = {} for index, artist in enumerate(self.artists): # アーティストがすでに登録されていた場合 if artist['original_name'] == song['artist']: artist_found = True track.update({"trackId": song['id']}) track.update({"source": 1}) track.update({"trackNumber": song['trackNumber']}) self.artists[index]['tracks'].append(track) break if artist_found: continue #新規アルバム作成 artists_len = len(self.artists) self.artists.append({}) self.artists[artists_len].update({"original_name": song['artist']}) self.artists[artists_len].update( {"name": cir.convert_into_romaji(song['artist'])}) track.update({"trackId": song['id']}) track.update({"source": 1}) track.update({"trackNumber": song['trackNumber']}) self.artists[artists_len].update({"tracks": [track]}) print(self.artists[artists_len]["name"]) print("[ create_artists finished ]") def load_playlist(self, name): name = name.strip().lower() print("Looking for...", name) top_diff = 0.0 top_playlist = {} # 検索 for playlist_dict in self.playlists: playlist_name = playlist_dict['name'].strip().lower() diff = difflib.SequenceMatcher(None, playlist_name, name).ratio() if diff > top_diff: print("diff match...", playlist_dict['name'], ":", diff) top_playlist = playlist_dict top_diff = diff else: pass #print("Found...", playlist_dict['name']) # 一番マッチしたものを返す if top_diff > DIFF_ARGS: self.loaded_tracks = [] print(top_diff) print("Found match...", top_playlist['name']) for track_dict in top_playlist['tracks']: self.loaded_tracks.append(track_dict) self.now_playing_playlist = top_playlist['original_name'] return top_playlist['original_name'] else: return None def load_song(self, name): name = name.strip().lower() print("Looking for...", name) top_diff = 0.0 top_song = {} for song_dict in self.songs: song_name = song_dict['name'].strip().lower() diff = difflib.SequenceMatcher(None, song_name, name).ratio() #print(diff) if diff > top_diff: print("diff match...", song_dict['name'], ":", diff) top_song = song_dict top_diff = diff else: pass #print("Found...", song_dict['name']) # 一番マッチしたものを返す if top_diff > DIFF_ARGS: self.loaded_tracks = [] print(top_diff) print("Found match...", top_song['name']) self.loaded_tracks.append(top_song) self.now_playing_playlist = "" return top_song['original_name'] else: return None def load_album(self, name): name = name.strip().lower() print("Looking for...", name) top_diff = 0.0 top_album = {} for album_dict in self.albums: album_name = album_dict['name'].strip().lower() diff = difflib.SequenceMatcher(None, album_name, name).ratio() #print(diff) if diff > top_diff: print("diff match...", album_dict['name'], ":", diff) top_album = album_dict top_diff = diff else: pass #print("Found...", album_dict['name']) # 一番マッチしたものを返す if top_diff > DIFF_ARGS: self.loaded_tracks = [] print(top_diff) print("Found match...", top_album['name']) for track_dict in top_album['tracks']: self.loaded_tracks.append(track_dict) self.now_playing_playlist = top_album['original_name'] return top_album['original_name'] else: return None def load_artist(self, name): name = name.strip().lower() print("Looking for...", name) top_diff = 0.0 top_artist = {} for artist_dict in self.artists: artist_name = artist_dict['name'].strip().lower() diff = difflib.SequenceMatcher(None, artist_name, name).ratio() #print(diff) if diff > top_diff: print("diff match...", artist_dict['name'], ":", diff) top_artist = artist_dict top_diff = diff else: pass # 一番マッチしたものを返す if top_diff > DIFF_ARGS: self.loaded_tracks = [] print(top_diff) print("Found match...", top_artist['name']) for track_dict in top_artist['tracks']: self.loaded_tracks.append(track_dict) self.now_playing_playlist = top_artist['original_name'] return top_artist['original_name'] else: return None def load_cloud(self, name, isArtist=True, isSong=True, isAlbum=True): search = self.api.search(name) # アーティストのトップ曲を流す if search["artist_hits"] and isArtist: for index in range(len(search["artist_hits"])): artist_id = search["artist_hits"][index]["artist"]["artistId"] artist = self.api.get_artist_info(artist_id, max_top_tracks=MAX_TRACK, include_albums=False, max_rel_artist=0) if "topTracks" in artist.keys(): break if "topTracks" in artist.keys(): self.loaded_tracks = [] for track_dict in artist["topTracks"]: track_dict.update({"track": track_dict}) track_dict.update({"trackId": track_dict["storeId"]}) track_dict.update({"source": "2"}) self.loaded_tracks.append(track_dict) self.now_playing_playlist = "" return artist["name"] # 単曲を流す(複数にしたほうがいいかも) elif search["song_hits"] and isSong: self.loaded_tracks = [] for index, track_dict in enumerate(search["song_hits"]): if index >= MAX_TRACK: break track_dict.update({"trackId": track_dict["track"]["storeId"]}) track_dict.update({"source": "2"}) self.loaded_tracks.append(track_dict) self.now_playing_playlist = "" return self.loaded_tracks[0]["track"]["title"] # アルバムを流す(正確さに欠ける) elif search["album_hits"] and isAlbum: album_id = search["album_hits"][0]["album"]["albumId"] album = self.api.get_album_info(album_id) self.loaded_tracks = [] for track_dict in album["tracks"]: track_dict.update({"track": track_dict}) track_dict.update({"trackId": track_dict["storeId"]}) track_dict.update({"source": "2"}) self.loaded_tracks.append(track_dict) self.now_playing_playlist = album["name"] return album["name"] # ステーション(ここまで回ってこない気が・・・) elif search["station_hits"]: pass return None def end_callback(self, event, track_index): # ランダム再生時処理 if self.random: self.song_index = random.randint(0, len(self.loaded_tracks) - 1) self.play_song(self.loaded_tracks[self.song_index]) event_manager = self.player.event_manager() event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, self.song_index + 1) return # 一曲リピート if self.repeat == Repeat.song: self.play_song(self.loaded_tracks[track_index - 1]) event_manager = self.player.event_manager() event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, track_index) return # 通常再生・プレイリストリピート if track_index < len(self.loaded_tracks): self.song_index = track_index self.play_song(self.loaded_tracks[track_index]) event_manager = self.player.event_manager() event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, track_index + 1) else: if self.repeat == Repeat.playlist: self.start_playlist() else: self.playing = False self.song_index = 0 def start_playlist(self): if len(self.loaded_tracks) > 0: # ランダム再生時処理 if self.random: self.song_index = random.randint(0, len(self.loaded_tracks) - 1) else: # 通常再生 self.song_index = 0 self.play_song(self.loaded_tracks[self.song_index]) event_manager = self.player.event_manager() event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, self.song_index + 1) return True return False def play_song(self, song_dict): stream_url = self.api.get_stream_url(song_dict['trackId']) self.player = self.vlc.media_player_new() media = self.vlc.media_new(stream_url) self.player.set_media(media) self.player.play() self.playing = True if (song_dict['source'] == '2'): self.now_playing_artist, self.now_playing_title = self.get_song_details( song_dict) else: self.now_playing_artist, self.now_playing_title = self.get_local_song_details( song_dict['trackId']) print("Playing...", self.now_playing_artist, " - ", self.now_playing_title) def stop(self): if self.player != None: self.player.stop() self.player = None self.playing = False self.repeat = Repeat.none self.random = False self.song_index = 0 self.now_playing_title = "" self.now_playing_artist = "" def pause(self): if self.player == None: return False if self.playing == True: self.player.set_pause(1) self.playing = False return True return False def resume(self): if self.player == None: return False if self.playing == False: self.player.set_pause(0) self.playing = True return True return False def next(self): if self.player == None: return False # ランダム if self.random: self.song_index = random.randint(0, len(self.loaded_tracks) - 1) self.player.stop() self.play_song(self.loaded_tracks[self.song_index]) event_manager = self.player.event_manager() event_manager.event_detach(EventType.MediaPlayerEndReached) event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, self.song_index + 1) return True # 通常 if self.song_index + 1 < len(self.loaded_tracks): self.song_index += 1 self.player.stop() self.play_song(self.loaded_tracks[self.song_index]) event_manager = self.player.event_manager() event_manager.event_detach(EventType.MediaPlayerEndReached) event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, self.song_index + 1) return True else: if self.repeat == Repeat.playlist: self.start_playlist() return True return False def prev(self): if self.player == None: return False if self.song_index - 1 <= 0: self.song_index -= 1 self.player.stop() self.play_song(self.loaded_tracks[self.song_index]) event_manager = self.player.event_manager() event_manager.event_detach(EventType.MediaPlayerEndReached) event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, self.song_index + 1) return True return False def get_local_song_details(self, track_id): for song_dict in self.song_library: if track_id == song_dict['id']: return song_dict['artist'], song_dict['title'] def get_song_details(self, song_dict): return song_dict['track']['albumArtist'], song_dict['track']['title'] def set_volume(self, volume): if self.player: self.player.audio_set_volume(volume)
if device['type'] == 'ANDROID': device_id = device['id'][2:] #.encode('ascii','ignore') break elif device['type'] == 'IOS': device_id = device['id'] break if not device_id: print("No Android or iOS device linked to account!") exit() mc = Mobileclient() mc.login(username, password, device_id) # Grab all playlists, and sort them into a structure playlists = mc.get_all_user_playlist_contents() if not quiet: print(len(playlists), "playlist(s) found.") master = [] for ply in playlists: name = ply['name'] curPlaylist = Playlist(name) tracks = ply['tracks'] for song in tracks: if song['source'] == u"2": # If song is not custom upload tid = song['trackId'] title = song['track']['title'] artist = song['track']['artist'] album = song['track']['album'] length = int(song['track']['durationMillis']) / 1000 newSong = Song(tid, title, artist, album, length)
class GmusicComponent(MediaPlayerDevice): def __init__(self, hass, config): from gmusicapi import Mobileclient # https://github.com/simon-weber/gmusicapi/issues/424 class GMusic(Mobileclient): def login(self, username, password, device_id, authtoken=None): if authtoken: self.session._authtoken = authtoken self.session.is_authenticated = True try: # Send a test request to ensure our authtoken is still valide and working self.get_registered_devices() return True except: # Faild with the test-request so we set "is_authenticated=False" # and go through the login-process again to get a new "authtoken" self.session.is_authenticated = False if device_id: if super(GMusic, self).login(username, password, device_id): return True # Prevent further execution in case we failed with the login-process raise Exception("Legacy login failed! Please check logs for any gmusicapi related WARNING") self.hass = hass #self._api = GMusic() self._api = Mobileclient() _login_type = config.get(CONF_LOGIN_TYPE, DEFAULT_LOGIN_TYPE) _device_id = config.get(CONF_DEVICE_ID) if _login_type == 'legacy': _authtoken = config.get(CONF_TOKEN_PATH, DEFAULT_TOKEN_PATH) + "gmusic_authtoken" if os.path.isfile(_authtoken): with open(_authtoken, 'rb') as handle: authtoken = pickle.load(handle) else: authtoken = None _username = config.get(CONF_USERNAME) _password = config.get(CONF_PASSWORD, DEFAULT_PASSWORD) logged_in = self._api.login(_username, _password, _device_id, authtoken) if not logged_in: _LOGGER.error("Failed legacy log in, check http://unofficial-google-music-api.readthedocs.io/en/latest/reference/mobileclient.html#gmusicapi.clients.Mobileclient.login") return False with open(_authtoken, 'wb') as f: pickle.dump(self._api.session._authtoken, f) elif _login_type == 'oauth': _oauth_cred = config.get(CONF_OAUTH_CRED, DEFAULT_OAUTH_CRED) if os.path.isfile(_oauth_cred): try: logged_in = self._api.oauth_login(_device_id, _oauth_cred) if not logged_in: raise Exception("Login failed! Please check logs for any gmusicapi related WARNING") except: raise Exception("Failed oauth login, check https://unofficial-google-music-api.readthedocs.io/en/latest/reference/mobileclient.html#gmusicapi.clients.Mobileclient.perform_oauth") else: raise Exception("Invalid - Not a file! oauth_cred: ", _oauth_cred) else: raise Exception("Invalid! login_type: ", _login_type) self._name = "gmusic_player" self._playlist = "input_select." + config.get(CONF_PLAYLISTS, DEFAULT_PLAYLISTS) self._media_player = "input_select." + config.get(CONF_SPEAKERS, DEFAULT_SPEAKERS) self._station = "input_select." + config.get(CONF_STATIONS, DEFAULT_STATIONS) self._source = "input_select." + config.get(CONF_SOURCE, DEFAULT_SOURCE) self._entity_ids = [] ## media_players - aka speakers self._playlists = [] self._playlist_to_index = {} self._stations = [] self._station_to_index = {} self._tracks = [] self._track = [] self._attributes = {} self._next_track_no = 0 hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self._update_playlists) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self._update_stations) self._shuffle = config.get(CONF_SHUFFLE, DEFAULT_SHUFFLE) self._shuffle_mode = config.get(CONF_SHUFFLE_MODE, DEFAULT_SHUFFLE_MODE) self._unsub_tracker = None self._playing = False self._state = STATE_OFF self._volume = 0.0 self._is_mute = False self._track_name = None self._track_artist = None self._track_album_name = None self._track_album_cover = None self._track_artist_cover = None self._attributes['_player_state'] = STATE_OFF @property def name(self): """ Return the name of the player. """ return self._name @property def icon(self): return 'mdi:music-circle' @property def supported_features(self): """ Flag media player features that are supported. """ return SUPPORT_GMUSIC_PLAYER @property def should_poll(self): """ No polling needed. """ return False @property def state(self): """ Return the state of the device. """ return self._state @property def device_state_attributes(self): """ Return the device state attributes. """ return self._attributes @property def is_volume_muted(self): """ Return True if device is muted """ return self._is_mute @property def is_on(self): """ Return True if device is on. """ return self._playing @property def media_content_type(self): """ Content type of current playing media. """ return MEDIA_TYPE_MUSIC @property def media_title(self): """ Title of current playing media. """ return self._track_name @property def media_artist(self): """ Artist of current playing media """ return self._track_artist @property def media_album_name(self): """ Album name of current playing media """ return self._track_album_name @property def media_image_url(self): """ Image url of current playing media. """ return self._track_album_cover @property def media_image_remotely_accessible(self): " True --> entity_picture: http://lh3.googleusercontent.com/Ndilu... " " False --> entity_picture: /api/media_player_proxy/media_player.gmusic_player?token=4454... " return True @property def shuffle(self): """Boolean if shuffling is enabled.""" return self._shuffle @property def volume_level(self): """Volume level of the media player (0..1).""" return self._volume def turn_on(self, *args, **kwargs): """ Turn on the selected media_player from input_select """ self._playing = False if not self._update_entity_ids(): return _player = self.hass.states.get(self._entity_ids) data = {ATTR_ENTITY_ID: _player.entity_id} if _player.state == STATE_OFF: self._unsub_tracker = track_state_change(self.hass, _player.entity_id, self._sync_player) self._turn_on_media_player(data) elif _player.state != STATE_OFF: self._turn_off_media_player(data) call_later(self.hass, 1, self.turn_on) def _turn_on_media_player(self, data=None): """Fire the on action.""" if data is None: data = {ATTR_ENTITY_ID: self._entity_ids} self._state = STATE_IDLE self.schedule_update_ha_state() self.hass.services.call(DOMAIN_MP, 'turn_on', data) def turn_off(self, entity_id=None, old_state=None, new_state=None, **kwargs): """ Turn off the selected media_player """ self._playing = False self._track_name = None self._track_artist = None self._track_album_name = None self._track_album_cover = None _player = self.hass.states.get(self._entity_ids) data = {ATTR_ENTITY_ID: _player.entity_id} self._turn_off_media_player(data) def _turn_off_media_player(self, data=None): """Fire the off action.""" self._playing = False self._state = STATE_OFF self._attributes['_player_state'] = STATE_OFF self.schedule_update_ha_state() if data is None: data = {ATTR_ENTITY_ID: self._entity_ids} self.hass.services.call(DOMAIN_MP, 'turn_off', data) def _update_entity_ids(self): """ sets the current media_player from input_select """ media_player = self.hass.states.get(self._media_player) if media_player is None: _LOGGER.error("(%s) is not a valid input_select entity.", self._media_player) return False _entity_ids = "media_player." + media_player.state if self.hass.states.get(_entity_ids) is None: _LOGGER.error("(%s) is not a valid media player.", media_player.state) return False # Example: self._entity_ids = media_player.bedroom_stereo self._entity_ids = _entity_ids return True def _sync_player(self, entity_id=None, old_state=None, new_state=None): """ Perform actions based on the state of the selected media_player """ # self._unsub_tracker = track_state_change(self.hass, self._entity_ids, self._sync_player) if not self._playing: return _player = self.hass.states.get(self._entity_ids) """ full state of device _player, include attributes. """ #self._attributes['_player_full'] = _player """ entity_id of _player. """ _player_id = _player.entity_id self._attributes['_player_id'] = _player_id """ _player "friendley_name" """ _player_friendly = _player.attributes['friendly_name'] self._attributes['_player_friendly'] = _player_friendly """ _player state - Example [playing -or- idle]. """ _player_state = _player.state self._attributes['_player_state'] = _player_state """ Set new volume if it has been changed on the _player """ if 'volume_level' in _player.attributes: self._volume = round(_player.attributes['volume_level'],2) if _player.state == 'off': self._state = STATE_OFF self.turn_off() self.schedule_update_ha_state() def _update_playlists(self, now=None): """ Sync playlists from Google Music library """ self._playlist_to_index = {} self._playlists = self._api.get_all_user_playlist_contents() idx = -1 for playlist in self._playlists: idx = idx + 1 name = playlist.get('name','') if len(name) < 1: continue self._playlist_to_index[name] = idx playlists = list(self._playlist_to_index.keys()) self._attributes['playlists'] = playlists data = {"options": list(playlists), "entity_id": self._playlist} self.hass.services.call(input_select.DOMAIN, input_select.SERVICE_SET_OPTIONS, data) def _update_stations(self, now=None): """ Sync stations from Google Music library """ self._station_to_index = {} self._stations = self._api.get_all_stations() idx = -1 for station in self._stations: idx = idx + 1 name = station.get('name','') library = station.get('inLibrary') if len(name) < 1: continue if library == True: self._station_to_index[name] = idx stations = list(self._station_to_index.keys()) stations.insert(0,"I'm Feeling Lucky") self._attributes['stations'] = stations data = {"options": list(stations), "entity_id": self._station} self.hass.services.call(input_select.DOMAIN, input_select.SERVICE_SET_OPTIONS, data) def _load_playlist(self, playlist=None): """ Load selected playlist to the track_queue """ if not self._update_entity_ids(): return """ if source == Playlist """ _playlist_id = self.hass.states.get(self._playlist) if _playlist_id is None: _LOGGER.error("(%s) is not a valid input_select entity.", self._playlist) return if playlist is None: playlist = _playlist_id.state idx = self._playlist_to_index.get(playlist) if idx is None: _LOGGER.error("playlist to index is none!") self._turn_off_media_player() return self._tracks = None self._tracks = self._playlists[idx]['tracks'] self._total_tracks = len(self._tracks) #self.log("Loading [{}] Tracks From: {}".format(len(self._tracks), _playlist_id)) if self._shuffle and self._shuffle_mode != 2: random.shuffle(self._tracks) self._play() def _load_station(self, station=None): """ Load selected station to the track_queue """ self._total_tracks = 100 if not self._update_entity_ids(): return """ if source == station """ self._tracks = None _station_id = self.hass.states.get(self._station) if _station_id is None: _LOGGER.error("(%s) is not a valid input_select entity.", self._station) return if station is None: station = _station_id.state if station == "I'm Feeling Lucky": self._tracks = self._api.get_station_tracks('IFL', num_tracks = self._total_tracks) else: idx = None idx = self._station_to_index.get(station) if idx is None: self._turn_off_media_player() return _id = self._stations[idx]['id'] self._tracks = self._api.get_station_tracks(_id, num_tracks = self._total_tracks) # self.log("Loading [{}] Tracks From: {}".format(len(self._tracks), _station_id)) self._play() def _play(self): self._playing = True self._next_track_no = -1 self._get_track() def _get_track(self, entity_id=None, old_state=None, new_state=None, retry=3): """ Get a track and play it from the track_queue. """ _track = None if self._shuffle and self._shuffle_mode != 1: self._next_track_no = random.randrange(self._total_tracks) - 1 else: self._next_track_no = self._next_track_no + 1 if self._next_track_no >= self._total_tracks: self._next_track_no = 0 ## Restart curent playlist (Loop) #random.shuffle(self._tracks) ## (re)Shuffle on Loop try: _track = self._tracks[self._next_track_no] except IndexError: _LOGGER.error("Out of range! Number of tracks in track_queue == (%s)", self._total_tracks) self._turn_off_media_player() return if _track is None: _LOGGER.error("_track is None!") self._turn_off_media_player() return """ If source is a playlist, track is inside of track """ if 'track' in _track: _track = _track['track'] """ Find the unique track id. """ uid = '' if 'trackId' in _track: uid = _track['trackId'] elif 'storeId' in _track: uid = _track['storeId'] elif 'id' in _track: uid = _track['id'] else: _LOGGER.error("Failed to get ID for track: (%s)", _track) if retry < 1: self._turn_off_media_player() return return self._get_track(retry=retry-1) """ If available, get track information. """ if 'title' in _track: self._track_name = _track['title'] else: self._track_name = None if 'artist' in _track: self._track_artist = _track['artist'] else: self._track_artist = None if 'album' in _track: self._track_album_name = _track['album'] else: self._track_album_name = None if 'albumArtRef' in _track: _album_art_ref = _track['albumArtRef'] ## returns a list self._track_album_cover = _album_art_ref[0]['url'] ## of dic else: self._track_album_cover = None if 'artistArtRef' in _track: _artist_art_ref = _track['artistArtRef'] self._track_artist_cover = _artist_art_ref[0]['url'] else: self._track_artist_cover = None """ Get the stream URL and play on media_player """ try: _url = self._api.get_stream_url(uid) except Exception as err: _LOGGER.error("Failed to get URL for track: (%s)", uid) if retry < 1: self._turn_off_media_player() return return self._get_track(retry=retry-1) self._state = STATE_PLAYING self.schedule_update_ha_state() data = { ATTR_MEDIA_CONTENT_ID: _url, ATTR_MEDIA_CONTENT_TYPE: "audio/mp3", ATTR_ENTITY_ID: self._entity_ids } self.hass.services.call(DOMAIN_MP, SERVICE_PLAY_MEDIA, data) def play_media(self, media_type, media_id, **kwargs): if not self._update_entity_ids(): return _player = self.hass.states.get(self._entity_ids) if media_type == "station": _source = {"option":"Station", "entity_id": self._source} _option = {"option": media_id, "entity_id": self._station} self.hass.services.call(input_select.DOMAIN, input_select.SERVICE_SELECT_OPTION, _source) self.hass.services.call(input_select.DOMAIN, input_select.SERVICE_SELECT_OPTION, _option) elif media_type == "playlist": _source = {"option":"Playlist", "entity_id": self._source} _option = {"option": media_id, "entity_id": self._playlist} self.hass.services.call(input_select.DOMAIN, input_select.SERVICE_SELECT_OPTION, _source) self.hass.services.call(input_select.DOMAIN, input_select.SERVICE_SELECT_OPTION, _option) else: _LOGGER.error("Invalid: (%s) --> media_types are 'station' or 'playlist'.", media_type) return if self._playing == True: self.media_stop() self.media_play() elif self._playing == False and self._state == STATE_OFF: if _player.state == STATE_OFF: self.turn_on() else: data = {ATTR_ENTITY_ID: _player.entity_id} self._turn_off_media_player(data) call_later(self.hass, 1, self.turn_on) else: _LOGGER.error("self._state is: (%s).", self._state) def media_play(self, entity_id=None, old_state=None, new_state=None, **kwargs): """Send play command.""" if self._state == STATE_PAUSED: self._state = STATE_PLAYING self.schedule_update_ha_state() data = {ATTR_ENTITY_ID: self._entity_ids} self.hass.services.call(DOMAIN_MP, 'media_play', data) else: _source = self.hass.states.get(self._source) source = _source.state if source == 'Playlist': self._load_playlist() elif source == 'Station': self._load_station() else: _LOGGER.error("Invalid source: (%s)", source) self.turn_off() return def media_pause(self, **kwargs): """ Send media pause command to media player """ self._state = STATE_PAUSED self.schedule_update_ha_state() data = {ATTR_ENTITY_ID: self._entity_ids} self.hass.services.call(DOMAIN_MP, 'media_pause', data) def media_play_pause(self, **kwargs): """Simulate play pause media player.""" if self._state == STATE_PLAYING: self.media_pause() else: self.media_play() def media_previous_track(self, **kwargs): """Send the previous track command.""" if self._playing: self._next_track_no = max(self._next_track_no - 2, -1) self._get_track() def media_next_track(self, **kwargs): """Send next track command.""" if self._playing: self._get_track() def media_stop(self, **kwargs): """Send stop command.""" self._state = STATE_IDLE self._playing = False self._track_artist = None self._track_album_name = None self._track_name = None self._track_album_cover = None self.schedule_update_ha_state() data = {ATTR_ENTITY_ID: self._entity_ids} self.hass.services.call(DOMAIN_MP, 'media_stop', data) def set_shuffle(self, shuffle): self._shuffle = shuffle if self._shuffle_mode == 1: self._attributes['shuffle_mode'] = 'Shuffle' elif self._shuffle_mode == 2: self._attributes['shuffle_mode'] = 'Random' elif self._shuffle_mode == 3: self._attributes['shuffle_mode'] = 'Shuffle Random' else: self._attributes['shuffle_mode'] = self._shuffle_mode return self.schedule_update_ha_state() def set_volume_level(self, volume): """Set volume level.""" self._volume = round(volume,2) data = {ATTR_ENTITY_ID: self._entity_ids, 'volume_level': self._volume} self.hass.services.call(DOMAIN_MP, 'volume_set', data) self.schedule_update_ha_state() def volume_up(self, **kwargs): """Volume up the media player.""" newvolume = min(self._volume + 0.05, 1) self.set_volume_level(newvolume) def volume_down(self, **kwargs): """Volume down media player.""" newvolume = max(self._volume - 0.05, 0.01) self.set_volume_level(newvolume) def mute_volume(self, mute): """Send mute command.""" if self._is_mute == False: self._is_mute = True else: self._is_mute = False self.schedule_update_ha_state() data = {ATTR_ENTITY_ID: self._entity_ids, "is_volume_muted": self._is_mute} self.hass.services.call(DOMAIN_MP, 'volume_mute', data)
class GMusicWrapper(object): def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login( username, password, getenv('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def log(self, log_str): self.logger.debug(log_str) def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return [x[query_type] for x in results[hits_key]] def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.log('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.log('Fetching library...') def get_artist(self, name): """ Fetches information about an artist given its name """ search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) def get_latest_album(self, artist_name=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True) for index, val in enumerate(sorted_list): album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: return album_info return False def get_album_by_artist(self, artist_name, album_id=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] random.shuffle(album_info) for index, val in enumerate(album_info): album = self._api.get_album_info( album_id=album_info[index]['albumId'], include_tracks=True) if album['albumId'] != album_id and len(album['tracks']) >= 5: return album return False def get_song(self, name, artist_name=None, album_name=None): if artist_name: name = "%s %s" % (artist_name, name) elif album_name: name = "%s %s" % (album_name, name) search = self._search("song", name) if len(search) == 0: return False if album_name: for i in range(0, len(search) - 1): if album_name in search[i]['album']: return search[i] return search[0] def get_promoted_songs(self): return self._api.get_promoted_songs() def get_station(self, title, track_id=None, artist_id=None, album_id=None): if artist_id is not None: if album_id is not None: if track_id is not None: return self._api.create_station(title, track_id=track_id) return self._api.create_station(title, album_id=album_id) return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/alexa/stream/%s" % (getenv('APP_URL'), song_id) def get_thumbnail(self, artist_art): return artist_art.replace("http://", "https://") def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if 'storeId' in track: return track, track['storeId'] elif 'trackId' in track: return self.library[track['trackId']], track['trackId'] else: return None, None def get_artist_album_list(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return "Unable to find the artist you requested." artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list_text = "Here's the album listing for %s: " % artist_name counter = 0 for index, val in enumerate(artist_info['albums']): if counter > 25: # alexa will time out after 10 seconds if the list takes too long to iterate through break album_info = self._api.get_album_info( album_id=artist_info['albums'][index]['albumId'], include_tracks=True) if len(album_info['tracks']) > 5: counter += 1 album_list_text += ( artist_info['albums'][index]['name']) + ", " return album_list_text def get_latest_artist_albums(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list = artist_info['albums'] sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True) speech_text = 'The latest albums by %s are ' % artist_name counter = 0 for index, val in enumerate(sorted_list): if counter > 5: break else: album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: counter += 1 album_name = sorted_list[index]['name'] album_year = sorted_list[index]['year'] speech_text += '%s, released in %d, ' % (album_name, album_year) return speech_text def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70): # Give each match a score based on its similarity to the requested # name self.log('Fetching library...') request_name = request_name.lower() + artist_name.lower() scored_matches = [] for i, match in enumerate(all_matches): try: name = match['name'].lower() except (KeyError, TypeError): i = match name = all_matches[match]['title'].lower() if artist_name != "": name += all_matches[match]['artist'].lower() scored_matches.append({ 'index': i, 'name': name, 'score': fuzz.ratio(name, request_name) }) sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True) top_scoring = sorted_matches[0] self.log('Fetching library...') best_match = all_matches[top_scoring['index']] # Make sure we have a decent match (the score is n where 0 <= n <= 100) if top_scoring['score'] < minimum_score: return None return best_match def get_genres(self, parent_genre_id=None): return self._api.get_genres(parent_genre_id) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) @classmethod def generate_api(cls, **kwargs): return cls(getenv('GOOGLE_EMAIL'), getenv('GOOGLE_PASSWORD'), **kwargs)
class GPMClient(object): """ Google Play Music client. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" #------------------------------------------------------------------------------ def __init__(self, email, password, device_id): self.__api = Mobileclient() self.logged_in = False self.__device_id = device_id attempts = 0 while not self.logged_in and attempts < 3: self.logged_in = self.__api.login(email, password, device_id) attempts += 1 self.all_tracks = dict() self.playlists = dict() self.library = dict() #------------------------------------------------------------------------------ def logout(self): self.__api.logout() #------------------------------------------------------------------------------ def update_local_lib(self): songs = self.__api.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Get main library song_map = dict() for song in songs: if "rating" in song and song["rating"] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song["id"] song_artist = song["artist"] song_album = song["album"] song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = dict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): for album in self.library[artist].keys(): if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album # Get all playlists plists = self.__api.get_all_user_playlist_contents() for plist in plists: plist_name = plist["name"] self.playlists[plist_name] = list() for track in plist["tracks"]: if track["trackId"] not in song_map: song = song_map[track["trackId"]] = track["track"] song["id"] = track["trackId"] else: song = song_map[track["trackId"]] self.playlists[plist_name].append(song) #------------------------------------------------------------------------------ def get_stream_url(self, song): return self.__api.get_stream_url(song["id"], self.__device_id) #------------------------------------------------------------------------------ def rate_song(self, song, rating): try: song["rating"] = rating song_list = [song] self.__api.change_song_metadata(song_list) print "Gave a Thumbs Up to {0} by {1} on Google Play.".format( song["title"].encode("utf-8"), song["artist"].encode("utf-8")) except RuntimeError: print "Error giving a Thumbs Up on Google Play."
class 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)
class GMAAC(hass.Hass): def initialize(self): self.gmc = Mobileclient() self.boolean_connect = self.args["boolean_connect"] self.boolean_sync = self.args["boolean_sync"] self.boolean_power = self.args["boolean_power"] self.select_player = self.args["select_player"] self.select_playlist = self.args["select_playlist"] self.select_station = self.args["select_station"] self.boolean_load_pl = self.args["boolean_load_pl"] self.select_source = self.args["select_source"] self.boolean_next = self.args["boolean_next"] self.boolean_prev = self.args["boolean_prev"] self.turn_off(self.boolean_connect) self._player_id = '' self._playlist_id = '' self._playlists = [] self._playlist_to_index = {} self._station_id = '' self._stations = [] self._station_to_index = {} self._tracks = [] self._next_track_no = 0 self._track = [] self._source = self.get_state(self.select_source) self.listen_state(self.connect, self.boolean_connect) self.listen_state(self.power, self.boolean_power, new="on") self.listen_state(self.sync, self.boolean_sync, new="on") self.listen_state(self.set_source, self.select_source) self.listen_state(self.get_tracks, self.boolean_load_pl, new="on") self.listen_state(self.next_track, self.boolean_next, new="on") self.listen_state(self.prev_track, self.boolean_prev, new="on") self.turn_on(self.boolean_connect) def reset_booleans(self): ''' Do not include "boolean_connect" here! - NOT HERE: self.turn_off(self.boolean_connect) That should be turned off directly when needed ''' self.turn_off(self.boolean_load_pl) self.turn_off(self.boolean_sync) self.turn_off(self.boolean_power) self.turn_off(self.boolean_next) self.turn_off(self.boolean_prev) def login(self): _login = self.args["login_type"] if _login == 'oauth': self.oauth_login() elif _login == 'legacy': self.legacy_login() else: self.turn_off(self.boolean_connect) raise SystemExit("Invalid login_type: {}".format(_login)) def legacy_login(self): ''' This legacy login may stop working at any time ''' authtoken_path = self.args["authtoken_path"] + "gmusic_authtoken" email = self.args["user"] password = self.args["password"] device_id = self.args["device_id"] if os.path.isfile(authtoken_path): with open(authtoken_path, 'rb') as handle: authtoken = pickle.load(handle) else: authtoken = None self._api_login = self.gmc.login(email, password, device_id, authtoken) if not self._api_login: self.turn_off(self.boolean_connect) raise SystemExit("legacy login failed") with open(authtoken_path, 'wb') as f: pickle.dump(self.gmc.session._authtoken, f) def oauth_login(self): if self.args["oauth_credentials"]: try: self._api_login = self.gmc.oauth_login( device_id=self.args["device_id"], oauth_credentials=self.args["oauth_credentials"], locale=self.args["locale"]) except: self.turn_off(self.boolean_connect) raise SystemExit("oauth login failed") def connect(self, entity, attribute, old, new, kwargs): _connect = self.get_state(self.boolean_connect) self.reset_booleans() if _connect == "on": self.login() if self._api_login == True: self.sync(entity=None, attribute=None, old=None, new=None, kwargs=None) else: ## This will not trigger when app level constaint is set on the input_boolean self.gmusic_api_logout(entity=None, attribute=None, old=None, new=None, kwargs=None) def power(self, entity, attribute, old, new, kwargs): self._power = self.get_state(self.boolean_power) if self._power == "on": self.select_media_player() self.log("Powered ON -- Connected: {}".format(self._player_id)) elif self._power == "off": self.unselect_media_player() self.log("Powered OFF -- Disconnected: {}".format(self._player_id)) def select_media_player(self): self._player_id = "media_player." + self.get_state(self.select_player) ## Set callbacks for media player self.power_off = self.listen_state(self.power, self.boolean_power, new="off", duration="1") self.advance_track = self.listen_state(self.get_track, self._player_id, new="idle", duration="2") self.show_meta = self.listen_state(self.show_info, self._player_id, new="playing") self.clear_meta = self.listen_state(self.clear_info, self._player_id, new="off", duration="1") def unselect_media_player(self): self.media_player_off(entity=None, attribute=None, old=None, new=None, kwargs=None) try: ## Cancel callbacks for media player self.cancel_listen_state(self.power_off) self.cancel_listen_state(self.advance_track) self.cancel_listen_state(self.show_meta) self.cancel_listen_state(self.clear_meta) except: self.log("cancel callback exception!") pass def update_playlists(self, entity, attribute, old, new, kwargs): self._playlist_to_index = {} self._playlists = self.gmc.get_all_user_playlist_contents() idx = -1 for playlist in self._playlists: idx = idx + 1 name = playlist.get('name', ' ') if len(name) < 1: continue self._playlist_to_index[name] = idx # self.log("Playlist: {} - {}".format(idx, name)) data = list(self._playlist_to_index.keys()) self.call_service("input_select/set_options", entity_id=self.select_playlist, options=data) self.turn_off(self.boolean_sync) self.log("--------------------------------------------") self.log(data) for _pl in self._playlist_to_index: _num = self._playlist_to_index.get(_pl) + 1 self.log("{}: {}".format(_num, _pl)) self.log("--------------------------------------------") def update_stations(self, entity, attribute, old, new, kwargs): self._station_to_index = {} self._stations = self.gmc.get_all_stations() idx = -1 for station in self._stations: idx = idx + 1 name = station.get('name', ' ') library = station.get('inLibrary') if len(name) < 1: continue if library == True: self._station_to_index[name] = idx # self.log("station: {} - {}: Library = {}".format(idx, name, library)) data = list(self._station_to_index.keys()) data.insert(0, "I'm Feeling Lucky") self.call_service("input_select/set_options", entity_id=self.select_station, options=data) self.turn_off(self.boolean_sync) self.log("--------------------------------------------") self.log(data) for _pl in self._station_to_index: _num = self._station_to_index.get(_pl) + 1 self.log("{}: {}".format(_num, _pl)) self.log("--------------------------------------------") def sync(self, entity, attribute, old, new, kwargs): self.update_playlists(entity=None, attribute=None, old=None, new=None, kwargs=None) self.update_stations(entity=None, attribute=None, old=None, new=None, kwargs=None) def set_source(self, entity, attribute, old, new, kwargs): self._source = self.get_state(self.select_source) def get_tracks(self, entity, attribute, old, new, kwargs): if self._source == 'Station': self.load_station(entity=None, attribute=None, old=None, new=None, kwargs=None) elif self._source == 'Playlist': self.load_playlist(entity=None, attribute=None, old=None, new=None, kwargs=None) else: self.log("invalid source: {}".format(self._source)) self.reset_booleans() def load_playlist(self, entity, attribute, old, new, kwargs): self.turn_on(self.boolean_power) self._playlist_id = self.get_state(self.select_playlist) idx = self._playlist_to_index.get(self._playlist_id) self._tracks = self._playlists[idx]['tracks'] random.shuffle(self._tracks) self.log("--------------------------------------------") self.log("Loading [{}] Tracks From: {}".format(len(self._tracks), self._playlist_id)) # self.log(self._tracks) self.log("--------------------------------------------") self._next_track_no = -1 self.get_track(entity=None, attribute=None, old=None, new=None, kwargs=None) self.turn_off(self.boolean_load_pl) def load_station(self, entity, attribute, old, new, kwargs): self.turn_on(self.boolean_power) self._station_id = self.get_state(self.select_station) if self._station_id == "I'm Feeling Lucky": self._tracks = self.gmc.get_station_tracks('IFL', num_tracks=100) else: idx = self._station_to_index.get(self._station_id) id = self._stations[idx]['id'] self._tracks = self.gmc.get_station_tracks(id, num_tracks=100) self.log("--------------------------------------------") self.log("Loading [{}] Tracks From: {}".format(len(self._tracks), self._station_id)) # self.log(self._tracks) self.log("--------------------------------------------") self._next_track_no = -1 self.get_track(entity=None, attribute=None, old=None, new=None, kwargs=None) self.turn_off(self.boolean_load_pl) def get_track(self, entity, attribute, old, new, kwargs): self._track = '' self._next_track_no = self._next_track_no + 1 if self._next_track_no >= len(self._tracks): self._next_track_no = 0 ## Restart curent playlist (Loop) random.shuffle(self._tracks) ## (re)Shuffle on Loop _track = self._tracks[self._next_track_no] if _track is None: self.reset_booleans() if 'track' in _track: _track = _track['track'] self._track = _track if 'trackId' in _track: _uid = _track['trackId'] elif 'storeId' in _track: _uid = _track['storeId'] elif 'id' in _track: _uid = _track['id'] else: self.log("TRACK ID NOT FOUND!") self.play_track(_uid) def play_track(self, uid): try: _url = self.gmc.get_stream_url(uid) self.call_service("media_player/play_media", entity_id=self._player_id, media_content_id=_url, media_content_type=self.args["media_type"]) except: self.log(" --- FAILED TO PLAY TRACK --- ") self.log("uid: {}".format(uid)) self.log("_url: {}".format(_url)) # self.get_track(entity=None,attribute=None,old=None,new=None,kwargs=None) def next_track(self, entity, attribute, old, new, kwargs): if new == 'on': self.get_track(entity=None, attribute=None, old=None, new=None, kwargs=None) self.turn_off(self.boolean_next) def prev_track(self, entity, attribute, old, new, kwargs): if new == 'on': self._next_track_no = self._next_track_no - 2 self.get_track(entity=None, attribute=None, old=None, new=None, kwargs=None) self.turn_off(self.boolean_prev) def show_info(self, entity, attribute, old, new, kwargs): _attr = {} _track = self._track if 'artist' in _track: _attr['media_artist'] = _track['artist'] if 'album' in _track: _attr['media_album_name'] = _track['album'] if 'title' in _track: _attr['media_title'] = _track['title'] if 'albumArtRef' in _track: _album_art_ref = _track['albumArtRef'] ## returns a list _attr['entity_picture'] = _album_art_ref[0]['url'] ## of dic if 'artistArtRef' in _track: _artist_art_ref = _track['artistArtRef'] self.artist_art = _artist_art_ref[0]['url'] if _attr: self.set_state(self._player_id, attributes=_attr) def clear_info(self, entity, attribute, old, new, kwargs): if new != 'playing' or new != 'paused': self.set_state(self._player_id, attributes={ "media_title": '', "media_artist": '', "media_album_name": '', "entity_picture": '' }) def media_player_off(self, entity, attribute, old, new, kwargs): self.call_service("media_player/turn_off", entity_id=self._player_id) def gmusic_api_logout(self, entity, attribute, old, new, kwargs): self.gmc.logout() self.reset_booleans()
_temp_pl = _v.copy() _temp_pl['tracks'] = [self.lookup_track(_t_id) for _t_id in _v['tracks']] self._detailed_playlists[_k] = _temp_pl keepers = ['Altivo', 'Runaway', 'Drives', 'Altivo 2.0', 'Slytherin', 'Now Showing: Anywhere But Here', 'Altivo 2.5', 'HELL YEAH', 'Angst 2.0'] api = Mobileclient() login_success = api.login(__CRED['GMusic']['username'], __CRED['GMusic']['password'], Mobileclient.FROM_MAC_ADDRESS) assert login_success new_col = GPlaylistCollection() for playlist in api.get_all_user_playlist_contents(): if playlist.get('name') in keepers: new_col.add_playlist(playlist) for s in api.get_all_songs(): if s.get('id') in new_col.all_track_ids: # This track belongs to one of the playlists new_col.define_track(s) new_col.build_detailed_playlists() found_tracks = [] for k_id in new_col.detailed_playlists.keys(): cur_pl = new_col.detailed_playlists.get(k_id) cur_pl_name = cur_pl.get('name')
class MusicLibrary(object): """This class reads information about your Google Play Music library""" def __init__(self, username=None, password=None, true_file_size=False, scan=True, verbose=0): self.verbose = False if verbose > 1: self.verbose = True self.__login_and_setup(username, password) self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__gartists = {} self.__albums = [] # [Album(), ...] self.__galbums = {} self.__tracks = {} self.__playlists = {} if scan: self.rescan() self.true_file_size = true_file_size def rescan(self): """Scan the Google Play Music library""" self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__gartists = {} self.__albums = [] # [Album(), ...] self.__galbums = {} self.__tracks = {} self.__playlists = {} self.__aggregate_albums() def __login_and_setup(self, username=None, password=None): # If credentials are not specified, get them from $HOME/.gmusicfs if not username or not password: cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs') 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) self.config = ConfigParser.ConfigParser() self.config.read(cred_path) username = self.config.get('credentials', 'username') password = self.config.get('credentials', 'password') global deviceId deviceId = self.config.get('credentials', 'deviceId') if not username or not password: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not deviceId: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) if deviceId.startswith("0x"): deviceId = deviceId[2:] self.api = GoogleMusicAPI(debug_logging=self.verbose) log.info('Logging in...') self.api.login(username, password, deviceId) log.info('Login successful.') def __set_key_from_ginfo(self, track, ginfo, key, to_key=None): """Set track key from either album_info or artist_info""" if to_key is None: to_key = key try: int_key = int(key) except ValueError: int_key = None if (not track.has_key(key) or track[key] == "" or int_key == 0) and ginfo.has_key(to_key): track[key] = ginfo[to_key] return track def __aggregate_albums(self): """Get all the tracks and playlists in the library, parse into relevant dicts""" log.info('Gathering track information...') tracks = self.api.get_all_songs() for track in tracks: log.debug('track = %s' % pp.pformat(track)) # Get album and artist information from Google if track.has_key('albumId'): if self.__galbums.has_key(track['albumId']): album_info = self.__galbums[track['albumId']] else: log.info("Downloading album info for %s '%s'", track['albumId'], track['album']) try: album_info = self.__galbums[track['albumId']] = self.api.get_album_info(track['albumId'], include_tracks=False) except gmusicapi.exceptions.CallFailure: log.exception("Failed to download album info for %s '%s'", track['albumId'], track['album']) #album_info = {} if album_info.has_key('artistId') and len(album_info['artistId']) > 0 and album_info['artistId'][0] != "": artist_id = album_info['artistId'][0] if self.__gartists.has_key(artist_id): artist_info = self.__gartists[artist_id] else: log.info("Downloading artist info for %s '%s'", artist_id, album_info['albumArtist']) #if album_info['albumArtist'] == "Various": # print album_info artist_info = self.__gartists[artist_id] = self.api.get_artist_info(artist_id, include_albums=False, max_top_tracks=0, max_rel_artist=0) else: artist_info = {} else: album_info = {} artist_info = {} track = self.__set_key_from_ginfo(track, album_info, 'album', 'name') track = self.__set_key_from_ginfo(track, album_info, 'year') track = self.__set_key_from_ginfo(track, artist_info, 'albumArtist', 'name') # Prefer the album artist over the track artist if there is one artist_name = formatNames(track['albumArtist']) if artist_name.strip() == '': artist_name = formatNames(track['artist']) if artist_name.strip() == '': artist_name = 'Unknown' # Get the Artist object, or create one if it doesn't exist artist = self.__artists.get(artist_name.lower(), None) if not artist: artist = Artist(self, artist_name) self.__artists[artist_name.lower()] = artist # Get the Album object, or create one if it doesn't exist album = artist.get_album(formatNames(track['album'])) if not album: album = Album(self, track['album']) self.__albums.append(album) # NOTE: Current no purpose other than to count artist.add_album(album) # Add track to album album.add_track(track) # Add track to list of all tracks, indexable by track ID if 'id' in track: self.__tracks[track['id']] = track log.info('%d tracks loaded.' % len(tracks)) log.info('%d artists loaded.' % len(self.__artists)) log.info('%d albums loaded.' % len(self.__albums)) # Add all playlists playlists = self.api.get_all_user_playlist_contents() for pldata in playlists: playlist = Playlist(self, pldata) self.__playlists[playlist.dirname.lower()] = playlist log.debug('%d playlists loaded.' % len(self.__playlists)) def get_artists(self): """Return all artists in the library""" return self.__artists def get_artist(self, name): """Return the artist from the library with the specified name""" return self.__artists.get(name.lower(), None) def get_playlists(self): """Return list of all playlists in the library""" return self.__playlists.values() def get_playlist(self, name): """Return the playlist from the library with the specified name""" return self.__playlists.get(name.lower(), None) def get_track(self, trackid): """Return the track from the library with the specified track ID""" return self.__tracks.get(trackid, None) def cleanup(self): pass
rfcTitle = data[['artist','title','energy','tempo','danceability','artist_discovery','speechiness','year','duration','trackType','acousticness','liveness','loudness','time_signature','valence','id','albumArtRef','storeId']] rfcTitle = rfcTitle.dropna(subset = filter(lambda x: x != "albumArtRef", rfcTitle.columns)) rfc = rfcTitle[['energy','tempo','danceability','artist_discovery','speechiness','year','trackType','acousticness','liveness','loudness','time_signature','valence']] km = pickle.load(open("/home/drew/tempo_scripts/tempo_model.p", "rb" )) klbls = km.labels_ rfcTitle['label'] = klbls results = [] for index, value in enumerate(rfcTitle.values): results.append({'artist': value[0], 'title': value[1], 'energy': value[2], 'tempo': value[3], 'danceability': value[4], 'artist_discovery': value[5], 'speechiness': value[6], 'year': value[7], 'duration': value[8], 'trackType': value[9], 'acousticness': value[10], 'liveness': value[11], 'loudness': value[12], 'time_signature': value[13], 'valence': value[14], 'cluster': value[18], 'id': value[15], 'albumArtRef': value[16], 'storeId': value[17]}) #playlists = api.get_all_playlists() #print json.dumps(playlists, indent=4, separators=(',', ': ')) plsongs = api.get_all_user_playlist_contents() #print json.dumps(plsongs, indent=4, separators=(',', ': ')) data = pd.read_csv("/home/drew/gpmusic_fixed.csv") ret = {'results': []} for i,v in enumerate(plsongs[0]['tracks']): for i2,v2 in enumerate(results): #print v2['storeId'], "==", plsongs[i]['track']['storeId'] if v2['id'] == v['trackId']: ret['results'].append(v2) break api.remove_entries_from_playlist("1c5aa722-db07-3801-8966-54b6cef43513") print json.dumps(plsongs, indent=4, separators=(',', ': ')) print json.dumps(ret, indent=4, separators=(',', ': '))
class MusicSync(object): def __init__(self, email=None, password=None): self.mm = Musicmanager() self.wc = Webclient() self.mc = Mobileclient() if not email: email = raw_input("Email: ") if not password: password = getpass() self.email = email self.password = password self.logged_in = self.auth() print "Fetching playlists from Google..." self.playlists = self.mc.get_all_user_playlist_contents() #self.playlists = self.mc.get_all_playlists() #self.playlists = self.wc.get_all_playlist_ids(auto=False) self.all_songs = self.mc.get_all_songs() #print "Got %d playlists." % len(self.playlists['user']) print "Got %d playlists containing %d songs." % (len(self.playlists), len(self.all_songs)) print "" def auth(self): self.logged_in = self.mc.login(self.email, self.password) #self.logged_in = self.wc.login(self.email, self.password) if not self.logged_in: print "Login failed..." exit() print "" print "Logged in as %s" % self.email print "" if not os.path.isfile(OAUTH_FILEPATH): print "First time login. Please follow the instructions below:" self.mm.perform_oauth() self.logged_in = self.mm.login() if not self.logged_in: print "OAuth failed... try deleting your %s file and trying again." % OAUTH_FILEPATH exit() print "Authenticated" print "" def sync_playlist(self, filename, remove_missing): #def sync_playlist(self, filename, remove_missing=False): filename = self.get_platform_path(filename) os.chdir(os.path.dirname(filename)) title = os.path.splitext(os.path.basename(filename))[0] print "Syncing playlist: %s" % filename #if title not in self.playlists['user']: #print " didn't exist... creating..." #self.playlists['user'][title] = [self.wc.create_playlist(title)] print "" plid = "" for pl in self.playlists: if pl['name'] == title: plid = pl['id'] goog_songs = pl['tracks'] if plid == "": print " didn't exist... creating..." plid = self.mc.create_playlist(self, title) #plid = self.playlists['user'][title][0] #goog_songs = self.wc.get_playlist_songs(plid) print "%d songs already in Google Music playlist" % len(goog_songs) pc_songs = self.get_files_from_playlist(filename) print "%d songs in local playlist" % len(pc_songs) print "" # Sanity check max 1000 songs per playlist if len(pc_songs) > MAX_SONGS_IN_PLAYLIST: print " Google music doesn't allow more than %d songs in a playlist..." % MAX_SONGS_IN_PLAYLIST print " Will only attempt to sync the first %d songs." % MAX_SONGS_IN_PLAYLIST del pc_songs[MAX_SONGS_IN_PLAYLIST:] existing_files = 0 added_files = 0 failed_files = 0 removed_files = 0 fatal_count = 0 for fn in pc_songs: if self.file_already_in_list(fn, goog_songs, self.all_songs): existing_files += 1 continue print "" print "Adding: %s" % os.path.basename(fn).encode('cp1252') #print "Adding: %s" % os.path.basename(fn) #online = False online = self.find_song(fn, goog_songs, self.all_songs) #online = self.find_song(fn) song_id = None if online: song_id = online['id'] print " already uploaded [%s]" % song_id else: attempts = 0 result = [] while not result and attempts < MAX_UPLOAD_ATTEMPTS_PER_FILE: print " uploading... (may take a while)" attempts += 1 try: result = self.mm.upload(fn) except (BadStatusLine, CannotSendRequest): # Bail out if we're getting too many disconnects if fatal_count >= MAX_CONNECTION_ERRORS_BEFORE_QUIT: print "" print "Too many disconnections - quitting. Please try running the script again." print "" exit() print "Connection Error -- Reattempting login" fatal_count += 1 self.wc.logout() self.mc.logout() self.mm.logout() result = [] time.sleep(STANDARD_SLEEP) except: result = [] time.sleep(STANDARD_SLEEP) try: if result[0]: song_id = result[0].itervalues().next() else: song_id = result[1].itervalues().next() print " upload complete [%s]" % song_id except: print " upload failed - skipping" tag = self.get_id3_tag(fn) print " failed song:\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) if not song_id: failed_files += 1 continue added = self.mc.add_songs_to_playlist(plid, song_id) time.sleep(.3) # Don't spam the server too fast... print " done adding to playlist" added_files += 1 if remove_missing: for g in goog_songs: for s in self.all_songs: if g['trackId'] == s['id']: print "" print "Removing: %s" % s['title'].encode('cp1252') self.mc.remove_entries_from_playlist(g['id']) #self.wc.remove_songs_from_playlist(plid, s.id) time.sleep(.3) # Don't spam the server too fast... removed_files += 1 print "" print "---" print "%d songs unmodified" % existing_files print "%d songs added" % added_files print "%d songs failed" % failed_files print "%d songs removed" % removed_files def get_files_from_playlist(self, filename): files = [] f = codecs.open(filename, encoding='cp1252') #f = codecs.open(filename, encoding='utf-8') for line in f: line = line.rstrip().replace(u'\ufeff',u'') if line == "" or line[0] == "#": continue path = os.path.abspath(self.get_platform_path(line)) if not os.path.exists(path): print "File not found: %s" % line continue files.append(path) f.close() return files def file_already_in_list(self, filename, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) i = 0 while i < len(goog_songs): for s in all_songs: if goog_songs[i]['trackId'] == s['id']: if self.tag_compare(s, tag): print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) goog_songs.pop(i) return True i += 1 return False def get_id3_tag(self, filename): data = mutagen.File(filename, easy=True) r = {} if 'title' not in data: title = os.path.splitext(os.path.basename(filename))[0] print 'Found song with no ID3 title, setting using filename:' print ' %s' % title print ' (please note - the id3 format used (v2.4) is invisible to windows)' data['title'] = [title] data.save() r['title'] = data['title'][0] r['track'] = int(data['tracknumber'][0].split('/')[0]) if 'tracknumber' in data else 0 # If there is no track, try and get a track number off the front of the file... since thats # what google seems to do... # Not sure how google expects it to be formatted, for now this is a best guess if r['track'] == 0: m = re.match("(\d+) ", os.path.basename(filename)) if m: r['track'] = int(m.group(0)) r['artist'] = data['artist'][0] if 'artist' in data else '' r['album'] = data['album'][0] if 'album' in data else '' return r def find_song(self, filename, goog_songs, all_songs): tag = self.get_id3_tag(filename) print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252')) #results = self.wc.search(tag['title']) # NOTE - diagnostic print here to check results if you're creating duplicates #print results['song_hits'] #for r in goog_songs: #for r in results['song_hits']: for s in all_songs: #if r['trackId'] == s['id']: if self.tag_compare(s, tag): # TODO: add rough time check to make sure its "close" print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252')) return s return None def tag_compare(self, g_song, tag): # If a google result has no track, google doesn't return a field for it if 'title' not in g_song: g_song['title'] = "" if 'artist' not in g_song: g_song['artist'] = "" if 'album' not in g_song: g_song['album'] = "" if 'track' not in g_song: g_song['track'] = 0 if (g_song['title'].lower() == tag['title'].lower() and g_song['artist'].lower() == tag['artist'].lower()) or\ (g_song['album'].lower() == tag['album'].lower() and g_song['title'].lower() == tag['title'].lower()) or\ (g_song['artist'].lower() == tag['artist'].lower() and g_song['album'].lower() == tag['album'].lower() and g_song['track'] == tag['track']): print "Partial match\t%s\t%s\t%s" % (g_song['title'].encode('cp1252'), g_song['artist'].encode('cp1252'), g_song['album'].encode('cp1252')) return g_song['title'].lower() == tag['title'].lower() and\ g_song['artist'].lower() == tag['artist'].lower() and\ g_song['album'].lower() == tag['album'].lower() #and\ #g_song['track'] == tag['track'] def delete_song(self, sid): self.mc.delete_songs(sid) print "Deleted song by id [%s]" % sid def get_platform_path(self, full_path): # Try to avoid messing with the path if possible if os.sep == '/' and '\\' not in full_path: return full_path if os.sep == '\\' and '\\' in full_path: return full_path if '\\' not in full_path: return full_path return os.path.normpath(full_path.replace('\\', '/'))
# remove the table header. del table[0] song = Song() api = Mobileclient() logged_in = api.login(args.u, args.p) if args.li: count = min(len(table), args.li) else: count = len(table) if logged_in: playLists = api.get_all_user_playlist_contents() playlist = next(p for p in playLists if p['name'] == args.pl) for i in range(0, count): column = table[i].xpath('td') """SomaFM prints breaks and in the schedule so we only want rows in the table that are a track""" if len(column) == 5: song.artist = column[1].xpath('a')[0].text song.songName = column[2].text song.album = column[3].xpath('a')[0].text result = api.search_all_access(song.artist + " " + song.songName, 1) if result['song_hits']:
class GoogleMusicManager(object): def __init__(self): """Default construct""" #Main management Script #This will scrape that data from a users account, peramiters can be set for downloading certains songs or entire playlists #metadata is preserved as well as playlist or albumn structure self.api = [] self.activeSongList = [] self.activePlayList = [] def Login(self, userName, userPassWord): """Login method to access you google play account""" self.api = Mobileclient() self.api.logout() #reload(MusicLibraryManager) if (self.api.login(userName, userPassWord, Mobileclient.FROM_MAC_ADDRESS)): print("You have been logged in") else: print("Seems the login is incorect") def getAllRadioStations(self): """This will get all of the radiostations that I have saved""" print("Collecting Radio Station Contents") #Generate base directory #check to see if a playlist directory exists baseDir = os.getcwd() + '/RadioStations' #if not os.path.exists(baseDir): # raise ValueError(baseDir + " does not exist") self.activeRadioStations = self.api.get_all_stations() for Station in self.activeRadioStations: print("Found a RadioStation called : " + Station['name']) #get radioContent RadioStationSongs = self.api.get_station_tracks( Station['id'], 1000, {}) print("It contains " + str(len(RadioStationSongs)) + " Songs") print("downloading songs") stationName = Station['name'] #create a dir for the radiostation activeDir = baseDir + "/" + stationName + "/" #check to see if the radio station folder exists if not os.path.exists(activeDir): #radiostation doesn't exist so we create it os.makedirs(activeDir) for Song in RadioStationSongs: print(str(Song)) if not os.path.exists(activeDir + Song['title'] + ".mp3"): #it doesn't exists in the playlist yet, so we need to add it songTitle = Song['title'] streamUrl = self.api.get_stream_url(Song['storeId']) urllib.urlretrieve(streamUrl, activeDir + songTitle + '.mp3') song = eyed3.load(activeDir + songTitle + '.mp3') song.initTag() song.tag.save() song = eyed3.load(activeDir + songTitle + '.mp3') if "title" in Song: song.tag.title = Song['title'] if "artist" in Song: song.tag.artist = Song['artist'] if "album" in Song: song.tag.album = Song['album'] #song.tag.year = currentSong['year'] if "genre" in song.tag: song.tag.genre = Song['genre'] if "trackNumber" in Song: song.tag.trackNumber = Song['trackNumber'] #song.tag.album = song['album'] #print song.tag.version song.tag.save() #print song.tag #for song in self.activeSongList: def getAllPlaylists(self): """Gets all of the users playlists that they have created""" print("Getting all PLaylist contents") self.activePlayList = self.api.get_all_user_playlist_contents() baseDir = os.getcwd() + '/Playlists' for Playlist in self.activePlayList: playlistName = Playlist['name'] print(playlistName) playlistSongs = Playlist['tracks'] activeDir = baseDir + '/' + playlistName + '/' if not os.path.exists(activeDir): os.makedirs(activeDir) for Song in playlistSongs: #print Song if 'track' in Song: activeSong = Song['track'] print('getting song : ' + activeSong['title']) if not os.path.exists(activeDir + activeSong['title'] + 'mp3'): #it doesn't exists in the playlist yet, so we need to add it songTitle = activeSong['title'] streamUrl = self.api.get_stream_url( activeSong['storeId']) urllib.request.urlretrieve( streamUrl, activeDir + songTitle + '.mp3') song = eyed3.load(activeDir + songTitle + '.mp3') song.initTag() song.tag.save() song = eyed3.load(activeDir + songTitle + '.mp3') if "title" in activeSong: song.tag.title = activeSong['title'] if "artist" in activeSong: song.tag.artist = activeSong['artist'] if "album" in activeSong: song.tag.album = activeSong['album'] #song.tag.year = currentSong['year'] if "genre" in activeSong: song.tag.genre = activeSong['genre'] if "trackNumber" in activeSong: song.tag.trackNumber = activeSong['trackNumber'] #song.tag.album = song['album'] #print song.tag.version song.tag.save() def getAllSongs(self): """Get all songs that the user has listen too that are associated with the account""" print("Getting all Songs") self.activeSongList = self.api.get_all_songs(False) #print str(self.activeSongList.count) baseDir = os.getcwd() + '/Music' print("Grabbed " + str(len(self.activeSongList)) + " Songs") #smallList = self.activeSongList[:3] for currentSong in self.activeSongList: #currentSong = self.activeSongList[0] #create a location sting for where the song will be saved. #check artist path artistPath = currentSong['artist'].strip().replace('/', '') albumPath = currentSong['album'].strip().replace('/', '') songTitle = currentSong['title'].replace('/', '') print(artistPath + " " + albumPath) songPath = '/' + artistPath + '/' + albumPath + "/" if os.path.exists(baseDir + songPath): print("Folder Structure exists") else: print("Folder Structure does not exist") if not os.path.exists(artistPath): os.makedirs(baseDir + songPath) print("created artitst and alartistPathbumn folder") else: os.makedirs(baseDir + songPath) print(os.path.isfile(baseDir + songPath + songTitle + '.mp3')) print(baseDir + songPath + songTitle + '.mp3') if not os.path.isfile(baseDir + songPath + songTitle + '.mp3'): #get stream url streamUrl = self.api.get_stream_url(currentSong['id']) urllib.request.urlretrieve( streamUrl, baseDir + songPath + songTitle + '.mp3') song = eyed3.load(baseDir + songPath + songTitle + '.mp3') song.initTag() song.tag.save() song = eyed3.load(baseDir + songPath + songTitle + '.mp3') song.tag.title = currentSong['title'] song.tag.artist = currentSong['artist'] song.tag.album = currentSong['album'] #song.tag.year = currentSong['year'] if 'genre' in currentSong: song.tag.genre = currentSong['genre'] song.tag.trackNumber = currentSong['trackNumber'] #song.tag.album = song['album'] #print song.tag.version song.tag.save() #print song.tag #for song in self.activeSongList: else: song = eyed3.load(baseDir + songPath + songTitle + '.mp3') song.initTag() song.tag.save() song = eyed3.load(baseDir + songPath + songTitle + '.mp3') song.tag.title = currentSong['title'] song.tag.artist = currentSong['artist'] song.tag.album = currentSong['album'] if 'genre' in currentSong: song.tag.genre = currentSong['genre'] #song.tag.year = currentSong['year'] song.tag.trackNumber = currentSong['trackNumber'] #song.tag.album = song['album'] #print song.tag.version song.tag.save() def Logout(self): self.api.logout() def updateSongs(self): """Update locally stored music with any newly listened to or added""" def getPlaylist(self, playlistName): """Gets a specific playlist by Name""" print("Getting " + playlistName)
class MusicPlayer(object): def __init__(self): self.playlist = [] # Array of all tracks self.playlist_id = 0 # Id of playlist self.current_track_index = 0 # Index of current song self.player = Player() # MPlayer instance self.webclient = Webclient() # Client for WebInterface self.mobileclient = Mobileclient() # Client for MobileInterface self.timer = None # Timer to start next track self.deviceid = 0 # DeviceId to use self.playtype = PlayType.LINEAR # LINEAR or SHUFFLE def login(self, username, password): """ Login to Google Music. Keyword arguments: username -- the username password -- the password Returns: True if successful else False """ # If either the web client or the mobile client failed to login return False if not self.webclient.login(username, password) or not self.mobileclient.login(username, password): return False # Use first found devices as ID devices = self.webclient.get_registered_devices(); # Convert HEX to INT self.deviceid = int(devices[0]['id'], 16) return True def load_playlist(self, playlist_name): # Load playlist for playlist in self.mobileclient.get_all_user_playlist_contents(): if playlist['name'] == playlist_name: for track_obj in playlist['tracks']: track_obj['track']['id'] = track_obj['id'] self.playlist.append(track_obj['track']) # Set playlist_id self.playlist_id = playlist['id'] break; # If playlist has not been found, create it if self.playlist_id == 0: self.playlist_id = self.mobileclient.create_playlist(playlist_name) def add_track_to_playlist(self, track): """ Append a track to the end of playlist Keyword arguments: track -- a dictionary containing the track informations """ track_id = self.mobileclient.add_songs_to_playlist(self.playlist_id, track['nid'])[0] track['id'] = track_id self.playlist.append(track) # Notify all clients about the new track factory.forwarder.dispatch(PLAYLIST_EVENT_TRACK_ADDED, json.dumps(track)) def remove_track_from_playlist(self, track_id): """ Removes a track from the playlist Keyword arguments: track_id -- The id of the track to remove """ self.mobileclient.remove_entries_from_playlist(track_id) index_to_remove = self._find_index_of_track_id(track_id) del self.playlist[index_to_remove] factory.forwarder.dispatch(PLAYLIST_EVENT_TRACK_REMOVED, track_id) def play_track(self, track_id): """ Play a track Keyword arguments: track_id -- Id of the track to play """ index_of_track = self._find_index_of_track_id(track_id) track_to_play = self.playlist[index_of_track] if track_to_play is not None: # Request stream url from google music stream_url = self.mobileclient.get_stream_url(track_to_play["storeId"], self.deviceid) # Load stream url to mplayer self.player.loadfile(stream_url) # For some reason OSX needs to unpause mplayer if sys.platform == "darwin": self.player.pause() # Set track self.current_track_index = index_of_track # Cancel previous timer if self.timer is not None: self.timer.cancel() # How many minutes does the track last track_duration = long(track_to_play["durationMillis"]) / 1000 # Set Timer to play next track when trackDuration is over self.timer = Timer(track_duration, self.play_next_track) self.timer.daemon = True self.timer.start() print "playing", track_to_play["artist"], " - ", track_to_play["title"], " : ", stream_url # Fire event that a new track is playing factory.forwarder.dispatch(TRACK_EVENT_PLAYBACK, json.dumps(track_to_play)) return True else: return False def play_next_track(self): """ Play the next track in the playlist. Returns: True or False """ if self.playtype == PlayType.LINEAR: # Index of next track to play next_track_index = self.current_track_index + 1 # Restart at index 0 if end of playlist is reached if next_track_index >= len(self.playlist): next_track_index = 0 elif self.playtype == PlayType.SHUFFLE: # Index of next track to play at random next_track_index = random.randrange(0, len(self.playlist), 1) # Obtain the id of the next track to play next_track_id = self.playlist[next_track_index]['id'] # Play track with that id return self.play_track(next_track_id) def play_previous_track(self): """ Play the previous track in the playlist. Returns: True or False """ if self.playtype == PlayType.LINEAR: # Index of previous track to play previous_track_index = self.current_track_index - 1 # Contiune from the end of the playlist if previous_track_index <= 0: previous_track_index = len(self.playlist) - 1 elif self.playtype == PlayType.SHUFFLE: # Index of the previous track is random previous_track_index = random.randrange(0, len(self.playlist), 1) # Obtain the id of the previous track to play previous_track_id = self.playlist[previous_track_index]['id'] # Play track with that id return self.play_track(previous_track_id) def stop(self): """ Stop playback. """ if self.timer is not None: self.timer.cancel() if self.player is not None: self.player.stop() def play(self): """ Start playing current track Returns: True if track has been started. Else False """ current_track_id = self.playlist[self.current_track_index] return self.play_track(current_track_id) def _find_index_of_track_id(self, track_id): index = 0 for track in self.playlist: if track['id'] == track_id: return index index += 1 return None
class PlaylistManager(object): def __init__(self, email_file): self.login(email_file) self._get_songs() self._get_playlists() def login(self, email_file): with open(os.path.expanduser(email_file), 'r') as gmail_email_file: gmail_email = gmail_email_file.read() print "Password for " + gmail_email.strip('\n') + ":" pw = getpass.getpass() self.client = Mobileclient() self.client.login(gmail_email, pw) def _get_songs(self): self.all_songs = self.client.get_all_songs() assert isinstance(self.all_songs, list) self.all_songs_by_id = {} for song in self.all_songs: self.all_songs_by_id[song['id']] = song def _playlist_entry_dict(self, entry_dict, playlist_name): track_dict = self.all_songs_by_id[entry_dict['trackId']] track_hash = hash(playlist_name + track_dict['title'] + track_dict['artist'] + track_dict['album']) return {'title': track_dict['title'], 'entry_id': entry_dict['id'], 'track_id': entry_dict['trackId'], 'hash': track_hash} def _get_playlists(self): self.all_playlists = self.client.get_all_user_playlist_contents() assert isinstance(self.all_playlists, list) def sort_and_deduplicate_playlists(self): """de-duplicates and then sorts all user playlists""" playlist_entry_ids_to_be_removed = [] for playlist in self.all_playlists: current_playlist = [] current_playlist_entry_hashes = [] for track in playlist['tracks']: if not track['deleted']: current_track = self._playlist_entry_dict(track, playlist['name']) if not current_track['hash'] in current_playlist_entry_hashes: current_playlist.append(current_track) current_playlist_entry_hashes.append(current_track['hash']) else: # we will remove these duplicates later, so they don't mess up our sorting playlist_entry_ids_to_be_removed.append(current_track['entry_id']) if playlist_entry_ids_to_be_removed != []: print "De-duplicated " + playlist['name'] get_track_title = lambda track_dict: track_dict['title'] sorted_songs = sorted(current_playlist, key=get_track_title) sorted_track_ids = [] sorted_entry_ids = [] for track in sorted_songs: sorted_track_ids.append(track['track_id']) sorted_entry_ids.append(track['entry_id']) # if the playlist is already sorted, don't bother sorting it if sorted_songs != current_playlist: # remove all things at once for maximum efficiency self.client.add_songs_to_playlist(playlist['id'], sorted_track_ids) playlist_entry_ids_to_be_removed += sorted_entry_ids print "Re-ordered " + playlist['name'] if playlist_entry_ids_to_be_removed != []: # remove all things to be removed at once self.client.remove_entries_from_playlist(playlist_entry_ids_to_be_removed) def export_songs(self, csv_output): """writes a csv file containing info on all the songs""" # these are the field names. More might exist, but I haven't found them field_names = [u'comment', u'rating', u'composer', u'year', u'creationTimestamp', u'id', u'album', u'totalDiscCount', u'title', u'recentTimestamp', u'albumArtist', u'trackNumber', u'discNumber', u'deleted', u'totalTrackCount', u'estimatedSize', u'beatsPerMinute', u'genre', u'playCount', u'kind', u'artist', u'lastModifiedTimestamp', u'clientId', u'durationMillis', u'albumArtRef', u'artistId', u'storeId', u'nid', u'albumId', u'artistArtRef', u'trackType', u'trackOrigin', u'contentType', u'lastRatingChangeTimestamp'] with open(csv_output, 'w') as output_file_obj: csv_writer = csv.DictWriter(output_file_obj, field_names) csv_writer.writeheader() for song in self.all_songs: for x in song: # remove the unicode, replacing bad characters with "?" if isinstance(song[x], unicode): song[x] = song[x].encode('ascii', 'replace') csv_writer.writerow(song) def export_playlists(self, output_path): """writes the csv contents of each playlist to the playlist-named file in the specified folder""" if not os.path.exists(output_path): os.mkdir(output_path) if not os.path.isdir(output_path): raise Exception("Not a folder!") playlist_entry_keys = [u'absolutePosition', u'clientId', u'creationTimestamp', u'deleted', u'id', u'kind', u'lastModifiedTimestamp', u'playlistId', u'source', u'trackId'] for playlist in self.all_playlists: file_name = os.path.join(output_path, playlist[u'name'] + '.csv') with open(file_name, 'w') as output_file: csv_writer = csv.DictWriter(output_file, playlist_entry_keys) csv_writer.writeheader() for track in playlist[u'tracks']: for x in track: # remove the unicode, replacing bad characters with "?" if isinstance(track[x], unicode): track[x] = track[x].encode('ascii', 'replace') csv_writer.writerow(track) def make_local_xspf_playlists(self): """write playlists in xspf format to named files in the playlist folder. Credit to https://github.com/alastair/xspf for the wonderful xspf python module""" # output_path = 'playlists' output_path = os.path.expanduser('~/Music/Playlists') music_path = os.path.expanduser('~/Music') extension = '.mp3' if not os.path.exists(output_path): os.mkdir(output_path) if not os.path.isdir(output_path): raise Exception("Not a folder!") for playlist in self.all_playlists: output_file_path = os.path.join(output_path, playlist[u'name'] + '.xspf') playlist_xspf = xspf.Xspf() playlist_xspf.title = playlist[u'name'] for track in playlist['tracks']: track_xspf = xspf.Track() track_id = track['trackId'] track_dict = self.all_songs_by_id[track_id] artist = track_dict['artist'] album = track_dict['album'] title = track_dict['title'] track_xspf.title = title track_xspf.album = album track_xspf.creator = artist # replace is to fix the slash in 'AC/DC' track_path = 'file://' + os.path.join(music_path, artist.replace('/', ','), album.replace('/', ','), title.replace('/', ',') + extension) track_xspf.location = track_path length = str(int(track_dict['durationMillis']) / 1000) track_xspf.duration = length playlist_xspf.add_track(track_xspf) with open(output_file_path, 'wb') as output_file: # we write this header so that Sublime Text recognizes it as an XML file, and sets syntax accordingly output_file.write('<?xml version="1.0" encoding="UTF-8"?>\n') output_file.write(playlist_xspf.toXml(pretty_print=True))
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()
from gmusicapi import Mobileclient import json client = Mobileclient() oauth = raw_input("first time?") if oauth == "y": client.perform_oauth() else: # todo: store oauth data in non-default client.oauth_login(Mobileclient.FROM_MAC_ADDRESS) playlists = client.get_all_user_playlist_contents() songs = [] # todo: allow playlist selection playlist = "Main" for p in playlists: if p["name"] == playlist: for track in p["tracks"]: try: song = client.get_track_info(track["trackId"]) song["entryId"] = track["id"] songs.append(song) except: print("Wasn't able to obtain information for one song") # with open("data.txt", "w") as outfile: # json.dump(songs, outfile)
class GMusicClient(ContentConsumer): """Element in charge of interfacing with GMusicApi Client""" def __init__(self, data_cache): self.client = Mobileclient() self.data_cache = data_cache def login(self): """Use data/unlocked/credentials.json to log in""" mac = Mobileclient.FROM_MAC_ADDRESS try: credentials = json.load(open("data/unlocked/credentials.json", "r")) result = self.client.login(credentials["username"], credentials["password"], mac) if result == False: print("\n\033[93mLogin failed.") print("Please double check that data/unlocked/credentials.json has correct information.\033[m\n") os._exit(1) except: print("\n\033[93mdata/unlocked/credentials.json is not valid.") print("You may need to delete and regnerate the credentials file.\033[m\n") exit(1) def load_my_library(self): """Load user's songs, playlists, and stations""" track_load = threading.Thread(target=self.load_tracks) radio_load = threading.Thread(target=self.load_radios) playlist_load = threading.Thread(target=self.load_playlists) track_load.start() radio_load.start() playlist_load.start() track_load.join() radio_load.join() playlist_load.join() def load_playlists(self): playlists = self.client.get_all_user_playlist_contents() playlists.reverse() self.data_cache.playlists = playlists def load_tracks(self): self.data_cache.tracks = [t for t in self.client.get_all_songs() if "nid" in t] def scrape_song(self, track): return track def load_radios(self): radios = self.client.get_all_stations() radios.reverse() self.data_cache.radios = radios def get_radio_contents(self, radio_id): tracks = self.client.get_station_tracks(radio_id) return tracks def get_radio_list(self, name): return [r for r in self.data_cache.radios if name in r["name"]] def filter(self, element, field, filter_by): return [e for e in element if filter_by in e[field]] def get_playlist_list(self, name): return self.filter(self.data_cache.playlists, "name", name) def search_all_access(self, query): return self.client.search_all_access(query) def create_radio(self, seed_type, id, name): """Create a radio""" # This is a weird way to do this, but there's no other choice ids = {"track": None, "album": None, "artist": None} seed_id_name = self.get_type_name(seed_type) ids[seed_id_name] = id return self.client.create_station( name=name, track_id=ids["track"], album_id=ids["album"], artist_id=ids["artist"] ) def search_items_all_access(self, type, query): """Searches Albums, Artists, and Songs; uses metaprogramming""" index_arguments = self.get_index_arguments(type) items = self.search_all_access(query)["{0}_hits".format(type[:-1])] return [self.format_item(item, type, index_arguments) for item in items] def get_sub_items(self, type_from, search_type, from_id): """Here type_from refers to artist or album we're indexing against""" args = self.get_index_arguments(search_type) if type_from == "playlist": return self.get_playlist_contents(from_id) else: # Get the appropriate search method and execute it search_method_name = "get_{0}_info".format(type_from) search_method = getattr(self.client, search_method_name) try: items = search_method(from_id, True)[args["type"] + "s"] # True here includes subelements except: # User passed in a bad id or something return # Now return appropriately return [self.format_subitems(t, args) for t in items if args["id"] in t] def get_playlist_contents(self, from_id): """Playlist exclusive stuff""" shareToken = [t for t in self.data_cache.playlists if t["id"] == from_id][0]["shareToken"] contents = self.client.get_shared_playlist_contents(shareToken) return [self.format_playlist_contents(t) for t in contents if "track" in t] def get_suggested(self): """Returns a list of tracks that the user might be interested in""" items = sorted(self.client.get_promoted_songs(), key=lambda y: y["title"]) return [self.format_suggested(t) for t in items if "storeId" in t] def get_information_about(self, from_type, from_id): """Gets specific information about an id (depending on the type)""" if "artist" in from_type.lower(): return self.client.get_artist_info(from_id, include_albums=False) if "album" in from_type.lower(): return self.client.get_album_info(from_id, include_tracks=False) return self.client.get_track_info(from_id) def get_stream_url(self, nid): return self.client.get_stream_url(nid) def lookup(self, nid): return self.client.get_track_info(nid) def add_track_to_library(self, nid): self.client.add_aa_track(nid) def add_to_playlist(self, playlist_id, nid): self.client.add_songs_to_playlist(playlist_id, nid) playlist_load = threading.Thread(target=self.load_playlists) playlist_load.daemon = True playlist_load.start() def format_suggested(self, t): return (t["title"], t["storeId"], "Play", t["album"]) def format_playlist_contents(self, t): return (t["track"]["title"], t["trackId"], "Play", t["track"]["album"]) def format_subitems(self, t, args): return (t[args["name"]], t[args["id"]], args["command"], t[args["alt"]])
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)
if migration_type != 'stations': log.info('Retrieving tracks from ' + export_username) export_tracks = export_api.get_all_songs() # strip out any tracks that are not available on All Access all_tracks = [t for t in export_tracks if track_has_aa_data(t) and not t.get('deleted')] if migration_type == 'all' or migration_type == 'ratings': log.info('Retrieving thumbs up tracks from ' + export_username) export_thumbs_up = export_api.get_thumbs_up_songs() # strip out any tracks that are not available on All Access thumbs_up_tracks = [t for t in export_thumbs_up if track_has_aa_data(t)] if migration_type == 'all' or migration_type == 'playlists': log.info('Retrieving playlists from ' + export_username) export_playlists = export_api.get_all_user_playlist_contents() playlists = [p for p in export_playlists if not p.get('deleted')] if migration_type == 'all' or migration_type == 'stations': log.info('Retrieving stations from ' + export_username) export_stations = export_api.get_all_stations() radio_stations = [s for s in export_stations if not s.get('deleted')] log.info('Export complete') export_api.logout() log.debug('API logout for ' + export_username) # import tracks if migration_type == 'all' or migration_type == 'tracks': log.info('Importing ' + str(len(all_tracks)) + ' All Access tracks to ' + import_username)
class GMusicWrapper(object): def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login( username, password, environ.get('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def populate_library( self ): #TODO: Use this as a function to refresh the library with Alexa via voice commands. # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return [x[query_type] for x in results[hits_key]] def _search_library_for_first(self, query_type, query): #try searching the library instead of the api for trackid, trackdata in self.library.items(): if query_type in trackdata: if query.lower() in trackdata[query_type].lower(): return trackdata return None def _search_library(self, query_type, query): #try searching the library instead of the api found = [] for trackid, trackdata in self.library.items(): if query_type in trackdata: if query.lower() in trackdata[query_type].lower(): found.append(trackdata) if not found: return None return found def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.logger.debug('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.logger.debug('Done! Discovered %d tracks.' % len(self.library)) def get_artist(self, name): """ Fetches information about an artist given its name """ search = self._search("artist", name) if len(search) == 0: search_lib = self._search_library("artist", name) if search_lib is not None: self.logger.debug(search_lib) return search_lib return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: search_lib = self._search_library("album", name) if search_lib is not None: self.logger.debug(search_lib) return search_lib return False return self._api.get_album_info(search[0]['albumId']) def get_latest_album(self, artist_name=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True) for index, val in enumerate(sorted_list): album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: return album_info return False def get_album_by_artist(self, artist_name, album_id=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] random.shuffle(album_info) for index, val in enumerate(album_info): album = self._api.get_album_info( album_id=album_info[index]['albumId'], include_tracks=True) if album['albumId'] != album_id and len(album['tracks']) >= 5: return album return False def get_song(self, song_name, artist_name=None, album_name=None): if artist_name: name = "%s %s" % (artist_name, song_name) elif album_name: name = "%s %s" % (album_name, song_name) self.logger.debug("get_song() : name: %s" % (name)) search = self._search("song", name) self.logger.debug("result length: %d" % len(search)) if len(search) == 0: search_lib = self._search_library_for_first("title", name) if search_lib is not None: return search_lib return False if album_name: for i in range(0, len(search) - 1): if album_name in search[i]['album']: return search[i] return search[0] def get_station(self, title, track_id=None, artist_id=None, album_id=None): if artist_id is not None: if album_id is not None: if track_id is not None: return self._api.create_station(title, track_id=track_id) return self._api.create_station(title, album_id=album_id) return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/alexa/stream/%s" % (environ['APP_URL'], song_id) def get_thumbnail(self, artist_art): # return artist_art.replace("http://", "https://") //OLD artistArtKey = 'artistArtRef' albumArtKey = 'albumArtRef' if artist_art is None: return self.default_thumbnail() elif artistArtKey in artist_art: artist_art = artist_art[artistArtKey] elif albumArtKey in artist_art: artist_art = artist_art[albumArtKey] else: return self.default_thumbnail() if type(artist_art) is list: if type(artist_art[0]) is dict: artUrl = artist_art[0]['url'] elif type(artist_art) is dict: artUrl = artist_art['url'] else: artUrl = artist_art return self.urlReplaceWithSecureHttps(artUrl) def urlReplaceWithSecureHttps(self, url): return url.replace("http://", "https://") def default_thumbnail(self): return 'https://lh3.googleusercontent.com/gdBHEk-u3YRDtuCU3iDTQ52nZd1t4GPmldYaT26Jh6EhXgp1mlhQiuLFl4eXDAXzDig5' def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if 'trackId' in track: return (self.library[track['trackId']], track['trackId']) if self.use_library_first(): #Using free version track id first if 'id' in track: return (track, track['id']) if 'storeId' in track: return track, track['storeId'] return (None, None) def get_artist_album_list(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return "Unable to find the artist you requested." artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list_text = "Here's the album listing for %s: " % artist_name counter = 0 for index, val in enumerate(artist_info['albums']): if counter > 25: # alexa will time out after 10 seconds if the list takes too long to iterate through break album_info = self._api.get_album_info( album_id=artist_info['albums'][index]['albumId'], include_tracks=True) if len(album_info['tracks']) > 5: counter += 1 album_list_text += ( artist_info['albums'][index]['name']) + ", " return album_list_text def get_latest_artist_albums(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list = artist_info['albums'] sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True) speech_text = 'The latest albums by %s are ' % artist_name counter = 0 for index, val in enumerate(sorted_list): if counter > 5: break else: album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: counter += 1 album_name = sorted_list[index]['name'] album_year = sorted_list[index]['year'] speech_text += '%s, released in %d, ' % (album_name, album_year) return speech_text def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70): # Give each match a score based on its similarity to the requested # name self.logger.debug("The artist name is " + str(artist_name)) request_name = request_name.lower() + artist_name.lower() scored_matches = [] for i, match in enumerate(all_matches): try: name = match['name'].lower() except (KeyError, TypeError): i = match name = all_matches[match]['title'].lower() if artist_name != "": name += all_matches[match]['artist'].lower() scored_matches.append({ 'index': i, 'name': name, 'score': fuzz.ratio(name, request_name) }) sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True) top_scoring = sorted_matches[0] self.logger.debug("The top scoring match was: " + str(top_scoring)) best_match = all_matches[top_scoring['index']] # Make sure the score is at least the min score value if top_scoring['score'] < minimum_score: return None return best_match def get_genres(self, parent_genre_id=None): return self._api.get_genres(parent_genre_id) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) def use_library_first(self): return environ['USE_LIBRARY_FIRST'].lower() == 'true' @classmethod def generate_api(cls, **kwargs): return cls(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'], **kwargs)
class GPMClient(object): """ Google Play Music client. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" #------------------------------------------------------------------------------ def __init__(self, email, password, device_id): self.__api = Mobileclient() self.logged_in = False self.__device_id = device_id attempts = 0 while not self.logged_in and attempts < 3: self.logged_in = self.__api.login(email, password, device_id) attempts += 1 self.all_tracks = dict() self.playlists = dict() self.library = dict() #------------------------------------------------------------------------------ def logout(self): self.__api.logout() #------------------------------------------------------------------------------ def update_local_lib(self): songs = self.__api.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Get main library song_map = dict() for song in songs: if "rating" in song and song["rating"] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song["id"] song_artist = song["artist"] song_album = song["album"] song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = dict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): for album in self.library[artist].keys(): if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted( self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album # Get all playlists plists = self.__api.get_all_user_playlist_contents() for plist in plists: plist_name = plist["name"] self.playlists[plist_name] = list() for track in plist["tracks"]: if track["trackId"] not in song_map: song = song_map[track["trackId"]] = track["track"] song["id"] = track["trackId"] else: song = song_map[track["trackId"]] self.playlists[plist_name].append(song) #------------------------------------------------------------------------------ def get_stream_url(self, song): return self.__api.get_stream_url(song["id"], self.__device_id) #------------------------------------------------------------------------------ def rate_song(self, song, rating): try: song["rating"] = rating song_list = [song] self.__api.change_song_metadata(song_list) print "Gave a Thumbs Up to {0} by {1} on Google Play.".format( song["title"].encode("utf-8"), song["artist"].encode("utf-8")) except RuntimeError: print "Error giving a Thumbs Up on Google Play."
def handle(self, *args, **options): if GPLAY_PASS == "" or GPLAY_USER == "": self.stdout.write('Credentials not set up. Please edit settings.py') return api = Mobileclient() if not api.login(GPLAY_USER,GPLAY_PASS): self.stdout.write('Incorrect credentials, login failed') return self.stdout.write('Connected to Google Music, downloading data...') #library = [] library = api.get_all_songs() self.stdout.write('Data downloaded!') self.stdout.write('Clearing DB...') cursor = connection.cursor() # This can take a long time using ORM commands on the Pi, so lets Truncate cursor.execute('DELETE FROM ' + Track._meta.db_table) cursor.execute('DELETE FROM ' + Album._meta.db_table) cursor.execute('DELETE FROM ' + Artist._meta.db_table) cursor.execute('DELETE FROM ' + Playlist._meta.db_table) cursor.execute('DELETE FROM ' + PlaylistConnection._meta.db_table) self.stdout.write('Parsing new data...') # Easier to keep track of who we've seen like this... artists = [] albums = [] count = len(library) self.stdout.write(str(count) + ' tracks found') i = 0 for song in library: i = i + 1 track = Track() if song['albumArtist'] == "": if song['artist'] == "": a = "Unknown Artist" else: a = song['artist'] else: a = song['albumArtist'] if a not in artists: artist = Artist() artist.name = a try: artist.art_url = song['artistArtRef'][0]['url'] except: print "No Art found." artist.save() artists.append(a) self.stdout.write('Added artist: ' + a) self.stdout.write(str(i) + '/' + str(count) + ' tracks completed') else: artist = Artist.objects.get(name=a) track.artist = artist if song['album'] + a not in albums: album = Album() album.name = song['album'] album.artist = artist try: album.year = song['year'] except: pass try: album.art_url = song['albumArtRef'][0]['url'] except: print "No Art found." album.save() albums.append(song['album'] + a) else: album = Album.objects.get(name=song['album'], artist=artist) track.album = album track.name = song['title'] track.stream_id = song['id'] try: track.track_no = song['trackNumber'] except: track.track_no = 0 track.save() self.stdout.write('All tracks saved!') self.stdout.write('Getting Playlists...') self.stdout.write('Saving playlists...') self.stdout.write('Getting playlist contents.') playlists = api.get_all_user_playlist_contents() for playlist in playlists: p = Playlist() p.pid = playlist['id'] p.name = playlist['name'] p.save() for entry in playlist['tracks']: try: track = Track.objects.get(stream_id=entry['trackId']) pc = PlaylistConnection() pc.playlist = p pc.track = track pc.save() except Exception: print "Not found." self.stdout.write('Library saved!')
from gmusicapi import Mobileclient import json api = Mobileclient() email = "" password = "" logged_in = api.login('', '', Mobileclient.FROM_MAC_ADDRESS) if logged_in: library = api.get_all_user_playlist_contents() f = open("textData/googleData.txt", "w+") f.write(json.dumps(library)) f.close() print("Success") else: print "Fail"
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))
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 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
export_tracks = export_api.get_all_songs() # strip out any tracks that are not available on All Access all_tracks = [ t for t in export_tracks if track_has_aa_data(t) and not t.get('deleted') ] if migration_type == 'all' or migration_type == 'ratings': log.info('Retrieving thumbs up/promoted tracks from ' + export_username) export_thumbs_up = export_api.get_promoted_songs() # strip out any tracks that are not available on All Access thumbs_up_tracks = [t for t in export_thumbs_up if track_has_aa_data(t)] if migration_type == 'all' or migration_type == 'playlists': log.info('Retrieving playlists from ' + export_username) export_playlists = export_api.get_all_user_playlist_contents() playlists = [p for p in export_playlists if not p.get('deleted')] if migration_type == 'all' or migration_type == 'stations': log.info('Retrieving stations from ' + export_username) export_stations = export_api.get_all_stations() radio_stations = [s for s in export_stations if not s.get('deleted')] log.info('Export complete') export_api.logout() log.debug('API logout for ' + export_username) # import tracks if migration_type == 'all' or migration_type == 'tracks': log.info('Importing ' + str(len(all_tracks)) + ' All Access tracks to ' + import_username)
class tizgmusicproxy(object): """A class for accessing a Google Music account to retrieve song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" def __init__(self, email, password, device_id): self.__api = Mobileclient() self.logged_in = False self.__device_id = device_id self.queue = list() self.queue_index = -1 self.play_mode = 0 self.now_playing_song = None attempts = 0 while not self.logged_in and attempts < 3: self.logged_in = self.__api.login(email, password) attempts += 1 self.playlists = CaseInsensitiveDict() self.library = CaseInsensitiveDict() def logout(self): self.__api.logout() def update_local_lib(self): songs = self.__api.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Get main library song_map = dict() for song in songs: if "rating" in song and song["rating"] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song["id"] song_artist = song["artist"] song_album = song["album"] song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if not (song_artist in self.library): self.library[song_artist] = dict() self.library[song_artist][self.all_songs_album_title] = list() if not (song_album in self.library[song_artist]): self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : {0}".format(artist.encode("utf-8"))) for album in self.library[artist].keys(): logging.info(" Album : {0}".format(album.encode("utf-8"))) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k["title"]) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get("trackNumber", 0)) self.library[artist][album] = sorted_album # Get all playlists plists = self.__api.get_all_user_playlist_contents() for plist in plists: plist_name = plist["name"] self.playlists[plist_name] = list() for track in plist["tracks"]: try: song = song_map[track["trackId"]] self.playlists[plist_name].append(song) except IndexError: pass def current_song_title_and_artist(self): logging.info("current_song_title_and_artist") song = self.now_playing_song if song is not None: title = self.now_playing_song["title"] artist = self.now_playing_song["artist"] logging.info("Now playing {0} by {1}".format(title.encode("utf-8"), artist.encode("utf-8"))) return artist.encode("utf-8"), title.encode("utf-8") else: return "", "" def current_song_album_and_duration(self): logging.info("current_song_album_and_duration") song = self.now_playing_song if song is not None: album = self.now_playing_song["album"] duration = self.now_playing_song["durationMillis"] logging.info("album {0} duration {1}".format(album.encode("utf-8"), duration.encode("utf-8"))) return album.encode("utf-8"), int(duration) else: return "", 0 def current_song_track_number_and_total_tracks(self): logging.info("current_song_track_number_and_total_tracks") song = self.now_playing_song if song is not None: track = self.now_playing_song["trackNumber"] total = self.now_playing_song["totalTrackCount"] logging.info("track number {0} total tracks {1}".format(track, total)) return track, total else: logging.info("current_song_track_number_and_total_tracks : not found") return 0, 0 def clear_queue(self): self.queue = list() self.queue_index = -1 def enqueue_artist(self, arg): try: artist = self.library[arg] count = 0 for album in artist: for song in artist[album]: self.queue.append(song) count += 1 logging.info("Added {0} tracks by {1} to queue".format(count, arg)) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def enqueue_album(self, arg): try: for artist in self.library: for album in self.library[artist]: logging.info("enqueue album : {0} | {1}".format(artist.encode("utf-8"), album.encode("utf-8"))) if album.lower() == arg.lower(): count = 0 for song in self.library[artist][album]: self.queue.append(song) count += 1 logging.info( "Added {0} tracks from {1} by " "{2} to queue".format(count, album.encode("utf-8"), artist.encode("utf-8")) ) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def enqueue_playlist(self, arg): try: playlist = self.playlists[arg] count = 0 for song in playlist: self.queue.append(song) count += 1 logging.info("Added {0} tracks from {1} to queue".format(count, arg)) except KeyError: logging.info("Cannot find {0}".format(arg)) raise def next_url(self): logging.info("next_url") if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): next_song = self.queue[self.queue_index] return self.__get_song_url(next_song) else: self.queue_index = -1 return self.next_url() else: return "" def prev_url(self): if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): prev_song = self.queue[self.queue_index] return self.__get_song_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return "" def __get_song_url(self, song): song_url = self.__api.get_stream_url(song["id"], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve song url!") raise
sys.exit() clientId = "<<YOUR SPOTIFY APP CLIENT ID>>" clientSecret = "<<YOUR SPOTIFY APP CLIENT SECRET>>" spotify = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials( client_id=clientId, client_secret=clientSecret)) mc = Mobileclient() if os.path.exists(googleEmail) == False: mc.perform_oauth(storage_filepath=googleEmail, open_browser=True) mc.oauth_login(Mobileclient.FROM_MAC_ADDRESS, oauth_credentials=googleEmail) playlists = {} for playlist in mc.get_all_user_playlist_contents(): print(playlist['name']) playlists[playlist['name']] = [] currentPlaylist = playlists[playlist['name']] for entry in playlist['tracks']: if 'track' in entry: title = entry['track']['title'] artist = entry['track']['artist'] album = entry['track']['album'] spotifyResults = spotify.search(q=artist + ', ' + title, type='track') if len(spotifyResults['tracks']['items']) > 0: spotifyTrackUri = spotifyResults['tracks']['items'][0]['uri'] else: spotifyTrackUri = "unknown"
args = parser.parse_args() password = '' if 'GOOGLE_PLAY_PASSWORD' in os.environ: password = os.environ['GOOGLE_PLAY_PASSWORD'] if not password: password = getpass.getpass('Google Play account password: '******'tracks'] for playlist in googlePlayPlaylists if playlist['name'] == args.playlist][0] googlePlayTrackIds = list(map(lambda x: x['trackId'], googlePlayPlaylistTrackList)) print("Fetching list of files in Google Play library") allTracksInGooglePlayLibrary = api.get_all_songs() print("Fetching track info for tracks in playlist") tracksInGooglePlayPlaylist = [track for track in allTracksInGooglePlayLibrary if track['id'] in googlePlayTrackIds] googlePlayTrackInfos = \
def handle(self, *args, **options): if GPLAY_PASS == "" or GPLAY_USER == "": self.stdout.write( 'Credentials not set up. Please edit settings.py') return api = Mobileclient() if not api.login(GPLAY_USER, GPLAY_PASS, DEVICE_ID): self.stdout.write('Incorrect credentials, login failed') return self.stdout.write('Connected to Google Music, downloading data...') #library = [] library = api.get_all_songs() self.stdout.write('Data downloaded!') self.stdout.write('Clearing DB...') cursor = connection.cursor() # This can take a long time using ORM commands on the Pi, so lets Truncate cursor.execute('DELETE FROM ' + Track._meta.db_table) cursor.execute('DELETE FROM ' + Album._meta.db_table) cursor.execute('DELETE FROM ' + Artist._meta.db_table) cursor.execute('DELETE FROM ' + Playlist._meta.db_table) cursor.execute('DELETE FROM ' + PlaylistConnection._meta.db_table) self.stdout.write('Parsing new data...') # Easier to keep track of who we've seen like this... artists = [] albums = [] count = len(library) self.stdout.write(str(count) + ' tracks found') i = 0 for song in library: i = i + 1 track = Track() if song['albumArtist'] == "": if song['artist'] == "": a = "Unknown Artist" else: a = song['artist'] else: a = song['albumArtist'] if a not in artists: artist = Artist() artist.name = a try: artist.art_url = song['artistArtRef'][0]['url'] except: print "No Art found." artist.save() artists.append(a) self.stdout.write('Added artist: ' + a) self.stdout.write( str(i) + '/' + str(count) + ' tracks completed') else: artist = Artist.objects.get(name=a) track.artist = artist if song['album'] + a not in albums: album = Album() album.name = song['album'] album.artist = artist try: album.year = song['year'] except: pass try: album.art_url = song['albumArtRef'][0]['url'] except: print "No Art found." album.save() albums.append(song['album'] + a) else: album = Album.objects.get(name=song['album'], artist=artist) track.album = album track.name = song['title'] track.stream_id = song['id'] try: track.track_no = song['trackNumber'] except: track.track_no = 0 track.save() self.stdout.write('All tracks saved!') self.stdout.write('Getting Playlists...') self.stdout.write('Saving playlists...') self.stdout.write('Getting playlist contents.') playlists = api.get_all_user_playlist_contents() for playlist in playlists: p = Playlist() p.pid = playlist['id'] p.name = playlist['name'] p.save() for entry in playlist['tracks']: try: track = Track.objects.get(stream_id=entry['trackId']) pc = PlaylistConnection() pc.playlist = p pc.track = track pc.save() except Exception: print "Not found." self.stdout.write('Library saved!')
#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 gArtists = [] gTracks = []
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
class MusicLibrary(object): """This class reads information about your Google Play Music library""" def __init__(self, username=None, password=None, true_file_size=False, scan=True, verbose=0): self.verbose = False if verbose > 1: self.verbose = True self.__login_and_setup(username, password) self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__albums = [] # [Album(), ...] self.__tracks = {} self.__playlists = {} if scan: self.rescan() self.true_file_size = true_file_size def rescan(self): """Scan the Google Play Music library""" self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__albums = [] # [Album(), ...] self.__tracks = {} self.__playlists = {} self.__aggregate_albums() def __login_and_setup(self, username=None, password=None): # If credentials are not specified, get them from $HOME/.gmusicfs if not username or not password: cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs') 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) self.config = ConfigParser.ConfigParser() self.config.read(cred_path) username = self.config.get('credentials','username') password = self.config.get('credentials','password') global deviceId deviceId = self.config.get('credentials','deviceId') if not username or not password: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not deviceId: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) if deviceId.startswith("0x"): deviceId = deviceId[2:] self.api = GoogleMusicAPI(debug_logging=self.verbose) log.info('Logging in...') self.api.login(username, password, deviceId) log.info('Login successful.') def __aggregate_albums(self): """Get all the tracks and playlists in the library, parse into relevant dicts""" log.info('Gathering track information...') tracks = self.api.get_all_songs() for track in tracks: log.debug('track = %s' % pp.pformat(track)) # Prefer the album artist over the track artist if there is one artist_name = formatNames(track['albumArtist']) if artist_name.strip() == '': artist_name = formatNames(track['artist']) if artist_name.strip() == '': artist_name = 'Unknown' # Get the Artist object, or create one if it doesn't exist artist = self.__artists.get(artist_name.lower(), None) if not artist: artist = Artist(self, artist_name) self.__artists[artist_name.lower()] = artist # Get the Album object, or create one if it doesn't exist album = artist.get_album(formatNames(track['album'])) if not album: album = Album(self, track['album']) self.__albums.append(album) # NOTE: Current no purpose other than to count artist.add_album(album) # Add track to album album.add_track(track) # Add track to list of all tracks, indexable by track ID if 'id' in track: self.__tracks[track['id']] = track log.debug('%d tracks loaded.' % len(tracks)) log.debug('%d artists loaded.' % len(self.__artists)) log.debug('%d albums loaded.' % len(self.__albums)) # Add all playlists playlists = self.api.get_all_user_playlist_contents() for pldata in playlists: playlist = Playlist(self, pldata) self.__playlists[playlist.dirname.lower()] = playlist log.debug('%d playlists loaded.' % len(self.__playlists)) def get_artists(self): """Return list of all artists in the library""" return self.__artists.values() def get_artist(self, name): """Return the artist from the library with the specified name""" return self.__artists.get(name.lower(), None) def get_playlists(self): """Return list of all playlists in the library""" return self.__playlists.values() def get_playlist(self, name): """Return the playlist from the library with the specified name""" return self.__playlists.get(name.lower(), None) def get_track(self, trackid): """Return the track from the library with the specified track ID""" return self.__tracks.get(trackid, None) def cleanup(self): pass
else: f = open("email.txt", "w") email = raw_input("Email: ") f.write(email) password = getpass.getpass("Password: "******"name") for x in playlist_list]) playlists = mc.get_all_user_playlist_contents() choice = raw_input("What playlist: ") for playlist in playlists: if str(playlist.get("name")) == choice: chosen = playlist tracks = [] for x in chosen.get("tracks"): tracks.append(x) if raw_input("Shuffle?\n").lower() == "yes": random.shuffle(tracks) for song in tracks:
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 Player(object): def __init__(self, email, password, device_id): self.api = Mobileclient() self.vlc = Instance() self.loaded_tracks = [] self.playing = False self.thread_running = False self.api.login(email, password, device_id) if os.path.isfile("songs.json"): # Load from file print("Found songs data.") with open('songs.json') as input_file: self.song_library = json.load(input_file) else: self.song_library = self.api.get_all_songs() # Save to file with open('songs.json', 'w') as output_file: json.dump(self.song_library, output_file) def load_playlist(self, name): name = name.strip().lower() print("Looking for...", name) if os.path.isfile("playlists.json"): # Load from file print("Found playlist data.") with open('playlists.json') as input_file: self.playlists = json.load(input_file) else: self.playlists = self.api.get_all_user_playlist_contents() # Save to file with open('playlists.json', 'w') as output_file: json.dump(self.playlists, output_file) self.loaded_tracks = [] for playlist_dict in self.playlists: playlist_name = playlist_dict['name'].strip().lower() if (playlist_name == name) or (name in playlist_name): print("Found match...", playlist_dict['name']) for track_dict in playlist_dict['tracks']: self.loaded_tracks.append(track_dict) return playlist_dict['name'] else: print("Found...", playlist_dict['name']) return None def end_callback(self, event, track_index): if track_index < len(self.loaded_tracks): self.play_song(self.loaded_tracks[track_index]) event_manager = self.player.event_manager() event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, track_index + 1) self.playing = True else: self.playing = False def start_playlist(self): if len(self.loaded_tracks) > 0: self.play_song(self.loaded_tracks[0]) if len(self.loaded_tracks) > 1: event_manager = self.player.event_manager() event_manager.event_attach(EventType.MediaPlayerEndReached, self.end_callback, 1) def play_song(self, song_dict): stream_url = self.api.get_stream_url(song_dict['trackId']) self.player = self.vlc.media_player_new() media = self.vlc.media_new(stream_url) self.player.set_media(media) self.player.play() song_string = "" if (song_dict['source'] == '2'): song_string = self.get_song_details(song_dict) else: song_string = self.get_local_song_details(song_dict['trackId']) print("Playing...",song_string) if enable_display: scrollphat.clear() scrollphat.write_string(" "*5+song_string) if not self.thread_running: thread = Thread(target=self.scroll_string) thread.start() self.playing = True def scroll_string(self): self.thread_running = True while self.thread_running: scrollphat.scroll() time.sleep(0.1) def stop(self): if self.player != None: self.player.stop() if enable_display: scrollphat.clear() self.thread_running = False self.playing = False def get_local_song_details(self, track_id): for song_dict in self.song_library: if track_id == song_dict['id']: return song_dict['albumArtist']+" - "+song_dict['title'] def get_song_details(self, song_dict): return song_dict['track']['albumArtist']+" - "+song_dict['track']['title']
class MusicLibrary(object): 'Read information about your Google Music library' def __init__(self, username=None, password=None, true_file_size=False, scan=True, verbose=0): self.verbose = False if verbose > 1: self.verbose = True self.__login_and_setup(username, password) self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__albums = [] # [Album(), ...] self.__tracks = {} self.__playlists = {} if scan: self.rescan() self.true_file_size = true_file_size def rescan(self): self.__artists = {} # 'artist name' -> {'album name' : Album(), ...} self.__albums = [] # [Album(), ...] self.__tracks = {} self.__playlists = {} self.__aggregate_albums() def __login_and_setup(self, username=None, password=None): # If credentials are not specified, get them from $HOME/.gmusicfs if not username or not password: cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs') 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) self.config = ConfigParser.ConfigParser() self.config.read(cred_path) username = self.config.get('credentials','username') password = self.config.get('credentials','password') global deviceId deviceId = self.config.get('credentials','deviceId') if not username or not password: raise NoCredentialException( 'No username/password could be read from config file' ': %s' % cred_path) if not deviceId: raise NoCredentialException( 'No deviceId could be read from config file' ': %s' % cred_path) self.api = GoogleMusicAPI(debug_logging=self.verbose) log.info('Logging in...') self.api.login(username, password) log.info('Login successful.') def __aggregate_albums(self): 'Get all the tracks in the library, parse into artist and album dicts' all_artist_albums = {} # 'Artist|||Album' -> Album() log.info('Gathering track information...') tracks = self.api.get_all_songs() for track in tracks: log.debug('track = %s' % pp.pformat(track)) # Prefer the album artist over the track artist if there is one: artist = formatNames(track['albumArtist'].lower()) if artist.strip() == '': artist = formatNames(track['artist'].lower()) # Get the Album object if it already exists: key = '%s|||%s' % (formatNames(artist), formatNames(track['album'].lower())) album = all_artist_albums.get(key, None) if not album: # New Album if artist == '': artist = 'unknown' album = all_artist_albums[key] = Album( self, formatNames(track['album'].lower())) self.__albums.append(album) artist_albums = self.__artists.get(artist, None) if artist_albums: artist_albums[formatNames(album.normtitle)] = album else: self.__artists[artist] = {album.normtitle: album} artist_albums = self.__artists[artist] album.add_track(track) if 'id' in track: self.__tracks[track['id']] = track log.debug('%d tracks loaded.' % len(tracks)) log.debug('%d artists loaded.' % len(self.__artists)) log.debug('%d albums loaded.' % len(self.__albums)) playlists = self.api.get_all_user_playlist_contents() for playlist in playlists: name = formatNames(playlist['name'].lower()) log.debug('Playlist %s' % name) self.__playlists[name] = [] entries = playlist['tracks'] for entry in entries: log.debug('Playlist entry = %s' % pp.pformat(entry)) if 'track' in entry: track = entry['track'] track['id'] = entry['trackId'] else: track = self.__tracks[entry['trackId']] self.__playlists[name].append(track) log.debug('%d playlists loaded.' % len(self.__playlists)) def get_artists(self): return self.__artists def get_playlists(self): return self.__playlists def get_albums(self): return self.__albums def get_artist_albums(self, artist): log.debug(artist) return self.__artists[artist] def cleanup(self): pass
def get_google(): db_path = 'ServerDatabase.db' # Columns in the table XFILES schema = ['Id', 'RootId', 'FileHandle', 'RevisionAdded', 'RevisionDeleted', 'FieldUpdateRevision', 'DisplayName', 'DisplayPath', 'IsFolder', 'Size', 'FileType', 'Usage', 'DateCreated', 'DateModified', 'TranscodingType', 'MusicChannels', 'MusicName', 'MusicAlbum', 'MusicArtist', 'MusicAlbumArtist', 'MusicComposer', 'MusicComment', 'MusicGenre', 'MusicYear', 'MusicDuration', 'MusicTrackCount', 'MusicTrackNumber', 'MusicDiscCount', 'MusicDiscNumber', 'MusicCompilation', 'MusicBitRate', 'MusicSampleRate', 'MusicBpm', 'MusicAlbumArtStart', 'MusicAlbumArtSize', 'MusicAlbumArtType', 'MusicAlbumArt', 'MusicStart', 'MusicLength', 'MD5Hash', 'ServerId', 'MusicRating', 'MusicPlayCount', 'MusicDateAdded', 'MusicDatePlayed', 'MusicUploadStatus', 'MusicUploadSelected', 'MusicLastScanned', 'MusicIsPodcast', 'StoreType', 'MusicLabelCode', 'MusicUitsJson', 'MusicContentRating', 'MusicAdditionalMetadataJson', 'DeleteAfterUpload', 'RowLastModified', 'MusicUploadStatusLastModified'] # Establish a connection to a local Google Play MusicManager sqlite # database, with local file paths for any songs that I uploaded to Google Play conn = connect(db_path) c = conn.cursor() # The first line of secret_google.txt is a Google Music user email, # the second line the user's password with open('secret_google.txt') as f: r = [l.strip() for l in f.readlines()] api = Mobileclient() api.login(r[0], r[1], Mobileclient.FROM_MAC_ADDRESS) # All playlists to record start with a three digit integer format_check = compile('^[0-9]{3}(?::|$)') # Note the clash between the search string ':' and the syntax of # non-capturing groups in regexes (?:...) inplaylists = api.get_all_user_playlist_contents() playlists = [p for p in inplaylists if format_check.search(p['name']) is not None] playlists = sorted(playlists, key = lambda k: k['name']) playlists = playlists[1:] + playlists[:1] google = '2017-01--' keys = list() keys.append(google) out = ''' <li><div id="{0}"> <strong><a href="#" id="{0}-title">{1}</a></strong> <div id="{0}-body" style="display: none;">\n<ul class="space-before space-after">'''.format(google, google.replace('--', '–')) for playlist in playlists: pn = playlist['name'] entries = list() for t in playlist['tracks']: if t['source'] == '1': ## 'trackId' corresponds to column 'ServerId' in table XFILES selector = "SELECT * FROM XFILES WHERE ServerId='{}'" c.execute(selector.format(t['trackId'])) matches = c.fetchall() if len(matches) == 1: m = matches[0] lookup = dict(zip(schema, m)) s = title_by_artist.format(lookup['MusicName'], lookup['MusicArtist']) entries.append('<li>{0}</li>'.format(s)) else: print('Weirdness in playlist {}. Track info:'.format(playlist['name'])) print(t) else: track = t['track'] s = title_by_artist.format(track['title'], track['artist']) entries.append('<li>{0}</li>'.format(s)) p_txt = '\n'.join(entries) p_title = ''.join([c for c in pn.replace(' ', '-') if c in alphanum]) keys.append(p_title) out = add_to_out(out, ''' <li><div id="{1}"> <a href="#" id="{1}-title">{0}</a> <div id="{1}-body" style="display: none;"> <ol class="space-before space-after"> {2} </ol> </div> <!-- #{1}-body --> </div> <!-- #{1} --> </li>'''.format(pn, p_title, p_txt)) out = add_to_out(out, ''' </div> <!-- #{0}-body --> </div> <!-- #{0} --> </li>'''.format(google)) return([out, keys])