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:
npeter83 2026-07-07 22:28:49 +02:00
parent 374ad4ddc4
commit cb170dfd32
12 changed files with 251 additions and 8 deletions

View file

@ -68,7 +68,7 @@ def owner_view(link: DownloadLink, base_url: str = "") -> dict:
}
def public_meta(link: DownloadLink, job, asset, file_url: str) -> dict:
def public_meta(link: DownloadLink, job, asset, file_url: str, poster_url: str | None = None) -> dict:
"""Metadata for the public watch page (only ever returned once access is authorized).
Resolves the owner's per-download display overrides (custom title/channel) over the asset's
@ -93,4 +93,5 @@ def public_meta(link: DownloadLink, job, asset, file_url: str) -> dict:
"height": asset.height,
"allow_download": link.allow_download,
"file_url": file_url,
"poster_url": poster_url,
}