Example #1
0
class Gopma():
    def __init__(self, action=None):
        print "Initialising GOPMA."
        config = ConfigParser.ConfigParser()
        config.read('config.ini')

        email = config.get('login', 'email')
        password = config.get('login', 'password')
        try:
            auth_token = config.get('login', 'auth_token')
        except:
            auth_token = False
            print "No auth token could be found"

        print "Logging into Google Play Music as", email
        logged_in = False
        bad_auth = False
        while not logged_in:
            if not auth_token or bad_auth:
                self.api = Mobileclient()
                login = self.api.login(email, password,
                                       Mobileclient.FROM_MAC_ADDRESS)
                if not login:
                    print "Login failed, check your credentials."
                    sys.exit()

                # Save the auth token for later
                with open('config.ini', 'w+') as f:
                    config.set('login', 'auth_token',
                               self.api.session._authtoken)
                    config.write(f)
                    f.close()
                    print "Saved auth token for later."

                logged_in = True
            else:
                print "Found an auth token, trying it."
                self.api = Mobileclient()
                self.api.session._authtoken = auth_token
                self.api.session.is_authenticated = True
                try:
                    # Test the auth token
                    self.api.get_registered_devices()
                    logged_in = True
                except:
                    # Failed
                    print "Bad auth token, manually signing in."
                    bad_auth = True
        print "Successfully logged in as", email

        if action != 'reset_genres':
            print "Loading data."
            self.playlists = self.api.get_all_playlists()
            self.content = self.api.get_all_user_playlist_contents()
            self.root_genres, self.child_genres = self.load_genres()
            print "Data successfully loaded."

    def create_or_retrieve_playlists(self, playlists):
        """ Helper function to create or retrieve playlist IDs for a given agg_lists

            Input: List of playlist names
            Output: Dict of playlist names and IDs
        """
        if type(playlists) is not list:
            print "Stop passing non-lists to this function."
            sys.exit()

        agg_lists = [
            p for p in self.content
            if p.get('type') == 'USER_GENERATED' and p.get('name') in playlists
        ]

        # Get all playlist IDs
        agg_playlists = {}
        existing_playlists = [playlist['name'] for playlist in agg_lists]
        for name in playlists:
            if name not in existing_playlists:
                print "Playlist not found, creating", name
                agg_playlists[name] = self.api.create_playlist(name)
                self.api.edit_playlist(agg_playlists[name], public=True)
            else:
                print "Playlist found", name + ", retrieving ID."
                playlist_id = [
                    p['id'] for p in agg_lists if p.get('name') == name
                ][0]
                agg_playlists[name] = playlist_id
                # self.api.edit_playlist(agg_playlists[name], public=True)

        return agg_playlists

    def load_genres(self, reset=False):
        """ Load all genres
        """
        # Get the root genres
        if os.path.isfile(ROOT_GENRE_FILE):
            print "Found a root genres file."
            if reset:
                root_genres = self.api.get_genres()

                with open(ROOT_GENRE_FILE, 'w') as fp:
                    pickle.dump(root_genres, fp)
                print "Root genres have been reset."
            else:
                with open(ROOT_GENRE_FILE) as fp:
                    root_genres = pickle.load(fp)
        else:
            print "Couldn't find a root genres file, retrieving data."
            root_genres = self.api.get_genres()

            with open(ROOT_GENRE_FILE, 'w') as fp:
                pickle.dump(root_genres, fp)

            print "Root genres file created."

        # Get the child genres
        if os.path.isfile(CHILD_GENRE_FILE):
            print "Found a child genres file."
            if reset:
                child_genres = {}

                for genre in root_genres:
                    children = self.api.get_genres(genre['id'])
                    child_names = []
                    for child in children:
                        child_names.append(child['name'])
                    child_genres[genre['id']] = child_names

                with open(CHILD_GENRE_FILE, 'w') as fp:
                    pickle.dump(child_genres, fp)
                print "Child genres have been reset."
            else:
                with open(CHILD_GENRE_FILE) as fp:
                    child_genres = pickle.load(fp)
        else:
            print "Couldn't find a child genres file, retrieving data."
            child_genres = {}

            for genre in root_genres:
                children = self.api.get_genres(genre['id'])
                child_names = []
                for child in children:
                    child_names.append(child['name'])
                child_genres[genre['id']] = child_names

            with open(CHILD_GENRE_FILE, 'w') as fp:
                pickle.dump(child_genres, fp)
            print "Child genres file created."

        return root_genres, child_genres

    def delete_empty_playlists(self):
        """ Delete ALL empty playlists. Be careful with this.
        """
        playlists = self.content
        for playlist in playlists:
            if len(playlist['tracks']
                   ) == 0 and playlist['name'] != AGGREGATE_PLAYLIST_NAME:
                self.api.delete_playlist(playlist['id'])
                print "Deleted", playlist['name']

    def create_playlists(self):
        """ Create all needed playlists
        """
        print "Creating/updating playlists."
        self.create_or_retrieve_playlists(
            [AGGREGATE_PLAYLIST_NAME, SHARED_PLAYLIST_NAME])
        self.create_or_retrieve_playlists(
            [PLAYLIST_PREFIX + genre for genre in GENRE_PLAYLISTS.values()])

    def get_playlist_urls(self):
        """ Get all gopma playlist URLS
        """
        urls = {}
        for playlist in self.playlists:
            if PLAYLIST_PREFIX in playlist['name'] and playlist[
                    'type'] == 'USER_GENERATED':
                urls[playlist[
                    'name']] = "https://play.google.com/music/playlist/" + playlist[
                        'shareToken']
        return urls

    def get_playlist_id(self, name):
        """ Get the playlist ID for a given playlist name
        """
        playlist = [p for p in self.playlists if p.get('name') == name][0]
        return playlist['id']

    def get_share_token(self, playlist_id):
        """ Get the share token for a given playlist ID
        """
        playlist = [p for p in self.playlists if p.get('id') == playlist_id]
        return playlist[0]['shareToken']

    def get_playlist_tracks(self, playlist_id):
        """ Get the tracks for a specified playlist id
        """
        return [p for p in self.content
                if p.get('id') == playlist_id][0]['tracks']

    def get_parent_genre_id(self, genre_name):
        """ Get the parent id for a given genre name
        """
        # Check the root genres first
        for genre in self.root_genres:
            if genre_name == genre['name']:
                return genre['id']

        # Check children genres
        for gid, genres in self.child_genres.items():
            for genre in genres:
                if genre == genre_name:
                    return gid

    def wipe_all_playlists(self):
        """ Wipe all Gopma playlists
        """
        for playlist in self.playlists:
            if PLAYLIST_PREFIX in playlist[
                    'name'] and SHARED_PLAYLIST_NAME not in playlist['name']:
                print "Wiping playlist: ", playlist['name']
                self.wipe_playlist(playlist['id'])

    def wipe_playlist(self, playlist_id):
        """ Wipe a given playlist
        """
        playlist_tracks = self.get_playlist_tracks(playlist_id)
        song_ids = [track['id'] for track in playlist_tracks]
        self.api.remove_entries_from_playlist(song_ids)

    def reset_daily_playlists(self):
        """ Reset the daily playlists
        """
        # Get playlists
        agg_playlists = self.create_or_retrieve_playlists([TODAY, YESTERDAY])
        yest_id = agg_playlists[YESTERDAY]
        today_id = agg_playlists[TODAY]

        # Wipe yesterday
        print "Wiping yesterday's playlist."
        self.wipe_playlist(yest_id)

        # Copy today to yesterday
        print "Copying", TODAY, "to", YESTERDAY
        today_tracks = self.get_playlist_tracks(today_id)
        self.api.add_songs_to_playlist(yest_id,
                                       [t['trackId'] for t in today_tracks])

        # Wipe today
        print "Wiping today's playlist."
        self.wipe_playlist(today_id)

    def update_group_playlist(self):
        """ Update the big group aggregate and the daily playlist with any new shared songs
        """
        # Get the aggregate playlist songs
        agg_token = self.get_share_token(
            self.get_playlist_id(AGGREGATE_PLAYLIST_NAME))
        agg_playlists = [
            p for p in self.playlists if p.get('type') == 'USER_GENERATED'
            and p.get('shareToken') == agg_token
        ]
        agg_id = agg_playlists[0]['id']

        # Get tracks
        agg_tracks = self.api.get_shared_playlist_contents(agg_token)
        agg_tracks_ids = [track['trackId'] for track in agg_tracks]
        print "Updating group playlists."

        # Get the playlists we want to update with
        shared_lists = [
            p for p in self.playlists if p.get('name') == SHARED_PLAYLIST_NAME
        ]

        for playlist in shared_lists:
            shared_tracks = self.api.get_shared_playlist_contents(
                playlist['shareToken'])
            print "\nRetrieving from", playlist[
                'name'], "by", playlist['ownerName'] + ":"

            # Add songs to aggregate playlist
            if len(shared_tracks) == 0:
                print "<< Playlist is empty. >>"
            else:
                no_new = True
                for track in shared_tracks:
                    if track['trackId'] not in agg_tracks_ids:
                        # Add to giant aggregate playlist
                        self.api.add_songs_to_playlist(agg_id,
                                                       track['trackId'])
                        # Add to daily playlist
                        self.api.add_songs_to_playlist(
                            self.get_playlist_id(TODAY), track['trackId'])
                        # Add to genre relevant playlist
                        self.api.add_songs_to_playlist(
                            self.get_playlist_id(
                                PLAYLIST_PREFIX +
                                GENRE_PLAYLISTS[self.get_parent_genre_id(
                                    track['track']['genre'])]),
                            track['trackId'])
                        title = track['track']['title'].encode(
                            'ascii', 'ignore')
                        artist = track['track']['artist'].encode(
                            'ascii', 'ignore')
                        print "+", title, "by", artist, "has been added."
                        no_new = False
                if no_new:
                    print "<< There are no new tracks to be added from this playlist. >>"

        print "Finished updating group playlists."

    def update_songs(self):
        """ Update the database with song information
        """
        # Connect to the database
        config = ConfigParser.ConfigParser()
        config.read('config.ini')

        dbname = config.get('database', 'dbname')
        dbuser = config.get('database', 'user')
        dbhost = config.get('database', 'host')
        dbpass = config.get('database', 'password')

        try:
            conn = psycopg2.connect("dbname=" + dbname + " user="******" host=" + dbhost + " password="******"<< Could not connect to the db. >>"
            print e
            sys.exit()

        # Update songs
        for c in self.content:
            if c.get('type') == 'USER_GENERATED' and c.get(
                    'name') == AGGREGATE_PLAYLIST_NAME:
                songs = c.get('tracks')
                for s in songs:
                    # Song details
                    details = s['track']
                    # Date song was added
                    date_added = datetime.fromtimestamp(
                        int(s.get('creationTimestamp')) / 1000000)
                    # Values to save
                    values = [
                        str(s.get('trackId')),
                        str(details.get('title').encode('UTF-8', 'ignore')),
                        str(details.get('artist').encode('UTF-8', 'ignore')),
                        str(details.get('album').encode('UTF-8', 'ignore')),
                        str(details.get('genre')), date_added
                    ]
                    # SQL query
                    insert_song = "INSERT INTO playlists_song VALUES (%s, %s, %s, %s, %s, %s) ON CONFLICT (tid) DO NOTHING;"
                    # Save to DB
                    self.commit_changes(conn, cur, insert_song, values)

        print "Songs successfully updated."
        # Close connection
        cur.close()
        conn.close()

    def commit_changes(self, conn, cur, query, values):
        try:  # Commit our changes made
            cur.execute(query, values)
            conn.commit()
        except psycopg2.Error as exc:
            print exc
            sys.exit()
Example #2
0
class tizgmusicproxy(object):
    """A class for logging into a Google Play Music account and retrieving song
    URLs.

    """

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

    def __init__(self, email, password, device_id):
        self.__gmusic = Mobileclient()
        self.__email = email
        self.__device_id = device_id
        self.logged_in = False
        self.queue = list()
        self.queue_index = -1
        self.play_queue_order = list()
        self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"])
        self.current_play_mode = self.play_modes.NORMAL
        self.now_playing_song = None

        userdir = os.path.expanduser('~')
        tizconfig = os.path.join(userdir, ".config/tizonia/." + email + ".auth_token")
        auth_token = ""
        if os.path.isfile(tizconfig):
            with open(tizconfig, "r") as f:
                auth_token = pickle.load(f)
                if auth_token:
                    # 'Keep track of the auth token' workaround. See:
                    # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198
                    print_msg("[Google Play Music] [Authenticating] : " \
                              "'with cached auth token'")
                    self.__gmusic.android_id = device_id
                    self.__gmusic.session._authtoken = auth_token
                    self.__gmusic.session.is_authenticated = True
                    try:
                        self.__gmusic.get_registered_devices()
                    except CallFailure:
                        # The token has expired. Reset the client object
                        print_wrn("[Google Play Music] [Authenticating] : " \
                                  "'auth token expired'")
                        self.__gmusic = Mobileclient()
                        auth_token = ""

        if not auth_token:
            attempts = 0
            print_nfo("[Google Play Music] [Authenticating] : " \
                      "'with user credentials'")
            while not self.logged_in and attempts < 3:
                self.logged_in = self.__gmusic.login(email, password, device_id)
                attempts += 1

            with open(tizconfig, "a+") as f:
                f.truncate()
                pickle.dump(self.__gmusic.session._authtoken, f)

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

    def logout(self):
        """ Reset the session to an unauthenticated, default state.

        """
        self.__gmusic.logout()

    def set_play_mode(self, mode):
        """ Set the playback mode.

        :param mode: curren tvalid values are "NORMAL" and "SHUFFLE"

        """
        self.current_play_mode = getattr(self.play_modes, mode)
        self.__update_play_queue_order()

    def current_song_title_and_artist(self):
        """ Retrieve the current track's title and artist name.

        """
        logging.info("current_song_title_and_artist")
        song = self.now_playing_song
        if song:
            title = to_ascii(self.now_playing_song.get('title'))
            artist = to_ascii(self.now_playing_song.get('artist'))
            logging.info("Now playing %s by %s", title, artist)
            return artist, title
        else:
            return '', ''

    def current_song_album_and_duration(self):
        """ Retrieve the current track's album and duration.

        """
        logging.info("current_song_album_and_duration")
        song = self.now_playing_song
        if song:
            album = to_ascii(self.now_playing_song.get('album'))
            duration = to_ascii \
                       (self.now_playing_song.get('durationMillis'))
            logging.info("album %s duration %s", album, duration)
            return album, int(duration)
        else:
            return '', 0

    def current_track_and_album_total(self):
        """Return the current track number and the total number of tracks in the
        album, if known.

        """
        logging.info("current_track_and_album_total")
        song = self.now_playing_song
        track = 0
        total = 0
        if song:
            try:
                track = self.now_playing_song['trackNumber']
                total = self.now_playing_song['totalTrackCount']
                logging.info("track number %s total tracks %s", track, total)
            except KeyError:
                logging.info("trackNumber or totalTrackCount : not found")
        else:
            logging.info("current_song_track_number_"
                         "and_total_tracks : not found")
        return track, total

    def current_song_year(self):
        """ Return the current track's year of publication.

        """
        logging.info("current_song_year")
        song = self.now_playing_song
        year = 0
        if song:
            try:
                year = song['year']
                logging.info("track year %s", year)
            except KeyError:
                logging.info("year : not found")
        else:
            logging.info("current_song_year : not found")
        return year

    def clear_queue(self):
        """ Clears the playback queue.

        """
        self.queue = list()
        self.queue_index = -1

    def enqueue_artist(self, arg):
        """ Search the user's library for tracks from the given artist and adds
        them to the playback queue.

        :param arg: an artist
        """
        try:
            self.__update_local_library()
            artist = None
            if arg not in self.library.keys():
                for name, art in self.library.iteritems():
                    if arg.lower() in name.lower():
                        artist = art
                        print_wrn("[Google Play Music] '{0}' not found. " \
                                  "Playing '{1}' instead." \
                                  .format(arg.encode('utf-8'), \
                                          name.encode('utf-8')))
                        break
                if not artist:
                    # Play some random artist from the library
                    random.seed()
                    artist = random.choice(self.library.keys())
                    artist = self.library[artist]
                    print_wrn("[Google Play Music] '{0}' not found. "\
                              "Feeling lucky?." \
                              .format(arg.encode('utf-8')))
            else:
                artist = self.library[arg]
            tracks_added = 0
            for album in artist:
                tracks_added += self.__enqueue_tracks(artist[album])
            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(artist)))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Artist not found : {0}".format(arg))

    def enqueue_album(self, arg):
        """ Search the user's library for albums with a given name and adds
        them to the playback queue.

        """
        try:
            self.__update_local_library()
            album = None
            artist = None
            tentative_album = None
            tentative_artist = None
            for library_artist in self.library:
                for artist_album in self.library[library_artist]:
                    print_nfo("[Google Play Music] [Album] '{0}'." \
                              .format(to_ascii(artist_album)))
                    if not album:
                        if arg.lower() == artist_album.lower():
                            album = artist_album
                            artist = library_artist
                            break
                    if not tentative_album:
                        if arg.lower() in artist_album.lower():
                            tentative_album = artist_album
                            tentative_artist = library_artist
                if album:
                    break

            if not album and tentative_album:
                album = tentative_album
                artist = tentative_artist
                print_wrn("[Google Play Music] '{0}' not found. " \
                          "Playing '{1}' instead." \
                          .format(arg.encode('utf-8'), \
                          album.encode('utf-8')))
            if not album:
                # Play some random album from the library
                random.seed()
                artist = random.choice(self.library.keys())
                album = random.choice(self.library[artist].keys())
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))

            if not album:
                raise KeyError("Album not found : {0}".format(arg))

            self.__enqueue_tracks(self.library[artist][album])
            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(album)))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Album not found : {0}".format(arg))

    def enqueue_playlist(self, arg):
        """Search the user's library for playlists with a given name
        and adds the tracks of the first match to the playback queue.

        Requires Unlimited subscription.

        """
        try:
            self.__update_local_library()
            self.__update_playlists()
            self.__update_playlists_unlimited()
            playlist = None
            playlist_name = None
            for name, plist in self.playlists.items():
                print_nfo("[Google Play Music] [Playlist] '{0}'." \
                          .format(to_ascii(name)))
            if arg not in self.playlists.keys():
                for name, plist in self.playlists.iteritems():
                    if arg.lower() in name.lower():
                        playlist = plist
                        playlist_name = name
                        print_wrn("[Google Play Music] '{0}' not found. " \
                                  "Playing '{1}' instead." \
                                  .format(arg.encode('utf-8'), \
                                          to_ascii(name)))
                        break
                if not playlist:
                    # Play some random playlist from the library
                    random.seed()
                    playlist_name = random.choice(self.playlists.keys())
                    playlist = self.playlists[playlist_name]
                    print_wrn("[Google Play Music] '{0}' not found. "\
                              "Feeling lucky?." \
                              .format(arg.encode('utf-8')))
            else:
                playlist_name = arg
                playlist = self.playlists[arg]

            self.__enqueue_tracks(playlist)
            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(playlist_name)))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Playlist not found : {0}".format(arg))

    def enqueue_station_unlimited(self, arg):
        """Search the user's library for a station with a given name
        and add its tracks to the playback queue.

        Requires Unlimited subscription.

        """
        try:
            # First try to find a suitable station in the user's library
            self.__enqueue_user_station_unlimited(arg)

            if not len(self.queue):
                # If no suitable station is found in the user's library, then
                # search google play unlimited for a potential match.
                self.__enqueue_station_unlimited(arg)

            if not len(self.queue):
                raise KeyError

        except KeyError:
            raise KeyError("Station not found : {0}".format(arg))

    def enqueue_genre_unlimited(self, arg):
        """Search Unlimited for a genre with a given name and add its
        tracks to the playback queue.

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \
                  .format(self.__email))

        try:
            all_genres = list()
            root_genres = self.__gmusic.get_genres()
            second_tier_genres = list()
            for root_genre in root_genres:
                second_tier_genres += self.__gmusic.get_genres(root_genre['id'])
            all_genres += root_genres
            all_genres += second_tier_genres
            for genre in all_genres:
                print_nfo("[Google Play Music] [Genre] '{0}'." \
                          .format(to_ascii(genre['name'])))
            genre = dict()
            if arg not in all_genres:
                genre = next((g for g in all_genres \
                              if arg.lower() in to_ascii(g['name']).lower()), \
                             None)

            tracks_added = 0
            while not tracks_added:
                if not genre and len(all_genres):
                    # Play some random genre from the search results
                    random.seed()
                    genre = random.choice(all_genres)
                    print_wrn("[Google Play Music] '{0}' not found. "\
                              "Feeling lucky?." \
                              .format(arg.encode('utf-8')))

                genre_name = genre['name']
                genre_id = genre['id']
                station_id = self.__gmusic.create_station(genre_name, \
                                                          None, None, None, genre_id)
                num_tracks = 200
                tracks = self.__gmusic.get_station_tracks(station_id, num_tracks)
                tracks_added = self.__enqueue_tracks(tracks)
                logging.info("Added %d tracks from %s to queue", tracks_added, genre_name)
                if not tracks_added:
                    # This will produce another iteration in the loop
                    genre = None

            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(genre['name'])))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Genre not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_situation_unlimited(self, arg):
        """Search Unlimited for a situation with a given name and add its
        tracks to the playback queue.

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \
                  .format(self.__email))

        try:

            self.__enqueue_situation_unlimited(arg)

            if not len(self.queue):
                raise KeyError

            logging.info("Added %d tracks from %s to queue", \
                         len(self.queue), arg)

        except KeyError:
            raise KeyError("Situation not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_artist_unlimited(self, arg):
        """Search Unlimited for an artist and adds the artist's 200 top tracks to the
        playback queue.

        Requires Unlimited subscription.

        """
        try:
            artist = self.__gmusic_search(arg, 'artist')

            include_albums = False
            max_top_tracks = 200
            max_rel_artist = 0
            artist_tracks = dict()
            if artist:
                artist_tracks = self.__gmusic.get_artist_info \
                                (artist['artist']['artistId'],
                                 include_albums, max_top_tracks,
                                 max_rel_artist)['topTracks']
            if not artist_tracks:
                raise KeyError

            tracks_added = self.__enqueue_tracks(artist_tracks)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Artist not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_album_unlimited(self, arg):
        """Search Unlimited for an album and add its tracks to the
        playback queue.

        Requires Unlimited subscription.

        """
        try:
            album = self.__gmusic_search(arg, 'album')
            album_tracks = dict()
            if album:
                album_tracks = self.__gmusic.get_album_info \
                               (album['album']['albumId'])['tracks']
            if not album_tracks:
                raise KeyError

            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format((album['album']['name']).encode('utf-8')))

            tracks_added = self.__enqueue_tracks(album_tracks)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Album not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_tracks_unlimited(self, arg):
        """ Search Unlimited for a track name and adds all the matching tracks
        to the playback queue.

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \
                  .format(self.__email))

        try:
            max_results = 200
            track_hits = self.__gmusic.search(arg, max_results)['song_hits']
            if not len(track_hits):
                # Do another search with an empty string
                track_hits = self.__gmusic.search("", max_results)['song_hits']
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))

            tracks = list()
            for hit in track_hits:
                tracks.append(hit['track'])
            tracks_added = self.__enqueue_tracks(tracks)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Playlist not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_promoted_tracks_unlimited(self):
        """ Retrieve the url of the next track in the playback queue.

        """
        try:
            tracks = self.__gmusic.get_promoted_songs()
            count = 0
            for track in tracks:
                store_track = self.__gmusic.get_track_info(track['storeId'])
                if u'id' not in store_track.keys():
                    store_track[u'id'] = store_track['nid']
                self.queue.append(store_track)
                count += 1
            if count == 0:
                print_wrn("[Google Play Music] Operation requires " \
                          "an Unlimited subscription.")
            logging.info("Added %d Unlimited promoted tracks to queue", \
                         count)
            self.__update_play_queue_order()
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def next_url(self):
        """ Retrieve the url of the next track in the playback queue.

        """
        if len(self.queue):
            self.queue_index += 1
            if (self.queue_index < len(self.queue)) \
               and (self.queue_index >= 0):
                next_song = self.queue[self.play_queue_order[self.queue_index]]
                return self.__retrieve_track_url(next_song)
            else:
                self.queue_index = -1
                return self.next_url()
        else:
            return ''

    def prev_url(self):
        """ Retrieve the url of the previous track in the playback queue.

        """
        if len(self.queue):
            self.queue_index -= 1
            if (self.queue_index < len(self.queue)) \
               and (self.queue_index >= 0):
                prev_song = self.queue[self.play_queue_order[self.queue_index]]
                return self.__retrieve_track_url(prev_song)
            else:
                self.queue_index = len(self.queue)
                return self.prev_url()
        else:
            return ''

    def __update_play_queue_order(self):
        """ Update the queue playback order.

        A sequential order is applied if the current play mode is "NORMAL" or a
        random order if current play mode is "SHUFFLE"

        """
        total_tracks = len(self.queue)
        if total_tracks:
            if not len(self.play_queue_order):
                # Create a sequential play order, if empty
                self.play_queue_order = range(total_tracks)
            if self.current_play_mode == self.play_modes.SHUFFLE:
                random.shuffle(self.play_queue_order)
            print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \
                      .format(total_tracks))

    def __retrieve_track_url(self, song):
        """ Retrieve a song url

        """
        song_url = self.__gmusic.get_stream_url(song['id'], self.__device_id)
        try:
            self.now_playing_song = song
            return song_url
        except AttributeError:
            logging.info("Could not retrieve the song url!")
            raise

    def __update_local_library(self):
        """ Retrieve the songs and albums from the user's library

        """
        print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \
                  .format(self.__email))

        songs = self.__gmusic.get_all_songs()
        self.playlists[self.thumbs_up_playlist_name] = list()

        # Retrieve the user's song library
        for song in songs:
            if "rating" in song and song['rating'] == "5":
                self.playlists[self.thumbs_up_playlist_name].append(song)

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

            self.song_map[song_id] = song

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

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

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

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

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

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

    def __update_stations_unlimited(self):
        """ Retrieve stations (Unlimited)

        """
        self.stations.clear()
        stations = self.__gmusic.get_all_stations()
        self.stations[u"I'm Feeling Lucky"] = 'IFL'
        for station in stations:
            station_name = station['name']
            logging.info("station name : %s", to_ascii(station_name))
            self.stations[station_name] = station['id']

    def __enqueue_user_station_unlimited(self, arg):
        """ Enqueue a user station (Unlimited)

        """
        print_msg("[Google Play Music] [Station search "\
                  "in user's library] : '{0}'. " \
                  .format(self.__email))
        self.__update_stations_unlimited()
        station_name = arg
        station_id = None
        for name, st_id in self.stations.iteritems():
            print_nfo("[Google Play Music] [Station] '{0}'." \
                      .format(to_ascii(name)))
        if arg not in self.stations.keys():
            for name, st_id in self.stations.iteritems():
                if arg.lower() in name.lower():
                    station_id = st_id
                    station_name = name
                    break
        else:
            station_id = self.stations[arg]

        num_tracks = 200
        tracks = list()
        if station_id:
            try:
                tracks = self.__gmusic.get_station_tracks(station_id, \
                                                          num_tracks)
            except KeyError:
                raise RuntimeError("Operation requires an "
                                   "Unlimited subscription.")
            tracks_added = self.__enqueue_tracks(tracks)
            if tracks_added:
                if arg != station_name:
                    print_wrn("[Google Play Music] '{0}' not found. " \
                              "Playing '{1}' instead." \
                              .format(arg.encode('utf-8'), name.encode('utf-8')))
                logging.info("Added %d tracks from %s to queue", tracks_added, arg)
                self.__update_play_queue_order()
            else:
                print_wrn("[Google Play Music] '{0}' has no tracks. " \
                          .format(station_name))

        if not len(self.queue):
            print_wrn("[Google Play Music] '{0}' " \
                      "not found in the user's library. " \
                      .format(arg.encode('utf-8')))

    def __enqueue_station_unlimited(self, arg, max_results=200, quiet=False):
        """Search for a station and enqueue all of its tracks (Unlimited)

        """
        if not quiet:
            print_msg("[Google Play Music] [Station search in "\
                      "Google Play Music] : '{0}'. " \
                      .format(arg.encode('utf-8')))
        try:
            station_name = arg
            station_id = None
            station = self.__gmusic_search(arg, 'station', max_results, quiet)

            if station:
                station = station['station']
                station_name = station['name']
                seed = station['seed']
                seed_type = seed['seedType']
                track_id = seed['trackId'] if seed_type == u'2' else None
                artist_id = seed['artistId'] if seed_type == u'3' else None
                album_id = seed['albumId'] if seed_type == u'4' else None
                genre_id = seed['genreId'] if seed_type == u'5' else None
                playlist_token = seed['playlistShareToken'] if seed_type == u'8' else None
                curated_station_id = seed['curatedStationId'] if seed_type == u'9' else None
                num_tracks = max_results
                tracks = list()
                try:
                    station_id \
                        = self.__gmusic.create_station(station_name, \
                                                       track_id, \
                                                       artist_id, \
                                                       album_id, \
                                                       genre_id, \
                                                       playlist_token, \
                                                       curated_station_id)
                    tracks \
                        = self.__gmusic.get_station_tracks(station_id, \
                                                           num_tracks)
                except KeyError:
                    raise RuntimeError("Operation requires an "
                                       "Unlimited subscription.")
                tracks_added = self.__enqueue_tracks(tracks)
                if tracks_added:
                    if not quiet:
                        print_wrn("[Google Play Music] [Station] : '{0}'." \
                                  .format(station_name.encode('utf-8')))
                    logging.info("Added %d tracks from %s to queue", \
                                 tracks_added, arg.encode('utf-8'))
                    self.__update_play_queue_order()

        except KeyError:
            raise KeyError("Station not found : {0}".format(arg))

    def __enqueue_situation_unlimited(self, arg):
        """Search for a situation and enqueue all of its tracks (Unlimited)

        """
        print_msg("[Google Play Music] [Situation search in "\
                  "Google Play Music] : '{0}'. " \
                  .format(arg.encode('utf-8')))
        try:
            situation_hits = self.__gmusic.search(arg)['situation_hits']

            if not len(situation_hits):
                # Do another search with an empty string
                situation_hits = self.__gmusic.search("")['situation_hits']
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))

            situation = next((hit for hit in situation_hits \
                              if 'best_result' in hit.keys()), None)

            num_tracks = 200
            if not situation and len(situation_hits):
                max_results = num_tracks / len(situation_hits)
                for hit in situation_hits:
                    situation = hit['situation']
                    print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \
                              .format((hit['situation']['title']).encode('utf-8'),
                                      (hit['situation']['description']).encode('utf-8')))

                    self.__enqueue_station_unlimited(situation['title'], max_results, True)

            if not situation:
                raise KeyError

        except KeyError:
            raise KeyError("Situation not found : {0}".format(arg))

    def __enqueue_tracks(self, tracks):
        """ Add tracks to the playback queue

        """
        count = 0
        for track in tracks:
            if u'id' not in track.keys():
                track[u'id'] = track['nid']
            self.queue.append(track)
            count += 1
        return count

    def __update_playlists(self):
        """ Retrieve the user's playlists

        """
        plists = self.__gmusic.get_all_user_playlist_contents()
        for plist in plists:
            plist_name = plist['name']
            logging.info("playlist name : %s", to_ascii(plist_name))
            tracks = plist['tracks']
            tracks.sort(key=itemgetter('creationTimestamp'))
            self.playlists[plist_name] = list()
            for track in tracks:
                try:
                    song = self.song_map[track['trackId']]
                    self.playlists[plist_name].append(song)
                except IndexError:
                    pass

    def __update_playlists_unlimited(self):
        """ Retrieve shared playlists (Unlimited)

        """
        plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \
                                if p.get('type') == 'SHARED']
        for plist in plists_subscribed_to:
            share_tok = plist['shareToken']
            playlist_items \
                = self.__gmusic.get_shared_playlist_contents(share_tok)
            plist_name = plist['name']
            logging.info("shared playlist name : %s", to_ascii(plist_name))
            self.playlists[plist_name] = list()
            for item in playlist_items:
                try:
                    song = item['track']
                    song['id'] = item['trackId']
                    self.playlists[plist_name].append(song)
                except IndexError:
                    pass

    def __gmusic_search(self, query, query_type, max_results=200, quiet=False):
        """ Search Google Play (Unlimited)

        """

        search_results = self.__gmusic.search(query, max_results)[query_type + '_hits']
        result = next((hit for hit in search_results \
                            if 'best_result' in hit.keys()), None)

        if not result and len(search_results):
            secondary_hit = None
            for hit in search_results:
                if not quiet:
                    print_nfo("[Google Play Music] [{0}] '{1}'." \
                              .format(query_type.capitalize(),
                                      (hit[query_type]['name']).encode('utf-8')))
                if query.lower() == \
                   to_ascii(hit[query_type]['name']).lower():
                    result = hit
                    break
                if query.lower() in \
                   to_ascii(hit[query_type]['name']).lower():
                    secondary_hit = hit
            if not result and secondary_hit:
                result = secondary_hit

        if not result and not len(search_results):
            # Do another search with an empty string
            search_results = self.__gmusic.search("")[query_type + '_hits']

        if not result and len(search_results):
            # Play some random result from the search results
            random.seed()
            result = random.choice(search_results)
            if not quiet:
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(query.encode('utf-8')))

        return result
Example #3
0
api = Mobileclient(debug_logging = False)

if not args.username:
    args.username = input('Username: '******'Password:'******'Logging in uwer %s.', args.username)
if not api.login(args.username, args.password, ''.join([choice('1234567890abcdef') for x in xrange(16)]) if args.random_id else api.FROM_MAC_ADDRESS):
    quit('Login failed.')

m = Menu('Select source to download')
m.add_entry('Library', api.get_all_songs)
for p in api.get_all_playlists():
    m.add_entry('%s playlist' % p.get('name', 'Untitled'), lambda token = p['shareToken']: [x['track'] for x in api.get_shared_playlist_contents(token)])
res = m.get_selection()
if res:
    logging.debug('Getting source contents.')
    program_start = time()
    logging.info('Started the download at %s.', ctime(program_start))
    total = 0.0 # The average download time.
    try:
        results = res()
        for i, r in enumerate(results):
            logging.debug('Result = %s', r)
            artist = valid_filename(r.get('artist', 'Unknown Artist'))
            album = valid_filename(r.get('album', 'Unknown Album'))
            number = r.get('trackNumber', 0)
            if number < 10:
                number = '0%s' % number
Example #4
0
class GMusic(object):
    def __init__(self):
        self.authenticated = False
        self.all_access = False
        self.library_loaded = False
        self.all_songs = []
        self.letters = {}
        self.artists = {}
        self.albums = {}
        self.genres = {}
        self.tracks_by_letter = {}
        self.tracks_by_artist = {}
        self.tracks_by_album = {}
        self.tracks_by_genre = {}
        self._device = None
        self._webclient = Webclient(debug_logging=False)
        self._mobileclient = Mobileclient(debug_logging=False)
        self._playlists = []
        self._playlist_contents = []
        self._stations = []

    def _get_device_id(self):
        if self.authenticated:
            devices = self._webclient.get_registered_devices()
            for dev in devices:
                if dev['type'] == 'PHONE':
                    self._device = dev['id'][2:]
                    break
                elif dev['type'] == 'IOS':
                    self._device = dev['id']
                    break

    def _set_all_access(self):
        settings = self._webclient._make_call(webclient.GetSettings, '')
        self.all_access = True if 'isSubscription' in settings[
            'settings'] and settings['settings'][
                'isSubscription'] == True else False

    def _set_all_songs(self):
        if len(self.all_songs) == 0:
            try:
                self.all_songs = self._mobileclient.get_all_songs()
            except NotLoggedIn:
                if self.authenticate():
                    self.all_songs = self._mobileclient.get_all_songs()
                else:
                    return []

        else:
            return self.all_songs

    def authenticate(self, email, password):
        try:
            mcauthenticated = self._mobileclient.login(email, password)
        except AlreadyLoggedIn:
            mcauthenticated = True

        try:
            wcauthenticated = self._webclient.login(email, password)
        except AlreadyLoggedIn:
            wcauthenticated = True

        self.authenticated = mcauthenticated and wcauthenticated
        self._set_all_access()
        self._get_device_id()
        return self.authenticated

    def load_data(self):
        self._set_all_songs()
        for song in self.all_songs:
            thumb = None
            letter = song['title'][0]
            artist = song['artist']
            album = song['album']
            genre = song['genre'] if 'genre' in song else '(None)'

            if letter not in self.tracks_by_letter:
                self.tracks_by_letter[letter] = []
                self.letters[letter] = None

            if artist not in self.tracks_by_artist:
                self.tracks_by_artist[artist] = []
                self.artists[artist] = None

            if album not in self.tracks_by_album:
                self.tracks_by_album[album] = []
                self.albums[album] = None

            if genre not in self.tracks_by_genre:
                self.tracks_by_genre[genre] = []
                self.genres[genre] = None

            track = {'artist': artist, 'album': album}

            if 'title' in song:
                track['title'] = song['title']

            if 'album' in song:
                track['album'] = song['album']

            if 'artist' in song:
                track['artist'] = song['artist']

            if 'durationMillis' in song:
                track['durationMillis'] = song['durationMillis']

            if 'id' in song:
                track['id'] = song['id']

            if 'trackNumber' in song:
                track['trackType'] = song['trackNumber']

            if 'storeId' in song:
                track['storeId'] = song['storeId']

            if 'albumArtRef' in song:
                track['albumArtRef'] = song['albumArtRef']
                thumb = song['albumArtRef'][0]['url']
                self.letters[letter] = thumb
                self.artists[artist] = thumb
                self.albums[album] = thumb
                self.genres[genre] = thumb

            self.tracks_by_letter[letter].append({
                'track': track,
                'thumb': thumb,
                'id': song['id']
            })
            self.tracks_by_artist[artist].append({
                'track': track,
                'thumb': thumb,
                'id': song['id']
            })
            self.tracks_by_album[album].append({
                'track': track,
                'thumb': thumb,
                'id': song['id']
            })
            self.tracks_by_genre[genre].append({
                'track': track,
                'thumb': thumb,
                'id': song['id']
            })

        self.library_loaded = True

    def get_tracks_for_type(self, type, name):
        type = type.lower()
        if type == 'artists':
            return self.tracks_by_artist[name]
        elif type == 'albums':
            return self.tracks_by_album[name]
        elif type == 'genres':
            return self.tracks_by_genre[name]
        elif type == 'songs by letter':
            return self.tracks_by_letter[name]
        else:
            return {}

    def get_song(self, id):
        return [x for x in self.all_songs if x['id'] == id][0]

    def get_all_playlists(self):
        if len(self._playlists) == 0:
            try:
                self._playlists = self._mobileclient.get_all_playlists()
            except NotLoggedIn:
                if self.authenticate():
                    self._playlists = self._mobileclient.get_all_playlists()
                else:
                    return []

        return self._playlists

    def get_all_user_playlist_contents(self, id):
        tracks = []
        if len(self._playlist_contents) == 0:
            try:
                self._playlist_contents = self._mobileclient.get_all_user_playlist_contents(
                )
            except NotLoggedIn:
                if self.authenticate():
                    self._playlist_contents = self._mobileclient.get_all_user_playlist_contents(
                    )
                else:
                    return []

        for playlist in self._playlist_contents:
            if id == playlist['id']:
                tracks = playlist['tracks']
                break

        return tracks

    def get_shared_playlist_contents(self, token):
        playlist = []
        try:
            playlist = self._mobileclient.get_shared_playlist_contents(token)
        except NotLoggedIn:
            if self.authenticate():
                playlist = self._mobileclient.get_shared_playlist_contents(
                    token)
            else:
                return []

        return playlist

    def get_all_stations(self):
        if len(self._stations) == 0:
            try:
                self._stations = self._mobileclient.get_all_stations()
            except NotLoggedIn:
                if self.authenticate():
                    self._stations = self._mobileclient.get_all_stations()
                else:
                    return []

        return self._stations

    def get_station_tracks(self, id, num_tracks=200):
        tracks = []
        try:
            tracks = self._mobileclient.get_station_tracks(id, num_tracks)
        except NotLoggedIn:
            if self.authenticate():
                tracks = self._mobileclient.get_station_tracks(id, num_tracks)
            else:
                return []

        return tracks

    def get_genres(self):
        genres = []
        try:
            genres = self._mobileclient.get_genres()
        except NotLoggedIn:
            if self.authenticate():
                genres = self._mobileclient.get_genres()
            else:
                return []

        return genres

    def create_station(self, name, id):
        station = None
        try:
            station = self._mobileclient.create_station(name=name, genre_id=id)
        except NotLoggedIn:
            if self.authenticate():
                station = self._mobileclient.create_station(name=name,
                                                            genre_id=id)
            else:
                return []

        return station

    def search_all_access(self, query, max_results=50):
        results = None
        try:
            results = self._mobileclient.search_all_access(query, max_results)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.search_all_access(
                    query, max_results)
            else:
                return []

        return results

    def get_artist_info(self,
                        id,
                        include_albums=True,
                        max_top_tracks=5,
                        max_rel_artist=5):
        results = None
        try:
            results = self._mobileclient.get_artist_info(
                id,
                include_albums=include_albums,
                max_top_tracks=max_top_tracks,
                max_rel_artist=max_rel_artist)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.get_artist_info(
                    id,
                    include_albums=include_albums,
                    max_top_tracks=max_top_tracks,
                    max_rel_artist=max_rel_artist)
            else:
                return []

        return results

    def get_album_info(self, id, include_tracks=True):
        results = None
        try:
            results = self._mobileclient.get_album_info(
                id, include_tracks=include_tracks)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.get_album_info(
                    id, include_tracks=include_tracks)
            else:
                return []

        return results

    def add_aa_track(self, id):
        track = None
        try:
            track = self._mobileclient.add_aa_track(id)
        except NotLoggedIn:
            if self.authenticate():
                track = self._mobileclient.add_aa_track(id)
            else:
                return None

        return track

    def add_songs_to_playlist(self, playlist_id, song_ids):
        tracks = None
        try:
            tracks = self._mobileclient.add_songs_to_playlist(
                playlist_id, song_ids)
        except NotLoggedIn:
            if self.authenticate():
                tracks = self._mobileclient.add_songs_to_playlist(
                    playlist_id, song_ids)
            else:
                return None

        return tracks

    def get_stream_url(self, id):
        try:
            stream_url = self._mobileclient.get_stream_url(id, self._device)
        except NotLoggedIn:
            if self.authenticate():
                stream_url = self._mobileclient.get_stream_url(
                    id, self._device)
            else:
                return ''
        except CallFailure:
            raise CallFailure('Could not play song with id: ' + id,
                              'get_stream_url')

        return stream_url
Example #5
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_user_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 #6
0
class GMusic(object):
    def __init__(self):
        self.authenticated = False
        self.all_access = False
        self._device = None
        self._webclient = Webclient(debug_logging=False)
        self._mobileclient = Mobileclient(debug_logging=False)
        self._playlists = []
        self._playlist_contents = []
        self._all_songs = []
        self._all_artists = {}
        self._all_albums = {}
        self._all_genres = {}
        self._stations = []

    def _get_device_id(self):
        if self.authenticated:
            devices = self._webclient.get_registered_devices()
            for dev in devices:
                if dev['type'] == 'PHONE':
                    self._device = dev['id'][2:]
                    break

    def _set_all_access(self):
        settings = self._webclient._make_call(webclient.GetSettings, '')
        self.all_access = True if 'isSubscription' in settings['settings'] and settings['settings']['isSubscription'] == True else False

    def authenticate(self, email, password):
        try:
            mcauthenticated = self._mobileclient.login(email, password)
        except AlreadyLoggedIn:
            mcauthenticated = True

        try:
            wcauthenticated = self._webclient.login(email, password)
        except AlreadyLoggedIn:
            wcauthenticated = True

        self.authenticated = mcauthenticated and wcauthenticated
        self._get_device_id()
        self._set_all_access()
        return self.authenticated

    def get_all_songs(self, id=None):
        if len(self._all_songs) == 0:
            try:
                self._all_songs = self._mobileclient.get_all_songs()
            except NotLoggedIn:
                if self.authenticate():
                    self._all_songs = self._mobileclient.get_all_songs()
                else:
                    return []

        if id:
            return [x for x in self._all_songs if x['id'] == id][0]
        else:
            return self._all_songs

    def get_all_artists(self):
        if not self._all_artists:
            songs = self.get_all_songs()
            for song in songs:
                artist = song['artist']
                thumb = None
                if artist not in self._all_artists:
                    self._all_artists[artist] = []

                track = {'title': song['title'],
                        'album': song['album'],
                        'artist': artist,
                        'durationMillis': song['durationMillis'],
                        'trackType': song['trackNumber'],
                        'id': song['id']}

                if 'albumArtRef' in song:
                    track['albumArtRef'] = song['albumArtRef']

                if 'artistArtRef' in song:
                    thumb = song['artistArtRef'][0]['url']

                if 'storeId' in song:
                    track['storeId'] = song['storeId']

                self._all_artists[artist].append({'track': track, 'thumb': thumb, 'id': song['id']})

        return self._all_artists

    def get_all_albums(self):
        if not self._all_albums:
            songs = self.get_all_songs()
            for song in songs:
                album = song['album']
                thumb = None
                if album not in self._all_albums:
                    self._all_albums[album] = []

                track = {'title': song['title'],
                        'album': album,
                        'artist': song['artist'],
                        'durationMillis': song['durationMillis'],
                        'trackType': song['trackNumber'],
                        'id': song['id']}

                if 'albumArtRef' in song:
                    track['albumArtRef'] = song['albumArtRef']
                    thumb = song['albumArtRef'][0]['url']

                if 'storeId' in song:
                    track['storeId'] = song['storeId']

                self._all_albums[album].append({'track': track, 'thumb': thumb, 'id': song['id']})

        return self._all_albums

    def get_all_genres(self):
        if not self._all_genres:
            songs = self.get_all_songs()
            for song in songs:
                genre = song['genre']
                if genre not in self._all_genres:
                    self._all_genres[genre] = []

                track = {'title': song['title'],
                        'album': song['album'],
                        'artist': song['artist'],
                        'durationMillis': song['durationMillis'],
                        'trackType': song['trackNumber'],
                        'id': song['id']}

                if 'albumArtRef' in song:
                    track['albumArtRef'] = song['albumArtRef']

                if 'storeId' in song:
                    track['storeId'] = song['storeId']

                self._all_genres[genre].append({'track': track, 'id': song['id']})

        return self._all_genres

    def get_all_playlists(self):
        if len(self._playlists) == 0:
            try:
                self._playlists = self._mobileclient.get_all_playlists()
            except NotLoggedIn:
                if self.authenticate():
                    self._playlists = self._mobileclient.get_all_playlists()
                else:
                    return []

        return self._playlists

    def get_all_user_playlist_contents(self, id):
        tracks = []
        if len(self._playlist_contents) == 0:
            try:
                self._playlist_contents = self._mobileclient.get_all_user_playlist_contents()
            except NotLoggedIn:
                if self.authenticate():
                    self._playlist_contents = self._mobileclient.get_all_user_playlist_contents()
                else:
                    return []

        for playlist in self._playlist_contents:
            if id == playlist['id']:
                tracks = playlist['tracks']
                break

        return tracks

    def get_shared_playlist_contents(self, token):
        playlist = []
        try:
            playlist = self._mobileclient.get_shared_playlist_contents(token)
        except NotLoggedIn:
            if self.authenticate():
                playlist = self._mobileclient.get_shared_playlist_contents(token)
            else:
                return []

        return playlist

    def get_all_stations(self):
        if len(self._stations) == 0:
            try:
                self._stations = self._mobileclient.get_all_stations()
            except NotLoggedIn:
                if self.authenticate():
                    self._stations = self._mobileclient.get_all_stations()
                else:
                    return []

        return self._stations

    def get_station_tracks(self, id, num_tracks=200):
        tracks = []
        try:
            tracks = self._mobileclient.get_station_tracks(id, num_tracks)
        except NotLoggedIn:
            if self.authenticate():
                tracks = self._mobileclient.get_station_tracks(id, num_tracks)
            else:
                return []

        return tracks

    def get_genres(self):
        genres = []
        try:
            genres = self._mobileclient.get_genres()
        except NotLoggedIn:
            if self.authenticate():
                genres = self._mobileclient.get_genres()
            else:
                return []

        return genres

    def create_station(self, name, id):
        station = None
        try:
            station = self._mobileclient.create_station(name=name, genre_id=id)
        except NotLoggedIn:
            if self.authenticate():
                station = self._mobileclient.create_station(name=name, genre_id=id)
            else:
                return []

        return station

    def search_all_access(self, query, max_results=50):
        results = None
        try:
            results = self._mobileclient.search_all_access(query, max_results)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.search_all_access(query, max_results)
            else:
                return []

        return results

    def get_artist_info(self, id, include_albums=True, max_top_tracks=5, max_rel_artist=5):
        results = None
        try:
            results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist)
            else:
                return []

        return results

    def get_album_info(self, id, include_tracks=True):
        results = None
        try:
            results = self._mobileclient.get_album_info(id, include_tracks=include_tracks)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.get_album_info(id, include_tracks=include_tracks)
            else:
                return []

        return results

    def get_stream_url(self, id):
        try:
            stream_url = self._mobileclient.get_stream_url(id, self._device)
        except NotLoggedIn:
            if self.authenticate():
                stream_url = self._mobileclient.get_stream_url(id, self._device)
            else:
                return ''
        except CallFailure:
            raise CallFailure('Could not play song with id: ' + id, 'get_stream_url')

        return stream_url
Example #7
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 #8
0
class GMusicClient(ContentConsumer):
    """Element in charge of interfacing with GMusicApi Client"""

    def __init__(self, data_cache):
        self.client = Mobileclient()
        self.data_cache = data_cache

    def login(self):
        """Use data/unlocked/credentials.json to log in"""
        mac = Mobileclient.FROM_MAC_ADDRESS
        try:
            credentials = json.load(open("data/unlocked/credentials.json", "r"))
            result = self.client.login(credentials["username"], credentials["password"], mac)
            if result == False:
                print("\n\033[93mLogin failed.")
                print("Please double check that data/unlocked/credentials.json has correct information.\033[m\n")
                os._exit(1)
        except:
            print("\n\033[93mdata/unlocked/credentials.json is not valid.")
            print("You may need to delete and regnerate the credentials file.\033[m\n")
            exit(1)

    def load_my_library(self):
        """Load user's songs, playlists, and stations"""
        track_load = threading.Thread(target=self.load_tracks)
        radio_load = threading.Thread(target=self.load_radios)
        playlist_load = threading.Thread(target=self.load_playlists)

        track_load.start()
        radio_load.start()
        playlist_load.start()

        track_load.join()
        radio_load.join()
        playlist_load.join()

    def load_playlists(self):
        playlists = self.client.get_all_user_playlist_contents()
        playlists.reverse()
        self.data_cache.playlists = playlists

    def load_tracks(self):
        self.data_cache.tracks = [t for t in self.client.get_all_songs() if "nid" in t]

    def scrape_song(self, track):
        return track

    def load_radios(self):
        radios = self.client.get_all_stations()
        radios.reverse()
        self.data_cache.radios = radios

    def get_radio_contents(self, radio_id):
        tracks = self.client.get_station_tracks(radio_id)
        return tracks

    def get_radio_list(self, name):
        return [r for r in self.data_cache.radios if name in r["name"]]

    def filter(self, element, field, filter_by):
        return [e for e in element if filter_by in e[field]]

    def get_playlist_list(self, name):
        return self.filter(self.data_cache.playlists, "name", name)

    def search_all_access(self, query):
        return self.client.search_all_access(query)

    def create_radio(self, seed_type, id, name):
        """Create a radio"""
        # This is a weird way to do this, but there's no other choice
        ids = {"track": None, "album": None, "artist": None}
        seed_id_name = self.get_type_name(seed_type)
        ids[seed_id_name] = id
        return self.client.create_station(
            name=name, track_id=ids["track"], album_id=ids["album"], artist_id=ids["artist"]
        )

    def search_items_all_access(self, type, query):
        """Searches Albums, Artists, and Songs; uses metaprogramming"""
        index_arguments = self.get_index_arguments(type)

        items = self.search_all_access(query)["{0}_hits".format(type[:-1])]
        return [self.format_item(item, type, index_arguments) for item in items]

    def get_sub_items(self, type_from, search_type, from_id):
        """Here type_from refers to artist or album we're indexing against"""
        args = self.get_index_arguments(search_type)

        if type_from == "playlist":
            return self.get_playlist_contents(from_id)

        else:
            # Get the appropriate search method and execute it
            search_method_name = "get_{0}_info".format(type_from)
            search_method = getattr(self.client, search_method_name)
            try:
                items = search_method(from_id, True)[args["type"] + "s"]  # True here includes subelements
            except:
                # User passed in a bad id or something
                return

        # Now return appropriately
        return [self.format_subitems(t, args) for t in items if args["id"] in t]

    def get_playlist_contents(self, from_id):
        """Playlist exclusive stuff"""
        shareToken = [t for t in self.data_cache.playlists if t["id"] == from_id][0]["shareToken"]

        contents = self.client.get_shared_playlist_contents(shareToken)
        return [self.format_playlist_contents(t) for t in contents if "track" in t]

    def get_suggested(self):
        """Returns a list of tracks that the user might be interested in"""
        items = sorted(self.client.get_promoted_songs(), key=lambda y: y["title"])
        return [self.format_suggested(t) for t in items if "storeId" in t]

    def get_information_about(self, from_type, from_id):
        """Gets specific information about an id (depending on the type)"""
        if "artist" in from_type.lower():
            return self.client.get_artist_info(from_id, include_albums=False)
        if "album" in from_type.lower():
            return self.client.get_album_info(from_id, include_tracks=False)
        return self.client.get_track_info(from_id)

    def get_stream_url(self, nid):
        return self.client.get_stream_url(nid)

    def lookup(self, nid):
        return self.client.get_track_info(nid)

    def add_track_to_library(self, nid):
        self.client.add_aa_track(nid)

    def add_to_playlist(self, playlist_id, nid):
        self.client.add_songs_to_playlist(playlist_id, nid)

        playlist_load = threading.Thread(target=self.load_playlists)
        playlist_load.daemon = True
        playlist_load.start()

    def format_suggested(self, t):
        return (t["title"], t["storeId"], "Play", t["album"])

    def format_playlist_contents(self, t):
        return (t["track"]["title"], t["trackId"], "Play", t["track"]["album"])

    def format_subitems(self, t, args):
        return (t[args["name"]], t[args["id"]], args["command"], t[args["alt"]])
