예제 #1
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