Ejemplo n.º 1
0
def album_from_title_artist(title: str, artists: List[str],
                            sp: spotipy.Spotify) -> Optional[Album]:
    """
    Return an album
    """
    q = query(title, artists)

    if q in search_memo:
        search_result = search_memo[q]
    else:
        search_result = sp.search(q, type="album", limit=50)
        print(f"Key error looking up the query {q}")

    album_id = find_album_id_from_search(search_result, artists)
    if album_id:
        try:
            tracks = album_tracks_memo[album_id]
        except KeyError:
            tracks = sp.album_tracks(album_id)
            print(f"Key error looking up the track with id {album_id}")
        return Album(
            title,
            album_id,
            artists,
            [
                Song(
                    title=item["name"],
                    uri=item["uri"],
                    features={},
                    artists=[artist["name"] for artist in item["artists"]],
                ) for item in tracks["items"]
            ],
        )

    return None
Ejemplo n.º 2
0
def cache(album_descriptions: List[AlbumDescription],
          sp: spotipy.Spotify) -> None:
    """
    Cache the results for the given album descriptions for fast lookup later.
    Calling this before using the spotify methods on a list of albums will improve
    performance.
    """
    global search_memo
    global album_tracks_memo
    global feature_memo
    missed = [False, False, False]
    for title, artists in album_descriptions:
        q = query(title, artists)
        if q not in search_memo:
            missed[0] = True
            search_memo[q] = sp.search(query(title, artists),
                                       type="album",
                                       limit=50)
        album_id = find_album_id_from_search(search_memo[q], artists)
        if album_id is not None and album_id not in album_tracks_memo:
            missed[1] = True
            album_tracks_memo[album_id] = sp.album_tracks(album_id)
    if missed[0]:
        set_memo(search_memo, "search")
    if missed[1]:
        set_memo(album_tracks_memo, "tracks")

    songs = get_songs(album_descriptions, sp)
    for songs_chunk in util.chunks(iter(songs), 100):
        seenAllSongs = True
        song_links = [song["uri"] for song in songs_chunk]
        for link in song_links:
            if link not in feature_memo:
                seenAllSongs = False
        if not seenAllSongs:
            missed[2] = True
            feature_list = sp.audio_features(song_links)
            for uri, features in zip(song_links, feature_list):
                feature_memo[uri] = features

    if missed[2]:
        set_memo(feature_memo, "features")
Ejemplo n.º 3
0
    def play(
        self,
        client: spotipy.Spotify,
        spotify_device_id:str,
        uri:str,
        random_song:bool,
        position:str,
        ignore_fully_played:str,
        country_code:str=None
    ) -> None:
        _LOGGER.debug(
            "Playing URI: %s on device-id: %s",
            uri,
            spotify_device_id,
        )

        if uri.find("show") > 0:
            show_episodes_info = client.show_episodes(uri, market=country_code)
            if show_episodes_info and len(show_episodes_info["items"]) > 0:
                if ignore_fully_played:
                    for episode in show_episodes_info["items"]:
                        if not episode["resume_point"]["fully_played"]:
                            episode_uri = episode["external_urls"]["spotify"]
                            break
                else:
                    episode_uri = show_episodes_info["items"][0]["external_urls"][
                        "spotify"
                    ]
                _LOGGER.debug(
                    "Playing episode using uris (latest podcast playlist)= for uri: %s",
                    episode_uri,
                )
                client.start_playback(device_id=spotify_device_id, uris=[episode_uri])
        elif uri.find("episode") > 0:
            _LOGGER.debug("Playing episode using uris= for uri: %s", uri)
            client.start_playback(device_id=spotify_device_id, uris=[uri])

        elif uri.find("track") > 0:
            _LOGGER.debug("Playing track using uris= for uri: %s", uri)
            client.start_playback(device_id=spotify_device_id, uris=[uri])
        else:
            if uri == "random":
                _LOGGER.debug(
                    "Cool, you found the easter egg with playing a random playlist"
                )
                playlists = client.user_playlists("me", 50)
                no_playlists = len(playlists["items"])
                uri = playlists["items"][random.randint(0, no_playlists - 1)]["uri"]
            kwargs = {"device_id": spotify_device_id, "context_uri": uri}

            if random_song:
                if uri.find("album") > 0:
                    results = client.album_tracks(uri, market=country_code)
                    position = random.randint(0, results["total"] - 1)
                elif uri.find("playlist") > 0:
                    results = client.playlist_tracks(uri)
                    position = random.randint(0, results["total"] - 1)
                _LOGGER.debug("Start playback at random position: %s", position)
            if uri.find("artist") < 1:
                kwargs["offset"] = {"position": position}
            _LOGGER.debug(
                'Playing context uri using context_uri for uri: "%s" (random_song: %s)',
                uri,
                random_song,
            )
            client.start_playback(**kwargs)
