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 #2
0
def _get_global_tracks(api: gmusicapi.Mobileclient, artist_ids, album_ids):
    artist_ids = list(artist_ids)
    album_ids = list(album_ids)

    for artist_id in artist_ids:
        results = api.get_artist_info(artist_id)
        for album_stub in results['albums']:
            album_id = album_stub['albumId']
            album_ids.append(album_id)

    for album_id in album_ids:
        album = api.get_album_info(album_id)
        yield from album['tracks']
Example #3
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 #4
0
class MusicLibrary(object):
    'Read information about your Google Music library'

    def __init__(self, username=None, password=None,
                 true_file_size=False, scan=True, verbose=0):
        self.verbose = False
        if verbose > 1:
            self.verbose = True

        self.__login_and_setup(username, password)

        self.__artists = {} # 'artist name' -> {'album name' : Album(), ...}
        self.__galbums = {}
        self.__gartists = {}
        self.__albums = [] # [Album(), ...]
        if scan:
            self.rescan()
        self.true_file_size = true_file_size

    def rescan(self):
        self.__artists = {} # 'artist name' -> {'album name' : Album(), ...}
        self.__albums = [] # [Album(), ...]
        self.__galbums = {}
        self.__gartists = {}
        self.__aggregate_albums()

    def __login_and_setup(self, username=None, password=None):
        # If credentials are not specified, get them from $HOME/.gmusicfs
        if not username or not password:
            cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs')
            if not os.path.isfile(cred_path):
                raise NoCredentialException(
                    'No username/password was specified. No config file could '
                    'be found either. Try creating %s and specifying your '
                    'username/password there. Make sure to chmod 600.'
                    % cred_path)
            if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'):
                raise NoCredentialException(
                    'Config file is not protected. Please run: '
                    'chmod 600 %s' % cred_path)
            self.config = ConfigParser.ConfigParser()
            self.config.read(cred_path)
            username = self.config.get('credentials','username')
            password = self.config.get('credentials','password')
            global deviceId
            deviceId = self.config.get('credentials','deviceId')
            if not username or not password:
                raise NoCredentialException(
                    'No username/password could be read from config file'
                    ': %s' % cred_path)
            if not deviceId:
                raise NoCredentialException(
                    'No deviceId could be read from config file'
                    ': %s' % cred_path)

        self.api = GoogleMusicAPI(debug_logging=self.verbose)
        log.info('Logging in...')
        self.api.login(username, password)
        log.info('Login successful.')

    def __set_key_from_ginfo(self, track, ginfo, key, to_key=None):
        'Set track key from either album_info or artist_info'
        if to_key is None:
            to_key = key

        try:
            int_key = int(key)
        except ValueError:
            int_key = None

        if (not track.has_key(key) or track[key] == "" or int_key == 0) and ginfo.has_key(to_key):
            track[key] = ginfo[to_key]

        return track

    def __cleanup_artist(self, artist):
        if artist.startswith("featuring"):
            artist = artist[len("featuring"):].strip()
        if artist.startswith("feat"):
            artist = artist[len("feat"):].strip()
        return artist

    def __cleanup_name(self, name, track):
        for bracket in (('\[', '\]'), ('\{', '\}'), ('\(', '\)')):
            # Remove (xxx Album Version) from track names
            match = re.compile('^(?P<name>(.*))([ ]+[%s-]([^%s]*)[Vv]ersion[%s]?[ ]*)$' % (bracket[0], bracket[1], bracket[1])).match(name)
            if match is not None:
                name = match.groupdict()['name']
                name, track = self.__cleanup_name(name, track)

            # Pull (feat. <artist>) out of name and add to artist list
            match = re.compile('^(?P<name>(.*))([ ]+[%s][ ]*[Ff]eat[\.]?[ ]*(?P<artist>(.*))[%s]+)(?P<postfix>(.*))$' % (bracket[0], bracket[1])).match(name)
            if match is not None:
                name = match.groupdict()['name']
                artist = match.groupdict()['artist']
                if match.groupdict().has_key('postfix') and match.groupdict()['postfix'] is not None:
                    name += match.groupdict()['postfix']
                artist = artist.strip()
                if artist[-1] in ")}]": # I hate regex's.  The one above doesn't catch the last parenthesis if there's one
                    artist = artist[:-1]
                if artist.find(" and ") > -1 or artist.find(" & ") > -1:
                    artist = artist.replace(', ', ';')
                artist = artist.replace(' & ', ';')
                artist = artist.replace(' and ', ';')
                alist = artist.split(';')
                for artist in alist:
                     track['artist'].append(artist.strip())
                name, track = self.__cleanup_name(name, track)

            # Remove () or ( ) from track names
            match = re.compile('^(?P<name>(.*))([ ]*[%s][ ]?[%s][ ]*)$' % (bracket[0], bracket[1])).match(name)
            if match is not None:
                name = match.groupdict()['name']
                name, track = self.__cleanup_name(name, track)

        # Strip any extra whitespace from the name
        name = name.strip()
        return name, track

    def __cleanup_track(self, track):
        name = track['title']
        name, track = self.__cleanup_name(name, track)
        track['title'] = name
        for anum in range(0, len(track['artist'])):
            track['artist'][anum] = self.__cleanup_artist(track['artist'][anum])
        return track

    def __aggregate_albums(self):
        'Get all the tracks in the library, parse into artist and album dicts'
        all_artist_albums = {}
        log.info('Gathering track information...')
        tracks = self.api.get_all_songs()
        for track in tracks:
            if track.has_key('artist'):
                if track['artist'].find(" and ") > -1 or track['artist'].find(" & ") > -1:
                    track['artist'] = track['artist'].replace(', ', ';')
                track['artist'] = track['artist'].replace(' & ', ';')
                track['artist'] = track['artist'].replace(' and ', ';')
                track['artist'] = track['artist'].split(';')
            else:
                track['artist'] = []

            track = self.__cleanup_track(track)

            if track.has_key('albumArtist') and track['albumArtist'] != "":
                albumartist = track['albumArtist']
            elif len(track['artist']) == 1 and track['artist'][0] != "":
                albumartist = track['artist'][0]
            else:
                albumartist = "Unknown"

            # Get album and artist information from Google
            if track.has_key('albumId'):
                if self.__galbums.has_key(track['albumId']):
                    album_info = self.__galbums[track['albumId']]
                else:
                    print "Downloading album info for '%s'" % track['album']
                    album_info = self.__galbums[track['albumId']] = self.api.get_album_info(track['albumId'], include_tracks=False)
                if album_info.has_key('artistId') and len(album_info['artistId']) > 0 and album_info['artistId'][0] != "":
                    artist_id = album_info['artistId'][0]
                    if self.__gartists.has_key(artist_id):
                        artist_info = self.__gartists[artist_id]
                    else:
                        print "Downloading artist info for '%s'" % album_info['albumArtist']
                        if album_info['albumArtist'] == "Various":
                            print album_info
                        artist_info = self.__gartists[artist_id] = self.api.get_artist_info(artist_id, include_albums=False, max_top_tracks=0, max_rel_artist=0)
                else:
                    artist_info = {}
            else:
                album_info = {}
                artist_info = {}

            track = self.__set_key_from_ginfo(track, album_info, 'album', 'name')
            track = self.__set_key_from_ginfo(track, album_info, 'year')
            track = self.__set_key_from_ginfo(track, artist_info, 'albumArtist', 'name')

            # Fix for odd capitalization issues
            if artist_info.has_key('name') and track['albumArtist'].lower() == artist_info['name'].lower() and track['albumArtist'] != artist_info['name']:
                track['albumArtist'] = artist_info['name']
            for anum in range(0, len(track['artist'])):
                if artist_info.has_key('name') and track['artist'][anum].lower() == artist_info['name'].lower() and track['artist'][anum] != artist_info['name']:
                    track['artist'][anum] = artist_info['name']

            if not track.has_key('albumId'):
                track['albumKey'] = "%s|||%s" % (albumartist, track['album'])
            else:
                track['albumKey'] = track['albumId']
            album = all_artist_albums.get(track['albumKey'], None)

            if not album:
                album = all_artist_albums[track['albumKey']] = Album(
                    self, formatNames(track['album']), track['albumArtist'], track['album'], track['year'] )
                self.__albums.append(album)
                artist_albums = self.__artists.get(track['albumArtist'], None)
                if artist_albums:
                    artist_albums[formatNames(album.normtitle)] = album
                else:
                    self.__artists[track['albumArtist']] = {album.normtitle: album}
                    artist_albums = self.__artists[track['albumArtist']]
            album.add_track(track)

        # Separate multi-disc albums
        for artist in self.__artists.values():
            for key in artist.keys():
                album = artist[key]
                if album.get_disc_count() > 1:
                    for d in album.get_discs():
                        new_name = "%s - Disc %i" % (album.album, d)
                        new_album = Album(album.library, formatNames(new_name), album.artist, new_name, album.year)
                        album.copy_art_to(new_album)
                        new_album.show_discnum = True
                        new_key = None
                        for t in album.get_tracks():
                            if int(t['discNumber']) == d:
                                new_album.add_track(t)
                        artist[formatNames(new_name)] = new_album
                    del artist[key]

        log.debug('%d tracks loaded.' % len(tracks))
        log.debug('%d artists loaded.' % len(self.__artists))
        log.debug('%d albums loaded.' % len(self.__albums))

    def get_artists(self):
        return self.__artists

    def get_albums(self):
        return self.__albums

    def get_artist_albums(self, artist):
        log.debug(artist)
        return self.__artists[artist]

    def cleanup(self):
        pass
Example #5
0
class MusicManager(object):
    def __init__(self):

        self.api = Mobileclient(validate=False, debug_logging=False)
        if config.GOOGLE_STREAMKEY is not None:
            self.api.login(config.GOOGLE_USERNAME, config.GOOGLE_PASSWORD, config.GOOGLE_STREAMKEY)
        else:
            self.api.login(config.GOOGLE_USERNAME, config.GOOGLE_PASSWORD, Mobileclient.FROM_MAC_ADDRESS)

        self.queue = []
        self.current_index = len(self.queue) - 1

        self.vlc = VlcManager()

        self.state_thread = Thread(target=self.check_state)
        self.state_thread.daemon = True
        self.state_thread.start()

    def play_song(self, id):
        song = self.queue_song(id)
        self.current_index = len(self.queue) - 1
        self.load_song()
        return song

    def queue_song(self, id):
        self.queue.append(self.getSongInfo(id))

    def play_radio_station(self, id):
        results = self.api.get_station_tracks(id, num_tracks=40)

        for song in results:
            song['albumArtRef'] = song['albumArtRef'][0]['url']
            if 'artistId' in song:
                song['artistId'] = song['artistId'][0]

        self.current_index = len(self.queue) - 1
        self.queue.append(results)
        self.load_song()

    def play_album(self, args):
        album = self.get_album_details(args)
        songs = []
        for index in range(len(album['tracks'])):
            song = album['tracks'][index]
            if index == 0:
                songs.append(self.play_song(song['nid']))
            else:
                songs.append(self.queue_song(song['nid']))
        return songs

    def queue_album(self, args):
        album = self.get_album_details(args)
        songs = []
        for song in album['tracks']:
            songs.append(self.queue_song(song['nid']))
        return songs

    def next(self):
        self.current_index += 1
        self.load_song()

    def prev(self):
        self.current_index -= 1
        self.load_song()

    def pause(self):
        self.vlc.vlc_pause()

    def resume(self):
        self.vlc.vlc_resume()

    def volume(self, val):
        self.vlc.vlc_volume(val)

    def delete(self, id):
        if id > self.current_index:
            del self.queue[id]
        elif id < self.current_index:
            del self.queue[id]
            self.current_index -= 1
        else:
            del self.queue[id]
            self.load_song()

    def go_to(self, id):
        self.current_index = id
        self.load_song()

    def load_song(self):
        if self.current_index < len(self.queue):
            song = self.queue[self.current_index]
            url = self.api.get_stream_url(song['nid'], config.GOOGLE_STREAMKEY)
            self.vlc.vlc_play(url)

    def check_state(self):
        while True:
            status = self.vlc.player.get_state()
            if status == vlc.State.Ended:
                if self.current_index != len(self.queue) - 1:
                    self.next()

            time.sleep(1)

    def get_status(self):

        status = self.vlc.vlc_status()

        # status['queue'] = self.queue[:]
        # for i in range(len(status['queue'])):
        #     status['queue'][i]['vlcid'] = i
        #     if i == self.current_index:
        #         status['queue'][i]['current'] = True
        #         status['current'] = status['queue'][i]
        if len(self.queue) > 0:
            status['current'] = self.queue[self.current_index]
        return status

    def get_queue(self):
        queue = self.queue[:]
        for i in range(len(queue)):
            queue[i]['vlcid'] = i

        return queue


    def search(self, query):
        results = self.api.search_all_access(query, max_results=50)

        results['artist_hits'] = [artist['artist'] for artist in results['artist_hits']]

        results['album_hits'] = [album['album'] for album in results['album_hits']]
        for album in results['album_hits']:
            album['artistId'] = album['artistId'][0]

        results['song_hits'] = [song['track'] for song in results['song_hits']]
        for song in results['song_hits']:
            song['albumArtRef'] = song['albumArtRef'][0]['url']
            if 'artistId' in song:
                song['artistId'] = song['artistId'][0]
        return results

    def get_album_details(self, id):
        results = self.api.get_album_info(album_id=id, include_tracks=True)
        results['artistId'] = results['artistId'][0]
        for song in results['tracks']:
            song['albumArtRef'] = song['albumArtRef'][0]['url']
            if 'artistId' in song:
                song['artistId'] = song['artistId'][0]
        return results

    def get_artist_details(self, id):
        results = self.api.get_artist_info(artist_id=id)
        for album in results['albums']:
            album['artistId'] = album['artistId'][0]

        for song in results['topTracks']:
            song['albumArtRef'] = song['albumArtRef'][0]['url']
            if 'artistId' in song:
                song['artistId'] = song['artistId'][0]
        return results


    def create_radio_station(self, name, id):
        if id[0] == 'A':
            station_id = self.api.create_station(name, artist_id=id)

        elif id[0] == 'B':
            station_id = self.api.create_station(name, album_id=id)

        else:
            station_id = self.api.create_station(name, track_id=id)

        return station_id

    def get_radio_stations(self):
        return self.api.get_all_stations()

    def flush(self):
        self.vlc.vlc_stop()
        self.queue = []

    def getSongInfo(self, id):
        song = self.api.get_track_info(id)
        song['albumArtRef'] = song['albumArtRef'][0]['url']
        if 'artistId' in song:
            song['artistId'] = song['artistId'][0]
        return song
Example #6
0
class Gmusic(object):
    """Class to handle Google Music-related functionality"""
    def __init__(self, bot):
        """ init """
        self.bot = bot
        self.mob = Mobileclient()

    def login(self,
              username,
              password,
              android_id=Mobileclient.FROM_MAC_ADDRESS):
        """ login method """
        self.mob.login(username, password, android_id)
        return self.mob.is_authenticated()

    def search(self, searchterms):
        """ search for stuff """
        hits = self.mob.search("{0}".format(searchterms))
        return hits

    def create_playlist(self, name, song_ids, public=True):
        """
        create new playlist named 'name', containing songs with 'song_id'
        """
        playlist_id = self.mob.create_playlist(name,
                                               description="Bot Playlist",
                                               public=public)
        self.mob.add_songs_to_playlist(playlist_id, song_ids)
        return playlist_id

    def _make_playlist_share_link(self, share_token):
        base_share_url = "https://play.google.com/music/playlist"
        return "{}/{}".format(base_share_url, share_token)

    def share_playlist(self, playlist_id):
        try:
            [share_token] = [
                plist['shareToken'] for plist in self.mob.get_all_playlists()
                if plist['id'] == playlist_id
            ]
            return self._make_playlist_share_link(share_token)
        except ValueError:
            return "Cannot find playlist"

    def get_best_song_match(self, artist, title):
        hits = self.search("{0} {1}".format(artist, title))
        tracks = self.filter_to_song_minimum_info(self.get_songs(hits))
        similarities = [(similarity(track['artist'], artist, track['title'],
                                    title), track) for track in tracks]

        sorted_tracks = sorted(similarities, key=lambda k: k[0])

        best_track = None
        if len(sorted_tracks) > 0:
            best_track = sorted_tracks[0][1]
        return best_track

    def get_best_album_match(self, artist, album):
        hits = self.search("{0} {1}".format(artist, album))
        albums = self.get_albums(hits)
        similarities = [(similarity(a['artist'], artist, a['album'], album), a)
                        for a in albums]

        sorted_albums = sorted(similarities, key=lambda k: k[0])

        if len(sorted_albums) == 0:
            return []

        best_album = sorted_albums[0][1]
        album_info = self.mob.get_album_info(best_album['albumId'])
        store_ids = [t['storeId'] for t in album_info['tracks']]
        print("Store ids in best_album_match: {0}".format(store_ids))
        return store_ids

    def format_best_match(self, artist, title):
        track = self.get_best_song_match(artist, title)
        share_base_url = 'https://play.google.com/music/m/'

        return "{0} {1} {2} - {3}{4}".format(track['artist'], track['album'],
                                             track['title'], share_base_url,
                                             track['storeId'])

    def get_albums(self, results):
        albums = [album.get('album', None) for album in results['album_hits']]
        album_details = [{
            'artist': a['artist'],
            'album': a['name'],
            'albumId': a['albumId']
        } for a in albums]
        return album_details

    def get_songs(self, results):
        return [song.get('track', None) for song in results['song_hits']]

    def filter_to_song_minimum_info(self, results):
        return [{
            'artist': song.get('artist', None),
            'album': song.get('album', None),
            'title': song.get('title', None),
            'storeId': song.get('storeId', None)
        } for song in results]

    def convert_spotify_embed_to_gmusic(self, url):
        s_list = SpotifyPlaylist(url)
        title = s_list.title
        best_matches = [
            self.get_best_song_match(i.artist, i.track) for i in s_list.items
        ]
        filtered_matches = [i for i in best_matches if i is not None]
        store_ids = [i.get('storeId') for i in filtered_matches]
        new_plist = self.create_playlist(title, store_ids)
        return self.share_playlist(new_plist)

    def convert_hbih_to_gmusic(self, url):
        hbih_list = HBIHPlaylist(url)
        title = hbih_list.title
        store_ids = []
        for item in hbih_list.items:
            album_store_ids = self.get_best_album_match(item[0], item[1])
            print("Adding store ids: {0}".format(album_store_ids))
            store_ids.extend(album_store_ids)

        store_id_set = IndexedSet(store_ids)
        no_dupes_store_ids = list(store_id_set)
        new_plist = self.create_playlist(title, no_dupes_store_ids[0:1000])
        return self.share_playlist(new_plist)

    def create_playlist_from_song_names(self, artist, songs):
        year = datetime.datetime.now().year
        title = "{} setlist ({})".format(artist, year)
        best_matches = [self.get_best_song_match(artist, s) for s in songs]
        filtered_matches = [i for i in best_matches if i is not None]
        store_ids = [i.get('storeId') for i in filtered_matches]
        new_plist = self.create_playlist(title, store_ids)
        return self.share_playlist(new_plist)

    def get_newest_playlists(self, count=5):
        """ return 'count' newest playlists """
        all_plists = self.mob.get_all_playlists()
        sorted_plists = sorted(all_plists,
                               key=lambda k: k['lastModifiedTimestamp'],
                               reverse=True)
        if count > 0:
            newest_plists = sorted_plists[:count]
        else:
            newest_plists = sorted_plists
        info = [{
            'name': p['name'],
            'share': self._make_playlist_share_link(p['shareToken'])
        } for p in newest_plists]
        return info

    def get_all_playlists(self):
        """ return all playlists """
        return self.get_newest_playlists(0)  # 0 = return everything

    def find_playlists(self, searchterm):
        """ find all playlists that have a name containing 'searchterm' """
        all_plists = self.get_all_playlists()
        all_matches = all_plists
        all_terms = searchterm.split(' ')
        for term in all_terms:
            all_matches = [
                p for p in all_matches
                if p['name'].lower().find(term.lower()) != -1
            ]
        return all_matches
