Example #1
0
class gmObject:
    
    def __init__(self):
        self.mc = Mobileclient()
        self.wc = Webclient()
        self.mm = Musicmanager()
    
    def login(self, username, password):
        error.e = 0
        
        if not self.mc.login(username, password):
            gmtPrintV("gmObject.login: Wrong username or password (or no internet connection)")
            error.e = error.LOGIN_FAILED
            return
        
        if not self.wc.login(username, password):
            gmtPrintV("gmObject.login: Wrong username or password (or no internet connection)")
            error.e = error.LOGIN_FAILED
            return
        
        if not self.mm.login(config.cred_path):
            gmtPrintV("gmObject.login: Wrong credentials (or no internet connection)")
            error.e = error.LOGIN_FAILED
            return
        
    def logout(self):
        error.e = 0
        
        try:
            self.mc.logout()
            self.wc.logout()
            self.mm.logout()
        except:
            gmtPrintV("gmObject.logout: Logout failed")
            error.e = error.LOGOUT_FAILED
	def get_data(self):
		mobileapi = Mobileclient()
		mobileapi.login(setting.GUSER, setting.GPASS)
		library = mobileapi.get_all_songs()
		mobileapi.logout()
		
		return library
Example #3
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 #4
0
class AudioStream:
    __username = configuration.get('google_username')
    __password = configuration.get('google_password')
    __track_prefetch = 15
    __client = None
    __playlist = []

    def __init__(self, station_id = 'IFL'):
        self.__client = Mobileclient()
        self.__client.login(self.__username, self.__password, Mobileclient.FROM_MAC_ADDRESS)
        self.__playlist = self.__fetchTrackIDs(station_id)

    def __del__(self):
        if self.__client:
            self.__client.logout()

    def __fetchTrackIDs(self, station_id):
        if not self.__client or not self.__client.is_authenticated():
            logger.error("Client is not authenticated!")
            return []

        tracklist = self.__client.get_station_tracks(station_id, num_tracks=self.__track_prefetch)
        logger.info("Received tracks: %r" % json.dumps(tracklist))

        # Filter out explicit tracks, where non-explicit is explicitType=2
        tracklist = [track for track in tracklist if not 'explicitType' in track or track['explicitType'] == "2"]
        logger.info("Non-explicit tracks: %r" % json.dumps(tracklist))

        # Fetch both song IDs and Nautilus (old) IDs
        songids = [track['id'] for track in tracklist if 'id' in track]
        nautids = [track['nid'] for track in tracklist if 'nid' in track]

        return songids + nautids

    def pop(self):
        while self.__playlist:
            track_id = self.__playlist.pop()

            try:
                stream_url = self.__client.get_stream_url(track_id, quality='low')
                return stream_url
            except(exceptions.CallFailure):
                logger.warning("Failed to fetch Stream URL for ID %s" % track_id)

            raise IndexError("pop from empty list")

    def reverse(self):
        # Reverse just returns itself, since the playlist is already chaos
        return self

    def __len__(self):
        return len(self.__playlist)
def main():
    parser = argparse.ArgumentParser(description='Sync iTunes Playlists to Google Play Music.')
    parser.add_argument('itunes_music_library', type=str,
                        help='Path to iTunes Music Library.xml')
    parser.add_argument('google_music_manager_db', type=str,
                        help='Path to Google Music Manager ServerDatabase.db')
    parser.add_argument('--verbose', action='store_true', default=False,
                        help='Print verbose output')
    parser.add_argument('playlists', type=str, nargs='*',
                        metavar='playlist',
                        help='Names of playlists to sync')
    args = parser.parse_args()
    global verbose
    verbose = args.verbose
    lib = pyItunes.Library(args.itunes_music_library)
    known_itunes_playlists = lib.getPlaylistNames()
    if args.playlists:
        itunes_playlists = args.playlists
        not_found = set(itunes_playlists) - set(known_itunes_playlists)
        if not_found:
            print('''Error: these playlists aren't in your iTunes Library:
%s
''' % (sorted(not_found), ))
            return 1
    else:
        itunes_playlists = known_itunes_playlists

    server_db = sqlite3.connect(args.google_music_manager_db)
    api = None
    username, password = open(os.path.join(os.path.dirname(__file__),
                                           'auth.txt'),
                              'r').read().splitlines()
    try:
        api = Mobileclient()
        if not api.login(username, password):
            print('Error: unable to login', file=sys.stderr)
            return 1
        all_google_playlists = api.get_all_user_playlist_contents()
        google_playlists = {p['name']: p for p in all_google_playlists}
        for name in itunes_playlists:
            sync_playlist(api, server_db, lib.getPlaylist(name),
                          google_playlists)
    finally:
        if api:
            api.logout()
    return 0
def get_albums_from_playlist(config):
    login, password, playlist_name, android_id = map(config.get, ('login', 'password', 'playlist', 'android_id'))
    api = Mobileclient()
    if not android_id:
        android_id = Mobileclient.FROM_MAC_ADDRESS 
    try:
        api.login(login, password, android_id)
        all_playlists = api.get_all_user_playlist_contents()
        matched_playlist = next(playlist for playlist in all_playlists if playlist['name'].lower() == playlist_name.lower())
        album_list = {(entry['track']['albumArtist'], entry['track']['album'])
              for entry in matched_playlist['tracks'] if 'track' in entry}
        return album_list
    except StopIteration:
        sys.exit('playlist not found.')
    except NotLoggedIn:
        sys.exit('wrong username or password.')
    finally:
        api.logout()