Ejemplo n.º 4
0
class TestSpotipy(unittest.TestCase):
    """
    These tests require user authentication - provide client credentials using the
    following environment variables

    ::

        'SPOTIPY_CLIENT_USERNAME'
        'SPOTIPY_CLIENT_ID'
        'SPOTIPY_CLIENT_SECRET'
        'SPOTIPY_REDIRECT_URI'
    """

    creep_urn = 'spotify:track:3HfB5hBU0dmBt8T0iCmH42'
    creep_id = '3HfB5hBU0dmBt8T0iCmH42'
    creep_url = 'http://open.spotify.com/track/3HfB5hBU0dmBt8T0iCmH42'
    el_scorcho_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
    el_scorcho_bad_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQK'
    pinkerton_urn = 'spotify:album:04xe676vyiTeYNXw15o9jT'
    weezer_urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
    pablo_honey_urn = 'spotify:album:6AZv3m27uyRxi8KyJSfUxL'
    radiohead_urn = 'spotify:artist:4Z8W4fKeB5YxbusRsdQVPb'
    angeles_haydn_urn = 'spotify:album:1vAbqAeuJVWNAe7UR00bdM'

    bad_id = 'BAD_ID'

    @classmethod
    def setUpClass(self):
        missing = list(filter(lambda var: not os.getenv(CCEV[var]), CCEV))

        if missing:
            raise Exception(
                'Please set the client credentials for the test application using the following environment variables: {}'
                .format(CCEV.values()))

        self.username = os.getenv(CCEV['client_username'])

        self.scope = 'user-library-read'

        self.token = prompt_for_user_token(self.username, scope=self.scope)

        self.spotify = Spotify(auth=self.token)

    def test_artist_urn(self):
        artist = self.spotify.artist(self.radiohead_urn)
        self.assertTrue(artist['name'] == 'Radiohead')

    def test_artists(self):
        results = self.spotify.artists([self.weezer_urn, self.radiohead_urn])
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']) == 2)

    def test_album_urn(self):
        album = self.spotify.album(self.pinkerton_urn)
        self.assertTrue(album['name'] == 'Pinkerton')

    def test_album_tracks(self):
        results = self.spotify.album_tracks(self.pinkerton_urn)
        self.assertTrue(len(results['items']) == 10)

    def test_album_tracks_many(self):
        results = self.spotify.album_tracks(self.angeles_haydn_urn)
        tracks = results['items']
        total, received = results['total'], len(tracks)
        while received < total:
            results = self.spotify.album_tracks(self.angeles_haydn_urn,
                                                offset=received)
            tracks.extend(results['items'])
            received = len(tracks)

        self.assertEqual(received, total)

    def test_albums(self):
        results = self.spotify.albums(
            [self.pinkerton_urn, self.pablo_honey_urn])
        self.assertTrue('albums' in results)
        self.assertTrue(len(results['albums']) == 2)

    def test_track_urn(self):
        track = self.spotify.track(self.creep_urn)
        self.assertTrue(track['name'] == 'Creep')

    def test_track_id(self):
        track = self.spotify.track(self.creep_id)
        self.assertTrue(track['name'] == 'Creep')

    def test_track_url(self):
        track = self.spotify.track(self.creep_url)
        self.assertTrue(track['name'] == 'Creep')

    def test_track_bad_urn(self):
        try:
            track = self.spotify.track(self.el_scorcho_bad_urn)
            self.assertTrue(False)
        except SpotifyException:
            self.assertTrue(True)

    def test_tracks(self):
        results = self.spotify.tracks([self.creep_url, self.el_scorcho_urn])
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']) == 2)

    def test_artist_top_tracks(self):
        results = self.spotify.artist_top_tracks(self.weezer_urn)
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']) == 10)

    def test_artist_related_artists(self):
        results = self.spotify.artist_related_artists(self.weezer_urn)
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']) == 20)
        for artist in results['artists']:
            if artist['name'] == 'Jimmy Eat World':
                found = True
        self.assertTrue(found)

    def test_artist_search(self):
        results = self.spotify.search(q='weezer', type='artist')
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']['items']) > 0)
        self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')

    def test_artist_search_with_market(self):
        results = self.spotify.search(q='weezer', type='artist', market='GB')
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']['items']) > 0)
        self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')

    def test_artist_albums(self):
        results = self.spotify.artist_albums(self.weezer_urn)
        self.assertTrue('items' in results)
        self.assertTrue(len(results['items']) > 0)

        found = False
        for album in results['items']:
            if album['name'] == 'Hurley':
                found = True

        self.assertTrue(found)

    def test_search_timeout(self):
        sp = Spotify(auth=self.token, requests_timeout=.01)
        try:
            results = sp.search(q='my*', type='track')
            self.assertTrue(False, 'unexpected search timeout')
        except requests.Timeout:
            self.assertTrue(True, 'expected search timeout')

    def test_album_search(self):
        results = self.spotify.search(q='weezer pinkerton', type='album')
        self.assertTrue('albums' in results)
        self.assertTrue(len(results['albums']['items']) > 0)
        self.assertTrue(
            results['albums']['items'][0]['name'].find('Pinkerton') >= 0)

    def test_track_search(self):
        results = self.spotify.search(q='el scorcho weezer', type='track')
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']['items']) > 0)
        self.assertTrue(results['tracks']['items'][0]['name'] == 'El Scorcho')

    def test_user(self):
        user = self.spotify.user(user='******')
        self.assertTrue(user['uri'] == 'spotify:user:plamere')

    def test_track_bad_id(self):
        try:
            track = self.spotify.track(self.bad_id)
            self.assertTrue(False)
        except SpotifyException:
            self.assertTrue(True)

    def test_track_bad_id(self):
        try:
            track = self.spotify.track(self.bad_id)
            self.assertTrue(False)
        except SpotifyException:
            self.assertTrue(True)

    def test_unauthenticated_post_fails(self):
        with self.assertRaises(SpotifyException) as cm:
            self.spotify.user_playlist_create("spotify",
                                              "Best hits of the 90s")
        self.assertTrue(cm.exception.http_status == 401
                        or cm.exception.http_status == 403)

    def test_custom_requests_session(self):
        sess = requests.Session()
        sess.headers["user-agent"] = "spotipy-test"
        with_custom_session = Spotify(auth=self.token, requests_session=sess)
        self.assertTrue(
            with_custom_session.user(user="******")["uri"] == "spotify:user:akx")

    def test_force_no_requests_session(self):
        with_no_session = Spotify(auth=self.token, requests_session=False)
        self.assertFalse(isinstance(with_no_session._session,
                                    requests.Session))
        self.assertTrue(
            with_no_session.user(user="******")["uri"] == "spotify:user:akx")
class AuthTestSpotipy(unittest.TestCase):
    """
    These tests require client authentication - provide client credentials
    using the following environment variables

    ::

        'SPOTIPY_CLIENT_ID'
        'SPOTIPY_CLIENT_SECRET'
    """

    playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx"
    four_tracks = [
        "spotify:track:6RtPijgfPKROxEzTHNRiDp",
        "spotify:track:7IHOIqZUUInxjVkko181PB", "4VrWlk8IQxevMvERoX08iC",
        "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"
    ]

    two_tracks = [
        "spotify:track:6RtPijgfPKROxEzTHNRiDp",
        "spotify:track:7IHOIqZUUInxjVkko181PB"
    ]

    other_tracks = [
        "spotify:track:2wySlB6vMzCbQrRnNGOYKa",
        "spotify:track:29xKs5BAHlmlX1u4gzQAbJ",
        "spotify:track:1PB7gRWcvefzu7t3LJLUlf"
    ]

    bad_id = 'BAD_ID'

    creep_urn = 'spotify:track:6b2oQwSGFkzsMtQruIWm2p'
    creep_id = '6b2oQwSGFkzsMtQruIWm2p'
    creep_url = 'http://open.spotify.com/track/6b2oQwSGFkzsMtQruIWm2p'
    el_scorcho_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
    el_scorcho_bad_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQK'
    pinkerton_urn = 'spotify:album:04xe676vyiTeYNXw15o9jT'
    weezer_urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
    pablo_honey_urn = 'spotify:album:6AZv3m27uyRxi8KyJSfUxL'
    radiohead_urn = 'spotify:artist:4Z8W4fKeB5YxbusRsdQVPb'
    angeles_haydn_urn = 'spotify:album:1vAbqAeuJVWNAe7UR00bdM'
    heavyweight_urn = 'spotify:show:5c26B28vZMN8PG0Nppmn5G'
    heavyweight_id = '5c26B28vZMN8PG0Nppmn5G'
    heavyweight_url = 'https://open.spotify.com/show/5c26B28vZMN8PG0Nppmn5G'
    reply_all_urn = 'spotify:show:7gozmLqbcbr6PScMjc0Zl4'
    heavyweight_ep1_urn = 'spotify:episode:68kq3bNz6hEuq8NtdfwERG'
    heavyweight_ep1_id = '68kq3bNz6hEuq8NtdfwERG'
    heavyweight_ep1_url = 'https://open.spotify.com/episode/68kq3bNz6hEuq8NtdfwERG'
    reply_all_ep1_urn = 'spotify:episode:1KHjbpnmNpFmNTczQmTZlR'

    @classmethod
    def setUpClass(self):
        self.spotify = Spotify(
            client_credentials_manager=SpotifyClientCredentials())
        self.spotify.trace = False

    def test_audio_analysis(self):
        result = self.spotify.audio_analysis(self.four_tracks[0])
        assert ('beats' in result)

    def test_audio_features(self):
        results = self.spotify.audio_features(self.four_tracks)
        self.assertTrue(len(results) == len(self.four_tracks))
        for track in results:
            assert ('speechiness' in track)

    def test_audio_features_with_bad_track(self):
        bad_tracks = ['spotify:track:bad']
        input = self.four_tracks + bad_tracks
        results = self.spotify.audio_features(input)
        self.assertTrue(len(results) == len(input))
        for track in results[:-1]:
            if track is not None:
                assert ('speechiness' in track)
        self.assertTrue(results[-1] is None)

    def test_recommendations(self):
        results = self.spotify.recommendations(seed_tracks=self.four_tracks,
                                               min_danceability=0,
                                               max_loudness=0,
                                               target_popularity=50)
        self.assertTrue(len(results['tracks']) == 20)

    def test_artist_urn(self):
        artist = self.spotify.artist(self.radiohead_urn)
        self.assertTrue(artist['name'] == 'Radiohead')

    def test_artists(self):
        results = self.spotify.artists([self.weezer_urn, self.radiohead_urn])
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']) == 2)

    def test_album_urn(self):
        album = self.spotify.album(self.pinkerton_urn)
        self.assertTrue(album['name'] == 'Pinkerton')

    def test_album_tracks(self):
        results = self.spotify.album_tracks(self.pinkerton_urn)
        self.assertTrue(len(results['items']) == 10)

    def test_album_tracks_many(self):
        results = self.spotify.album_tracks(self.angeles_haydn_urn)
        tracks = results['items']
        total, received = results['total'], len(tracks)
        while received < total:
            results = self.spotify.album_tracks(self.angeles_haydn_urn,
                                                offset=received)
            tracks.extend(results['items'])
            received = len(tracks)

        self.assertEqual(received, total)

    def test_albums(self):
        results = self.spotify.albums(
            [self.pinkerton_urn, self.pablo_honey_urn])
        self.assertTrue('albums' in results)
        self.assertTrue(len(results['albums']) == 2)

    def test_track_urn(self):
        track = self.spotify.track(self.creep_urn)
        self.assertTrue(track['name'] == 'Creep')

    def test_track_id(self):
        track = self.spotify.track(self.creep_id)
        self.assertTrue(track['name'] == 'Creep')
        self.assertTrue(track['popularity'] > 0)

    def test_track_url(self):
        track = self.spotify.track(self.creep_url)
        self.assertTrue(track['name'] == 'Creep')

    def test_track_bad_urn(self):
        try:
            self.spotify.track(self.el_scorcho_bad_urn)
            self.assertTrue(False)
        except SpotifyException:
            self.assertTrue(True)

    def test_tracks(self):
        results = self.spotify.tracks([self.creep_url, self.el_scorcho_urn])
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']) == 2)

    def test_artist_top_tracks(self):
        results = self.spotify.artist_top_tracks(self.weezer_urn)
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']) == 10)

    def test_artist_related_artists(self):
        results = self.spotify.artist_related_artists(self.weezer_urn)
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']) == 20)
        for artist in results['artists']:
            if artist['name'] == 'Jimmy Eat World':
                found = True
        self.assertTrue(found)

    def test_artist_search(self):
        results = self.spotify.search(q='weezer', type='artist')
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']['items']) > 0)
        self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')

    def test_artist_search_with_market(self):
        results = self.spotify.search(q='weezer', type='artist', market='GB')
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']['items']) > 0)
        self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')

    def test_artist_search_with_multiple_markets(self):
        total = 5
        countries_list = ['GB', 'US', 'AU']
        countries_tuple = ('GB', 'US', 'AU')

        results_multiple = self.spotify.search_markets(q='weezer',
                                                       type='artist',
                                                       markets=countries_list)
        results_all = self.spotify.search_markets(q='weezer', type='artist')
        results_tuple = self.spotify.search_markets(q='weezer',
                                                    type='artist',
                                                    markets=countries_tuple)
        results_limited = self.spotify.search_markets(q='weezer',
                                                      limit=3,
                                                      type='artist',
                                                      markets=countries_list,
                                                      total=total)

        self.assertTrue(
            all('artists' in results_multiple[country]
                for country in results_multiple))
        self.assertTrue(
            all('artists' in results_all[country] for country in results_all))
        self.assertTrue(
            all('artists' in results_tuple[country]
                for country in results_tuple))
        self.assertTrue(
            all('artists' in results_limited[country]
                for country in results_limited))

        self.assertTrue(
            all(
                len(results_multiple[country]['artists']['items']) > 0
                for country in results_multiple))
        self.assertTrue(
            all(
                len(results_all[country]['artists']['items']) > 0
                for country in results_all))
        self.assertTrue(
            all(
                len(results_tuple[country]['artists']['items']) > 0
                for country in results_tuple))
        self.assertTrue(
            all(
                len(results_limited[country]['artists']['items']) > 0
                for country in results_limited))

        self.assertTrue(
            all(results_multiple[country]['artists']['items'][0]['name'] ==
                'Weezer' for country in results_multiple))
        self.assertTrue(
            all(results_all[country]['artists']['items'][0]['name'] == 'Weezer'
                for country in results_all))
        self.assertTrue(
            all(results_tuple[country]['artists']['items'][0]['name'] ==
                'Weezer' for country in results_tuple))
        self.assertTrue(
            all(results_limited[country]['artists']['items'][0]['name'] ==
                'Weezer' for country in results_limited))

        total_limited_results = 0
        for country in results_limited:
            total_limited_results += len(
                results_limited[country]['artists']['items'])
        self.assertTrue(total_limited_results <= total)

    def test_artist_albums(self):
        results = self.spotify.artist_albums(self.weezer_urn)
        self.assertTrue('items' in results)
        self.assertTrue(len(results['items']) > 0)

        found = False
        for album in results['items']:
            if album['name'] == 'Hurley':
                found = True

        self.assertTrue(found)

    def test_search_timeout(self):
        client_credentials_manager = SpotifyClientCredentials()
        sp = spotipy.Spotify(
            requests_timeout=0.01,
            client_credentials_manager=client_credentials_manager)

        # depending on the timing or bandwidth, this raises a timeout or connection error"
        self.assertRaises(
            (requests.exceptions.Timeout, requests.exceptions.ConnectionError),
            lambda: sp.search(q='my*', type='track'))

    def test_album_search(self):
        results = self.spotify.search(q='weezer pinkerton', type='album')
        self.assertTrue('albums' in results)
        self.assertTrue(len(results['albums']['items']) > 0)
        self.assertTrue(
            results['albums']['items'][0]['name'].find('Pinkerton') >= 0)

    def test_track_search(self):
        results = self.spotify.search(q='el scorcho weezer', type='track')
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']['items']) > 0)
        self.assertTrue(results['tracks']['items'][0]['name'] == 'El Scorcho')

    def test_user(self):
        user = self.spotify.user(user='******')
        self.assertTrue(user['uri'] == 'spotify:user:plamere')

    def test_track_bad_id(self):
        try:
            self.spotify.track(self.bad_id)
            self.assertTrue(False)
        except SpotifyException:
            self.assertTrue(True)

    def test_show_urn(self):
        show = self.spotify.show(self.heavyweight_urn, market="US")
        self.assertTrue(show['name'] == 'Heavyweight')

    def test_show_id(self):
        show = self.spotify.show(self.heavyweight_id, market="US")
        self.assertTrue(show['name'] == 'Heavyweight')

    def test_show_url(self):
        show = self.spotify.show(self.heavyweight_url, market="US")
        self.assertTrue(show['name'] == 'Heavyweight')

    def test_show_bad_urn(self):
        with self.assertRaises(SpotifyException):
            self.spotify.show("bogus_urn", market="US")

    def test_shows(self):
        results = self.spotify.shows(
            [self.heavyweight_urn, self.reply_all_urn], market="US")
        self.assertTrue('shows' in results)
        self.assertTrue(len(results['shows']) == 2)

    def test_show_episodes(self):
        results = self.spotify.show_episodes(self.heavyweight_urn, market="US")
        self.assertTrue(len(results['items']) > 1)

    def test_show_episodes_many(self):
        results = self.spotify.show_episodes(self.reply_all_urn, market="US")
        episodes = results['items']
        total, received = results['total'], len(episodes)
        while received < total:
            results = self.spotify.show_episodes(self.reply_all_urn,
                                                 offset=received,
                                                 market="US")
            episodes.extend(results['items'])
            received = len(episodes)

        self.assertEqual(received, total)

    def test_episode_urn(self):
        episode = self.spotify.episode(self.heavyweight_ep1_urn, market="US")
        self.assertTrue(episode['name'] == '#1 Buzz')

    def test_episode_id(self):
        episode = self.spotify.episode(self.heavyweight_ep1_id, market="US")
        self.assertTrue(episode['name'] == '#1 Buzz')

    def test_episode_url(self):
        episode = self.spotify.episode(self.heavyweight_ep1_url, market="US")
        self.assertTrue(episode['name'] == '#1 Buzz')

    def test_episode_bad_urn(self):
        with self.assertRaises(SpotifyException):
            self.spotify.episode("bogus_urn", market="US")

    def test_episodes(self):
        results = self.spotify.episodes(
            [self.heavyweight_ep1_urn, self.reply_all_ep1_urn], market="US")
        self.assertTrue('episodes' in results)
        self.assertTrue(len(results['episodes']) == 2)

    def test_unauthenticated_post_fails(self):
        with self.assertRaises(SpotifyException) as cm:
            self.spotify.user_playlist_create("spotify",
                                              "Best hits of the 90s")
        self.assertTrue(cm.exception.http_status == 401
                        or cm.exception.http_status == 403)

    def test_custom_requests_session(self):
        sess = requests.Session()
        sess.headers["user-agent"] = "spotipy-test"
        with_custom_session = spotipy.Spotify(
            client_credentials_manager=SpotifyClientCredentials(),
            requests_session=sess)
        self.assertTrue(
            with_custom_session.user(user="******")["uri"] == "spotify:user:akx")
        sess.close()

    def test_force_no_requests_session(self):
        with_no_session = spotipy.Spotify(
            client_credentials_manager=SpotifyClientCredentials(),
            requests_session=False)
        self.assertNotIsInstance(with_no_session._session, requests.Session)
        user = with_no_session.user(user="******")
        self.assertEqual(user["uri"], "spotify:user:akx")
