mirror of
https://github.com/xpltdco/media-rip.git
synced 2026-04-03 02:53:58 -06:00
M002/S04: fix filename resolution for downloads
- Use extract_info + prepare_filename to determine output filename before downloading (yt-dlp skips progress hooks when file exists) - Normalize filenames to relative paths (strip output dir prefix) - Include filename in completion SSE event so frontend displays it - Fixes file download 404s from subdirectory source templates
This commit is contained in:
parent
fd25ea7d05
commit
1da3ef37f1
1 changed files with 31 additions and 5 deletions
|
|
@ -14,6 +14,7 @@ import os
|
||||||
import uuid
|
import uuid
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
|
||||||
|
|
@ -206,7 +207,6 @@ class DownloadService:
|
||||||
# Normalize filename to be relative to the output directory
|
# Normalize filename to be relative to the output directory
|
||||||
# so the frontend can construct download URLs correctly.
|
# so the frontend can construct download URLs correctly.
|
||||||
if event.filename:
|
if event.filename:
|
||||||
from pathlib import PurePosixPath, Path
|
|
||||||
abs_path = Path(event.filename).resolve()
|
abs_path = Path(event.filename).resolve()
|
||||||
out_dir = Path(self._config.downloads.output_dir).resolve()
|
out_dir = Path(self._config.downloads.output_dir).resolve()
|
||||||
try:
|
try:
|
||||||
|
|
@ -226,8 +226,8 @@ class DownloadService:
|
||||||
if pct_changed or status_changed:
|
if pct_changed or status_changed:
|
||||||
self._last_db_percent[job_id] = event.percent
|
self._last_db_percent[job_id] = event.percent
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Job %s DB write: percent=%.1f status=%s",
|
"Job %s DB write: percent=%.1f status=%s filename=%s",
|
||||||
job_id, event.percent, event.status,
|
job_id, event.percent, event.status, event.filename,
|
||||||
)
|
)
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
update_job_progress(
|
update_job_progress(
|
||||||
|
|
@ -243,7 +243,7 @@ class DownloadService:
|
||||||
# Block worker thread until DB write completes
|
# Block worker thread until DB write completes
|
||||||
future.result(timeout=10)
|
future.result(timeout=10)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Job %s progress hook error", job_id)
|
logger.exception("Job %s progress hook error (status=%s)", job_id, d.get("status"))
|
||||||
|
|
||||||
opts["progress_hooks"] = [progress_hook]
|
opts["progress_hooks"] = [progress_hook]
|
||||||
|
|
||||||
|
|
@ -261,8 +261,34 @@ class DownloadService:
|
||||||
|
|
||||||
# Fresh YoutubeDL instance — never shared
|
# Fresh YoutubeDL instance — never shared
|
||||||
with yt_dlp.YoutubeDL(opts) as ydl:
|
with yt_dlp.YoutubeDL(opts) as ydl:
|
||||||
|
# Extract info first to determine the output filename.
|
||||||
|
# This is needed because yt-dlp may skip progress hooks
|
||||||
|
# entirely when the file already exists.
|
||||||
|
info = ydl.extract_info(url, download=False)
|
||||||
|
if info:
|
||||||
|
raw_fn = ydl.prepare_filename(info)
|
||||||
|
abs_path = Path(raw_fn).resolve()
|
||||||
|
out_dir = Path(self._config.downloads.output_dir).resolve()
|
||||||
|
try:
|
||||||
|
relative_fn = str(abs_path.relative_to(out_dir))
|
||||||
|
except ValueError:
|
||||||
|
relative_fn = abs_path.name
|
||||||
|
else:
|
||||||
|
relative_fn = None
|
||||||
|
|
||||||
ydl.download([url])
|
ydl.download([url])
|
||||||
|
|
||||||
|
# Persist filename to DB (progress hooks may not have fired
|
||||||
|
# if the file already existed)
|
||||||
|
if relative_fn:
|
||||||
|
asyncio.run_coroutine_threadsafe(
|
||||||
|
update_job_progress(
|
||||||
|
self._db, job_id, 100.0,
|
||||||
|
None, None, relative_fn,
|
||||||
|
),
|
||||||
|
self._loop,
|
||||||
|
).result(timeout=10)
|
||||||
|
|
||||||
# Mark as completed and notify SSE
|
# Mark as completed and notify SSE
|
||||||
asyncio.run_coroutine_threadsafe(
|
asyncio.run_coroutine_threadsafe(
|
||||||
update_job_status(self._db, job_id, JobStatus.completed.value),
|
update_job_status(self._db, job_id, JobStatus.completed.value),
|
||||||
|
|
@ -271,7 +297,7 @@ class DownloadService:
|
||||||
self._broker.publish(session_id, {
|
self._broker.publish(session_id, {
|
||||||
"event": "job_update",
|
"event": "job_update",
|
||||||
"data": {"job_id": job_id, "status": "completed", "percent": 100,
|
"data": {"job_id": job_id, "status": "completed", "percent": 100,
|
||||||
"speed": None, "eta": None, "filename": None},
|
"speed": None, "eta": None, "filename": relative_fn},
|
||||||
})
|
})
|
||||||
logger.info("Job %s completed", job_id)
|
logger.info("Job %s completed", job_id)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue