From 40eafce17bbfc04a50d63627d323bc85d3de3325 Mon Sep 17 00:00:00 2001 From: AviDev Date: Sun, 21 Jun 2026 21:40:16 +0300 Subject: [PATCH] feat(api): expose active-track segment counts and transfer speed in job progress (#124) The job progress already reports a weighted percentage, phase and track counts. The per-track tee in build_job_progress_callables already tracks each track's segment counts and sees the downloader's speed string, so also surface the currently-downloading track's segments_done / segments_total and speed in the emitted progress (and on the job + its to_dict). Lets a client show "197/663 segments, 3.2 MB/s" instead of only a percentage. --- unshackle/core/api/download_manager.py | 15 +++++++++++++++ unshackle/core/api/progress.py | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/unshackle/core/api/download_manager.py b/unshackle/core/api/download_manager.py index 718f4c50..2b6f4f87 100644 --- a/unshackle/core/api/download_manager.py +++ b/unshackle/core/api/download_manager.py @@ -104,6 +104,10 @@ class DownloadJob: completed_tracks: int = 0 total_tracks: int = 0 active_tracks: List[str] = field(default_factory=list) + # Segment counts and transfer speed of the track downloading now (granular display) + segments_done: float = 0.0 + segments_total: float = 0.0 + speed: Optional[str] = None # Subtitles skipped under skip_subtitle_errors (non-fatal). Each entry is a dl.SkippedSubtitle # dict (id / language / title) so a client can report which weren't available. @@ -125,6 +129,9 @@ class DownloadJob: "completed_tracks": self.completed_tracks, "total_tracks": self.total_tracks, "active_tracks": self.active_tracks, + "segments_done": self.segments_done, + "segments_total": self.segments_total, + "speed": self.speed, "skipped_subtitles": self.skipped_subtitles, } @@ -756,6 +763,14 @@ class DownloadQueueManager: job.completed_tracks = int(progress_data["completed_tracks"]) if "active_tracks" in progress_data: job.active_tracks = list(progress_data["active_tracks"]) + for _sk in ("segments_done", "segments_total"): + if _sk in progress_data: + try: + setattr(job, _sk, float(progress_data[_sk])) + except (TypeError, ValueError): + pass + if progress_data.get("speed"): + job.speed = str(progress_data["speed"]) if progress_data.get("skipped_subtitles"): job.skipped_subtitles = progress_data["skipped_subtitles"] if "progress" in progress_data: diff --git a/unshackle/core/api/progress.py b/unshackle/core/api/progress.py index 1cfbd450..a9ff6c43 100644 --- a/unshackle/core/api/progress.py +++ b/unshackle/core/api/progress.py @@ -6,7 +6,7 @@ a bitrate-weighted percentage, track counts, and the labels of the tracks downlo from __future__ import annotations -from typing import Any, Callable +from typing import Any, Callable, Optional from unshackle.core.constants import AnyTrack @@ -70,6 +70,9 @@ def build_job_progress_callables( fractions = [0.0] * total done = [False] * total started = [False] * total + seg_done = [0.0] * total + seg_total = [0.0] * total + speeds: list[Optional[str]] = [None] * total def emit() -> None: completed = sum(done) @@ -82,6 +85,8 @@ def build_job_progress_callables( phase += f" (+{len(active) - 3} more)" else: phase = f"downloading {completed}/{total} tracks" + # segment counts and transfer speed of the track downloading now, for a granular display + active_i = next((i for i in range(total) if started[i] and not done[i]), None) sink( { "progress": progress, @@ -89,6 +94,9 @@ def build_job_progress_callables( "completed_tracks": completed, "total_tracks": total, "active_tracks": active, + "segments_done": seg_done[active_i] if active_i is not None else 0.0, + "segments_total": seg_total[active_i] if active_i is not None else 0.0, + "speed": speeds[active_i] if active_i is not None else None, "status": "downloading", } ) @@ -104,6 +112,11 @@ def build_job_progress_callables( counts["completed"] = kwargs["completed"] if "advance" in kwargs: counts["completed"] += kwargs["advance"] + seg_done[index] = counts["completed"] + seg_total[index] = counts["total"] + _dl = kwargs.get("downloaded") + if isinstance(_dl, str) and _dl.endswith("/s"): + speeds[index] = _dl if kwargs.get("downloaded") in JOB_PROGRESS_TERMINAL_STATES: done[index] = True fractions[index] = 1.0