diff --git a/api/derive.ts b/api/derive.ts index 776b4b0..53e4864 100644 --- a/api/derive.ts +++ b/api/derive.ts @@ -658,17 +658,18 @@ async function buildVideo( order rather than the other one. */ const placeholder = await encodePlaceholder(stillJpg, join(staging, 'placeholder.webp'), email); - await rm(stillJpg, { force: true }); variants.push({ file: 'poster.webp', width: width || null, height: height || null, bytes: poster.bytes }); /* The poster's own ladder, cut from the same still. From `stillJpg` rather than from `poster.webp`, so each rung is scaled once from the original frame - instead of being a re-encode of an encode. The still is a JPEG at the video's full size and it is - about to be deleted anyway. + instead of being a re-encode of an encode. - Rungs narrower than the source only: one at or above it would be `poster.webp` under another name. + EVERYTHING that reads the still happens above the line that removes it, and that ordering is the + only thing holding this together — the placeholder, then poster.webp, then these. This loop was + added below the removal on 2026-08-30 and every video failed to convert until it moved. The comment + warning about exactly this was already three lines up. */ for (const rung of POSTER_LADDER) { if (!width || rung >= width) continue; @@ -686,6 +687,10 @@ async function buildVideo( }); } + // Last, once nothing else needs it. It is an intermediate: a JPEG in a directory where everything + // served is WebP, and leaving it would publish a rendition nothing references. + await rm(stillJpg, { force: true }); + return { kind: 'video', source: { name, bytes: source.bytes, mtimeMs: source.mtimeMs, width: width || null, height: height || null },