Ejemplo n.º 6
0
class SpotifyService(MusicService):
    """
    Spotify client music service.
    """
    def __init__(self,
                 state,
                 client_id=None,
                 client_secret=None,
                 redirect_uri=None,
                 device_name=None):
        """
        @see Service.__init__()

        For the real meanings of the kwargs, read the Spotipy documentation:
            https://spotipy.readthedocs.io
        You can also set them as the environment variables::
          * ``SPOTIPY_CLIENT_ID``
          * ``SPOTIPY_CLIENT_SECRET``
          * ``SPOTIPY_REDIRECT_URI``

        :type  client_id: str
        :param client_id:
            The Spotify client ID.
        :type  client_secret: str
        :param client_secret:
            The Spotify client secret.
        :type  redirect_uri: str
        :param redirect_uri:
            The Spotify redirect URI.
        :type  device_name: str
        :param device_name:
            The name of the device to control, if not the default one.
        """
        super(SpotifyService, self).__init__("SpotifyService", state,
                                             "Spotify")

        self._client_id = client_id
        self._client_secret = client_secret
        self._redirect_uri = redirect_uri
        self._device_name = device_name
        self._device_id = None
        self._spotify = None
        self._volume = None

    def set_volume(self, volume):
        """
        @see MusicService.set_volume()
        """
        if MIN_VOLUME <= volume <= MAX_VOLUME:
            self._volume = volume
            self._spotify.volume(100.0 * (volume - MIN_VOLUME) /
                                 (MAX_VOLUME - MIN_VOLUME),
                                 device_id=self._device_id)
        else:
            raise ValueError("Bad volume: %s", volume)

    def get_volume():
        """
        @see MusicService.get_volume()
        """
        return self._volume

    def is_playing(self):
        """
        Whether the player is playing.

        :rtype: bool
        :return:
           Whether the player is playing.
        """
        try:
            cur = self._spotify.currently_playing()
            return cur['is_playing']
        except:
            return False

    def play(self, uris):
        """
        Set the list of URIs playing.
        """
        LOG.info("Playing: %s", ' '.join(uris))
        self._spotify.start_playback(uris=uris, device_id=self._device_id)

    def pause(self):
        """
        Pause any currently playing music.
        """
        self._spotify.pause_playback(device_id=self._device_id)

    def unpause(self):
        """
        Resume any currently paused music.
        """
        self._spotify.start_playback(device_id=self._device_id)

    def _start(self):
        """
        @see Startable._start()
        """
        # This is what we need to be able to do
        scope = ','.join(('user-library-read', 'user-read-playback-state',
                          'user-modify-playback-state'))

        # Create the authorization manager, and then use that to create the
        # client
        auth_manager = SpotifyOAuth(client_id=self._client_id,
                                    client_secret=self._client_secret,
                                    redirect_uri=self._redirect_uri,
                                    scope=scope)
        self._spotify = Spotify(auth_manager=auth_manager)

        # See what devices we have to hand
        try:
            if self._device_name is not None:
                LOG.info("Looking for device named '%s'", self._device_name)

            devices = self._spotify.devices()
            for device in devices['devices']:
                # Say what we see
                name = device['name']
                id_ = device['id']
                type_ = device['type']
                active = device['is_active']
                vol = device['volume_percent']
                LOG.info("Found %sactive %s device: '%s'",
                         '' if active else 'in', type_, name)

                # See if we're looking for a specific device, if not just snoop
                # the volume from the first active one
                if self._device_name is not None:
                    if name == self._device_name:
                        LOG.info("Matched '%s' to ID '%s'", name, id_)
                        self._device_id = id_
                        self._volume = vol / 100.0 * MAX_VOLUME
                else:
                    if active and self._volume is None:
                        self._volume = vol / 100.0 * MAX_VOLUME

        except Exception as e:
            LOG.warning("Unable to determine active Spoify devices: %s", e)

        # If we were looking for a specific device then make sure that we found
        # it in the list
        if self._device_name is not None and self._device_id is None:
            raise ValueError("Failed to find device with name '%s'" %
                             (self._device_name, ))

    def _stop(self):
        """
        @see Startable._stop()
        """
        try:
            self._spotify.pause_playback(device_id=self._device_id)
        except:
            # Best effort
            pass

    def _match_artist(self, artist):
        """
        @see MusicService._match_artist()
        """
        artist = ' '.join(artist).lower()
        LOG.debug("Matching artist '%s'", artist)
        result = self._spotify.search(artist, type='artist')
        if 'artists' in result and 'items' in result['artists']:
            items = result['artists']['items']
            LOG.debug("Checking %d results", len(items))
            for item in items:
                name = item.get('name', '').lower()
                LOG.debug("Matching against '%s'", name)
                if fuzz.ratio(name, artist) > 80:
                    return True
        return False

    def _get_stop_handler(self, tokens):
        """
        @see MusicService._get_stop_handler()
        """
        return _SpotifyServicePauseHandler(self, tokens)

    def _get_play_handler(self, tokens):
        """
        @see MusicService._get_play_handler()
        """
        return _SpotifyServiceUnpauseHandler(self, tokens)

    def _get_toggle_pause_handler(self, tokens):
        """
        @see MusicService._get_toggle_pause_handler()
        """
        return _SpotifyServiceTogglePauseHandler(self, tokens)

    def _get_handler_for(self, tokens, platform_match, genre, artist,
                         song_or_album):
        """
        @see MusicService._get_handler_for()
        """
        # Do nothing if we have no name
        if song_or_album is None or len(song_or_album) == 0:
            return None

        # Normalise to strings
        name = ' '.join(song_or_album).lower()
        if artist is None or len(artist) == 0:
            artist = None
        else:
            artist = ' '.join(artist).lower()

        # We will put all the track URIs in here
        uris = []

        # Search by track name then album name, these are essentially the same
        # logic
        for which in ('track', 'album'):
            LOG.info("Looking for '%s'%s as a %s", name,
                     " by '%s'" % artist if artist else '', which)

            # This is the key in the results
            plural = which + 's'

            # Try using the song_or_album as the name
            result = self._spotify.search(name, type=which)
            if not result:
                LOG.info("No results")
                continue

            # Did we get back any tracks
            if plural not in result:
                LOG.error("%s was not in result keys: %s", plural,
                          result.keys())
                continue

            # We got some results back, let's assign scores to them all
            results = result[plural]
            matches = []
            for item in results.get('items', []):
                # It must have a uri
                if 'uri' not in item and item['uri']:
                    LOG.error("No URI in %s", item)

                # Look at all the candidate entries
                if 'name' in item:
                    # See if this is better than any existing match
                    name_score = fuzz.ratio(name, item['name'].lower())
                    LOG.debug("'%s' matches '%s' with score %d", item['name'],
                              name, name_score)

                    # Check to make sure that we have an artist match as well
                    if artist is None:
                        # Treat as a wildcard
                        artist_score = 100
                    else:
                        artist_score = 0
                        for entry in item.get('artists', []):
                            score = fuzz.ratio(artist,
                                               entry.get('name', '').lower())
                            LOG.debug("Artist match score for '%s' was %d",
                                      entry.get('name', ''), score)
                            if score > artist_score:
                                artist_score = score
                    LOG.debug("Artist match score was %d", artist_score)

                    # Only consider cases where the scores look "good enough"
                    if name_score > 75 and artist_score > 75:
                        LOG.debug("Adding match")
                        matches.append((item, name_score, artist_score))

            # Anything?
            if len(matches) > 0:
                LOG.debug("Got %d matches", len(matches))

                # Order them accordingly
                matches.sort(key=lambda e: (e[1], e[2]))

                # Now, pick the top one
                best = matches[0]
                item = best[0]
                LOG.debug("Best match was: %s", item)

                # Extract the info
                item_name = item.get('name', None) or name
                artists = item.get('artists', [])
                artist_name = (artists[0].get('name', None)
                               if len(artists) > 0 else None) or artist

                # Description of what we are playing
                what = item_name if item_name else name
                if artist_name:
                    what += " by " + artist_name
                what += " on Spotify"

                # The score is the geometric value of the two
                score = sqrt(best[1] * best[1] + best[2] * best[2]) / 100.0

                # The should be here
                assert 'uri' in item, "Missing URI in %s" % (item, )
                uri = item['uri']

                # If we are an album then grab the track URIs
                if which == 'album':
                    tracks = self._spotify.album_tracks(uri)
                    if tracks and 'items' in tracks:
                        uris = [track['uri'] for track in tracks['items']]
                else:
                    # Just the track
                    uris = [uri]

                # And we're done
                break

        # Otherwise assume that it's an artist
        if len(uris) == 0 and artist is None:
            LOG.info("Looking for '%s' as an artist", name)
            result = self._spotify.search(name, type='artist')
            LOG.debug("Got: %s", result)

            if result and 'artists' in result and 'items' in result['artists']:
                items = sorted(result['artists']['items'],
                               key=lambda entry: fuzz.ratio(
                                   name,
                                   entry.get('name', '').lower()),
                               reverse=True)

                # Look at the best one, if any
                LOG.debug("Got %d matches", len(items))
                if len(items) > 0:
                    match = items[0]
                    who = match['name']
                    what = "%s on Spotify" % (who, )
                    score = fuzz.ratio(who.lower(), name)

                    # Find all their albums
                    if 'uri' in match:
                        LOG.debug("Got match: %s", match['uri'])
                        artist_albums = self._spotify.artist_albums(
                            match['uri'])
                        for album in artist_albums.get('items', []):
                            # Append all the tracks
                            LOG.debug("Looking at album: %s", album)
                            if 'uri' in album:
                                tracks = self._spotify.album_tracks(
                                    album['uri'])
                                if tracks and 'items' in tracks:
                                    LOG.debug(
                                        "Adding tracks: %s",
                                        ' '.join(track['name']
                                                 for track in tracks['items']))
                                    uris.extend([
                                        track['uri']
                                        for track in tracks['items']
                                    ])

        # And now we can give it back, if we had something
        if len(uris) > 0:
            return _SpotifyServicePlayHandler(self, tokens, what, uris, score)
        else:
            # We got nothing
            return None
