👷 CLI: Download list is scrolling vertically and overall progress is shown at the bottom. Fixes #374

This commit is contained in:
Robert Honz
2025-03-01 21:53:46 +01:00
parent 326246cdcc
commit 0726175503
2 changed files with 78 additions and 48 deletions
+68 -41
View File
@@ -4,9 +4,16 @@ from pathlib import Path
from typing import Annotated, Optional from typing import Annotated, Optional
import typer import typer
from rich.console import Group
from rich.live import Live from rich.live import Live
from rich.panel import Panel from rich.progress import (
from rich.progress import BarColumn, Console, Progress, SpinnerColumn, TextColumn BarColumn,
Console,
Progress,
SpinnerColumn,
TaskProgressColumn,
TextColumn,
)
from rich.table import Table from rich.table import Table
from tidal_dl_ng import __version__ from tidal_dl_ng import __version__
@@ -58,10 +65,20 @@ def _download(ctx: typer.Context, urls: list[str], try_login: bool = True) -> bo
# Create initial objects. # Create initial objects.
settings: Settings = Settings() settings: Settings = Settings()
progress: Progress = Progress( progress: Progress = Progress(
"{task.description}", TextColumn("[progress.description]{task.description}"),
SpinnerColumn(), SpinnerColumn(),
BarColumn(), BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), TaskProgressColumn(),
refresh_per_second=20,
auto_refresh=True,
expand=True,
transient=False, # Prevent progress from disappearing
)
progress_overall = Progress(
TextColumn("[progress.description]{task.description}"),
SpinnerColumn(),
BarColumn(),
TaskProgressColumn(),
refresh_per_second=20, refresh_per_second=20,
auto_refresh=True, auto_refresh=True,
expand=True, expand=True,
@@ -74,59 +91,69 @@ def _download(ctx: typer.Context, urls: list[str], try_login: bool = True) -> bo
path_base=settings.data.download_base_path, path_base=settings.data.download_base_path,
fn_logger=fn_logger, fn_logger=fn_logger,
progress=progress, progress=progress,
progress_overall=progress_overall,
) )
progress_table = Table.grid() progress_table = Table.grid()
# Style Progress display. # Style Progress display.
progress_table.add_row(Panel.fit(progress, title="Download Progress", border_style="green", padding=(2, 2))) progress_table.add_row(progress)
progress_table.add_row(progress_overall)
progress_group = Group(
progress_table,
)
urls_pos_last = len(urls) - 1 urls_pos_last = len(urls) - 1
# Use a single Live display for both progress and table # Use a single Live display for both progress and table
with Live(progress_table, refresh_per_second=20): with Live(progress_group, refresh_per_second=20, vertical_overflow="visible"):
for item in urls: try:
media_type: MediaType | bool = False for item in urls:
media_type: MediaType | bool = False
# Extract media name and id from link. # Extract media name and id from link.
if "http" in item: if "http" in item:
media_type = get_tidal_media_type(item) media_type = get_tidal_media_type(item)
item_id = get_tidal_media_id(item) item_id = get_tidal_media_id(item)
file_template = get_format_template(media_type, settings) file_template = get_format_template(media_type, settings)
else:
print(f"It seems like that you have supplied an invalid URL: {item}")
continue
# Download media.
if media_type in [MediaType.TRACK, MediaType.VIDEO]:
download_delay: bool = bool(settings.data.download_delay and urls.index(item) < urls_pos_last)
dl.item(
media_id=item_id, media_type=media_type, file_template=file_template, download_delay=download_delay
)
elif media_type in [MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX, MediaType.ARTIST]:
item_ids: [int] = []
if media_type == MediaType.ARTIST:
media = instantiate_media(ctx.obj[CTX_TIDAL].session, media_type, item_id)
media_type = MediaType.ALBUM
item_ids = item_ids + all_artist_album_ids(media)
else: else:
item_ids.append(item_id) print(f"It seems like that you have supplied an invalid URL: {item}")
for item_id in item_ids: continue
dl.items(
# Download media.
if media_type in [MediaType.TRACK, MediaType.VIDEO]:
download_delay: bool = bool(settings.data.download_delay and urls.index(item) < urls_pos_last)
dl.item(
media_id=item_id, media_id=item_id,
media_type=media_type, media_type=media_type,
file_template=file_template, file_template=file_template,
video_download=ctx.obj[CTX_TIDAL].settings.data.video_download, download_delay=download_delay,
download_delay=settings.data.download_delay,
) )
elif media_type in [MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX, MediaType.ARTIST]:
item_ids: [int] = []
# Clear and stop progress display if media_type == MediaType.ARTIST:
progress.refresh() media = instantiate_media(ctx.obj[CTX_TIDAL].session, media_type, item_id)
progress.stop() media_type = MediaType.ALBUM
print("\nDownload completed!") item_ids = item_ids + all_artist_album_ids(media)
else:
item_ids.append(item_id)
for item_id in item_ids:
dl.items(
media_id=item_id,
media_type=media_type,
file_template=file_template,
video_download=ctx.obj[CTX_TIDAL].settings.data.video_download,
download_delay=settings.data.download_delay,
)
finally:
# Clear and stop progress display
progress.refresh()
progress.stop()
return True return True
+10 -7
View File
@@ -73,6 +73,7 @@ class Download:
fn_logger: Callable fn_logger: Callable
progress_gui: ProgressBars progress_gui: ProgressBars
progress: Progress progress: Progress
progress_overall: Progress
def __init__( def __init__(
self, self,
@@ -82,6 +83,7 @@ class Download:
skip_existing: bool = False, skip_existing: bool = False,
progress_gui: ProgressBars = None, progress_gui: ProgressBars = None,
progress: Progress = None, progress: Progress = None,
progress_overall: Progress = None,
): ):
self.settings = Settings() self.settings = Settings()
self.session = session self.session = session
@@ -89,6 +91,7 @@ class Download:
self.fn_logger = fn_logger self.fn_logger = fn_logger
self.progress_gui = progress_gui self.progress_gui = progress_gui
self.progress = progress self.progress = progress
self.progress_overall = progress_overall
self.path_base = path_base self.path_base = path_base
if not self.settings.data.path_binary_ffmpeg and ( if not self.settings.data.path_binary_ffmpeg and (
@@ -150,8 +153,6 @@ class Download:
total_size_in_bytes: int = int(r.headers.get("content-length", 0)) total_size_in_bytes: int = int(r.headers.get("content-length", 0))
block_size: int | None = 1048576 block_size: int | None = 1048576
progress_total: float = total_size_in_bytes / block_size progress_total: float = total_size_in_bytes / block_size
except:
raise
finally: finally:
r.close() r.close()
else: else:
@@ -709,10 +710,12 @@ class Download:
progress_stdout: bool = True progress_stdout: bool = True
else: else:
progress_stdout: bool = False progress_stdout: bool = False
self.progress_gui.list_name.emit(list_media_name_short[:30]) self.progress_gui.list_name.emit(list_media_name_short)
progress: Progress = self.progress_overall if self.progress_overall else self.progress
# Create the list progress task. # Create the list progress task.
p_task1: TaskID = self.progress.add_task( p_task1: TaskID = progress.add_task(
f"[green]List '{list_media_name_short}'", total=len(items), visible=progress_stdout f"[green]List '{list_media_name_short}'", total=len(items), visible=progress_stdout
) )
@@ -720,7 +723,7 @@ class Download:
result_dirs: [pathlib.Path] = [] result_dirs: [pathlib.Path] = []
# Iterate through list items # Iterate through list items
while not self.progress.finished: while not progress.finished:
with futures.ThreadPoolExecutor(max_workers=self.settings.data.downloads_concurrent_max) as executor: with futures.ThreadPoolExecutor(max_workers=self.settings.data.downloads_concurrent_max) as executor:
# Dispatch all download tasks to worker threads # Dispatch all download tasks to worker threads
l_futures: [any] = [ l_futures: [any] = [
@@ -744,10 +747,10 @@ class Download:
result_dirs.append(result_path_file.parent) result_dirs.append(result_path_file.parent)
# Advance progress bar. # Advance progress bar.
self.progress.advance(p_task1) progress.advance(p_task1)
if not progress_stdout: if not progress_stdout:
self.progress_gui.list_item.emit(self.progress.tasks[p_task1].percentage) self.progress_gui.list_item.emit(progress.tasks[p_task1].percentage)
# Create playlist file # Create playlist file
if self.settings.data.playlist_create: if self.settings.data.playlist_create: