diff --git a/api/derive.ts b/api/derive.ts index d7b1917..e7964ba 100644 --- a/api/derive.ts +++ b/api/derive.ts @@ -154,6 +154,22 @@ export const POSTER_LADDER = [448, 512, 768, 1024] as const; */ const WEBP_QUALITY_SMALL = 74; const WEBP_QUALITY = 82; + +/* + A poster is a FIRST PAINT, not a photograph, and is encoded accordingly. + + `WEBP_QUALITY` is described below as what a rung is worth, and 82 is reserved for the file somebody + actually opens and looks at. Nobody opens a poster: it fills the frame for about a second and is then + covered by the video it belongs to. Paying photograph quality for that is paying for a second that + ends. + + Measured on the same pixels with the same encoder, at 768 wide: 22,906 bytes at 82, 17,738 at 74 — + 23% of the LARGEST CONTENTFUL PAINT, for a difference nothing is on screen long enough to show. + + Not lower than the small rungs use. It IS the LCP, so it is the one image whose first impression is + the page's, and 74 is the quality every thumbnail on the site already ships at. +*/ +const WEBP_QUALITY_POSTER = WEBP_QUALITY_SMALL; const THUMB_MAX_WIDTH = 640; /** @@ -818,7 +834,8 @@ async function buildVideo( const poster = await post<{ path: string; bytes: number }>('/v1/sharp', email, { inputPath: stillJpg, outputPath: join(staging, 'poster.webp'), - output: { format: 'webp', options: { quality: WEBP_QUALITY } }, + // The widest rung, and a poster like all the others — see WEBP_QUALITY_POSTER. + output: { format: 'webp', options: { quality: WEBP_QUALITY_POSTER } }, }); /* From the STILL, not from the video. @@ -847,7 +864,7 @@ async function buildVideo( inputPath: stillJpg, outputPath: join(staging, `poster-${rung}.webp`), pipeline: [{ resize: { width: rung, fit: 'inside', withoutEnlargement: true } }], - output: { format: 'webp', options: { quality: qualityFor(rung) } }, + output: { format: 'webp', options: { quality: WEBP_QUALITY_POSTER } }, }); variants.push({ file: `poster-${rung}.webp`,