Example #7
0
class GMusicWrapper:
    def __init__(self, username, password, logger=None):
        self._api = Mobileclient()
        self.logger = logger
        success = self._api.login(
            username, password,
            environ.get('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS))

        if not success:
            raise Exception("Unsuccessful login. Aborting!")

        # Populate our library
        self.library = {}
        self.indexing_thread = threading.Thread(target=self.index_library)
        self.indexing_thread.start()

    def _search(self, query_type, query):
        try:
            results = self._api.search(query)
        except CallFailure:
            return []

        hits_key = "%s_hits" % query_type

        if hits_key not in results:
            return []

        # Ugh, Google had to make this schema nonstandard...
        if query_type == 'song':
            query_type = 'track'

        return map(lambda x: x[query_type], results[hits_key])

    def is_indexing(self):
        return self.indexing_thread.is_alive()

    def index_library(self):
        """
        Downloads the a list of every track in a user's library and populates
        self.library with storeIds -> track definitions
        """
        self.logger.debug('Fetching library...')
        tracks = self.get_all_songs()

        for track in tracks:
            song_id = track['id']
            self.library[song_id] = track

        self.logger.debug('Done! Discovered %d tracks.' % len(self.library))

    def get_artist(self, name):
        """
        Fetches information about an artist given its name
        """
        search = self._search("artist", name)

        if len(search) == 0:
            return False

        return self._api.get_artist_info(search[0]['artistId'],
                                         max_top_tracks=100)

    def get_album(self, name, artist_name=None):
        if artist_name:
            name = "%s %s" % (name, artist_name)

        search = self._search("album", name)

        if len(search) == 0:
            return False

        return self._api.get_album_info(search[0]['albumId'])

    def get_song(self, name, artist_name=None):
        if artist_name:
            name = "%s %s" % (artist_name, name)

        search = self._search("song", name)

        if len(search) == 0:
            return False

        return search[0]

    def get_station(self, title, artist_id=None):
        if artist_id != None:
            return self._api.create_station(title, artist_id=artist_id)

    def get_station_tracks(self, station_id):
        return self._api.get_station_tracks(station_id)

    def get_google_stream_url(self, song_id):
        return self._api.get_stream_url(song_id)

    def get_stream_url(self, song_id):
        return "%s/stream/%s" % (environ['APP_URL'], song_id)

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

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

    def rate_song(self, song, rating):
        return self._api.rate_songs(song, rating)

    def extract_track_info(self, track):
        # When coming from a playlist, track info is nested under the "track"
        # key
        if 'track' in track:
            track = track['track']

        if 'storeId' in track:
            return (track, track['storeId'])
        elif 'trackId' in track:
            return (self.library[track['trackId']], track['trackId'])
        else:
            return (None, None)

    def increment_song_playcount(self, song_id, plays=1, playtime=None):
        return self._api.increment_song_playcount(song_id, plays, playtime)

    def get_song_data(self, song_id):
        return self._api.get_track_info(song_id)

    @classmethod
    def generate_api(cls, **kwargs):
        return cls(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'],
                   **kwargs)
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
        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']])
Example #9
0
class IBCMusicClient():
    # Please have this be an absolute path
    SONG_DIR = "/home/pi/Desktop/JukeSite/songs"

    def __init__(self):
        self.api = None
        self.player = None

    def start(self):
        """
        Starts the MobileClient
        """
        self.api = Mobileclient()

    def stop(self):
        """
        Deletes MobileClient and sets self.api to default(None)
        """
        del self.api
        self.api = None

    def logon(self, email, password):
        """
        Logs onto google music as a mobile client. Returns true is sucessful.

        :param email: Email of the Google user
        :param password: Pass of the google user
        :return: Bool if connection was successful
        """
        if self.api is None:
            raise errors.MobileClientNotInitError(
                "The Client has not been init therefor it cannot logon.", 1000)

        try:
            res = self.api.login(email, password, self.api.FROM_MAC_ADDRESS)
        except AlreadyLoggedIn as e:
            self.api.logout()
            res = self.api.login(email, password, self.api.FROM_MAC_ADDRESS)

        del email
        del password
        return res

    def logout(self):
        """
        logs out of google music mobile client.

        :return: if it was succesful
        """
        return self.api.logout()

    def is_authenticated(self):
        if not self.api.is_authenticated():
            raise errors.SessionNotActive(
                "The session is no longer active. Either it timedout or you have not logged in",
                1001)

    def search_song(self, query):
        """
        Searchs for the given query and return the song results
        Will check for authentication.

        [{
            'track': {
                'album': 'Work Out',
                'albumArtRef': [{
                    'aspectRatio': '1',
                    'autogen': False,
                    'kind': 'sj#imageRef',
                    'url': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fFdxj5w...'
                }],
                'albumArtist': 'J.Cole',
                'albumAvailableForPurchase': True,
                'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y',
                'artist': 'J Cole',
                'artistId': ['Ajgnxme45wcqqv44vykrleifpji', 'Ampniqsqcwxk7btbgh5ycujij5i'],
                'composer': '',
                'discNumber': 1,
                'durationMillis': '234000',
                'estimatedSize': '9368582',
                'explicitType': '1',
                'genre': 'Pop',
                'kind': 'sj#track',
                'nid': 'Tq3nsmzeumhilpegkimjcnbr6aq',
                'primaryVideo': {
                    'id': '6PN78PS_QsM',
                    'kind': 'sj#video',
                    'thumbnails': [{
                        'height': 180,
                        'url': 'https://i.ytimg.com/vi/6PN78PS_QsM/mqdefault.jpg',
                        'width': 320
                    }]
                },
                'storeId': 'Tq3nsmzeumhilpegkimjcnbr6aq',
                'title': 'Work Out',
                'trackAvailableForPurchase': True,
                'trackAvailableForSubscription': True,
                'trackNumber': 1,
                'trackType': '7',
                'year': 2011
            },
            'type': '1'
        }]
        :param query: The song query
        :return: [list] all the song hits
        """
        self.is_authenticated()
        res = self.api.search(query)
        songs = res['song_hits']
        return songs

    def search_album(self, query):
        """
        Searchs for the given query and returns the album results.
        Will check for authenitcation.

        e.g return:
        [{
            'album': {
                'albumArtRef': 'http://lh5.ggpht.com/DVIg4GiD6msHfgPs_Vu_2eRxCyAoz0fF...',
                'albumArtist': 'J.Cole',
                'albumId': 'Bfp2tuhynyqppnp6zennhmf6w3y',
                'artist': 'J.Cole',
                'artistId': ['Ajgnxme45wcqqv44vykrleifpji'],
                'description_attribution': {
                    'kind': 'sj#attribution',
                    'license_title': 'Creative Commons Attribution CC-BY',
                    'license_url': 'http://creativecommons.org/licenses/by/4.0/legalcode',
                    'source_title': 'Freebase',
                    'source_url': ''
                },
                'explicitType': '1',
                'kind': 'sj#album',
                'name': 'Work Out',
                'year': 2011
            },
            'type': '3'
        }]

        :param query: [string] The album query
        :return: [list] A list of all the album hits
        """
        self.is_authenticated()
        res = self.api.search(query)
        albums = res['album_hits']
        return albums

    def get_album_info(self, album_id):
        """
        Returns information about an album

        e.g return:
        {
            'kind': 'sj#album',
            'name': 'Circle',
            'artist': 'Amorphis',
            'albumArtRef': 'http://lh6.ggpht.com/...',
            'tracks': [  # if `include_tracks` is True
            {
                'album': 'Circle',
                'kind': 'sj#track',
                'storeId': 'T5zb7luo2vkroozmj57g2nljdsy',  # can be used as a song id
                'artist': 'Amorphis',
                'albumArtRef': [
                {
                    'url': 'http://lh6.ggpht.com/...'
                }],
                'title': 'Shades of Grey',
                'nid': 'T5zb7luo2vkroozmj57g2nljdsy',
                'estimatedSize': '13115591',
                'albumId': 'Bfr2onjv7g7tm4rzosewnnwxxyy',
                'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'],
                'albumArtist': 'Amorphis',
                'durationMillis': '327000',
                'composer': '',
                'genre': 'Metal',
                'trackNumber': 1,
                'discNumber': 1,
                'trackAvailableForPurchase': True,
                'trackType': '7',
                'albumAvailableForPurchase': True
            }, # ...
            ],
            'albumId': 'Bfr2onjv7g7tm4rzosewnnwxxyy',
            'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'],
            'albumArtist': 'Amorphis',
            'year': 2013
        }

        :param album_id: The albumId
        :return: Dictionary in the format above
        """
        self.is_authenticated()
        return self.api.get_album_info(album_id)

    def get_song_info(self, song_id):
        """
        Returns information about a song

        e.g return
        {
            'album': 'Best Of',
            'kind': 'sj#track',
            'storeId': 'Te2qokfjmhqxw4bnkswbfphzs4m',
            'artist': 'Amorphis',
            'albumArtRef': [
            {
                'url': 'http://lh5.ggpht.com/...'
            }],
            'title': 'Hopeless Days',
            'nid': 'Te2qokfjmhqxw4bnkswbfphzs4m',
            'estimatedSize': '12325643',
            'albumId': 'Bsbjjc24a5xutbutvbvg3h4y2k4',
            'artistId': ['Apoecs6off3y6k4h5nvqqos4b5e'],
            'albumArtist': 'Amorphis',
            'durationMillis': '308000',
            'composer': '',
            'genre': 'Metal',
            'trackNumber': 2,
            'discNumber': 1,
            'trackAvailableForPurchase': True,
            'trackType': '7',
            'albumAvailableForPurchase': True
        }

        :param song_id: The songds storeId
        :return: A dict with the above information
        """
        self.is_authenticated()
        return self.api.get_track_info(song_id)

    def get_song_url(self, song_id):
        self.is_authenticated()
        res = self.api.get_stream_url(song_id)
        return res

    def download_song(self, song_id):
        """
        Download the song from the storeId.

        :param song_id: the 'storeId' of the specific song
        """
        url = self.get_song_url(song_id)
        song_file_path = "{}/{}.mp3".format(IBCMusicClient.SONG_DIR, song_id)
        if os.path.isfile(song_file_path):
            raise errors.SongAlreadyDownloadedException(
                "The song '{}' has already been downloaded and cached".format(
                    song_file_path), 8002)
        # This need to not use subprocessing
        command = ['wget', url, '-O', song_file_path]
        res = check_output(command)
        lines = res.decode().split('\n')
        error_lines = [line for line in lines if 'failed' in line]

        if len(error_lines) > 0:
            # We have an error
            raise errors.CannotDownloadSongError(
                "Could not download the given song. {}".format(
                    str(error_lines)), 1003)
Example #10
0
class MusicLibrary(object):
    """This class reads information about your Google Play Music library"""
    def __init__(self,
                 username=None,
                 password=None,
                 true_file_size=False,
                 scan=True,
                 verbose=0):
        self.verbose = False
        if verbose > 1:
            self.verbose = True

        self.__login_and_setup(username, password)

        self.__artists = {}  # 'artist name' -> {'album name' : Album(), ...}
        self.__gartists = {}
        self.__albums = []  # [Album(), ...]
        self.__galbums = {}
        self.__tracks = {}
        self.__playlists = {}
        if scan:
            self.rescan()
        self.true_file_size = true_file_size

    def rescan(self):
        """Scan the Google Play Music library"""
        self.__artists = {}  # 'artist name' -> {'album name' : Album(), ...}
        self.__gartists = {}
        self.__albums = []  # [Album(), ...]
        self.__galbums = {}
        self.__tracks = {}
        self.__playlists = {}
        self.__aggregate_albums()

    def __login_and_setup(self, username=None, password=None):
        # If credentials are not specified, get them from $HOME/.gmusicfs
        if not username or not password:
            cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs')
            if not os.path.isfile(cred_path):
                raise NoCredentialException(
                    'No username/password was specified. No config file could '
                    'be found either. Try creating %s and specifying your '
                    'username/password there. Make sure to chmod 600.' %
                    cred_path)
            if not oct(
                    os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'):
                raise NoCredentialException(
                    'Config file is not protected. Please run: '
                    'chmod 600 %s' % cred_path)
            self.config = ConfigParser.ConfigParser()
            self.config.read(cred_path)
            username = self.config.get('credentials', 'username')
            password = self.config.get('credentials', 'password')
            global deviceId
            deviceId = self.config.get('credentials', 'deviceId')
            if not username or not password:
                raise NoCredentialException(
                    'No username/password could be read from config file'
                    ': %s' % cred_path)
            if not deviceId:
                raise NoCredentialException(
                    'No deviceId could be read from config file'
                    ': %s' % cred_path)
            if deviceId.startswith("0x"):
                deviceId = deviceId[2:]

        self.api = GoogleMusicAPI(debug_logging=self.verbose)
        log.info('Logging in...')
        self.api.login(username, password, deviceId)
        log.info('Login successful.')

    def __set_key_from_ginfo(self, track, ginfo, key, to_key=None):
        """Set track key from either album_info or artist_info"""
        if to_key is None:
            to_key = key

        try:
            int_key = int(key)
        except ValueError:
            int_key = None

        if (not track.has_key(key) or track[key] == ""
                or int_key == 0) and ginfo.has_key(to_key):
            track[key] = ginfo[to_key]

        return track

    def __aggregate_albums(self):
        """Get all the tracks and playlists in the library, parse into relevant dicts"""
        log.info('Gathering track information...')
        tracks = self.api.get_all_songs()
        for track in tracks:
            log.debug('track = %s' % pp.pformat(track))

            # Get album and artist information from Google
            if track.has_key('albumId'):
                if self.__galbums.has_key(track['albumId']):
                    album_info = self.__galbums[track['albumId']]
                else:
                    log.info("Downloading album info for %s '%s'",
                             track['albumId'], track['album'])
                    try:
                        album_info = self.__galbums[
                            track['albumId']] = self.api.get_album_info(
                                track['albumId'], include_tracks=False)
                    except gmusicapi.exceptions.CallFailure:
                        log.exception(
                            "Failed to download album info for %s '%s'",
                            track['albumId'], track['album'])
                        album_info = {}
                if album_info.has_key('artistId') and len(
                        album_info['artistId']
                ) > 0 and album_info['artistId'][0] != "":
                    artist_id = album_info['artistId'][0]
                    if self.__gartists.has_key(artist_id):
                        artist_info = self.__gartists[artist_id]
                    else:
                        log.info("Downloading artist info for %s '%s'",
                                 artist_id, album_info['albumArtist'])
                        #if album_info['albumArtist'] == "Various":
                        #    print album_info
                        artist_info = self.__gartists[
                            artist_id] = self.api.get_artist_info(
                                artist_id,
                                include_albums=False,
                                max_top_tracks=0,
                                max_rel_artist=0)
                else:
                    artist_info = {}
            else:
                album_info = {}
                artist_info = {}

            track = self.__set_key_from_ginfo(track, album_info, 'album',
                                              'name')
            track = self.__set_key_from_ginfo(track, album_info, 'year')
            track = self.__set_key_from_ginfo(track, artist_info,
                                              'albumArtist', 'name')

            # Prefer the album artist over the track artist if there is one
            artist_name = formatNames(track['albumArtist'])
            if artist_name.strip() == '':
                artist_name = formatNames(track['artist'])
            if artist_name.strip() == '':
                artist_name = 'Unknown'

            # Get the Artist object, or create one if it doesn't exist
            artist = self.__artists.get(artist_name.lower(), None)
            if not artist:
                artist = Artist(self, artist_name)
                self.__artists[artist_name.lower()] = artist

            # Get the Album object, or create one if it doesn't exist
            album = artist.get_album(formatNames(track['album']))
            if not album:
                album = Album(self, track['album'])
                self.__albums.append(
                    album)  # NOTE: Current no purpose other than to count
                artist.add_album(album)

            # Add track to album
            album.add_track(track)

            # Add track to list of all tracks, indexable by track ID
            if 'id' in track:
                self.__tracks[track['id']] = track

        log.info('%d tracks loaded.' % len(tracks))
        log.info('%d artists loaded.' % len(self.__artists))
        log.info('%d albums loaded.' % len(self.__albums))

        # Add all playlists
        playlists = self.api.get_all_user_playlist_contents()
        for pldata in playlists:
            playlist = Playlist(self, pldata)
            self.__playlists[playlist.dirname.lower()] = playlist
        log.debug('%d playlists loaded.' % len(self.__playlists))

    def get_artists(self):
        """Return all artists in the library"""
        return self.__artists

    def get_artist(self, name):
        """Return the artist from the library with the specified name"""
        return self.__artists.get(name.lower(), None)

    def get_playlists(self):
        """Return list of all playlists in the library"""
        return self.__playlists.values()

    def get_playlist(self, name):
        """Return the playlist from the library with the specified name"""
        return self.__playlists.get(name.lower(), None)

    def get_track(self, trackid):
        """Return the track from the library with the specified track ID"""
        return self.__tracks.get(trackid, None)

    def cleanup(self):
        pass
Example #11
0
class Session(object):
    def __init__(self):
        self.api = None
        self.user = None
        self.lib_albums = {}
        self.lib_artists = {}
        self.lib_tracks = {}
        self.lib_playlists = {}
        self.lib_updatetime = 0
        self.sitdata = []
        self.sitbyid = {}
        self.sitdataupdtime = 0
        
    def dmpdata(self, who, data):
        uplog("%s: %s" % (who, json.dumps(data, indent=4)))

    # Look for an Android device id in the registered devices.
    def find_device_id(self, data):
        for entry in data:
            if "type" in entry and entry["type"] == u"ANDROID":
                # Get rid of 0x
                id = entry["id"][2:]
                uplog("Using deviceid %s" % id)
                return id
        return None
    
    def login(self, username, password, deviceid=None):
        self.api = Mobileclient(debug_logging=False)

        if deviceid is None:
            logged_in = self.api.login(username, password,
                                       Mobileclient.FROM_MAC_ADDRESS)
            if logged_in:
                # Try to re-login with a valid deviceid
                data = self.api.get_registered_devices()
                #self.dmpdata("registered devices", data)
                deviceid = self.find_device_id(data)
                if deviceid:
                    logged_in = self.login(username, password, deviceid)
        else:
            logged_in = self.api.login(username, password, deviceid)

        isauth = self.api.is_authenticated()
        #uplog("login: Logged in: %s. Auth ok: %s" % (logged_in, isauth))
        return logged_in

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

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

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

    def get_user_playlist_tracks(self, playlist_id):
        self._get_user_library()
        # Unfortunately gmusic does not offer incremental updates for
        # playlists.  This means we must download all playlist data any
        # time we want an update.  Playlists include track information
        # for gmusic songs, so if a user has a lot of playlists this
        # can take some time.
        # For now, we only load the playlists one time for performance purposes
        if len(self.lib_playlists) == 0:
            data = self.api.get_all_user_playlist_contents()
            self.lib_playlists = dict([(pl['id'], pl) for pl in data])
        tracks = []
        if playlist_id in self.lib_playlists:
            for entry in self.lib_playlists[playlist_id]['tracks']:
                if entry['deleted']:
                    continue
                if entry['source'] == u'1':
                    tracks.append(self.lib_tracks[entry['trackId']])
                elif 'track' in entry:
                    tracks.append(_parse_track(entry['track']) )
        return tracks

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

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

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

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

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

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

        # Root is special, it's a list of situations
        if id is None:
            ret['situations'] = [self._parse_situation(s) \
                                 for s in self.sitdata]
            return ret
        
        # not root
        if id not in self.sitbyid:
            print("get_situation_content: %s unknown" % id, file=sys.stderr)
            return ret

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

        return ret

    def _parse_situation(self, data):
        self.sitbyid[data['id']] = data
        return Playlist(id=data['id'], name=data['title'])
        
    def create_curated_and_get_tracks(self, id):
        sid = self.api.create_station("station"+id, curated_station_id=id)
        print("create_curated: sid %s"%sid, file=sys.stderr)
        tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)]
        #print("curated tracks: %s"%tracks, file=sys.stderr)
        self.api.delete_stations(sid)
        return tracks
    
    def get_station_tracks(self, id):
        return [_parse_track(t) for t in self.api.get_station_tracks(id)]
    
    def get_media_url(self, song_id, quality=u'med'):
        url = self.api.get_stream_url(song_id, quality=quality)
        print("get_media_url got: %s" % url, file=sys.stderr)
        return url

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

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

    def get_genres(self, parent=None):
        data = self.api.get_genres(parent_genre_id=parent)
        return [_parse_genre(g) for g in data]
                
    def get_artist_info(self, artist_id, doRelated=False):
        ret = {"albums" : [], "toptracks" : [], "related" : []} 
        # Happens,some library tracks have no artistId entry
        if artist_id is None or artist_id == 'None':
            uplog("get_artist_albums: artist_id is None")
            return ret
        else:
            uplog("get_artist_albums: artist_id %s" % artist_id)

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

    def get_artist_related(self, artist_id):
        data = self.get_artist_info(artist_id, doRelated=True)
        return data["related"]
    
    def search(self, query):
        data = self.api.search(query, max_results=50)
        #self.dmpdata("Search", data)

        tr = [_parse_track(i['track']) for i in data['song_hits']]
        ar = [_parse_artist(i['artist']) for i in data['artist_hits']]
        al = [_parse_album(i['album']) for i in data['album_hits']]
        #self.dmpdata("Search playlists", data['playlist_hits'])
        try:
            pl = [_parse_splaylist(i) for i in data['playlist_hits']]
        except:
            pl = []
        return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
Example #12
0
class Session(object):
    def __init__(self):
        self.api = None
        self.user = None
        self.lib_albums = {}
        self.lib_artists = {}
        self.lib_tracks = {}
        self.lib_updatetime = 0
        self.sitdata = []
        self.sitbyid = {}
        self.sitdataupdtime = 0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return ret

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

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

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

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

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

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

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

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

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

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

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

        tr = [_parse_track(i["track"]) for i in data["song_hits"]]
        print("track ok", file=sys.stderr)
        ar = [_parse_artist(i["artist"]) for i in data["artist_hits"]]
        print("artist ok", file=sys.stderr)
        al = [_parse_album(i["album"]) for i in data["album_hits"]]
        print("album ok", file=sys.stderr)
        # self.dmpdata("Search playlists", data['playlist_hits'])
        try:
            pl = [_parse_splaylist(i) for i in data["playlist_hits"]]
        except:
            pl = []
        print("playlist ok", file=sys.stderr)
        return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr)
Example #13
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()
class Gmusicdownloader:
    _api = None
    outputDir = ""

    def __init__(self, email, password, deviceid, outputDir=os.getcwd()):
        self._api = Mobileclient(False)
        self.outputDir = outputDir
        print("Logging in...", end='', flush=True)
        self._api.login(email, password, deviceid)
        if self._api.is_authenticated():
            print(bColors.OKGREEN + " Logged in!" + bColors.ENDC)
        else:
            print(bColors.FAIL + " Unable to log in!" + bColors.ENDC)
            sys.exit(1)

    def escapeName(self, string):
        return re.sub('[<>:"/\\\|?*]|\.$', '', string)

    def tagTrack(self, path, track):
        audiofile = eyed3.load(path)
        audiofile.initTag()
        audiofile.tag.artist = track['artist']
        audiofile.tag.album = track['album']
        audiofile.tag.album_artist = track['albumArtist']
        audiofile.tag.title = track['title']
        audiofile.tag.track_num = track['trackNumber']
        if 'discNumber' in track:
            audiofile.tag.disc_num = track['discNumber']
        if 'year' in track:
            audiofile.tag.release_date = track['year']
        if 'genre' in track:
            audiofile.tag.genre = track['genre']
        audiofile.tag.save()

    def downloadCover(self, dirpath, album):
        if os.path.exists(dirpath + '/' + 'cover.jpg'):
            return
        resp = requests.get(album['albumArtRef'])
        with open(dirpath + '/' + 'cover.jpg', 'wb') as f:
            f.write(resp.content)

    def parseSelection(self, selection):
        try:
            items = []
            if selection.find(',') > 0:
                items += selection.split(',')
            else:
                items += [selection]
            for i in range(len(items)):
                item = items[i]
                if item.find('-') < 0:
                    continue
                rng = item.split('-')
                del items[i]
                items += [str(i) for i in range(int(rng[0]), int(rng[1]) + 1)]

            items = [int(i) for i in items]
            items.sort()
            return items
        except:
            raise ValueError('Invalid selection!')

    def downloadAlbum(self, album):
        dirpath = "{}/{}/{}".format(self.outputDir,
                                    self.escapeName(album['albumArtist']),
                                    self.escapeName(album['name']))
        dirpath = unidecode.unidecode(dirpath)
        if not os.path.isdir(dirpath):
            os.makedirs(dirpath)
        self.downloadCover(dirpath, album)

        #Iterate over album tracks
        for track in album['tracks']:
            trackId = track['nid']
            path = dirpath + "/{} {}.mp3".format(
                str(track['trackNumber']).zfill(2),
                self.escapeName(track['title']))
            path = unidecode.unidecode(path)
            if not os.path.exists(path):
                stream = self._api.get_stream_url(trackId)
                resp = requests.get(stream)
                with open(path, 'wb') as f:
                    f.write(resp.content)
                self.tagTrack(path, track)
                print("{}✔️  {} -- {} {}".format(bColors.OKGREEN,
                                                 track['artist'],
                                                 track['title'], bColors.ENDC))

    #Iterate over selected albums
    def downloadSelection(self, selection, search):
        for albumNum in self.parseSelection(selection):

            if albumNum > len(search['album_hits']):
                raise ValueError('Invalid index: ' + str(albumNum))
            album = self._api.get_album_info(
                search['album_hits'][albumNum - 1]['album']['albumId'])
            print()
            print(bColors.WARNING + 'Downloading ' + bColors.ENDC +
                  bColors.FAIL + album['albumArtist'] + ' -- ' + \
                  album['name'] + '...' + bColors.ENDC)
            self.downloadAlbum(album)

    def searchAndDownload(self):
        while True:
            print()
            #Album search
            searchStr = input(bColors.OKBLUE + bColors.BOLD +
                              "? Search for an album: " + bColors.ENDC)
            print()
            search = self._api.search(searchStr, 20)
            if len(search['album_hits']) == 0:
                print(bColors.FAIL + "Nothing found!" + bColors.ENDC)
                print()
                continue
            if 'album_hits' in search:
                albumCounter = 1
                for album in search['album_hits']:
                    album = album['album']
                    albumStr = "{}) {} -- {}".format(str(albumCounter),
                                                     album['albumArtist'],
                                                     album['name'])
                    if 'year' in album:
                        albumStr += " ({})".format(str(album['year']))
                    print(bColors.HEADER + albumStr + bColors.ENDC)
                    albumCounter += 1
                print()
                selection = input(
                    bColors.OKBLUE + bColors.BOLD +
                    '? Album to download (eg. 1, 2, 3.. or 1-3) or (b)ack or (e)xit: '
                    + bColors.ENDC)
                if selection == 'e':
                    sys.exit(0)
                elif selection == 'b':
                    continue
            self.downloadSelection(selection, search)

    def sync(self):
        library = self._api.get_all_songs()
        library = sorted(library,
                         key=lambda k:
                         (k['artist'], k['album'], k['trackNumber']))
        print()
        for track in library:

            dirpath = "{}/{}/{}".format(self.outputDir,
                                        self.escapeName(track['albumArtist']),
                                        self.escapeName(track['album']))
            dirpath = unidecode.unidecode(dirpath)
            if not os.path.isdir(dirpath):
                os.makedirs(dirpath)
            track['albumArtRef'] = track['albumArtRef'][0]['url']
            self.downloadCover(dirpath, track)
            trackId = track['nid']
            path = dirpath + "/{} {}.mp3".format(
                str(track['trackNumber']).zfill(2),
                self.escapeName(track['title']))
            path = unidecode.unidecode(path)
            if not os.path.exists(path):
                stream = self._api.get_stream_url(trackId)
                resp = requests.get(stream)
                with open(path, 'wb') as f:
                    f.write(resp.content)
                self.tagTrack(path, track)
                print("{}✔️  {} -- {} {}".format(bColors.OKGREEN,
                                                 track['artist'],
                                                 track['title'], bColors.ENDC))
Example #15
0
def normalizePath(input):
    return vfn(input, space="keep", initCap=False).decode('utf-8').rstrip(".")


login = sys.argv[1]
targetDir = os.getcwd()
albumId = sys.argv[2]
password = getpass.getpass()

eyed3.log.setLevel("ERROR")

api = Mobileclient(debug_logging=False)
api.login(login, password, Mobileclient.FROM_MAC_ADDRESS)

album = api.get_album_info(albumId)
dirName = normalizePath("%s - %s" % (album["artist"], album["name"]))
dirPath = targetDir + "/" + dirName

print("downloading to directory: " + dirPath)
if not os.path.exists(dirPath):
    os.makedirs(dirPath)

for song in album["tracks"]:
    url = api.get_stream_url(song_id=song["storeId"], quality="hi")
    fileName = normalizePath(
        "%s. %s - %s.mp3" %
        (song["trackNumber"], song["artist"], song["title"]))
    filePath = dirPath + "/" + fileName
    print("downloading: " + fileName)
    urlretrieve(url, filePath)
Example #16
0
class GoogleMusicApi:
    def __init__(self):
        self._api = Mobileclient()

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

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

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

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

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

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

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

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

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

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

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

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

    def increment_track_playcount(self, track_id):
        self._api.increment_song_playcount(track_id)