Ejemplo n.º 7
0
class SpotifyGateway:
    """Class implementing a gateway to the spotify web api.

    Available functions:
    - fetch_playlist: Fetch a Playlist namedtuple given a playlist_uri.
    - fetch_related_tracks: Fetch a track's related tracks by artist or album.
    - fetch_related_tracks_by_track: Concurrently fetch a mapping of tracks to their related tracks.
    - create_playlist: Create a playlist for a user[]
    """

    @raise_spotipy_error_as_souffle_error
    def __init__(self, access_token: str):
        """C'tor"""
        self._access_token = access_token
        self._spotify = Spotify(access_token, requests_session=False)


    @raise_spotipy_error_as_souffle_error
    def fetch_playlist(self, playlist_uri: str):
        """Fetch a Playlist namedtuple representation of a spotify playlist given a playlist_uri."""
        playlist_id = playlist_uri.split(':')[-1]

        name, description = fetch_playlist_metadata(playlist_id, self._access_token)
        track_data = fetch_playlist_track_data(playlist_id, self._access_token)
        tracks = [pluck_track(track_record) for track_record in track_data]

        return Playlist(
            name=name,
            tracks=tracks,
            description=description
        )


    @raise_spotipy_error_as_souffle_error
    def fetch_related_tracks(self, track, related_by):
        """Fetch a set of Track namedtuples that are related to TRACK by RELATED_BY."""
        if related_by == 'artist':
            related_track_data = self._spotify.artist_top_tracks(track.artist)['tracks']
            related_tracks = {pluck_track(track_record, artist=track.artist)
                              for track_record in related_track_data}

        elif related_by == 'album':
            related_track_data = self._spotify.album_tracks(track.album)['items']
            related_tracks = {pluck_track(track_record, album=track.album)
                              for track_record in related_track_data}

        else:
            raise SouffleParameterError('Invalid souffle type "{}".'.format(related_by))

        return related_tracks


    @raise_spotipy_error_as_souffle_error
    def fetch_related_tracks_by_track(self, tracks, related_by):
        """Fetch a related_tracks_by_track mapping, where each related_tracks set is the set of
        tracks related to a track in TRACKS by RELATED_BY.
        """
        with futures.ThreadPoolExecutor(max_workers=10) as executor:
            related_tracks_by_track = dict(executor.map(
                lambda track: (track, self.fetch_related_tracks(track, related_by)),
                tracks
            ))

        return related_tracks_by_track


    @raise_spotipy_error_as_souffle_error
    def create_playlist(self, playlist: Playlist, is_public: bool = True) -> str:
        """Create a new playlist for the given Playlist namedtuple.  Return the uri of the new
        playlist.
        """
        user_id = self.fetch_current_user_id()
        response = requests.post(
            f'{BASE}/users/{user_id}/playlists',
            json={
                'name': playlist.name,
                'description': playlist.description,
                'public': is_public
            },
            headers={'Authorization': f'Bearer {self._access_token}'}
            )
        response_json = response.json()
        playlist_id = response_json['id']
        playlist_track_uris = [f'spotify:track:{track.id}' for track in playlist.tracks]
        response = requests.post(
            f'{BASE}/playlists/{playlist_id}/tracks',
            params={
                'uris': ','.join(playlist_track_uris)
            },
            headers={'Authorization': f'Bearer {self._access_token}'}
            )
        return playlist_id


    @raise_spotipy_error_as_souffle_error
    def fetch_current_user_id(self):
        """Fetch the current user id based on the access token provided to the SpotifyGateway
        constructor.

        Note: This should eventually return a plucked User namedtuple.
        """
        response = self._spotify.current_user()
        return response['id']