class MusicSync(object):
    def __init__(self, email=None, password=None):
        self.mm = Musicmanager()
        self.wc = Webclient()
        self.mc = Mobileclient()
        if not email:
            email = raw_input("Email: ")
        if not password:
            password = getpass()

        self.email = email
        self.password = password

        self.logged_in = self.auth()

        print "Fetching playlists from Google..."
        self.playlists = self.mc.get_all_user_playlist_contents()
        #self.playlists = self.mc.get_all_playlists()
        #self.playlists = self.wc.get_all_playlist_ids(auto=False)
        self.all_songs = self.mc.get_all_songs()
        #print "Got %d playlists." % len(self.playlists['user'])
        print "Got %d playlists containing %d songs." % (len(self.playlists), len(self.all_songs))
        print ""


    def auth(self):
        self.logged_in = self.mc.login(self.email, self.password)
        #self.logged_in = self.wc.login(self.email, self.password)
        if not self.logged_in:
            print "Login failed..."
            exit()

        print ""
        print "Logged in as %s" % self.email
        print ""

        if not os.path.isfile(OAUTH_FILEPATH):
            print "First time login. Please follow the instructions below:"
            self.mm.perform_oauth()
        self.logged_in = self.mm.login()
        if not self.logged_in:
            print "OAuth failed... try deleting your %s file and trying again." % OAUTH_FILEPATH
            exit()

        print "Authenticated"
        print ""


    def sync_playlist(self, filename, remove_missing):
    #def sync_playlist(self, filename, remove_missing=False):
        filename = self.get_platform_path(filename)
        os.chdir(os.path.dirname(filename))
        title = os.path.splitext(os.path.basename(filename))[0]
        print "Syncing playlist: %s" % filename
        #if title not in self.playlists['user']:
            #print "   didn't exist... creating..."
            #self.playlists['user'][title] = [self.wc.create_playlist(title)]
        print ""

        plid = ""

        for pl in self.playlists:
            if pl['name'] == title:
                plid = pl['id']
                goog_songs = pl['tracks']

        if plid == "":
            print "   didn't exist... creating..."
            plid = self.mc.create_playlist(self, title)

        #plid = self.playlists['user'][title][0]
        #goog_songs = self.wc.get_playlist_songs(plid)
        print "%d songs already in Google Music playlist" % len(goog_songs)
        pc_songs = self.get_files_from_playlist(filename)
        print "%d songs in local playlist" % len(pc_songs)
        print ""

        # Sanity check max 1000 songs per playlist
        if len(pc_songs) > MAX_SONGS_IN_PLAYLIST:
            print "    Google music doesn't allow more than %d songs in a playlist..." % MAX_SONGS_IN_PLAYLIST
            print "    Will only attempt to sync the first %d songs." % MAX_SONGS_IN_PLAYLIST
            del pc_songs[MAX_SONGS_IN_PLAYLIST:]

        existing_files = 0
        added_files = 0
        failed_files = 0
        removed_files = 0
        fatal_count = 0

        for fn in pc_songs:
            if self.file_already_in_list(fn, goog_songs, self.all_songs):
                existing_files += 1
                continue
            print ""
            print "Adding: %s" % os.path.basename(fn).encode('cp1252')
            #print "Adding: %s" % os.path.basename(fn)
            #online = False
            online = self.find_song(fn, goog_songs, self.all_songs)
            #online = self.find_song(fn)
            song_id = None
            if online:
                song_id = online['id']
                print "   already uploaded [%s]" % song_id
            else:
                attempts = 0
                result = []
                while not result and attempts < MAX_UPLOAD_ATTEMPTS_PER_FILE:
                    print "   uploading... (may take a while)"
                    attempts += 1
                    try:
                        result = self.mm.upload(fn)
                    except (BadStatusLine, CannotSendRequest):
                        # Bail out if we're getting too many disconnects
                        if fatal_count >= MAX_CONNECTION_ERRORS_BEFORE_QUIT:
                            print ""
                            print "Too many disconnections - quitting. Please try running the script again."
                            print ""
                            exit()

                        print "Connection Error -- Reattempting login"
                        fatal_count += 1
                        self.wc.logout()
                        self.mc.logout()
                        self.mm.logout()
                        result = []
                        time.sleep(STANDARD_SLEEP)

                    except:
                        result = []
                        time.sleep(STANDARD_SLEEP)

                try:
                    if result[0]:
                        song_id = result[0].itervalues().next()
                    else:
                        song_id = result[1].itervalues().next()
                    print "   upload complete [%s]" % song_id
                except:
                    print "      upload failed - skipping"
                    tag = self.get_id3_tag(fn)
                    print "      failed song:\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252'))

            if not song_id:
                failed_files += 1
                continue

            added = self.mc.add_songs_to_playlist(plid, song_id)
            time.sleep(.3) # Don't spam the server too fast...
            print "   done adding to playlist"
            added_files += 1

        if remove_missing:
            for g in goog_songs:
                for s in self.all_songs:
                    if g['trackId'] == s['id']:
                        print ""
                        print "Removing: %s" % s['title'].encode('cp1252')
                        self.mc.remove_entries_from_playlist(g['id'])
                        #self.wc.remove_songs_from_playlist(plid, s.id)
                        time.sleep(.3) # Don't spam the server too fast...
                        removed_files += 1

        print ""
        print "---"
        print "%d songs unmodified" % existing_files
        print "%d songs added" % added_files
        print "%d songs failed" % failed_files
        print "%d songs removed" % removed_files


    def get_files_from_playlist(self, filename):
        files = []
        f = codecs.open(filename, encoding='cp1252')
        #f = codecs.open(filename, encoding='utf-8')
        for line in f:
            line = line.rstrip().replace(u'\ufeff',u'')
            if line == "" or line[0] == "#":
                continue
            path  = os.path.abspath(self.get_platform_path(line))
            if not os.path.exists(path):
                print "File not found: %s" % line
                continue
            files.append(path)
        f.close()
        return files

    def file_already_in_list(self, filename, goog_songs, all_songs):
        tag = self.get_id3_tag(filename)
        print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252'))
        i = 0
        while i < len(goog_songs):
            for s in all_songs:
                if goog_songs[i]['trackId'] == s['id']:
                    if self.tag_compare(s, tag):
                        print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252'))
                        goog_songs.pop(i)
                        return True
            i += 1
        return False

    def get_id3_tag(self, filename):
        data = mutagen.File(filename, easy=True)
        r = {}
        if 'title' not in data:
            title = os.path.splitext(os.path.basename(filename))[0]
            print 'Found song with no ID3 title, setting using filename:'
            print '  %s' % title
            print '  (please note - the id3 format used (v2.4) is invisible to windows)'
            data['title'] = [title]
            data.save()
        r['title'] = data['title'][0]
        r['track'] = int(data['tracknumber'][0].split('/')[0]) if 'tracknumber' in data else 0
        # If there is no track, try and get a track number off the front of the file... since thats
        # what google seems to do...
        # Not sure how google expects it to be formatted, for now this is a best guess
        if r['track'] == 0:
            m = re.match("(\d+) ", os.path.basename(filename))
            if m:
                r['track'] = int(m.group(0))
        r['artist'] = data['artist'][0] if 'artist' in data else ''
        r['album'] = data['album'][0] if 'album' in data else ''
        return r

    def find_song(self, filename, goog_songs, all_songs):
        tag = self.get_id3_tag(filename)
        print "Searching for\t%s\t%s\t%s" % (tag['title'].encode('cp1252'), tag['artist'].encode('cp1252'), tag['album'].encode('cp1252'))
        #results = self.wc.search(tag['title'])
        # NOTE - diagnostic print here to check results if you're creating duplicates
        #print results['song_hits']
        #for r in goog_songs:
        #for r in results['song_hits']:
        for s in all_songs:
            #if r['trackId'] == s['id']:
            if self.tag_compare(s, tag):
                # TODO: add rough time check to make sure its "close"
                print "Found match\t%s\t%s\t%s" % (s['title'].encode('cp1252'), s['artist'].encode('cp1252'), s['album'].encode('cp1252'))
                return s
        return None

    def tag_compare(self, g_song, tag):
        # If a google result has no track, google doesn't return a field for it
        if 'title' not in g_song:
            g_song['title'] = ""
        if 'artist' not in g_song:
            g_song['artist'] = ""
        if 'album' not in g_song:
            g_song['album'] = ""
        if 'track' not in g_song:
            g_song['track'] = 0
        if (g_song['title'].lower() == tag['title'].lower() and g_song['artist'].lower() == tag['artist'].lower()) or\
           (g_song['album'].lower() == tag['album'].lower() and g_song['title'].lower() == tag['title'].lower()) or\
           (g_song['artist'].lower() == tag['artist'].lower() and g_song['album'].lower() == tag['album'].lower() and g_song['track'] == tag['track']):
            print "Partial match\t%s\t%s\t%s" % (g_song['title'].encode('cp1252'), g_song['artist'].encode('cp1252'), g_song['album'].encode('cp1252'))
        return g_song['title'].lower() == tag['title'].lower() and\
               g_song['artist'].lower() == tag['artist'].lower() and\
               g_song['album'].lower() == tag['album'].lower() #and\
               #g_song['track'] == tag['track']

    def delete_song(self, sid):
        self.mc.delete_songs(sid)
        print "Deleted song by id [%s]" % sid

    def get_platform_path(self, full_path):
        # Try to avoid messing with the path if possible
        if os.sep == '/' and '\\' not in full_path:
            return full_path
        if os.sep == '\\' and '\\' in full_path:
            return full_path
        if '\\' not in full_path:
            return full_path
        return os.path.normpath(full_path.replace('\\', '/'))
