def get_new_releases( sp: Spotify, artists: List[Dict[str, Any]], date: Optional[datetime.datetime] = None, weeks: int = 4, album_type: str = "album", country: str = "FR", ) -> List[Dict[str, Any]]: """Returns a list of released albums from the given artists since the a given date (first choice) or during a given interval""" if date is None: date = datetime.datetime.now() - datetime.timedelta(weeks=weeks) new_releases = [] click.echo(f"Fetching from {date.strftime('%Y-%m-%d')}") with click.progressbar(artists, label="Fetching new releases") as progress_bar: for artist in progress_bar: results = sp.artist_albums( artist["id"], album_type=album_type, country=country, limit=1 )["items"] if len(results) == 0: continue # only get last album album = results[0] release_date = parse_release_date(album["release_date"]) if release_date > date: new_releases.append(album) return new_releases
def get_artist_albums( sp: Spotify, artist_id: str, album_type: str = "album", country: str = "FR", limit: int = 20, ) -> List[Dict[str, Any]]: """Returns the albums of an artist given its ID or URI""" return sp.artist_albums(artist_id, album_type=album_type, country=country)["items"]
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")
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
class SpotifyReleaseTweeter(object): def __init__(self): token = spotipy.util.prompt_for_user_token( settings.SPOTIFY_USER_NAME, scope='user-follow-read', client_id=settings.SPOTIFY_CLIENT_ID, client_secret=settings.SPOTIFY_CLIENT_SECRET, redirect_uri=settings.SPOTIFY_REDIRECT_URI) self.spotify = Spotify(auth=token) self.is_first_run = is_first_run() self.cache = AlreadyHandledCache(settings.TWEETED_IDS_CACHE_PATH) def get_ids_of_followed_artists(self): result = list() last_artist_id = None while True: artist_result = self.spotify.current_user_followed_artists( 50, last_artist_id) artists = artist_result['artists']['items'] if not artists: break for artist in artists: last_artist_id = artist['id'] result.append(artist['id']) print("{} followed artists found.".format(len(result))) return result def filter_releases(self, artist_releases): release_ids = [release.release_id for release in artist_releases] reduced_ids = self.cache.reduce(release_ids) filtered_releases = filter( lambda release: release.release_id in reduced_ids, artist_releases) return list(filtered_releases) def get_releases_per_artist(self, artist_ids, limit=settings.LAST_N_RELEASES): ALBUM = 'Album' SINGLE = 'Single' result = dict() for artist_id in artist_ids: artist_releases = list() # Albums try: result_albums = self.spotify.artist_albums( artist_id=artist_id, album_type=ALBUM, country=settings.SPOTIFY_MARKET, limit=limit) albums = [ item_to_spotify_release(item, ALBUM) for item in result_albums['items'] ] artist_releases.extend(albums) except ConnectionError: print(("Could not establish connection while fetching " "albums for artist with id {}. Skipping." ).format(artist_id)) # Singles try: result_singles = self.spotify.artist_albums( artist_id=artist_id, album_type=SINGLE, country=settings.SPOTIFY_MARKET, limit=limit) singles = [ item_to_spotify_release(item, SINGLE) for item in result_singles['items'] ] artist_releases.extend(singles) except ConnectionError: print(("Could not establish connection while fetching " "singles for artist with id {}. Skipping." ).format(artist_id)) # Filter filtered_releases = self.filter_releases(artist_releases) if filtered_releases: result[artist_id] = filtered_releases return result def process(self): print("Start ({}).".format(datetime.now())) artist_ids = self.get_ids_of_followed_artists() releases_per_artist = self.get_releases_per_artist(artist_ids) twitter_status_strings = create_twitter_status_strings_from_releases_per_artist( releases_per_artist) if not self.is_first_run: print("{} releases will be tweeted.".format( len(twitter_status_strings))) Tweeter().tweet_list(twitter_status_strings) else: print(( "Zero tweets at first run, due to Twython API limit (200 tweets a day). " "The cache is now initialized and the script will run as promised in the " "next run. All releases until now will not be tweeted anymore." )) print("Done ({}).".format(datetime.now()))
class SpotifyReleaseGun(object): def __init__(self, spotify_user_data): self.user_name = spotify_user_data['user_name'] token = spotipy.util.prompt_for_user_token( self.user_name, scope='user-follow-read', client_id=spotify_user_data['client_id'], client_secret=spotify_user_data['client_secret'], redirect_uri=spotify_user_data['redirect_uri'] ) self.spotify = Spotify(auth=token) self.cache_path = settings.CACHE_PATH_PATTERN.format(self.user_name) self.is_first_run = is_first_run(self.cache_path) self.cache = AlreadyHandledCache(self.cache_path) def get_ids_of_followed_artists(self): result = [] last_artist_id = None while True: artist_result = self.spotify.\ current_user_followed_artists(50, last_artist_id) artists = artist_result['artists']['items'] if not artists: break for artist in artists: last_artist_id = artist['id'] result.append(artist['id']) return result def filter_releases(self, artist_releases): release_ids = [release.release_id for release in artist_releases] reduced_ids = self.cache.reduce(release_ids) filtered = filter( lambda release: release.release_id in reduced_ids, artist_releases ) return list(filtered) def get_releases_per_artist(self, artist_ids, limit=settings.LAST_N_RELEASES): releases_per_artist_id = {} for artist_id in artist_ids: artist_releases = [] try: # Albums result_albums = self.spotify.artist_albums( artist_id=artist_id, album_type='Album', country=settings.SPOTIFY_MARKET, limit=limit ) albums = [SpotifyRelease(d) for d in result_albums['items']] artist_releases.extend(albums) # Singles result_singles = self.spotify.artist_albums( artist_id=artist_id, album_type='Single', country=settings.SPOTIFY_MARKET, limit=limit ) singles = [SpotifyRelease(d) for d in result_singles['items']] artist_releases.extend(singles) except ConnectionError: print(("> Could not establish connection while " "fetching releases for artist with id {}. " "Skipping.").format(artist_id)) except JSONDecodeError: print(("> Could not decode JSON response " "of artist with id {}. Skipping.").format(artist_id)) filtered_releases = self.filter_releases(artist_releases) if filtered_releases: releases_per_artist_id[artist_id] = filtered_releases return releases_per_artist_id def get_releases_of_artists(self): artist_ids = self.get_ids_of_followed_artists() print("> {} followed artists found.".format(len(artist_ids))) releases_per_artist = self.get_releases_per_artist(artist_ids) return releases_per_artist def process(self, to_console=True): print("> Start ({}).".format(datetime.now())) releases = self.get_releases_of_artists() if to_console: send_to_console(releases, self.user_name) if settings.REALLY_SIMPLE_RSS_SERVER_URL: send_to_really_simple_rss_server(releases, self.user_name) if settings.SLACK_URL: send_to_slack(releases, self.user_name) print("> Done ({}).".format(datetime.now()))
while artists: artist_result = spotify.current_user_followed_artists(50, last_artist_id) artists = artist_result['artists']['items'] for artist in artists: last_artist_id = artist['id'] followed_artists.append(artist) # Get 5 newest albums and singles from artists. releases = [] for artist in followed_artists: try: # Albums result_albums = spotify.artist_albums( artist_id=artist['id'], album_type='Album', country='NL', limit=5 ) releases.extend(result_albums['items']) # Singles result_singles = spotify.artist_albums( artist_id=artist['id'], album_type='Single', country='NL', limit=5 ) releases.extend(result_singles['items']) except ConnectionError: print(("Could not establish connection while " "fetching releases for artist {0} with id {1}. " "Skipping.").format(artist['name'], artist['id']))
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")
class SpotifyReleaseGun(object): def __init__(self, spotify_user_data): self.user_name = spotify_user_data['user_name'] token = spotipy.util.prompt_for_user_token( self.user_name, scope='user-follow-read', client_id=spotify_user_data['client_id'], client_secret=spotify_user_data['client_secret'], redirect_uri=spotify_user_data['redirect_uri']) self.spotify = Spotify(auth=token) self.cache_path = settings.CACHE_PATH_PATTERN.format(self.user_name) self.is_first_run = is_first_run(self.cache_path) self.cache = AlreadyHandledCache(self.cache_path) def get_ids_of_followed_artists(self): result = [] last_artist_id = None while True: artist_result = self.spotify.\ current_user_followed_artists(50, last_artist_id) artists = artist_result['artists']['items'] if not artists: break for artist in artists: last_artist_id = artist['id'] result.append(artist['id']) return result def filter_releases(self, artist_releases): release_ids = [release.release_id for release in artist_releases] reduced_ids = self.cache.reduce(release_ids) filtered = filter(lambda release: release.release_id in reduced_ids, artist_releases) return list(filtered) def get_releases_per_artist(self, artist_ids, limit=settings.LAST_N_RELEASES): releases_per_artist_id = {} for artist_id in artist_ids: artist_releases = [] try: # Albums result_albums = self.spotify.artist_albums( artist_id=artist_id, album_type='Album', country=settings.SPOTIFY_MARKET, limit=limit) albums = [SpotifyRelease(d) for d in result_albums['items']] artist_releases.extend(albums) # Singles result_singles = self.spotify.artist_albums( artist_id=artist_id, album_type='Single', country=settings.SPOTIFY_MARKET, limit=limit) singles = [SpotifyRelease(d) for d in result_singles['items']] artist_releases.extend(singles) except ConnectionError: print(("> Could not establish connection while " "fetching releases for artist with id {}. " "Skipping.").format(artist_id)) except JSONDecodeError: print(("> Could not decode JSON response " "of artist with id {}. Skipping.").format(artist_id)) filtered_releases = self.filter_releases(artist_releases) if filtered_releases: releases_per_artist_id[artist_id] = filtered_releases return releases_per_artist_id def get_releases_of_artists(self): artist_ids = self.get_ids_of_followed_artists() print("> {} followed artists found.".format(len(artist_ids))) releases_per_artist = self.get_releases_per_artist(artist_ids) return releases_per_artist def process(self, to_console=True): print("> Start ({}).".format(datetime.now())) releases = self.get_releases_of_artists() if to_console: send_to_console(releases, self.user_name) if settings.REALLY_SIMPLE_RSS_SERVER_URL: send_to_really_simple_rss_server(releases, self.user_name) if settings.SLACK_URL: send_to_slack(releases, self.user_name) print("> Done ({}).".format(datetime.now()))
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