예제 #1
0
        # get tracks of playlist
        tracks_in_playlist = {}
        if "tracks" in playlist:
            for track in playlist["tracks"]:
                if "track" in track:  # it is play music's track
                    tracks_in_playlist[track["track"]["storeId"]] = True
        debug(f"{len(tracks_in_playlist)} unique tracks found in playlist")

        with SqliteDict(cache_db, autocommit=True) as cache:
            for entry in [json.loads(cache[x]) for x in cache]:
                for track_id in entry["tracks"]:
                    if track_id in tracks_in_playlist:
                        debug(f"track {track_id} is already in playlist")
                    else:
                        debug(f"adding track {track_id} to playlist")
                        added = api.add_songs_to_playlist(playlist["id"], track_id)
                        if len(added):
                            debug(f"added {len(added)} track(s) to playlist")
                        else:
                            error(f"failed to add track {id} to playlist (no reason given)")

    # load to playlist mode ends
    else:
        # file import mode
        for dirname in sys.argv[1:]:

            os.chdir(dirname)

            for root, dirs, files in os.walk(u"."):
                for orig_filename in files:
                    if not orig_filename.lower().endswith(".mp3"):
예제 #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 GoogleClient(object):
    def __init__(self):

        self.api = Mobileclient()

    def _get_playlist_id(self):
        """
        Gets user input for the playlist URL and attempts to parse it
        to get the Playlist ID
        """
        playlist_shared_url = input(
            "Enter the shared URL for the Google playlist:  ")
        try:
            playlist_id = parse.unquote(
                playlist_shared_url.split('playlist/')[1])
        except:
            playlist_id = None
        return playlist_id

    def _search_for_track(self, album, artist, track_name):
        """
        Searches Google for a track matching the provided album, artist, and name
        Returns the track ID if found, else None
        """
        query = track_name + ',' + artist + ',' + album
        result = self.api.search(query)
        song_hits = result['song_hits']
        # TODO this search has gotta get better...
        for h in song_hits:
            if h['track']['title'] == track_name:
                if h['track']['album'] == album or h['track'][
                        'artist'] == artist:
                    return h['track']['storeId']
        # TODO Return the best match if no full match is made
        return None

    def _delete_playlist(self, playlist_id):
        """
        Unfollow a playlist so that it does not appear in your account
        Mostly useful for testing, but maybe this should be used if 
        an exception occurs in the track add process?
        Return playlist ID
        """
        return (self.api.delete_playlist(playlist_id) == playlist_id)

    def authenticate(self):

        email = input("Enter your Google email address:  ")
        password = getpass.getpass(
            "Enter the password for your Google account:  ")
        # TODO store email locally
        return self.api.login(email, password, Mobileclient.FROM_MAC_ADDRESS)

    def get_playlist_tracks(self, playlist_id=None):
        # TODO Get playlist namea s well!!!

        if not playlist_id:
            playlist_id = self._get_playlist_id()

        tracks = self.api.get_shared_playlist_contents(playlist_id)
        tracks = [{
            "track": t['track']['title'],
            "album": t['track']['album'],
            "artist": t['track']['artist']
        } for t in tracks]
        return {"name": None, "tracks": tracks}

    def search_for_tracklist(self, tracklist):
        """
        Searches Google for a provided list of tracks
        Track list should be the format provided by indexing 'tracks' in the 
        dict returned from get_playlist_tracks()
        """
        found, not_found = [], []
        for t in tracklist:
            result = self._search_for_track(album=t['album'],
                                            artist=t['artist'],
                                            track_name=t['track'])
            if result:
                found.append(result)
            else:
                not_found.append(t)

        return {"found": found, "not_found": not_found}

    def create_playlist(self, playlist_name):
        """
        Create a new playlist with the provided name
        "prompt" param is mostly just here for testing
        Return the generated playlist ID
        """
        return self.api.create_playlist(name=playlist_name)

    def add_tracks_to_playlist(self, playlist_id, track_list):
        """
        Add all track in provided track_list to desired playlist
        track_list should be a list of Google track IDs, 
        provided by the 'found' index of search_for_tracks()
        """
        return self.api.add_songs_to_playlist(playlist_id=playlist_id,
                                              song_ids=track_list)