Example #8
0
class GoogleMusicController(object):
    def __init__(self):
        self.device_id = os.environ['GOOGLE_MUSIC_DEVICE_ID']
        self.client = Mobileclient(debug_logging=False)
        # TODO: change this to relative path from run location
        self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS,
                                'iota/auth.json')
        self.player_data = Manager().dict()
        self.player = None
        self.player_pid = None
        self.playlist = Playlist('Now Playing')

    def _get_entity(self, name: str, type: str, extra_filter=lambda _: True):
        results = self.search(name).__dict__[type]
        if len(results) == 0:
            return None
        results = list(filter(extra_filter, results))
        if len(results) == 0:
            return None
        # We will trust Google's ability to filter search results... :P
        return results[0]

    def _get_song(self, name: str, artist: str = '', album: str = '') -> Song:
        if artist != '' and album != '':
            return self._get_entity(
                name, 'songs', lambda x: x.artist.lower() == artist and x.album
                .lower() == album)
        if artist != '':
            return self._get_entity(name, 'songs',
                                    lambda x: x.artist.lower() == artist)
        if album != '':
            return self._get_entity(name, 'songs',
                                    lambda x: x.album.lower() == album)
        return self._get_entity(name, 'songs')

    def _get_album(self, name: str, artist: str = '') -> Album:
        if artist != '':
            return self._get_entity(name, 'albums',
                                    lambda x: x.artist == artist)
        return self._get_entity(name, 'albums')

    def _get_artist(self, name: str) -> Artist:
        return self._get_entity(name, 'artists')

    def _get_playlist(self, name: str) -> Playlist:
        playlists = self.client.get_all_user_playlist_contents()
        matched_playlists = []
        for p in playlists:
            p_name = p['name'].lower()
            if p_name == name or p_name == name.replace(' ', ''):
                matched_playlists.append(p)
        if len(matched_playlists) > 0:
            found = matched_playlists[0]
            self.playlist = Playlist(found['name'])
            [
                self.playlist.add_song(track['track'])
                for track in found['tracks']
            ]
            return self.playlist
        return None

    def play_song(self,
                  name: str,
                  callback_at_end,
                  artist: str = '',
                  album: str = ''):
        song = self._get_song(name, artist, album)
        if song is None:
            return f'I couldn\'t find a song called {name} by {artist}'
        return self.play_playlist('Now Playing',
                                  callback_at_end,
                                  song_list=[song])

    def play_playlist(
        self,
        name: str,
        callback_at_end,
        song_list=[],
        start=0,
        shuffle=False,
    ) -> str:
        if song_list == []:
            self.playlist = self._get_playlist(name)
            if self.playlist is None:
                return f'I couldn\'t find a playlist called {name}'
        else:
            self.playlist = Playlist(name)
            self.playlist.set_list(song_list)
        if shuffle:
            self.playlist.shuffle()

        # Embed this so we don't have to pass a bunch of context out
        def get_url(id):
            # we need to logout and log back in to allow rapid requesting
            # of stream_urls -- they expire after a minute, and can't be
            # re-requested before then without an SSLError...thanks Google.
            self.client.logout()
            self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS, 'auth.json')
            return self.client.get_stream_url(id, device_id=self.device_id)

        # Spawn a subprocess for the player
        self.player = Process(target=spawn_player,
                              args=(get_url, self.playlist, self.player_data,
                                    callback_at_end, start))
        self.player.start()
        self.player_pid = self.player.pid
        return None

    def pause_song(self):
        if 'pid' in self.player_data.keys():
            psutil.Process(self.player_data['pid']).send_signal(signal.SIGSTOP)

    def resume_song(self):
        if 'pid' in self.player_data.keys():
            psutil.Process(self.player_data['pid']).send_signal(signal.SIGCONT)

    def stop_player(self):
        if 'pid' in self.player_data.keys():
            psutil.Process(self.player_data['pid']).send_signal(signal.SIGSTOP)
        # self.player.terminate()

    def next_song(self) -> str:
        if 'pid' in self.player_data.keys():
            psutil.Process(self.player_data['pid']).send_signal(signal.SIGTERM)

    def previous_song(self) -> str:
        if 'index' not in self.player_data.keys():
            return 'Could not start the playlist, missing index'
        idx = self.player_data['index']
        idx = idx - 1 if idx > 0 else 0
        if not self.player_data['done']:
            self.stop_player()
        self.play_playlist(self.playlist.name.lower(),
                           self.playlist.songs,
                           start=idx)
        return ''

    def start_over(self):
        return ''

    def search(self, query: str, max_results: int = 100) -> SearchResults:
        results = self.client.search(query, max_results)
        return SearchResults(results)
Example #9
0
class BasePlayer(object):
    def __init__(self,
                 *,
                 email=None,
                 password=None,
                 interval=3,
                 width=50,
                 shuffle=True,
                 repeat=True,
                 loop=False):
        self.api = Mobileclient()
        self.vlc_media_player = vlc.MediaPlayer()
        self.interval = abs(interval)
        self.width = int(abs(width))
        self.shuffle = shuffle
        self.repeat = repeat
        self.loop = loop

        if email is not None and password is not None:
            self._logged_in = False
            self.api_login(email, password)
        else:
            self._logged_in = False

    def api_login(self, email, password):
        attempts = 0

        while not self._logged_in and attempts < 3:
            self._logged_in = self.api.login(email, password,
                                             Mobileclient.FROM_MAC_ADDRESS)
            attempts += 1

    def close(self):
        if self._logged_in:
            self.api.logout()

    def prepare(self):
        if (not self._logged_in) or (not self.api.is_authenticated()):
            raise LoginFailure
        else:
            return True

    def start(self):
        try:
            self._run_player()
        except (KeyboardInterrupt, PlayerExitException):
            self.close()
            print('\nGood bye')
        finally:
            return True

    def get_tracks(self):
        # This method returns list of tracks
        raise NotImplementedError

    def _loop_index(self, index, cmd, length):
        if self.repeat:
            index = loop_index(index, cmd, length)
        else:
            index += 1

        return index

    def _run_player(self):

        while True:
            tracks = self.get_tracks()
            if self.shuffle:
                random.shuffle(tracks)
            i = 0
            ns = 0
            while i < len(tracks):
                try:
                    track_id = choose_track_id(tracks[i])
                except KeyError:
                    i = self._loop_index(index=i, cmd='f', length=len(tracks))
                    continue
                except StoredTrackError:
                    ns += 1
                    i = loop_index(index=i, cmd='f', length=len(tracks))
                    warnings.warn('Track is not in the store.\n')
                    if ns >= len(tracks):
                        warnings.warn('All tracks are not in the store.\n')
                        break
                    else:
                        continue

                cmd = self._play_track(track_id)
                if cmd == 's':
                    break

                i = self._loop_index(index=i, cmd=cmd, length=len(tracks))

    def _play_track(self, track_id):
        self.prepare()

        try:
            info = self.api.get_track_info(track_id)
            url = self.api.get_stream_url(track_id)
        except CallFailure as e:
            warnings.warn(str(e))
            return 'f'

        tmp = tempfile.NamedTemporaryFile(delete=False)

        def close_player():
            self.vlc_media_player.stop()
            tmp.close()
            os.remove(tmp.name)

        try:
            tmp.write(urllib.request.urlopen(url).read())

            self.vlc_media_player.set_mrl(tmp.name)
            self.vlc_media_player.play()

            paused = False
            duration = int(info['durationMillis'])

            while True:
                clear_screen()
                print_track_info(info)

                current = self.vlc_media_player.get_time()
                remain = (duration - current) / 1000
                timeout = min(remain, self.interval)

                print_bar(current, duration, remain, self.width)
                print_command_list()

                if paused:
                    cmd = input('PAUSED\n>>')
                else:
                    try:
                        cmd = inputimeout(timeout=timeout, prompt='>>')
                    except TimeoutOccurred:
                        if remain > self.interval:
                            continue
                        if self.loop:
                            cmd = 'r'
                        else:
                            cmd = 'f'

                if is_next(cmd):
                    close_player()
                    return cmd
                elif is_quit(cmd):
                    raise PlayerExitException
                elif cmd == 'p':
                    paused = not paused
                    self.vlc_media_player.pause()

        except BaseException:
            close_player()
            raise
category_b_playlist = [p for p in playlists if p['name'] == secret.CATEGORY_B][0]

# Splitting up songs by whether they are categorized or not
all_songs_set = get_trackids_set(big_playlist)
cat_a_songs_set = get_trackids_set(category_a_playlist)
cat_b_songs_set = get_trackids_set(category_b_playlist)
all_categorized_songs_set = cat_a_songs_set | cat_b_songs_set
uncategorized_songs_set = all_songs_set - all_categorized_songs_set

