fix(downloads): show real title + thumbnail while queued/downloading
A queued/downloading row previously showed the bare video id and no thumbnail because asset metadata was only filled when the download completed. Now: - service.populate_from_catalog fills title/uploader/thumbnail/date/duration at enqueue from our own Video/Channel catalog (0 network cost) — feed downloads look right instantly - worker fills the same from yt-dlp's info_dict on the first progress event (covers ad-hoc URLs / catalog misses), best-effort, never fails a download Verified: a paused feed download now shows its real title, channel, thumbnail and duration.
This commit is contained in:
parent
f61ca96b6c
commit
c6ceaea899
2 changed files with 49 additions and 4 deletions
|
|
@ -13,7 +13,7 @@ from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.downloads import formats, quota
|
from app.downloads import formats, quota
|
||||||
from app.models import DownloadJob, MediaAsset
|
from app.models import Channel, DownloadJob, MediaAsset, Video
|
||||||
|
|
||||||
|
|
||||||
def source_url(source_kind: str, source_ref: str) -> str:
|
def source_url(source_kind: str, source_ref: str) -> str:
|
||||||
|
|
@ -54,6 +54,27 @@ def get_or_create_asset(
|
||||||
return asset
|
return asset
|
||||||
|
|
||||||
|
|
||||||
|
def populate_from_catalog(db: Session, asset: MediaAsset) -> bool:
|
||||||
|
"""Fill an asset's display metadata (title/uploader/thumbnail/date/duration) from our own
|
||||||
|
catalog when the source is a known YouTube video — so a queued/downloading row shows the
|
||||||
|
real title + thumbnail immediately instead of the bare id. 0 network cost. Returns True if
|
||||||
|
it found a catalog match."""
|
||||||
|
if asset.source_kind != "youtube" or asset.title:
|
||||||
|
return False
|
||||||
|
video = db.get(Video, asset.source_ref)
|
||||||
|
if video is None:
|
||||||
|
return False
|
||||||
|
asset.title = video.title
|
||||||
|
asset.thumbnail_url = video.thumbnail_url
|
||||||
|
asset.duration_s = video.duration_seconds
|
||||||
|
if video.published_at:
|
||||||
|
asset.upload_date = video.published_at.date()
|
||||||
|
channel = db.get(Channel, video.channel_id)
|
||||||
|
if channel is not None:
|
||||||
|
asset.uploader = channel.title
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _next_queue_pos(db: Session) -> int:
|
def _next_queue_pos(db: Session) -> int:
|
||||||
return (db.execute(select(func.coalesce(func.max(DownloadJob.queue_pos), 0))).scalar() or 0) + 1
|
return (db.execute(select(func.coalesce(func.max(DownloadJob.queue_pos), 0))).scalar() or 0) + 1
|
||||||
|
|
||||||
|
|
@ -74,6 +95,8 @@ def enqueue(
|
||||||
spec = formats.normalize(spec)
|
spec = formats.normalize(spec)
|
||||||
sig = formats.format_sig(spec)
|
sig = formats.format_sig(spec)
|
||||||
asset = get_or_create_asset(db, source_kind, source_ref, sig)
|
asset = get_or_create_asset(db, source_kind, source_ref, sig)
|
||||||
|
# Show a real title + thumbnail the moment it's queued (feed downloads are catalog videos).
|
||||||
|
populate_from_catalog(db, asset)
|
||||||
|
|
||||||
job = DownloadJob(
|
job = DownloadJob(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
|
|
||||||
|
|
@ -182,8 +182,24 @@ def _finish_from_cache(job_id: int, asset_id: int) -> None:
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
def _make_progress_hook(job_id: int):
|
def _fill_asset_meta_early(asset_id: int, info: dict) -> None:
|
||||||
state = {"last_db": 0.0, "last_check": 0.0}
|
"""Populate an asset's display metadata from yt-dlp's info_dict the first time it's seen
|
||||||
|
(only if not already filled at enqueue from our catalog) — so an ad-hoc URL's row shows a
|
||||||
|
real title + thumbnail while it downloads, not just the URL. Zero extra network cost."""
|
||||||
|
with SessionLocal() as db:
|
||||||
|
asset = db.get(MediaAsset, asset_id)
|
||||||
|
if asset is None or asset.title:
|
||||||
|
return
|
||||||
|
asset.title = info.get("title")
|
||||||
|
asset.uploader = info.get("uploader") or info.get("channel")
|
||||||
|
asset.thumbnail_url = info.get("thumbnail")
|
||||||
|
asset.duration_s = int(info["duration"]) if info.get("duration") else None
|
||||||
|
asset.upload_date = _parse_upload_date(info.get("upload_date"))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _make_progress_hook(job_id: int, asset_id: int):
|
||||||
|
state = {"last_db": 0.0, "last_check": 0.0, "meta_done": False}
|
||||||
|
|
||||||
def hook(d: dict) -> None:
|
def hook(d: dict) -> None:
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
|
|
@ -192,6 +208,12 @@ def _make_progress_hook(job_id: int):
|
||||||
state["last_check"] = now
|
state["last_check"] = now
|
||||||
if _job_status(job_id) not in ("running", None):
|
if _job_status(job_id) not in ("running", None):
|
||||||
raise _Aborted
|
raise _Aborted
|
||||||
|
if not state["meta_done"] and d.get("info_dict"):
|
||||||
|
state["meta_done"] = True
|
||||||
|
try:
|
||||||
|
_fill_asset_meta_early(asset_id, d["info_dict"])
|
||||||
|
except Exception: # noqa: BLE001 — metadata is best-effort, never fail a download
|
||||||
|
pass
|
||||||
if d.get("status") != "downloading":
|
if d.get("status") != "downloading":
|
||||||
return
|
return
|
||||||
if now - state["last_db"] < 1.0:
|
if now - state["last_db"] < 1.0:
|
||||||
|
|
@ -230,7 +252,7 @@ def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spe
|
||||||
staging.mkdir(parents=True, exist_ok=True)
|
staging.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
outtmpl = str(staging / "%(id)s.%(ext)s")
|
outtmpl = str(staging / "%(id)s.%(ext)s")
|
||||||
opts = formats.build_ydl_opts(spec, outtmpl, _make_progress_hook(job_id))
|
opts = formats.build_ydl_opts(spec, outtmpl, _make_progress_hook(job_id, asset_id))
|
||||||
url = service.source_url(source_kind, source_ref)
|
url = service.source_url(source_kind, source_ref)
|
||||||
|
|
||||||
_set_job(job_id, phase="downloading", progress=0)
|
_set_job(job_id, phase="downloading", progress=0)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue