/** * normalize-size.ts — picking the canvas for a mixed-size concat. * * The demuxer concat path uses `-c copy`, which assumes every segment shares * stream parameters. When they do not, ffmpeg does NOT error: it emits a master * with a wrong duration and frame rate that looks entirely plausible (a 3x15s * mixed 1080p/720p film once came out as 108s at ~10fps, rc=0, and nothing * downstream noticed). So the prep pass re-encodes every segment to one size — * and this module decides which size that is. */ /** * The size to normalise a mixed-size concat to: whichever frame size the most * segments already have, ties broken toward the LARGER (so a 50/50 split keeps * the detail rather than discarding it). * * PURE — the probing lives in `stitch`. Exported for the unit tests, because the * cost of getting this wrong is invisible: ffmpeg does not error on a mixed * concat, it emits a master with a wrong duration and frame rate that looks * plausible. */ export function majoritySize( sizes: ReadonlyArray<{ width: number; height: number }>, ): { width: number; height: number } { const counts = new Map(); for (const size of sizes) { const key = `${size.width}x${size.height}`; const hit = counts.get(key); if (hit) hit.n += 1; else counts.set(key, { size, n: 1 }); } let best: { size: { width: number; height: number }; n: number } | undefined; for (const entry of counts.values()) { if ( best === undefined || entry.n > best.n || (entry.n === best.n && entry.size.width * entry.size.height > best.size.width * best.size.height) ) { best = entry; } } return best!.size; }