Example #17
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 #18
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 #19
0
class GMusicWrapper:
    def __init__(self, username, password):
        self._api = Mobileclient()
        success = self._api.login(username, password, Mobileclient.FROM_MAC_ADDRESS)

        if not success:
            raise Exception("Unsuccessful login. Aborting!")

    def _search(self, query_type, query):
        results = self._api.search(query)
        hits_key = "%s_hits" % query_type

        if hits_key not in results:
            return []

        # Ugh, Google had to make this schema nonstandard...
        if query_type == 'song':
            query_type = 'track'

        return map(lambda x: x[query_type], results[hits_key])

    def get_artist(self, name):
        search = self._search("artist", name)

        if len(search) == 0:
            return False

        return self._api.get_artist_info(search[0]['artistId'], max_top_tracks=100)

    def get_album(self, name, artist_name=None):
        if artist_name:
            name = "%s %s" % (name, artist_name)

        search = self._search("album", name)

        if len(search) == 0:
            return False

        return self._api.get_album_info(search[0]['albumId'])

    def get_song(self, name, artist_name=None):
        if artist_name:
            name = "%s %s" % (artist_name, name)

        search = self._search("song", name)

        if len(search) == 0:
            return False

        return search[0]

    def get_station(self, title, artist_id=None):
        if artist_id != None:
            return self._api.create_station(title, artist_id=artist_id)

    def get_station_tracks(self, station_id):
        return self._api.get_station_tracks(station_id)

    def get_google_stream_url(self, song_id):
        return self._api.get_stream_url(song_id)

    def get_stream_url(self, song_id):
        return "%s/stream/%s" % (environ['APP_URL'], song_id)

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

    @classmethod
    def generate_api(self):
        return self(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'])
Example #20
0
    try:
        for album in api.get_artist_info(artistID, True, 0, 0)['albums']:
            albumIDs.append(album['albumId'])
    except KeyError:
        print("No albums for " + artist)
        exit()
    except:
        print("Critical error for " + artist)
        print(error)
        exit()

# Use album IDs to get track IDs
trackIDs = []
for albumID in albumIDs:
    try:
        for track in api.get_album_info(albumID)['tracks']:
            trackIDs.append(track['storeId'])
    except KeyError:
        print(albumID + ' has no tracks')
    except HTTPError as error:
        print(error)
    except CallFailure as error:
        print(error)

# Download using get_url_stream
downDir = input('Enter download directory: ')
if not downDir.endswith('\\'): downDir += '\\'
totalTracks = len(trackIDs)
for i in range(totalTracks):
    try:
        trackInfo = api.get_track_info(trackIDs[0])
Example #21
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 #22
0
class GMusicWrapper(object):
    def __init__(self, username, password, logger=None):
        self._api = Mobileclient()
        self.logger = logger
        success = self._api.login(username, password, getenv('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS))

        if not success:
            raise Exception("Unsuccessful login. Aborting!")

        try:
            assert literal_eval(getenv("DEBUG_FORCE_LIBRARY", "False"))
            self.use_store = False
        except (AssertionError, ValueError):  # AssertionError if it's False, ValueError if it's not set / not set to a proper boolean string
            self.use_store = self._api.is_subscribed
        # Populate our library
        self.start_indexing()

    def start_indexing(self):
        self.library = {}
        self.albums = set([])
        self.artists = set([])
        self.indexing_thread = threading.Thread(
            target=self.index_library
        )
        self.indexing_thread.start()

    def log(self, log_str):
        if self.logger != None:
            self.logger.debug(log_str)

    def _search(self, query_type, query):
        try:
            results = self._api.search(query)
        except CallFailure:
            return []

        hits_key = "%s_hits" % query_type

        if hits_key not in results:
            return []

        # Ugh, Google had to make this schema nonstandard...
        if query_type == 'song':
            query_type = 'track'

        return [x[query_type] for x in results[hits_key]]

    def is_indexing(self):
        return self.indexing_thread.is_alive()

    def index_library(self):
        """
        Downloads the a list of every track in a user's library and populates
        self.library with storeIds -> track definitions
        """
        self.log('Fetching library...')

        tracks = self.get_all_songs()

        for track in tracks:
            song_id = track['id']
            self.library[song_id] = track
            self.albums.add(track['album'])
            self.artists.add(track['artist'])

        self.log('Fetching library complete.')

    def get_artist(self, name):
        """
        Fetches information about an artist given its name
        """
        if self.use_store:
            search = self._search("artist", name)

            if len(search) == 0:
                return False

            return self._api.get_artist_info(search[0]['artistId'],
                                             max_top_tracks=100)
        else:
            search = {}
            search['topTracks'] = []
            # Find the best artist we have, and then match songs to that artist
            likely_artist, score = process.extractOne(name, self.artists)
            if score < 70:
                return False
            for song_id, song in self.library.items():
                if 'artist' in song and song['artist'].lower() == likely_artist.lower() and 'artistId' in song:
                    if not search['topTracks']:  # First entry
                        # Copy artist details from the first song into the general artist response
                        try:
                            search['artistArtRef'] = song['artistArtRef'][0]['url']
                        except KeyError:
                            pass
                        search['name'] = song['artist']
                        search['artistId'] = song['artistId']
                    search['topTracks'].append(song)
            random.shuffle(search['topTracks'])  # This is all music, not top, but the user probably would prefer it shuffled.
            if not search['topTracks']:
                return False

            return search

    def get_album(self, name, artist_name=None):
        if self.use_store:
            if artist_name:
                name = "%s %s" % (name, artist_name)

            search = self._search("album", name)

            if len(search) == 0:
                return False

            return self._api.get_album_info(search[0]['albumId'])
        else:
            search = {}
            search['tracks'] = []
            if artist_name:
                artist_name, score = process.extractOne(artist_name, self.artists)
                if score < 70:
                    return False
            name, score = process.extractOne(name, self.albums)
            if score < 70:
                return False
            for song_id, song in self.library.items():
                if 'album' in song and song['album'].lower() == name.lower():
                    if not artist_name or ('artist' in song and song['artist'].lower() == artist_name.lower()):
                        if not search['tracks']:  # First entry
                            search['albumArtist'] = song['albumArtist']
                            search['name'] = song['album']
                            try:
                                search['albumId'] = song['albumId']
                            except KeyError:
                                pass

                        search['tracks'].append(song)
            if not search['tracks']:
                return False

            return search

    def get_latest_album(self, artist_name=None):
        search = self._search("artist", artist_name)

        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True)
        album_info = artist_info['albums']
        sorted_list = sorted(album_info.__iter__(), key=lambda s: s['year'], reverse=True)

        for index, val in enumerate(sorted_list):
            album_info = self._api.get_album_info(album_id=sorted_list[index]['albumId'], include_tracks=True)
            if len(album_info['tracks']) >= 5:
                return album_info

        return False

    def get_album_by_artist(self, artist_name, album_id=None):
        search = self._search("artist", artist_name)
        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True)
        album_info = artist_info['albums']
        random.shuffle(album_info)

        for index, val in enumerate(album_info):
            album = self._api.get_album_info(album_id=album_info[index]['albumId'], include_tracks=True)
            if album['albumId'] != album_id and len(album['tracks']) >= 5:
                return album

        return False

    def get_song(self, name, artist_name=None, album_name=None):
        if self.use_store:
            if artist_name:
                name = "%s %s" % (artist_name, name)
            elif album_name:
                name = "%s %s" % (album_name, name)

            search = self._search("song", name)

            if len(search) == 0:
                return False

            if album_name:
                for i in range(0, len(search) - 1):
                    if album_name in search[i]['album']:
                        return search[i]
            return search[0]
        else:
            search = {}
            if not name:
                return False
            if artist_name:
                artist_name, score = process.extractOne(artist_name, self.artists)
                if score < 70:
                    return False
            if album_name:
                album_name, score = process.extractOne(album_name, self.albums)
                if score < 70:
                    return False
            possible_songs = {song_id: song['title'] for song_id, song in self.library.items() if (not artist_name or ('artist' in song and song['artist'].lower() == artist_name.lower())) and (not album_name or ('album' in song and song['album'].lower() == album_name.lower()))}
            song, score, song_id = process.extractOne(name.lower(), possible_songs)
            if score < 70:
                return False
            else:
                return self.library[song_id]

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

    def get_station(self, title, track_id=None, artist_id=None, album_id=None):
        if artist_id is not None:
            if album_id is not None:
                if track_id is not None:
                    return self._api.create_station(title, track_id=track_id)
                return self._api.create_station(title, album_id=album_id)
            return self._api.create_station(title, artist_id=artist_id)

    def get_station_tracks(self, station_id):
        return self._api.get_station_tracks(station_id)

    def get_google_stream_url(self, song_id):
        return self._api.get_stream_url(song_id)

    def get_stream_url(self, song_id):
        return "%s/alexa/stream/%s" % (getenv('APP_URL'), song_id)

    def get_thumbnail(self, artist_art):
        return artist_art.replace("http://", "https://")

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

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

    def rate_song(self, song, rating):
        return self._api.rate_songs(song, rating)

    def extract_track_info(self, track):
        # When coming from a playlist, track info is nested under the "track"
        # key
        if 'track' in track:
            track = track['track']

        if self.use_store and 'storeId' in track:
            return track, track['storeId']
        elif 'id' in track:
            return self.library[track['id']], track['id']
        elif 'trackId' in track:
            return self.library[track['trackId']], track['trackId']
        else:
            return None, None

    def get_artist_album_list(self, artist_name):
        search = self._search("artist", artist_name)
        if len(search) == 0:
            return "Unable to find the artist you requested."

        artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True)
        album_list_text = "Here's the album listing for %s: " % artist_name

        counter = 0
        for index, val in enumerate(artist_info['albums']):
            if counter > 25:  # alexa will time out after 10 seconds if the list takes too long to iterate through
                break
            album_info = self._api.get_album_info(album_id=artist_info['albums'][index]['albumId'], include_tracks=True)
            if len(album_info['tracks']) > 5:
                counter += 1
                album_list_text += (artist_info['albums'][index]['name']) + ", "
        return album_list_text

    def get_latest_artist_albums(self, artist_name):
        search = self._search("artist", artist_name)

        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'], include_albums=True)
        album_list = artist_info['albums']

        sorted_list = sorted(album_list.__iter__(), key=lambda s: s['year'], reverse=True)

        speech_text = 'The latest albums by %s are ' % artist_name

        counter = 0
        for index, val in enumerate(sorted_list):
            if counter > 5:
                break
            else:
                album_info = self._api.get_album_info(album_id=sorted_list[index]['albumId'], include_tracks=True)
                if len(album_info['tracks']) >= 5:
                    counter += 1
                    album_name = sorted_list[index]['name']
                    album_year = sorted_list[index]['year']
                    speech_text += '%s, released in %d, ' % (album_name, album_year)

        return speech_text

    def closest_match(self, request_name, all_matches, artist_name='', minimum_score=70):
        # Give each match a score based on its similarity to the requested
        # name
        self.log('Finding closest match...')

        best_match = None

        request_name = request_name.lower() + artist_name.lower()
        scored_matches = []
        for i, match in enumerate(all_matches):
            try:
                name = match['name'].lower()
            except (KeyError, TypeError):
                i = match
                name = all_matches[match]['title'].lower()
                if artist_name != "":
                    name += all_matches[match]['artist'].lower()

            scored_matches.append({
                'index': i,
                'name': name,
                'score': fuzz.ratio(name, request_name)
            })

        sorted_matches = sorted(scored_matches, key=lambda a: a['score'], reverse=True)

        try:
            top_scoring = sorted_matches[0]
            # Make sure we have a decent match (the score is n where 0 <= n <= 100)
            if top_scoring['score'] >= minimum_score:
                best_match = all_matches[top_scoring['index']]
        except IndexError:
            pass

        self.log('Found %s...' % best_match)
        return best_match

    def get_genres(self, parent_genre_id=None):
        return self._api.get_genres(parent_genre_id)

    def increment_song_playcount(self, song_id, plays=1, playtime=None):
        return self._api.increment_song_playcount(song_id, plays, playtime)

    def get_song_data(self, song_id):
        return self._api.get_track_info(song_id)

    @classmethod
    def generate_api(cls, **kwargs):
        return cls(getenv('GOOGLE_EMAIL'), getenv('GOOGLE_PASSWORD'),
                   **kwargs)
