From b733ee84fa545f729b106f5607c863fd28ec2e58 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Wed, 19 Aug 2026 01:13:40 +0200 Subject: [PATCH] fix(downloads): use the local poster as the Library thumbnail primary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Library card used the source's remote thumbnail_url as the primary src and only fell back to our self-hosted poster on error. For Facebook downloads (the bulk of a typical library) that URL is an fbcdn signed link that expires within days, and is also blocked by ad-blockers / Brave Shields on the facebook domain — so the thumbnail broke even though the local poster was present on disk. Flip the priority: serve /api/downloads/{id}/poster.jpg first (always present, never expires, same-origin) and keep the remote as the fallback for the rare poster-less asset. --- frontend/src/components/DownloadCenter.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/DownloadCenter.tsx b/frontend/src/components/DownloadCenter.tsx index cf4a61a9..9facb718 100644 --- a/frontend/src/components/DownloadCenter.tsx +++ b/frontend/src/components/DownloadCenter.tsx @@ -84,10 +84,13 @@ function StatusPill({ job }: { job: DownloadJob }) { } function Thumb({ job }: { job: DownloadJob }) { - // Prefer the source thumbnail; fall back to our generated poster for thumbnail-less sources. - const primary = job.asset?.thumbnail_url || job.poster_url; - // A remote i.ytimg.com thumbnail 404s once the source video is deleted/privated, leaving a - // broken image. On load failure fall back to our always-present self-hosted poster. + // Prefer OUR self-hosted poster: it's always present on disk, never expires, and is same-origin, so + // a source-CDN signature expiry (Facebook's fbcdn URLs expire within days) or an ad-blocker/Shields + // rule on the source domain can't break it. The source's remote thumbnail_url is only a fallback for + // the rare poster-less asset (a thumbnail-less source whose ffmpeg frame-cut also failed). + const remote = job.asset?.thumbnail_url ?? null; + const primary = job.poster_url || remote; + // On load failure (a genuinely missing poster file) fall back to the remote source thumbnail. const [src, setSrc] = useState(primary); useEffect(() => setSrc(primary), [primary]); return ( @@ -99,7 +102,7 @@ function Thumb({ job }: { job: DownloadJob }) { loading="lazy" className="w-full h-full object-cover" onError={() => { - if (job.poster_url && src !== job.poster_url) setSrc(job.poster_url); + if (remote && src !== remote) setSrc(remote); }} /> ) : null}