예제 #1
0
    if sys.argv[1] == "-l":
        # load to playlist mode
        playlists = api.get_all_user_playlist_contents()
        # find prevously created playlist with our name
        playlist = None
        for pl in playlists:
            if pl["name"] == playlist_name:
                playlist = pl
                break
        # playlist not found, create it
        if not playlist:
            debug(f"playlist {playlist_name} not found, creating it...")
            id = api.create_playlist(playlist_name)
            debug(f"created playlist, id {id}")
            playlists = api.get_all_playlists()
            for pl in playlists:
                if pl["id"] == id:
                    playlist = pl
                    break
        if not playlist:
            error(f"Internal error: failed to find or create playlist {playlist_name}")
            sys.exit(1)
        playlists = None

        debug(f"found target playlist")

        # get tracks of playlist
        tracks_in_playlist = {}
        if "tracks" in playlist:
            for track in playlist["tracks"]:
예제 #2
0
class MusicProviderGoogle(MusicproviderBase):
    music_service = 'gmusic'
    gmusic_client_id = gmusic_client_id
    api = None
    station_current = None  # current station
    station_recently_played = None  # list of tracks already seen from current station
    station_current_tracks = None  # list of tracks got last time from station
    station_current_unseen = None  # count of tracks in current tracks list that were unseen in this playback session
    station_now_playing = None  # index of 'currently playing' track (points to track returned by last station_get_next_track call)

    def login(self):
        # log in to google music suppressing it's debugging messages
        oldloglevel = logging.getLogger().level
        logging.getLogger().setLevel(logging.ERROR)
        self.api = Mobileclient(debug_logging=False)
        logging.getLogger("gmusicapi.Mobileclient1").setLevel(logging.WARNING)
        rv = self.api.oauth_login(self.gmusic_client_id)
        logging.getLogger().setLevel(oldloglevel)
        if not rv:
            raise ExBpmCrawlGeneric(
                f"Please login to Google Music first (run gmusic-login.py)")

    def get_playlist(self, playlist_id_uri_name):
        if re.match('^https?://', playlist_id_uri_name, re.I):
            # this is uri of shared playlist
            try:
                self.api.get_shared_playlist_contents(
                    urllib.parse.unquote(playlist_id_uri_name))
            except Exception as e:
                debug(f"{self.whoami()}: got exception:", exc_info=True)
                raise ExBpmCrawlGeneric(
                    f"failed to get shared playlist: {e}, enable debug for more"
                )
        # find previously created playlist with specified name
        playlists = self.api.get_all_user_playlist_contents()
        playlist = None
        for pl in playlists:
            if pl["name"] == playlist_id_uri_name:
                playlist = pl
                break
        return playlist

    def get_or_create_my_playlist(self, playlist_name):
        playlist = self.get_playlist(playlist_name)
        if not playlist:
            debug(
                f"{whoami}: playlist {playlist_name} not found, creating it..."
            )
            id = self.api.create_playlist(playlist_name)
            debug(f"{whoami}: created playlist, id {id}")
            playlists = self.api.get_all_playlists()
            for pl in playlists:
                if pl["id"] == id:
                    playlist = pl
                    break
        if not playlist:
            raise ExBpmCrawlGeneric(
                f"Failed to find or create playlist {playlist_name}")
        return playlist

    def get_playlist_tracks(self, playlist):
        # get track ids of playlist
        tracks_in_playlist = []
        if "tracks" in playlist:
            for track in playlist["tracks"]:
                if "track" in track:  # it is google play music's track, not some local crap
                    tracks_in_playlist.append(track["track"])
        return tracks_in_playlist

    def add_track_to_playlist(self, playlist, track_id):
        added = self.api.add_songs_to_playlist(playlist["id"], track_id)
        if len(added):
            return True
        else:
            return False

    def get_station_from_url(self, url):
        if url == "IFL":
            return {"id": "IFL", "name": "I'm Feeling Lucky"}
        station_id_str = urllib.parse.unquote(url).rsplit("/", 1)[-1].split(
            "?", 1)[0].split('#', 1)[0]
        stations = self.api.get_all_stations()
        for station in stations:
            if 'seed' in station and 'curatedStationId' in station['seed']:
                if station['seed']['curatedStationId'] == station_id_str:
                    debug(f"{whoami()}: found station {station['id']}")
                    return station
        raise ExBpmCrawlGeneric(
            f"Failed to find station by string '{station_id_str}' (from url '{url}')"
        )

    def get_station_name(self, station):
        return station['name']

    def station_prepare(self, station):
        self.station_recently_played = []
        self.station_current_tracks = None
        self.station_current_unseen = 0
        self.station_now_playing = None
        self.station_current = station

    def station_get_next_track(self):
        need_new_tracks = False
        if self.station_current_tracks is None:
            need_new_tracks = True
        else:
            while not need_new_tracks:
                self.station_now_playing += 1
                if self.station_now_playing >= len(
                        self.station_current_tracks):
                    if self.station_current_unseen == 0:
                        # all played tracks already seen, so probably we've seen all tracks of station, let's stop it
                        debug(
                            f"{self.whoami()}: all played tracks were already seen, stopping this playback cycle"
                        )
                        return None
                    else:
                        need_new_tracks = True
                else:
                    track = self.station_current_tracks[
                        self.station_now_playing]
                    track_id = self.get_track_id(track)
                    if track_id in self.station_recently_played:
                        # try to get next track
                        debug(
                            f"{self.whoami()}: (level 1) skipping already seen track {track_id}"
                        )
                        continue
                    else:
                        self.station_current_unseen += 1
                        self.station_recently_played.append(track_id)
                        debug(
                            f"{self.whoami()}: (level 1) returning next track {track_id}, station_current_unseen=={self.station_current_unseen}"
                        )
                        return track
        # here we are only if we need more tracks from station
        debug(f"{self.whoami()}: getting new set of tracks")
        self.station_current_tracks = self.api.get_station_tracks(
            self.station_current['id'],
            num_tracks=25,
            recently_played_ids=self.station_recently_played)
        self.station_current_unseen = 0
        self.station_now_playing = 0
        if not self.station_current_tracks or len(
                self.station_current_tracks) == 0:
            debug(
                f"{self.whoami()}: got no tracks, stopping this playback cycle"
            )
            return None
        debug(
            f"{self.whoami()}: got {len(self.station_current_tracks)} tracks")

        while not (self.station_now_playing >= len(
                self.station_current_tracks)):
            track = self.station_current_tracks[self.station_now_playing]
            track_id = self.get_track_id(track)
            if track_id in self.station_recently_played:
                # try to get next track
                debug(
                    f"{self.whoami()}: (level 2) skipping already seen track {track_id}"
                )
                self.station_now_playing += 1
                continue
            else:
                self.station_current_unseen += 1
                self.station_recently_played.append(track_id)
                debug(
                    f"{self.whoami()}: (level 2) returning next track {track_id}, station_current_unseen=={self.station_current_unseen}"
                )
                return track
        debug(
            f"{self.whoami()}: (level 2) reached end of list, stopping this playback cycle"
        )
        return None

    def get_track_id(self, track):
        return track['storeId']

    def download_track(self, track):
        file = tempfile.NamedTemporaryFile(mode='w+b',
                                           dir=temp_dir,
                                           prefix='track',
                                           suffix='.mp3')
        stream_url = self.api.get_stream_url(self.get_track_id(track),
                                             quality='low')
        with requests.get(stream_url, stream=True) as r:
            r.raise_for_status()
            for chunk in r.iter_content(chunk_size=8192):
                # If you have chunk encoded response uncomment if
                # and set chunk_size parameter to None.
                # if chunk:
                file.write(chunk)
        file.flush()
        return file

    def get_track_id(self, track):
        return track['storeId']

    def calc_bpm_histogram(self, track):
        file = self.download_track(track)
        debug(
            f"{self.whoami()}: got track {self.get_track_id(track)} to {file.name}"
        )
        histogram = calc_file_bpm_histogram(file.name)
        file.close()
        return histogram
