def _get_global_tracks(api: gmusicapi.Mobileclient, artist_ids, album_ids): artist_ids = list(artist_ids) album_ids = list(album_ids) for artist_id in artist_ids: results = api.get_artist_info(artist_id) for album_stub in results['albums']: album_id = album_stub['albumId'] album_ids.append(album_id) for album_id in album_ids: album = api.get_album_info(album_id) yield from album['tracks']
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
import sys from gmusicapi import Mobileclient if __name__ == "__main__": if sys.argv[1] == "1": mc = Mobileclient() success = mc.login(sys.argv[3], sys.argv[4]) if success == True: sresults = mc.search_all_access(sys.argv[2], 1) #2 = query song = sresults['song_hits'][0] sid = song['track']['nid'] sname = song['track']['title'] sartist = song['track']['artist'] sartistid = song['track']['artistId'][0] salbum = song['track']['album'] salbumart = song['track']['albumArtRef'][0]['url'] aresults = mc.get_artist_info(sartistid, False, 1, 1) artistart = aresults['artistArtRef'] print(sid) print(sname) print(sartist) print(salbum) print(salbumart) print(artistart) if sys.argv[1] == "2": print("Spotify is not yet supported.")
class MusicManager(object): def __init__(self): self.api = Mobileclient(validate=False, debug_logging=False) if config.GOOGLE_STREAMKEY is not None: self.api.login(config.GOOGLE_USERNAME, config.GOOGLE_PASSWORD, config.GOOGLE_STREAMKEY) else: self.api.login(config.GOOGLE_USERNAME, config.GOOGLE_PASSWORD, Mobileclient.FROM_MAC_ADDRESS) self.queue = [] self.current_index = len(self.queue) - 1 self.vlc = VlcManager() self.state_thread = Thread(target=self.check_state) self.state_thread.daemon = True self.state_thread.start() def play_song(self, id): song = self.queue_song(id) self.current_index = len(self.queue) - 1 self.load_song() return song def queue_song(self, id): self.queue.append(self.getSongInfo(id)) def play_radio_station(self, id): results = self.api.get_station_tracks(id, num_tracks=40) for song in results: song['albumArtRef'] = song['albumArtRef'][0]['url'] if 'artistId' in song: song['artistId'] = song['artistId'][0] self.current_index = len(self.queue) - 1 self.queue.append(results) self.load_song() def play_album(self, args): album = self.get_album_details(args) songs = [] for index in range(len(album['tracks'])): song = album['tracks'][index] if index == 0: songs.append(self.play_song(song['nid'])) else: songs.append(self.queue_song(song['nid'])) return songs def queue_album(self, args): album = self.get_album_details(args) songs = [] for song in album['tracks']: songs.append(self.queue_song(song['nid'])) return songs def next(self): self.current_index += 1 self.load_song() def prev(self): self.current_index -= 1 self.load_song() def pause(self): self.vlc.vlc_pause() def resume(self): self.vlc.vlc_resume() def volume(self, val): self.vlc.vlc_volume(val) def delete(self, id): if id > self.current_index: del self.queue[id] elif id < self.current_index: del self.queue[id] self.current_index -= 1 else: del self.queue[id] self.load_song() def go_to(self, id): self.current_index = id self.load_song() def load_song(self): if self.current_index < len(self.queue): song = self.queue[self.current_index] url = self.api.get_stream_url(song['nid'], config.GOOGLE_STREAMKEY) self.vlc.vlc_play(url) def check_state(self): while True: status = self.vlc.player.get_state() if status == vlc.State.Ended: if self.current_index != len(self.queue) - 1: self.next() time.sleep(1) def get_status(self): status = self.vlc.vlc_status() # status['queue'] = self.queue[:] # for i in range(len(status['queue'])): # status['queue'][i]['vlcid'] = i # if i == self.current_index: # status['queue'][i]['current'] = True # status['current'] = status['queue'][i] if len(self.queue) > 0: status['current'] = self.queue[self.current_index] return status def get_queue(self): queue = self.queue[:] for i in range(len(queue)): queue[i]['vlcid'] = i return queue def search(self, query): results = self.api.search_all_access(query, max_results=50) results['artist_hits'] = [artist['artist'] for artist in results['artist_hits']] results['album_hits'] = [album['album'] for album in results['album_hits']] for album in results['album_hits']: album['artistId'] = album['artistId'][0] results['song_hits'] = [song['track'] for song in results['song_hits']] for song in results['song_hits']: song['albumArtRef'] = song['albumArtRef'][0]['url'] if 'artistId' in song: song['artistId'] = song['artistId'][0] return results def get_album_details(self, id): results = self.api.get_album_info(album_id=id, include_tracks=True) results['artistId'] = results['artistId'][0] for song in results['tracks']: song['albumArtRef'] = song['albumArtRef'][0]['url'] if 'artistId' in song: song['artistId'] = song['artistId'][0] return results def get_artist_details(self, id): results = self.api.get_artist_info(artist_id=id) for album in results['albums']: album['artistId'] = album['artistId'][0] for song in results['topTracks']: song['albumArtRef'] = song['albumArtRef'][0]['url'] if 'artistId' in song: song['artistId'] = song['artistId'][0] return results def create_radio_station(self, name, id): if id[0] == 'A': station_id = self.api.create_station(name, artist_id=id) elif id[0] == 'B': station_id = self.api.create_station(name, album_id=id) else: station_id = self.api.create_station(name, track_id=id) return station_id def get_radio_stations(self): return self.api.get_all_stations() def flush(self): self.vlc.vlc_stop() self.queue = [] def getSongInfo(self, id): song = self.api.get_track_info(id) song['albumArtRef'] = song['albumArtRef'][0]['url'] if 'artistId' in song: song['artistId'] = song['artistId'][0] return song
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"]])
else: # Attempt to search artist name and pull first result try: artistID = api.search(artist)['artist_hits'][0]['artist']['artistId'] albumIDs = [] except: print( "No search results for " + artist + '\nTry getting Artist ID from artist\'s Google Music page URL.\nIt starts with A and is 27 characters.' ) exit() # Use artist ID to retrieve all album IDs if len(albumIDs) == 0: try: for album in api.get_artist_info(artistID, True, 0, 0)['albums']: albumIDs.append(album['albumId']) except KeyError: print("No albums for " + artist) exit() except: print("Critical error for " + artist) print(error) exit() # Use album IDs to get track IDs trackIDs = [] for albumID in albumIDs: try: for track in api.get_album_info(albumID)['tracks']: trackIDs.append(track['storeId'])
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.__galbums = {} self.__gartists = {} self.__albums = [] # [Album(), ...] 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.__galbums = {} self.__gartists = {} 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 __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 __cleanup_artist(self, artist): if artist.startswith("featuring"): artist = artist[len("featuring"):].strip() if artist.startswith("feat"): artist = artist[len("feat"):].strip() return artist def __cleanup_name(self, name, track): for bracket in (('\[', '\]'), ('\{', '\}'), ('\(', '\)')): # Remove (xxx Album Version) from track names match = re.compile('^(?P<name>(.*))([ ]+[%s-]([^%s]*)[Vv]ersion[%s]?[ ]*)$' % (bracket[0], bracket[1], bracket[1])).match(name) if match is not None: name = match.groupdict()['name'] name, track = self.__cleanup_name(name, track) # Pull (feat. <artist>) out of name and add to artist list match = re.compile('^(?P<name>(.*))([ ]+[%s][ ]*[Ff]eat[\.]?[ ]*(?P<artist>(.*))[%s]+)(?P<postfix>(.*))$' % (bracket[0], bracket[1])).match(name) if match is not None: name = match.groupdict()['name'] artist = match.groupdict()['artist'] if match.groupdict().has_key('postfix') and match.groupdict()['postfix'] is not None: name += match.groupdict()['postfix'] artist = artist.strip() if artist[-1] in ")}]": # I hate regex's. The one above doesn't catch the last parenthesis if there's one artist = artist[:-1] if artist.find(" and ") > -1 or artist.find(" & ") > -1: artist = artist.replace(', ', ';') artist = artist.replace(' & ', ';') artist = artist.replace(' and ', ';') alist = artist.split(';') for artist in alist: track['artist'].append(artist.strip()) name, track = self.__cleanup_name(name, track) # Remove () or ( ) from track names match = re.compile('^(?P<name>(.*))([ ]*[%s][ ]?[%s][ ]*)$' % (bracket[0], bracket[1])).match(name) if match is not None: name = match.groupdict()['name'] name, track = self.__cleanup_name(name, track) # Strip any extra whitespace from the name name = name.strip() return name, track def __cleanup_track(self, track): name = track['title'] name, track = self.__cleanup_name(name, track) track['title'] = name for anum in range(0, len(track['artist'])): track['artist'][anum] = self.__cleanup_artist(track['artist'][anum]) return track def __aggregate_albums(self): 'Get all the tracks in the library, parse into artist and album dicts' all_artist_albums = {} log.info('Gathering track information...') tracks = self.api.get_all_songs() for track in tracks: if track.has_key('artist'): if track['artist'].find(" and ") > -1 or track['artist'].find(" & ") > -1: track['artist'] = track['artist'].replace(', ', ';') track['artist'] = track['artist'].replace(' & ', ';') track['artist'] = track['artist'].replace(' and ', ';') track['artist'] = track['artist'].split(';') else: track['artist'] = [] track = self.__cleanup_track(track) if track.has_key('albumArtist') and track['albumArtist'] != "": albumartist = track['albumArtist'] elif len(track['artist']) == 1 and track['artist'][0] != "": albumartist = track['artist'][0] else: albumartist = "Unknown" # 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: print "Downloading album info for '%s'" % track['album'] album_info = self.__galbums[track['albumId']] = self.api.get_album_info(track['albumId'], include_tracks=False) 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: print "Downloading artist info for '%s'" % 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') # Fix for odd capitalization issues if artist_info.has_key('name') and track['albumArtist'].lower() == artist_info['name'].lower() and track['albumArtist'] != artist_info['name']: track['albumArtist'] = artist_info['name'] for anum in range(0, len(track['artist'])): if artist_info.has_key('name') and track['artist'][anum].lower() == artist_info['name'].lower() and track['artist'][anum] != artist_info['name']: track['artist'][anum] = artist_info['name'] if not track.has_key('albumId'): track['albumKey'] = "%s|||%s" % (albumartist, track['album']) else: track['albumKey'] = track['albumId'] album = all_artist_albums.get(track['albumKey'], None) if not album: album = all_artist_albums[track['albumKey']] = Album( self, formatNames(track['album']), track['albumArtist'], track['album'], track['year'] ) self.__albums.append(album) artist_albums = self.__artists.get(track['albumArtist'], None) if artist_albums: artist_albums[formatNames(album.normtitle)] = album else: self.__artists[track['albumArtist']] = {album.normtitle: album} artist_albums = self.__artists[track['albumArtist']] album.add_track(track) # Separate multi-disc albums for artist in self.__artists.values(): for key in artist.keys(): album = artist[key] if album.get_disc_count() > 1: for d in album.get_discs(): new_name = "%s - Disc %i" % (album.album, d) new_album = Album(album.library, formatNames(new_name), album.artist, new_name, album.year) album.copy_art_to(new_album) new_album.show_discnum = True new_key = None for t in album.get_tracks(): if int(t['discNumber']) == d: new_album.add_track(t) artist[formatNames(new_name)] = new_album del artist[key] log.debug('%d tracks loaded.' % len(tracks)) log.debug('%d artists loaded.' % len(self.__artists)) log.debug('%d albums loaded.' % len(self.__albums)) def get_artists(self): return self.__artists def get_albums(self): return self.__albums def get_artist_albums(self, artist): log.debug(artist) return self.__artists[artist] def cleanup(self): pass
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
info = mc.get_track_info(tid) title = info['title'] number = info['trackNumber'] artist = info['artist'] album = info['album'] album_art = info.get('albumArtRef', [{}])[0].get('url') length = int(info['durationMillis']) / 1000 metadata = json.dumps(info, indent=2) artist_id = info['artistId'][0] if artist_id in artist_cache: a_info = artist_cache[artist_id] else: a_info = mc.get_artist_info( artist_id, include_albums=False, max_top_tracks=0, max_rel_artist=0) artist_cache[artist_id] = a_info artist_art = a_info.get('artistArtRef') newSong = Song(tid, title, number, artist, album, length, artist_art, album_art, metadata) curPlaylist.addSong(newSong) if curPlaylist.songs: master.append(curPlaylist) # Step through the playlists and download songs for playlist in master: if not should_download_playlist(playlist): continue
from gmusicapi import Mobileclient from pprint import pprint api = Mobileclient() api.login('', '', Mobileclient.FROM_MAC_ADDRESS) playlist_name = 'ToListenTo' answer = api.search_all_access('', max_results=1) sweet_track_ids = [] artist_id = answer['artist_hits'][0]['artist']['artistId'] response = api.get_artist_info(artist_id, include_albums=False, max_top_tracks=3, max_rel_artist=0) for song in response['topTracks']: sweet_track_ids.append(song['nid']) playlists = api.get_all_playlists() playlist_id = None for playlist in playlists: if playlist_name in playlist['name']: playlist_id = playlist['id'] if not playlist_id: playlist_id = api.create_playlist(playlist_name) api.add_songs_to_playlist(playlist_id, sweet_track_ids)
class GMusicWrapper(object): def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login(username, password, getenv('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") try: assert literal_eval(getenv("DEBUG_FORCE_LIBRARY", "False")) self.use_store = False except (AssertionError, ValueError): # AssertionError if it's False, ValueError if it's not set / not set to a proper boolean string self.use_store = self._api.is_subscribed # Populate our library self.start_indexing() def start_indexing(self): self.library = {} self.albums = set([]) self.artists = set([]) self.indexing_thread = threading.Thread( target=self.index_library ) self.indexing_thread.start() def log(self, log_str): if self.logger != None: self.logger.debug(log_str) def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return [x[query_type] for x in results[hits_key]] def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.log('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.albums.add(track['album']) self.artists.add(track['artist']) self.log('Fetching library complete.') def get_artist(self, name): """ Fetches information about an artist given its name """ if self.use_store: search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) else: search = {} search['topTracks'] = [] # Find the best artist we have, and then match songs to that artist likely_artist, score = process.extractOne(name, self.artists) if score < 70: return False for song_id, song in self.library.items(): if 'artist' in song and song['artist'].lower() == likely_artist.lower() and 'artistId' in song: if not search['topTracks']: # First entry # Copy artist details from the first song into the general artist response try: search['artistArtRef'] = song['artistArtRef'][0]['url'] except KeyError: pass search['name'] = song['artist'] search['artistId'] = song['artistId'] search['topTracks'].append(song) random.shuffle(search['topTracks']) # This is all music, not top, but the user probably would prefer it shuffled. if not search['topTracks']: return False return search def get_album(self, name, artist_name=None): if self.use_store: if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) else: search = {} search['tracks'] = [] if artist_name: artist_name, score = process.extractOne(artist_name, self.artists) if score < 70: return False name, score = process.extractOne(name, self.albums) if score < 70: return False for song_id, song in self.library.items(): if 'album' in song and song['album'].lower() == name.lower(): if not artist_name or ('artist' in song and song['artist'].lower() == artist_name.lower()): if not search['tracks']: # First entry search['albumArtist'] = song['albumArtist'] search['name'] = song['album'] try: search['albumId'] = song['albumId'] except KeyError: pass search['tracks'].append(song) if not search['tracks']: return False return search def get_latest_album(self, artist_name=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True) for index, val in enumerate(sorted_list): album_info = self._api.get_album_info(album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: return album_info return False def get_album_by_artist(self, artist_name, album_id=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] random.shuffle(album_info) for index, val in enumerate(album_info): album = self._api.get_album_info(album_id=album_info[index]['albumId'], include_tracks=True) if album['albumId'] != album_id and len(album['tracks']) >= 5: return album return False def get_song(self, name, artist_name=None, album_name=None): if self.use_store: if artist_name: name = "%s %s" % (artist_name, name) elif album_name: name = "%s %s" % (album_name, name) search = self._search("song", name) if len(search) == 0: return False if album_name: for i in range(0, len(search) - 1): if album_name in search[i]['album']: return search[i] return search[0] else: search = {} if not name: return False if artist_name: artist_name, score = process.extractOne(artist_name, self.artists) if score < 70: return False if album_name: album_name, score = process.extractOne(album_name, self.albums) if score < 70: return False possible_songs = {song_id: song['title'] for song_id, song in self.library.items() if (not artist_name or ('artist' in song and song['artist'].lower() == artist_name.lower())) and (not album_name or ('album' in song and song['album'].lower() == album_name.lower()))} song, score, song_id = process.extractOne(name.lower(), possible_songs) if score < 70: return False else: return self.library[song_id] def get_promoted_songs(self): return self._api.get_promoted_songs() def get_station(self, title, track_id=None, artist_id=None, album_id=None): if artist_id is not None: if album_id is not None: if track_id is not None: return self._api.create_station(title, track_id=track_id) return self._api.create_station(title, album_id=album_id) return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/alexa/stream/%s" % (getenv('APP_URL'), song_id) def get_thumbnail(self, artist_art): return artist_art.replace("http://", "https://") def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if self.use_store and 'storeId' in track: return track, track['storeId'] elif 'id' in track: return self.library[track['id']], track['id'] elif 'trackId' in track: return self.library[track['trackId']], track['trackId'] else: return None, None def get_artist_album_list(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return "Unable to find the artist you requested." artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list_text = "Here's the album listing for %s: " % artist_name counter = 0 for index, val in enumerate(artist_info['albums']): if counter > 25: # alexa will time out after 10 seconds if the list takes too long to iterate through break album_info = self._api.get_album_info(album_id=artist_info['albums'][index]['albumId'], include_tracks=True) if len(album_info['tracks']) > 5: counter += 1 album_list_text += (artist_info['albums'][index]['name']) + ", " return album_list_text def get_latest_artist_albums(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list = artist_info['albums'] sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True) speech_text = 'The latest albums by %s are ' % artist_name counter = 0 for index, val in enumerate(sorted_list): if counter > 5: break else: album_info = self._api.get_album_info(album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: counter += 1 album_name = sorted_list[index]['name'] album_year = sorted_list[index]['year'] speech_text += '%s, released in %d, ' % (album_name, album_year) return speech_text def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70): # Give each match a score based on its similarity to the requested # name self.log('Finding closest match...') best_match = None request_name = request_name.lower() + artist_name.lower() scored_matches = [] for i, match in enumerate(all_matches): try: name = match['name'].lower() except (KeyError, TypeError): i = match name = all_matches[match]['title'].lower() if artist_name != "": name += all_matches[match]['artist'].lower() scored_matches.append({ 'index': i, 'name': name, 'score': fuzz.ratio(name, request_name) }) sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True) try: top_scoring = sorted_matches[0] # Make sure we have a decent match (the score is n where 0 <= n <= 100) if top_scoring['score'] >= minimum_score: best_match = all_matches[top_scoring['index']] except IndexError: pass self.log('Found %s...' % best_match) return best_match def get_genres(self, parent_genre_id=None): return self._api.get_genres(parent_genre_id) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) @classmethod def generate_api(cls, **kwargs): return cls(getenv('GOOGLE_EMAIL'), getenv('GOOGLE_PASSWORD'), **kwargs)
class 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
class GMusicWrapper: def __init__(self, username, password): self._api = Mobileclient() success = self._api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) if not success: raise Exception("Unsuccessful login. Aborting!") def _search(self, query_type, query): results = self._api.search(query) hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return map(lambda x: x[query_type], results[hits_key]) def get_artist(self, name): search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) def get_song(self, name, artist_name=None): if artist_name: name = "%s %s" % (artist_name, name) search = self._search("song", name) if len(search) == 0: return False return search[0] def get_station(self, title, artist_id=None): if artist_id != None: return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/stream/%s" % (environ['APP_URL'], song_id) def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() @classmethod def generate_api(self): return self(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'])
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()
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)
import parsing if __name__ == '__main__': api = Mobileclient() device_id = input("Input your Device ID: ") api.oauth_login(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(5) 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']) for artist in artists: print(parsing.parser(artist))
'page': 0, 'api_key': jambase_api_key} response = requests.get("http://api.jambase.com/events", params=data) data = response.json() for event in data['Events']: for artist in event['Artists']: artist_name = artist['Name'] print "\n", artist_name, "@", event['Venue']['Name'], # search Google All Access for the artist result = gapi.search_all_access(artist_name) # look at the first result for google_artist_data in result.get('artist_hits', []): google_artist = google_artist_data['artist'] google_artist_id = google_artist['artistId'] print "----> %s (%0.2f)" % (google_artist['name'], google_artist_data['score']) if google_artist_data['score'] > 200: # TODO: confirm high string similarity, as sometimes Google gives high scores to # strange matches. song_data = gapi.get_artist_info(google_artist_id, include_albums=False, max_top_tracks=5, max_rel_artist=0) for top_track in song_data.get('topTracks', []): #if top_track['genre'] in banned_genres: # continue print " + %s [%s]" % (top_track['title'], top_track.get('genre','N/A')) song_id = top_track.get('id') or top_track.get('nid') gapi.add_songs_to_playlist(playlist_id, song_id) break
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 Session(object): def __init__(self): self.api = None self.user = None self.lib_albums = {} self.lib_artists = {} self.lib_tracks = {} self.lib_playlists = {} self.lib_updatetime = 0 self.sitdata = [] self.sitbyid = {} self.sitdataupdtime = 0 def dmpdata(self, who, data): uplog("%s: %s" % (who, json.dumps(data, indent=4))) # Look for an Android device id in the registered devices. def find_device_id(self, data): for entry in data: if "type" in entry and entry["type"] == u"ANDROID": # Get rid of 0x id = entry["id"][2:] uplog("Using deviceid %s" % id) return id return None def login(self, username, password, deviceid=None): self.api = Mobileclient(debug_logging=False) if deviceid is None: logged_in = self.api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) if logged_in: # Try to re-login with a valid deviceid data = self.api.get_registered_devices() #self.dmpdata("registered devices", data) deviceid = self.find_device_id(data) if deviceid: logged_in = self.login(username, password, deviceid) else: logged_in = self.api.login(username, password, deviceid) isauth = self.api.is_authenticated() #uplog("login: Logged in: %s. Auth ok: %s" % (logged_in, isauth)) return logged_in def _get_user_library(self): now = time.time() if now - self.lib_updatetime < 300: return if self.lib_updatetime == 0: data = self.api.get_all_songs() #self.dmpdata("all_songs", data) else: data = self.api.get_all_songs(updated_after=datetime.datetime.fromtimestamp(self.lib_updatetime)) #self.dmpdata("all_songs_since_update", data) self.lib_updatetime = now tracks = [_parse_track(t) for t in data] self.lib_tracks.update(dict([(t.id, t) for t in tracks])) for track in tracks: # We would like to use the album id here, but gmusic # associates the tracks with any compilations after # uploading (does not use the metadata apparently), so # that we can't (we would end up with multiple # albums). OTOH, the album name is correct (so seems to # come from the metadata). What we should do is test the # album ids for one album with a matching title, but we're # not sure to succeed. So at this point, the album id we # end up storing could be for a different albums, and we # should have a special library-local get_album_tracks self.lib_albums[track.album.name] = track.album self.lib_artists[track.artist.id] = track.artist def get_user_albums(self): self._get_user_library() return self.lib_albums.values() def get_user_artists(self): self._get_user_library() return self.lib_artists.values() def get_user_playlists(self): pldata = self.api.get_all_playlists() #self.dmpdata("playlists", pldata) return [_parse_playlist(pl) for pl in pldata] def get_user_playlist_tracks(self, playlist_id): self._get_user_library() # Unfortunately gmusic does not offer incremental updates for # playlists. This means we must download all playlist data any # time we want an update. Playlists include track information # for gmusic songs, so if a user has a lot of playlists this # can take some time. # For now, we only load the playlists one time for performance purposes if len(self.lib_playlists) == 0: data = self.api.get_all_user_playlist_contents() self.lib_playlists = dict([(pl['id'], pl) for pl in data]) tracks = [] if playlist_id in self.lib_playlists: for entry in self.lib_playlists[playlist_id]['tracks']: if entry['deleted']: continue if entry['source'] == u'1': tracks.append(self.lib_tracks[entry['trackId']]) elif 'track' in entry: tracks.append(_parse_track(entry['track']) ) return tracks def create_station_for_genre(self, genre_id): id = self.api.create_station("station"+genre_id, genre_id=genre_id) return id def get_user_stations(self): data = self.api.get_all_stations() # parse_playlist works fine for stations stations = [_parse_playlist(d) for d in data] return stations def delete_user_station(self, id): self.api.delete_stations(id) # not working right now def listen_now(self): print("api.get_listen_now_items()", file=sys.stderr) ret = {'albums' : [], 'stations' : []} try: data = self.api.get_listen_now_items() except Exception as err: print("api.get_listen_now_items failed: %s" % err, file=sys.stderr) data = None # listen_now entries are not like normal albums or stations, # and need special parsing. I could not make obvious sense of # the station-like listen_now entries, so left them aside for # now. Maybe should use create_station on the artist id? if data: ret['albums'] = [_parse_ln_album(a['album']) \ for a in data if 'album' in a] #ret['stations'] = [_parse_ln_station(d['radio_station']) \ # for d in data if 'radio_station' in d] else: print("listen_now: no items returned !", file=sys.stderr) print("get_listen_now_items: returning %d albums and %d stations" %\ (len(ret['albums']), len(ret['stations'])), file=sys.stderr) return ret def get_situation_content(self, id = None): ret = {'situations' : [], 'stations' : []} now = time.time() if id is None and now - self.sitdataupdtime > 300: self.sitbyid = {} self.sitdata = self.api.get_listen_now_situations() self.sitdataupdtime = now # Root is special, it's a list of situations if id is None: ret['situations'] = [self._parse_situation(s) \ for s in self.sitdata] return ret # not root if id not in self.sitbyid: print("get_situation_content: %s unknown" % id, file=sys.stderr) return ret situation = self.sitbyid[id] #self.dmpdata("situation", situation) if 'situations' in situation: ret['situations'] = [self._parse_situation(s) \ for s in situation['situations']] if 'stations' in situation: ret['stations'] = [_parse_situation_station(s) \ for s in situation['stations']] return ret def _parse_situation(self, data): self.sitbyid[data['id']] = data return Playlist(id=data['id'], name=data['title']) def create_curated_and_get_tracks(self, id): sid = self.api.create_station("station"+id, curated_station_id=id) print("create_curated: sid %s"%sid, file=sys.stderr) tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)] #print("curated tracks: %s"%tracks, file=sys.stderr) self.api.delete_stations(sid) return tracks def get_station_tracks(self, id): return [_parse_track(t) for t in self.api.get_station_tracks(id)] def get_media_url(self, song_id, quality=u'med'): url = self.api.get_stream_url(song_id, quality=quality) print("get_media_url got: %s" % url, file=sys.stderr) return url def get_album_tracks(self, album_id): data = self.api.get_album_info(album_id, include_tracks=True) album = _parse_album(data) return [_parse_track(t, album) for t in data['tracks']] def get_promoted_tracks(self): data = self.api.get_promoted_songs() #self.dmpdata("promoted_tracks", data) return [_parse_track(t) for t in data] def get_genres(self, parent=None): data = self.api.get_genres(parent_genre_id=parent) return [_parse_genre(g) for g in data] def get_artist_info(self, artist_id, doRelated=False): ret = {"albums" : [], "toptracks" : [], "related" : []} # Happens,some library tracks have no artistId entry if artist_id is None or artist_id == 'None': uplog("get_artist_albums: artist_id is None") return ret else: uplog("get_artist_albums: artist_id %s" % artist_id) maxrel = 20 if doRelated else 0 maxtop = 0 if doRelated else 10 incalbs = False if doRelated else True data = self.api.get_artist_info(artist_id, include_albums=incalbs, max_top_tracks=maxtop, max_rel_artist=maxrel) #self.dmpdata("artist_info", data) if 'albums' in data: ret["albums"] = [_parse_album(alb) for alb in data['albums']] if 'topTracks' in data: ret["toptracks"] = [_parse_track(t) for t in data['topTracks']] if 'related_artists' in data: ret["related"] = [_parse_artist(a) for a in data['related_artists']] return ret def get_artist_related(self, artist_id): data = self.get_artist_info(artist_id, doRelated=True) return data["related"] def search(self, query): data = self.api.search(query, max_results=50) #self.dmpdata("Search", data) tr = [_parse_track(i['track']) for i in data['song_hits']] ar = [_parse_artist(i['artist']) for i in data['artist_hits']] al = [_parse_album(i['album']) for i in data['album_hits']] #self.dmpdata("Search playlists", data['playlist_hits']) try: pl = [_parse_splaylist(i) for i in data['playlist_hits']] except: pl = [] return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
from gmusicapi import Mobileclient from pprint import pprint api = Mobileclient() api.login('', '', Mobileclient.FROM_MAC_ADDRESS) playlist_name = 'ToListenTo' answer=api.search_all_access('', max_results=1) sweet_track_ids = [] artist_id=answer['artist_hits'][0]['artist']['artistId'] response = api.get_artist_info(artist_id, include_albums=False, max_top_tracks=3, max_rel_artist=0) for song in response['topTracks']: sweet_track_ids.append(song['nid']) playlists = api.get_all_playlists() playlist_id = None for playlist in playlists: if playlist_name in playlist['name']: playlist_id = playlist['id'] if not playlist_id: playlist_id = api.create_playlist(playlist_name) api.add_songs_to_playlist(playlist_id, sweet_track_ids)
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 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 credentials = json.load(open('data/unlocked/credentials.json', 'r')) self.client.login(credentials['username'], credentials['password'], mac) 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 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)
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
class GMusicWrapper(object): def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login( username, password, getenv('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def log(self, log_str): self.logger.debug(log_str) def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return [x[query_type] for x in results[hits_key]] def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.log('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.log('Fetching library...') def get_artist(self, name): """ Fetches information about an artist given its name """ search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) def get_latest_album(self, artist_name=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True) for index, val in enumerate(sorted_list): album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: return album_info return False def get_album_by_artist(self, artist_name, album_id=None): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_info = artist_info['albums'] random.shuffle(album_info) for index, val in enumerate(album_info): album = self._api.get_album_info( album_id=album_info[index]['albumId'], include_tracks=True) if album['albumId'] != album_id and len(album['tracks']) >= 5: return album return False def get_song(self, name, artist_name=None, album_name=None): if artist_name: name = "%s %s" % (artist_name, name) elif album_name: name = "%s %s" % (album_name, name) search = self._search("song", name) if len(search) == 0: return False if album_name: for i in range(0, len(search) - 1): if album_name in search[i]['album']: return search[i] return search[0] def get_promoted_songs(self): return self._api.get_promoted_songs() def get_station(self, title, track_id=None, artist_id=None, album_id=None): if artist_id is not None: if album_id is not None: if track_id is not None: return self._api.create_station(title, track_id=track_id) return self._api.create_station(title, album_id=album_id) return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/alexa/stream/%s" % (getenv('APP_URL'), song_id) def get_thumbnail(self, artist_art): return artist_art.replace("http://", "https://") def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if 'storeId' in track: return track, track['storeId'] elif 'trackId' in track: return self.library[track['trackId']], track['trackId'] else: return None, None def get_artist_album_list(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return "Unable to find the artist you requested." artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list_text = "Here's the album listing for %s: " % artist_name counter = 0 for index, val in enumerate(artist_info['albums']): if counter > 25: # alexa will time out after 10 seconds if the list takes too long to iterate through break album_info = self._api.get_album_info( album_id=artist_info['albums'][index]['albumId'], include_tracks=True) if len(album_info['tracks']) > 5: counter += 1 album_list_text += ( artist_info['albums'][index]['name']) + ", " return album_list_text def get_latest_artist_albums(self, artist_name): search = self._search("artist", artist_name) if len(search) == 0: return False artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True) album_list = artist_info['albums'] sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True) speech_text = 'The latest albums by %s are ' % artist_name counter = 0 for index, val in enumerate(sorted_list): if counter > 5: break else: album_info = self._api.get_album_info( album_id=sorted_list[index]['albumId'], include_tracks=True) if len(album_info['tracks']) >= 5: counter += 1 album_name = sorted_list[index]['name'] album_year = sorted_list[index]['year'] speech_text += '%s, released in %d, ' % (album_name, album_year) return speech_text def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70): # Give each match a score based on its similarity to the requested # name self.log('Fetching library...') request_name = request_name.lower() + artist_name.lower() scored_matches = [] for i, match in enumerate(all_matches): try: name = match['name'].lower() except (KeyError, TypeError): i = match name = all_matches[match]['title'].lower() if artist_name != "": name += all_matches[match]['artist'].lower() scored_matches.append({ 'index': i, 'name': name, 'score': fuzz.ratio(name, request_name) }) sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True) top_scoring = sorted_matches[0] self.log('Fetching library...') best_match = all_matches[top_scoring['index']] # Make sure we have a decent match (the score is n where 0 <= n <= 100) if top_scoring['score'] < minimum_score: return None return best_match def get_genres(self, parent_genre_id=None): return self._api.get_genres(parent_genre_id) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) @classmethod def generate_api(cls, **kwargs): return cls(getenv('GOOGLE_EMAIL'), getenv('GOOGLE_PASSWORD'), **kwargs)
class GMusicWrapper: def __init__(self, username, password, logger=None): self._api = Mobileclient() self.logger = logger success = self._api.login( username, password, environ.get('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS)) if not success: raise Exception("Unsuccessful login. Aborting!") # Populate our library self.library = {} self.indexing_thread = threading.Thread(target=self.index_library) self.indexing_thread.start() def _search(self, query_type, query): try: results = self._api.search(query) except CallFailure: return [] hits_key = "%s_hits" % query_type if hits_key not in results: return [] # Ugh, Google had to make this schema nonstandard... if query_type == 'song': query_type = 'track' return map(lambda x: x[query_type], results[hits_key]) def is_indexing(self): return self.indexing_thread.is_alive() def index_library(self): """ Downloads the a list of every track in a user's library and populates self.library with storeIds -> track definitions """ self.logger.debug('Fetching library...') tracks = self.get_all_songs() for track in tracks: song_id = track['id'] self.library[song_id] = track self.logger.debug('Done! Discovered %d tracks.' % len(self.library)) def get_artist(self, name): """ Fetches information about an artist given its name """ search = self._search("artist", name) if len(search) == 0: return False return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100) def get_album(self, name, artist_name=None): if artist_name: name = "%s %s" % (name, artist_name) search = self._search("album", name) if len(search) == 0: return False return self._api.get_album_info(search[0]['albumId']) def get_song(self, name, artist_name=None): if artist_name: name = "%s %s" % (artist_name, name) search = self._search("song", name) if len(search) == 0: return False return search[0] def get_station(self, title, artist_id=None): if artist_id != None: return self._api.create_station(title, artist_id=artist_id) def get_station_tracks(self, station_id): return self._api.get_station_tracks(station_id) def get_google_stream_url(self, song_id): return self._api.get_stream_url(song_id) def get_stream_url(self, song_id): return "%s/stream/%s" % (environ['APP_URL'], song_id) def get_all_user_playlist_contents(self): return self._api.get_all_user_playlist_contents() def get_all_songs(self): return self._api.get_all_songs() def rate_song(self, song, rating): return self._api.rate_songs(song, rating) def extract_track_info(self, track): # When coming from a playlist, track info is nested under the "track" # key if 'track' in track: track = track['track'] if 'storeId' in track: return (track, track['storeId']) elif 'trackId' in track: return (self.library[track['trackId']], track['trackId']) else: return (None, None) def increment_song_playcount(self, song_id, plays=1, playtime=None): return self._api.increment_song_playcount(song_id, plays, playtime) def get_song_data(self, song_id): return self._api.get_track_info(song_id) @classmethod def generate_api(cls, **kwargs): return cls(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'], **kwargs)