// The encode profiles, in ONE place. // // These came off `share-report-clips/reencode.py` in ~/reports/elfpire-eva, // which is where the first share batch was actually encoded and therefore the // only honest source for "what the last batch looked like". Ported rather than // spawned: a python file living beside one project's deliverables is not a // profile the next project can reach, and two copies of an x264 command line // is exactly the drift that makes batch 2 not match batch 1. // // `std` and `small` are that script's two profiles, argument for argument. // `accurate` is neither: it is the re-encode a CUT falls back to when the cut // cannot be stream-copied, and it deliberately keeps the source's geometry -- // an `orig/` file is "the window as fetched", and rescaling it here would make // the folder's own description untrue. import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { FFPROBE_BIN } from "umtool-report-to-video/build-video"; const execFileP = promisify(execFile); /** Scale-and-pad to an exact frame, the shape both share profiles use. */ const box = (w, h) => `scale=w=${w}:h=${h}:force_original_aspect_ratio=decrease,` + `pad=${w}:${h}:(ow-iw)/2:(oh-ih)/2,format=yuv420p`; export const SHARE_PROFILES = { // Exactly 1280x720, faststart, a level every phone and every forum player // will take. This is the one people download. std: { label: "1280x720 · crf 23", args: [ "-vf", box(1280, 720), "-c:v", "libx264", "-profile:v", "high", "-level", "4.0", "-preset", "medium", "-crf", "23", "-r", "30", "-g", "60", "-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2", "-movflags", "+faststart", ], }, // 640x360 under a 300k ceiling: the copy that survives an upload limit. small: { label: "640x360 · crf 28 · 300k cap", args: [ "-vf", box(640, 360), "-c:v", "libx264", "-profile:v", "main", "-level", "3.1", "-preset", "medium", "-crf", "28", "-maxrate", "300k", "-bufsize", "600k", "-r", "30", "-g", "60", "-c:a", "aac", "-b:a", "64k", "-ar", "44100", "-ac", "2", "-movflags", "+faststart", ], }, }; /** The encode names a batch produces, beside the untouched `orig`. */ export const SHARE_VARIANTS = ["orig", ...Object.keys(SHARE_PROFILES)]; /** * The re-encode a cut falls back to when the window's keyframes are in the * wrong places. No scaling and no frame-rate change: the point of this file is * that its first and last frames are the seconds that were asked for. */ export const ACCURATE_CUT_ARGS = [ "-c:v", "libx264", "-preset", "veryfast", "-crf", "20", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "160k", "-movflags", "+faststart", ]; /** * A file's duration in seconds, from the container. * * build-video's probeDuration() counts VIDEO FRAMES, because a rendered * segment's length has to agree with the timeline it is concatenated into. * Nothing here is concatenated: the question is "did this cut come out the * length it was asked for", and the container's own answer is the one a * downloader will see. */ export async function probeSeconds(file) { const { stdout } = await execFileP(FFPROBE_BIN, [ "-v", "error", "-show_entries", "format=duration", "-of", "default=nw=1:nk=1", file, ]); const n = Number(String(stdout).trim()); return Number.isFinite(n) ? n : null; }