-
Notifications
You must be signed in to change notification settings - Fork 0
/
spotify.py
118 lines (94 loc) · 3.37 KB
/
spotify.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"""DJ GPT CLI
Module to deal with all the Spotify API interactions and functionality
"""
import time
from dataclasses import dataclass
from functools import cache
from typing import NamedTuple, Dict, Optional, List
import spotipy
from spotipy import SpotifyException
from utils import debug, retry, CONSOLE
# Spotify globals
S_DEVICE_ID = S_CLIENT_ID = S_SECRET_ID = None
class Spotify(NamedTuple):
"""Store spotify API data.
Tuple of what we care about the URI/URLs and stash the rest just incase
"""
url: str
uri: str
stash: Dict
@dataclass
class Track:
"""Store track data.
Simple data class to deal with tracks from GPT and spotify queries.
"""
artist: str
trackname: str
genre: Optional[str] = None
reason: Optional[str] = None
quality: Optional[float] = None
error: Optional[str] = None
@property
def spotify(self) -> Optional[Spotify]:
return search_spotify(self.artist, self.trackname)
@cache
def get_spotify():
"""Get the cached spotify API caller.
On first ever use you will be asked to authorize the app use against your Spotify account (say yes in the browser)
you wont get asked ever again.
"""
# TODO: work out if we can avoid these globals in a nice way with Typer
global S_CLIENT_ID, S_SECRET_ID
oauth = spotipy.SpotifyOAuth(
client_id=S_CLIENT_ID,
client_secret=S_SECRET_ID,
redirect_uri="https://localhost:8888/callback",
scope="user-read-playback-state user-modify-playback-state",
show_dialog=True,
)
# Create/get cached token for a session with the API
token = oauth.get_access_token(as_dict=False)
return spotipy.Spotify(auth=token)
def wait_for_spotify():
spotify = get_spotify()
track_name = artist_name = None
with CONSOLE.status("[bold green]Waiting for Spotify...") as status:
while(True):
playback = spotify.current_playback()
if playback is None:
break
is_playing = playback["is_playing"]
if is_playing:
track_name = playback["item"]["name"]
artist_name = playback["item"]["artists"][0]["name"]
else:
break
status.update(f"[bold green]Waiting for Spotify to finish, listening to {track_name} by {artist_name}")
time.sleep(30)
CONSOLE.log("[bold red]Ready to DJ!")
return Track(trackname=track_name, artist=artist_name)
def search_spotify(artist: str, trackname: str) -> Optional[Spotify]:
"""Search Spotify using an artist and track name, get back an exteranl URL"""
try:
search_results = get_spotify().search(
f"artist:{artist} track:{trackname}",
limit=1,
offset=0,
type='track'
)
if not search_results:
return None
track = search_results['tracks']['items'][0]
url = track['external_urls']['spotify']
uri = track['uri']
return Spotify(url, uri, search_results)
except Exception as e:
debug(e)
return None
@retry(
exception_class=SpotifyException,
prompt="Try again with Spotify? (If the error is 'No active device found' just press play/pause in Spotify)",
none_is_fail=False
)
def play_on_spotify(tracks: List[Track]):
get_spotify().start_playback(uris=[t.spotify.uri for t in tracks if t.spotify])