Skip to content

Commit 49f6e3a

Browse files
authored
Deprecate web-ui repo (#2463)
This removed the need for a second repo for the web content by making all HTML SSG using Jinja and Datastar. The old API routes are not being used now but I left them in place since they are quite useful. After merging and releasing, the old https://github.com/spotDL/web-ui/ can be archived.
2 parents 1df373c + 39c868e commit 49f6e3a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1579
-507
lines changed

.vscode/settings.json

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
{
2-
"cSpell.words": [
3-
"isrc",
4-
"pdoc",
5-
"spotdl"
6-
],
2+
"cSpell.words": ["datastar", "isrc", "pdoc", "spotdl"],
73
"python.formatting.provider": "black",
8-
"python.testing.pytestArgs": [
9-
"tests"
10-
],
4+
"python.testing.pytestArgs": ["tests"],
115
"python.testing.unittestEnabled": false,
126
"python.testing.pytestEnabled": true
13-
}
7+
}

docs/usage.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,6 @@ If you don't want the config to load automatically, change the `load_config` opt
338338
"ca_file": null,
339339
"allowed_origins": null,
340340
"keep_sessions": false,
341-
"force_update_gui": false,
342-
"web_gui_repo": null,
343-
"web_gui_location": null
344341
}
345342
```
346343

@@ -435,7 +432,7 @@ Spotify options:
435432
--max-retries MAX_RETRIES
436433
The maximum number of retries to perform when getting metadata.
437434
--headless Run in headless mode.
438-
--use-cache-file Use the cache file to get metadata. It's located under C:\Users\user\.spotdl\.spotify_cache or ~/.spotdl/.spotify_cache under linux. It only caches tracks and
435+
--use-cache-file Use the cache file to get metadata. It's located under C:\Users\<user>\.spotdl\.spotify_cache or ~/.spotdl/.spotify_cache under linux. It only caches tracks and
439436
gets updated whenever spotDL gets metadata from Spotify. (It may provide outdated metadata use with caution)
440437
441438
FFmpeg options:
@@ -510,11 +507,6 @@ Web options:
510507
The allowed origins for the web server.
511508
--web-use-output-dir Use the output directory instead of the session directory for downloads. (This might cause issues if you have multiple users using the web-ui at the same time)
512509
--keep-sessions Keep the session directory after the web server is closed.
513-
--force-update-gui Refresh the web server directory with a fresh git checkout
514-
--web-gui-repo WEB_GUI_REPO
515-
Custom web gui repo to use for the web server. Example: https://github.com/spotdl/web-ui/tree/master/dist
516-
--web-gui-location WEB_GUI_LOCATION
517-
Path to the web gui directory to use for the web server.
518510
--enable-tls Enable TLS on the web server.
519511
--cert-file CERT_FILE
520512
File Path to the TLS Certificate (PEM format).

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ dependencies = [
4646
"syncedlyrics>=1.0.1,<2",
4747
"soundcloud-v2>=1.6.0,<2",
4848
"websockets~=14.1",
49+
"datastar-py>=0.6.3",
50+
"jinja2>=3.1.5",
51+
"python-multipart>=0.0.20",
4952
]
5053

5154
[project.urls]

spotdl/console/web.py

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import signal
1010
import sys
1111
import webbrowser
12-
from pathlib import Path
1312

1413
from fastapi import Depends, FastAPI
1514
from fastapi.middleware.cors import CORSMiddleware
@@ -26,8 +25,8 @@
2625
app_state,
2726
fix_mime_types,
2827
get_current_state,
29-
router,
3028
)
29+
from spotdl.web import api, routes
3130

3231
__all__ = ["web"]
3332

@@ -66,41 +65,15 @@ def web(web_settings: WebOptions, downloader_settings: DownloaderOptions):
6665

6766
downloader_settings["simple_tui"] = True
6867

69-
# Download web app from GitHub if not already downloaded or force flag set
70-
web_app_dir = get_web_ui_path()
71-
dist_dir = web_app_dir / "dist"
72-
if (not dist_dir.exists() or web_settings["force_update_gui"]) and web_settings[
73-
"web_gui_location"
74-
] is None:
75-
if web_settings["web_gui_repo"] is None:
76-
gui_repo = "https://github.com/spotdl/web-ui/tree/master/dist"
77-
else:
78-
gui_repo = web_settings["web_gui_repo"]
79-
80-
logger.info("Updating web app from %s", gui_repo)
81-
82-
download_github_dir(
83-
gui_repo,
84-
output_dir=str(web_app_dir),
85-
)
86-
web_app_dir = Path(os.path.join(web_app_dir, "dist")).resolve()
87-
elif web_settings["web_gui_location"]:
88-
web_app_dir = Path(web_settings["web_gui_location"]).resolve()
89-
logger.info("Using custom web app location: %s", web_app_dir)
90-
else:
91-
logger.info(
92-
"Using cached web app. To update use the `--force-update-gui` flag."
93-
)
94-
web_app_dir = Path(os.path.join(web_app_dir, "dist")).resolve()
95-
9668
app_state.api = FastAPI(
9769
title="spotDL",
9870
description="Download music from Spotify",
9971
version=__version__,
10072
dependencies=[Depends(get_current_state)],
10173
)
10274

103-
app_state.api.include_router(router)
75+
app_state.api.include_router(api.router)
76+
app_state.api.include_router(routes.router)
10477

10578
# Add the CORS middleware
10679
app_state.api.add_middleware(
@@ -116,17 +89,18 @@ def web(web_settings: WebOptions, downloader_settings: DownloaderOptions):
11689
)
11790

11891
# Add the static files
92+
web_app_dir = get_web_ui_path()
11993
app_state.api.mount(
120-
"/",
121-
SPAStaticFiles(directory=web_app_dir, html=True),
94+
"/assets",
95+
SPAStaticFiles(directory=web_app_dir / "assets", html=True),
12296
name="static",
12397
)
12498
protocol = "http"
12599
config = Config(
126100
app=app_state.api,
127101
host=web_settings["host"],
128102
port=web_settings["port"],
129-
workers=1,
103+
# workers=1,
130104
log_level=NAME_TO_LEVEL[downloader_settings["log_level"]],
131105
loop=app_state.loop, # type: ignore
132106
)

spotdl/download/downloader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,7 @@ def search_and_download( # pylint: disable=R0911
838838
generate_lrc(song, output_file)
839839

840840
display_progress_tracker.notify_complete()
841+
display_progress_tracker.set_path(str(output_file))
841842

842843
# Add the song to the known songs
843844
self.known_songs.get(song.url, []).append(output_file)

spotdl/download/progress_handler.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import logging
6+
from dataclasses import dataclass
67
from typing import Any, Callable, Dict, List, Optional
78

89
from rich import get_console
@@ -99,6 +100,64 @@ def render(self, task: Task) -> Text:
99100
return text
100101

101102

103+
@dataclass
104+
class ClientSongDownload:
105+
"""
106+
Represents the download progress of a single song for a client.
107+
"""
108+
109+
song: Song
110+
progress: int
111+
message: str
112+
path: Optional[str] = None
113+
114+
115+
class ProgressTracker:
116+
"""
117+
Tracks the progress of each song download.
118+
Similar to Rich Progress but without any TUI elements.
119+
"""
120+
121+
songs: Dict[str, ClientSongDownload] = {}
122+
123+
def add(self, song: Song):
124+
"""
125+
Add a song to the progress tracker.
126+
"""
127+
# check if exists
128+
if song.url in self.songs:
129+
return
130+
self.songs[song.url] = ClientSongDownload(
131+
song=song, progress=0, message="Processing"
132+
)
133+
134+
def update(self, song: Song, progress: int, message: str):
135+
"""
136+
Update the progress of a song in the progress tracker.
137+
"""
138+
if song.url in self.songs:
139+
self.songs[song.url].progress = progress
140+
self.songs[song.url].message = message
141+
else:
142+
self.songs[song.url] = ClientSongDownload(
143+
song=song, progress=progress, message=message
144+
)
145+
146+
def remove(self, song: Song):
147+
"""
148+
Remove a song from the progress tracker.
149+
"""
150+
if song.url in self.songs:
151+
del self.songs[song.url]
152+
153+
def set_path(self, song: Song, path: str):
154+
"""
155+
Set the download path for a song.
156+
"""
157+
if song.url in self.songs:
158+
self.songs[song.url].path = path
159+
160+
102161
class ProgressHandler:
103162
"""
104163
Class for handling the progress of a download, including the progress bar.
@@ -131,6 +190,8 @@ def __init__(
131190
self.quiet = logger.getEffectiveLevel() < 10
132191
self.overall_task_id: Optional[TaskID] = None
133192

193+
self.progress_tracker = ProgressTracker()
194+
134195
if not self.simple_tui:
135196
console = get_console()
136197

@@ -274,7 +335,9 @@ def __init__(self, parent, song: Song) -> None:
274335
self.progress: int = 0
275336
self.old_progress: int = 0
276337
self.status = ""
338+
self.path: Optional[str] = None
277339

340+
self.parent.progress_tracker.add(self.song)
278341
if not self.parent.simple_tui:
279342
self.task_id = self.parent.rich_progress_bar.add_task(
280343
description=escape(self.song_name),
@@ -299,9 +362,14 @@ def update(self, message=""):
299362
# The change in progress since last update
300363
delta = self.progress - self.old_progress
301364

365+
self.parent.progress_tracker.update(self.song, self.progress, message)
366+
if self.progress == 100 or message == "Error":
367+
if not self.parent.web_ui:
368+
self.parent.progress_tracker.remove(self.song)
369+
302370
if not self.parent.simple_tui:
303371
# Update the progress bar
304-
# `start_task` called everytime to ensure progress is remove from indeterminate state
372+
# `start_task` called every time to ensure progress is remove from indeterminate state
305373
self.parent.rich_progress_bar.start_task(self.task_id)
306374
self.parent.rich_progress_bar.update(
307375
self.task_id,
@@ -310,6 +378,9 @@ def update(self, message=""):
310378
completed=self.progress,
311379
)
312380

381+
# Refresh the progress bar to show the changes before it gets removed in case of 100%
382+
self.parent.rich_progress_bar.refresh()
383+
313384
# If task is complete
314385
if self.progress == 100 or message == "Error":
315386
self.parent.overall_completed_tasks += 1
@@ -438,3 +509,14 @@ def yt_dlp_progress_hook(self, data: Dict[str, Any]) -> None:
438509
self.progress = downloaded_bytes / file_bytes * 50
439510

440511
self.update("Downloading")
512+
513+
def set_path(self, path: str) -> None:
514+
"""
515+
Sets the path of the song.
516+
517+
### Arguments
518+
- path: The path to set.
519+
"""
520+
521+
self.path = path
522+
self.parent.progress_tracker.set_path(self.song, path)

spotdl/types/options.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@ class WebOptions(TypedDict):
106106
ca_file: Optional[str]
107107
allowed_origins: Optional[List[str]]
108108
keep_sessions: bool
109-
force_update_gui: bool
110-
web_gui_repo: Optional[str]
111-
web_gui_location: Optional[str]
112109

113110

114111
class SpotDLOptions(SpotifyOptions, DownloaderOptions, WebOptions):
@@ -202,9 +199,6 @@ class WebOptionalOptions(TypedDict, total=False):
202199
ca_file: Optional[str]
203200
allowed_origins: Optional[str]
204201
keep_sessions: bool
205-
force_update_gui: bool
206-
web_gui_repo: Optional[str]
207-
web_gui_location: Optional[str]
208202

209203

210204
class SpotDLOptionalOptions(

spotdl/types/song.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,15 @@ def json(self) -> Dict[str, Any]:
278278

279279
return asdict(self)
280280

281+
# def __json__(self) -> str:
282+
# """
283+
# Returns a JSON string of the song's data.
284+
285+
# ### Returns
286+
# - The JSON string.
287+
# """
288+
# return json.dumps(self.json)
289+
281290

282291
@dataclass(frozen=True)
283292
class SongList:

spotdl/utils/arguments.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -684,32 +684,6 @@ def parse_web_options(parser: _ArgumentGroup):
684684
help="Keep the session directory after the web server is closed.",
685685
)
686686

687-
# Add keep sessions argument
688-
parser.add_argument(
689-
"--force-update-gui",
690-
action="store_const",
691-
const=True,
692-
default=False,
693-
help="Refresh the web server directory with a fresh git checkout",
694-
)
695-
696-
# Add custom web gui repo
697-
parser.add_argument(
698-
"--web-gui-repo",
699-
type=str,
700-
help=(
701-
"Custom web gui repo to use for the web server. "
702-
"Example: https://github.com/spotdl/web-ui/tree/master/dist"
703-
),
704-
)
705-
706-
# Add custom web gui repo
707-
parser.add_argument(
708-
"--web-gui-location",
709-
type=str,
710-
help="Path to the web gui directory to use for the web server.",
711-
)
712-
713687
# Enable TLS for the web server
714688
parser.add_argument(
715689
"--enable-tls",

spotdl/utils/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,10 @@ def get_web_ui_path() -> Path:
162162
- If the web-ui directory does not exist, it will be created.
163163
"""
164164

165-
web_ui_path = get_spotdl_path() / "web-ui"
165+
# web_ui_path = get_spotdl_path() / "web-ui"
166+
# web_ui_path = get_spotdl_path() / "src" / "spotdl" / "web" / "static"
167+
web_ui_path = Path(__file__).parent.parent / "web" / "static"
168+
print("Web UI path:", web_ui_path)
166169

167170
if not web_ui_path.exists():
168171
os.mkdir(web_ui_path)
@@ -378,9 +381,6 @@ def get_parameter(cls, key):
378381
"ca_file": None,
379382
"allowed_origins": None,
380383
"keep_sessions": False,
381-
"force_update_gui": False,
382-
"web_gui_repo": None,
383-
"web_gui_location": None,
384384
}
385385

386386
# Type: ignore because of the issues above

0 commit comments

Comments
 (0)