예제 #3
0
class PlaylistSync:

    def __init__(self, root, playlist_name):
        self.root = root
        self.playlist_name = playlist_name
 
    def _login_mc(self):
        APP_NAME = 'gmusic-sync-playlist'
        CONFIG_FILE = 'auth.cfg'

        config = SafeConfigParser({
            'username': '',
            'device_id': ''
        })
        config.read(CONFIG_FILE)
        if not config.has_section('auth'):
            config.add_section('auth')
    
        username = config.get('auth','username')
        password = None
        if username != '':
            password = keyring.get_password(APP_NAME, username)
    
        if password == None or not self.mc.login(username, password):
            while 1:
                username = raw_input("Username: "******"Password: "******"Sign-on failed."
    
            config.set('auth', 'username', username)
            with open(CONFIG_FILE, 'wb') as f:
                config.write(f)
    
            keyring.set_password(APP_NAME, username, password)


        device_id = config.get('auth', 'device_id')

        if device_id == '':
            wc = Webclient()
            if not wc.login(username, password):
                raise Exception('could not log in via Webclient')
            devices = wc.get_registered_devices()
            mobile_devices = [d for d in devices if d[u'type'] in (u'PHONE', u'IOS')]
            if len(mobile_devices) < 1:
                raise Exception('could not find any registered mobile devices')
            device_id = mobile_devices[0][u'id']
            if device_id.startswith(u'0x'):
                device_id = device_id[2:]
            
            config.set('auth', 'device_id', device_id)
            with open(CONFIG_FILE, 'wb') as f:
                config.write(f)

        print('Device ID: {}'.format(device_id))
        self.mc.device_id = device_id


    def login(self):
        self.mc = Mobileclient()
        self._login_mc()
        self.mm = Musicmanager()
        #self.mm.perform_oauth()
        self.mm.login()

    def track_file_name(self, track):
        if 'albumArtist' in track:
            albumartist = track['albumArtist']
        else:
            albumartist = 'Various'
        if not albumartist:
            albumartist = 'Various'
        file_name = escape_path(u'{trackNumber:02d} {title}.mp3'.format(**track))
        if track.get('totalDiscCount', 1) > 1:
            file_name = u'{discNumber}-'.format(**track) + file_name
        return unicodedata.normalize('NFD', os.path.join(self.root, escape_path(albumartist), escape_path(track['album']), file_name))

    def get_local_tracks(self):
        # return (metadata, file_name) of all files in root
        tracks = []
        for root, dirs, files in os.walk(self.root):
            for f in files:
                if os.path.splitext(f)[1].lower() == '.mp3':
                    file_name = os.path.join(root, f)
                    #id3 = EasyID3(file_name)
                    track = {}
                    #track = {
                    #  'name': id3['title'],
                    #  'album': id3['album'],
                    #  'track': id3['tracknumber'],
                    #  'disc': id3['discnumber']
                    #}
                    yield unicodedata.normalize('NFD', file_name.decode('utf-8')), track

    def get_playlist_tracks(self):
        # return (metadata, local_file_name) for each track in playlist
        all_playlists = self.mc.get_all_playlists()
        try:
            playlist = next(p for p in all_playlists if p['name'] == self.playlist_name)
        except StopIteration:
            raise Exception('playlist "{0}" not found'.format(self.playlist_name))
        contents = self.mc.get_shared_playlist_contents(playlist['shareToken'])
        for t in contents:
            track = t[u'track']
            #pprint(track)
            #raw_input()
            yield (self.track_file_name(track), track)

        #for p in all_playlists:
        #    shared = self.mc.get_shared_playlist_contents(p['shareToken'])
        #    pprint(shared)
        #for p in self.mc.get_all_user_playlist_contents():
        #    del p['tracks']
        #    pprint(p)
        #    raw_input()

        return

        all_songs = self.mc.get_all_songs()
        pprint(all_songs[0])
        for p in self.mc.get_all_user_playlist_contents():
            if p['name'] == self.playlist_name:
                for track in p['tracks']:
                    song = next(s for s in all_songs if s['id'] == track['trackId'])
                    print(u'{album} - {title}'.format(**song))
                    #pprint(song)
                    yield self.track_file_name(song), song

    def add_track(self, track, file_name):
        # download track from gmusic, write to file_name
        if not os.path.exists(os.path.dirname(file_name)):
            os.makedirs(os.path.dirname(file_name))
        if track[u'kind'] != u'sj#track' or u'id' not in track:
            if u'id' not in track:
                track[u'id'] = track[u'storeId']
            url = self.mc.get_stream_url(track[u'id'], self.mc.device_id)
            r = requests.get(url)
            data = r.content
            #data = self.wc.get_stream_audio(track['id'])
            with open(file_name, 'wb') as f:
                f.write(data)
            _copy_track_metadata(file_name, track)
        else:
            fn, audio = self.mm.download_song(track['id'])
            with open(file_name, 'wb') as f:
                f.write(audio)
        
    def remove_track(self, file_name):
        """Removes the track and walks up the tree deleting empty folders
        """
        os.remove(file_name)
        rel = os.path.relpath(file_name, self.root)
        dirs = os.path.split(rel)[0:-1]
        for i in xrange(1, len(dirs) + 1):
            dir_path = os.path.join(self.root, *dirs[0:i])
            if not os.listdir(dir_path):
                os.unlink(dir_path)


    def sync(self, confirm=True, remove=False):
        print 'Searching for local tracks ...'
        local = dict(self.get_local_tracks())
        print 'Getting playlist ...'
        playlist = OrderedDict(self.get_playlist_tracks())

        to_add = []
        to_remove = []
        to_rename = []

        for file_name, track in playlist.iteritems():
            if file_name not in local and file_name.encode('ascii', 'replace').replace('?','_') not in local:
                to_add.append((track, file_name))
            elif file_name not in local and file_name.encode('ascii', 'replace').replace('?','_') in local:
                to_rename.append((file_name.encode('ascii', 'replace').replace('?','_'), file_name))

        if remove:
            for file_name, track in sorted(local.iteritems()):
                if file_name not in playlist:
                    to_remove.append((track, file_name))

        if to_remove:
            print 'Deleting tracks:'
            for track, file_name in to_remove:
                print '  ' + file_name
            print ''
        if to_add:
            to_add = list(reversed(to_add))
            print 'Adding tracks:'
            for track, file_name in to_add:
                print '  ' + file_name
            print ''
        if to_rename:
            print 'Renaming tracks:'
            for src, dst in to_rename:
                print '  {0} to {1}'.format(src, dst)
            print ''
        if not (to_add or to_remove):
            print 'Nothing to do.'
            print ''

        if confirm:
            raw_input('Press enter to proceed')

        for src, dst in to_rename:
            if not os.path.exists(os.path.dirname(dst)):
                os.makedirs(os.path.dirname(dst))
            shutil.move(src, dst)
        for track, file_name in to_remove:
            print 'removing track ' + file_name
            self.remove_track(file_name)
        for track, file_name in to_add:
            print u'adding track: {album} / \n  {title}'.format(**track).encode('utf-8', 'replace')
            self.add_track(track, file_name)