Example #1
0
class GoogleMusic(Source):
    def __init__(self, library, username, password):
        Source.__init__(self, library, SourceType.GOOGLE)
        self.GOOGLE_DEVICE_ID = None

        print username
        print password

        self.client = Mobileclient()
        logged_in = self.client.login(username, password, Mobileclient.FROM_MAC_ADDRESS)
        print "Google logged in:", logged_in

        DList = self.client.get_registered_devices()
        self.GOOGLE_DEVICE_ID = DList[0]["id"]
        if self.GOOGLE_DEVICE_ID[:2] == '0x':
            self.GOOGLE_DEVICE_ID = self.GOOGLE_DEVICE_ID[2:]

        print self.GOOGLE_DEVICE_ID

        #>testing
        # self.get_stream_URL("47b9d52c-9d66-3ff2-94d4-3ae55c0d2acc")

    def get_stream_URL(self, song_id):
        return self.client.get_stream_url(song_id, self.GOOGLE_DEVICE_ID)

    def sync(self):
        gmusic_tracks = self.client.get_all_songs()
        for track in gmusic_tracks:
            art = ''
            try:
                art = track['albumArtRef'][0]['url']
            except KeyError:
                art = ''
            self.library.insert_track(track['title'], track['album'], track['artist'], self._source, str(track['id']), track['trackNumber'], art)
def download_album(username, password, artist_name, album_name):
    api = Mobileclient()
    api.login(username, password, Mobileclient.FROM_MAC_ADDRESS)

    library = api.get_all_songs()
    songs = [s for s in library if s['albumArtist'] == artist_name and s['album'] == album_name]

    if len(songs) == 0:
        print('Error: Album not found', file=sys.stderr)
        return

    device_id = api.get_registered_devices()[0]['id'].replace('0x', '')
    dname = slugify(unicode(album_name))
    os.mkdir(dname)
    
    # download songs
    for song in tqdm(songs, desc='Downloading'):
        fname = slugify(song['title'])
        mpg_name = os.path.join(dname, fname + '.mpg')
        mp3_name = os.path.join(dname, fname + '.mp3')

        url = api.get_stream_url(song['id'], device_id=device_id)
        response = requests.get(url)

        # first save as MPEG video
        with open(mpg_name, 'wb') as fout:
            for chunk in response.iter_content(chunk_size=128):
                fout.write(chunk)

        # call FFMPEG to convert to MP3
        os.system(' '.join([FFMPEG_CMD] + FFMPEG_ARGS).format(
            input=mpg_name,
            output=mp3_name,
            title=song['title'],
            artist=song['albumArtist'],
            album=song['album'],
            track=song['trackNumber']))

        os.remove(mpg_name)

    # download album art
    art_name = os.path.join(dname, dname + '.png')
    album_info = api.get_album_info(songs[0]['albumId'], include_tracks=False)
    response = requests.get(album_info['albumArtRef'])
    t = magic.from_buffer(response.content, mime=True)

    if t == 'image/jpeg':
        ext = '.jpg'
    elif t == 'image/png':
        ext = '.png'
    else:
        print('Unknown MIME type: {}'.format(t), file=sys.stderr)
        ext = '.wat'

    with open(os.path.join(dname, dname + ext), 'wb') as fout:
        fout.write(response.content)
Example #3
0
def get_device_id(api: gmusicapi.Mobileclient):
    devices = api.get_registered_devices()
    # TODO: sort devices by the last accessed time, grab the most recent
    for device in devices:
        if device['type'] != 'ANDROID':
            continue

        return device['id'].lstrip('0x')

    raise Exception('could not find an android device id')
Example #4
0
 def __init__(self, hass, config):
     """Initialize the books authors."""
     global G_GM_MOBILE_CLIENT_API
     self.hass = hass
     self.all_gm_tracks = []
     self.selected_books = []
     from gmusicapi import Mobileclient
     G_GM_MOBILE_CLIENT_API = Mobileclient()
     G_GM_MOBILE_CLIENT_API.login(GM_USER, GM_PASS, GM_DEV_KEY)
     if not G_GM_MOBILE_CLIENT_API.is_authenticated():
         _LOGGER.error("Failed to log in, check gmusicapi")
         return False
     else:
         _LOGGER.info("OK - we are in Gmusic")
         _LOGGER.info("devices: " +
                      str(G_GM_MOBILE_CLIENT_API.get_registered_devices()))
class GoogleMusicLogin():
    def __init__(self):
        import requests
        from requests.packages.urllib3.exceptions import InsecureRequestWarning
        from requests.packages.urllib3.exceptions import InsecurePlatformWarning
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
        self.gmusicapi = Mobileclient(debug_logging=False, validate=False, verify_ssl=False)

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

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

    def getApi(self):
        return self.gmusicapi

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

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

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

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

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

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

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

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

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

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

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

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

            utils.log("Loading auth from cache")
            self.gmusicapi.session._authtoken = utils.addon.getSetting('authtoken-mobile')
            self.gmusicapi.session.is_authenticated = True
Example #6
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 #7
0
def print_all_my_devices(api: Mobileclient):
    for device in api.get_registered_devices():
        print('{')
        for k in device:
            print('\t{}: {}'.format(k, device[k]))
        print('}')
Example #8
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=True)
        self._mobileclient = Mobileclient(debug_logging=False)
        self._playlists = []
        self._playlist_contents = []
        self._stations = []

    def _get_device_id(self):
        if self.authenticated:
            devices = self._mobileclient.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, Mobileclient.FROM_MAC_ADDRESS)
        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 #9
0
class GoogleMusicApi:
    def __init__(self):
        self._api = Mobileclient()

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

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

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

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

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

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

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

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

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

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

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

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

    def increment_track_playcount(self, track_id):
        self._api.increment_song_playcount(track_id)