video rungs stop upscaling, which is why the one video failed
`web-1080` was asked for whenever the source was at least 720 tall, so a 720p video was upscaled to 1080p — the exact thing the image ladder is careful never to do. The output was a 14.6 MB file that looks worse than the 7.8 MB one beside it, and it cost 84 seconds of a single core on an idle box. Under a running sync that overran av-club's 255 s idle timeout and homepage_video.mp4 failed outright, which is the only reason the waste was noticed. A rung is now made only when the source is at least that tall. `web-480` becomes a floor rather than a rung, so a source smaller than 720 still gets something instead of nothing or an enlargement. An unreadable height makes everything: a probe that told us nothing is not evidence the video is small. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+27
-7
@@ -55,12 +55,25 @@ export const LADDER = [320, 640, 960, 1280, 1920, 2560] as const;
|
|||||||
/** WebP quality. 82 is the usual knee: visually clean on photographs, well off the top of the curve. */
|
/** WebP quality. 82 is the usual knee: visually clean on photographs, well off the top of the curve. */
|
||||||
const WEBP_QUALITY = 82;
|
const WEBP_QUALITY = 82;
|
||||||
|
|
||||||
/** Video renditions, as av-club preset names paired with the source height each one needs to be worth making. */
|
/**
|
||||||
|
* Video renditions, tallest first. `height` is what the preset scales TO.
|
||||||
|
*
|
||||||
|
* A rung is made only when the source is at least that tall — the same no-upscaling rule the image
|
||||||
|
* ladder follows, and it was got wrong here first: `web-1080` was being asked for from a 720p source,
|
||||||
|
* which ffmpeg obligingly did. The result was a 14.6 MB file that looks worse than the 7.8 MB one
|
||||||
|
* beside it, and 84 seconds of a single core to make it. Under a running sync that overran av-club's
|
||||||
|
* idle timeout and the video failed outright, which is how the bug surfaced at all.
|
||||||
|
*
|
||||||
|
* `web-480` is the floor, not a rung: it exists so a source smaller than 720 still gets something,
|
||||||
|
* rather than nothing or an upscale.
|
||||||
|
*/
|
||||||
const VIDEO_RUNGS = [
|
const VIDEO_RUNGS = [
|
||||||
{ file: '1080.mp4', preset: 'web-1080', minSourceHeight: 720 },
|
{ file: '1080.mp4', preset: 'web-1080', height: 1080 },
|
||||||
{ file: '720.mp4', preset: 'web-720', minSourceHeight: 0 },
|
{ file: '720.mp4', preset: 'web-720', height: 720 },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const VIDEO_FLOOR = { file: '480.mp4', preset: 'web-480', height: 480 } as const;
|
||||||
|
|
||||||
const IMAGE_EXT = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'avif', 'tif', 'tiff', 'heic', 'heif']);
|
const IMAGE_EXT = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'avif', 'tif', 'tiff', 'heic', 'heif']);
|
||||||
const VIDEO_EXT = new Set(['mp4', 'mov', 'm4v', 'webm', 'mkv', 'avi', 'wmv', 'flv', 'mpg', 'mpeg', '3gp', 'm2ts', 'mts']);
|
const VIDEO_EXT = new Set(['mp4', 'mov', 'm4v', 'webm', 'mkv', 'avi', 'wmv', 'flv', 'mpg', 'mpeg', '3gp', 'm2ts', 'mts']);
|
||||||
|
|
||||||
@@ -330,10 +343,17 @@ async function buildVideo(
|
|||||||
|
|
||||||
const variants: Variant[] = [];
|
const variants: Variant[] = [];
|
||||||
|
|
||||||
for (const rung of VIDEO_RUNGS) {
|
/*
|
||||||
// A 720p source re-encoded as "1080" is bigger and no better. The lowest rung has no floor, so
|
Every rung the source is actually tall enough for, and the floor when it is tall enough for none.
|
||||||
// every video gets at least one rendition however small it started.
|
|
||||||
if (height && height < rung.minSourceHeight) continue;
|
An unreadable height (0) is treated as "make everything": a probe that told us nothing is not
|
||||||
|
evidence the video is small, and the encoder will decline to enlarge on its own long before we
|
||||||
|
should decide on its behalf.
|
||||||
|
*/
|
||||||
|
const wanted = height ? VIDEO_RUNGS.filter((rung) => height >= rung.height) : [...VIDEO_RUNGS];
|
||||||
|
const rungs = wanted.length ? wanted : [VIDEO_FLOOR];
|
||||||
|
|
||||||
|
for (const rung of rungs) {
|
||||||
const result = await post<{ path: string; bytes: number }>('/v1/ffmpeg', email, {
|
const result = await post<{ path: string; bytes: number }>('/v1/ffmpeg', email, {
|
||||||
inputPath: sourcePath,
|
inputPath: sourcePath,
|
||||||
outputPath: join(staging, rung.file),
|
outputPath: join(staging, rung.file),
|
||||||
|
|||||||
Reference in New Issue
Block a user