feat(downloads): clear multi-phase download progress + transient-error retries
The progress bar looked broken (95% -> 5% -> recount) because yt-dlp downloads the video and audio streams separately (each 0->100%) then merges — with no indication of which step is running. Now: - worker labels the phase from the stream codecs (video / audio) and via a postprocessor hook (merging / processing), so the user sees what's happening - DownloadCenter shows the phase as the status and renders an indeterminate pulse (no bogus %) during merge/processing; phase i18n en/hu/de - yt-dlp retries (3) + fragment_retries (5) so transient network blips self-heal instead of failing the job (seen once as a DNS 'No address associated with hostname' error) Verified: phase transitions queued -> video -> processing -> done.
This commit is contained in:
parent
c6ceaea899
commit
b200f61642
6 changed files with 73 additions and 9 deletions
|
|
@ -219,6 +219,18 @@ def _make_progress_hook(job_id: int, asset_id: int):
|
|||
if now - state["last_db"] < 1.0:
|
||||
return
|
||||
state["last_db"] = now
|
||||
# yt-dlp downloads the video and audio streams SEPARATELY (each 0→100%), then merges.
|
||||
# Label the phase so the user understands the bar resetting between streams instead of
|
||||
# seeing a mysterious 95%→5% jump.
|
||||
info = d.get("info_dict") or {}
|
||||
vcodec = info.get("vcodec") or "none"
|
||||
acodec = info.get("acodec") or "none"
|
||||
if vcodec != "none" and acodec == "none":
|
||||
phase = "video"
|
||||
elif acodec != "none" and vcodec == "none":
|
||||
phase = "audio"
|
||||
else:
|
||||
phase = "media"
|
||||
total = d.get("total_bytes") or d.get("total_bytes_estimate") or 0
|
||||
done = d.get("downloaded_bytes") or 0
|
||||
pct = int(done * 100 / total) if total else 0
|
||||
|
|
@ -227,12 +239,25 @@ def _make_progress_hook(job_id: int, asset_id: int):
|
|||
progress=min(pct, 99),
|
||||
speed_bps=int(d["speed"]) if d.get("speed") else None,
|
||||
eta_s=int(d["eta"]) if d.get("eta") else None,
|
||||
phase="downloading",
|
||||
phase=phase,
|
||||
)
|
||||
|
||||
return hook
|
||||
|
||||
|
||||
def _make_pp_hook(job_id: int):
|
||||
"""Postprocessor hook: surface the ffmpeg merge/convert/extract step so the bar shows a
|
||||
'Merging/Processing' label instead of freezing at 100% after the streams finish."""
|
||||
|
||||
def hook(d: dict) -> None:
|
||||
if d.get("status") in ("started", "processing"):
|
||||
pp = d.get("postprocessor") or ""
|
||||
phase = "merging" if "Merg" in pp else "processing"
|
||||
_set_job(job_id, phase=phase, speed_bps=None, eta_s=None)
|
||||
|
||||
return hook
|
||||
|
||||
|
||||
def _staging_dir(asset_id: int) -> Path:
|
||||
return Path(settings.download_root) / ".staging" / f"asset-{asset_id}"
|
||||
|
||||
|
|
@ -253,6 +278,7 @@ def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spe
|
|||
|
||||
outtmpl = str(staging / "%(id)s.%(ext)s")
|
||||
opts = formats.build_ydl_opts(spec, outtmpl, _make_progress_hook(job_id, asset_id))
|
||||
opts["postprocessor_hooks"] = [_make_pp_hook(job_id)]
|
||||
url = service.source_url(source_kind, source_ref)
|
||||
|
||||
_set_job(job_id, phase="downloading", progress=0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue