From c2e3ee7dd2a60122a30c58daa340990503a5deb5 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Thu, 4 Jun 2026 18:39:29 +0000 Subject: [PATCH] =?UTF-8?q?fix(growing):=20XDCAM=20HD422=201080p29.97=20?= =?UTF-8?q?=E2=80=94=20exact=20match=20to=20working=20Delta7=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ffprobe of the proven-working Delta7_20260603 file (Premiere opened it): mpeg2video 4:2:2 yuv422p 1920x1080 PROGRESSIVE 30000/1001 + 2x pcm_s16le. The 1080p59.94 source is frame-rate-halved to 1080p29.97 progressive (NOT interlaced, NOT 59.94p). Verified on-node: mpeg2video yuv422p -r 30000/1001 -> raw2bmx -t rdd9 --mpeg2lg_422p_hl_1080p -f 30000/1001 produces an MXF byte-identical in format to the working file (rc=0). --- services/capture/src/capture-manager.js | 50 +++++++++++++------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/services/capture/src/capture-manager.js b/services/capture/src/capture-manager.js index 9149dd2..6f3bafb 100644 --- a/services/capture/src/capture-manager.js +++ b/services/capture/src/capture-manager.js @@ -359,15 +359,15 @@ const CONTAINER_EXT = { // bitrate (operator target, default 50 Mbps) match XDCAM HD422 essence. `-g 15` // keeps a short GOP. Muxed to a raw `mpeg2video` elementary stream (no // container) so raw2bmx ingests it via --mpeg2lg_*. -// CPU AVC-Intra 100 via libx264 — the proven-working growing essence for -// 1080p59.94 (NVENC h264 can't do 4:2:2; mpeg2 422 can't do 1080p59.94). -// raw2bmx wraps this via --avci100_1080p at 60000/1001. +// XDCAM HD422 (MPEG-2 422 Long GOP) — the PROVEN-working growing essence. +// ffprobe of the working Delta7/SDI2 files on the share: mpeg2video, profile +// 4:2:2, yuv422p, 1920x1080, PROGRESSIVE, r_frame_rate 30000/1001. The source +// is 1080p59.94 but it is frame-rate-halved to 1080p29.97 (NOT interlaced) for +// the XDCAM HD422 essence. raw2bmx wraps this via --mpeg2lg_422p_hl_1080p +// at -f 30000/1001, clip type rdd9. const GROWING_VIDEO_ELEMENTARY_ARGS = [ - '-c:v', 'libx264', '-profile:v', 'high422', '-level', '4.2', - '-preset', 'ultrafast', '-tune', 'zerolatency', - '-pix_fmt', 'yuv422p10le', - '-x264-params', 'avcintra-class=100:bframes=0:keyint=1:scenecut=0', - '-aud', '1', + '-c:v', 'mpeg2video', '-pix_fmt', 'yuv422p', + '-dc', '10', '-g', '15', '-bf', '2', ]; const GROWING_DEFAULT_BITRATE = '25M'; const GROWING_EXT = 'mxf'; @@ -417,32 +417,34 @@ function deriveGrowingRaster(resolution, framerate, scanHint = null) { } if (height == null) height = 1080; // default raster - // ffmpeg rate + raw2bmx rate strings. AVC-Intra 100 DOES support true - // 1080p59.94 (unlike XDCAM HD422 / MPEG-2 422 which raw2bmx rejects at 60000/1001), - // so a 1080p59.94 SDI feed is wrapped progressively at its native 60000/1001. + // ffmpeg rate + raw2bmx rate strings. XDCAM HD422 at 1080 lines does not + // support 60000/1001; the proven-working files wrap the 1080p59.94 SDI feed as + // 1080p29.97 PROGRESSIVE (frame-rate halved). So 59.94 maps to 30000/1001 and + // ffmpeg's -r drops every other frame (60p -> 30p), matching the working files. function rates(fps) { - if (fps == null) return { ff: '60000/1001', raw: '60000/1001' }; - if (Math.abs(fps - 59.94) < 0.2) return { ff: '60000/1001', raw: '60000/1001' }; - if (Math.abs(fps - 29.97) < 0.05) return { ff: '30000/1001', raw: '30000/1001' }; - if (Math.abs(fps - 60) < 0.05) return { ff: '60', raw: '60' }; - if (Math.abs(fps - 50) < 0.05) return { ff: '50', raw: '50' }; + if (fps == null) return { ff: '30000/1001', raw: '30000/1001' }; + if (Math.abs(fps - 59.94) < 0.2 || Math.abs(fps - 29.97) < 0.05) + return { ff: '30000/1001', raw: '30000/1001' }; + if (Math.abs(fps - 60) < 0.05) return { ff: '30', raw: '30' }; + if (Math.abs(fps - 50) < 0.05) return { ff: '25', raw: '25' }; if (Math.abs(fps - 25) < 0.05) return { ff: '25', raw: '25' }; if (Math.abs(fps - 24) < 0.2) return { ff: '24000/1001', raw: '24000/1001' }; if (Math.abs(fps - 30) < 0.05) return { ff: '30', raw: '30' }; return { ff: String(fps), raw: String(fps) }; } - // AVC-Intra wraps progressive natively, so respect the scanHint (Deltacast - // reports progressive). Default 1080 to interlaced only when no hint is given. - if (scan == null) scan = scanHint || ((height >= 1080) ? 'i' : 'p'); + // The proven-working files are PROGRESSIVE 1080p29.97 (field_order=progressive). + // Deltacast reports progressive; honor it. 1080-line MPEG-2 422 progressive is + // accepted by raw2bmx at 30000/1001. + if (scan == null) scan = scanHint || 'p'; const r = rates(fpsNum); - // AVC-Intra 100 raster flags. raw2bmx accepts 1080p59.94 here (verified). + // XDCAM HD422 (MPEG-2 422 Long GOP) raster flags — matches the working files. let rawFlag; if (height >= 1080) { - rawFlag = (scan === 'i') ? '--avci100_1080i' : '--avci100_1080p'; + rawFlag = (scan === 'i') ? '--mpeg2lg_422p_hl_1080i' : '--mpeg2lg_422p_hl_1080p'; } else if (height >= 720) { - rawFlag = '--avci100_720p'; + rawFlag = '--mpeg2lg_422p_hl_720p'; if (fpsNum == null) { r.ff = '60000/1001'; r.raw = '60000/1001'; } } else { rawFlag = '--mpeg2lg_422p_ml_576i'; @@ -865,7 +867,7 @@ class CaptureManager { ...GROWING_VIDEO_ELEMENTARY_ARGS, '-b:v', vb, '-minrate', vb, '-maxrate', vb, '-bufsize', vb, '-r', ffRate, - '-f', 'h264', '@VF@', + '-f', 'mpeg2video', '@VF@', // (b) PCM s16le audio → "$AF" '-map', audioMap, '-c:a', 'pcm_s16le', '-ar', '48000', '-ac', String(ach), @@ -914,7 +916,7 @@ class CaptureManager { // fields with the live frame count every 3s (Premiere reads the header // Duration on each refresh; without the patch it sees duration=N/A). const bmx = [ - 'raw2bmx', '-t', 'op1a', '-o', '"$OUT"', '-f', frameRate, + 'raw2bmx', '-t', 'rdd9', '-o', '"$OUT"', '-f', frameRate, '--part', String(GROWING_PART_INTERVAL_FRAMES), '--index-follows', rawFlag, '"$VF"',