Example #9
0
class tizgmusicproxy(object):
    """A class for logging into a Google Play Music account and retrieving song
    URLs.

    """

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

    # pylint: disable=too-many-instance-attributes,too-many-public-methods
    def __init__(self, email, password, device_id):
        self.__gmusic = Mobileclient()
        self.__email = email
        self.__device_id = device_id
        self.logged_in = False
        self.queue = list()
        self.queue_index = -1
        self.play_queue_order = list()
        self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"])
        self.current_play_mode = self.play_modes.NORMAL
        self.now_playing_song = None

        userdir = os.path.expanduser('~')
        tizconfig = os.path.join(userdir,
                                 ".config/tizonia/." + email + ".auth_token")
        auth_token = ""
        if os.path.isfile(tizconfig):
            with open(tizconfig, "r") as f:
                auth_token = pickle.load(f)
                if auth_token:
                    # 'Keep track of the auth token' workaround. See:
                    # https://github.com/diraimondo/gmusicproxy/issues/34#issuecomment-147359198
                    print_msg("[Google Play Music] [Authenticating] : " \
                              "'with cached auth token'")
                    self.__gmusic.android_id = device_id
                    self.__gmusic.session._authtoken = auth_token
                    self.__gmusic.session.is_authenticated = True
                    try:
                        self.__gmusic.get_registered_devices()
                    except CallFailure:
                        # The token has expired. Reset the client object
                        print_wrn("[Google Play Music] [Authenticating] : " \
                                  "'auth token expired'")
                        self.__gmusic = Mobileclient()
                        auth_token = ""

        if not auth_token:
            attempts = 0
            print_nfo("[Google Play Music] [Authenticating] : " \
                      "'with user credentials'")
            while not self.logged_in and attempts < 3:
                self.logged_in = self.__gmusic.login(email, password,
                                                     device_id)
                attempts += 1

            with open(tizconfig, "a+") as f:
                f.truncate()
                pickle.dump(self.__gmusic.session._authtoken, f)

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

    def logout(self):
        """ Reset the session to an unauthenticated, default state.

        """
        self.__gmusic.logout()

    def set_play_mode(self, mode):
        """ Set the playback mode.

        :param mode: curren tvalid values are "NORMAL" and "SHUFFLE"

        """
        self.current_play_mode = getattr(self.play_modes, mode)
        self.__update_play_queue_order()

    def current_song_title_and_artist(self):
        """ Retrieve the current track's title and artist name.

        """
        logging.info("current_song_title_and_artist")
        song = self.now_playing_song
        if song:
            title = to_ascii(self.now_playing_song.get('title'))
            artist = to_ascii(self.now_playing_song.get('artist'))
            logging.info("Now playing %s by %s", title, artist)
            return artist, title
        else:
            return '', ''

    def current_song_album_and_duration(self):
        """ Retrieve the current track's album and duration.

        """
        logging.info("current_song_album_and_duration")
        song = self.now_playing_song
        if song:
            album = to_ascii(self.now_playing_song.get('album'))
            duration = to_ascii \
                       (self.now_playing_song.get('durationMillis'))
            logging.info("album %s duration %s", album, duration)
            return album, int(duration)
        else:
            return '', 0

    def current_track_and_album_total(self):
        """Return the current track number and the total number of tracks in the
        album, if known.

        """
        logging.info("current_track_and_album_total")
        song = self.now_playing_song
        track = 0
        total = 0
        if song:
            try:
                track = self.now_playing_song['trackNumber']
                total = self.now_playing_song['totalTrackCount']
                logging.info("track number %s total tracks %s", track, total)
            except KeyError:
                logging.info("trackNumber or totalTrackCount : not found")
        else:
            logging.info("current_song_track_number_"
                         "and_total_tracks : not found")
        return track, total

    def current_song_year(self):
        """ Return the current track's year of publication.

        """
        logging.info("current_song_year")
        song = self.now_playing_song
        year = 0
        if song:
            try:
                year = song['year']
                logging.info("track year %s", year)
            except KeyError:
                logging.info("year : not found")
        else:
            logging.info("current_song_year : not found")
        return year

    def clear_queue(self):
        """ Clears the playback queue.

        """
        self.queue = list()
        self.queue_index = -1

    def enqueue_tracks(self, arg):
        """ Search the user's library for tracks and add
        them to the playback queue.

        :param arg: a track search term
        """
        try:
            songs = self.__gmusic.get_all_songs()

            track_hits = list()
            for song in songs:
                song_title = song['title']
                if arg.lower() in song_title.lower():
                    track_hits.append(song)
                    print_nfo("[Google Play Music] [Track] '{0}'." \
                              .format(to_ascii(song_title)))

            if not len(track_hits):
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))
                random.seed()
                track_hits = random.sample(songs, MAX_TRACKS)
                for hit in track_hits:
                    song_title = hit['title']
                    print_nfo("[Google Play Music] [Track] '{0}'." \
                              .format(to_ascii(song_title)))

            if not len(track_hits):
                raise KeyError

            tracks_added = self.__enqueue_tracks(track_hits)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)

            self.__update_play_queue_order()

        except KeyError:
            raise KeyError("Track not found : {0}".format(arg))

    def enqueue_artist(self, arg):
        """ Search the user's library for tracks from the given artist and add
        them to the playback queue.

        :param arg: an artist
        """
        try:
            self.__update_local_library()
            artist = None
            artist_dict = None
            if arg not in self.library.keys():
                for name, art in self.library.iteritems():
                    if arg.lower() in name.lower():
                        artist = name
                        artist_dict = art
                        if arg.lower() != name.lower():
                            print_wrn("[Google Play Music] '{0}' not found. " \
                                      "Playing '{1}' instead." \
                                      .format(arg.encode('utf-8'), \
                                              name.encode('utf-8')))
                        break
                if not artist:
                    # Play some random artist from the library
                    random.seed()
                    artist = random.choice(self.library.keys())
                    artist_dict = self.library[artist]
                    print_wrn("[Google Play Music] '{0}' not found. "\
                              "Feeling lucky?." \
                              .format(arg.encode('utf-8')))
            else:
                artist = arg
                artist_dict = self.library[arg]
            tracks_added = 0
            for album in artist_dict:
                tracks_added += self.__enqueue_tracks(artist_dict[album])
            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(artist)))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Artist not found : {0}".format(arg))

    def enqueue_album(self, arg):
        """ Search the user's library for albums with a given name and add
        them to the playback queue.

        """
        try:
            self.__update_local_library()
            album = None
            artist = None
            tentative_album = None
            tentative_artist = None
            for library_artist in self.library:
                for artist_album in self.library[library_artist]:
                    print_nfo("[Google Play Music] [Album] '{0}'." \
                              .format(to_ascii(artist_album)))
                    if not album:
                        if arg.lower() == artist_album.lower():
                            album = artist_album
                            artist = library_artist
                            break
                    if not tentative_album:
                        if arg.lower() in artist_album.lower():
                            tentative_album = artist_album
                            tentative_artist = library_artist
                if album:
                    break

            if not album and tentative_album:
                album = tentative_album
                artist = tentative_artist
                print_wrn("[Google Play Music] '{0}' not found. " \
                          "Playing '{1}' instead." \
                          .format(arg.encode('utf-8'), \
                          album.encode('utf-8')))
            if not album:
                # Play some random album from the library
                random.seed()
                artist = random.choice(self.library.keys())
                album = random.choice(self.library[artist].keys())
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))

            if not album:
                raise KeyError("Album not found : {0}".format(arg))

            self.__enqueue_tracks(self.library[artist][album])
            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(album)))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Album not found : {0}".format(arg))

    def enqueue_playlist(self, arg):
        """Search the user's library for playlists with a given name
        and add the tracks of the first match to the playback queue.

        Requires Unlimited subscription.

        """
        try:
            self.__update_local_library()
            self.__update_playlists()
            self.__update_playlists_unlimited()
            playlist = None
            playlist_name = None
            for name, plist in self.playlists.items():
                print_nfo("[Google Play Music] [Playlist] '{0}'." \
                          .format(to_ascii(name)))
            if arg not in self.playlists.keys():
                for name, plist in self.playlists.iteritems():
                    if arg.lower() in name.lower():
                        playlist = plist
                        playlist_name = name
                        if arg.lower() != name.lower():
                            print_wrn("[Google Play Music] '{0}' not found. " \
                                      "Playing '{1}' instead." \
                                      .format(arg.encode('utf-8'), \
                                              to_ascii(name)))
                            break
            else:
                playlist_name = arg
                playlist = self.playlists[arg]

            random.seed()
            x = 0
            while (not playlist or not len(playlist)) and x < 3:
                x += 1
                # Play some random playlist from the library
                playlist_name = random.choice(self.playlists.keys())
                playlist = self.playlists[playlist_name]
                print_wrn("[Google Play Music] '{0}' not found or found empty. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))

            if not len(playlist):
                raise KeyError

            self.__enqueue_tracks(playlist)
            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(playlist_name)))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError(
                "Playlist not found or found empty : {0}".format(arg))

    def enqueue_podcast(self, arg):
        """Search Google Play Music for a podcast series and add its tracks to the
        playback queue ().

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving podcasts] : '{0}'. " \
                  .format(self.__email))

        try:

            self.__enqueue_podcast(arg)

            if not len(self.queue):
                raise KeyError

            logging.info("Added %d episodes from '%s' to queue", \
                         len(self.queue), arg)
            self.__update_play_queue_order()

        except KeyError:
            raise KeyError("Podcast not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_station_unlimited(self, arg):
        """Search the user's library for a station with a given name
        and add its tracks to the playback queue.

        Requires Unlimited subscription.

        """
        try:
            # First try to find a suitable station in the user's library
            self.__enqueue_user_station_unlimited(arg)

            if not len(self.queue):
                # If no suitable station is found in the user's library, then
                # search google play unlimited for a potential match.
                self.__enqueue_station_unlimited(arg)

            if not len(self.queue):
                raise KeyError

        except KeyError:
            raise KeyError("Station not found : {0}".format(arg))

    def enqueue_genre_unlimited(self, arg):
        """Search Unlimited for a genre with a given name and add its
        tracks to the playback queue.

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving genres] : '{0}'. " \
                  .format(self.__email))

        try:
            all_genres = list()
            root_genres = self.__gmusic.get_genres()
            second_tier_genres = list()
            for root_genre in root_genres:
                second_tier_genres += self.__gmusic.get_genres(
                    root_genre['id'])
            all_genres += root_genres
            all_genres += second_tier_genres
            for genre in all_genres:
                print_nfo("[Google Play Music] [Genre] '{0}'." \
                          .format(to_ascii(genre['name'])))
            genre = dict()
            if arg not in all_genres:
                genre = next((g for g in all_genres \
                              if arg.lower() in to_ascii(g['name']).lower()), \
                             None)

            tracks_added = 0
            while not tracks_added:
                if not genre and len(all_genres):
                    # Play some random genre from the search results
                    random.seed()
                    genre = random.choice(all_genres)
                    print_wrn("[Google Play Music] '{0}' not found. "\
                              "Feeling lucky?." \
                              .format(arg.encode('utf-8')))

                genre_name = genre['name']
                genre_id = genre['id']
                station_id = self.__gmusic.create_station(genre_name, \
                                                          None, None, None, genre_id)
                num_tracks = MAX_TRACKS
                tracks = self.__gmusic.get_station_tracks(
                    station_id, num_tracks)
                tracks_added = self.__enqueue_tracks(tracks)
                logging.info("Added %d tracks from %s to queue", tracks_added,
                             genre_name)
                if not tracks_added:
                    # This will produce another iteration in the loop
                    genre = None

            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format(to_ascii(genre['name'])))
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Genre not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_situation_unlimited(self, arg):
        """Search Unlimited for a situation with a given name and add its
        tracks to the playback queue.

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving situations] : '{0}'. " \
                  .format(self.__email))

        try:

            self.__enqueue_situation_unlimited(arg)

            if not len(self.queue):
                raise KeyError

            logging.info("Added %d tracks from %s to queue", \
                         len(self.queue), arg)

        except KeyError:
            raise KeyError("Situation not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_artist_unlimited(self, arg):
        """Search Unlimited for an artist and add the artist's 200 top tracks to the
        playback queue.

        Requires Unlimited subscription.

        """
        try:
            artist = self.__gmusic_search(arg, 'artist')

            include_albums = False
            max_top_tracks = MAX_TRACKS
            max_rel_artist = 0
            artist_tracks = dict()
            if artist:
                artist_tracks = self.__gmusic.get_artist_info \
                                (artist['artist']['artistId'],
                                 include_albums, max_top_tracks,
                                 max_rel_artist)['topTracks']

            if not artist_tracks:
                raise KeyError

            for track in artist_tracks:
                song_title = track['title']
                print_nfo("[Google Play Music] [Track] '{0}'." \
                          .format(to_ascii(song_title)))

            tracks_added = self.__enqueue_tracks(artist_tracks)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Artist not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_album_unlimited(self, arg):
        """Search Unlimited for an album and add its tracks to the
        playback queue.

        Requires Unlimited subscription.

        """
        try:
            album = self.__gmusic_search(arg, 'album')
            album_tracks = dict()
            if album:
                album_tracks = self.__gmusic.get_album_info \
                               (album['album']['albumId'])['tracks']
            if not album_tracks:
                raise KeyError

            print_wrn("[Google Play Music] Playing '{0}'." \
                      .format((album['album']['name']).encode('utf-8')))

            tracks_added = self.__enqueue_tracks(album_tracks)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Album not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_tracks_unlimited(self, arg):
        """ Search Unlimited for a track name and add all the matching tracks
        to the playback queue.

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \
                  .format(self.__email))

        try:
            max_results = MAX_TRACKS
            track_hits = self.__gmusic.search(arg, max_results)['song_hits']
            if not len(track_hits):
                # Do another search with an empty string
                track_hits = self.__gmusic.search("", max_results)['song_hits']
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))

            tracks = list()
            for hit in track_hits:
                tracks.append(hit['track'])
            tracks_added = self.__enqueue_tracks(tracks)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)
            self.__update_play_queue_order()
        except KeyError:
            raise KeyError("Playlist not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_playlist_unlimited(self, arg):
        """Search Unlimited for a playlist name and add all its tracks to the
        playback queue.

        Requires Unlimited subscription.

        """
        print_msg("[Google Play Music] [Retrieving playlists] : '{0}'. " \
                  .format(self.__email))

        try:
            playlist_tracks = list()

            playlist_hits = self.__gmusic_search(arg, 'playlist')
            if playlist_hits:
                playlist = playlist_hits['playlist']
                playlist_contents = self.__gmusic.get_shared_playlist_contents(
                    playlist['shareToken'])
            else:
                raise KeyError

            print_nfo("[Google Play Music] [Playlist] '{}'." \
                      .format(playlist['name']).encode('utf-8'))

            for item in playlist_contents:
                print_nfo("[Google Play Music] [Playlist Track] '{} by {} (Album: {}, {})'." \
                          .format((item['track']['title']).encode('utf-8'),
                                  (item['track']['artist']).encode('utf-8'),
                                  (item['track']['album']).encode('utf-8'),
                                  (item['track']['year'])))
                track = item['track']
                playlist_tracks.append(track)

            if not playlist_tracks:
                raise KeyError

            tracks_added = self.__enqueue_tracks(playlist_tracks)
            logging.info("Added %d tracks from %s to queue", \
                         tracks_added, arg)
            self.__update_play_queue_order()

        except KeyError:
            raise KeyError("Playlist not found : {0}".format(arg))
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def enqueue_promoted_tracks_unlimited(self):
        """ Retrieve the url of the next track in the playback queue.

        """
        try:
            tracks = self.__gmusic.get_promoted_songs()
            count = 0
            for track in tracks:
                store_track = self.__gmusic.get_track_info(track['storeId'])
                if u'id' not in store_track.keys():
                    store_track[u'id'] = store_track['storeId']
                self.queue.append(store_track)
                count += 1
            if count == 0:
                print_wrn("[Google Play Music] Operation requires " \
                          "an Unlimited subscription.")
            logging.info("Added %d Unlimited promoted tracks to queue", \
                         count)
            self.__update_play_queue_order()
        except CallFailure:
            raise RuntimeError("Operation requires an Unlimited subscription.")

    def next_url(self):
        """ Retrieve the url of the next track in the playback queue.

        """
        if len(self.queue):
            self.queue_index += 1
            if (self.queue_index < len(self.queue)) \
               and (self.queue_index >= 0):
                next_song = self.queue[self.play_queue_order[self.queue_index]]
                return self.__retrieve_track_url(next_song)
            else:
                self.queue_index = -1
                return self.next_url()
        else:
            return ''

    def prev_url(self):
        """ Retrieve the url of the previous track in the playback queue.

        """
        if len(self.queue):
            self.queue_index -= 1
            if (self.queue_index < len(self.queue)) \
               and (self.queue_index >= 0):
                prev_song = self.queue[self.play_queue_order[self.queue_index]]
                return self.__retrieve_track_url(prev_song)
            else:
                self.queue_index = len(self.queue)
                return self.prev_url()
        else:
            return ''

    def __update_play_queue_order(self):
        """ Update the queue playback order.

        A sequential order is applied if the current play mode is "NORMAL" or a
        random order if current play mode is "SHUFFLE"

        """
        total_tracks = len(self.queue)
        if total_tracks:
            if not len(self.play_queue_order):
                # Create a sequential play order, if empty
                self.play_queue_order = range(total_tracks)
            if self.current_play_mode == self.play_modes.SHUFFLE:
                random.shuffle(self.play_queue_order)
            print_nfo("[Google Play Music] [Tracks in queue] '{0}'." \
                      .format(total_tracks))

    def __retrieve_track_url(self, song):
        """ Retrieve a song url

        """
        if song.get('episodeId'):
            song_url = self.__gmusic.get_podcast_episode_stream_url(
                song['episodeId'], self.__device_id)
        else:
            song_url = self.__gmusic.get_stream_url(song['id'],
                                                    self.__device_id)

        try:
            self.now_playing_song = song
            return song_url
        except AttributeError:
            logging.info("Could not retrieve the song url!")
            raise

    def __update_local_library(self):
        """ Retrieve the songs and albums from the user's library

        """
        print_msg("[Google Play Music] [Retrieving library] : '{0}'. " \
                  .format(self.__email))

        songs = self.__gmusic.get_all_songs()
        self.playlists[self.thumbs_up_playlist_name] = list()

        # Retrieve the user's song library
        for song in songs:
            if "rating" in song and song['rating'] == "5":
                self.playlists[self.thumbs_up_playlist_name].append(song)

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

            self.song_map[song_id] = song

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

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

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

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

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

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

    def __update_stations_unlimited(self):
        """ Retrieve stations (Unlimited)

        """
        self.stations.clear()
        stations = self.__gmusic.get_all_stations()
        self.stations[u"I'm Feeling Lucky"] = 'IFL'
        for station in stations:
            station_name = station['name']
            logging.info("station name : %s", to_ascii(station_name))
            self.stations[station_name] = station['id']

    def __enqueue_user_station_unlimited(self, arg):
        """ Enqueue a user station (Unlimited)

        """
        print_msg("[Google Play Music] [Station search "\
                  "in user's library] : '{0}'. " \
                  .format(self.__email))
        self.__update_stations_unlimited()
        station_name = arg
        station_id = None
        for name, st_id in self.stations.iteritems():
            print_nfo("[Google Play Music] [Station] '{0}'." \
                      .format(to_ascii(name)))
        if arg not in self.stations.keys():
            for name, st_id in self.stations.iteritems():
                if arg.lower() in name.lower():
                    station_id = st_id
                    station_name = name
                    break
        else:
            station_id = self.stations[arg]

        num_tracks = MAX_TRACKS
        tracks = list()
        if station_id:
            try:
                tracks = self.__gmusic.get_station_tracks(station_id, \
                                                          num_tracks)
            except KeyError:
                raise RuntimeError("Operation requires an "
                                   "Unlimited subscription.")
            tracks_added = self.__enqueue_tracks(tracks)
            if tracks_added:
                if arg.lower() != station_name.lower():
                    print_wrn("[Google Play Music] '{0}' not found. " \
                              "Playing '{1}' instead." \
                              .format(arg.encode('utf-8'), name.encode('utf-8')))
                logging.info("Added %d tracks from %s to queue", tracks_added,
                             arg)
                self.__update_play_queue_order()
            else:
                print_wrn("[Google Play Music] '{0}' has no tracks. " \
                          .format(station_name))

        if not len(self.queue):
            print_wrn("[Google Play Music] '{0}' " \
                      "not found in the user's library. " \
                      .format(arg.encode('utf-8')))

    def __enqueue_station_unlimited(self,
                                    arg,
                                    max_results=MAX_TRACKS,
                                    quiet=False):
        """Search for a station and enqueue all of its tracks (Unlimited)

        """
        if not quiet:
            print_msg("[Google Play Music] [Station search in "\
                      "Google Play Music] : '{0}'. " \
                      .format(arg.encode('utf-8')))
        try:
            station_name = arg
            station_id = None
            station = self.__gmusic_search(arg, 'station', max_results, quiet)

            if station:
                station = station['station']
                station_name = station['name']
                seed = station['seed']
                seed_type = seed['seedType']
                track_id = seed['trackId'] if seed_type == u'2' else None
                artist_id = seed['artistId'] if seed_type == u'3' else None
                album_id = seed['albumId'] if seed_type == u'4' else None
                genre_id = seed['genreId'] if seed_type == u'5' else None
                playlist_token = seed[
                    'playlistShareToken'] if seed_type == u'8' else None
                curated_station_id = seed[
                    'curatedStationId'] if seed_type == u'9' else None
                num_tracks = max_results
                tracks = list()
                try:
                    station_id \
                        = self.__gmusic.create_station(station_name, \
                                                       track_id, \
                                                       artist_id, \
                                                       album_id, \
                                                       genre_id, \
                                                       playlist_token, \
                                                       curated_station_id)
                    tracks \
                        = self.__gmusic.get_station_tracks(station_id, \
                                                           num_tracks)
                except KeyError:
                    raise RuntimeError("Operation requires an "
                                       "Unlimited subscription.")
                tracks_added = self.__enqueue_tracks(tracks)
                if tracks_added:
                    if not quiet:
                        print_wrn("[Google Play Music] [Station] : '{0}'." \
                                  .format(station_name.encode('utf-8')))
                    logging.info("Added %d tracks from %s to queue", \
                                 tracks_added, arg.encode('utf-8'))
                    self.__update_play_queue_order()

        except KeyError:
            raise KeyError("Station not found : {0}".format(arg))

    def __enqueue_situation_unlimited(self, arg):
        """Search for a situation and enqueue all of its tracks (Unlimited)

        """
        print_msg("[Google Play Music] [Situation search in "\
                  "Google Play Music] : '{0}'. " \
                  .format(arg.encode('utf-8')))
        try:
            situation_hits = self.__gmusic.search(arg)['situation_hits']

            # If the search didn't return results, just do another search with
            # an empty string
            if not len(situation_hits):
                situation_hits = self.__gmusic.search("")['situation_hits']
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(arg.encode('utf-8')))

            # Try to find a "best result", if one exists
            situation = next((hit for hit in situation_hits \
                              if 'best_result' in hit.keys() \
                              and hit['best_result'] == True), None)

            num_tracks = MAX_TRACKS

            # If there is no best result, then get a selection of tracks from
            # each situation. At least we'll play some music.
            if not situation and len(situation_hits):
                max_results = num_tracks / len(situation_hits)
                for hit in situation_hits:
                    situation = hit['situation']
                    print_nfo("[Google Play Music] [Situation] '{0} : {1}'." \
                              .format((hit['situation']['title']).encode('utf-8'),
                                      (hit['situation']['description']).encode('utf-8')))
                    self.__enqueue_station_unlimited(situation['title'],
                                                     max_results, True)
            elif situation:
                # There is at list one sitution, enqueue its tracks.
                situation = situation['situation']
                max_results = num_tracks
                self.__enqueue_station_unlimited(situation['title'],
                                                 max_results, True)

            if not situation:
                raise KeyError

        except KeyError:
            raise KeyError("Situation not found : {0}".format(arg))

    def __enqueue_podcast(self, arg):
        """Search for a podcast series and enqueue all of its tracks.

        """
        print_msg("[Google Play Music] [Podcast search in "\
                  "Google Play Music] : '{0}'. " \
                  .format(arg.encode('utf-8')))
        try:
            podcast_hits = self.__gmusic_search(arg,
                                                'podcast',
                                                10,
                                                quiet=False)

            if not podcast_hits:
                print_wrn(
                    "[Google Play Music] [Podcast] 'Search returned zero results'."
                )
                print_wrn(
                    "[Google Play Music] [Podcast] 'Are you in a supported region "
                    "(currently only US and Canada) ?'")

            # Use the first podcast retrieved. At least we'll play something.
            podcast = dict()
            if podcast_hits and len(podcast_hits):
                podcast = podcast_hits['series']

            episodes_added = 0
            if podcast:
                # There is a podcast, enqueue its episodes.
                print_nfo("[Google Play Music] [Podcast] 'Playing '{0}' by {1}'." \
                          .format((podcast['title']).encode('utf-8'),
                                  (podcast['author']).encode('utf-8')))
                print_nfo("[Google Play Music] [Podcast] '{0}'." \
                          .format((podcast['description'][0:150]).encode('utf-8')))
                series = self.__gmusic.get_podcast_series_info(
                    podcast['seriesId'])
                episodes = series['episodes']
                for episode in episodes:
                    print_nfo("[Google Play Music] [Podcast Episode] '{0} : {1}'." \
                              .format((episode['title']).encode('utf-8'),
                                      (episode['description'][0:80]).encode('utf-8')))
                episodes_added = self.__enqueue_tracks(episodes)

            if not podcast or not episodes_added:
                raise KeyError

        except KeyError:
            raise KeyError(
                "Podcast not found or no episodes found: {0}".format(arg))

    def __enqueue_tracks(self, tracks):
        """ Add tracks to the playback queue

        """
        count = 0
        for track in tracks:
            if u'id' not in track.keys() and track.get('storeId'):
                track[u'id'] = track['storeId']
            self.queue.append(track)
            count += 1
        return count

    def __update_playlists(self):
        """ Retrieve the user's playlists

        """
        plists = self.__gmusic.get_all_user_playlist_contents()
        for plist in plists:
            plist_name = plist.get('name')
            tracks = plist.get('tracks')
            if plist_name and tracks:
                logging.info("playlist name : %s", to_ascii(plist_name))
                tracks.sort(key=itemgetter('creationTimestamp'))
                self.playlists[plist_name] = list()
                for track in tracks:
                    song_id = track.get('trackId')
                    if song_id:
                        song = self.song_map.get(song_id)
                        if song:
                            self.playlists[plist_name].append(song)

    def __update_playlists_unlimited(self):
        """ Retrieve shared playlists (Unlimited)

        """
        plists_subscribed_to = [p for p in self.__gmusic.get_all_playlists() \
                                if p.get('type') == 'SHARED']
        for plist in plists_subscribed_to:
            share_tok = plist['shareToken']
            playlist_items \
                = self.__gmusic.get_shared_playlist_contents(share_tok)
            plist_name = plist['name']
            logging.info("shared playlist name : %s", to_ascii(plist_name))
            self.playlists[plist_name] = list()
            for item in playlist_items:
                try:
                    song = item['track']
                    song['id'] = item['trackId']
                    self.playlists[plist_name].append(song)
                except IndexError:
                    pass

    def __gmusic_search(self,
                        query,
                        query_type,
                        max_results=MAX_TRACKS,
                        quiet=False):
        """ Search Google Play (Unlimited)

        """

        search_results = self.__gmusic.search(query, max_results)[query_type +
                                                                  '_hits']

        # This is a workaround. Some podcast results come without these two
        # keys in the dictionary
        if query_type == "podcast" and len(search_results) \
           and not search_results[0].get('navigational_result'):
            for res in search_results:
                res[u'best_result'] = False
                res[u'navigational_result'] = False
                res[query_type] = res['series']

        result = ''
        if query_type != "playlist":
            result = next((hit for hit in search_results \
                           if 'best_result' in hit.keys() \
                           and hit['best_result'] == True), None)

        if not result and len(search_results):
            secondary_hit = None
            for hit in search_results:
                name = ''
                if hit[query_type].get('name'):
                    name = hit[query_type].get('name')
                elif hit[query_type].get('title'):
                    name = hit[query_type].get('title')
                if not quiet:
                    print_nfo("[Google Play Music] [{0}] '{1}'." \
                              .format(query_type.capitalize(),
                                      (name).encode('utf-8')))
                if query.lower() == \
                   to_ascii(name).lower():
                    result = hit
                    break
                if query.lower() in \
                   to_ascii(name).lower():
                    secondary_hit = hit
            if not result and secondary_hit:
                result = secondary_hit

        if not result and not len(search_results):
            # Do another search with an empty string
            search_results = self.__gmusic.search("")[query_type + '_hits']

        if not result and len(search_results):
            # Play some random result from the search results
            random.seed()
            result = random.choice(search_results)
            if not quiet:
                print_wrn("[Google Play Music] '{0}' not found. "\
                          "Feeling lucky?." \
                          .format(query.encode('utf-8')))

        return result
