def youtubesr(self, request, user): request = unidecode(request) try: results = (Mobileclient.search(api, request, SONGBLSIZE)['video_hits']) top_result = results[0]['youtube_video'] video_url = "https://www.youtube.com/watch?v=" + top_result['id'] key = top_result['id'] title = top_result['title'] except IndexError: try: self.video = YouTube("https://www.youtube.com/watch?v=" + request) return self.songrequest(" https://www.youtube.com/watch?v=" + request, user) except: return "No results at all" try: self.video = YouTube(video_url) except: return user + " >> That video is unavailable, it's probably age restricted." # Check the queue to see if the song is already in there. self.db = sqliteread('''SELECT id, count(*) FROM songs WHERE key="{0}"'''.format(key)) if self.db[1] > (MAX_DUPLICATE_SONGS - 1): return user + " >> That song is already in the queue." songtime = self.getsongtime(video_url, key) if songtime > (MAXTIME * 60000): return user + " >> That song exceeds the maximum length of " + str(MAXTIME) + " minutes." sqlitewrite('''INSERT INTO songs(name, song, key, time) VALUES("{user}", "{request}", "{key}", "{time}");'''.format(user=user, request=(video_url.replace('"', "'")), key=(key.replace('"', "'")), time=songtime)) removetopqueue() return user + " >> Added: " + title + " to the queue (YT). ID: " + getnewentry()
def songtitlefilter(song_name, redo): blacklist = BLACKLISTED_SONG_TITLE_CONTENTS[:] try: results = (Mobileclient.search(api, song_name, SONGBLSIZE)['song_hits'])[:SONGBLSIZE] except Exception as e: print(e) reconnect() songs = [] for item in results: songs.append(item['track']) #Remove things from the blacklist if theyre explicitly requested for term in blacklist: if term.lower() in song_name.lower(): blacklist.remove(term) #Iterate through the blacklisted contents, then the songs. Last song standing wins. for term in blacklist: if len(songs) == 1: break for song in reversed(songs): if len(songs) == 1: break if term.lower() in song['title'].lower(): print((">Removed: " + song['title'])) songs.remove(song) for item in songs: print((">>>Allowed: " + item['title'])) print((">>>>>>Playing: " + songs[0]['title'])) return songs[redo]
class Client(object): def __init__(self): self.client = Mobileclient() self.client.login(config.gmusic['email'], config.gmusic['password'], Mobileclient.FROM_MAC_ADDRESS) def search_songs(self, query_str): song_hits = self.client.search(unicode(query_str), 8)['song_hits'] songs = [] for song_hit in song_hits: songs.append({ 'title': song_hit['track']['title'], 'artist': song_hit['track']['artist'], 'album': song_hit['track']['album'], 'nid': song_hit['track']['nid'] }) return songs def get_song_url(self, song_nid): song_id = self.__prepare_song_id(song_nid) return self.client.get_stream_url(song_id) def get_song_info(self, song_nid): song_id = self.__prepare_song_id(song_nid) return self.client.get_track_info(song_id) def __prepare_song_id(self, song_nid): return 'T{0}'.format(song_nid)
class GoogleMusicHelper(object): def __init__(self, email=None, password=None): self.google_music_client = Mobileclient() if email and password: self.login(email, password) def login(self, email, password): if self.google_music_client.login(email, password, Mobileclient.FROM_MAC_ADDRESS): return "Logged in to Google" return "Error logging in" def add_song_by_name_to_google_library(self, song="", artist=""): results = self.google_music_client.search(query=song + " " + artist, max_results=1) if results: track = results["song_hits"][0]["track"] return self.google_music_client.add_store_tracks( track.get("storeId") or track.get("nid")) def list_playlists(self): return self.google_music_client.get_all_user_playlist_contents() def sync_playlists_with_library(self, password=None, username=None): if self.google_music_client.login(username, password, Mobileclient.FROM_MAC_ADDRESS): all_tracks = [] for playlist in self.google_music_client.get_all_user_playlist_contents( ): for track in playlist["tracks"]: all_tracks.append(track["track"]) playlist_store_ids = [track["storeId"] for track in all_tracks] all_songs = self.google_music_client.get_all_songs( incremental=False) print all_songs[0] added_store_ids = [] for song in all_songs: store_id = None if song.get("nid"): store_id = song["nid"] elif song.get("storeId"): store_id = song["storeId"] added_store_ids.append(store_id) new_store_ids = set(playlist_store_ids) - set(added_store_ids) new_tracks = [ track for track in all_tracks if track["storeId"] not in added_store_ids ] for storeId in new_store_ids: for track in new_tracks: if track["storeId"] == storeId: break print track['title'] + " by " + track["artist"] print self.google_music_client.add_store_tracks(storeId)
class Playlist(): ''' Parent class for specific types of playlists, like: Live Setlist Playlist setlist based on the set of songs played at a live concert Upcoming Concert Playlist setlist generated from a list of bands that have upcoming concerts in an area ''' def __init__(self, make=False): self.setup_logging() self.api = Mobileclient() self.logged_in = self.api.login( EMAIL, TOKEN, # Mobileclient.FROM_MAC_ADDRESS) DEVICE_ID) if self.logged_in: self.info("Logged into GPMAA successfully") def setup_logging(self): logger_name = '.'.join([__name__, __class__.__name__]) self.logger = logging.getLogger(logger_name) logging.getLogger('gmusicapi.protocol.shared').setLevel(logging.INFO) logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING) def error(self, msg): self.logger.error(msg) def info(self, msg): self.logger.info(msg) def debug(self, msg): self.logger.debug(msg) def search(self, query): ''' This function got pulled to the parent class because we'll always be searching for things and wanting the song results. They're always going to have to be processed this way because of how the search result is formatted. The result is a list of song dictionaries with keys such as storeId, artist, etc. ''' res = self.api.search(query)['song_hits'] res = [song['track'] for song in res] return res def create_playlist(self, song_ids, name, description='', public=False): self.info("Creating {}".format(name)) self.id = self.api.create_playlist(name, description, public) self.api.add_songs_to_playlist(self.id, song_ids) def delete_playlist(self): if hasattr(self, 'id') and self.id is not None: self.info("Deleting playlist id: %s".format(self.id)) self.api.delete_playlist(self.id) else: self.info("Can't delete a playlist without its id")
class Plugin: name = 'gmusic' def __init__(self, username, password): self.client = Mobileclient() self.client.login(username, password, Mobileclient.FROM_MAC_ADDRESS) # self.webclient = Webclient() # self.webclient.login(username, password) def get_tracks(self, artist=None, album=None): """ Fetches tracks from api. If no filter is defined, it will get user tracks """ return TrackList(self.client.get_all_songs()) def get_playlists(self): """ Get playlists and radios """ playlists = [] for playlist in self.client.get_all_user_playlist_contents(): tracks = TrackList([ self.client.get_track_info(x['trackId']) for x in playlist['tracks'] ]) playlists.append(PlayList(playlist['name'], tracks)) return playlists def stream(self, track): def _stream(url): inp = requests.get(url, stream=True) chunk_size = 1024 for chunk in inp.iter_content(chunk_size): if not chunk: continue yield chunk song_id = track.uri.split(':')[-1] return _stream(self.client.get_stream_url(song_id)) def search(self, keywords, matches): results = self.client.search(keywords) if matches == 'artist': return {'artists': results.get('artist_hits', [])} elif matches == 'album': return {'albums': results.get('album_hits', [])} elif matches == 'tracks': return {'tracks': results.get('song_hits', [])} elif matches == 'all': return { 'artists': results.get('artist_hits', []), 'albums': results.get('album_hits', []), 'tracks': results.get('song_hits', []) }
class Jukebox: def __init__(self, credentials_file): with open(credentials_file) as f: credentials = yaml.load(f) email = credentials['email'] password = credentials['password'] android_id = credentials['android_id'] self.api = Mobileclient() self.__authenticated = self.api.login(email, password, android_id) self.player = vlc.MediaPlayer() def search(self, query): self.__is_authenticated() return self.api.search(query) def get_track_url(self, track_id): self.__is_authenticated() return self.api.get_stream_url(track_id) def set_track(self, filepath): self.player.set_media( vlc.get_default_instance().media_new('songs/' + filepath + '.mp3')) def play(self): self.__is_authenticated() self.player.play() def pause(self): self.__is_authenticated() self.player.pause() def stop(self): self.__is_authenticated() self.player.stop() def get_length(self): self.__is_authenticated() return self.player.get_length() def get_current_time(self): self.__is_authenticated() return self.player.get_time() def is_playing(self): self.__is_authenticated() return self.player.is_playing() def __is_authenticated(self): if not self.__authenticated: raise NotAuthenticatedError def create_station(self, store_id): self.__is_authenticated() station_id = self.api.create_station('station', track_id=store_id) station_tracks = self.api.get_station_tracks(station_id) return station_tracks
class GMusicAPI(): def __init__(self, username=None, encrypted_pass=None): self._api = Mobileclient() self.logged_in = False if username and encrypted_pass: self.login(username, encrypted_pass) def login(self, username, encrypted_pass): self.logged_in = self._api.login(username, decrypt(encrypted_pass), Mobileclient.FROM_MAC_ADDRESS) def logout(self): self._api.logout() self.logged_in = False def clear_playlist(self, playlist_name): playlists = self._api.get_all_user_playlist_contents() playlist = [playlist for playlist in playlists if playlist['name'] == playlist_name][0] entry_ids = [entry['id'] for entry in playlist['tracks']] removed = self._api.remove_entries_from_playlist(entry_ids) return len(removed) def search(self, *args): """ Returns the best-fitting track dict for the given information. :param args: Strings which can be artist, song title, album etc. :return: """ query = sanitise_query(' '.join(args)) result = self._api.search(query) song_results = result['song_hits'] if not song_results: warnings.warn('Warning: query {} returned no song hits.'.format(query)) return None tracks = [song_result['track'] for song_result in song_results[:5]] for track in tracks: if not is_tribute(track, query): return track warnings.warn('Warning: query {} returned no non-tribute song hits.'.format(query)) return None def get_playlist_id(self, playlist_name): for playlist in self._api.get_all_playlists(): if playlist['name'] == playlist_name: return playlist['id'] raise ValueError("Playlist '{}' not found".format(playlist_name)) def add_songs(self, playlist_name, tracks): playlist_id = self.get_playlist_id(playlist_name) track_ids = [track['nid'] for track in tracks if track] self._api.add_songs_to_playlist(playlist_id, track_ids)
class GoogleMusic(object): def __init__(self): self.webclient = Webclient() self.mobileclient = Mobileclient() def is_authenticated(self): if not self.webclient.is_authenticated(): if self.mobileclient.is_authenticated(): return True return False def login(self, username, password): if not self.is_authenticated(): try: self.mobileclient.login(username, password, Mobileclient.FROM_MAC_ADDRESS) self.webclient.login(username, password) except Exception as e: raise Exception('Couldn\'t log into Google Music: ' + e.message) def search(self, query, kind): if self.is_authenticated(): results = self.mobileclient.search(query)[kind + '_hits'] return results def get_track(self, store_id): return self.mobileclient.get_track_info(store_id) def save_stream(self, track, destination): if self.is_authenticated(): with open(destination, 'w+b') as stream_file: url = self.mobileclient.get_stream_url(track.get('storeId')) stream_file.truncate(0) stream_file.seek(0, 2) audio = self.webclient.session._rsession.get(url).content stream_file.write(audio) tag = easyid3.EasyID3() tag['title'] = track.get('title').__str__() tag['artist'] = track.get('artist').__str__() tag['album'] = track.get('album').__str__() tag['date'] = track.get('year').__str__() tag['discnumber'] = track.get('discNumber').__str__() tag['tracknumber'] = track.get('trackNumber').__str__() tag['performer'] = track.get('albumArtist').__str__() tag.save(destination) tag = mp3.MP3(destination) tag.tags.add( id3.APIC(3, 'image/jpeg', 3, 'Front cover', urllib.urlopen(track.get('albumArtRef')[0].get('url')).read()) ) tag.save()
def add_songs_to_library(self): api = Mobileclient() logged_in = api.login('*****@*****.**', '1944_D-d@y', Mobileclient.FROM_MAC_ADDRESS) playlist = api.create_playlist("JackFM #2", "This is what Jack is playing next") for sn in crawler.songs: print sn.name search_res = api.search(sn.name + " " + sn.artist, 10) songid = search_res["song_hits"][0]["track"]["storeId"] api.add_songs_to_playlist(playlist, songid)
class Plugin: name = 'gmusic' def __init__(self, username, password): self.client = Mobileclient() self.client.login(username, password, Mobileclient.FROM_MAC_ADDRESS) # self.webclient = Webclient() # self.webclient.login(username, password) def get_tracks(self, artist=None, album=None): """ Fetches tracks from api. If no filter is defined, it will get user tracks """ return TrackList(self.client.get_all_songs()) def get_playlists(self): """ Get playlists and radios """ playlists = [] for playlist in self.client.get_all_user_playlist_contents(): tracks = TrackList([self.client.get_track_info(x['trackId']) for x in playlist['tracks']]) playlists.append(PlayList(playlist['name'], tracks)) return playlists def stream(self, track): def _stream(url): inp = requests.get(url, stream=True) chunk_size = 1024 for chunk in inp.iter_content(chunk_size): if not chunk: continue yield chunk song_id = track.uri.split(':')[-1] return _stream(self.client.get_stream_url(song_id)) def search(self, keywords, matches): results = self.client.search(keywords) if matches == 'artist': return {'artists': results.get('artist_hits', [])} elif matches == 'album': return {'albums': results.get('album_hits', [])} elif matches == 'tracks': return {'tracks': results.get('song_hits', [])} elif matches == 'all': return {'artists': results.get('artist_hits', []), 'albums': results.get('album_hits', []), 'tracks': results.get('song_hits', [])}
def addTracksGPM(trackList, api): #List of Song id's idList = [] #Searches store for each track id and adds it too list for i in range(len(trackList)): tracks = Mobileclient.search(api, trackList[i], 50)['song_hits'][0]['track'] idList.append(tracks['storeId']) #Add tracks to user library if (len(idList) > 0): Mobileclient.add_store_tracks(api, idList) else: print("All Google Play Music tracks are already added") return None
class GmSession: def __init__(self): self.session = Mobileclient() self.device_id = gmusic_device_id self.cred_path = gmusic_cred_path self.playlist_id = gmusic_playlist_id def login(self): self.session.oauth_login(device_id=self.device_id, oauth_credentials=self.cred_path) def logout(self): self.session.logout() def search(self, artist, song): search_string = f'{artist.lower()}' + f', {song.lower()}' results = self.session.search(search_string, max_results=20) if len(results['song_hits']) > 0: first_result = results['song_hits'][0]['track'] if 'storeId' in first_result.keys(): return first_result['storeId'] elif 'id' in first_result.keys(): print('bad id') return first_result['id'] elif 'nid' in first_result.keys(): print('bad id') return results['song_hits'][0]['track']['nid'] else: print('No songs found...') def add_to_playlist(self, song_list): playlists = self.session.get_all_user_playlist_contents() for playlist in playlists: if playlist['id'] == self.playlist_id: to_remove = [] for track in playlist['tracks']: to_remove.append(track['id']) print('Adding new songs...') res = self.session.add_songs_to_playlist( self.playlist_id, song_list) print('Removing previous songs...') out = self.session.remove_entries_from_playlist(to_remove) print('Finished')
def ask_for_song(song): api = Mobileclient() # mm.perform_oauth('C:\\Users\\mitsu\\Documents\\Python\\projects\\gmusicapi-develop\\oauth.cred') api.oauth_login(Mobileclient.FROM_MAC_ADDRESS, 'oauth.cred') api.login songs = api.search(song, 2) # print(json.dumps((library), indent=4)) title = songs['song_hits'][0]['track']['title'] artist = songs['song_hits'][0]['track']['artist'] song_id = songs['song_hits'][0]['track']['storeId'] stream_url = api.get_stream_url(song_id) track = [title, artist, stream_url] api.logout() return track
class GoogleMusicProvider: api = None def __init__(self, login, password, android_id, *args): self.api = Mobileclient() auth = self.api.login(login, password, android_id) if not auth: self.api.get print('GPM login: {0}'.format(auth)) def search(self, query): return self.api.search(query, max_results=1)['song_hits'][0]['track'] def getUrl(self, id): return self.api.get_stream_url(id) def add_playlist(self, name): return self.api.create_playlist(name) def add_to_playlist(self, trackId, playlistId): self.api.add_songs_to_playlist(playlist_id=playlistId, song_ids=trackId)
add_search_string(tracks, search_strings) else: print("Can't get token for", username) print(f'Detected {len(search_strings)} songs to transfer.') mm = Mobileclient() mm.perform_oauth() mm.oauth_login(Mobileclient.FROM_MAC_ADDRESS) playlist_id = mm.create_playlist(playlist_name) print(f'Playlist \'{playlist_name}\' created.') found_songs = 0 for row in search_strings: print(f'\t Searching \'{row}\'.') search_result = mm.search(row) songs = search_result.get('song_hits') song_id = None if len(songs) > 0: song_id = songs[0].get('track').get('storeId') found_songs += 1 else: print('Song not found.') continue mm.add_songs_to_playlist(playlist_id, song_id) print(f'Imported {found_songs} songs.')
class GoogleMusic: __metaclass__ = Singleton SKIP_ARTIST = ['vox freaks'] def __init__(self): self.gmusicapi = Mobileclient(debug_logging=False) logged_in = self.gmusicapi.login( email=Config.GOOGLE_MUSIC_USER_NAME, password=Config.GOOGLE_MUSIC_APP_PASSWORD, locale='en_US', android_id=Mobileclient.FROM_MAC_ADDRESS) if not logged_in: raise Exception('Unable to log in to GoogleMusic') def get_lib_from_gmusic(self): lib = self.gmusicapi.get_all_songs() lib_list = [] song = None for song in lib: song = Song(artist=song['artist'], title=song['title']) lib_list.append(song) return lib_list def delete_playlist_if_exists(self, name): all_playlists = self.gmusicapi.get_all_playlists() for playlist in all_playlists: if playlist['name'] == name: self.gmusicapi.delete_playlist(playlist['id']) def create_playlist(self, name, description, public=True): return self.gmusicapi.create_playlist(name=name, description=description, public=public) def add_songs_to_playlist(self, playlist_id, song_ids=None, song_df=None): if (song_ids is None and song_df is None): raise ValueError('Need song_ids or song_dfs to add to playlist') if song_df is not None and (not song_df.empty): song_ids = song_df.google_music_store_id.dropna().tolist() return self.gmusicapi.add_songs_to_playlist(playlist_id, song_ids) def gmusic_constrained_search(self, song, query, strict): song_hits = query['song_hits'] for result in song_hits: track = result['track'] if track['albumArtist'].lower() in SKIP_ARTIST: continue if song.remix: if "remix" not in track['title'].lower(): continue else: if "remix" in track['title'].lower(): continue if strict: full_title = "{} - {}".format(track['albumArtist'], track['title']) score = difflib.SequenceMatcher(None, song.full_title.lower(), full_title.lower()).ratio() if score < 0.6: continue return track return None def search_song(self, song, strict=False): try: first_query = self.gmusicapi.search(song.full_title) first_result = self.gmusic_constrained_search( song, first_query, strict) if first_result is None: second_query = self.gmusicapi.search(song.full_title_stripped) first_result = self.gmusic_constrained_search( song, second_query, strict) if first_result is None: logger.warning( 'No satisfactory result found in Google Music for {}'. format(song.full_title)) return first_result except Exception as e: logger.debug('Exception: {}'.format(e)) logger.info(u'Skipped {}'.format(song.title)) return None def get_store_id(self, result): store_id = None if result: if result.has_key('storeId'): store_id = result['storeId'] return store_id def get_google_rating(self, result): rating = None if result: if result.has_key('rating'): return result['rating'] return rating def update_playlist(self, playlist, public=True, exclude_0_rating=True): #Delete Playlist if present. logger.info(u'Updating the playlist {} in GoogleMusic'.format( playlist.name)) self.delete_playlist_if_exists(playlist.name) #Create Playlist playlist_id = self.create_playlist(name=playlist.name, description=playlist.description, public=public) if exclude_0_rating: playlist.song_df = playlist.song_df[ playlist.song_df['google_music_rating'] != '1'] self.add_songs_to_playlist(playlist_id=playlist_id, song_df=playlist.song_df)
class PlaylistSyncer: def __init__(self): self.gsongs = set() self.spsongs = set() with open('creds.json') as f: self.creds = json.loads(f.read()) self.cookie_jar = browsercookie.chrome() self.gapi = Mobileclient() self.glogged_in = self.gapi.login(self.creds['gmusic_username'], self.creds['gmusic_password'], Mobileclient.FROM_MAC_ADDRESS) self.spcc = spoauth2.SpotifyClientCredentials(client_id=self.creds['spotify_client_id'], client_secret=self.creds['spotify_client_secret']) self.spapi = spotipy.Spotify(auth=self.spcc.get_access_token()) self.force_load_gmusic_playlist() self.load_spotify_playlist() def force_load_gmusic_playlist(self): url = 'https://play.google.com/music/services/loaduserplaylist' params = {'format': 'jsarray'} # TODO: See if there's some way to create this cookie rather than manually setting it # Will it expire? # A: yes, but only every few months # xt, on the other hand response = requests.post(url, params=params, data='[[],["{}"]]'.format(self.creds['gmusic_playlist_id']), cookies=self.cookie_jar) tracks = response.json()[1][0] self.gsongs = set() for track in tracks: self.gsongs.add(Song(track[1], track[3])) def load_spotify_playlist(self): pl = self.spapi.user_playlist(self.creds['spotify_user_id'], self.creds['spotify_playlist_id']) tracks = pl['tracks'] for track in tracks['items']: t = track['track'] self.spsongs.add(Song(t['name'], ' & '.join(a['name'] for a in t['artists']))) while tracks['next']: tracks = self.spapi.next(tracks) for track in tracks['items']: t = track['track'] self.spsongs.add(Song(t['name'], ' & '.join(a['name'] for a in t['artists']))) def symmetric_song_difference(self): diff = set() # lmao can't use anything that uses hashes diff.update(unique_in(self.gsongs, self.spsongs)) diff.update(unique_in(self.spsongs, self.gsongs)) return diff def update_gmusic(self): to_add = unique_in(self.spsongs, self.gsongs) for song in to_add: results = self.gapi.search(str(song)) hits = results.get('song_hits') if hits: pass print(results) # search for song # add song to playlist return to_add def update_spmusic(self): to_add = unique_in(self.gsongs, self.spsongs) return to_add def sync(self): self.update_gmusic() self.update_spmusic()
class Gmusic(object): """Class to handle Google Music-related functionality""" def __init__(self, bot): """ init """ self.bot = bot self.mob = Mobileclient() def login(self, username, password, android_id=Mobileclient.FROM_MAC_ADDRESS): """ login method """ self.mob.login(username, password, android_id) return self.mob.is_authenticated() def search(self, searchterms): """ search for stuff """ hits = self.mob.search("{0}".format(searchterms)) return hits def create_playlist(self, name, song_ids, public=True): """ create new playlist named 'name', containing songs with 'song_id' """ playlist_id = self.mob.create_playlist(name, description="Bot Playlist", public=public) self.mob.add_songs_to_playlist(playlist_id, song_ids) return playlist_id def _make_playlist_share_link(self, share_token): base_share_url = "https://play.google.com/music/playlist" return "{}/{}".format(base_share_url, share_token) def share_playlist(self, playlist_id): try: [share_token] = [ plist['shareToken'] for plist in self.mob.get_all_playlists() if plist['id'] == playlist_id ] return self._make_playlist_share_link(share_token) except ValueError: return "Cannot find playlist" def get_best_song_match(self, artist, title): hits = self.search("{0} {1}".format(artist, title)) tracks = self.filter_to_song_minimum_info(self.get_songs(hits)) similarities = [(similarity(track['artist'], artist, track['title'], title), track) for track in tracks] sorted_tracks = sorted(similarities, key=lambda k: k[0]) best_track = None if len(sorted_tracks) > 0: best_track = sorted_tracks[0][1] return best_track def get_best_album_match(self, artist, album): hits = self.search("{0} {1}".format(artist, album)) albums = self.get_albums(hits) similarities = [(similarity(a['artist'], artist, a['album'], album), a) for a in albums] sorted_albums = sorted(similarities, key=lambda k: k[0]) if len(sorted_albums) == 0: return [] best_album = sorted_albums[0][1] album_info = self.mob.get_album_info(best_album['albumId']) store_ids = [t['storeId'] for t in album_info['tracks']] print("Store ids in best_album_match: {0}".format(store_ids)) return store_ids def format_best_match(self, artist, title): track = self.get_best_song_match(artist, title) share_base_url = 'https://play.google.com/music/m/' return "{0} {1} {2} - {3}{4}".format(track['artist'], track['album'], track['title'], share_base_url, track['storeId']) def get_albums(self, results): albums = [album.get('album', None) for album in results['album_hits']] album_details = [{ 'artist': a['artist'], 'album': a['name'], 'albumId': a['albumId'] } for a in albums] return album_details def get_songs(self, results): return [song.get('track', None) for song in results['song_hits']] def filter_to_song_minimum_info(self, results): return [{ 'artist': song.get('artist', None), 'album': song.get('album', None), 'title': song.get('title', None), 'storeId': song.get('storeId', None) } for song in results] def convert_spotify_embed_to_gmusic(self, url): s_list = SpotifyPlaylist(url) title = s_list.title best_matches = [ self.get_best_song_match(i.artist, i.track) for i in s_list.items ] filtered_matches = [i for i in best_matches if i is not None] store_ids = [i.get('storeId') for i in filtered_matches] new_plist = self.create_playlist(title, store_ids) return self.share_playlist(new_plist) def convert_hbih_to_gmusic(self, url): hbih_list = HBIHPlaylist(url) title = hbih_list.title store_ids = [] for item in hbih_list.items: album_store_ids = self.get_best_album_match(item[0], item[1]) print("Adding store ids: {0}".format(album_store_ids)) store_ids.extend(album_store_ids) store_id_set = IndexedSet(store_ids) no_dupes_store_ids = list(store_id_set) new_plist = self.create_playlist(title, no_dupes_store_ids[0:1000]) return self.share_playlist(new_plist) def create_playlist_from_song_names(self, artist, songs): year = datetime.datetime.now().year title = "{} setlist ({})".format(artist, year) best_matches = [self.get_best_song_match(artist, s) for s in songs] filtered_matches = [i for i in best_matches if i is not None] store_ids = [i.get('storeId') for i in filtered_matches] new_plist = self.create_playlist(title, store_ids) return self.share_playlist(new_plist) def get_newest_playlists(self, count=5): """ return 'count' newest playlists """ all_plists = self.mob.get_all_playlists() sorted_plists = sorted(all_plists, key=lambda k: k['lastModifiedTimestamp'], reverse=True) if count > 0: newest_plists = sorted_plists[:count] else: newest_plists = sorted_plists info = [{ 'name': p['name'], 'share': self._make_playlist_share_link(p['shareToken']) } for p in newest_plists] return info def get_all_playlists(self): """ return all playlists """ return self.get_newest_playlists(0) # 0 = return everything def find_playlists(self, searchterm): """ find all playlists that have a name containing 'searchterm' """ all_plists = self.get_all_playlists() all_matches = all_plists all_terms = searchterm.split(' ') for term in all_terms: all_matches = [ p for p in all_matches if p['name'].lower().find(term.lower()) != -1 ] return all_matches
#If we do have this song already, set the song_already_in_library flag to 1 so we know later on for owned_song in owned_songs: if owned_song["title"] == song_title and owned_song[ "artist"] == artist_name and owned_song[ "albumArtist"] == artist_name and explicit_agree( owned_song["explicitType"], args.explicit): #The song is already in our library, skip to the next song in songs_to_add song_already_in_library = 1 library_song_id = owned_song["storeId"] break #Song isn't already in our library if song_already_in_library == 0: #Perform the actual search of the play music store results = mc.search(artist_name + " " + song_title) #Traverse the results, but only if the list of songs has things in it if len(results) > 0 and len(results["song_hits"]) > 0: for result in results["song_hits"]: track = result["track"] title = track["title"] artist = track["artist"] album_artist = track["albumArtist"] length = track["durationMillis"] #I'm assuming this tells us whether the track is the explicit version, but it apparently has values 1, 2, or 3 and for the life of me I can't find documentation on which is which #Through testing, it appears af 1 means the song is marked explicity, 2 has no marking? and 3 may mean its specifically a "clean" version. try: explicit = track["explicitType"] except:
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)
exit('No songs extracted - exiting.') # Login to Google Play Music and create new playlist from fetched songs api = Mobileclient() logged_in = api.login(google_user, google_password, Mobileclient.FROM_MAC_ADDRESS) if not logged_in: exit('Could not log in to Google Play Music') # Search songs in Google Play Music and obtain track IDs storeIds = [] not_found_songs = [] for song in songs: print u'Searching for "{s[artist]} - {s[title]}"...'.format(s=song) search_term = u'{s[artist]} {s[title]}'.format(s=song) result = api.search(search_term) song_hits = result['song_hits'] print 'Found {} song(s).'.format(len(song_hits)) if len(song_hits) == 0: not_found_songs.append(song) else: song_hit = song_hits[0] # Only add first search result for this song to playlist track = song_hit['track'] msg = u'Adding "{t[artist]} - {t[title]}"'.format(t=track) if 'album' in track.keys(): msg = msg + u' from album "{t[album]}"'.format(t=track) if 'year' in track.keys(): msg = msg + u' ({t[year]})'.format(t=track)
pw = getpass.getpass() if not client.login(args.username, pw, Mobileclient.FROM_MAC_ADDRESS): print("Authentication failed. Please check the provided credentials.") with open(args.source) as f: data = json.load(f) if args.dryrun: print("[/!\] We're currently running in dry-run mode") for playlist in data["playlists"]: if args.dryrun: print("Checking importability of %s" % playlist["title"]) else: print("Importing %s" % playlist["title"]) toimport = [] for track in playlist["tracks"]: query = "%s %s" % (track["title"], track["artist"]) results = client.search(query) match = None if args.verbose: print("Fetching matches for %s" % query) for hit_i, hit in enumerate(results["song_hits"]): trackDeets = hit["track"]["title"] match = hit["track"]["storeId"] print("Found match:\n%s" % trackDeets) break if match is not None: toimport.append(match) else: print("[!!!] No good match for %s" % query) if not args.dryrun and toimport: playlist_id = client.create_playlist(playlist["title"]) client.add_songs_to_playlist(playlist_id, toimport)
if path_data[path_piece] == 'user': spotify_user_id = path_data[path_piece + 1] if path_data[path_piece] == 'playlist': spotify_playlist_id = path_data[path_piece + 1] # authenticate gapi = Mobileclient() logged_in = gapi.login(g_username, g_password, Mobileclient.FROM_MAC_ADDRESS) client_credentials_manager = spotipy.oauth2.SpotifyClientCredentials( client_id=SPOTIFY_CLIENT_ID, client_secret=SPOTIFY_CLIENT_SECRET) sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) # get playlist playlist = sp.user_playlist(spotify_user_id, spotify_playlist_id) track_response = playlist['tracks'] gplaylist = [] for song in track_response['items']: song_search_string = "%s - %s - %s" % (song['track']['artists'][0]['name'], song['track']['name'], song['track']['album']['name']) song_result = gapi.search(song_search_string) gplaylist.append(song_result['song_hits'][0]['track']['storeId']) if g_playlist_name == '': g_playlist_name = playlist['name'] playlist_id = gapi.create_playlist(g_playlist_name, 'Imported From Spotify') gapi.add_songs_to_playlist(playlist_id, gplaylist)
class GMusicDownloader(threading.Thread): api = None library = list() filtered_library = list() download_workers = list() max_threads = None config_error = False loggedin = False playlists = None fetchedlists = None # type: list filecreationlock = threading.Lock() trackqueue = Queue() threadqueue = Queue() player = None current_displayed_content_type = "Track" def __init__(self, queue: Queue): super().__init__() self.communicationqueue = queue self.api = Mobileclient() config = configparser.ConfigParser() config.read("config.ini") self.load_settings(config) if not self.config_error: self.threaded_api_query(self.login) for i in range(self.max_threads): self.threadqueue.put("I'm a thread") def login(self): if not self.loggedin: self.api.login(self.username, self.password, Mobileclient.FROM_MAC_ADDRESS) print("logged in") self.library = self.api.get_all_songs() print("songs fetched") self.communicationqueue.put({ "login": self.username, "library": self.library }) self.loggedin = True def get_directory_path(self, track: dict, and_create=False): artist = self.slugify(track["artist"]) album = self.slugify(track["album"]) artist_path = os.path.join(self.music_directory, artist) album_path = os.path.join(artist_path, album) if and_create: if not os.path.exists(artist_path): os.makedirs(artist_path) if not os.path.exists(album_path): os.makedirs(album_path) return album_path def get_file_path(self, track: dict, directory_path: str = None): if directory_path is not None: return os.path.join(directory_path, self.slugify(track["title"]) + self.file_type) else: return os.path.join(self.get_directory_path(track), self.slugify(track["title"]) + self.file_type) def threaded_stream_downloads(self, tracklist: list): for i in range(self.max_threads): threading.Thread(target=self.__downloadworker).start() for track in tracklist: self.trackqueue.put(track) # stop threads when they're done for i in range(self.max_threads): self.trackqueue.put(None) def __downloadworker(self): while True: permission = self.threadqueue.get(block=True) track = self.trackqueue.get() if track is None: self.threadqueue.put(permission) break self.communicationqueue.put({"downloading": track}) self.stream_download(track) self.threadqueue.put(permission) self.trackqueue.task_done() def stream_download(self, track: dict): track_title = track["title"] self.filecreationlock.acquire() directory_path = self.get_directory_path(track, and_create=True) self.filecreationlock.release() file_path = self.get_file_path(track, directory_path) if not os.path.exists(file_path): dl = 0 track_url = self.api.get_stream_url(track['id']) response = requests.get(track_url, stream=True) # total_length = int(response.headers.get('content-length')) with open(file_path, "wb") as songfile: for chunk in response.iter_content(chunk_size=self.chunk_size): songfile.write(chunk) dl += len(chunk) print(track_title, " done.") # next(filter(lambda t: t == track, self.filtered_library)) else: print(track_title + " already exists, skipping") self.add_tags(file_path, track) self.communicationqueue.put({"download complete": track}) def search_library(self, search_term: str): if search_term == "*": self.filtered_library = self.library else: self.filtered_library = list( filter(lambda t: search_term in t["artist"], self.library)) @staticmethod def threaded_api_query(worker: types.FunctionType, *args): threading.Thread(target=worker, args=(*args, )).start() def search_worker_thread(self, searchstring: str): search_results = self.api.search(self.slugify(searchstring)) def parse_song_hit(song_hit): # couldn't fit it into a lambda :'( track = song_hit["track"] track["id"] = track["storeId"] return track self.filtered_library = list( map(parse_song_hit, search_results["song_hits"])) self.communicationqueue.put({"search results": True}) @staticmethod def slugify(value): """ Normalizes string, removes non-alpha characters """ value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('utf-8') value = re.sub('[^\w\s-]', '', value).strip() return value def check_filtered_tracks_for_download(self): for track in self.filtered_library: if "saved" not in track: if os.path.exists(self.get_file_path(track)): track["saved"] = "√" else: track["saved"] = "" def sort(self, sort: str, is_reversed: bool, content_type: str): if content_type == "Track": self.filtered_library = sorted(self.filtered_library, key=lambda k: k[sort], reverse=is_reversed) elif content_type == "Playlist": def lazy_hack_for_playlist_sort(playlist: dict): if sort == "title": return playlist["name"] if sort == "artist": return playlist["ownerName"] if sort == "album": return playlist['type'] self.playlists = sorted(self.playlists, key=lazy_hack_for_playlist_sort, reverse=is_reversed) def load_settings(self, config: configparser.ConfigParser): try: account = config["Account"] self.username = account["username"] self.password = account["password"] settings = config["Settings"] self.music_directory = settings["music_directory"] self.file_type = "." + settings["file_type"] self.chunk_size = settings.getint("chunk_size") self.max_threads = settings.getint("download_threads", 5) self.config_error = False except KeyError as e: self.communicationqueue.put({ "error": { "title": "Configuration Error GMusicDownloader", "body": "Could not find " + e.args[0] + " in preferences, please update prefs and try again" } }) self.config_error = True def add_tags(self, filepath: str, track: dict): try: tags = EasyID3(filepath) except ID3NoHeaderError: tags = mutagen.File(filepath, easy=True) tags.add_tags() tags["tracknumber"] = str( track["trackNumber"]).encode("utf-8").decode("utf-8") tags["title"] = track["title"] tags["artist"] = track["artist"] tags["album"] = track["album"] tags["discnumber"] = str( track["discNumber"]).encode("utf-8").decode("utf-8") tags["genre"] = track["genre"] tags["composer"] = track["composer"] tags["albumartist"] = track["albumArtist"] if "beatsPerMinute" in track and not track["beatsPerMinute"] == 0: tags["bpm"] = str( track["beatsPerMinute"]).encode("utf-8").decode("utf-8") # TODO store Year. will have to use standard ID3 instead of easy tags.save(v2_version=3) def open_playlists(self): if self.playlists is None: self.playlists = self.api.get_all_playlists() if not "lastAdded" in map(lambda p: p["id"], self.playlists): self.add_automatic_playlists() self.communicationqueue.put({"playlists loaded": self.playlists}) def add_automatic_playlists(self): last_added = { "id": 'lastAdded', "tracks": sorted(self.library, key=lambda t: t['creationTimestamp'], reverse=True), "name": "Last Added", "ownerName": "System", "type": "Automatic" } self.playlists.append(last_added) thumbs_up = { "id": 'thumbsup', "tracks": filter(lambda t: t['rating'] > 3, self.library), "name": "Thumbs Up", "ownerName": "System", "type": "Automatic" } self.playlists.append(thumbs_up) def fetch_all_playlists_and_return_one_with_iid(self, iid: str): # TODO make this work for non user owned playlists. should use get_shared_playlist_contents for those. if self.fetchedlists is None: self.fetchedlists = self.api.get_all_user_playlist_contents( ) # type: list # noinspection PyTypeChecker for playlist in self.fetchedlists: existing_playlist = next( filter(lambda p: p["id"] == playlist["id"], self.playlists)) existing_playlist["tracks"] = playlist["tracks"] # noinspection PyTypeChecker for playlist in self.playlists: if playlist["id"] == iid: playlist_tracks = playlist["tracks"] if playlist["type"] == "Automatic": self.filtered_library = playlist_tracks else: self.filtered_library = self.songs_from_playlist( playlist_tracks) self.communicationqueue.put({"search results": True}) return self.current_displayed_content_type = "Playlist" self.communicationqueue.put( {"error": { "title": "Could not fetch playlist", "body": ":(" }}) def songs_from_playlist(self, playlist): tracks = list() for id_dict in playlist: if "track" in id_dict: track = id_dict["track"] else: track = next( filter(lambda t: t["id"] == id_dict['trackId'], self.library), None) if "id" not in track: track["id"] = track["storeId"] tracks.append(track) return tracks def play_song(self, trackid): try: import vlc if isinstance(trackid, dict): print('playing track', trackid['title']) trackid = trackid["id"] if trackid is None and self.player is not None: self.player.pause() return url = self.api.get_stream_url(trackid) if self.player is None: self.player = vlc.MediaPlayer(url) # type: vlc.MediaPlayer else: # TODO: this is terrible and I should fix it. Lucky it works! self.player = vlc.MediaPlayer(url) self.player.play() self.player.event_manager().event_attach( vlc.EventType.MediaPlayerEndReached, self.song_complete, trackid) except ImportError: self.communicationqueue.put({ "error": { "title": "Could not import VLC", "body": "Please make sure you have 64-bit VLC installed" } }) @callbackmethod def song_complete(self, event, trackId): if self.current_displayed_content_type == "Track": libiterator = iter(self.filtered_library) for track in libiterator: if track["id"] == trackId: self.play_song(next(libiterator, None)) return
def playMusic(): #play music from... play music. api = Mobileclient() api.search("token code red", max_results=1)
class tizgmusicproxy(object): """A class for logging into a Google Play Music account and retrieving song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" # pylint: disable=too-many-instance-attributes,too-many-public-methods def __init__(self, email, password, device_id): self.__gmusic = Mobileclient() self.__email = email self.__device_id = device_id self.logged_in = False self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_song = None userdir = os.path.expanduser('~') tizconfig = os.path.join(userdir, ".config/tizonia/." + email + ".auth_token") auth_token = "" if os.path.isfile(tizconfig): with open(tizconfig, "r") as f: auth_token = pickle.load(f) if auth_token: # 'Keep track of the auth token' workaround. See: # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198 print_msg("[Google Play Music] [Authenticating] : " \ "'with cached auth token'") self.__gmusic.android_id = device_id self.__gmusic.session._authtoken = auth_token self.__gmusic.session.is_authenticated = True try: self.__gmusic.get_registered_devices() except CallFailure: # The token has expired. Reset the client object print_wrn("[Google Play Music] [Authenticating] : " \ "'auth token expired'") self.__gmusic = Mobileclient() auth_token = "" if not auth_token: attempts = 0 print_nfo("[Google Play Music] [Authenticating] : " \ "'with user credentials'") while not self.logged_in and attempts < 3: self.logged_in = self.__gmusic.login(email, password, device_id) attempts += 1 with open(tizconfig, "a+") as f: f.truncate() pickle.dump(self.__gmusic.session._authtoken, f) self.library = CaseInsensitiveDict() self.song_map = CaseInsensitiveDict() self.playlists = CaseInsensitiveDict() self.stations = CaseInsensitiveDict() def logout(self): """ Reset the session to an unauthenticated, default state. """ self.__gmusic.logout() def set_play_mode(self, mode): """ Set the playback mode. :param mode: curren tvalid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def current_song_title_and_artist(self): """ Retrieve the current track's title and artist name. """ logging.info("current_song_title_and_artist") song = self.now_playing_song if song: title = to_ascii(self.now_playing_song.get('title')) artist = to_ascii(self.now_playing_song.get('artist')) logging.info("Now playing %s by %s", title, artist) return artist, title else: return '', '' def current_song_album_and_duration(self): """ Retrieve the current track's album and duration. """ logging.info("current_song_album_and_duration") song = self.now_playing_song if song: album = to_ascii(self.now_playing_song.get('album')) duration = to_ascii \ (self.now_playing_song.get('durationMillis')) logging.info("album %s duration %s", album, duration) return album, int(duration) else: return '', 0 def current_track_and_album_total(self): """Return the current track number and the total number of tracks in the album, if known. """ logging.info("current_track_and_album_total") song = self.now_playing_song track = 0 total = 0 if song: try: track = self.now_playing_song['trackNumber'] total = self.now_playing_song['totalTrackCount'] logging.info("track number %s total tracks %s", track, total) except KeyError: logging.info("trackNumber or totalTrackCount : not found") else: logging.info("current_song_track_number_" "and_total_tracks : not found") return track, total def current_song_year(self): """ Return the current track's year of publication. """ logging.info("current_song_year") song = self.now_playing_song year = 0 if song: try: year = song['year'] logging.info("track year %s", year) except KeyError: logging.info("year : not found") else: logging.info("current_song_year : not found") return year def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def enqueue_tracks(self, arg): """ Search the user's library for tracks and add them to the playback queue. :param arg: a track search term """ try: songs = self.__gmusic.get_all_songs() track_hits = list() for song in songs: song_title = song['title'] if arg.lower() in song_title.lower(): track_hits.append(song) print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) if not len(track_hits): print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) random.seed() track_hits = random.sample(songs, MAX_TRACKS) for hit in track_hits: song_title = hit['title'] print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) if not len(track_hits): raise KeyError tracks_added = self.__enqueue_tracks(track_hits) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Track not found : {0}".format(arg)) def enqueue_artist(self, arg): """ Search the user's library for tracks from the given artist and add them to the playback queue. :param arg: an artist """ try: self.__update_local_library() artist = None artist_dict = None if arg not in self.library.keys(): for name, art in self.library.iteritems(): if arg.lower() in name.lower(): artist = name artist_dict = art if arg.lower() != name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ name.encode('utf-8'))) break if not artist: # Play some random artist from the library random.seed() artist = random.choice(self.library.keys()) artist_dict = self.library[artist] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: artist = arg artist_dict = self.library[arg] tracks_added = 0 for album in artist_dict: tracks_added += self.__enqueue_tracks(artist_dict[album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(artist))) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) def enqueue_album(self, arg): """ Search the user's library for albums with a given name and add them to the playback queue. """ try: self.__update_local_library() album = None artist = None tentative_album = None tentative_artist = None for library_artist in self.library: for artist_album in self.library[library_artist]: print_nfo("[Google Play Music] [Album] '{0}'." \ .format(to_ascii(artist_album))) if not album: if arg.lower() == artist_album.lower(): album = artist_album artist = library_artist break if not tentative_album: if arg.lower() in artist_album.lower(): tentative_album = artist_album tentative_artist = library_artist if album: break if not album and tentative_album: album = tentative_album artist = tentative_artist print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album.encode('utf-8'))) if not album: # Play some random album from the library random.seed() artist = random.choice(self.library.keys()) album = random.choice(self.library[artist].keys()) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not album: raise KeyError("Album not found : {0}".format(arg)) self.__enqueue_tracks(self.library[artist][album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(album))) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) def enqueue_playlist(self, arg): """Search the user's library for playlists with a given name and add the tracks of the first match to the playback queue. Requires Unlimited subscription. """ try: self.__update_local_library() self.__update_playlists() self.__update_playlists_unlimited() playlist = None playlist_name = None for name, plist in self.playlists.items(): print_nfo("[Google Play Music] [Playlist] '{0}'." \ .format(to_ascii(name))) if arg not in self.playlists.keys(): for name, plist in self.playlists.iteritems(): if arg.lower() in name.lower(): playlist = plist playlist_name = name if arg.lower() != name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ to_ascii(name))) break else: playlist_name = arg playlist = self.playlists[arg] random.seed() x = 0 while (not playlist or not len(playlist)) and x < 3: x += 1 # Play some random playlist from the library playlist_name = random.choice(self.playlists.keys()) playlist = self.playlists[playlist_name] print_wrn("[Google Play Music] '{0}' not found or found empty. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not len(playlist): raise KeyError self.__enqueue_tracks(playlist) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(playlist_name))) self.__update_play_queue_order() except KeyError: raise KeyError( "Playlist not found or found empty : {0}".format(arg)) def enqueue_podcast(self, arg): """Search Google Play Music for a podcast series and add its tracks to the playback queue (). Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving podcasts] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_podcast(arg) if not len(self.queue): raise KeyError logging.info("Added %d episodes from '%s' to queue", \ len(self.queue), arg) self.__update_play_queue_order() except KeyError: raise KeyError("Podcast not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_station_unlimited(self, arg): """Search the user's library for a station with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ try: # First try to find a suitable station in the user's library self.__enqueue_user_station_unlimited(arg) if not len(self.queue): # If no suitable station is found in the user's library, then # search google play unlimited for a potential match. self.__enqueue_station_unlimited(arg) if not len(self.queue): raise KeyError except KeyError: raise KeyError("Station not found : {0}".format(arg)) def enqueue_genre_unlimited(self, arg): """Search Unlimited for a genre with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \ .format(self.__email)) try: all_genres = list() root_genres = self.__gmusic.get_genres() second_tier_genres = list() for root_genre in root_genres: second_tier_genres += self.__gmusic.get_genres( root_genre['id']) all_genres += root_genres all_genres += second_tier_genres for genre in all_genres: print_nfo("[Google Play Music] [Genre] '{0}'." \ .format(to_ascii(genre['name']))) genre = dict() if arg not in all_genres: genre = next((g for g in all_genres \ if arg.lower() in to_ascii(g['name']).lower()), \ None) tracks_added = 0 while not tracks_added: if not genre and len(all_genres): # Play some random genre from the search results random.seed() genre = random.choice(all_genres) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) genre_name = genre['name'] genre_id = genre['id'] station_id = self.__gmusic.create_station(genre_name, \ None, None, None, genre_id) num_tracks = MAX_TRACKS tracks = self.__gmusic.get_station_tracks( station_id, num_tracks) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", tracks_added, genre_name) if not tracks_added: # This will produce another iteration in the loop genre = None print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(genre['name']))) self.__update_play_queue_order() except KeyError: raise KeyError("Genre not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_situation_unlimited(self, arg): """Search Unlimited for a situation with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_situation_unlimited(arg) if not len(self.queue): raise KeyError logging.info("Added %d tracks from %s to queue", \ len(self.queue), arg) except KeyError: raise KeyError("Situation not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_artist_unlimited(self, arg): """Search Unlimited for an artist and add the artist's 200 top tracks to the playback queue. Requires Unlimited subscription. """ try: artist = self.__gmusic_search(arg, 'artist') include_albums = False max_top_tracks = MAX_TRACKS max_rel_artist = 0 artist_tracks = dict() if artist: artist_tracks = self.__gmusic.get_artist_info \ (artist['artist']['artistId'], include_albums, max_top_tracks, max_rel_artist)['topTracks'] if not artist_tracks: raise KeyError for track in artist_tracks: song_title = track['title'] print_nfo("[Google Play Music] [Track] '{0}'." \ .format(to_ascii(song_title))) tracks_added = self.__enqueue_tracks(artist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_album_unlimited(self, arg): """Search Unlimited for an album and add its tracks to the playback queue. Requires Unlimited subscription. """ try: album = self.__gmusic_search(arg, 'album') album_tracks = dict() if album: album_tracks = self.__gmusic.get_album_info \ (album['album']['albumId'])['tracks'] if not album_tracks: raise KeyError print_wrn("[Google Play Music] Playing '{0}'." \ .format((album['album']['name']).encode('utf-8'))) tracks_added = self.__enqueue_tracks(album_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_tracks_unlimited(self, arg): """ Search Unlimited for a track name and add all the matching tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) try: max_results = MAX_TRACKS track_hits = self.__gmusic.search(arg, max_results)['song_hits'] if not len(track_hits): # Do another search with an empty string track_hits = self.__gmusic.search("", max_results)['song_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) tracks = list() for hit in track_hits: tracks.append(hit['track']) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_playlist_unlimited(self, arg): """Search Unlimited for a playlist name and add all its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving playlists] : '{0}'. " \ .format(self.__email)) try: playlist_tracks = list() playlist_hits = self.__gmusic_search(arg, 'playlist') if playlist_hits: playlist = playlist_hits['playlist'] playlist_contents = self.__gmusic.get_shared_playlist_contents( playlist['shareToken']) else: raise KeyError print_nfo("[Google Play Music] [Playlist] '{}'." \ .format(playlist['name']).encode('utf-8')) for item in playlist_contents: print_nfo("[Google Play Music] [Playlist Track] '{} by {} (Album: {}, {})'." \ .format((item['track']['title']).encode('utf-8'), (item['track']['artist']).encode('utf-8'), (item['track']['album']).encode('utf-8'), (item['track']['year']))) track = item['track'] playlist_tracks.append(track) if not playlist_tracks: raise KeyError tracks_added = self.__enqueue_tracks(playlist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_promoted_tracks_unlimited(self): """ Retrieve the url of the next track in the playback queue. """ try: tracks = self.__gmusic.get_promoted_songs() count = 0 for track in tracks: store_track = self.__gmusic.get_track_info(track['storeId']) if u'id' not in store_track.keys(): store_track[u'id'] = store_track['storeId'] self.queue.append(store_track) count += 1 if count == 0: print_wrn("[Google Play Music] Operation requires " \ "an Unlimited subscription.") logging.info("Added %d Unlimited promoted tracks to queue", \ count) self.__update_play_queue_order() except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def next_url(self): """ Retrieve the url of the next track in the playback queue. """ if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(next_song) else: self.queue_index = -1 return self.next_url() else: return '' def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, song): """ Retrieve a song url """ if song.get('episodeId'): song_url = self.__gmusic.get_podcast_episode_stream_url( song['episodeId'], self.__device_id) else: song_url = self.__gmusic.get_stream_url(song['id'], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve the song url!") raise def __update_local_library(self): """ Retrieve the songs and albums from the user's library """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) songs = self.__gmusic.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Retrieve the user's song library for song in songs: if "rating" in song and song['rating'] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song['id'] song_artist = song['artist'] song_album = song['album'] self.song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = CaseInsensitiveDict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : %s", to_ascii(artist)) for album in self.library[artist].keys(): logging.info(" Album : %s", to_ascii(album)) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted( self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album def __update_stations_unlimited(self): """ Retrieve stations (Unlimited) """ self.stations.clear() stations = self.__gmusic.get_all_stations() self.stations[u"I'm Feeling Lucky"] = 'IFL' for station in stations: station_name = station['name'] logging.info("station name : %s", to_ascii(station_name)) self.stations[station_name] = station['id'] def __enqueue_user_station_unlimited(self, arg): """ Enqueue a user station (Unlimited) """ print_msg("[Google Play Music] [Station search "\ "in user's library] : '{0}'. " \ .format(self.__email)) self.__update_stations_unlimited() station_name = arg station_id = None for name, st_id in self.stations.iteritems(): print_nfo("[Google Play Music] [Station] '{0}'." \ .format(to_ascii(name))) if arg not in self.stations.keys(): for name, st_id in self.stations.iteritems(): if arg.lower() in name.lower(): station_id = st_id station_name = name break else: station_id = self.stations[arg] num_tracks = MAX_TRACKS tracks = list() if station_id: try: tracks = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if arg.lower() != station_name.lower(): print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", tracks_added, arg) self.__update_play_queue_order() else: print_wrn("[Google Play Music] '{0}' has no tracks. " \ .format(station_name)) if not len(self.queue): print_wrn("[Google Play Music] '{0}' " \ "not found in the user's library. " \ .format(arg.encode('utf-8'))) def __enqueue_station_unlimited(self, arg, max_results=MAX_TRACKS, quiet=False): """Search for a station and enqueue all of its tracks (Unlimited) """ if not quiet: print_msg("[Google Play Music] [Station search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: station_name = arg station_id = None station = self.__gmusic_search(arg, 'station', max_results, quiet) if station: station = station['station'] station_name = station['name'] seed = station['seed'] seed_type = seed['seedType'] track_id = seed['trackId'] if seed_type == u'2' else None artist_id = seed['artistId'] if seed_type == u'3' else None album_id = seed['albumId'] if seed_type == u'4' else None genre_id = seed['genreId'] if seed_type == u'5' else None playlist_token = seed[ 'playlistShareToken'] if seed_type == u'8' else None curated_station_id = seed[ 'curatedStationId'] if seed_type == u'9' else None num_tracks = max_results tracks = list() try: station_id \ = self.__gmusic.create_station(station_name, \ track_id, \ artist_id, \ album_id, \ genre_id, \ playlist_token, \ curated_station_id) tracks \ = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if not quiet: print_wrn("[Google Play Music] [Station] : '{0}'." \ .format(station_name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg.encode('utf-8')) self.__update_play_queue_order() except KeyError: raise KeyError("Station not found : {0}".format(arg)) def __enqueue_situation_unlimited(self, arg): """Search for a situation and enqueue all of its tracks (Unlimited) """ print_msg("[Google Play Music] [Situation search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: situation_hits = self.__gmusic.search(arg)['situation_hits'] # If the search didn't return results, just do another search with # an empty string if not len(situation_hits): situation_hits = self.__gmusic.search("")['situation_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) # Try to find a "best result", if one exists situation = next((hit for hit in situation_hits \ if 'best_result' in hit.keys() \ and hit['best_result'] == True), None) num_tracks = MAX_TRACKS # If there is no best result, then get a selection of tracks from # each situation. At least we'll play some music. if not situation and len(situation_hits): max_results = num_tracks / len(situation_hits) for hit in situation_hits: situation = hit['situation'] print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \ .format((hit['situation']['title']).encode('utf-8'), (hit['situation']['description']).encode('utf-8'))) self.__enqueue_station_unlimited(situation['title'], max_results, True) elif situation: # There is at list one sitution, enqueue its tracks. situation = situation['situation'] max_results = num_tracks self.__enqueue_station_unlimited(situation['title'], max_results, True) if not situation: raise KeyError except KeyError: raise KeyError("Situation not found : {0}".format(arg)) def __enqueue_podcast(self, arg): """Search for a podcast series and enqueue all of its tracks. """ print_msg("[Google Play Music] [Podcast search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: podcast_hits = self.__gmusic_search(arg, 'podcast', 10, quiet=False) if not podcast_hits: print_wrn( "[Google Play Music] [Podcast] 'Search returned zero results'." ) print_wrn( "[Google Play Music] [Podcast] 'Are you in a supported region " "(currently only US and Canada) ?'") # Use the first podcast retrieved. At least we'll play something. podcast = dict() if podcast_hits and len(podcast_hits): podcast = podcast_hits['series'] episodes_added = 0 if podcast: # There is a podcast, enqueue its episodes. print_nfo("[Google Play Music] [Podcast] 'Playing '{0}' by {1}'." \ .format((podcast['title']).encode('utf-8'), (podcast['author']).encode('utf-8'))) print_nfo("[Google Play Music] [Podcast] '{0}'." \ .format((podcast['description'][0:150]).encode('utf-8'))) series = self.__gmusic.get_podcast_series_info( podcast['seriesId']) episodes = series['episodes'] for episode in episodes: print_nfo("[Google Play Music] [Podcast Episode] '{0} : {1}'." \ .format((episode['title']).encode('utf-8'), (episode['description'][0:80]).encode('utf-8'))) episodes_added = self.__enqueue_tracks(episodes) if not podcast or not episodes_added: raise KeyError except KeyError: raise KeyError( "Podcast not found or no episodes found: {0}".format(arg)) def __enqueue_tracks(self, tracks): """ Add tracks to the playback queue """ count = 0 for track in tracks: if u'id' not in track.keys() and track.get('storeId'): track[u'id'] = track['storeId'] self.queue.append(track) count += 1 return count def __update_playlists(self): """ Retrieve the user's playlists """ plists = self.__gmusic.get_all_user_playlist_contents() for plist in plists: plist_name = plist.get('name') tracks = plist.get('tracks') if plist_name and tracks: logging.info("playlist name : %s", to_ascii(plist_name)) tracks.sort(key=itemgetter('creationTimestamp')) self.playlists[plist_name] = list() for track in tracks: song_id = track.get('trackId') if song_id: song = self.song_map.get(song_id) if song: self.playlists[plist_name].append(song) def __update_playlists_unlimited(self): """ Retrieve shared playlists (Unlimited) """ plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \ if p.get('type') == 'SHARED'] for plist in plists_subscribed_to: share_tok = plist['shareToken'] playlist_items \ = self.__gmusic.get_shared_playlist_contents(share_tok) plist_name = plist['name'] logging.info("shared playlist name : %s", to_ascii(plist_name)) self.playlists[plist_name] = list() for item in playlist_items: try: song = item['track'] song['id'] = item['trackId'] self.playlists[plist_name].append(song) except IndexError: pass def __gmusic_search(self, query, query_type, max_results=MAX_TRACKS, quiet=False): """ Search Google Play (Unlimited) """ search_results = self.__gmusic.search(query, max_results)[query_type + '_hits'] # This is a workaround. Some podcast results come without these two # keys in the dictionary if query_type == "podcast" and len(search_results) \ and not search_results[0].get('navigational_result'): for res in search_results: res[u'best_result'] = False res[u'navigational_result'] = False res[query_type] = res['series'] result = '' if query_type != "playlist": result = next((hit for hit in search_results \ if 'best_result' in hit.keys() \ and hit['best_result'] == True), None) if not result and len(search_results): secondary_hit = None for hit in search_results: name = '' if hit[query_type].get('name'): name = hit[query_type].get('name') elif hit[query_type].get('title'): name = hit[query_type].get('title') if not quiet: print_nfo("[Google Play Music] [{0}] '{1}'." \ .format(query_type.capitalize(), (name).encode('utf-8'))) if query.lower() == \ to_ascii(name).lower(): result = hit break if query.lower() in \ to_ascii(name).lower(): secondary_hit = hit if not result and secondary_hit: result = secondary_hit if not result and not len(search_results): # Do another search with an empty string search_results = self.__gmusic.search("")[query_type + '_hits'] if not result and len(search_results): # Play some random result from the search results random.seed() result = random.choice(search_results) if not quiet: print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(query.encode('utf-8'))) return result
class 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 IBCMusicClient(): # Please have this be an absolute path SONG_DIR = "/home/pi/Desktop/JukeSite/songs" def __init__(self): self.api = None self.player = None def start(self): """ Starts the MobileClient """ self.api = Mobileclient() def stop(self): """ Deletes MobileClient and sets self.api to default(None) """ del self.api self.api = None def logon(self, email, password): """ Logs onto google music as a mobile client. Returns true is sucessful. :param email: Email of the Google user :param password: Pass of the google user :return: Bool if connection was successful """ if self.api is None: raise errors.MobileClientNotInitError( "The Client has not been init therefor it cannot logon.", 1000) try: res = self.api.login(email, password, self.api.FROM_MAC_ADDRESS) except AlreadyLoggedIn as e: self.api.logout() res = self.api.login(email, password, self.api.FROM_MAC_ADDRESS) del email del password return res def logout(self): """ logs out of google music mobile client. :return: if it was succesful """ return self.api.logout() def is_authenticated(self): if not self.api.is_authenticated(): raise errors.SessionNotActive( "The session is no longer active. Either it timedout or you have not logged in", 1001) def search_song(self, query): """ Searchs for the given query and return the song results Will check for authentication. [{ 'track': { 'album': 'Work Out', 'albumArtRef': [{ 'aspectRatio': '1', 'autogen': False, 'kind': 'sj#imageRef', 'url': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fFdxj5w...' }], 'albumArtist': 'J.Cole', 'albumAvailableForPurchase': True, 'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y', 'artist': 'J Cole', 'artistId': ['Ajgnxme45wcqqv44vykrleifpji', 'Ampniqsqcwxk7btbgh5ycujij5i'], 'composer': '', 'discNumber': 1, 'durationMillis': '234000', 'estimatedSize': '9368582', 'explicitType': '1', 'genre': 'Pop', 'kind': 'sj#track', 'nid': 'Tq3nsmzeumhilpegkimjcnbr6aq', 'primaryVideo': { 'id': '6PN78PS_QsM', 'kind': 'sj#video', 'thumbnails': [{ 'height': 180, 'url': 'https://i.ytimg.com/vi/6PN78PS_QsM/mqdefault.jpg', 'width': 320 }] }, 'storeId': 'Tq3nsmzeumhilpegkimjcnbr6aq', 'title': 'Work Out', 'trackAvailableForPurchase': True, 'trackAvailableForSubscription': True, 'trackNumber': 1, 'trackType': '7', 'year': 2011 }, 'type': '1' }] :param query: The song query :return: [list] all the song hits """ self.is_authenticated() res = self.api.search(query) songs = res['song_hits'] return songs def search_album(self, query): """ Searchs for the given query and returns the album results. Will check for authenitcation. e.g return: [{ 'album': { 'albumArtRef': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fF...', 'albumArtist': 'J.Cole', 'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y', 'artist': 'J.Cole', 'artistId': ['Ajgnxme45wcqqv44vykrleifpji'], 'description_attribution': { 'kind': 'sj#attribution', 'license_title': 'Creative Commons Attribution CC-BY', 'license_url': 'http://creativecommons.org/licenses/by/4.0/legalcode', 'source_title': 'Freebase', 'source_url': '' }, 'explicitType': '1', 'kind': 'sj#album', 'name': 'Work Out', 'year': 2011 }, 'type': '3' }] :param query: [string] The album query :return: [list] A list of all the album hits """ self.is_authenticated() res = self.api.search(query) albums = res['album_hits'] return albums def get_album_info(self, album_id): """ Returns information about an album e.g return: { 'kind': 'sj#album', 'name': 'Circle', 'artist': 'Amorphis', 'albumArtRef': 'http://lh6.ggpht.com/...', 'tracks': [ # if `include_tracks` is True { 'album': 'Circle', 'kind': 'sj#track', 'storeId': 'T5zb7luo2vkroozmj57g2nljdsy', # can be used as a song id 'artist': 'Amorphis', 'albumArtRef': [ { 'url': 'http://lh6.ggpht.com/...' }], 'title': 'Shades of Grey', 'nid': 'T5zb7luo2vkroozmj57g2nljdsy', 'estimatedSize': '13115591', 'albumId': 'Bfr2onjv7g7tm4rzosewnnwxxyy', 'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'], 'albumArtist': 'Amorphis', 'durationMillis': '327000', 'composer': '', 'genre': 'Metal', 'trackNumber': 1, 'discNumber': 1, 'trackAvailableForPurchase': True, 'trackType': '7', 'albumAvailableForPurchase': True }, # ... ], 'albumId': 'Bfr2onjv7g7tm4rzosewnnwxxyy', 'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'], 'albumArtist': 'Amorphis', 'year': 2013 } :param album_id: The albumId :return: Dictionary in the format above """ self.is_authenticated() return self.api.get_album_info(album_id) def get_song_info(self, song_id): """ Returns information about a song e.g return { 'album': 'Best Of', 'kind': 'sj#track', 'storeId': 'Te2qokfjmhqxw4bnkswbfphzs4m', 'artist': 'Amorphis', 'albumArtRef': [ { 'url': 'http://lh5.ggpht.com/...' }], 'title': 'Hopeless Days', 'nid': 'Te2qokfjmhqxw4bnkswbfphzs4m', 'estimatedSize': '12325643', 'albumId': 'Bsbjjc24a5xutbutvbvg3h4y2k4', 'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'], 'albumArtist': 'Amorphis', 'durationMillis': '308000', 'composer': '', 'genre': 'Metal', 'trackNumber': 2, 'discNumber': 1, 'trackAvailableForPurchase': True, 'trackType': '7', 'albumAvailableForPurchase': True } :param song_id: The songds storeId :return: A dict with the above information """ self.is_authenticated() return self.api.get_track_info(song_id) def get_song_url(self, song_id): self.is_authenticated() res = self.api.get_stream_url(song_id) return res def download_song(self, song_id): """ Download the song from the storeId. :param song_id: the 'storeId' of the specific song """ url = self.get_song_url(song_id) song_file_path = "{}/{}.mp3".format(IBCMusicClient.SONG_DIR, song_id) if os.path.isfile(song_file_path): raise errors.SongAlreadyDownloadedException( "The song '{}' has already been downloaded and cached".format( song_file_path), 8002) # This need to not use subprocessing command = ['wget', url, '-O', song_file_path] res = check_output(command) lines = res.decode().split('\n') error_lines = [line for line in lines if 'failed' in line] if len(error_lines) > 0: # We have an error raise errors.CannotDownloadSongError( "Could not download the given song. {}".format( str(error_lines)), 1003)
class Session(object): def __init__(self): self.api = None self.user = None self.lib_albums = {} self.lib_artists = {} self.lib_tracks = {} self.lib_updatetime = 0 self.sitdata = [] self.sitbyid = {} self.sitdataupdtime = 0 def dmpdata(self, who, data): print("%s: %s" % (who, json.dumps(data, indent=4)), file=sys.stderr) def login(self, username, password, deviceid=None): self.api = Mobileclient(debug_logging=False) if deviceid is None: logged_in = self.api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) else: logged_in = self.api.login(username, password, deviceid) # print("Logged in: %s" % logged_in) # data = self.api.get_registered_devices() # print("registered: %s" % data) # isauth = self.api.is_authenticated() # print("Auth ok: %s" % isauth) return logged_in def _get_user_library(self): now = time.time() if now - self.lib_updatetime < 300: return data = self.api.get_all_songs() # self.dmpdata("all_songs", data) self.lib_updatetime = now tracks = [_parse_track(t) for t in data] self.lib_tracks = dict([(t.id, t) for t in tracks]) for track in tracks: # We would like to use the album id here, but gmusic # associates the tracks with any compilations after # uploading (does not use the metadata apparently), so # that we can't (we would end up with multiple # albums). OTOH, the album name is correct (so seems to # come from the metadata). What we should do is test the # album ids for one album with a matching title, but we're # not sure to succeed. So at this point, the album id we # end up storing could be for a different albums, and we # should have a special library-local get_album_tracks self.lib_albums[track.album.name] = track.album self.lib_artists[track.artist.id] = track.artist def get_user_albums(self): self._get_user_library() return self.lib_albums.values() def get_user_artists(self): self._get_user_library() return self.lib_artists.values() def get_user_playlists(self): pldata = self.api.get_all_playlists() # self.dmpdata("playlists", pldata) return [_parse_playlist(pl) for pl in pldata] def get_user_playlist_tracks(self, playlist_id): self._get_user_library() data = self.api.get_all_user_playlist_contents() # self.dmpdata("user_playlist_content", data) trkl = [item["tracks"] for item in data if item["id"] == playlist_id] if not trkl: return [] try: return [self.lib_tracks[track["trackId"]] for track in trkl[0]] except: return [] def create_station_for_genre(self, genre_id): id = self.api.create_station("station" + genre_id, genre_id=genre_id) return id def get_user_stations(self): data = self.api.get_all_stations() # parse_playlist works fine for stations stations = [_parse_playlist(d) for d in data] return stations def delete_user_station(self, id): self.api.delete_stations(id) # not working right now def listen_now(self): print("api.get_listen_now_items()", file=sys.stderr) ret = {"albums": [], "stations": []} try: data = self.api.get_listen_now_items() except Exception as err: print("api.get_listen_now_items failed: %s" % err, file=sys.stderr) data = None # listen_now entries are not like normal albums or stations, # and need special parsing. I could not make obvious sense of # the station-like listen_now entries, so left them aside for # now. Maybe should use create_station on the artist id? if data: ret["albums"] = [_parse_ln_album(a["album"]) for a in data if "album" in a] # ret['stations'] = [_parse_ln_station(d['radio_station']) \ # for d in data if 'radio_station' in d] else: print("listen_now: no items returned !", file=sys.stderr) print( "get_listen_now_items: returning %d albums and %d stations" % (len(ret["albums"]), len(ret["stations"])), file=sys.stderr, ) return ret def get_situation_content(self, id=None): ret = {"situations": [], "stations": []} now = time.time() if id is None and now - self.sitdataupdtime > 300: self.sitbyid = {} self.sitdata = self.api.get_listen_now_situations() self.sitdataupdtime = now # Root is special, it's a list of situations if id is None: ret["situations"] = [self._parse_situation(s) for s in self.sitdata] return ret # not root if id not in self.sitbyid: print("get_situation_content: %s unknown" % id, file=sys.stderr) return ret situation = self.sitbyid[id] # self.dmpdata("situation", situation) if "situations" in situation: ret["situations"] = [self._parse_situation(s) for s in situation["situations"]] if "stations" in situation: ret["stations"] = [_parse_situation_station(s) for s in situation["stations"]] return ret def _parse_situation(self, data): self.sitbyid[data["id"]] = data return Playlist(id=data["id"], name=data["title"]) def create_curated_and_get_tracks(self, id): sid = self.api.create_station("station" + id, curated_station_id=id) print("create_curated: sid %s" % sid, file=sys.stderr) tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)] # print("curated tracks: %s"%tracks, file=sys.stderr) self.api.delete_stations(sid) return tracks def get_station_tracks(self, id): return [_parse_track(t) for t in self.api.get_station_tracks(id)] def get_media_url(self, song_id, quality=u"med"): url = self.api.get_stream_url(song_id, quality=quality) print("get_media_url got: %s" % url, file=sys.stderr) return url def get_album_tracks(self, album_id): data = self.api.get_album_info(album_id, include_tracks=True) album = _parse_album(data) return [_parse_track(t, album) for t in data["tracks"]] def get_promoted_tracks(self): data = self.api.get_promoted_songs() # self.dmpdata("promoted_tracks", data) return [_parse_track(t) for t in data] def get_genres(self, parent=None): data = self.api.get_genres(parent_genre_id=parent) return [_parse_genre(g) for g in data] def get_artist_info(self, artist_id, doRelated=False): ret = {"albums": [], "toptracks": [], "related": []} # Happens,some library tracks have no artistId entry if artist_id is None or artist_id == "None": print("get_artist_albums: artist_id is None", file=sys.stderr) return ret else: print("get_artist_albums: artist_id %s" % artist_id, file=sys.stderr) maxrel = 20 if doRelated else 0 maxtop = 0 if doRelated else 10 incalbs = False if doRelated else True data = self.api.get_artist_info(artist_id, include_albums=incalbs, max_top_tracks=maxtop, max_rel_artist=maxrel) # self.dmpdata("artist_info", data) if "albums" in data: ret["albums"] = [_parse_album(alb) for alb in data["albums"]] if "topTracks" in data: ret["toptracks"] = [_parse_track(t) for t in data["topTracks"]] if "related_artists" in data: ret["related"] = [_parse_artist(a) for a in data["related_artists"]] return ret def get_artist_related(self, artist_id): data = self.get_artist_info(artist_id, doRelated=True) return data["related"] def search(self, query): data = self.api.search(query, max_results=50) # self.dmpdata("Search", data) tr = [_parse_track(i["track"]) for i in data["song_hits"]] print("track ok", file=sys.stderr) ar = [_parse_artist(i["artist"]) for i in data["artist_hits"]] print("artist ok", file=sys.stderr) al = [_parse_album(i["album"]) for i in data["album_hits"]] print("album ok", file=sys.stderr) # self.dmpdata("Search playlists", data['playlist_hits']) try: pl = [_parse_splaylist(i) for i in data["playlist_hits"]] except: pl = [] print("playlist ok", file=sys.stderr) return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
class tizgmusicproxy(object): """A class for logging into a Google Play Music account and retrieving song URLs. """ all_songs_album_title = "All Songs" thumbs_up_playlist_name = "Thumbs Up" def __init__(self, email, password, device_id): self.__gmusic = Mobileclient() self.__email = email self.__device_id = device_id self.logged_in = False self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_song = None userdir = os.path.expanduser('~') tizconfig = os.path.join(userdir, ".config/tizonia/." + email + ".auth_token") auth_token = "" if os.path.isfile(tizconfig): with open(tizconfig, "r") as f: auth_token = pickle.load(f) if auth_token: # 'Keep track of the auth token' workaround. See: # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198 print_msg("[Google Play Music] [Authenticating] : " \ "'with cached auth token'") self.__gmusic.android_id = device_id self.__gmusic.session._authtoken = auth_token self.__gmusic.session.is_authenticated = True try: self.__gmusic.get_registered_devices() except CallFailure: # The token has expired. Reset the client object print_wrn("[Google Play Music] [Authenticating] : " \ "'auth token expired'") self.__gmusic = Mobileclient() auth_token = "" if not auth_token: attempts = 0 print_nfo("[Google Play Music] [Authenticating] : " \ "'with user credentials'") while not self.logged_in and attempts < 3: self.logged_in = self.__gmusic.login(email, password, device_id) attempts += 1 with open(tizconfig, "a+") as f: f.truncate() pickle.dump(self.__gmusic.session._authtoken, f) self.library = CaseInsensitiveDict() self.song_map = CaseInsensitiveDict() self.playlists = CaseInsensitiveDict() self.stations = CaseInsensitiveDict() def logout(self): """ Reset the session to an unauthenticated, default state. """ self.__gmusic.logout() def set_play_mode(self, mode): """ Set the playback mode. :param mode: curren tvalid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def current_song_title_and_artist(self): """ Retrieve the current track's title and artist name. """ logging.info("current_song_title_and_artist") song = self.now_playing_song if song: title = to_ascii(self.now_playing_song.get('title')) artist = to_ascii(self.now_playing_song.get('artist')) logging.info("Now playing %s by %s", title, artist) return artist, title else: return '', '' def current_song_album_and_duration(self): """ Retrieve the current track's album and duration. """ logging.info("current_song_album_and_duration") song = self.now_playing_song if song: album = to_ascii(self.now_playing_song.get('album')) duration = to_ascii \ (self.now_playing_song.get('durationMillis')) logging.info("album %s duration %s", album, duration) return album, int(duration) else: return '', 0 def current_track_and_album_total(self): """Return the current track number and the total number of tracks in the album, if known. """ logging.info("current_track_and_album_total") song = self.now_playing_song track = 0 total = 0 if song: try: track = self.now_playing_song['trackNumber'] total = self.now_playing_song['totalTrackCount'] logging.info("track number %s total tracks %s", track, total) except KeyError: logging.info("trackNumber or totalTrackCount : not found") else: logging.info("current_song_track_number_" "and_total_tracks : not found") return track, total def current_song_year(self): """ Return the current track's year of publication. """ logging.info("current_song_year") song = self.now_playing_song year = 0 if song: try: year = song['year'] logging.info("track year %s", year) except KeyError: logging.info("year : not found") else: logging.info("current_song_year : not found") return year def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def enqueue_artist(self, arg): """ Search the user's library for tracks from the given artist and adds them to the playback queue. :param arg: an artist """ try: self.__update_local_library() artist = None if arg not in self.library.keys(): for name, art in self.library.iteritems(): if arg.lower() in name.lower(): artist = art print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ name.encode('utf-8'))) break if not artist: # Play some random artist from the library random.seed() artist = random.choice(self.library.keys()) artist = self.library[artist] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: artist = self.library[arg] tracks_added = 0 for album in artist: tracks_added += self.__enqueue_tracks(artist[album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(artist))) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) def enqueue_album(self, arg): """ Search the user's library for albums with a given name and adds them to the playback queue. """ try: self.__update_local_library() album = None artist = None tentative_album = None tentative_artist = None for library_artist in self.library: for artist_album in self.library[library_artist]: print_nfo("[Google Play Music] [Album] '{0}'." \ .format(to_ascii(artist_album))) if not album: if arg.lower() == artist_album.lower(): album = artist_album artist = library_artist break if not tentative_album: if arg.lower() in artist_album.lower(): tentative_album = artist_album tentative_artist = library_artist if album: break if not album and tentative_album: album = tentative_album artist = tentative_artist print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album.encode('utf-8'))) if not album: # Play some random album from the library random.seed() artist = random.choice(self.library.keys()) album = random.choice(self.library[artist].keys()) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) if not album: raise KeyError("Album not found : {0}".format(arg)) self.__enqueue_tracks(self.library[artist][album]) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(album))) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) def enqueue_playlist(self, arg): """Search the user's library for playlists with a given name and adds the tracks of the first match to the playback queue. Requires Unlimited subscription. """ try: self.__update_local_library() self.__update_playlists() self.__update_playlists_unlimited() playlist = None playlist_name = None for name, plist in self.playlists.items(): print_nfo("[Google Play Music] [Playlist] '{0}'." \ .format(to_ascii(name))) if arg not in self.playlists.keys(): for name, plist in self.playlists.iteritems(): if arg.lower() in name.lower(): playlist = plist playlist_name = name print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ to_ascii(name))) break if not playlist: # Play some random playlist from the library random.seed() playlist_name = random.choice(self.playlists.keys()) playlist = self.playlists[playlist_name] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) else: playlist_name = arg playlist = self.playlists[arg] self.__enqueue_tracks(playlist) print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(playlist_name))) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) def enqueue_station_unlimited(self, arg): """Search the user's library for a station with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ try: # First try to find a suitable station in the user's library self.__enqueue_user_station_unlimited(arg) if not len(self.queue): # If no suitable station is found in the user's library, then # search google play unlimited for a potential match. self.__enqueue_station_unlimited(arg) if not len(self.queue): raise KeyError except KeyError: raise KeyError("Station not found : {0}".format(arg)) def enqueue_genre_unlimited(self, arg): """Search Unlimited for a genre with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \ .format(self.__email)) try: all_genres = list() root_genres = self.__gmusic.get_genres() second_tier_genres = list() for root_genre in root_genres: second_tier_genres += self.__gmusic.get_genres(root_genre['id']) all_genres += root_genres all_genres += second_tier_genres for genre in all_genres: print_nfo("[Google Play Music] [Genre] '{0}'." \ .format(to_ascii(genre['name']))) genre = dict() if arg not in all_genres: genre = next((g for g in all_genres \ if arg.lower() in to_ascii(g['name']).lower()), \ None) tracks_added = 0 while not tracks_added: if not genre and len(all_genres): # Play some random genre from the search results random.seed() genre = random.choice(all_genres) print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) genre_name = genre['name'] genre_id = genre['id'] station_id = self.__gmusic.create_station(genre_name, \ None, None, None, genre_id) num_tracks = 200 tracks = self.__gmusic.get_station_tracks(station_id, num_tracks) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", tracks_added, genre_name) if not tracks_added: # This will produce another iteration in the loop genre = None print_wrn("[Google Play Music] Playing '{0}'." \ .format(to_ascii(genre['name']))) self.__update_play_queue_order() except KeyError: raise KeyError("Genre not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_situation_unlimited(self, arg): """Search Unlimited for a situation with a given name and add its tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \ .format(self.__email)) try: self.__enqueue_situation_unlimited(arg) if not len(self.queue): raise KeyError logging.info("Added %d tracks from %s to queue", \ len(self.queue), arg) except KeyError: raise KeyError("Situation not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_artist_unlimited(self, arg): """Search Unlimited for an artist and adds the artist's 200 top tracks to the playback queue. Requires Unlimited subscription. """ try: artist = self.__gmusic_search(arg, 'artist') include_albums = False max_top_tracks = 200 max_rel_artist = 0 artist_tracks = dict() if artist: artist_tracks = self.__gmusic.get_artist_info \ (artist['artist']['artistId'], include_albums, max_top_tracks, max_rel_artist)['topTracks'] if not artist_tracks: raise KeyError tracks_added = self.__enqueue_tracks(artist_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Artist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_album_unlimited(self, arg): """Search Unlimited for an album and add its tracks to the playback queue. Requires Unlimited subscription. """ try: album = self.__gmusic_search(arg, 'album') album_tracks = dict() if album: album_tracks = self.__gmusic.get_album_info \ (album['album']['albumId'])['tracks'] if not album_tracks: raise KeyError print_wrn("[Google Play Music] Playing '{0}'." \ .format((album['album']['name']).encode('utf-8'))) tracks_added = self.__enqueue_tracks(album_tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Album not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_tracks_unlimited(self, arg): """ Search Unlimited for a track name and adds all the matching tracks to the playback queue. Requires Unlimited subscription. """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) try: max_results = 200 track_hits = self.__gmusic.search(arg, max_results)['song_hits'] if not len(track_hits): # Do another search with an empty string track_hits = self.__gmusic.search("", max_results)['song_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) tracks = list() for hit in track_hits: tracks.append(hit['track']) tracks_added = self.__enqueue_tracks(tracks) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg) self.__update_play_queue_order() except KeyError: raise KeyError("Playlist not found : {0}".format(arg)) except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def enqueue_promoted_tracks_unlimited(self): """ Retrieve the url of the next track in the playback queue. """ try: tracks = self.__gmusic.get_promoted_songs() count = 0 for track in tracks: store_track = self.__gmusic.get_track_info(track['storeId']) if u'id' not in store_track.keys(): store_track[u'id'] = store_track['nid'] self.queue.append(store_track) count += 1 if count == 0: print_wrn("[Google Play Music] Operation requires " \ "an Unlimited subscription.") logging.info("Added %d Unlimited promoted tracks to queue", \ count) self.__update_play_queue_order() except CallFailure: raise RuntimeError("Operation requires an Unlimited subscription.") def next_url(self): """ Retrieve the url of the next track in the playback queue. """ if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(next_song) else: self.queue_index = -1 return self.next_url() else: return '' def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_song = self.queue[self.play_queue_order[self.queue_index]] return self.__retrieve_track_url(prev_song) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, song): """ Retrieve a song url """ song_url = self.__gmusic.get_stream_url(song['id'], self.__device_id) try: self.now_playing_song = song return song_url except AttributeError: logging.info("Could not retrieve the song url!") raise def __update_local_library(self): """ Retrieve the songs and albums from the user's library """ print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \ .format(self.__email)) songs = self.__gmusic.get_all_songs() self.playlists[self.thumbs_up_playlist_name] = list() # Retrieve the user's song library for song in songs: if "rating" in song and song['rating'] == "5": self.playlists[self.thumbs_up_playlist_name].append(song) song_id = song['id'] song_artist = song['artist'] song_album = song['album'] self.song_map[song_id] = song if song_artist == "": song_artist = "Unknown Artist" if song_album == "": song_album = "Unknown Album" if song_artist not in self.library: self.library[song_artist] = CaseInsensitiveDict() self.library[song_artist][self.all_songs_album_title] = list() if song_album not in self.library[song_artist]: self.library[song_artist][song_album] = list() self.library[song_artist][song_album].append(song) self.library[song_artist][self.all_songs_album_title].append(song) # Sort albums by track number for artist in self.library.keys(): logging.info("Artist : %s", to_ascii(artist)) for album in self.library[artist].keys(): logging.info(" Album : %s", to_ascii(album)) if album == self.all_songs_album_title: sorted_album = sorted(self.library[artist][album], key=lambda k: k['title']) else: sorted_album = sorted(self.library[artist][album], key=lambda k: k.get('trackNumber', 0)) self.library[artist][album] = sorted_album def __update_stations_unlimited(self): """ Retrieve stations (Unlimited) """ self.stations.clear() stations = self.__gmusic.get_all_stations() self.stations[u"I'm Feeling Lucky"] = 'IFL' for station in stations: station_name = station['name'] logging.info("station name : %s", to_ascii(station_name)) self.stations[station_name] = station['id'] def __enqueue_user_station_unlimited(self, arg): """ Enqueue a user station (Unlimited) """ print_msg("[Google Play Music] [Station search "\ "in user's library] : '{0}'. " \ .format(self.__email)) self.__update_stations_unlimited() station_name = arg station_id = None for name, st_id in self.stations.iteritems(): print_nfo("[Google Play Music] [Station] '{0}'." \ .format(to_ascii(name))) if arg not in self.stations.keys(): for name, st_id in self.stations.iteritems(): if arg.lower() in name.lower(): station_id = st_id station_name = name break else: station_id = self.stations[arg] num_tracks = 200 tracks = list() if station_id: try: tracks = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if arg != station_name: print_wrn("[Google Play Music] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", tracks_added, arg) self.__update_play_queue_order() else: print_wrn("[Google Play Music] '{0}' has no tracks. " \ .format(station_name)) if not len(self.queue): print_wrn("[Google Play Music] '{0}' " \ "not found in the user's library. " \ .format(arg.encode('utf-8'))) def __enqueue_station_unlimited(self, arg, max_results=200, quiet=False): """Search for a station and enqueue all of its tracks (Unlimited) """ if not quiet: print_msg("[Google Play Music] [Station search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: station_name = arg station_id = None station = self.__gmusic_search(arg, 'station', max_results, quiet) if station: station = station['station'] station_name = station['name'] seed = station['seed'] seed_type = seed['seedType'] track_id = seed['trackId'] if seed_type == u'2' else None artist_id = seed['artistId'] if seed_type == u'3' else None album_id = seed['albumId'] if seed_type == u'4' else None genre_id = seed['genreId'] if seed_type == u'5' else None playlist_token = seed['playlistShareToken'] if seed_type == u'8' else None curated_station_id = seed['curatedStationId'] if seed_type == u'9' else None num_tracks = max_results tracks = list() try: station_id \ = self.__gmusic.create_station(station_name, \ track_id, \ artist_id, \ album_id, \ genre_id, \ playlist_token, \ curated_station_id) tracks \ = self.__gmusic.get_station_tracks(station_id, \ num_tracks) except KeyError: raise RuntimeError("Operation requires an " "Unlimited subscription.") tracks_added = self.__enqueue_tracks(tracks) if tracks_added: if not quiet: print_wrn("[Google Play Music] [Station] : '{0}'." \ .format(station_name.encode('utf-8'))) logging.info("Added %d tracks from %s to queue", \ tracks_added, arg.encode('utf-8')) self.__update_play_queue_order() except KeyError: raise KeyError("Station not found : {0}".format(arg)) def __enqueue_situation_unlimited(self, arg): """Search for a situation and enqueue all of its tracks (Unlimited) """ print_msg("[Google Play Music] [Situation search in "\ "Google Play Music] : '{0}'. " \ .format(arg.encode('utf-8'))) try: situation_hits = self.__gmusic.search(arg)['situation_hits'] if not len(situation_hits): # Do another search with an empty string situation_hits = self.__gmusic.search("")['situation_hits'] print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(arg.encode('utf-8'))) situation = next((hit for hit in situation_hits \ if 'best_result' in hit.keys()), None) num_tracks = 200 if not situation and len(situation_hits): max_results = num_tracks / len(situation_hits) for hit in situation_hits: situation = hit['situation'] print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \ .format((hit['situation']['title']).encode('utf-8'), (hit['situation']['description']).encode('utf-8'))) self.__enqueue_station_unlimited(situation['title'], max_results, True) if not situation: raise KeyError except KeyError: raise KeyError("Situation not found : {0}".format(arg)) def __enqueue_tracks(self, tracks): """ Add tracks to the playback queue """ count = 0 for track in tracks: if u'id' not in track.keys(): track[u'id'] = track['nid'] self.queue.append(track) count += 1 return count def __update_playlists(self): """ Retrieve the user's playlists """ plists = self.__gmusic.get_all_user_playlist_contents() for plist in plists: plist_name = plist['name'] logging.info("playlist name : %s", to_ascii(plist_name)) tracks = plist['tracks'] tracks.sort(key=itemgetter('creationTimestamp')) self.playlists[plist_name] = list() for track in tracks: try: song = self.song_map[track['trackId']] self.playlists[plist_name].append(song) except IndexError: pass def __update_playlists_unlimited(self): """ Retrieve shared playlists (Unlimited) """ plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \ if p.get('type') == 'SHARED'] for plist in plists_subscribed_to: share_tok = plist['shareToken'] playlist_items \ = self.__gmusic.get_shared_playlist_contents(share_tok) plist_name = plist['name'] logging.info("shared playlist name : %s", to_ascii(plist_name)) self.playlists[plist_name] = list() for item in playlist_items: try: song = item['track'] song['id'] = item['trackId'] self.playlists[plist_name].append(song) except IndexError: pass def __gmusic_search(self, query, query_type, max_results=200, quiet=False): """ Search Google Play (Unlimited) """ search_results = self.__gmusic.search(query, max_results)[query_type + '_hits'] result = next((hit for hit in search_results \ if 'best_result' in hit.keys()), None) if not result and len(search_results): secondary_hit = None for hit in search_results: if not quiet: print_nfo("[Google Play Music] [{0}] '{1}'." \ .format(query_type.capitalize(), (hit[query_type]['name']).encode('utf-8'))) if query.lower() == \ to_ascii(hit[query_type]['name']).lower(): result = hit break if query.lower() in \ to_ascii(hit[query_type]['name']).lower(): secondary_hit = hit if not result and secondary_hit: result = secondary_hit if not result and not len(search_results): # Do another search with an empty string search_results = self.__gmusic.search("")[query_type + '_hits'] if not result and len(search_results): # Play some random result from the search results random.seed() result = random.choice(search_results) if not quiet: print_wrn("[Google Play Music] '{0}' not found. "\ "Feeling lucky?." \ .format(query.encode('utf-8'))) return result
class GMusicHandler: """ GMusicHandler is the class handling the communication with the google music api. """ def __init__(self, user, password): self.user = user self.password = password self.api = Mobileclient() if not self.api.oauth_login(Mobileclient.FROM_MAC_ADDRESS): raise Exception('Failed to login...') self.tracks = {} self.playlists = {} def get_all_song(self): if len(self.tracks) == 0: songs = self.api.get_all_songs() for song in songs: track_id = song['id'] title = song['title'] artist = song['artist'] album = song['album'] track_number = song['trackNumber'] self.tracks[track_id] = Track(track_id, title, artist, album, track_number) return self.tracks def get_song(self, track_id): songs = self.get_all_song() return songs.get(track_id) def get_all_playlist(self): if len(self.playlists) == 0: playlists = self.api.get_all_user_playlist_contents() for pl in playlists: playlist = self._build_playlist(pl) self.playlists[playlist.id] = playlist return self.playlists def _build_playlist(self, data): playlist_id = data['id'] playlist_name = data['name'] p = Playlist(playlist_id, playlist_name) for track in data['tracks']: track_id = track['trackId'] source = track['source'] if source == '1': song = self.get_song(track_id) p.tracks[song.id] = song elif source == '2': song = self._create_track(track_id, track['track']) p.tracks[song.id] = song return p def _create_track(self, track_id, data): title = data['title'] artist = data['artist'] album = data['album'] track_number = data['trackNumber'] song = Track(track_id, title, artist, album, track_number) self.tracks[song.id] = song return song def get_playlist(self, playlist_name): playlists = self.get_all_playlist() return next( iter([p for p in playlists.values() if p.name == playlist_name]), None) def download_track(self, track_id): url = self.api.get_stream_url(track_id) file_path, headers = urllib.request.urlretrieve(url) return file_path def search(self, query): return self.api.search(query)
class Session(object): def __init__(self): self.api = None self.user = None self.lib_albums = {} self.lib_artists = {} self.lib_tracks = {} self.lib_playlists = {} self.lib_updatetime = 0 self.sitdata = [] self.sitbyid = {} self.sitdataupdtime = 0 def dmpdata(self, who, data): uplog("%s: %s" % (who, json.dumps(data, indent=4))) # Look for an Android device id in the registered devices. def find_device_id(self, data): for entry in data: if "type" in entry and entry["type"] == u"ANDROID": # Get rid of 0x id = entry["id"][2:] uplog("Using deviceid %s" % id) return id return None def login(self, username, password, deviceid=None): self.api = Mobileclient(debug_logging=False) if deviceid is None: logged_in = self.api.login(username, password, Mobileclient.FROM_MAC_ADDRESS) if logged_in: # Try to re-login with a valid deviceid data = self.api.get_registered_devices() #self.dmpdata("registered devices", data) deviceid = self.find_device_id(data) if deviceid: logged_in = self.login(username, password, deviceid) else: logged_in = self.api.login(username, password, deviceid) isauth = self.api.is_authenticated() #uplog("login: Logged in: %s. Auth ok: %s" % (logged_in, isauth)) return logged_in def _get_user_library(self): now = time.time() if now - self.lib_updatetime < 300: return if self.lib_updatetime == 0: data = self.api.get_all_songs() #self.dmpdata("all_songs", data) else: data = self.api.get_all_songs(updated_after=datetime.datetime.fromtimestamp(self.lib_updatetime)) #self.dmpdata("all_songs_since_update", data) self.lib_updatetime = now tracks = [_parse_track(t) for t in data] self.lib_tracks.update(dict([(t.id, t) for t in tracks])) for track in tracks: # We would like to use the album id here, but gmusic # associates the tracks with any compilations after # uploading (does not use the metadata apparently), so # that we can't (we would end up with multiple # albums). OTOH, the album name is correct (so seems to # come from the metadata). What we should do is test the # album ids for one album with a matching title, but we're # not sure to succeed. So at this point, the album id we # end up storing could be for a different albums, and we # should have a special library-local get_album_tracks self.lib_albums[track.album.name] = track.album self.lib_artists[track.artist.id] = track.artist def get_user_albums(self): self._get_user_library() return self.lib_albums.values() def get_user_artists(self): self._get_user_library() return self.lib_artists.values() def get_user_playlists(self): pldata = self.api.get_all_playlists() #self.dmpdata("playlists", pldata) return [_parse_playlist(pl) for pl in pldata] def get_user_playlist_tracks(self, playlist_id): self._get_user_library() # Unfortunately gmusic does not offer incremental updates for # playlists. This means we must download all playlist data any # time we want an update. Playlists include track information # for gmusic songs, so if a user has a lot of playlists this # can take some time. # For now, we only load the playlists one time for performance purposes if len(self.lib_playlists) == 0: data = self.api.get_all_user_playlist_contents() self.lib_playlists = dict([(pl['id'], pl) for pl in data]) tracks = [] if playlist_id in self.lib_playlists: for entry in self.lib_playlists[playlist_id]['tracks']: if entry['deleted']: continue if entry['source'] == u'1': tracks.append(self.lib_tracks[entry['trackId']]) elif 'track' in entry: tracks.append(_parse_track(entry['track']) ) return tracks def create_station_for_genre(self, genre_id): id = self.api.create_station("station"+genre_id, genre_id=genre_id) return id def get_user_stations(self): data = self.api.get_all_stations() # parse_playlist works fine for stations stations = [_parse_playlist(d) for d in data] return stations def delete_user_station(self, id): self.api.delete_stations(id) # not working right now def listen_now(self): print("api.get_listen_now_items()", file=sys.stderr) ret = {'albums' : [], 'stations' : []} try: data = self.api.get_listen_now_items() except Exception as err: print("api.get_listen_now_items failed: %s" % err, file=sys.stderr) data = None # listen_now entries are not like normal albums or stations, # and need special parsing. I could not make obvious sense of # the station-like listen_now entries, so left them aside for # now. Maybe should use create_station on the artist id? if data: ret['albums'] = [_parse_ln_album(a['album']) \ for a in data if 'album' in a] #ret['stations'] = [_parse_ln_station(d['radio_station']) \ # for d in data if 'radio_station' in d] else: print("listen_now: no items returned !", file=sys.stderr) print("get_listen_now_items: returning %d albums and %d stations" %\ (len(ret['albums']), len(ret['stations'])), file=sys.stderr) return ret def get_situation_content(self, id = None): ret = {'situations' : [], 'stations' : []} now = time.time() if id is None and now - self.sitdataupdtime > 300: self.sitbyid = {} self.sitdata = self.api.get_listen_now_situations() self.sitdataupdtime = now # Root is special, it's a list of situations if id is None: ret['situations'] = [self._parse_situation(s) \ for s in self.sitdata] return ret # not root if id not in self.sitbyid: print("get_situation_content: %s unknown" % id, file=sys.stderr) return ret situation = self.sitbyid[id] #self.dmpdata("situation", situation) if 'situations' in situation: ret['situations'] = [self._parse_situation(s) \ for s in situation['situations']] if 'stations' in situation: ret['stations'] = [_parse_situation_station(s) \ for s in situation['stations']] return ret def _parse_situation(self, data): self.sitbyid[data['id']] = data return Playlist(id=data['id'], name=data['title']) def create_curated_and_get_tracks(self, id): sid = self.api.create_station("station"+id, curated_station_id=id) print("create_curated: sid %s"%sid, file=sys.stderr) tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)] #print("curated tracks: %s"%tracks, file=sys.stderr) self.api.delete_stations(sid) return tracks def get_station_tracks(self, id): return [_parse_track(t) for t in self.api.get_station_tracks(id)] def get_media_url(self, song_id, quality=u'med'): url = self.api.get_stream_url(song_id, quality=quality) print("get_media_url got: %s" % url, file=sys.stderr) return url def get_album_tracks(self, album_id): data = self.api.get_album_info(album_id, include_tracks=True) album = _parse_album(data) return [_parse_track(t, album) for t in data['tracks']] def get_promoted_tracks(self): data = self.api.get_promoted_songs() #self.dmpdata("promoted_tracks", data) return [_parse_track(t) for t in data] def get_genres(self, parent=None): data = self.api.get_genres(parent_genre_id=parent) return [_parse_genre(g) for g in data] def get_artist_info(self, artist_id, doRelated=False): ret = {"albums" : [], "toptracks" : [], "related" : []} # Happens,some library tracks have no artistId entry if artist_id is None or artist_id == 'None': uplog("get_artist_albums: artist_id is None") return ret else: uplog("get_artist_albums: artist_id %s" % artist_id) maxrel = 20 if doRelated else 0 maxtop = 0 if doRelated else 10 incalbs = False if doRelated else True data = self.api.get_artist_info(artist_id, include_albums=incalbs, max_top_tracks=maxtop, max_rel_artist=maxrel) #self.dmpdata("artist_info", data) if 'albums' in data: ret["albums"] = [_parse_album(alb) for alb in data['albums']] if 'topTracks' in data: ret["toptracks"] = [_parse_track(t) for t in data['topTracks']] if 'related_artists' in data: ret["related"] = [_parse_artist(a) for a in data['related_artists']] return ret def get_artist_related(self, artist_id): data = self.get_artist_info(artist_id, doRelated=True) return data["related"] def search(self, query): data = self.api.search(query, max_results=50) #self.dmpdata("Search", data) tr = [_parse_track(i['track']) for i in data['song_hits']] ar = [_parse_artist(i['artist']) for i in data['artist_hits']] al = [_parse_album(i['album']) for i in data['album_hits']] #self.dmpdata("Search playlists", data['playlist_hits']) try: pl = [_parse_splaylist(i) for i in data['playlist_hits']] except: pl = [] return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
class FetchSongs(object): """ Class to manage updating and creating of playlists and songs in the Google Play Music service for a specific Play Music account """ def __init__(self): self.songs = [] self.nids = [] os.environ['REQUESTS_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt' self.api = Mobileclient() self.api.login('xxxxxx', 'xxxxx', Mobileclient.FROM_MAC_ADDRESS) def add_songs_to_gmusic_playlist(self, playlist_id, song_ids): """ Add songs to the GMUSIC API playlist using song ids :param playlist_id: str playlist id :param song_ids: list of strings :returns: None """ return self.api.add_songs_to_playlist(playlist_id, song_ids) def search_for_songs(self, artist, title): """ Search for songs in the GMUSIC API :param artist: string :param title: string :return song_nid: string """ song_nid = None search_content = self.api.search(''.join([artist, ' ', title])) for each_song in search_content['song_hits']: song_detail = box.Box(each_song['track']) if artist.casefold() in song_detail.artist.casefold( ) and title.casefold() in song_detail.title.casefold(): song_nid = song_detail.storeId break return song_nid def get_playlists_length(self, api_content): """ Get size of GMUSIC playlists in terms of number of songs :param api_content: dict :returns playlist_sizes: dict """ playlist_sizes = {} api_playlists = self.api.get_all_playlists() for api_playlist in api_playlists: for content_playlist in api_content: if content_playlist['id'] == api_playlist['id']: playlist_sizes[api_playlist['id']] = len( content_playlist['tracks']) return playlist_sizes def get_available_playlists(playlist_dict): """ Get available playlist ids and associated number of songs :param playlist_dict: dict :return list_id, number_of_songs: tuple of list_id and number of songs """ available_lists = {k for (k, v) in playlist_dict.items() if v <= 800} if available_lists: for list_id, number_of_songs in available_lists.items(): yield (list_id, number_of_songs) else: return None @classmethod def create_gmusic_playlist(self, name): """ Create GMUSIC playlist :param name: string name of playlist :return: string success or fail""" return self.api.create_playlist(name)
count = 0 with progressbar.ProgressBar(max_value=len(bandlist), redirect_stdout=True) as bar: for item in bandlist: band = item.name printable = False try: print(('Trying {0}'.format(band))) printable = True except: print('Unprintable band.') if printable == True: if existing_bands[band] < 1: query = str(band) results1 = api.search(query, max_results=50) try: cleanname = cleanup( results1['artist_hits'][0]['artist']['name']) except Exception as e: print(str(e)) try: print(results1['artist_hits'][0]) except: misses.append(band) pass cleanname = '' cleanq = cleanup(query) if cleanq == cleanname: artistId = results1['artist_hits'][0]['artist'][ 'artistId']
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 Music: def __init__(self, bot): self.bot = bot self.voice_states = {} self.api = Mobileclient() self.logged_in = self.api.login('email', 'password', '1234567890abcdef') self.VOLUME_LEVEL = .1 def get_permissions(self): permissions = "" try: with open("permissions.txt", "r") as f: permissions = f.read() except Exception as e: with open("permissions.txt", "a") as f: f.write("") return permissions def get_voice_state(self, server): state = self.voice_states.get(server.id) if state is None: state = VoiceState(self.bot) self.voice_states[server.id] = state return state def check(self, ctx): command_text = "!{0}:{1}".format(ctx.command, ctx.message.author.mention) if command_text not in self.get_permissions(): print(command_text, "doesn't have access.") self.bot.say('You do not have permissions for this command.') return False return True async def create_voice_client(self, channel): voice = await self.bot.join_voice_channel(channel) state = self.get_voice_state(channel.server) state.voice = voice def __unload(self): for state in self.voice_states.values(): try: state.audio_player.cancel() if state.voice: self.bot.loop.create_task(state.voice.disconnect()) except: pass @commands.command(pass_context=True, no_pm=True) async def join(self, ctx, *, channel: discord.Channel): try: await self.create_voice_client(channel) except discord.ClientException: await self.bot.say('Already in a voice channel...') except discord.InvalidArgument: await self.bot.say('This is not a voice channel...') else: await self.bot.say('Ready to play audio in ' + channel.name) @commands.command(pass_context=True, no_pm=True) async def summon(self, ctx): if not self.check(ctx): await self.bot.say("You don't have access to that command.") return """Summons the bot to join your voice channel.""" summoned_channel = ctx.message.author.voice_channel if summoned_channel is None: await self.bot.say('You are not in a voice channel.') return False state = self.get_voice_state(ctx.message.server) if state.voice is None: state.voice = await self.bot.join_voice_channel(summoned_channel) else: await state.voice.move_to(summoned_channel) return True @commands.command(pass_context=True, no_pm=True) async def play(self, ctx, *, song: str): # if not self.check(ctx): # await self.bot.say("You don't have access to that command.") # return """Plays a song. If there is a song currently in the queue, then it is queued until the next song is done playing. This command automatically searches as well from YouTube. The list of supported sites can be found here: https://rg3.github.io/youtube-dl/supportedsites.html """ state = self.get_voice_state(ctx.message.server) opts = { 'default_search': 'auto', 'quiet': True, } if state.voice is None: success = await ctx.invoke(self.summon) if not success: return def s(): results = self.api.search(song, max_results=1) with open("output.txt", "wb") as f: f.write( str(results["song_hits"]).encode(sys.stdout.encoding, errors='replace')) track = results['song_hits'][0]['track'] song_id = track['storeId'] artist = track['artist'] # album = track['album'] title = track['title'] # track_nr = track['trackNumber'] # year = track['year'] # genre = track['genre'] # album_artist = track['albumArtist'] album_art = track['albumArtRef'][0]['url'] url = self.api.get_stream_url(song_id) # print(track, song_id, artist, album, title, track_nr, year, genre, album_artist) return url, title, artist, album_art try: track_url, title, artist, album_art = s() track_raw = request.urlopen(track_url) import glob if "./music/{}_{}.mp3".format( artist, title) not in glob.glob("./music/*.mp3"): await self.bot.say("Downloading {}'s {}.mp3".format( artist, title)) with open("./music/{}_{}.mp3".format(artist, title), "wb") as track_file: track_file.write(track_raw.read()) track_file.close() # player = state.voice.create_stream_player(track_raw, after=state.toggle_next) player = state.voice.create_ffmpeg_player( "./music/{}_{}.mp3".format(artist, title), after=state.toggle_next) data = {"title": title, "artist": artist, "album_art": album_art} # await state.voice.play_audio(open("temp.mp3", "rb")) # raise Exception("Not really an error") except Exception as e: fmt = 'An error occurred while processing this request: ```py\n{}: {}\n```' await self.bot.send_message(ctx.message.channel, fmt.format(type(e).__name__, e)) else: player.volume = self.VOLUME_LEVEL entry = VoiceEntry(ctx.message, player, data) # await self.bot.say('Enqueued ' + str(entry)) em = discord.Embed(title=data["artist"], description=data["title"], colour=0xDEADBF) em.set_author(name="Queued", icon_url=data["album_art"]) await self.bot.say("Beamin' up the music.", embed=em) await state.songs.put(entry) @commands.command(pass_context=True, no_pm=True) async def volume(self, ctx, value: int): if not self.check(ctx): await self.bot.say("You don't have access to that command.") return """Sets the volume of the currently playing song.""" state = self.get_voice_state(ctx.message.server) if state.is_playing(): player = state.player player.volume = value / 100 self.VOLUME_LEVEL = player.volume await self.bot.say('Set the volume to {:.0%}'.format(player.volume) ) @commands.command(pass_context=True, no_pm=True) async def pause(self, ctx): if not self.check(ctx): await self.bot.say("You don't have access to that command.") return """Pauses the currently played song.""" state = self.get_voice_state(ctx.message.server) if state.is_playing(): player = state.player player.pause() @commands.command(pass_context=True, no_pm=True) async def resume(self, ctx): if not self.check(ctx): await self.bot.say("You don't have access to that command.") return """Resumes the currently played song.""" state = self.get_voice_state(ctx.message.server) if state.is_playing(): player = state.player player.resume() @commands.command(pass_context=True, no_pm=True) async def stop(self, ctx): if not self.check(ctx): await self.bot.say("You don't have access to that command.") return """Stops playing audio and leaves the voice channel. This also clears the queue. """ server = ctx.message.server state = self.get_voice_state(server) if state.is_playing(): player = state.player player.stop() try: state.audio_player.cancel() del self.voice_states[server.id] await state.voice.disconnect() except: pass @commands.command(pass_context=True, no_pm=True) async def skip(self, ctx): # if not self.check(ctx): # await self.bot.say("You don't have access to that command.") # return """Vote to skip a song. The song requester can automatically skip. 3 skip votes are needed for the song to be skipped. """ state = self.get_voice_state(ctx.message.server) if not state.is_playing(): await self.bot.say('Not playing any music right now...') return num_members = len(ctx.message.server.members) voter = ctx.message.author if voter == state.current.requester: await self.bot.say('Requester requested skipping song...') state.skip() elif voter.id not in state.skip_votes: state.skip_votes.add(voter.id) total_votes = len(state.skip_votes) if total_votes >= num_members * .5: await self.bot.say('Skip vote passed, skipping song...') state.skip() else: await self.bot.say( 'Skip vote added, currently at [{}/3]'.format(total_votes)) else: await self.bot.say('You have already voted to skip this song.') @commands.command(pass_context=True, no_pm=True) async def playing(self, ctx): if not self.check(ctx): await self.bot.say("You don't have access to that command.") return """Shows info about the currently played song.""" state = self.get_voice_state(ctx.message.server) if state.current is None: await self.bot.say('Not playing anything.') else: skip_count = len(state.skip_votes) await self.bot.say('Now playing {} [skips: {}/3]'.format( state.current, skip_count)) # @commands.command(pass_context=True, no_pm=True) # async def test(self, ctx): # em = discord.Embed(title='My Embed Title', description='My Embed Content.', colour=0xDEADBF) # em.set_author(name='Someone', icon_url=bot.user.default_avatar_url) # await self.bot.say("test", embed=em) @commands.command(pass_context=True) async def grant(self, ctx, user, command): if not self.check(ctx): await self.bot.say("You don't have access to that command.") return permissions = self.get_permissions() commands = command.split("+") for command in commands: to_file_text = "!{0}:{1}".format(command, user) if to_file_text in permissions: permissions = permissions.replace(to_file_text + "\n", "") with open("permissions.txt", "w") as f: f.write(permissions) await self.bot.say("Removed {} access to !{}".format( user, command)) else: with open("permissions.txt", "a") as f: f.write(to_file_text + "\n") await self.bot.say("Granted {} access to !{}".format( user, command))