# Remove songs from uncategorized that have been categorized already
print("Removing newly categorized songs from the uncategorized playlist named", secret.UNCATEGORIZED)
uncategorized_playlist_songs_set = get_trackids_set(uncategorized_playlist)
already_categorized_songs_set = all_categorized_songs_set & uncategorized_playlist_songs_set
already_categorized_songs_ids = [song['id'] for song in uncategorized_playlist['tracks'] if song['trackId'] in already_categorized_songs_set]
api.remove_entries_from_playlist(already_categorized_songs_ids)
print("Removed", len(already_categorized_songs_ids), "songs from the uncategorized playlist named", secret.UNCATEGORIZED)

# Add songs that are not categorized, to uncategorized
# First, don't re-add songs that are still uncategorized
print("Adding uncategorized songs to playlist named",  secret.UNCATEGORIZED)
newly_uncategorized_songs_set = uncategorized_songs_set - uncategorized_playlist_songs_set
api.add_songs_to_playlist(uncategorized_playlist['id'], list(newly_uncategorized_songs_set))
print("Success! Added", len(newly_uncategorized_songs_set), "songs to playlist named", secret.UNCATEGORIZED)
api.logout()
print("Logged out")





if not mc.is_subscribed:
    print("This user is not subscribed")
    print("Google Play Music empties playlists while a user is unsubscribed")
    print("Therefore playlists transferred over will be empty\n")

songs = mc.get_all_songs()
playlists = mc.get_all_user_playlist_contents()
#radios = mc.get_all_stations()
#podcasts = mc.get_all_podcast_series()

print("I've found " + str(len(songs)) + " songs in your library")
print("I've found " + str(len(playlists)) + " user playlists in your library")
#print("I've found " + str(len(radios)) + " radios in your library")
#print("I've found " + str(len(podcasts)) + " podcasts in your library")

mc.logout()
print("Logged Out\n")

#Logging into new account
print("Log into the Play Music account that you want to transfer songs to")

while 1:
    login(mc)
    if not mc.is_subscribed:
        print("This account is not subscribed")
        print("Please log in with a subscribed account\n")
        mc.logout()
        continue
    break

#Adding songs
    export_thumbs_up = export_api.get_thumbs_up_songs()
    # strip out any tracks that are not available on All Access
    thumbs_up_tracks = [t for t in export_thumbs_up if track_has_aa_data(t)]

if migration_type == 'all' or migration_type == 'playlists':
    log.info('Retrieving playlists from ' + export_username)
    export_playlists = export_api.get_all_user_playlist_contents()
    playlists = [p for p in export_playlists if not p.get('deleted')]

if migration_type == 'all' or migration_type == 'stations':  
    log.info('Retrieving stations from ' + export_username)
    export_stations = export_api.get_all_stations()
    radio_stations = [s for s in export_stations if not s.get('deleted')]

log.info('Export complete')
export_api.logout()
log.debug('API logout for ' + export_username)

# import tracks
if migration_type == 'all' or migration_type == 'tracks':
    log.info('Importing ' + str(len(all_tracks)) + ' All Access tracks to ' + import_username)
    
    for i, track in enumerate(all_tracks, start=1):
        track_id = get_aa_id(track)
        track_artist = track.get('artist')
        track_title = track.get('title')

        if i % 100 == 0:
            log.info('Importing track ' + str(i) + ' of ' + str(len(all_tracks)))
        if not simulate:
            try:
Example #13
0
class MobileClientWrapper:
    def __init__(self, config):
        self.client = Mobileclient(debug_logging=False)
        login = self.client.login(config.user_name, config.password,
                                  Mobileclient.FROM_MAC_ADDRESS)
        if not login:
            raise ConnectionError(
                'MobileClientWrapper - Login Error Please Check Google Play Username and Password'
            )

    def logout(self):
        self.client.logout()

    def get_all_playlist_content(self):
        """
        :return: list of all Playlist content as dictionaries.
        """
        return self.client.get_all_user_playlist_contents()

    def create_playlist(self, new_playlist_name, description_text,
                        public_bool):
        """
        Creates a Playlist with given information and returns its id

        :param new_playlist_name: name to give new PlayList
        :param description_text: description text of new Playlist
        :param public_bool: True/False value to specify public sharing on new Playlist
        :return: playlist id
        """
        return self.client.create_playlist(new_playlist_name,
                                           description=description_text,
                                           public=public_bool)

    def delete_playlist(self, play_list_id):
        """
        Delete a Playlist with given Id

        :param play_list_id: playlist ID
        """
        self.client.delete_playlist(play_list_id)

    def add_songs_to_playlist(self, play_list_id, song_ids):
        """
        Adds given song(s) to given Playlist.

        :param play_list_id: id of the target Playlist
        :param song_ids: id(s) of the target Song to add
        :return: list of Playlist Entry ids added
        """
        return self.client.add_songs_to_playlist(play_list_id, song_ids)

    def get_track_info(self, store_track_id):
        """
        Returns information on a store track

        :param store_track_id: target TrackId
        """
        return self.client.get_track_info(store_track_id)

    def search(self, search_query):
        """
        Searches based on searchQuery
        :param search_query: query to run through music library

        :return: dictionary of hits
        """
        return self.client.search(search_query, max_results=10)
Example #14
0
class GPMClient(object):
    """
    Google Play Music client.
    """

    all_songs_album_title = "All Songs"
    thumbs_up_playlist_name = "Thumbs Up"

#------------------------------------------------------------------------------

    def __init__(self, email, password, device_id):
        self.__api = Mobileclient()
        self.logged_in = False
        self.__device_id = device_id

        attempts = 0
        while not self.logged_in and attempts < 3:
            self.logged_in = self.__api.login(email, password, device_id)
            attempts += 1

        self.all_tracks = dict()
        self.playlists = dict()
        self.library = dict()

#------------------------------------------------------------------------------

    def logout(self):
        self.__api.logout()

#------------------------------------------------------------------------------

    def update_local_lib(self):
        songs = self.__api.get_all_songs()
        self.playlists[self.thumbs_up_playlist_name] = list()

        # Get main library
        song_map = dict()
        for song in songs:
            if "rating" in song and song["rating"] == "5":
                self.playlists[self.thumbs_up_playlist_name].append(song)

            song_id = song["id"]
            song_artist = song["artist"]
            song_album = song["album"]

            song_map[song_id] = song

            if song_artist == "":
                song_artist = "Unknown Artist"

            if song_album == "":
                song_album = "Unknown Album"

            if song_artist not in self.library:
                self.library[song_artist] = dict()
                self.library[song_artist][self.all_songs_album_title] = list()

            if song_album not in self.library[song_artist]:
                self.library[song_artist][song_album] = list()

            self.library[song_artist][song_album].append(song)
            self.library[song_artist][self.all_songs_album_title].append(song)

        # Sort albums by track number
        for artist in self.library.keys():
            for album in self.library[artist].keys():
                if album == self.all_songs_album_title:
                    sorted_album = sorted(self.library[artist][album],
                                          key=lambda k: k['title'])
                else:
                    sorted_album = sorted(self.library[artist][album],
                                          key=lambda k: k.get('trackNumber', 0))
                self.library[artist][album] = sorted_album

        # Get all playlists
        plists = self.__api.get_all_user_playlist_contents()
        for plist in plists:
            plist_name = plist["name"]
            self.playlists[plist_name] = list()
            for track in plist["tracks"]:
                if track["trackId"] not in song_map:
                    song = song_map[track["trackId"]] = track["track"]
                    song["id"] = track["trackId"]
                else:
                    song = song_map[track["trackId"]]
                self.playlists[plist_name].append(song)

#------------------------------------------------------------------------------

    def get_stream_url(self, song):
        return self.__api.get_stream_url(song["id"], self.__device_id)

#------------------------------------------------------------------------------

    def rate_song(self, song, rating):
        try:
            song["rating"] = rating
            song_list = [song]
            self.__api.change_song_metadata(song_list)
            print "Gave a Thumbs Up to {0} by {1} on Google Play.".format(
                   song["title"].encode("utf-8"),
                   song["artist"].encode("utf-8"))
        except RuntimeError:
            print "Error giving a Thumbs Up on Google Play."
class GoogleMusicDownloader:

    LONG_FILE_DUR = 600000
    FILE_NAME_RE = re.compile(r'[\\/:"*?<>|]+')

    def __init__(self):
        self.client = Mobileclient()
        logged_in = self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS) if path.exists(Mobileclient.OAUTH_FILEPATH) else False

        if not logged_in:
            print('No oauth credentials found, please authenticate your account')
            self.client.perform_oauth(open_browser=True)
            self.client.oauth_login(Mobileclient.FROM_MAC_ADDRESS)
        else:
            print('Logged in!')

    def __ask(self, text):
        inpt = input(text + ' (y/n): ').lower()
        return inpt.startswith('y')

    def __update_metadata(self, path = str(), info = dict()):
        with open(path, 'r+b') as mp3f:
            mp3 = MP3(mp3f)
            id3 = ID3()

            track_num = info.get('trackNumber', 1)
            id3.add(TRCK(encoding=3, text=[track_num if track_num > 0 else 1]))

            id3.add(TIT2(encoding=3, text=info['title']))
            id3.add(TPE1(encoding=3, text=[info.get('artist', None)]))
            id3.add(TCOM(encoding=3, text=[info.get('composer', None)]))
            id3.add(TCON(encoding=3, text=[info.get('genre', None)]))
            id3.add(TAL(encoding=3, text=[info.get('album', None)]))

            year = info.get('year', 0)
            if year > 0:
                id3.add(TYER(encoding=3, text=[year]))
            
            if 'albumArtRef' in info and len(info['albumArtRef']) > 0:
                img_url = info['albumArtRef'][0]['url']
                if img_url:
                    req = requests.get(img_url, allow_redirects=True)
                    id3.add(APIC(encoding=3, mime='image/jpeg', type=3, data=req.content))
                    
            mp3.tags = id3
            mp3.save(mp3f)

    def __kill(self):
        self.client.logout()
        exit()

    def download_all_songs(self):

        print('Loading music library...')
        library = self.client.get_all_songs()
        print(len(library), 'tracks detected.')
        if len(library) == 0:
            self.__kill()

        current_path = path.dirname(path.realpath(__file__))
        include_long_files = self.__ask('Also download long files? (10+ min)')

        if not self.__ask('Begin downloading?'):
            self.__kill()

        long_skipped_count = 0
        errors_count = 0
        successful_count = 0
        song_num = 0

        folder_path = path.join(current_path, 'downloads')
        if not path.exists(folder_path):
            mkdir(folder_path)

        for song in library:
            song_num += 1
            song_id = song['id'] if song['id'] else song['storeId']
            song_name = song['artist'] + ' - ' + song['title']
            mp3_path = path.join(folder_path, self.FILE_NAME_RE.sub(' ', song_name) + '.mp3')
            song_name = '%d. %s' % (song_num, song_name) # song name with index number only for display

            if path.exists(mp3_path):
                print('Track', song_name, 'already exists! Updating metadata...')
                self.__update_metadata(mp3_path, song)
                continue

            if not include_long_files and int(song.get('durationMillis', 0)) >= self.LONG_FILE_DUR:
                long_skipped_count += 1
                continue

            song_url = self.client.get_stream_url(song_id)
            if not song_url:
                print('Warning:', song_name, 'url is empty! Skip...')
                errors_count += 1
                continue

            req = requests.get(song_url, allow_redirects=True, stream=True)
            if not req.ok:
                print(song_name, 'download error!')
                errors_count += 1
                req.raise_for_status()
                continue
            total_size = int(req.headers.get('content-length'))
            with open(mp3_path, 'wb') as mp3f:
                with tqdm(total=total_size, unit='B', unit_scale=True, desc=song_name + '.mp3') as pbar:
                    for chunk in req.iter_content(1024):
                        if chunk:
                            mp3f.write(chunk)
                            mp3f.flush()
                            pbar.update(len(chunk))
                successful_count += 1

            print('Filling metadata for', song_name)
            self.__update_metadata(mp3_path, song)

        status_text = 'Process complete! Downloaded: {downloaded}; '
        if not include_long_files:
            status_text += 'Long files skipped: {long_skipped}; '
        status_text += 'Errors count: {errors}'
        print(status_text.format(downloaded=successful_count, long_skipped=long_skipped_count, errors=errors_count))

        self.client.logout()
Example #16
0
class GoogleMusicLogin():
    def __init__(self):
        import requests
        from requests.packages.urllib3.exceptions import InsecureRequestWarning
        from requests.packages.urllib3.exceptions import InsecurePlatformWarning
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
        self.gmusicapi = Mobileclient(debug_logging=False, validate=False, verify_ssl=False)

    def checkCookie(self):
        # Remove cookie data if it is older then 7 days
        if utils.addon.getSetting('cookie-date') != None and len(utils.addon.getSetting('cookie-date')) > 0:
            import time
            if (datetime.now() - datetime(*time.strptime(utils.addon.getSetting('cookie-date'), '%Y-%m-%d %H:%M:%S.%f')[0:6])).days >= 7:
                self.clearCookie()

    def checkCredentials(self):
        if not utils.addon.getSetting('username'):
            utils.addon.openSettings()
        if utils.addon.getSetting('password') and utils.addon.getSetting('password') != '**encoded**':
            import base64
            utils.addon.setSetting('encpassword',base64.b64encode(utils.addon.getSetting('password')))
            utils.addon.setSetting('password','**encoded**')

    def getApi(self):
        return self.gmusicapi

    def getStreamUrl(self,song_id):
        # retrieve registered device
        device_id = self.getDevice()
        # retrieve stream quality from settings
        quality = { '0':'hi','1':'med','2':'low' } [utils.addon.getSetting('quality')]
        utils.log("getStreamUrl songid: %s device: %s quality: %s"%(song_id, device_id, quality))

        return self.gmusicapi.get_stream_url(song_id, device_id, quality)

    def getDevice(self):
        return utils.addon.getSetting('device_id')

    def initDevice(self):
        device_id = self.getDevice()

        if not device_id:
            utils.log('Trying to fetch the device_id')
            self.login(True)
            try:
                devices = self.gmusicapi.get_registered_devices()
                if len(devices) == 10:
                    utils.log("WARNING: 10 devices already registered!")
                utils.log(repr(devices))
                for device in devices:
                    if device["type"] in ("ANDROID","PHONE","IOS"):
                        device_id = str(device["id"])
                        break
            except:
                pass

            if device_id:
                if device_id.lower().startswith('0x'): device_id = device_id[2:]
                utils.addon.setSetting('device_id', device_id)
                utils.log('Found device_id: '+device_id)
            else:
                #utils.log('No device found, using default.')
                #device_id = "333c60412226c96f"
                raise Exception('No devices found, registered mobile device required!')

    def clearCookie(self):
        utils.addon.setSetting('logged_in-mobile', "")
        utils.addon.setSetting('authtoken-mobile', "")
        utils.addon.setSetting('device_id', "")

    def logout(self):
        self.gmusicapi.logout()

    def login(self, nocache=False):
        if not utils.addon.getSetting('logged_in-mobile') or nocache:
            import base64

            utils.log('Logging in')
            self.checkCredentials()
            username = utils.addon.getSetting('username')
            password = base64.b64decode(utils.addon.getSetting('encpassword'))

            try:
                self.gmusicapi.login(username, password, utils.addon.getSetting('device_id'))
                if not self.gmusicapi.is_authenticated():
                    self.gmusicapi.login(username, password, Mobileclient.FROM_MAC_ADDRESS)
            except Exception as e:
                utils.log(repr(e))

            if not self.gmusicapi.is_authenticated():
                utils.log("Login failed")
                utils.addon.setSetting('logged_in-mobile', "")
                self.language = utils.addon.getLocalizedString
                dialog = xbmcgui.Dialog()
                dialog.ok(self.language(30101), self.language(30102))
                #utils.addon.openSettings()
                raise
            else:
                utils.log("Login succeeded")
                utils.addon.setSetting('logged_in-mobile', "1")
                utils.addon.setSetting('authtoken-mobile', self.gmusicapi.session._authtoken)
                utils.addon.setSetting('cookie-date', str(datetime.now()))
        else:

            utils.log("Loading auth from cache")
            self.gmusicapi.session._authtoken = utils.addon.getSetting('authtoken-mobile')
            self.gmusicapi.session.is_authenticated = True
Example #17
0
'''
Created on Jan 12, 2015

@author: snoecker
'''

if __name__ == '__main__':
    pass
from gmusicapi import Mobileclient, Api
import login

api = Mobileclient()
GAcct=login.readGoogleLogin()

if GAcct:
    logged_in = api.login(GAcct.GUserName, GAcct.GPassword)
#     logged_in is True if login was successful

    if logged_in == True:
        print "logged in"
    
    song_list= api.get_all_songs(True)
    for songs in song_list:
        print "size of list" + str(len(songs))
    logged_out= api.logout()
    print "logged out " + str(logged_out)
    
    
Example #18
0
class Main:
    def __init__(self):
        GPIO.setmode(GPIO.BOARD)

        self.display = LCDDisplay.LCDThread()

        self.display.spool_string_value("GPM-Connecting..", "Please wait     ")

        self.mus = Mobileclient()
        fd = open('Secret.txt', 'r')
        data = fd.read().split("\n")
        fd.close()
        self.mus.login(data[0], data[1], Mobileclient.FROM_MAC_ADDRESS)

        self.Lib = self.mus.get_all_songs()
        self.playlists = self.mus.get_all_user_playlist_contents()

        tplay = self.playlists
        self.playlists = {}

        self.index = 0

        for pl in tplay:
            out = self.append_song_to_playlist(pl, songs=self.Lib)
            self.playlists[out["name"]] = out

        self.lists_w_id = [{'quit': 0}]
        for item in self.playlists.values():
            self.lists_w_id.append({item["name"]: item["id"]})

        self.display.spool_string_value("Ready           ", "Awaiting input  ")
        self.button_set_up()
        self.display.spool_string_value(
            self.lists_w_id[self.index].items()[0][0][:16])

        self.player = MusicPlayer.PlayerThread(self.playlists, self.display,
                                               self, self.Lib)
        self.working = True

        try:
            while self.working:
                time.sleep(10)
        except KeyboardInterrupt:
            pass
        finally:
            self.display.spool_string_value("Stopping...", "Have a nice day")
            self.end_clear()

    def get_songs(self):
        return self.Lib

    def get_core(self):
        return self.mus

    # noinspection PyUnusedLocal
    def previous_button(self, channel):  # button left, pin 21
        self.index -= 1
        if self.index < 0:
            self.index = len(self.lists_w_id) - 1

        self.display.spool_string_value(
            self.lists_w_id[self.index].items()[0][0][:16])

    # noinspection PyUnusedLocal
    def next_button(self, channel):  # button right, pin 2
        self.index += 1
        if self.index >= len(self.lists_w_id):
            self.index = 0

        self.display.spool_string_value(
            self.lists_w_id[self.index].items()[0][0][:16])

    # noinspection PyUnusedLocal
    def accept_button(self, channel):  # button down, pin 24
        if self.lists_w_id[self.index].items()[0][0] == "quit":
            self.working = False
        else:
            self.player.stop_music()
            self.player.set_playlist_to_play(
                self.lists_w_id[self.index].items()[0][1])

    # noinspection PyUnusedLocal
    def random_button(self, channel):  # button up, pin 19
        self.player.stop_music()
        self.player.play_random()

    def button_set_up(self):
        GPIO.setup(19, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)  # up
        GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)  # left
        GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)  # right
        GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)  # down

        GPIO.add_event_detect(19,
                              GPIO.RISING,
                              callback=self.random_button,
                              bouncetime=300)
        GPIO.add_event_detect(21,
                              GPIO.RISING,
                              callback=self.previous_button,
                              bouncetime=300)
        GPIO.add_event_detect(23,
                              GPIO.RISING,
                              callback=self.next_button,
                              bouncetime=300)
        GPIO.add_event_detect(24,
                              GPIO.RISING,
                              callback=self.accept_button,
                              bouncetime=300)

    def end_clear(self):
        self.display.stop()
        self.player.stop()
        self.mus.logout()
        GPIO.cleanup()

    # noinspection PyMethodMayBeStatic
    def append_song_to_playlist(self, playlist, mus_entity=None, songs=None):
        if not songs:
            songs = mus_entity.get_all_songs()
        out_list = []
        for track in playlist['tracks']:
            if track['source'] == 2:
                out_list.append(track)
            else:
                for song in songs:
                    if song['id'] == track['trackId']:
                        out_list.append(song)
        playlist['songs'] = out_list
        return playlist
Example #19
0
class GoogleMusicApi:
    def __init__(self):
        self._api = Mobileclient()

    def login(self, username, password, device_id):
        try:
            return self._api.login(username, password, device_id)
        except AlreadyLoggedIn:
            Logger.debug('API: Already logged in')
            return True

    def relogin(self, username, password, device_id):
        try:
            return self._api.login(username, password, device_id)
        except AlreadyLoggedIn:
            self._api.logout()
            return self._api.login(username, password, device_id)

    def logout(self):
        return self._api.logout()

    def get_registered_mobile_devices(self):
        devices = self._api.get_registered_devices()
        mobile_devices = []
        for device in devices:
            if device['type'] == "ANDROID":  # TODO: Add iOS
                mobile_devices.append({
                    'name': device['friendlyName'],
                    'id': device['id'][2:]
                })
        return mobile_devices

    def get_stream_url(self, track_id, quality):
        return self._api.get_stream_url(song_id=track_id, quality=quality)

    def get_library(self):
        return self._api.get_all_songs()

    def get_album_info(self, album_id):
        return self._api.get_album_info(album_id)

    def search(self, query, max_results=25):
        # TODO: make number of results configurable / add to settings
        try:
            return self._api.search_all_access(query, max_results)
            # TODO: remove when gmusicapi 9.0.1 is stable
        except AttributeError:  # develop version of gmusicapi is installed
            return self._api.search(query, max_results)

    def get_station_tracks(self, title, seed, num_tracks=25, recently_played_ids=None):
        # TODO: make number of results configurable / add to settings
        # TODO: check for existing stations, so we don't always create new ones (maybe not necessary: stations created with same seed have the same id
        seed_type = seed['type']
        seed = seed['seed']
        station_id = ''
        Logger.debug('Station: Creating station (Title: {}, Seed: {}, Type:{}'.format(title, seed, seed_type))
        if seed_type == 'track':
            station_id = self.create_station(title, track_id=seed)
        elif seed_type == 'artist':
            station_id = self.create_station(title, artist_id=seed)
        elif seed_type == 'album':
            station_id = self.create_station(title, album_id=seed)
        elif seed_type == 'genre':
            station_id = self.create_station(title, genre_id=seed)
        elif seed_type == 'curated':
            Logger.debug("Station: CuratedStationId seed, don't know what to do :(")
        else:
            Logger.error("Station: Unknown seed, don't know what to do :(")

        if station_id:
            Logger.debug('Station: ID is ' + station_id)
            station_tracks = self._api.get_station_tracks(station_id, num_tracks, recently_played_ids)
            Logger.debug('Station: Station has {} tracks'.format(len(station_tracks)))
            return station_tracks
        else:
            Logger.warning("Station: Could not retrieve station ID")
            return []

    def get_feeling_lucky_station_tracks(self, num_tracks=25, recently_played_ids=None):
        # TODO: make number of results configurable / add to settings
        return self._api.get_station_tracks('IFL', num_tracks, recently_played_ids)

    def create_station(self, name, track_id=None, artist_id=None, album_id=None, genre_id=None, playlist_token=None):
        return self._api.create_station(name, track_id=track_id, artist_id=artist_id, album_id=album_id,
                                        genre_id=genre_id, playlist_token=playlist_token)

    def increment_track_playcount(self, track_id):
        self._api.increment_song_playcount(track_id)
Example #20
0
class GMusicSession(object):

    def __init__(self):
        super(GMusicSession, self).__init__()
        logger.info('Mopidy uses Google Music')
        self.api = Mobileclient()

    def login(self, username, password, deviceid):
        if self.api.is_authenticated():
            self.api.logout()
        try:
            self.api.login(username, password)
        except CallFailure as error:
            logger.error(u'Failed to login as "%s": %s', username, error)
        if self.api.is_authenticated():
            if deviceid is None:
                self.deviceid = self.get_deviceid(username, password)
            else:
                self.deviceid = deviceid
        else:
            return False

    def logout(self):
        if self.api.is_authenticated():
            return self.api.logout()
        else:
            return True

    def get_all_songs(self):
        if self.api.is_authenticated():
            return self.api.get_all_songs()
        else:
            return {}

    def get_stream_url(self, song_id):
        if self.api.is_authenticated():
            try:
                return self.api.get_stream_url(song_id, self.deviceid)
            except CallFailure as error:
                logger.error(u'Failed to lookup "%s": %s', song_id, error)

    def get_all_playlist_contents(self):
        if self.api.is_authenticated():
            return self.api.get_all_user_playlist_contents()
        else:
            return {}

    def get_shared_playlist_contents(self, shareToken):
        if self.api.is_authenticated():
            return self.api.get_shared_playlist_contents(shareToken)
        else:
            return {}

    def get_all_playlists(self):
        if self.api.is_authenticated():
            return self.api.get_all_playlists()
        else:
            return {}

    def get_deviceid(self, username, password):
        logger.warning(u'No mobile device ID configured. '
                       u'Trying to detect one.')
        webapi = Webclient(validate=False)
        webapi.login(username, password)
        devices = webapi.get_registered_devices()
        deviceid = None
        for device in devices:
            if device['type'] == 'PHONE' and device['id'][0:2] == u'0x':
                # Omit the '0x' prefix
                deviceid = device['id'][2:]
                break
        webapi.logout()
        if deviceid is None:
            logger.error(u'No valid mobile device ID found. '
                         u'Playing songs will not work.')
        else:
            logger.info(u'Using mobile device ID %s', deviceid)
        return deviceid

    def get_track_info(self, store_track_id):
        if self.api.is_authenticated():
            try:
                return self.api.get_track_info(store_track_id)
            except CallFailure as error:
                logger.error(u'Failed to get All Access track info: %s', error)

    def get_album_info(self, albumid, include_tracks=True):
        if self.api.is_authenticated():
            try:
                return self.api.get_album_info(albumid, include_tracks)
            except CallFailure as error:
                logger.error(u'Failed to get All Access album info: %s', error)
Example #21
0
def main():
    global player
    global api

    parser = OptionParser()
    parser.add_option("-p", "--playlist", dest="playlist", help="Playlist (Name or ID)")
    parser.add_option("-t", "--track", dest="track", help="Track (Name or ID)")
    parser.add_option("-l", "--listen", action="store_true", dest="listen", help="Start listening")
    parser.add_option("-c", "--continue", action="store_true", dest="cont", help="Continue playlist after track")
    parser.add_option("-s", "--shuffle", action="store_true", dest="shuffle", help="Randomize playlist")

    (opts, args) = parser.parse_args()

    config = ConfigParser.RawConfigParser()
    directory = os.path.dirname(os.path.realpath(sys.argv[0]))
    config.read(directory + '/.gmusicpy')
    username = config.get('gmusic', 'username')
    password = config.get('gmusic', 'password')

    api = Mobileclient()
    api.login(username, password, Mobileclient.FROM_MAC_ADDRESS)

    if not api.is_authenticated():
        print "Bad username/password"
        return

    id = 0

    try:
        if(opts.playlist):
            playlists = api.get_all_user_playlist_contents()
            playlist = findPlaylist(opts.playlist, playlists)
            
            if(playlist is None):
                print 'Playlist not found'
                return

            if(opts.track):
                item = findTrack(opts.track, playlist)

                if(item is None):
                    print 'Track not found'
                    return

                track = item['track']
                track['trackId'] = item['trackId']
                tracklist.append(track)

            else:
                for item in playlist['tracks']:
                    track = item['track']
                    track['trackId'] = item['trackId']
                    tracklist.append(track)

            if(opts.shuffle):
                shuffle(tracklist)

            if(opts.listen):
                track = tracklist.pop(0)
                printTrack("", track)
                url = api.get_stream_url(track['trackId'])
                player = play(url)
            else:
                for track in tracklist:
                    printTrack(id, track)
                    id = id + 1

        else:
            playlists = api.get_all_playlists()
            for playlist in playlists:
                print str(id) + ' ' + playlist['name']
                id = id + 1

        while(True):
            if player == None:
                break
            if isinstance(player, subprocess.Popen) and player.poll() != None:
                if(len(tracklist) > 0):
                    track = tracklist.pop(0)
                    printTrack("", track)
                    url = api.get_stream_url(track['trackId'])
                    player = play(url)
                else:
                    break;
            sleep(0.2)

    finally:
        if isinstance(player, subprocess.Popen):
            player.terminate()
        api.logout()
Example #22
0
	
print "##### {} of {} Songs Found. #####".format(len(songs), len(rows))

log("### Missing Songs ###")

for row in missing:
	log(decode(row['title']) + " could not be found.")

log("### Fetching Playlists ###")
playlists = api.get_all_user_playlist_contents()
playlist = [p for p in playlists if p['name'] == gmusic_playlist_name]

song_ids = [s['id'] for s in songs]

if len(playlist) == 0: # Playlist not found, create a new one.
	playlist_id = api.create_playlist(u'foobar2000')
else:
	playlist_id = playlist[0]['id']
	# Prevent duplicated songs in existing playlist
	for track in playlist[0]['tracks']:
		if track['trackId'] in song_ids:
			song_ids.remove(track['trackId'])

log("Importing to Playlist ID: " + playlist_id)
api.add_songs_to_playlist(playlist_id, song_ids)

log("### Import Completed ###")