Ejemplo n.º 8
0
# Filter releases to be newer than x. Where x currently is today.
filtered_releases = []
for release in releases:
    release_date = datetime.strptime(release['release_date'].split(' ')[0], get_date_precision_format(release['release_date_precision']))
    if release_date >= (t0-timedelta(days=1)):
        filtered_releases.append(release)

# Extract track ids from releases and add them if they are not already in the playlist. This can happen when multiple
# followed artists work on the same track.
# If a track is selected to be added to the playlist, also save the artists.
track_ids = []
artists = dict()
followed_artists_names = [x['name'] for x in followed_artists]
for release in filtered_releases:
    tracks = spotify.album_tracks(release['id'])
    for x in tracks['items']:
        if x['id'] not in track_ids and x['id'] not in playlist_track_ids:
            track_ids.append(x['id'])

            for artist in x['artists']:
                if artist['name'] in followed_artists_names:
                    if artist['name'] not in artists:
                        artists[artist['name']] = 1
                    else:
                        artists[artist['name']] += 1

# Add tracks to playlist.
n = 100
added = False
for i in range(0, len(track_ids), n):
Ejemplo n.º 9
0
class AuthTestSpotipy(unittest.TestCase):
    """
    These tests require client authentication - provide client credentials
    using the following environment variables

    ::

        'SPOTIPY_CLIENT_ID'
        'SPOTIPY_CLIENT_SECRET'
    """

    playlist = "spotify:user:plamere:playlist:2oCEWyyAPbZp9xhVSxZavx"
    four_tracks = [
        "spotify:track:6RtPijgfPKROxEzTHNRiDp",
        "spotify:track:7IHOIqZUUInxjVkko181PB", "4VrWlk8IQxevMvERoX08iC",
        "http://open.spotify.com/track/3cySlItpiPiIAzU3NyHCJf"
    ]

    two_tracks = [
        "spotify:track:6RtPijgfPKROxEzTHNRiDp",
        "spotify:track:7IHOIqZUUInxjVkko181PB"
    ]

    other_tracks = [
        "spotify:track:2wySlB6vMzCbQrRnNGOYKa",
        "spotify:track:29xKs5BAHlmlX1u4gzQAbJ",
        "spotify:track:1PB7gRWcvefzu7t3LJLUlf"
    ]

    bad_id = 'BAD_ID'

    creep_urn = 'spotify:track:3HfB5hBU0dmBt8T0iCmH42'
    creep_id = '3HfB5hBU0dmBt8T0iCmH42'
    creep_url = 'http://open.spotify.com/track/3HfB5hBU0dmBt8T0iCmH42'
    el_scorcho_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQJ'
    el_scorcho_bad_urn = 'spotify:track:0Svkvt5I79wficMFgaqEQK'
    pinkerton_urn = 'spotify:album:04xe676vyiTeYNXw15o9jT'
    weezer_urn = 'spotify:artist:3jOstUTkEu2JkjvRdBA5Gu'
    pablo_honey_urn = 'spotify:album:6AZv3m27uyRxi8KyJSfUxL'
    radiohead_urn = 'spotify:artist:4Z8W4fKeB5YxbusRsdQVPb'
    angeles_haydn_urn = 'spotify:album:1vAbqAeuJVWNAe7UR00bdM'

    @classmethod
    def setUpClass(self):
        self.spotify = Spotify(
            client_credentials_manager=SpotifyClientCredentials())
        self.spotify.trace = False

    def test_audio_analysis(self):
        result = self.spotify.audio_analysis(self.four_tracks[0])
        assert ('beats' in result)

    def test_audio_features(self):
        results = self.spotify.audio_features(self.four_tracks)
        self.assertTrue(len(results) == len(self.four_tracks))
        for track in results:
            assert ('speechiness' in track)

    def test_audio_features_with_bad_track(self):
        bad_tracks = ['spotify:track:bad']
        input = self.four_tracks + bad_tracks
        results = self.spotify.audio_features(input)
        self.assertTrue(len(results) == len(input))
        for track in results[:-1]:
            if track is not None:
                assert ('speechiness' in track)
        self.assertTrue(results[-1] is None)

    def test_recommendations(self):
        results = self.spotify.recommendations(seed_tracks=self.four_tracks,
                                               min_danceability=0,
                                               max_loudness=0,
                                               target_popularity=50)
        self.assertTrue(len(results['tracks']) == 20)

    def test_artist_urn(self):
        artist = self.spotify.artist(self.radiohead_urn)
        self.assertTrue(artist['name'] == 'Radiohead')

    def test_artists(self):
        results = self.spotify.artists([self.weezer_urn, self.radiohead_urn])
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']) == 2)

    def test_album_urn(self):
        album = self.spotify.album(self.pinkerton_urn)
        self.assertTrue(album['name'] == 'Pinkerton')

    def test_album_tracks(self):
        results = self.spotify.album_tracks(self.pinkerton_urn)
        self.assertTrue(len(results['items']) == 10)

    def test_album_tracks_many(self):
        results = self.spotify.album_tracks(self.angeles_haydn_urn)
        tracks = results['items']
        total, received = results['total'], len(tracks)
        while received < total:
            results = self.spotify.album_tracks(self.angeles_haydn_urn,
                                                offset=received)
            tracks.extend(results['items'])
            received = len(tracks)

        self.assertEqual(received, total)

    def test_albums(self):
        results = self.spotify.albums(
            [self.pinkerton_urn, self.pablo_honey_urn])
        self.assertTrue('albums' in results)
        self.assertTrue(len(results['albums']) == 2)

    def test_track_urn(self):
        track = self.spotify.track(self.creep_urn)
        self.assertTrue(track['name'] == 'Creep')

    def test_track_id(self):
        track = self.spotify.track(self.creep_id)
        self.assertTrue(track['name'] == 'Creep')
        self.assertTrue(track['popularity'] > 0)

    def test_track_url(self):
        track = self.spotify.track(self.creep_url)
        self.assertTrue(track['name'] == 'Creep')

    def test_track_bad_urn(self):
        try:
            self.spotify.track(self.el_scorcho_bad_urn)
            self.assertTrue(False)
        except SpotifyException:
            self.assertTrue(True)

    def test_tracks(self):
        results = self.spotify.tracks([self.creep_url, self.el_scorcho_urn])
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']) == 2)

    def test_artist_top_tracks(self):
        results = self.spotify.artist_top_tracks(self.weezer_urn)
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']) == 10)

    def test_artist_related_artists(self):
        results = self.spotify.artist_related_artists(self.weezer_urn)
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']) == 20)
        for artist in results['artists']:
            if artist['name'] == 'Jimmy Eat World':
                found = True
        self.assertTrue(found)

    def test_artist_search(self):
        results = self.spotify.search(q='weezer', type='artist')
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']['items']) > 0)
        self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')

    def test_artist_search_with_market(self):
        results = self.spotify.search(q='weezer', type='artist', market='GB')
        self.assertTrue('artists' in results)
        self.assertTrue(len(results['artists']['items']) > 0)
        self.assertTrue(results['artists']['items'][0]['name'] == 'Weezer')

    def test_artist_albums(self):
        results = self.spotify.artist_albums(self.weezer_urn)
        self.assertTrue('items' in results)
        self.assertTrue(len(results['items']) > 0)

        found = False
        for album in results['items']:
            if album['name'] == 'Hurley':
                found = True

        self.assertTrue(found)

    def test_search_timeout(self):
        client_credentials_manager = SpotifyClientCredentials()
        sp = spotipy.Spotify(
            client_credentials_manager=client_credentials_manager,
            requests_timeout=.01)

        try:
            sp.search(q='my*', type='track')
            self.assertTrue(False, 'unexpected search timeout')
        except requests.exceptions.Timeout:
            self.assertTrue(True, 'expected search timeout')

    def test_album_search(self):
        results = self.spotify.search(q='weezer pinkerton', type='album')
        self.assertTrue('albums' in results)
        self.assertTrue(len(results['albums']['items']) > 0)
        self.assertTrue(
            results['albums']['items'][0]['name'].find('Pinkerton') >= 0)

    def test_track_search(self):
        results = self.spotify.search(q='el scorcho weezer', type='track')
        self.assertTrue('tracks' in results)
        self.assertTrue(len(results['tracks']['items']) > 0)
        self.assertTrue(results['tracks']['items'][0]['name'] == 'El Scorcho')

    def test_user(self):
        user = self.spotify.user(user='******')
        self.assertTrue(user['uri'] == 'spotify:user:plamere')

    def test_track_bad_id(self):
        try:
            self.spotify.track(self.bad_id)
            self.assertTrue(False)
        except SpotifyException:
            self.assertTrue(True)

    def test_unauthenticated_post_fails(self):
        with self.assertRaises(SpotifyException) as cm:
            self.spotify.user_playlist_create("spotify",
                                              "Best hits of the 90s")
        self.assertTrue(cm.exception.http_status == 401
                        or cm.exception.http_status == 403)

    def test_custom_requests_session(self):
        sess = requests.Session()
        sess.headers["user-agent"] = "spotipy-test"
        with_custom_session = spotipy.Spotify(
            client_credentials_manager=SpotifyClientCredentials(),
            requests_session=sess)
        self.assertTrue(
            with_custom_session.user(user="******")["uri"] == "spotify:user:akx")
        sess.close()

    def test_force_no_requests_session(self):
        from requests import Session
        with_no_session = spotipy.Spotify(
            client_credentials_manager=SpotifyClientCredentials(),
            requests_session=False)
        self.assertFalse(isinstance(with_no_session._session, Session))
        self.assertTrue(
            with_no_session.user(user="******")["uri"] == "spotify:user:akx")
Ejemplo n.º 10
0
class Apollo(object):
    '''
		This object is the main interface between the Google Speech
		transcriber and Spotify. It parses the transcribed text from
		Google into commands, conducts queries with the Spotipy API
		and interfaces with the Spotify desktop app to play music.
	'''
    def __init__(self):
        # Authentication with Spotipy API
        self._SP_USER = "******"
        self._SP_CI = os.getenv('SPOTIPY_CLIENT_ID')
        self._SP_CS = os.getenv('SPOTIPY_CLIENT_SECRET')
        self._SP_CRED = oauth2.SpotifyClientCredentials(
            client_id=self._SP_CI, client_secret=self._SP_CS)
        self._SP_AUTH = self._SP_CRED.get_access_token()
        self._SP = Spotify(auth=self._SP_AUTH)
        self._SP_OAUTH_TOKEN = sp_client.get_oauth_token()
        self._SP_CSRF_TOKEN = sp_client.get_csrf_token()
        self._SP_PLAYLISTS = self._SP.user_playlists(user=self._SP_USER)
        # Dictionary of playlist URI's with names as keys
        self._SP_PLAYLIST_DICT = defaultdict(lambda: defaultdict(lambda: None))
        # Number of items to return from track search
        self._SP_TLIMIT = 10
        # Number of items to return from playlist search
        self._SP_PLIMIT = 100
        self._TRACK_LIST = []
        self.createPlaylistDict()

    #@classmethod
    def createPlaylistDict(self):
        # Populates the playlist dict
        for item in self._SP_PLAYLISTS.items()[0][1]:
            name = item['name'].lower()
            self._SP_PLAYLIST_DICT[name]['uri'] = item['uri']
            self._SP_PLAYLIST_DICT[name]['id'] = item['id']

    #@staticmethod
    def parseCommand(self, phrase):
        '''
			Parses transcribed phrse from Google Speech into 
			command keywords. Curently supports 3 command classes:
				A: 
					- "Play/Pause"
					- "Skip"
				B:
					- "Play <songname>"
					- "Play <songname> by <artist>"
					- "Play playlist <playlistname>"
				C:
					- "Add this song to playlist <playlistname>"

			@param phrase: transcribed phrase from Google Speech API
		'''
        cmd = defaultdict(lambda: None)
        if (not len(phrase)):
            asay("No command received")
            return None
        phrase = phrase.lower().strip(re.search(r'\b(spotify)\b', phrase))
        if ("play" in phrase and len(phrase) <= 2):
            cmd['A'] = "play"
        elif ("pause" in phrase or "does" in phrase):
            cmd['A'] = "pause"
        elif ("skip" in phrase):
            cmd['A'] = "skip"
        elif ("previous" in phrase or "last" in phrase):
            cmd['A'] = "prev"
        elif ("play" in phrase):
            cmd['B'] = phrase
        elif ("by" in phrase):
            keywords = phrase.replace("play ", "").split("by")
            cmd['B'] = keywords
        elif ("play" in phrase and "playlist" in phrase):
            keywords = phrase.split("play ")[-1]
            cmd['B'] = keywords
        else:
            asay("No command received")
            return None
        if (len(cmd) >= 1):
            asay("Sending request to Spotify")
            ret = self.sendRequest(cmd)
        else:
            ret = None
        return ret

    #@classmethod
    def sendRequest(self, command):
        '''
			Sends appropriate request to Spotify
			@param command: parsed command dict (key = ['A','B','C']) 
		'''
        if (len(command) == 0):
            asay("Error: Command was empty")
            return
        key = command.keys()[0]
        cmd = command[key]
        asay("key: %s\t command: %s" % (key, str(cmd)))
        if (key == 'A'):
            if (cmd == "play"):
                sp_client.unpause(self._SP_OAUTH_TOKEN, self._SP_CSRF_TOKEN)
                asay(">> Play")
            elif ("pause" in cmd or "does" in cmd):
                sp_client.pause(self._SP_OAUTH_TOKEN, self._SP_CSRF_TOKEN)
                asay("|| Paused")
            elif ("skip" in cmd or "next" in cmd):
                if (len(self._TRACK_LIST) == 0):
                    asay("Empty track list! Play a song to populate tracklist")
                    return
                track_number = random.randint(0, len(self._TRACK_LIST) - 1)
                asay("Playing track number " + str(track_number))
                sp_client.play(self._SP_OAUTH_TOKEN, self._SP_CSRF_TOKEN,
                               self._TRACK_LIST[track_number])

        elif (key == 'B'):
            # Play playlist
            if ("playlist" in cmd):
                req_playlist = cmd.split("playlist ")[-1]
                playlist_uri = self._SP_PLAYLIST_DICT[req_playlist]['uri']
                playlist_id = self._SP_PLAYLIST_DICT[req_playlist]['id']
                if (playlist_uri == '0'):
                    asay("Couldn't find playlist " + req_playlist)
                    return False
                else:
                    sp_client.play(self._SP_OAUTH_TOKEN, self._SP_CSRF_TOKEN,
                                   playlist_uri)
                    playlist_tracks = self._SP.user_playlist_tracks(
                        user=self._SP_USER, playlist_id=playlist_id)
                    self._TRACK_LIST = [
                        item['track']['uri']
                        for item in playlist_tracks['items']
                    ]
            # Play single track
            else:
                # Artist name only
                if ("artist" in cmd):
                    artist = cmd.split("artist")[-1]
                    asay("Looking for " + str(artist))
                    result = self._SP.search(q=artist,
                                             type='artist',
                                             limit=self._SP_TLIMIT)
                    if (not result):
                        asay("Couldn't find artist " + artist)
                        return False
                    asay("Playing " +
                         str(result['artists']['items'][0]['name']))
                    artist_uri = result['artists']['items'][0]['uri']
                    sp_client.play(self._SP_OAUTH_TOKEN, self._SP_CSRF_TOKEN,
                                   artist_uri)
                    # TODO Get remaining tracks in album to enable skipping

                    return True
                # Track + Artist name provided
                elif ("by" in cmd):
                    cmd = cmd.replace("play ", "")
                    track, artist = cmd.split("by")
                    asay("Looking for " + str(artist) + ":" + str(track))
                    result = self._SP.search(q=track + " " + artist,
                                             type='track',
                                             limit=self._SP_TLIMIT)
                # Track name only
                else:
                    track = cmd[0]
                    asay("Looking for " + str(track))
                    result = self._SP.search(q=track,
                                             type='track',
                                             limit=self._SP_TLIMIT)
                try:
                    track_uri = result['tracks']['items'][0]['uri']
                    sp_client.play(self._SP_OAUTH_TOKEN, self._SP_CSRF_TOKEN,
                                   track_uri)
                    album_uri = result['tracks']['items'][0]['album']['uri']
                    album_tracks = self._SP.album_tracks(album_id=album_uri)
                    self._TRACK_LIST = [
                        item['uri'] for item in album_tracks['items']
                    ]
                    # Single record? (one song in the album): search the artist playlist
                    if (len(self._TRACK_LIST) <= 1):
                        artist = result['tracks']['items'][0]['artists'][0]
                        singles = self._SP.artist_albums(artist['id'])
                        self._TRACK_LIST = []
                        self._TRACK_LIST = [
                            item['uri'] for item in singles['items']
                        ]
                    return True
                except KeyError or IndexError:
                    asay("Couldn't find URI")
                    return False