This commit is contained in:
nose
2025-12-13 12:09:50 -08:00
parent 30eb628aa3
commit 52a79b0086
16 changed files with 729 additions and 655 deletions

View File

@@ -57,6 +57,9 @@ except ImportError:
_EXTRACTOR_CACHE: List[Any] | None = None
# Reused progress formatter for yt-dlp callbacks (stderr only).
_YTDLP_PROGRESS_BAR = ProgressBar()
def _ensure_yt_dlp_ready() -> None:
if yt_dlp is not None:
@@ -248,7 +251,8 @@ def _build_ytdlp_options(opts: DownloadOptions) -> Dict[str, Any]:
"fragment_retries": 10,
"http_chunk_size": 10_485_760,
"restrictfilenames": True,
"progress_hooks": [] if opts.quiet else [_progress_callback],
# Always show a progress indicator; do not tie it to debug logging.
"progress_hooks": [_progress_callback],
}
if opts.cookies_path and opts.cookies_path.is_file():
@@ -423,17 +427,36 @@ def _progress_callback(status: Dict[str, Any]) -> None:
"""Simple progress callback using logger."""
event = status.get("status")
if event == "downloading":
percent = status.get("_percent_str", "?")
speed = status.get("_speed_str", "?")
eta = status.get("_eta_str", "?")
sys.stdout.write(f"\r[download] {percent} at {speed} ETA {eta} ")
sys.stdout.flush()
# Always print progress to stderr so piped stdout remains clean.
percent = status.get("_percent_str")
downloaded = status.get("downloaded_bytes")
total = status.get("total_bytes") or status.get("total_bytes_estimate")
speed = status.get("_speed_str")
eta = status.get("_eta_str")
try:
line = _YTDLP_PROGRESS_BAR.format_progress(
percent_str=str(percent) if percent is not None else None,
downloaded=int(downloaded) if downloaded is not None else None,
total=int(total) if total is not None else None,
speed_str=str(speed) if speed is not None else None,
eta_str=str(eta) if eta is not None else None,
)
except Exception:
pct = str(percent) if percent is not None else "?"
spd = str(speed) if speed is not None else "?"
et = str(eta) if eta is not None else "?"
line = f"[download] {pct} at {spd} ETA {et}"
sys.stderr.write("\r" + line + " ")
sys.stderr.flush()
elif event == "finished":
sys.stdout.write("\r" + " " * 70 + "\r")
sys.stdout.flush()
debug(f"✓ Download finished: {status.get('filename')}")
# Clear the in-place progress line.
sys.stderr.write("\r" + (" " * 140) + "\r")
sys.stderr.write("\n")
sys.stderr.flush()
elif event in ("postprocessing", "processing"):
debug(f"Post-processing: {status.get('postprocessor')}")
return
def _download_direct_file(
@@ -530,17 +553,17 @@ def _download_direct_file(
speed_str=speed_str,
eta_str=eta_str,
)
if not quiet:
debug(progress_line)
sys.stderr.write("\r" + progress_line + " ")
sys.stderr.flush()
last_progress_time[0] = now
with HTTPClient(timeout=30.0) as client:
client.download(url, str(file_path), progress_callback=progress_callback)
elapsed = time.time() - start_time
avg_speed_str = progress_bar.format_bytes(downloaded_bytes[0] / elapsed if elapsed > 0 else 0) + "/s"
if not quiet:
debug(f"✓ Downloaded in {elapsed:.1f}s at {avg_speed_str}")
# Clear progress line after completion.
sys.stderr.write("\r" + (" " * 140) + "\r")
sys.stderr.write("\n")
sys.stderr.flush()
# For direct file downloads, create minimal info dict without filename as title
# This prevents creating duplicate title: tags when filename gets auto-generated
@@ -1403,9 +1426,16 @@ class Download_Media(Cmdlet):
# Emit one PipeObject per downloaded file (playlists/albums return a list)
results_to_emit = result_obj if isinstance(result_obj, list) else [result_obj]
debug(f"Emitting {len(results_to_emit)} result(s) to pipeline...")
stage_ctx = pipeline_context.get_stage_context()
emit_enabled = bool(stage_ctx is not None and not getattr(stage_ctx, "is_last_stage", False))
for downloaded in results_to_emit:
pipe_obj_dict = self._build_pipe_object(downloaded, url, opts)
pipeline_context.emit(pipe_obj_dict)
# Only emit when there is a downstream stage.
# This keeps `download-media` from producing a result table when run standalone.
if emit_enabled:
pipeline_context.emit(pipe_obj_dict)
# Automatically register url with local library
if pipe_obj_dict.get("url"):