logfile.close()
api.logout()
class tizgmusicproxy(object):
    """A class for accessing a Google Music account to retrieve song URLs.
    """

    all_songs_album_title = "All Songs"
    thumbs_up_playlist_name = "Thumbs Up"

    def __init__(self, email, password, device_id):
        self.__api = Mobileclient()
        self.logged_in = False
        self.__device_id = device_id
        self.queue = list()
        self.queue_index = -1
        self.play_mode = 0
        self.now_playing_song = None

        attempts = 0
        while not self.logged_in and attempts < 3:
            self.logged_in = self.__api.login(email, password)
            attempts += 1

        self.playlists = CaseInsensitiveDict()
        self.library = CaseInsensitiveDict()

    def logout(self):
        self.__api.logout()

    def update_local_lib(self):
        songs = self.__api.get_all_songs()
        self.playlists[self.thumbs_up_playlist_name] = list()

        # Get main library
        song_map = dict()
        for song in songs:
            if "rating" in song and song["rating"] == "5":
                self.playlists[self.thumbs_up_playlist_name].append(song)

            song_id = song["id"]
            song_artist = song["artist"]
            song_album = song["album"]

            song_map[song_id] = song

            if song_artist == "":
                song_artist = "Unknown Artist"

            if song_album == "":
                song_album = "Unknown Album"

            if not (song_artist in self.library):
                self.library[song_artist] = dict()
                self.library[song_artist][self.all_songs_album_title] = list()

            if not (song_album in self.library[song_artist]):
                self.library[song_artist][song_album] = list()

            self.library[song_artist][song_album].append(song)
            self.library[song_artist][self.all_songs_album_title].append(song)

        # Sort albums by track number
        for artist in self.library.keys():
            logging.info("Artist : {0}".format(artist.encode("utf-8")))
            for album in self.library[artist].keys():
                logging.info("   Album : {0}".format(album.encode("utf-8")))
                if album == self.all_songs_album_title:
                    sorted_album = sorted(self.library[artist][album], key=lambda k: k["title"])
                else:
                    sorted_album = sorted(self.library[artist][album], key=lambda k: k.get("trackNumber", 0))
                self.library[artist][album] = sorted_album

        # Get all playlists
        plists = self.__api.get_all_user_playlist_contents()
        for plist in plists:
            plist_name = plist["name"]
            self.playlists[plist_name] = list()
            for track in plist["tracks"]:
                try:
                    song = song_map[track["trackId"]]
                    self.playlists[plist_name].append(song)
                except IndexError:
                    pass

    def current_song_title_and_artist(self):
        logging.info("current_song_title_and_artist")
        song = self.now_playing_song
        if song is not None:
            title = self.now_playing_song["title"]
            artist = self.now_playing_song["artist"]
            logging.info("Now playing {0} by {1}".format(title.encode("utf-8"), artist.encode("utf-8")))
            return artist.encode("utf-8"), title.encode("utf-8")
        else:
            return "", ""

    def current_song_album_and_duration(self):
        logging.info("current_song_album_and_duration")
        song = self.now_playing_song
        if song is not None:
            album = self.now_playing_song["album"]
            duration = self.now_playing_song["durationMillis"]
            logging.info("album {0} duration {1}".format(album.encode("utf-8"), duration.encode("utf-8")))
            return album.encode("utf-8"), int(duration)
        else:
            return "", 0

    def current_song_track_number_and_total_tracks(self):
        logging.info("current_song_track_number_and_total_tracks")
        song = self.now_playing_song
        if song is not None:
            track = self.now_playing_song["trackNumber"]
            total = self.now_playing_song["totalTrackCount"]
            logging.info("track number {0} total tracks {1}".format(track, total))
            return track, total
        else:
            logging.info("current_song_track_number_and_total_tracks : not found")
            return 0, 0

    def clear_queue(self):
        self.queue = list()
        self.queue_index = -1

    def enqueue_artist(self, arg):
        try:
            artist = self.library[arg]
            count = 0
            for album in artist:
                for song in artist[album]:
                    self.queue.append(song)
                    count += 1
            logging.info("Added {0} tracks by {1} to queue".format(count, arg))
        except KeyError:
            logging.info("Cannot find {0}".format(arg))
            raise

    def enqueue_album(self, arg):
        try:
            for artist in self.library:
                for album in self.library[artist]:
                    logging.info("enqueue album : {0} | {1}".format(artist.encode("utf-8"), album.encode("utf-8")))
                    if album.lower() == arg.lower():
                        count = 0
                        for song in self.library[artist][album]:
                            self.queue.append(song)
                            count += 1
                        logging.info(
                            "Added {0} tracks from {1} by "
                            "{2} to queue".format(count, album.encode("utf-8"), artist.encode("utf-8"))
                        )
        except KeyError:
            logging.info("Cannot find {0}".format(arg))
            raise

    def enqueue_playlist(self, arg):
        try:
            playlist = self.playlists[arg]
            count = 0
            for song in playlist:
                self.queue.append(song)
                count += 1
            logging.info("Added {0} tracks from {1} to queue".format(count, arg))
        except KeyError:
            logging.info("Cannot find {0}".format(arg))
            raise

    def next_url(self):
        logging.info("next_url")
        if len(self.queue):
            self.queue_index += 1
            if (self.queue_index < len(self.queue)) and (self.queue_index >= 0):
                next_song = self.queue[self.queue_index]
                return self.__get_song_url(next_song)
            else:
                self.queue_index = -1
                return self.next_url()
        else:
            return ""

    def prev_url(self):
        if len(self.queue):
            self.queue_index -= 1
            if (self.queue_index < len(self.queue)) and (self.queue_index >= 0):
                prev_song = self.queue[self.queue_index]
                return self.__get_song_url(prev_song)
            else:
                self.queue_index = len(self.queue)
                return self.prev_url()
        else:
            return ""

    def __get_song_url(self, song):
        song_url = self.__api.get_stream_url(song["id"], self.__device_id)
        try:
            self.now_playing_song = song
            return song_url
        except AttributeError:
            logging.info("Could not retrieve song url!")
            raise
Example #24
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
def play_song(song):
    if Mobileclient.is_authenticated(gpm):
        mm = Musicmanager()
        mm.login('/home/pi/oauth.cred')

        if Musicmanager.is_authenticated(mm):
            song_dict = mm.get_purchased_songs()
            song_pattern = re.compile(
                r'(?:.)*\s?(' + re.escape(song) + r')\s?(?:.)*', re.IGNORECASE)

            btn = OnButtonPress()
            btn.start()

            for song in song_dict:
                m = re.match(song_pattern, song['title'])
                print(m)

                if re.match(song_pattern, song['title']) is not None:
                    print('Song found!')
                    song_id = song['id']
                    (filename, audio) = mm.download_song(song_id)

                    # get rid of non-ascii characters in file name

                    filename = filename.encode('ascii', errors='ignore')

                    # check if song is already downloaded
                    # path will look something like:
                    # /home/pi/Music/02 - Raindrop Prelude.mp3
                    # forces filename to be a string

                    filename = filename.decode('ascii')
                    path = song_location + filename
                    try:
                        if os.path.isfile(path):
                            print('Song is already downloaded...')
                            print(path)
                            print('Playing song.')

                            vlc_instance = vlc.Instance()

                            p = vlc_instance.media_player_new()
                            media = vlc_instance.media_new(path)

                            p.set_media(media)
                            events = p.event_manager()
                            events.event_attach(
                                vlc.EventType.MediaPlayerEndReached,
                                SongFinished)
                            p.play()
                            p.audio_set_volume(58)

                            while finish == 0:
                                duration = p.get_time() / 1000
                                (m, s) = divmod(duration, 60)

                                print('Current song is: ', path)
                                print('Length:', '%02d:%02d' % (m, s))
                                time.sleep(5)

                            p.stop()
                            break
                        else:
                            with open(path, 'wb') as f:
                                f.write(audio)
                            print('Song has been added to: ' + path)
                            print('Playing song.')

                            vlc_instance = vlc.Instance()

                            p = vlc_instance.media_player_new()
                            media = vlc_instance.media_new(path)

                            p.set_media(media)
                            events = p.event_manager()
                            events.event_attach(
                                vlc.EventType.MediaPlayerEndReached,
                                SongFinished)
                            p.play()
                            p.audio_set_volume(58)

                            while finish == 0:
                                duration = p.get_time() / 1000
                                (m, s) = divmod(duration, 60)

                                print('Current song is: ', path)
                                print('Length:', '%02d:%02d' % (m, s))
                                time.sleep(5)

                            p.stop()
                            break
                    except (OSError, IOError):
                        print('An error has occurred.')
                        break
                else:

                    print('Song not found yet.')
        else:

            print('Looks like you need to authenticate.')
            mm.perform_oauth('/home/pi/oauth.cred')

        print('Logging out.')
        Mobileclient.logout(gpm)
        mm.logout()
    else:
        print('Mobileclient could not authenticate.')
        Mobileclient.logout(gpm)
Example #26
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['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=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 #27
0
class CBMMusicManager():
    # Please have this be an absolute path
    SONG_DIR = "/home/pi/Desktop/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(CBMMusicManager.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)