From 9bf3e090bb1251358192be6fe74555388b2d09fd Mon Sep 17 00:00:00 2001 From: Mathieu B Date: Thu, 29 Jul 2021 23:04:31 +0200 Subject: [PATCH] Fully working spotify song query (added spotipy) --- .../skills/entertainement/spotify/__init__.py | 10 ++- .../skills/entertainement/spotify/spotify.py | 61 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 jarvis/skills/entertainement/spotify/spotify.py diff --git a/jarvis/skills/entertainement/spotify/__init__.py b/jarvis/skills/entertainement/spotify/__init__.py index 5f66dd0..8454245 100644 --- a/jarvis/skills/entertainement/spotify/__init__.py +++ b/jarvis/skills/entertainement/spotify/__init__.py @@ -1,5 +1,6 @@ from jarvis.skills import Skill, SkillRegistering from jarvis.skills.decorators import intent_file_handler +from jarvis.skills.entertainement.spotify import spotify class SpotifySkill(Skill, metaclass=SkillRegistering): @@ -8,4 +9,11 @@ class SpotifySkill(Skill, metaclass=SkillRegistering): @intent_file_handler("play_a_song.intent", "PlaySongWithSpotifyIntent") def handle_play_a_song(self, data): - print("Play song") + if 'song' in data: + song_lists_matching = spotify.query_song(data['song']) + if 'singer' in data: + song_lists_matching = spotify.query_song(data['song'], data['singer']) + + if song_lists_matching is not None and len(song_lists_matching) >= 1: + print(song_lists_matching[0]['uri'] + " / " + song_lists_matching[0]['name'] + " / " + + song_lists_matching[0]['artists'][0]['name']) diff --git a/jarvis/skills/entertainement/spotify/spotify.py b/jarvis/skills/entertainement/spotify/spotify.py new file mode 100644 index 0000000..7f0f42a --- /dev/null +++ b/jarvis/skills/entertainement/spotify/spotify.py @@ -0,0 +1,61 @@ +import re + +import spotipy +from lingua_franca.parse import fuzzy_match +from spotipy import SpotifyOAuth + +from jarvis.utils import config_utils + +scope = "user-read-playback-state, user-modify-playback-state, user-read-currently-playing" +sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope, + client_id=config_utils.get_in_config("SPOTIFY_CLIENT_ID"), + client_secret=config_utils.get_in_config("SPOTIFY_CLIENT_SECRET"), + redirect_uri='http://localhost:8888/callback/', + open_browser=False)) +# TODO: Investigate the open_browser and automatic auth renewing without user interaction + + +def get_spotify(): + return sp + + +def query_song(song, artist=None): + if song is not None and artist is not None: + song_search = '*{}* artist:{}'.format(song, artist) + else: + song_search = song + + data = get_spotify().search(q=song_search, type='track')['tracks']['items'] + if data and len(data) > 0: + tracks = [(best_confidence(d['name'], song), d) + for d in data] + tracks.sort(key=lambda x: x[0]) + + tracks.reverse() # Place best matches first + + # Find pretty similar tracks to the best match + tracks = [t for t in tracks if t[0] > tracks[0][0] - 0.1] + + # Sort remaining tracks by popularity + tracks.sort(key=lambda x: x[1]['popularity']) + # print([(t[0], t[1]['name'], t[1]['artists'][0]['name']) for t in tracks]) # DEBUG + data = [tracks[-1][1]] + + # return tracks[-1][0], {'data': data, 'name': None, 'type': 'track'} + return data + + +def best_confidence(title, query): + """Find best match for a title against a query. + Some titles include ( Remastered 2016 ) and similar info. This method + will test the raw title and a version that has been parsed to remove + such information. + Arguments: + title: title name from spotify search + query: query from user + Returns: + (float) best condidence + """ + best = title.lower() + best_stripped = re.sub(r'(\(.+\)|-.+)$', '', best).strip() + return max(fuzzy_match(best, query), fuzzy_match(best_stripped, query))