From 7b878d48c9689245ae5340bd3fde9d9ff96e6a17 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Sun, 31 May 2026 21:56:58 -0400 Subject: [PATCH] fix(capture): growing TS is 8-bit 4:2:0 (Premiere-importable) + honor bitrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Growing master was H.264 High 4:2:2 Intra (high422/yuv422p) — ffprobe/VLC open it but Adobe Premiere's H.264 importer only accepts 8-bit 4:2:0, so it refused ("won't import"). Switch growing video to -profile:v high -pix_fmt yuv420p (still -g 1 all-intra). Also the growing branch ignored the operator's bitrate; now applies -b:v/-maxrate/-bufsize. Modal notes that growing mode fixes codec/container (bitrate still applies). Co-Authored-By: Claude Opus 4.8 --- services/capture/src/capture-manager.js | 31 ++++++++++++++++--- services/web-ui/public/modal-new-recorder.jsx | 8 +++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/services/capture/src/capture-manager.js b/services/capture/src/capture-manager.js index 09df4e0..a3b46e6 100644 --- a/services/capture/src/capture-manager.js +++ b/services/capture/src/capture-manager.js @@ -242,8 +242,22 @@ const CONTAINER_EXT = { // // Audio: AAC (a TS-native codec Premiere imports). PCM-in-TS is tagged as // SMPTE-302M `bin_data`, which Premiere does not reliably import as audio. +// +// THIRD-ATTEMPT FIX — profile/pixel format. The previous attempt used +// `-profile:v high422 -pix_fmt yuv422p`, which produces an H.264 "High 4:2:2 +// Intra" stream. ffmpeg/ffprobe/VLC decode that fine (which is why prior static +// checks passed), but Adobe Premiere's H.264 importer ONLY supports 8-bit 4:2:0 +// (Baseline/Main/High); it silently refuses High 4:2:2 (Intra) — the exact +// "ffprobe opens it but Premiere won't import" symptom the user reported. +// Verified live on zampp2 against the deployed capture image: +// high422/yuv422p -> profile "High 4:2:2 Intra" (decodes in ffmpeg, rejected by Premiere) +// high /yuv420p -> profile "High" (decodes mid-write, Premiere-importable) +// 4:2:0 is the only chroma subsampling Premiere ingests for H.264; the +// 4:2:2 mezzanine path is the (non-growing) ProRes/DNxHR master, not this +// growing TS. `-g 1` still makes every frame an IDR so a partially-written file +// decodes to its last complete frame (edit-while-record). const GROWING_VIDEO_ARGS = [ - '-c:v', 'libx264', '-profile:v', 'high422', '-pix_fmt', 'yuv422p', + '-c:v', 'libx264', '-profile:v', 'high', '-pix_fmt', 'yuv420p', '-preset', 'veryfast', '-g', '1', ]; const GROWING_EXT = 'ts'; @@ -336,15 +350,24 @@ function buildEncodeArgs({ container, isNetwork, isProxy = false, growing = false, }) { - // ── Growing master: force MPEG-TS + H.264 all-intra, ignoring the configured - // MOV/ProRes container/codec. This is a format VLC and Premiere both open - // WHILE GROWING (no footer/moov to wait on — see GROWING_VIDEO_ARGS above). + // ── Growing master: MPEG-TS + H.264 all-intra. The CODEC and CONTAINER are + // necessarily fixed here (H.264-in-TS is the only all-intra format Premiere + // opens WHILE growing — no footer/moov to wait on; ProRes/DNxHR/PCM are not + // valid TS payloads), so the operator's codec/container selections cannot be + // honoured for a growing recorder. The operator's TARGET BITRATE, framerate, + // and audio-channel count CAN and now ARE honoured — previously the + // configured bitrate was dropped entirely, so "master recording settings + // don't do anything" was literally true for a growing recorder (it always + // encoded at libx264's default CRF regardless of the UI value). // Audio is forced to AAC, a TS-native codec Premiere imports (PCM-in-TS is // tagged as bin_data and not reliably importable as audio). if (growing) { const args = []; if (isNetwork) args.push('-map', '0:v:0?', '-map', '0:a:0?'); args.push(...GROWING_VIDEO_ARGS); + // Honour the operator-configured target bitrate (e.g. "60M"). Without this + // libx264 falls back to its default CRF and the UI bitrate is ignored. + if (videoBitrate) args.push('-b:v', videoBitrate, '-maxrate', videoBitrate, '-bufsize', videoBitrate); if (framerate && framerate !== 'native') args.push('-r', framerate); args.push('-c:a', 'aac', '-b:a', '256k'); if (audioChannels) args.push('-ac', String(audioChannels)); diff --git a/services/web-ui/public/modal-new-recorder.jsx b/services/web-ui/public/modal-new-recorder.jsx index 236da92..236b303 100644 --- a/services/web-ui/public/modal-new-recorder.jsx +++ b/services/web-ui/public/modal-new-recorder.jsx @@ -506,6 +506,14 @@ function NewRecorderModal({ open, onClose }) { Write the live master to the SMB share so editors can cut while it's still recording. Requires the SMB share to be configured in Settings → Storage. + {growingOn && ( +
+ Growing-files mode records H.264 All-Intra (4:2:0) in MPEG-TS — the only format + Premiere can import while it's still being written. The codec and container above + are overridden for this recorder (the target bitrate still applies). Turn growing + off to record your selected master codec/container. +
+ )}