Example #10
0
class GmusicStore(BackendStore):

    # this *must* be set. Because the (most used) MediaServer Coherence also
    # allows other kind of Backends (like remote lights).
    implements = ['MediaServer']

    def __init__(self, server, *args, **kwargs):
        # first we initialize our heritage
        BackendStore.__init__(self, server, **kwargs)

        reload(sys)
        sys.setdefaultencoding('utf-8')

        # When a Backend is initialized, the configuration is given as keyword
        # arguments to the initialization. We receive it here as a dictionary
        # and allow some values to be set:

        # the name of the MediaServer as it appears in the network
        self.name = kwargs.get('name', 'Google Music')

        # timeout between updates in hours:
        self.refresh = int(kwargs.get('refresh', 1)) * (60 * 60)

        # the UPnP device that's hosting that backend, that's already done
        # in the BackendStore.__init__, just left here the sake of completeness
        self.server = server

        # initialize our containers
        self.container = GmusicContainer(None, ROOT_ID, "Google Music")
        self.container.tracks = GmusicContainer(self.container, TRACKS_ID, "Tracks")
        self.container.albums = GmusicContainer(self.container, ALBUM_ID, "Albums")
        self.container.playlists = GmusicContainer(self.container, PLAYLIST_ID, "Playlists")

        self.container.children.append(self.container.tracks)
        self.container.children.append(self.container.albums)
        self.container.children.append(self.container.playlists)

        # but as we also have to return them on 'get_by_id', we have our local
        # store of items per id:
        self.tracks = {}
        self.albums = {}
        self.artists = {}
        self.playlists = {}

        # we tell that if an XBox sends a request for images we'll
        # map the WMC id of that request to our local one
        # Todo: What the hell is going on here?
        # self.wmc_mapping = {'16': 0}

        self.username = kwargs.get('username', '')
        self.password = kwargs.get('password', '')
        self.device_id = kwargs.get('device_id', '')

        self.api = Mobileclient()

        if not self.login():
            self.info("Could not login")
            return

        # and trigger an update of the data
        dfr = self.update_data()

        # So, even though the initialize is kind of done, Coherence does not yet
        # announce our Media Server.
        # Coherence does wait for signal send by us that we are ready now.
        # And we don't want that to happen as long as we don't have succeeded
        # in fetching some first data, so we delay this signaling after the update is done:
        dfr.addCallback(self.init_completed)
        dfr.addCallback(self.queue_update)

    def login(self):
        return self.api.login(self.username, self.password, self.device_id)

    def get_by_id(self, id):
        print("asked for", id, type(id))
        # what ever we are asked for, we want to return the container only
        if isinstance(id, basestring):
            id = id.split('@', 1)
            id = id[0]
        if id == str(ROOT_ID):
            return self.container
        if id == str(ALBUM_ID):
            return self.container.albums
        if id == str(TRACKS_ID):
            return self.container.tracks
        if id == str(PLAYLIST_ID):
            return self.container.playlists

        if id in self.albums:
            self.info("id in albums:", id)
            album = self.albums.get(id, None)
            return album

        if id in self.playlists:
            self.info("id in playlists:", id)
            playlist = self.playlists.get(id, None)
            return playlist

        return self.tracks.get(id, None)

    def upnp_init(self):
        # after the signal was triggered, this method is called by coherence and

        # from now on self.server is existing and we can do
        # the necessary setup here

        # that allows us to specify our server options in more detail.

        # here we define what kind of media content we do provide
        # mostly needed to make some naughty DLNA devices behave
        # will probably move into Coherence internals one day
        self.server.connection_manager_server.set_variable(0, 'SourceProtocolInfo',
                                                           ['http-get:*:audio/mpeg:*',
                                                            'http-get:*:application/ogg:*', ]
                                                           )

        # and as it was done after we fetched the data the first time
        # we want to take care about the server wide updates as well
        self._update_container()

    def _update_container(self, result=None):
        # we need to inform Coherence about these changes
        # again this is something that will probably move
        # into Coherence internals one day
        if self.server:
            self.server.content_directory_server.set_variable(0,
                    'SystemUpdateID', self.update_id)
            value = (ROOT_ID, self.container.update_id)
            self.server.content_directory_server.set_variable(0,
                    'ContainerUpdateIDs', value)
        return result

    def update_loop(self):
        # in the loop we want to call update_data
        dfr = self.update_data()
        # after it was done we want to take care of updating
        # the container
        dfr.addCallback(self._update_container)
        # in ANY case queue an update of the data
        dfr.addBoth(self.queue_update)
        # finally clean the tempfiles
        print("Cleaning tempfiles")
        for file_id, file_tmp in cache.iteritems():
            if file_id not in recent_cache:
                os.close(file_tmp[0])
                os.remove(file_tmp[1])
        cache.clear()
        cache.update(recent_cache)
        recent_cache.clear()

    def get_data(self):
        subscribed_to_playlists = [p for p in self.api.get_all_playlists() if p.get('type') == 'SHARED']
        for playlist in subscribed_to_playlists:
            playlist["tracks"] = self.api.get_shared_playlist_contents(playlist.get("shareToken", ""))
        return (self.api.get_all_songs(), self.api.get_all_user_playlist_contents(), subscribed_to_playlists)

    def update_data(self):
        # trigger an update of the data
        self.info("Updating data")
        dfr = task.deferLater(reactor, 0, self.get_data)
        # then parse the data into our models
        dfr.addCallback(self.parse_data)
        # self.parse_data(dfr)
        self.info("Finished update")
        return dfr

    def parse_library(self, songs):
        for song in songs:
            try:
                song_id = song.get("storeId", song.get("nid", 0))
                title = song.get("title", "")
                artist_id = song.get("artistId", [0])[0]
                album_id = song.get("albumId", 0)
                album_name = song.get("album", "")
                duration = song.get("durationMillis", 0)
                album_art_uri = song.get("albumArtRef", [{"url":""}])[0].get("url", "")
                track_no = song.get("trackNumber", "0")
                disc_no = song.get("discNumber", "0")
                artist = song.get("artist", "")
                album_artist = song.get("albumArtist", artist)
                if album_id in self.albums:
                    album = self.albums[album_id]
                else:
                    album = GmusicAlbum(ALBUM_ID, self, album_id, album_name, artist_id, album_artist, album_art_uri)
                    self.container.albums.children.append(album)
                    self.albums[album_id] = album

                track = GmusicTrack(TRACKS_ID, self, song_id, title, artist, album_name, artist_id, album_id, track_no,
                                    duration, album_art_uri)

                album.tracks[int(str(disc_no) + str('{:0>10}'.format(track_no)))] = track
                self.container.tracks.children.append(track)
                self.tracks[song_id] = track
                # i = i + 1
            except Exception as e:
                print(e)
                print(song)
                traceback.print_exc()
        try:
            self.container.albums.children.sort(key=lambda x: (x.artist, x.title))
        except Exception as e:
            print("Failed to sort albums")
            print(e)
            traceback.print_exc()

    def parse_playlist_tracks(self, tracks):
        tracklist = []
        for track in tracks:
            try:
                song = track.get("track", {})
                song_id = song.get("storeId", song.get("nid", 0))
                if song_id in self.tracks:
                    tracklist.append(self.tracks[song_id])
                else:
                    title = song.get("title", "")
                    artist_id = song.get("artistId", [0])[0]
                    album_id = song.get("albumId", 0)
                    album_name = song.get("album", "")
                    duration = song.get("durationMillis", 0)
                    album_art_uri = song.get("albumArtRef", [{"url":""}])[0].get("url", "")
                    track_no = song.get("trackNumber", "0")
                    artist = song.get("artist", "")

                    track_container = GmusicTrack(TRACKS_ID, self, song_id, title, artist, album_name, artist_id, album_id, track_no,
                                        duration, album_art_uri)
                    tracklist.append(track_container)
                    self.tracks[song_id] = track_container
            except Exception as e:
                print(e)
                print(track)
                traceback.print_exc()

        return tracklist

    def parse_playlists(self, playlists):
        for playlist in playlists:
            try:
                playlist_id = playlist.get("id", playlist.get("shareToken", "DEBUG: NO_ID_TOKEN"))
                playlist_owner = playlist.get("ownerName", "DEBUG: NO_AUTHOR")
                playlist_owner_image = playlist.get("ownerProfilePhotoUrl", "DEBUG: NO_AUTHOR_IMAGE")
                playlist_name = playlist.get("name", "DEBUG: NO_NAME")
                playlist_tracks = playlist.get("tracks", {})

                playlist_container = GmusicPlaylist(PLAYLIST_ID, self, playlist_id, playlist_name, playlist_owner, playlist_owner_image)
                self.container.playlists.children.append(playlist_container)
                self.playlists[playlist_id] = playlist_container

                playlist_container.tracks = self.parse_playlist_tracks(playlist_tracks)

            except Exception as e:
                print(e)
                print(playlist)
                traceback.print_exc()

    def parse_data(self, data):
        self.info("Parsing data")
        # reset the storage
        self.container.tracks.children = []
        self.container.albums.children = []
        self.container.playlists.children = []
        self.tracks = {}
        self.albums = {}
        self.playlists = {}

        self.parse_library(data[0])
        self.parse_playlists(data[1])
        self.parse_playlists(data[2])

        self.info("Finished parsing")

        # and increase the container update id and the system update id
        # so that the clients can refresh with the new data
        # Todo: Does this actually do anyting?
        self.container.update_id += 1
        self.update_id += 1

    def queue_update(self, error_or_failure):
        # We use the reactor to queue another updating of our data
        print error_or_failure
        reactor.callLater(self.refresh, self.update_loop)
Example #11
0
class GMusic(object):
    def __init__(self):
        self.authenticated = False
        self.all_access = False
        self.library_loaded = False
        self.all_songs = []
        self.letters = {}
        self.artists = {}
        self.albums = {}
        self.genres = {}
        self.tracks_by_letter = {}
        self.tracks_by_artist = {}
        self.tracks_by_album = {}
        self.tracks_by_genre = {}
        self._device = None
        self._webclient = Webclient(debug_logging=False)
        self._mobileclient = Mobileclient(debug_logging=False)
        self._playlists = []
        self._playlist_contents = []
        self._stations = []

    def _get_device_id(self):
        if self.authenticated:
            devices = self._webclient.get_registered_devices()
            for dev in devices:
                if dev['type'] == 'PHONE':
                    self._device = dev['id'][2:]
                    break
                elif dev['type'] == 'IOS':
                    self._device = dev['id']
                    break

    def _set_all_access(self):
        settings = self._webclient._make_call(webclient.GetSettings, '')
        self.all_access = True if 'isSubscription' in settings['settings'] and settings['settings']['isSubscription'] == True else False

    def _set_all_songs(self):
        if len(self.all_songs) == 0:
            try:
                self.all_songs = self._mobileclient.get_all_songs()
            except NotLoggedIn:
                if self.authenticate():
                    self.all_songs = self._mobileclient.get_all_songs()
                else:
                    return []

        else:
            return self.all_songs

    def authenticate(self, email, password):
        try:
            mcauthenticated = self._mobileclient.login(email, password)
        except AlreadyLoggedIn:
            mcauthenticated = True

        try:
            wcauthenticated = self._webclient.login(email, password)
        except AlreadyLoggedIn:
            wcauthenticated = True

        self.authenticated = mcauthenticated and wcauthenticated
        self._set_all_access()
        self._get_device_id()
        return self.authenticated

    def load_data(self):
        self._set_all_songs()
        for song in self.all_songs:
            thumb = None
            letter = song['title'][0]
            artist = song['artist']
            album = song['album']
            genre = song['genre'] if 'genre' in song else '(None)'

            if letter not in self.tracks_by_letter:
                self.tracks_by_letter[letter] = []
                self.letters[letter] = None

            if artist not in self.tracks_by_artist:
                self.tracks_by_artist[artist] = []
                self.artists[artist] = None

            if album not in self.tracks_by_album:
                self.tracks_by_album[album] = []
                self.albums[album] = None

            if genre not in self.tracks_by_genre:
                self.tracks_by_genre[genre] = []
                self.genres[genre] = None

            track = {'artist': artist, 'album': album}

            if 'title' in song:
                track['title'] = song['title']

            if 'album' in song:
                track['album'] = song['album']

            if 'artist' in song:
                track['artist'] = song['artist']

            if 'durationMillis' in song:
                track['durationMillis'] = song['durationMillis']

            if 'id' in song:
                track['id'] = song['id']

            if 'trackNumber' in song:
                track['trackType'] = song['trackNumber']

            if 'storeId' in song:
                track['storeId'] = song['storeId']

            if 'albumArtRef' in song:
                track['albumArtRef'] = song['albumArtRef']
                thumb = song['albumArtRef'][0]['url']
                self.letters[letter] = thumb
                self.artists[artist] = thumb
                self.albums[album] = thumb
                self.genres[genre] = thumb

            self.tracks_by_letter[letter].append({'track': track, 'thumb': thumb, 'id': song['id']})
            self.tracks_by_artist[artist].append({'track': track, 'thumb': thumb, 'id': song['id']})
            self.tracks_by_album[album].append({'track': track, 'thumb': thumb, 'id': song['id']})
            self.tracks_by_genre[genre].append({'track': track, 'thumb': thumb, 'id': song['id']})

        self.library_loaded = True

    def get_tracks_for_type(self, type, name):
        type = type.lower()
        if type == 'artists':
            return self.tracks_by_artist[name]
        elif type == 'albums':
            return self.tracks_by_album[name]
        elif type == 'genres':
            return self.tracks_by_genre[name]
        elif type == 'songs by letter':
            return self.tracks_by_letter[name]
        else:
            return {}

    def get_song(self, id):
        return [x for x in self.all_songs if x['id'] == id][0]

    def get_all_playlists(self):
        if len(self._playlists) == 0:
            try:
                self._playlists = self._mobileclient.get_all_playlists()
            except NotLoggedIn:
                if self.authenticate():
                    self._playlists = self._mobileclient.get_all_playlists()
                else:
                    return []

        return self._playlists

    def get_all_user_playlist_contents(self, id):
        tracks = []
        if len(self._playlist_contents) == 0:
            try:
                self._playlist_contents = self._mobileclient.get_all_user_playlist_contents()
            except NotLoggedIn:
                if self.authenticate():
                    self._playlist_contents = self._mobileclient.get_all_user_playlist_contents()
                else:
                    return []

        for playlist in self._playlist_contents:
            if id == playlist['id']:
                tracks = playlist['tracks']
                break

        return tracks

    def get_shared_playlist_contents(self, token):
        playlist = []
        try:
            playlist = self._mobileclient.get_shared_playlist_contents(token)
        except NotLoggedIn:
            if self.authenticate():
                playlist = self._mobileclient.get_shared_playlist_contents(token)
            else:
                return []

        return playlist

    def get_all_stations(self):
        if len(self._stations) == 0:
            try:
                self._stations = self._mobileclient.get_all_stations()
            except NotLoggedIn:
                if self.authenticate():
                    self._stations = self._mobileclient.get_all_stations()
                else:
                    return []

        return self._stations

    def get_station_tracks(self, id, num_tracks=200):
        tracks = []
        try:
            tracks = self._mobileclient.get_station_tracks(id, num_tracks)
        except NotLoggedIn:
            if self.authenticate():
                tracks = self._mobileclient.get_station_tracks(id, num_tracks)
            else:
                return []

        return tracks

    def get_genres(self):
        genres = []
        try:
            genres = self._mobileclient.get_genres()
        except NotLoggedIn:
            if self.authenticate():
                genres = self._mobileclient.get_genres()
            else:
                return []

        return genres

    def create_station(self, name, id):
        station = None
        try:
            station = self._mobileclient.create_station(name=name, genre_id=id)
        except NotLoggedIn:
            if self.authenticate():
                station = self._mobileclient.create_station(name=name, genre_id=id)
            else:
                return []

        return station

    def search_all_access(self, query, max_results=50):
        results = None
        try:
            results = self._mobileclient.search_all_access(query, max_results)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.search_all_access(query, max_results)
            else:
                return []

        return results

    def get_artist_info(self, id, include_albums=True, max_top_tracks=5, max_rel_artist=5):
        results = None
        try:
            results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.get_artist_info(id, include_albums=include_albums, max_top_tracks=max_top_tracks, max_rel_artist=max_rel_artist)
            else:
                return []

        return results

    def get_album_info(self, id, include_tracks=True):
        results = None
        try:
            results = self._mobileclient.get_album_info(id, include_tracks=include_tracks)
        except NotLoggedIn:
            if self.authenticate():
                results = self._mobileclient.get_album_info(id, include_tracks=include_tracks)
            else:
                return []

        return results

    def add_aa_track(self, id):
        track = None
        try:
            track = self._mobileclient.add_aa_track(id)
        except NotLoggedIn:
            if self.authenticate():
                track = self._mobileclient.add_aa_track(id)
            else:
                return None

        return track

    def add_songs_to_playlist(self, playlist_id, song_ids):
        tracks = None
        try:
            tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids)
        except NotLoggedIn:
            if self.authenticate():
                tracks = self._mobileclient.add_songs_to_playlist(playlist_id, song_ids)
            else:
                return None

        return tracks

    def get_stream_url(self, id):
        try:
            stream_url = self._mobileclient.get_stream_url(id, self._device)
        except NotLoggedIn:
            if self.authenticate():
                stream_url = self._mobileclient.get_stream_url(id, self._device)
            else:
                return ''
        except CallFailure:
            raise CallFailure('Could not play song with id: ' + id, 'get_stream_url')

        return stream_url

    def reset_library(self):
        self.library_loaded = False
        self.all_songs[:] = []
        self._playlists[:] = []
        self._playlist_contents[:] = []
        self._stations[:] = []
        self.letters.clear()
        self.artists.clear()
        self.albums.clear()
        self.genres.clear()
        self.tracks_by_letter.clear()
        self.tracks_by_artist.clear()
        self.tracks_by_album.clear()
        self.tracks_by_genre.clear()