Example #23
0
class GMusicWrapper(object):
    def __init__(self, username, password, logger=None):
        self._api = Mobileclient()
        self.logger = logger
        success = self._api.login(
            username, password,
            environ.get('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS))

        if not success:
            raise Exception("Unsuccessful login. Aborting!")

        # Populate our library
        self.library = {}
        self.indexing_thread = threading.Thread(target=self.index_library)
        self.indexing_thread.start()

    def populate_library(
        self
    ):  #TODO: Use this as a function to refresh the library with Alexa via voice commands.
        # Populate our library
        self.library = {}
        self.indexing_thread = threading.Thread(target=self.index_library)
        self.indexing_thread.start()

    def _search(self, query_type, query):
        try:
            results = self._api.search(query)
        except CallFailure:
            return []

        hits_key = "%s_hits" % query_type

        if hits_key not in results:
            return []

        # Ugh, Google had to make this schema nonstandard...
        if query_type == 'song':
            query_type = 'track'

        return [x[query_type] for x in results[hits_key]]

    def _search_library_for_first(self, query_type, query):
        #try searching the library instead of the api
        for trackid, trackdata in self.library.items():
            if query_type in trackdata:
                if query.lower() in trackdata[query_type].lower():
                    return trackdata
        return None

    def _search_library(self, query_type, query):
        #try searching the library instead of the api
        found = []
        for trackid, trackdata in self.library.items():
            if query_type in trackdata:
                if query.lower() in trackdata[query_type].lower():
                    found.append(trackdata)
        if not found:
            return None
        return found

    def is_indexing(self):
        return self.indexing_thread.is_alive()

    def index_library(self):
        """
        Downloads the a list of every track in a user's library and populates
        self.library with storeIds -> track definitions
        """
        self.logger.debug('Fetching library...')
        tracks = self.get_all_songs()

        for track in tracks:
            song_id = track['id']
            self.library[song_id] = track

        self.logger.debug('Done! Discovered %d tracks.' % len(self.library))

    def get_artist(self, name):
        """
        Fetches information about an artist given its name
        """
        search = self._search("artist", name)

        if len(search) == 0:
            search_lib = self._search_library("artist", name)
            if search_lib is not None:
                self.logger.debug(search_lib)
                return search_lib
            return False

        return self._api.get_artist_info(search[0]['artistId'],
                                         max_top_tracks=100)

    def get_album(self, name, artist_name=None):
        if artist_name:
            name = "%s %s" % (name, artist_name)

        search = self._search("album", name)

        if len(search) == 0:
            search_lib = self._search_library("album", name)
            if search_lib is not None:
                self.logger.debug(search_lib)
                return search_lib
            return False

        return self._api.get_album_info(search[0]['albumId'])

    def get_latest_album(self, artist_name=None):
        search = self._search("artist", artist_name)

        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_info = artist_info['albums']
        sorted_list = sorted(album_info.__iter__(),
                             key=lambda s: s['year'],
                             reverse=True)

        for index, val in enumerate(sorted_list):
            album_info = self._api.get_album_info(
                album_id=sorted_list[index]['albumId'], include_tracks=True)
            if len(album_info['tracks']) >= 5:
                return album_info

        return False

    def get_album_by_artist(self, artist_name, album_id=None):
        search = self._search("artist", artist_name)
        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_info = artist_info['albums']
        random.shuffle(album_info)

        for index, val in enumerate(album_info):
            album = self._api.get_album_info(
                album_id=album_info[index]['albumId'], include_tracks=True)
            if album['albumId'] != album_id and len(album['tracks']) >= 5:
                return album

        return False

    def get_song(self, song_name, artist_name=None, album_name=None):
        if artist_name:
            name = "%s %s" % (artist_name, song_name)
        elif album_name:
            name = "%s %s" % (album_name, song_name)

        self.logger.debug("get_song() : name: %s" % (name))

        search = self._search("song", name)

        self.logger.debug("result length: %d" % len(search))

        if len(search) == 0:
            search_lib = self._search_library_for_first("title", name)
            if search_lib is not None:
                return search_lib
            return False

        if album_name:
            for i in range(0, len(search) - 1):
                if album_name in search[i]['album']:
                    return search[i]
        return search[0]

    def get_station(self, title, track_id=None, artist_id=None, album_id=None):
        if artist_id is not None:
            if album_id is not None:
                if track_id is not None:
                    return self._api.create_station(title, track_id=track_id)
                return self._api.create_station(title, album_id=album_id)
            return self._api.create_station(title, artist_id=artist_id)

    def get_station_tracks(self, station_id):
        return self._api.get_station_tracks(station_id)

    def get_google_stream_url(self, song_id):
        return self._api.get_stream_url(song_id)

    def get_stream_url(self, song_id):
        return "%s/alexa/stream/%s" % (environ['APP_URL'], song_id)

    def get_thumbnail(self, artist_art):
        # return artist_art.replace("http://", "https://") //OLD
        artistArtKey = 'artistArtRef'
        albumArtKey = 'albumArtRef'
        if artist_art is None:
            return self.default_thumbnail()
        elif artistArtKey in artist_art:
            artist_art = artist_art[artistArtKey]
        elif albumArtKey in artist_art:
            artist_art = artist_art[albumArtKey]
        else:
            return self.default_thumbnail()
        if type(artist_art) is list:
            if type(artist_art[0]) is dict:
                artUrl = artist_art[0]['url']
        elif type(artist_art) is dict:
            artUrl = artist_art['url']
        else:
            artUrl = artist_art
        return self.urlReplaceWithSecureHttps(artUrl)

    def urlReplaceWithSecureHttps(self, url):
        return url.replace("http://", "https://")

    def default_thumbnail(self):
        return 'https://lh3.googleusercontent.com/gdBHEk-u3YRDtuCU3iDTQ52nZd1t4GPmldYaT26Jh6EhXgp1mlhQiuLFl4eXDAXzDig5'

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

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

    def rate_song(self, song, rating):
        return self._api.rate_songs(song, rating)

    def extract_track_info(self, track):
        # When coming from a playlist, track info is nested under the "track"
        # key
        if 'track' in track:
            track = track['track']

        if 'trackId' in track:
            return (self.library[track['trackId']], track['trackId'])

        if self.use_library_first():
            #Using free version track id first
            if 'id' in track:
                return (track, track['id'])
        if 'storeId' in track:
            return track, track['storeId']
        return (None, None)

    def get_artist_album_list(self, artist_name):
        search = self._search("artist", artist_name)
        if len(search) == 0:
            return "Unable to find the artist you requested."

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_list_text = "Here's the album listing for %s: " % artist_name

        counter = 0
        for index, val in enumerate(artist_info['albums']):
            if counter > 25:  # alexa will time out after 10 seconds if the list takes too long to iterate through
                break
            album_info = self._api.get_album_info(
                album_id=artist_info['albums'][index]['albumId'],
                include_tracks=True)
            if len(album_info['tracks']) > 5:
                counter += 1
                album_list_text += (
                    artist_info['albums'][index]['name']) + ", "
        return album_list_text

    def get_latest_artist_albums(self, artist_name):
        search = self._search("artist", artist_name)

        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_list = artist_info['albums']

        sorted_list = sorted(album_list.__iter__(),
                             key=lambda s: s['year'],
                             reverse=True)

        speech_text = 'The latest albums by %s are ' % artist_name

        counter = 0
        for index, val in enumerate(sorted_list):
            if counter > 5:
                break
            else:
                album_info = self._api.get_album_info(
                    album_id=sorted_list[index]['albumId'],
                    include_tracks=True)
                if len(album_info['tracks']) >= 5:
                    counter += 1
                    album_name = sorted_list[index]['name']
                    album_year = sorted_list[index]['year']
                    speech_text += '%s, released in %d, ' % (album_name,
                                                             album_year)

        return speech_text

    def closest_match(self,
                      request_name,
                      all_matches,
                      artist_name='',
                      minimum_score=70):
        # Give each match a score based on its similarity to the requested
        # name
        self.logger.debug("The artist name is " + str(artist_name))
        request_name = request_name.lower() + artist_name.lower()
        scored_matches = []
        for i, match in enumerate(all_matches):
            try:
                name = match['name'].lower()
            except (KeyError, TypeError):
                i = match
                name = all_matches[match]['title'].lower()
                if artist_name != "":
                    name += all_matches[match]['artist'].lower()

            scored_matches.append({
                'index': i,
                'name': name,
                'score': fuzz.ratio(name, request_name)
            })

        sorted_matches = sorted(scored_matches,
                                key=lambda a: a['score'],
                                reverse=True)
        top_scoring = sorted_matches[0]
        self.logger.debug("The top scoring match was: " + str(top_scoring))
        best_match = all_matches[top_scoring['index']]

        # Make sure the score is at least the min score value
        if top_scoring['score'] < minimum_score:
            return None

        return best_match

    def get_genres(self, parent_genre_id=None):
        return self._api.get_genres(parent_genre_id)

    def increment_song_playcount(self, song_id, plays=1, playtime=None):
        return self._api.increment_song_playcount(song_id, plays, playtime)

    def get_song_data(self, song_id):
        return self._api.get_track_info(song_id)

    def use_library_first(self):
        return environ['USE_LIBRARY_FIRST'].lower() == 'true'

    @classmethod
    def generate_api(cls, **kwargs):
        return cls(environ['GOOGLE_EMAIL'], environ['GOOGLE_PASSWORD'],
                   **kwargs)
Example #24
0
class MusicLibrary(object):
    """This class reads information about your Google Play Music library"""

    def __init__(self, username=None, password=None,
                 true_file_size=False, scan=True, verbose=0):
        self.verbose = False
        if verbose > 1:
            self.verbose = True

        self.__login_and_setup(username, password)

        self.__artists = {}  # 'artist name' -> {'album name' : Album(), ...}
        self.__gartists = {}
        self.__albums = []  # [Album(), ...]
        self.__galbums = {}
        self.__tracks = {}
        self.__playlists = {}
        if scan:
            self.rescan()
        self.true_file_size = true_file_size

    def rescan(self):
        """Scan the Google Play Music library"""
        self.__artists = {}  # 'artist name' -> {'album name' : Album(), ...}
        self.__gartists = {}
        self.__albums = []  # [Album(), ...]
        self.__galbums = {}
        self.__tracks = {}
        self.__playlists = {}
        self.__aggregate_albums()

    def __login_and_setup(self, username=None, password=None):
        # If credentials are not specified, get them from $HOME/.gmusicfs
        if not username or not password:
            cred_path = os.path.join(os.path.expanduser('~'), '.gmusicfs')
            if not os.path.isfile(cred_path):
                raise NoCredentialException(
                    'No username/password was specified. No config file could '
                    'be found either. Try creating %s and specifying your '
                    'username/password there. Make sure to chmod 600.'
                    % cred_path)
            if not oct(os.stat(cred_path)[os.path.stat.ST_MODE]).endswith('00'):
                raise NoCredentialException(
                    'Config file is not protected. Please run: '
                    'chmod 600 %s' % cred_path)
            self.config = ConfigParser.ConfigParser()
            self.config.read(cred_path)
            username = self.config.get('credentials', 'username')
            password = self.config.get('credentials', 'password')
            global deviceId
            deviceId = self.config.get('credentials', 'deviceId')
            if not username or not password:
                raise NoCredentialException(
                    'No username/password could be read from config file'
                    ': %s' % cred_path)
            if not deviceId:
                raise NoCredentialException(
                    'No deviceId could be read from config file'
                    ': %s' % cred_path)
            if deviceId.startswith("0x"):
                deviceId = deviceId[2:]

        self.api = GoogleMusicAPI(debug_logging=self.verbose)
        log.info('Logging in...')
        self.api.login(username, password, deviceId)
        log.info('Login successful.')

    def __set_key_from_ginfo(self, track, ginfo, key, to_key=None):
        """Set track key from either album_info or artist_info"""
        if to_key is None:
            to_key = key

        try:
            int_key = int(key)
        except ValueError:
            int_key = None

        if (not track.has_key(key) or track[key] == "" or int_key == 0) and ginfo.has_key(to_key):
            track[key] = ginfo[to_key]

        return track

    def __aggregate_albums(self):
        """Get all the tracks and playlists in the library, parse into relevant dicts"""
        log.info('Gathering track information...')
        tracks = self.api.get_all_songs()
        for track in tracks:
            log.debug('track = %s' % pp.pformat(track))

            # Get album and artist information from Google
            if track.has_key('albumId'):
                if self.__galbums.has_key(track['albumId']):
                    album_info = self.__galbums[track['albumId']]
                else:
                    log.info("Downloading album info for %s '%s'", track['albumId'], track['album'])
                    try:
                        album_info = self.__galbums[track['albumId']] = self.api.get_album_info(track['albumId'], include_tracks=False)
                    except gmusicapi.exceptions.CallFailure:
                        log.exception("Failed to download album info for %s '%s'", track['albumId'], track['album'])
                        #album_info = {}
                if album_info.has_key('artistId') and len(album_info['artistId']) > 0 and album_info['artistId'][0] != "":
                    artist_id = album_info['artistId'][0]
                    if self.__gartists.has_key(artist_id):
                        artist_info = self.__gartists[artist_id]
                    else:
                        log.info("Downloading artist info for %s '%s'", artist_id, album_info['albumArtist'])
                        #if album_info['albumArtist'] == "Various":
                        #    print album_info
                        artist_info = self.__gartists[artist_id] = self.api.get_artist_info(artist_id, include_albums=False, max_top_tracks=0, max_rel_artist=0)
                else:
                    artist_info = {}
            else:
                album_info = {}
                artist_info = {}

            track = self.__set_key_from_ginfo(track, album_info, 'album', 'name')
            track = self.__set_key_from_ginfo(track, album_info, 'year')
            track = self.__set_key_from_ginfo(track, artist_info, 'albumArtist', 'name')

            # Prefer the album artist over the track artist if there is one
            artist_name = formatNames(track['albumArtist'])
            if artist_name.strip() == '':
                artist_name = formatNames(track['artist'])
            if artist_name.strip() == '':
                artist_name = 'Unknown'

            # Get the Artist object, or create one if it doesn't exist
            artist = self.__artists.get(artist_name.lower(), None)
            if not artist:
                artist = Artist(self, artist_name)
                self.__artists[artist_name.lower()] = artist

            # Get the Album object, or create one if it doesn't exist
            album = artist.get_album(formatNames(track['album']))
            if not album:
                album = Album(self, track['album'])
                self.__albums.append(album)  # NOTE: Current no purpose other than to count
                artist.add_album(album)

            # Add track to album
            album.add_track(track)

            # Add track to list of all tracks, indexable by track ID
            if 'id' in track:
                self.__tracks[track['id']] = track

        log.info('%d tracks loaded.' % len(tracks))
        log.info('%d artists loaded.' % len(self.__artists))
        log.info('%d albums loaded.' % len(self.__albums))

        # Add all playlists
        playlists = self.api.get_all_user_playlist_contents()
        for pldata in playlists:
            playlist = Playlist(self, pldata)
            self.__playlists[playlist.dirname.lower()] = playlist
        log.debug('%d playlists loaded.' % len(self.__playlists))

    def get_artists(self):
        """Return all artists in the library"""
        return self.__artists

    def get_artist(self, name):
        """Return the artist from the library with the specified name"""
        return self.__artists.get(name.lower(), None)

    def get_playlists(self):
        """Return list of all playlists in the library"""
        return self.__playlists.values()

    def get_playlist(self, name):
        """Return the playlist from the library with the specified name"""
        return self.__playlists.get(name.lower(), None)

    def get_track(self, trackid):
        """Return the track from the library with the specified track ID"""
        return self.__tracks.get(trackid, None)

    def cleanup(self):
        pass
Example #25
0
class Player(object):
    def __init__(self, device_id):
        self.api = Mobileclient()
        self.api.logger.setLevel(logging.INFO)
        #print(utils.log_filepath)

        options = ["--aout=alsa", "-I dummy", "--fullscreen"]
        self.vlc = Instance(options)
        self.player = None

        self.loaded_tracks = []

        self.playing = False
        self.repeat = Repeat.none
        self.random = False
        self.song_index = 0
        self.now_playing_title = ""
        self.now_playing_artist = ""
        self.now_playing_playlist = ""

        # 取得したjsonの生データ
        self.song_library = []
        self.playlist_library = []
        # 整頓した楽曲ライブラリ
        self.songs = []
        self.albums = []
        self.playlists = []
        self.artists = []

        # play musicログイン
        if not os.path.exists(CREDENTIAL_FILE):
            self.api.perform_oauth(CREDENTIAL_FILE)
        self.api.oauth_login(device_id, CREDENTIAL_FILE)
        # 曲一覧読み込み
        if os.path.isfile(JSON_DIR + "songs.json"):
            # Load from file
            print("Found songs data.")
            with open(JSON_DIR + 'songs.json') as input_file:
                self.song_library = json.load(input_file)
        else:
            self.song_library = self.api.get_all_songs()
            # Save to file
            with open(JSON_DIR + 'songs.json', 'w') as output_file:
                json.dump(self.song_library, output_file)

        self.create_songs()
        self.create_albums()
        self.create_artists()

        # プレイリスト読み込み
        if os.path.isfile(JSON_DIR + "playlists.json"):
            # Load from file
            print("Found playlist data.")
            with open(JSON_DIR + 'playlists.json') as input_file:
                self.playlist_library = json.load(input_file)
        else:
            self.playlist_library = self.api.get_all_user_playlist_contents()
            # Save to file
            with open(JSON_DIR + 'playlists.json', 'w') as output_file:
                json.dump(self.playlist_library, output_file)
        #プレイリスト名編集
        self.create_playlists()

        # 定時ライブラリ更新処理
        t = threading.Timer(RELOAD_LIB_TIME, self.auto_reload)
        t.start()

    def auto_reload(self):
        while True:
            if not self.playing:
                break
            time.sleep(60)
        self.reload_library()
        print("[ music list auto reloaded ]")
        t = threading.Timer(RELOAD_LIB_TIME, self.auto_reload)
        t.start()

    def reload_library(self):
        # 曲一覧読み込み
        self.song_library = self.api.get_all_songs()
        # Save to file
        with open(JSON_DIR + 'songs.json', 'w') as output_file:
            json.dump(self.song_library, output_file)

        self.create_songs()
        self.create_albums()
        self.create_artists()
        # プレイリスト読み込み
        self.playlist_library = self.api.get_all_user_playlist_contents()
        # Save to file
        with open(JSON_DIR + 'playlists.json', 'w') as output_file:
            json.dump(self.playlist_library, output_file)
        #プレイリスト名編集
        self.create_playlists()

    def create_songs(self):
        self.songs = []
        # 曲名編集
        for index, song in enumerate(self.song_library):
            self.songs.append({})
            self.songs[index].update({"original_name": song['title']})
            self.songs[index].update(
                {"name": cir.convert_into_romaji(song['title'])})
            self.songs[index].update({"artist": song['artist']})
            self.songs[index].update({"trackId": song['id']})
            self.songs[index].update({"source": 1})
            #print(self.songs[index])
            #sleep(0.1)
        print("[ create_songs finished ]")

    def create_playlists(self):
        self.playlists = []
        #プレイリスト名編集
        for index, playlist in enumerate(self.playlist_library):
            self.playlists.append({})
            self.playlists[index].update({"original_name": playlist['name']})
            self.playlists[index].update(
                {"name": cir.convert_into_romaji(playlist['name'])})
            self.playlists[index].update({"tracks": playlist['tracks']})
            print(self.playlists[index]['name'])
        print("[ create_playlists finished ]")

    def create_albums(self):
        self.albums = []
        # アルバムリスト作成
        for song in self.song_library:
            album_found = False
            track = {}
            for index, album in enumerate(self.albums):
                # アルバムがすでに登録されていた場合
                if album['original_name'] == song['album']:
                    album_found = True
                    track.update({"trackId": song['id']})
                    track.update({"source": 1})
                    track.update({"trackNumber": song['trackNumber']})
                    self.albums[index]['tracks'].append(track)
                    #print(self.albums[index])
                    break
            if album_found:
                continue

            #新規アルバム作成
            albums_len = len(self.albums)
            self.albums.append({})
            self.albums[albums_len].update({"original_name": song['album']})
            self.albums[albums_len].update(
                {"name": cir.convert_into_romaji(song['album'])})

            track.update({"trackId": song['id']})
            track.update({"source": 1})
            track.update({"trackNumber": song['trackNumber']})
            self.albums[albums_len].update({"tracks": [track]})
            #print(self.albums[albums_len])
        # tracknumberでソート
        for album in self.albums:
            album['tracks'] = sorted(album['tracks'],
                                     key=lambda x: x['trackNumber'])
            print(album["name"])

        print("[ create_albums finished ]")

    def create_artists(self):
        self.artists = []
        # アーティストリスト作成
        for song in self.song_library:
            artist_found = False
            track = {}
            for index, artist in enumerate(self.artists):
                # アーティストがすでに登録されていた場合
                if artist['original_name'] == song['artist']:
                    artist_found = True
                    track.update({"trackId": song['id']})
                    track.update({"source": 1})
                    track.update({"trackNumber": song['trackNumber']})
                    self.artists[index]['tracks'].append(track)
                    break
            if artist_found:
                continue

            #新規アルバム作成
            artists_len = len(self.artists)
            self.artists.append({})
            self.artists[artists_len].update({"original_name": song['artist']})
            self.artists[artists_len].update(
                {"name": cir.convert_into_romaji(song['artist'])})

            track.update({"trackId": song['id']})
            track.update({"source": 1})
            track.update({"trackNumber": song['trackNumber']})
            self.artists[artists_len].update({"tracks": [track]})
            print(self.artists[artists_len]["name"])
        print("[ create_artists finished ]")

    def load_playlist(self, name):
        name = name.strip().lower()
        print("Looking for...", name)

        top_diff = 0.0
        top_playlist = {}
        # 検索
        for playlist_dict in self.playlists:
            playlist_name = playlist_dict['name'].strip().lower()
            diff = difflib.SequenceMatcher(None, playlist_name, name).ratio()

            if diff > top_diff:
                print("diff match...", playlist_dict['name'], ":", diff)
                top_playlist = playlist_dict
                top_diff = diff
            else:
                pass
                #print("Found...", playlist_dict['name'])
        # 一番マッチしたものを返す
        if top_diff > DIFF_ARGS:
            self.loaded_tracks = []
            print(top_diff)
            print("Found match...", top_playlist['name'])
            for track_dict in top_playlist['tracks']:
                self.loaded_tracks.append(track_dict)
            self.now_playing_playlist = top_playlist['original_name']
            return top_playlist['original_name']
        else:
            return None

    def load_song(self, name):
        name = name.strip().lower()
        print("Looking for...", name)

        top_diff = 0.0
        top_song = {}
        for song_dict in self.songs:
            song_name = song_dict['name'].strip().lower()
            diff = difflib.SequenceMatcher(None, song_name, name).ratio()
            #print(diff)
            if diff > top_diff:
                print("diff match...", song_dict['name'], ":", diff)
                top_song = song_dict
                top_diff = diff
            else:
                pass
                #print("Found...", song_dict['name'])
        # 一番マッチしたものを返す
        if top_diff > DIFF_ARGS:
            self.loaded_tracks = []
            print(top_diff)
            print("Found match...", top_song['name'])
            self.loaded_tracks.append(top_song)
            self.now_playing_playlist = ""
            return top_song['original_name']
        else:
            return None

    def load_album(self, name):
        name = name.strip().lower()
        print("Looking for...", name)

        top_diff = 0.0
        top_album = {}
        for album_dict in self.albums:
            album_name = album_dict['name'].strip().lower()
            diff = difflib.SequenceMatcher(None, album_name, name).ratio()
            #print(diff)
            if diff > top_diff:
                print("diff match...", album_dict['name'], ":", diff)
                top_album = album_dict
                top_diff = diff
            else:
                pass
                #print("Found...", album_dict['name'])
        # 一番マッチしたものを返す
        if top_diff > DIFF_ARGS:
            self.loaded_tracks = []
            print(top_diff)
            print("Found match...", top_album['name'])
            for track_dict in top_album['tracks']:
                self.loaded_tracks.append(track_dict)
            self.now_playing_playlist = top_album['original_name']
            return top_album['original_name']
        else:
            return None

    def load_artist(self, name):
        name = name.strip().lower()
        print("Looking for...", name)

        top_diff = 0.0
        top_artist = {}
        for artist_dict in self.artists:
            artist_name = artist_dict['name'].strip().lower()
            diff = difflib.SequenceMatcher(None, artist_name, name).ratio()
            #print(diff)
            if diff > top_diff:
                print("diff match...", artist_dict['name'], ":", diff)
                top_artist = artist_dict
                top_diff = diff
            else:
                pass
        # 一番マッチしたものを返す
        if top_diff > DIFF_ARGS:
            self.loaded_tracks = []
            print(top_diff)
            print("Found match...", top_artist['name'])
            for track_dict in top_artist['tracks']:
                self.loaded_tracks.append(track_dict)
            self.now_playing_playlist = top_artist['original_name']
            return top_artist['original_name']
        else:
            return None

    def load_cloud(self, name, isArtist=True, isSong=True, isAlbum=True):
        search = self.api.search(name)

        # アーティストのトップ曲を流す
        if search["artist_hits"] and isArtist:
            for index in range(len(search["artist_hits"])):
                artist_id = search["artist_hits"][index]["artist"]["artistId"]
                artist = self.api.get_artist_info(artist_id,
                                                  max_top_tracks=MAX_TRACK,
                                                  include_albums=False,
                                                  max_rel_artist=0)
                if "topTracks" in artist.keys():
                    break
            if "topTracks" in artist.keys():
                self.loaded_tracks = []
                for track_dict in artist["topTracks"]:
                    track_dict.update({"track": track_dict})
                    track_dict.update({"trackId": track_dict["storeId"]})
                    track_dict.update({"source": "2"})
                    self.loaded_tracks.append(track_dict)
                self.now_playing_playlist = ""
                return artist["name"]

        # 単曲を流す(複数にしたほうがいいかも)
        elif search["song_hits"] and isSong:
            self.loaded_tracks = []
            for index, track_dict in enumerate(search["song_hits"]):
                if index >= MAX_TRACK: break
                track_dict.update({"trackId": track_dict["track"]["storeId"]})
                track_dict.update({"source": "2"})
                self.loaded_tracks.append(track_dict)
            self.now_playing_playlist = ""
            return self.loaded_tracks[0]["track"]["title"]

        # アルバムを流す(正確さに欠ける)
        elif search["album_hits"] and isAlbum:
            album_id = search["album_hits"][0]["album"]["albumId"]
            album = self.api.get_album_info(album_id)
            self.loaded_tracks = []
            for track_dict in album["tracks"]:
                track_dict.update({"track": track_dict})
                track_dict.update({"trackId": track_dict["storeId"]})
                track_dict.update({"source": "2"})
                self.loaded_tracks.append(track_dict)
            self.now_playing_playlist = album["name"]
            return album["name"]

        # ステーション(ここまで回ってこない気が・・・)
        elif search["station_hits"]:
            pass
        return None

    def end_callback(self, event, track_index):
        # ランダム再生時処理
        if self.random:
            self.song_index = random.randint(0, len(self.loaded_tracks) - 1)
            self.play_song(self.loaded_tracks[self.song_index])
            event_manager = self.player.event_manager()
            event_manager.event_attach(EventType.MediaPlayerEndReached,
                                       self.end_callback, self.song_index + 1)
            return
        # 一曲リピート
        if self.repeat == Repeat.song:
            self.play_song(self.loaded_tracks[track_index - 1])
            event_manager = self.player.event_manager()
            event_manager.event_attach(EventType.MediaPlayerEndReached,
                                       self.end_callback, track_index)
            return
        # 通常再生・プレイリストリピート
        if track_index < len(self.loaded_tracks):
            self.song_index = track_index
            self.play_song(self.loaded_tracks[track_index])
            event_manager = self.player.event_manager()
            event_manager.event_attach(EventType.MediaPlayerEndReached,
                                       self.end_callback, track_index + 1)
        else:
            if self.repeat == Repeat.playlist:
                self.start_playlist()
            else:
                self.playing = False
                self.song_index = 0

    def start_playlist(self):
        if len(self.loaded_tracks) > 0:
            # ランダム再生時処理
            if self.random:
                self.song_index = random.randint(0,
                                                 len(self.loaded_tracks) - 1)
            else:
                # 通常再生
                self.song_index = 0
            self.play_song(self.loaded_tracks[self.song_index])
            event_manager = self.player.event_manager()
            event_manager.event_attach(EventType.MediaPlayerEndReached,
                                       self.end_callback, self.song_index + 1)
            return True
        return False

    def play_song(self, song_dict):
        stream_url = self.api.get_stream_url(song_dict['trackId'])
        self.player = self.vlc.media_player_new()
        media = self.vlc.media_new(stream_url)
        self.player.set_media(media)
        self.player.play()
        self.playing = True

        if (song_dict['source'] == '2'):
            self.now_playing_artist, self.now_playing_title = self.get_song_details(
                song_dict)
        else:
            self.now_playing_artist, self.now_playing_title = self.get_local_song_details(
                song_dict['trackId'])

        print("Playing...", self.now_playing_artist, " - ",
              self.now_playing_title)

    def stop(self):
        if self.player != None:
            self.player.stop()
            self.player = None
        self.playing = False
        self.repeat = Repeat.none
        self.random = False
        self.song_index = 0
        self.now_playing_title = ""
        self.now_playing_artist = ""

    def pause(self):
        if self.player == None:
            return False
        if self.playing == True:
            self.player.set_pause(1)
            self.playing = False
            return True
        return False

    def resume(self):
        if self.player == None:
            return False
        if self.playing == False:
            self.player.set_pause(0)
            self.playing = True
            return True
        return False

    def next(self):
        if self.player == None:
            return False
        # ランダム
        if self.random:
            self.song_index = random.randint(0, len(self.loaded_tracks) - 1)
            self.player.stop()
            self.play_song(self.loaded_tracks[self.song_index])
            event_manager = self.player.event_manager()
            event_manager.event_detach(EventType.MediaPlayerEndReached)
            event_manager.event_attach(EventType.MediaPlayerEndReached,
                                       self.end_callback, self.song_index + 1)
            return True
        # 通常
        if self.song_index + 1 < len(self.loaded_tracks):
            self.song_index += 1
            self.player.stop()
            self.play_song(self.loaded_tracks[self.song_index])
            event_manager = self.player.event_manager()
            event_manager.event_detach(EventType.MediaPlayerEndReached)
            event_manager.event_attach(EventType.MediaPlayerEndReached,
                                       self.end_callback, self.song_index + 1)
            return True
        else:
            if self.repeat == Repeat.playlist:
                self.start_playlist()
                return True

        return False

    def prev(self):
        if self.player == None:
            return False
        if self.song_index - 1 <= 0:
            self.song_index -= 1
            self.player.stop()
            self.play_song(self.loaded_tracks[self.song_index])
            event_manager = self.player.event_manager()
            event_manager.event_detach(EventType.MediaPlayerEndReached)
            event_manager.event_attach(EventType.MediaPlayerEndReached,
                                       self.end_callback, self.song_index + 1)
            return True
        return False

    def get_local_song_details(self, track_id):
        for song_dict in self.song_library:
            if track_id == song_dict['id']:
                return song_dict['artist'], song_dict['title']

    def get_song_details(self, song_dict):
        return song_dict['track']['albumArtist'], song_dict['track']['title']

    def set_volume(self, volume):
        if self.player:
            self.player.audio_set_volume(volume)
Example #26
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 #27
0
class GMusicWrapper(object):
    def __init__(self, username, password, logger=None):
        self._api = Mobileclient()
        self.logger = logger
        success = self._api.login(
            username, password,
            getenv('ANDROID_ID', Mobileclient.FROM_MAC_ADDRESS))

        if not success:
            raise Exception("Unsuccessful login. Aborting!")

        # Populate our library
        self.library = {}
        self.indexing_thread = threading.Thread(target=self.index_library)
        self.indexing_thread.start()

    def log(self, log_str):
        self.logger.debug(log_str)

    def _search(self, query_type, query):
        try:
            results = self._api.search(query)
        except CallFailure:
            return []

        hits_key = "%s_hits" % query_type

        if hits_key not in results:
            return []

        # Ugh, Google had to make this schema nonstandard...
        if query_type == 'song':
            query_type = 'track'

        return [x[query_type] for x in results[hits_key]]

    def is_indexing(self):
        return self.indexing_thread.is_alive()

    def index_library(self):
        """
        Downloads the a list of every track in a user's library and populates
        self.library with storeIds -> track definitions
        """
        self.log('Fetching library...')

        tracks = self.get_all_songs()

        for track in tracks:
            song_id = track['id']
            self.library[song_id] = track

        self.log('Fetching library...')

    def get_artist(self, name):
        """
        Fetches information about an artist given its name
        """
        search = self._search("artist", name)

        if len(search) == 0:
            return False

        return self._api.get_artist_info(search[0]['artistId'],
                                         max_top_tracks=100)

    def get_album(self, name, artist_name=None):
        if artist_name:
            name = "%s %s" % (name, artist_name)

        search = self._search("album", name)

        if len(search) == 0:
            return False

        return self._api.get_album_info(search[0]['albumId'])

    def get_latest_album(self, artist_name=None):
        search = self._search("artist", artist_name)

        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_info = artist_info['albums']
        sorted_list = sorted(album_info.__iter__(),
                             key=lambda s: s['year'],
                             reverse=True)

        for index, val in enumerate(sorted_list):
            album_info = self._api.get_album_info(
                album_id=sorted_list[index]['albumId'], include_tracks=True)
            if len(album_info['tracks']) >= 5:
                return album_info

        return False

    def get_album_by_artist(self, artist_name, album_id=None):
        search = self._search("artist", artist_name)
        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_info = artist_info['albums']
        random.shuffle(album_info)

        for index, val in enumerate(album_info):
            album = self._api.get_album_info(
                album_id=album_info[index]['albumId'], include_tracks=True)
            if album['albumId'] != album_id and len(album['tracks']) >= 5:
                return album

        return False

    def get_song(self, name, artist_name=None, album_name=None):
        if artist_name:
            name = "%s %s" % (artist_name, name)
        elif album_name:
            name = "%s %s" % (album_name, name)

        search = self._search("song", name)

        if len(search) == 0:
            return False

        if album_name:
            for i in range(0, len(search) - 1):
                if album_name in search[i]['album']:
                    return search[i]
        return search[0]

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

    def get_station(self, title, track_id=None, artist_id=None, album_id=None):
        if artist_id is not None:
            if album_id is not None:
                if track_id is not None:
                    return self._api.create_station(title, track_id=track_id)
                return self._api.create_station(title, album_id=album_id)
            return self._api.create_station(title, artist_id=artist_id)

    def get_station_tracks(self, station_id):
        return self._api.get_station_tracks(station_id)

    def get_google_stream_url(self, song_id):
        return self._api.get_stream_url(song_id)

    def get_stream_url(self, song_id):
        return "%s/alexa/stream/%s" % (getenv('APP_URL'), song_id)

    def get_thumbnail(self, artist_art):
        return artist_art.replace("http://", "https://")

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

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

    def rate_song(self, song, rating):
        return self._api.rate_songs(song, rating)

    def extract_track_info(self, track):
        # When coming from a playlist, track info is nested under the "track"
        # key
        if 'track' in track:
            track = track['track']

        if 'storeId' in track:
            return track, track['storeId']
        elif 'trackId' in track:
            return self.library[track['trackId']], track['trackId']
        else:
            return None, None

    def get_artist_album_list(self, artist_name):
        search = self._search("artist", artist_name)
        if len(search) == 0:
            return "Unable to find the artist you requested."

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_list_text = "Here's the album listing for %s: " % artist_name

        counter = 0
        for index, val in enumerate(artist_info['albums']):
            if counter > 25:  # alexa will time out after 10 seconds if the list takes too long to iterate through
                break
            album_info = self._api.get_album_info(
                album_id=artist_info['albums'][index]['albumId'],
                include_tracks=True)
            if len(album_info['tracks']) > 5:
                counter += 1
                album_list_text += (
                    artist_info['albums'][index]['name']) + ", "
        return album_list_text

    def get_latest_artist_albums(self, artist_name):
        search = self._search("artist", artist_name)

        if len(search) == 0:
            return False

        artist_info = self._api.get_artist_info(search[0]['artistId'],
                                                include_albums=True)
        album_list = artist_info['albums']

        sorted_list = sorted(album_list.__iter__(),
                             key=lambda s: s['year'],
                             reverse=True)

        speech_text = 'The latest albums by %s are ' % artist_name

        counter = 0
        for index, val in enumerate(sorted_list):
            if counter > 5:
                break
            else:
                album_info = self._api.get_album_info(
                    album_id=sorted_list[index]['albumId'],
                    include_tracks=True)
                if len(album_info['tracks']) >= 5:
                    counter += 1
                    album_name = sorted_list[index]['name']
                    album_year = sorted_list[index]['year']
                    speech_text += '%s, released in %d, ' % (album_name,
                                                             album_year)

        return speech_text

    def closest_match(self,
                      request_name,
                      all_matches,
                      artist_name='',
                      minimum_score=70):
        # Give each match a score based on its similarity to the requested
        # name
        self.log('Fetching library...')

        request_name = request_name.lower() + artist_name.lower()
        scored_matches = []
        for i, match in enumerate(all_matches):
            try:
                name = match['name'].lower()
            except (KeyError, TypeError):
                i = match
                name = all_matches[match]['title'].lower()
                if artist_name != "":
                    name += all_matches[match]['artist'].lower()

            scored_matches.append({
                'index': i,
                'name': name,
                'score': fuzz.ratio(name, request_name)
            })

        sorted_matches = sorted(scored_matches,
                                key=lambda a: a['score'],
                                reverse=True)
        top_scoring = sorted_matches[0]
        self.log('Fetching library...')

        best_match = all_matches[top_scoring['index']]

        # Make sure we have a decent match (the score is n where 0 <= n <= 100)
        if top_scoring['score'] < minimum_score:
            return None

        return best_match

    def get_genres(self, parent_genre_id=None):
        return self._api.get_genres(parent_genre_id)

    def increment_song_playcount(self, song_id, plays=1, playtime=None):
        return self._api.increment_song_playcount(song_id, plays, playtime)

    def get_song_data(self, song_id):
        return self._api.get_track_info(song_id)

    @classmethod
    def generate_api(cls, **kwargs):
        return cls(getenv('GOOGLE_EMAIL'), getenv('GOOGLE_PASSWORD'), **kwargs)
class GoogleMusicClient(object):

  def __init__(self, username, password):
    self._username = username
    self._password = password
    self._apiClient = None

  def initConnection(self):
    self._apiClient = Mobileclient(debug_logging=False)
    if not self._apiClient.login(self._username, self._password):
      raise RuntimeError("Could not connect %s to Google Music." % \
                          self._username)

  def addPlaylist(self, playlist):
    plid = self._apiClient.create_playlist(playlist.name)
    tids = []
    for track in playlist.tracks:
      aaid = self._getAllAccessTrackId(track)
      if aaid:
        try:
          tid = self._apiClient.add_aa_track(aaid)
          if tid:
            tids.append(tid)
          else:
            LOGGER.warning( "Could not add track %s to library.", str(track))
        except:
          LOGGER.error("Could not add track %s to library.", str(track))
          continue
      else:
        LOGGER.warning("Track %s not found.", str(track))
    self._apiClient.add_songs_to_playlist(plid, tids)

  def addAlbum(self, album):
    aaid = self._getAllAccessAlbumId(album)
    if aaid:
      albumInfo = self._apiClient.get_album_info(aaid, include_tracks=True)
      for track in albumInfo["tracks"]:
        try:
          self._apiClient.add_aa_track(track[Track.GM_ID_KEY])
        except:
          LOGGER.error("Could not add track %s to library.", str(track))
          continue
    else:
      LOGGER.warning("Album %s not found.", str(album))

  def _getAllAccessFirstResult(self, resource):
    queryStr = re.sub("[-:\(\)\",]","", "%s %s" % (resource.name,
                                                   resource.artist))
    queryStr = re.sub("\s+", "+", queryStr)
    searchResults = self._apiClient.search_all_access(queryStr)
    gmusicResources = searchResults.get(resource.GM_HITS_KEY)
    if gmusicResources:
      firstResult = gmusicResources[0][resource.GM_NAME]
      return firstResult[resource.GM_ID_KEY]
    else:
      return None

  def _getAllAccessTrackId(self, track):
    return self._getAllAccessFirstResult(track)

  def _getAllAccessAlbumId(self, album):
    return self._getAllAccessFirstResult(album)