jisaacso219 commited on
Commit
dc5eb33
Β·
verified Β·
1 Parent(s): e4f4d5f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -50
app.py CHANGED
@@ -1,22 +1,26 @@
1
  import os
 
2
  import random
3
  import gradio as gr
4
  import spotipy
5
  from spotipy.oauth2 import SpotifyOAuth
6
 
7
- # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
8
- # 1) Spotify App Credentials (in HF secrets)
9
  CLIENT_ID = os.environ["SPOTIFY_CLIENT_ID"]
10
  CLIENT_SECRET = os.environ["SPOTIFY_CLIENT_SECRET"]
11
 
12
- # 2) Redirect URI must exactly match your Spotify Dashboard
13
  REDIRECT_URI = "https://jisaacso219-rng-shuffle.hf.space/"
14
 
15
- # 3) Scopes for controlling playback & reading playlists
16
- SCOPE = "user-read-playback-state user-modify-playback-state playlist-read-private"
 
 
 
 
 
17
 
18
- # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
19
- # Spotify OAuth helper
20
  sp_oauth = SpotifyOAuth(
21
  client_id=CLIENT_ID,
22
  client_secret=CLIENT_SECRET,
@@ -32,88 +36,178 @@ def get_auth_url():
32
  return sp_oauth.get_authorize_url()
33
 
34
  def check_login(code: str):
 
 
 
 
 
 
35
  global sp, user_playlists
36
  if not code:
37
- # Still waiting for Spotify to redirect back with ?code=
38
  return (
39
  gr.update(visible=False), # status
40
- gr.update(visible=False), # playlist dropdown
41
- gr.update(visible=False), # shuffle button
42
  )
43
 
44
- # Exchange code for an access token
45
- token = sp_oauth.get_access_token(code)
46
- sp = spotipy.Spotify(auth=token)
 
47
 
48
- # Fetch first 50 playlists
49
  items = sp.current_user_playlists(limit=50)["items"]
50
  user_playlists = {p["name"]: p["id"] for p in items}
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  return (
53
- gr.update(visible=True, value="βœ… Logged in! Pick a playlist below."),
54
  gr.update(visible=True, choices=list(user_playlists.keys())),
55
- gr.update(visible=True),
56
  )
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def shuffle_and_play(playlist_name: str):
 
 
 
 
59
  pid = user_playlists.get(playlist_name)
60
  if not pid:
61
- return "⚠️ No playlist selected. Please choose one.",
62
 
63
- # Gather all track URIs
64
  tracks, results = [], sp.playlist_tracks(pid)
65
  tracks.extend(results["items"])
66
  while results["next"]:
67
  results = sp.next(results)
68
  tracks.extend(results["items"])
69
  uris = [t["track"]["uri"] for t in tracks if t["track"]]
70
-
71
- # RNG shuffle
72
  random.shuffle(uris)
73
 
74
- # Send to first active device
75
- devices = sp.devices().get("devices", [])
76
- if not devices:
77
- return "⚠️ No active Spotify device found. Open your Spotify app and try again."
78
-
79
- sp.start_playback(device_id=devices[0]["id"], uris=uris)
80
- return f"▢️ Playing shuffled **{playlist_name}** on your active device!"
81
-
82
- # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  with gr.Blocks() as demo:
84
- gr.Markdown("## 🎡 RNG Spotify Playlist Shuffler")
85
- gr.Markdown("1) Login β†’ 2) Pick a playlist β†’ 3) Shuffle & play")
86
-
87
- # Step 1: Login button (same tab)
 
 
 
 
 
 
 
 
 
 
 
88
  login_btn = gr.Button("πŸ” Step 1: Login to Spotify")
89
  login_btn.click(
90
- None, None, None,
91
  js=f"() => window.location.href = '{get_auth_url()}'"
92
  )
93
 
94
- # Hidden textbox to capture OAuth code
95
- code_box = gr.Textbox(visible=False, elem_id="auth_code")
96
- login_status = gr.Markdown(visible=False)
97
- playlist_dd = gr.Dropdown(choices=[], label="Step 2: Select a Playlist", visible=False)
98
- shuffle_button = gr.Button("πŸ”€ Step 3: Shuffle & Play", visible=False)
99
- result = gr.Markdown(visible=False)
 
 
 
100
 
101
- # On every load (or redirect back with ?code=), call check_login
102
  demo.load(
103
  fn=check_login,
104
  inputs=[code_box],
105
- outputs=[login_status, playlist_dd, shuffle_button],
106
  js="() => new URLSearchParams(window.location.search).get('code') || ''"
107
  )
108
 
109
- # Finally: when they click Shuffle & Play
110
- shuffle_button.click(
111
- shuffle_and_play,
112
- inputs=[playlist_dd],
113
- outputs=[result]
114
- )
115
 
116
  if __name__ == "__main__":
117
- # No SSR, default port
118
  demo.launch(server_name="0.0.0.0", ssr_mode=False)
119
-
 
1
  import os
2
+ import json
3
  import random
4
  import gradio as gr
5
  import spotipy
6
  from spotipy.oauth2 import SpotifyOAuth
7
 
8
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
9
+ # 1) Spotify credentials (stored in HF Secrets)
10
  CLIENT_ID = os.environ["SPOTIFY_CLIENT_ID"]
11
  CLIENT_SECRET = os.environ["SPOTIFY_CLIENT_SECRET"]
12
 
13
+ # 2) Your Space’s URL must match this Redirect URI exactly
14
  REDIRECT_URI = "https://jisaacso219-rng-shuffle.hf.space/"
15
 
16
+ # 3) Include `streaming` for Web Playback SDK + playback scopes
17
+ SCOPE = (
18
+ "streaming "
19
+ "user-read-playback-state "
20
+ "user-modify-playback-state "
21
+ "playlist-read-private"
22
+ )
23
 
 
 
24
  sp_oauth = SpotifyOAuth(
25
  client_id=CLIENT_ID,
26
  client_secret=CLIENT_SECRET,
 
36
  return sp_oauth.get_authorize_url()
37
 
38
  def check_login(code: str):
39
+ """
40
+ Called on page‐load via demo.load(). Once we have a `code`:
41
+ - exchange it for an access token
42
+ - fetch playlists
43
+ - inject the Web Playback SDK + token + debug hooks
44
+ """
45
  global sp, user_playlists
46
  if not code:
 
47
  return (
48
  gr.update(visible=False), # status
49
+ gr.update(visible=False), # dropdown
50
+ gr.update(visible=False), # sdk_html
51
  )
52
 
53
+ # 1) Exchange code for token
54
+ tokinfo = sp_oauth.get_access_token(code, as_dict=True)
55
+ access_token = tokinfo["access_token"]
56
+ sp = spotipy.Spotify(auth=access_token)
57
 
58
+ # 2) Fetch your playlists
59
  items = sp.current_user_playlists(limit=50)["items"]
60
  user_playlists = {p["name"]: p["id"] for p in items}
61
 
62
+ # 3) Build SDK + injection scripts
63
+ sdk_js = f"""
64
+ <script>
65
+ // If still sandboxed in an iframe, break out:
66
+ if (window.self !== window.top) {{
67
+ window.top.location.href = window.location.href;
68
+ }}
69
+ console.log("[SDK] Setting ACCESS_TOKEN");
70
+ window.ACCESS_TOKEN = "{access_token}";
71
+ </script>
72
+ <script src="https://sdk.scdn.co/spotify-player.js"></script>
73
+ <script>
74
+ window.onSpotifyWebPlaybackSDKReady = () => {{
75
+ console.log("[SDK] SDK Ready");
76
+ const player = new Spotify.Player({{
77
+ name: 'RNG Web Player',
78
+ getOAuthToken: cb => cb(window.ACCESS_TOKEN),
79
+ volume: 0.5
80
+ }});
81
+ ['initialization_error','authentication_error','account_error','playback_error']
82
+ .forEach(evt => player.addListener(evt, e => console.error(`[SDK ${evt}]`, e)));
83
+ player.addListener('ready', ({ device_id }) => {{
84
+ console.log('[SDK] Player ready, device_id:', device_id);
85
+ window._webDeviceId = device_id;
86
+ }});
87
+ player.connect().then(success => console.log('[SDK] connect()', success));
88
+ }};
89
+ </script>
90
+ """
91
+
92
  return (
93
+ gr.update(visible=True, value="βœ… Logged in! Select a playlist below."),
94
  gr.update(visible=True, choices=list(user_playlists.keys())),
95
+ gr.update(visible=True, value=sdk_js),
96
  )
97
 
98
+ def load_playlist_info(playlist_name: str):
99
+ """
100
+ When a playlist is selected, show its artwork, name, and description.
101
+ """
102
+ pid = user_playlists[playlist_name]
103
+ data = sp.playlist(pid)
104
+ img = data["images"][0]["url"] if data["images"] else ""
105
+ owner = data["owner"]["display_name"]
106
+ desc = data.get("description", "")
107
+ html = f"""
108
+ <img src="{img}" width="300" style="border-radius:8px;"/><br/>
109
+ <strong>{playlist_name}</strong> by {owner}<br/>{desc}
110
+ """
111
+ return html, gr.update(visible=True)
112
+
113
  def shuffle_and_play(playlist_name: str):
114
+ """
115
+ Gather all track URIs, RNG-shuffle them, then fire off the Web Playback SDK
116
+ to play them in-browser on window._webDeviceId.
117
+ """
118
  pid = user_playlists.get(playlist_name)
119
  if not pid:
120
+ return gr.update(value="⚠️ No playlist selected.", visible=True)
121
 
122
+ # fetch + shuffle
123
  tracks, results = [], sp.playlist_tracks(pid)
124
  tracks.extend(results["items"])
125
  while results["next"]:
126
  results = sp.next(results)
127
  tracks.extend(results["items"])
128
  uris = [t["track"]["uri"] for t in tracks if t["track"]]
 
 
129
  random.shuffle(uris)
130
 
131
+ # in-page JS to start playback
132
+ uris_json = json.dumps(uris)
133
+ play_js = f"""
134
+ <div id="player_debug" style="color:white;font-family:monospace;"></div>
135
+ <script>
136
+ console.log("[JS] Starting playback with", window._webDeviceId);
137
+ console.log("[JS] URIs count:", {len(uris)});
138
+ (async () => {{
139
+ try {{
140
+ const resp = await fetch(
141
+ 'https://api.spotify.com/v1/me/player/play?device_id=' + window._webDeviceId,
142
+ {{
143
+ method: 'PUT',
144
+ headers: {{
145
+ 'Authorization': 'Bearer ' + window.ACCESS_TOKEN,
146
+ 'Content-Type': 'application/json'
147
+ }},
148
+ body: JSON.stringify({{ uris: {uris_json} }})
149
+ }}
150
+ );
151
+ const text = await resp.text();
152
+ console.log("[JS] fetch status:", resp.status, text);
153
+ document.getElementById("player_debug").innerText =
154
+ `[JS] status: ${{resp.status}} – ${{text}}`;
155
+ }} catch(e) {{
156
+ console.error("[JS] Playback error:", e);
157
+ document.getElementById("player_debug").innerText =
158
+ "[JS] Playback error: " + e;
159
+ }}
160
+ }})();
161
+ </script>
162
+ """
163
+ return gr.update(value="▢️ Now playing in-browser!" + play_js, visible=True)
164
+
165
+ # β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
166
  with gr.Blocks() as demo:
167
+ # 0) Auto-bust from any huggingface.co iframe
168
+ gr.HTML("""
169
+ <script>
170
+ if (window.self !== window.top) {
171
+ window.top.location.href = window.location.href;
172
+ }
173
+ </script>
174
+ """)
175
+ # 0b) Fallback standalone link
176
+ gr.HTML('<a href="https://jisaacso219-rng-shuffle.hf.space/" '
177
+ 'target="_blank">πŸ”— Open in standalone tab</a>')
178
+
179
+ gr.Markdown("**1)** Login &nbsp;&nbsp; **2)** Select a playlist &nbsp;&nbsp; **3)** Shuffle & Play in-browser")
180
+
181
+ # Step 1: Login to Spotify in SAME tab
182
  login_btn = gr.Button("πŸ” Step 1: Login to Spotify")
183
  login_btn.click(
184
+ fn=None, inputs=None, outputs=None,
185
  js=f"() => window.location.href = '{get_auth_url()}'"
186
  )
187
 
188
+ # These will be revealed after login
189
+ code_box = gr.Textbox(visible=False, elem_id="auth_code")
190
+ status = gr.Markdown(visible=False)
191
+ playlist_dd= gr.Dropdown(choices=[], label="Select Playlist", visible=False)
192
+ sdk_html = gr.HTML(visible=False)
193
+
194
+ info_html = gr.HTML(visible=False)
195
+ shuffle_btn= gr.Button("πŸ”€ Step 3: Shuffle & Play", visible=False)
196
+ result = gr.HTML(visible=False)
197
 
198
+ # On page-load (or after redirect with ?code), run check_login
199
  demo.load(
200
  fn=check_login,
201
  inputs=[code_box],
202
+ outputs=[status, playlist_dd, sdk_html],
203
  js="() => new URLSearchParams(window.location.search).get('code') || ''"
204
  )
205
 
206
+ # Show playlist info, then reveal shuffle button
207
+ playlist_dd.change(load_playlist_info, [playlist_dd], [info_html, shuffle_btn])
208
+
209
+ # Shuffle & play via Web Playback SDK
210
+ shuffle_btn.click(shuffle_and_play, [playlist_dd], [result])
 
211
 
212
  if __name__ == "__main__":
 
213
  demo.launch(server_name="0.0.0.0", ssr_mode=False)