Example #12
0
api = Mobileclient(debug_logging = False)

if not args.username:
 args.username = raw_input('Username: '******'Password:'******'Logging in uwer %s.', args.username)
if not api.login(args.username, args.password, ''.join([choice('1234567890abcdef') for x in xrange(16)]) if args.random_id else api.FROM_MAC_ADDRESS):
 quit('Login failed.')

m = Menu('Select source to download')
m.add_entry('Library', api.get_all_songs)
for p in api.get_all_playlists():
 m.add_entry('%s playlist' % p.get('name', 'Untitled'), lambda token = p['shareToken']: [x['track'] for x in api.get_shared_playlist_contents(token)])
res = m.get_selection()
if res:
 logging.debug('Getting source contents.')
 program_start = time()
 logging.info('Started the download at %s.', ctime(program_start))
 total = 0.0 # The average download time.
 try:
  for i, r in enumerate(res()):
   logging.debug('Result = %s', r)
   artist = valid_filename(r.get('artist', 'Unknown Artist'))
   album = valid_filename(r.get('album', 'Unknown Album'))
   number = r.get('trackNumber', 0)
   if number < 10:
    number = '0%s' % number
   else:
Example #13
0
class GMusicClient(ContentConsumer):
    '''Element in charge of interfacing with GMusicApi Client'''

    def __init__(self, data_cache):
        self.client = Mobileclient()
        self.data_cache = data_cache

    def login(self):
        '''Use data/unlocked/credentials.json to log in'''
        mac = Mobileclient.FROM_MAC_ADDRESS
        credentials = json.load(open('data/unlocked/credentials.json', 'r'))
        self.client.login(credentials['username'], credentials['password'], mac)

    def load_my_library(self):
        '''Load user's songs, playlists, and stations'''
        track_load = threading.Thread(target=self.load_tracks)
        radio_load = threading.Thread(target=self.load_radios)
        playlist_load = threading.Thread(target=self.load_playlists)

        track_load.start()
        radio_load.start()
        playlist_load.start()

        track_load.join()
        radio_load.join()
        playlist_load.join()

    def load_playlists(self):
        playlists = self.client.get_all_user_playlist_contents()
        playlists.reverse()
        self.data_cache.playlists = playlists

    def load_tracks(self):
        self.data_cache.tracks = [t for t in self.client.get_all_songs() if 'nid' in t]

    def scrape_song(self, track):
        return track

    def load_radios(self):
        radios = self.client.get_all_stations()
        radios.reverse()
        self.data_cache.radios = radios

    def get_radio_contents(self, radio_id):
        tracks = self.client.get_station_tracks(radio_id)
        return tracks

    def get_radio_list(self, name):
        return [r for r in self.data_cache.radios if name in r['name']]

    def filter(self, element, field, filter_by):
        return [e for e in element if filter_by in e[field]]

    def get_playlist_list(self, name):
        return self.filter(self.data_cache.playlists, 'name', name)

    def search_all_access(self, query):
        return self.client.search_all_access(query)

    def create_radio(self, seed_type, id, name):
        '''Create a radio'''
        # This is a weird way to do this, but there's no other choice
        ids = {"track": None, "album": None, "artist": None}
        seed_id_name = self.get_type_name(seed_type)
        ids[seed_id_name] = id
        return self.client.create_station(name=name, track_id=ids['track'], album_id=ids['album'], artist_id=ids['artist'])

    def search_items_all_access(self, type, query):
        '''Searches Albums, Artists, and Songs; uses metaprogramming'''
        index_arguments = self.get_index_arguments(type)

        items = self.search_all_access(query)['{0}_hits'.format(type[:-1])]
        return [self.format_item(item, type, index_arguments) for item in items]

    def get_sub_items(self, type_from, search_type, from_id):
        '''Here type_from refers to artist or album we're indexing against'''
        args = self.get_index_arguments(search_type)

        if type_from == 'playlist':
            return self.get_playlist_contents(from_id)

        else:
            # Get the appropriate search method and execute it
            search_method_name = 'get_{0}_info'.format(type_from)
            search_method = getattr(self.client, search_method_name)
            try:
                items = search_method(from_id, True)[args['type']+'s'] # True here includes subelements
            except:
                # User passed in a bad id or something
                return

        # Now return appropriately
        return [self.format_subitems(t, args) for t in items if args['id'] in t]

    def get_playlist_contents(self, from_id):
        '''Playlist exclusive stuff'''
        shareToken = [t for t in self.data_cache.playlists \
            if t['id'] == from_id][0]['shareToken']

        contents = self.client.get_shared_playlist_contents(shareToken)
        return [self.format_playlist_contents(t) for t in contents if 'track' in t]

    def get_suggested(self):
        '''Returns a list of tracks that the user might be interested in'''
        items = sorted(self.client.get_promoted_songs(), key=lambda y: y['title'])
        return [self.format_suggested(t) for t in items if 'storeId' in t]

    def get_information_about(self, from_type, from_id):
        '''Gets specific information about an id (depending on the type)'''
        if 'artist' in from_type.lower():
            return self.client.get_artist_info(from_id, include_albums=False)
        if 'album' in from_type.lower():
            return self.client.get_album_info(from_id, include_tracks=False)
        return self.client.get_track_info(from_id)

    def get_stream_url(self, nid):
        return self.client.get_stream_url(nid)

    def lookup(self, nid):
        return self.client.get_track_info(nid)

    def add_track_to_library(self, nid):
        self.client.add_aa_track(nid)

    def add_to_playlist(self, playlist_id, nid):
        self.client.add_songs_to_playlist(playlist_id, nid)

        playlist_load = threading.Thread(target=self.load_playlists)
        playlist_load.daemon = True
        playlist_load.start()

    def format_suggested(self, t):
        return (t['title'], t['storeId'], 'Play', t['album'])

    def format_playlist_contents(self, t):
        return (t['track']['title'], t['trackId'], 'Play', t['track']['album'])

    def format_subitems(self, t, args):
        return (t[args['name']], t[args['id']], args['command'], t[args['alt']])