feat(downloads): clickable channels + poster fallback for thumbnail-less sources
Two visual gaps for non-catalog downloads: - Channel link: YouTube already exposes channel_url; Facebook exposes none but a numeric uploader_id that resolves at facebook.com/<id>. `_uploader_url` derives it so the auto-detected channel renders as a real clickable link. - Poster: a source with no thumbnail (e.g. a direct reddit HLS URL) showed a blank image box. The worker now cuts a representative frame with ffmpeg (`ensure_poster`) into the `<base>.jpg` sidecar and records `poster_path` (migration 0050). The card, the public watch page (<video poster> + og:image), and link previews fall back to it via new authed + public poster endpoints. Adds `app.downloads.backfill` (one-off, re-run-safe) to fill uploader_url (re-extract YouTube/Facebook metadata) and posters for pre-existing downloads.
This commit is contained in:
parent
374ad4ddc4
commit
cb170dfd32
12 changed files with 251 additions and 8 deletions
|
|
@ -209,6 +209,21 @@ def _finish_from_cache(job_id: int, asset_id: int) -> None:
|
|||
db.commit()
|
||||
|
||||
|
||||
def _uploader_url(info: dict) -> str | None:
|
||||
"""The channel/uploader page URL for the auto-clickable channel line. YouTube gives a
|
||||
`channel_url`/`uploader_url` directly; Facebook gives neither but exposes a numeric
|
||||
`uploader_id` (the page id) that resolves at facebook.com/<id>. Returns None for sources with
|
||||
no derivable channel page (e.g. reddit / a raw media URL)."""
|
||||
url = info.get("channel_url") or info.get("uploader_url")
|
||||
if url:
|
||||
return url
|
||||
uid = info.get("uploader_id")
|
||||
tag = f"{info.get('extractor_key') or info.get('extractor') or ''} {info.get('webpage_url') or ''}".lower()
|
||||
if uid and "facebook" in tag:
|
||||
return f"https://www.facebook.com/{uid}"
|
||||
return None
|
||||
|
||||
|
||||
def _fill_asset_meta_early(asset_id: int, info: dict) -> None:
|
||||
"""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
|
||||
|
|
@ -219,7 +234,7 @@ def _fill_asset_meta_early(asset_id: int, info: dict) -> None:
|
|||
return
|
||||
asset.title = normalize_title(info.get("title"))
|
||||
asset.uploader = info.get("uploader") or info.get("channel")
|
||||
asset.uploader_url = info.get("channel_url") or info.get("uploader_url")
|
||||
asset.uploader_url = _uploader_url(info)
|
||||
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"))
|
||||
|
|
@ -410,9 +425,13 @@ def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spe
|
|||
asset.acodec = info.get("acodec") if info.get("acodec") not in (None, "none") else None
|
||||
asset.title = meta.title
|
||||
asset.uploader = meta.uploader
|
||||
asset.uploader_url = info.get("channel_url") or info.get("uploader_url")
|
||||
asset.uploader_url = _uploader_url(info)
|
||||
asset.upload_date = meta.upload_date
|
||||
asset.thumbnail_url = meta.thumbnail_url
|
||||
# No thumbnail from the source (e.g. a direct media URL) → cut a poster frame from
|
||||
# the file so the card / watch page / link preview still shows an image.
|
||||
if not meta.thumbnail_url:
|
||||
asset.poster_path = editmod.ensure_poster(asset, settings.download_root)
|
||||
asset.source_webpage_url = info.get("webpage_url")
|
||||
asset.nfo_written = nfo_ok
|
||||
asset.error = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue