Example #1
0
    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()
Example #2
0
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]
Example #3
0
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)
Example #5
0
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")
Example #6
0
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', [])
            }
Example #7
0
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
Example #8
0
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)
Example #9
0
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()
Example #10
0
    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)
Example #11
0
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', [])}
Example #12
0
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
Example #13
0
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')
Example #14
0
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
Example #15
0
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)
Example #16
0
            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.')
Example #17
0
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)
Example #18
0
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()
Example #19
0
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:
Example #21
0
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)
Example #23
0
    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)
Example #24
0
    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)
Example #25
0
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
Example #26
0
def playMusic(): #play music from... play music.
	api = Mobileclient()
	api.search("token code red", max_results=1)
Example #27
0
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
Example #28
0
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)
Example #29
0
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)
Example #30
0
class Session(object):
    def __init__(self):
        self.api = None
        self.user = None
        self.lib_albums = {}
        self.lib_artists = {}
        self.lib_tracks = {}
        self.lib_updatetime = 0
        self.sitdata = []
        self.sitbyid = {}
        self.sitdataupdtime = 0

    def dmpdata(self, who, data):
        print("%s: %s" % (who, json.dumps(data, indent=4)), file=sys.stderr)

    def login(self, username, password, deviceid=None):
        self.api = Mobileclient(debug_logging=False)

        if deviceid is None:
            logged_in = self.api.login(username, password, Mobileclient.FROM_MAC_ADDRESS)
        else:
            logged_in = self.api.login(username, password, deviceid)

        # print("Logged in: %s" % logged_in)
        # data = self.api.get_registered_devices()
        # print("registered: %s" % data)
        # isauth = self.api.is_authenticated()
        # print("Auth ok: %s" % isauth)
        return logged_in

    def _get_user_library(self):
        now = time.time()
        if now - self.lib_updatetime < 300:
            return
        data = self.api.get_all_songs()
        # self.dmpdata("all_songs", data)
        self.lib_updatetime = now
        tracks = [_parse_track(t) for t in data]
        self.lib_tracks = dict([(t.id, t) for t in tracks])
        for track in tracks:
            # We would like to use the album id here, but gmusic
            # associates the tracks with any compilations after
            # uploading (does not use the metadata apparently), so
            # that we can't (we would end up with multiple
            # albums). OTOH, the album name is correct (so seems to
            # come from the metadata). What we should do is test the
            # album ids for one album with a matching title, but we're
            # not sure to succeed. So at this point, the album id we
            # end up storing could be for a different albums, and we
            # should have a special library-local get_album_tracks
            self.lib_albums[track.album.name] = track.album
            self.lib_artists[track.artist.id] = track.artist

    def get_user_albums(self):
        self._get_user_library()
        return self.lib_albums.values()

    def get_user_artists(self):
        self._get_user_library()
        return self.lib_artists.values()

    def get_user_playlists(self):
        pldata = self.api.get_all_playlists()
        # self.dmpdata("playlists", pldata)
        return [_parse_playlist(pl) for pl in pldata]

    def get_user_playlist_tracks(self, playlist_id):
        self._get_user_library()
        data = self.api.get_all_user_playlist_contents()
        # self.dmpdata("user_playlist_content", data)
        trkl = [item["tracks"] for item in data if item["id"] == playlist_id]
        if not trkl:
            return []
        try:
            return [self.lib_tracks[track["trackId"]] for track in trkl[0]]
        except:
            return []

    def create_station_for_genre(self, genre_id):
        id = self.api.create_station("station" + genre_id, genre_id=genre_id)
        return id

    def get_user_stations(self):
        data = self.api.get_all_stations()
        # parse_playlist works fine for stations
        stations = [_parse_playlist(d) for d in data]
        return stations

    def delete_user_station(self, id):
        self.api.delete_stations(id)

    # not working right now
    def listen_now(self):
        print("api.get_listen_now_items()", file=sys.stderr)
        ret = {"albums": [], "stations": []}
        try:
            data = self.api.get_listen_now_items()
        except Exception as err:
            print("api.get_listen_now_items failed: %s" % err, file=sys.stderr)
            data = None

        # listen_now entries are not like normal albums or stations,
        # and need special parsing. I could not make obvious sense of
        # the station-like listen_now entries, so left them aside for
        # now. Maybe should use create_station on the artist id?
        if data:
            ret["albums"] = [_parse_ln_album(a["album"]) for a in data if "album" in a]
            # ret['stations'] = [_parse_ln_station(d['radio_station']) \
            #                   for d in data if 'radio_station' in d]
        else:
            print("listen_now: no items returned !", file=sys.stderr)
        print(
            "get_listen_now_items: returning %d albums and %d stations" % (len(ret["albums"]), len(ret["stations"])),
            file=sys.stderr,
        )
        return ret

    def get_situation_content(self, id=None):
        ret = {"situations": [], "stations": []}
        now = time.time()
        if id is None and now - self.sitdataupdtime > 300:
            self.sitbyid = {}
            self.sitdata = self.api.get_listen_now_situations()
            self.sitdataupdtime = now

        # Root is special, it's a list of situations
        if id is None:
            ret["situations"] = [self._parse_situation(s) for s in self.sitdata]
            return ret

        # not root
        if id not in self.sitbyid:
            print("get_situation_content: %s unknown" % id, file=sys.stderr)
            return ret

        situation = self.sitbyid[id]
        # self.dmpdata("situation", situation)
        if "situations" in situation:
            ret["situations"] = [self._parse_situation(s) for s in situation["situations"]]
        if "stations" in situation:
            ret["stations"] = [_parse_situation_station(s) for s in situation["stations"]]

        return ret

    def _parse_situation(self, data):
        self.sitbyid[data["id"]] = data
        return Playlist(id=data["id"], name=data["title"])

    def create_curated_and_get_tracks(self, id):
        sid = self.api.create_station("station" + id, curated_station_id=id)
        print("create_curated: sid %s" % sid, file=sys.stderr)
        tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)]
        # print("curated tracks: %s"%tracks, file=sys.stderr)
        self.api.delete_stations(sid)
        return tracks

    def get_station_tracks(self, id):
        return [_parse_track(t) for t in self.api.get_station_tracks(id)]

    def get_media_url(self, song_id, quality=u"med"):
        url = self.api.get_stream_url(song_id, quality=quality)
        print("get_media_url got: %s" % url, file=sys.stderr)
        return url

    def get_album_tracks(self, album_id):
        data = self.api.get_album_info(album_id, include_tracks=True)
        album = _parse_album(data)
        return [_parse_track(t, album) for t in data["tracks"]]

    def get_promoted_tracks(self):
        data = self.api.get_promoted_songs()
        # self.dmpdata("promoted_tracks", data)
        return [_parse_track(t) for t in data]

    def get_genres(self, parent=None):
        data = self.api.get_genres(parent_genre_id=parent)
        return [_parse_genre(g) for g in data]

    def get_artist_info(self, artist_id, doRelated=False):
        ret = {"albums": [], "toptracks": [], "related": []}
        # Happens,some library tracks have no artistId entry
        if artist_id is None or artist_id == "None":
            print("get_artist_albums: artist_id is None", file=sys.stderr)
            return ret
        else:
            print("get_artist_albums: artist_id %s" % artist_id, file=sys.stderr)

        maxrel = 20 if doRelated else 0
        maxtop = 0 if doRelated else 10
        incalbs = False if doRelated else True
        data = self.api.get_artist_info(artist_id, include_albums=incalbs, max_top_tracks=maxtop, max_rel_artist=maxrel)
        # self.dmpdata("artist_info", data)
        if "albums" in data:
            ret["albums"] = [_parse_album(alb) for alb in data["albums"]]
        if "topTracks" in data:
            ret["toptracks"] = [_parse_track(t) for t in data["topTracks"]]
        if "related_artists" in data:
            ret["related"] = [_parse_artist(a) for a in data["related_artists"]]
        return ret

    def get_artist_related(self, artist_id):
        data = self.get_artist_info(artist_id, doRelated=True)
        return data["related"]

    def search(self, query):
        data = self.api.search(query, max_results=50)
        # self.dmpdata("Search", data)

        tr = [_parse_track(i["track"]) for i in data["song_hits"]]
        print("track ok", file=sys.stderr)
        ar = [_parse_artist(i["artist"]) for i in data["artist_hits"]]
        print("artist ok", file=sys.stderr)
        al = [_parse_album(i["album"]) for i in data["album_hits"]]
        print("album ok", file=sys.stderr)
        # self.dmpdata("Search playlists", data['playlist_hits'])
        try:
            pl = [_parse_splaylist(i) for i in data["playlist_hits"]]
        except:
            pl = []
        print("playlist ok", file=sys.stderr)
        return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
Example #31
0
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
Example #32
0
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)
Example #33
0
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)
Example #34
0
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)
Example #35
0
    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']
Example #36
0
 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)
Example #37
0
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)
Example #38
0
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))