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
|
|
@ -50,7 +50,12 @@ def _watch_tags(token: str, db: Session) -> str | None:
|
|||
return None
|
||||
title = (job.display_name or asset.title or "Video").strip()
|
||||
channel = (job.display_uploader or asset.uploader or "").strip()
|
||||
image = asset.thumbnail_url # already an absolute (youtube/fb) URL, or None
|
||||
# Prefer the source thumbnail; fall back to our generated poster (absolute, token-gated URL) so
|
||||
# even a thumbnail-less video (e.g. a reddit clip) unfurls with an image. (This block only runs
|
||||
# for non-password links, so the poster needs no grant.)
|
||||
image = asset.thumbnail_url or (
|
||||
f"{settings.app_base}/api/public/watch/{token}/poster.jpg" if asset.poster_path else None
|
||||
)
|
||||
tags = [
|
||||
f"<title>{html.escape(title)}</title>",
|
||||
_tag("og:title", title),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue