Module which contains the web client routes and functions.
downloads(request)
async
Handle the downloads page request.
Source code in spotdl/web/routes.py
59
60
61
62
63
64
65
66
67
68
69
70 | @router.get("/downloads")
async def downloads(request: Request):
"""
Handle the downloads page request.
"""
return templates.TemplateResponse(
name="downloads.html.j2",
context={
"request": request,
"__version__": __version__,
},
)
|
gen_download(signals)
async
Generate the download process for the client.
Source code in spotdl/web/routes.py
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351 | async def gen_download(signals: Signals):
"""
Generate the download process for the client.
"""
client = Client.get_instance(signals.client_id)
if client is None:
app_state.logger.warning(
f"Client {signals.client_id} not found, cannot load downloads."
)
yield SSE.patch_elements(
templates.get_template("status-disconnected.html.j2").render()
)
return
app_state.logger.info(
f"[{signals.client_id}] Download requested: {signals.song_url}"
)
yield SSE.patch_elements(
f"""
<button id="download-{signals.song_url}" class="btn btn-primary btn-square loading">
</button>
"""
)
if app_state.web_settings.get("web_use_output_dir", False):
client.downloader.settings["output"] = client.downloader_settings["output"]
else:
client.downloader.settings["output"] = str(
(get_spotdl_path() / f"web/sessions/{client.client_id}").absolute()
)
try:
# Fetch song metadata
song = Song.from_url(signals.song_url)
app_state.logger.info(f"Downloading song: {song}")
# Download Song
_, path = await client.downloader.pool_download(song)
yield SSE.patch_elements(
f"""
<button id="download-{signals.song_url}" class="btn btn-primary btn-square">
<iconify-icon icon="clarity:check-line" style="font-size: 24px"></iconify-icon>
</button>
"""
)
if path is None:
app_state.logger.error(f"Failure downloading {song.name}")
# return str(path.absolute())
except Exception as exception:
app_state.logger.error(f"Error downloading! {exception}")
|
Handle the search input rotating placeholder component.
Source code in spotdl/web/routes.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421 | @router.get("/client/component/search-input-rotating-placeholder")
@datastar_response
async def handle_client_component_search_input_rotating_placeholder():
"""
Handle the search input rotating placeholder component.
"""
app_state.logger.info("Loading rotating-placeholder...")
placeholder_items = [
"All Eyes On Me - Bo Burnham",
"https://open.spotify.com/track/4vfN00PlILRXy5dcXHQE9M?si=e4d9e7c044dd4a8f",
"Lil Wayne",
"Drive - Miley Cyrus",
"Sofia - TMG",
"Lightning Crashes - Live",
]
index = 0
while True:
t = templates.get_template("search-input-rotating-placeholder.html.j2").render(
placeholder_item=placeholder_items[index]
)
yield SSE.patch_elements(t)
await asyncio.sleep(5)
index += 1
if index >= len(placeholder_items):
index = 0
|
handle_get_client_component_settings(datastar_signals)
async
Handle the request for the client settings component.
Source code in spotdl/web/routes.py
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394 | @router.get("/client/component/settings-content")
@datastar_response
async def handle_get_client_component_settings(datastar_signals: ReadSignals):
"""
Handle the request for the client settings component.
"""
signals = handle_signals(datastar_signals)
client = Client.get_instance(signals.client_id)
if client is None:
app_state.logger.warning(
f"Client {signals.client_id} not found, cannot update settings."
)
yield SSE.patch_elements(
templates.get_template("status-disconnected.html.j2").render()
)
return
app_state.logger.info(f"[{signals.client_id}] Loading settings view...")
# clear state
yield SSE.patch_elements("""<div id="component-settings-content"></div>""")
# render the settings content
yield SSE.patch_elements(
templates.get_template("settings-content.html.j2").render(
downloader_settings=client.downloader_settings,
AUDIO_PROVIDERS=AUDIO_PROVIDERS,
LYRICS_PROVIDERS=LYRICS_PROVIDERS,
FORMATS=FFMPEG_FORMATS.keys(),
)
)
# spotify_client = SpotifyClient()
# print(f"{spotify_client = }")
yield SSE.patch_signals(
{
"downloader_settings": client.downloader_settings,
# "spotify_settings": {
# "client_id": spotify_client.credential_manager.client_id
# },
}
)
|
handle_get_client_downloads(datastar_signals)
async
Handle the retrieval of client downloads.
Source code in spotdl/web/routes.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179 | @router.get("/client/downloads")
@datastar_response
async def handle_get_client_downloads(datastar_signals: ReadSignals):
"""
Handle the retrieval of client downloads.
"""
app_state.logger.info("Loading downloads...")
signals = handle_signals(datastar_signals)
app_state.logger.info(f"[{signals.client_id}] Downloads requested.")
client = Client.get_instance(signals.client_id)
if client is None:
app_state.logger.warning(
f"[{signals.client_id}] Client not found, cannot load downloads."
)
yield SSE.patch_elements(
templates.get_template("status-disconnected.html.j2").render()
)
return
while True:
client_song_downloads = (
client.downloader.progress_handler.progress_tracker.songs
)
yield SSE.patch_elements(
templates.get_template("download-list.html.j2").render(
client_song_downloads=client_song_downloads.values()
)
)
await asyncio.sleep(1)
|
handle_get_client_load(datastar_signals)
async
Handle the loading of the client.
Source code in spotdl/web/routes.py
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
119
120 | @router.get("/client/load")
@datastar_response
async def handle_get_client_load(datastar_signals: ReadSignals):
"""
Handle the loading of the client.
"""
app_state.logger.info("Loading client...")
signals = handle_signals(datastar_signals)
if not signals.client_id:
# Generate a new client ID if not provided
app_state.logger.warning("No client ID provided, generating a new one.")
signals.client_id = uuid.uuid4().hex
client = Client(signals.client_id)
else:
found_client = Client.get_instance(signals.client_id)
if found_client is None:
# Create a new client if not found
app_state.logger.warning(
f"Client {signals.client_id} not found, creating new client..."
)
signals.client_id = uuid.uuid4().hex
client = Client(signals.client_id)
else:
client = found_client
await client.connect()
# First send the client ID and then the home template.
yield SSE.patch_elements("""<div id="status"></div>""")
# Send the client ID to the client
yield SSE.patch_signals(
{
"client_id": client.client_id,
}
)
try:
while True:
yield SSE.patch_elements(
f"""<div id="overall-completed-tasks">
{len(client.downloader.progress_handler.progress_tracker.songs)}
</div>"""
)
await asyncio.sleep(1)
finally:
app_state.logger.info(f"[{signals.client_id}] Unloading client...")
await client.disconnect()
|
handle_get_client_search(datastar_signals)
async
Handle the search input.
Source code in spotdl/web/routes.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 | @router.get("/client/search")
@datastar_response
async def handle_get_client_search(datastar_signals: ReadSignals):
"""
Handle the search input.
"""
app_state.logger.info("Loading search...")
signals = handle_signals(datastar_signals)
app_state.logger.info(f"[{signals.client_id}] Search term: {signals.search_term}")
is_valid_url = validate_search_term(signals.search_term)
if is_valid_url:
# redirect client to downloads page
app_state.logger.info(
f"[{signals.client_id}] Valid URL detected, redirecting to downloads..."
)
yield SSE.redirect("/downloads")
signals.song_url = signals.search_term
async for update in gen_download(signals):
yield update
songs = get_search_results(signals.search_term)
yield SSE.patch_elements(
templates.get_template("search-list.html.j2").render(
songs=songs,
)
)
|
handle_get_client_settings(datastar_signals)
async
Handle the retrieval of client settings.
Source code in spotdl/web/routes.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 | @router.get("/client/settings")
@datastar_response
async def handle_get_client_settings(datastar_signals: ReadSignals):
"""
Handle the retrieval of client settings.
"""
signals = handle_signals(datastar_signals)
client = Client.get_instance(signals.client_id)
if client is None:
app_state.logger.warning(
f"Client {signals.client_id} not found, cannot update settings."
)
yield SSE.patch_elements(
templates.get_template("status-disconnected.html.j2").render()
)
return
app_state.logger.info(f"[{signals.client_id}] Sending client settings...")
yield SSE.patch_signals(
{
"downloader_settings": client.downloader_settings,
}
)
|
handle_post_client_download(datastar_signals)
async
Handle the download request from the client.
Source code in spotdl/web/routes.py
286
287
288
289
290
291
292
293
294 | @router.post("/client/download/")
@datastar_response
async def handle_post_client_download(datastar_signals: ReadSignals):
"""
Handle the download request from the client.
"""
signals = handle_signals(datastar_signals)
async for update in gen_download(signals):
yield update
|
handle_post_client_settings(datastar_signals)
async
Handle the update of client settings.
Source code in spotdl/web/routes.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283 | @router.post("/client/settings")
@datastar_response
async def handle_post_client_settings(datastar_signals: ReadSignals):
"""
Handle the update of client settings.
"""
signals = handle_signals(datastar_signals)
client = Client.get_instance(signals.client_id)
if client is not None:
app_state.logger.info(f"[{signals.client_id}] Updating settings...")
if signals.downloader_settings is not None:
client.downloader_settings = signals.downloader_settings
yield SSE.patch_elements(
"""
<div id="settings-status">
<div id="settings-is-saved" class="alert alert-success shadow-lg">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current flex-shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>Changes saved</span>
</div>
</div>
</div>
"""
)
yield SSE.patch_signals(
{
"downloader_settings": client.downloader_settings,
}
)
else:
app_state.logger.warning(
f"[{signals.client_id}] Client not found, cannot update settings."
)
yield SSE.patch_elements(
templates.get_template("status-disconnected.html.j2").render()
)
yield SSE.patch_elements(
"""
<div id="settings-status">
<div id="settings-is-not-saved" class="alert alert-error shadow-lg">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current
flex-shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Error! Unable to save settings</span>
</div>
</div>
</div>
"""
)
# sleep for 3 seconds then clear the status message
await asyncio.sleep(3)
yield SSE.patch_elements(
"""
<div id="settings-status">
</div>
"""
)
|
home(request)
async
Handle the home page request.
Source code in spotdl/web/routes.py
37
38
39
40
41
42
43
44
45 | @router.get("/")
async def home(request: Request):
"""
Handle the home page request.
"""
return templates.TemplateResponse(
name="home.html.j2",
context={"request": request, "__version__": __version__},
)
|
search(q, request)
async
Handle the search input.
Source code in spotdl/web/routes.py
48
49
50
51
52
53
54
55
56 | @router.get("/search")
async def search(q: Optional[str], request: Request):
"""
Handle the search input.
"""
return templates.TemplateResponse(
name="search.html.j2",
context={"request": request, "__version__": __version__, "search_term": q},
)
|