From b021cb77be16811ddaeddb5ab61b776e72be3159 Mon Sep 17 00:00:00 2001 From: xiayewang-heygen Date: Wed, 23 Sep 2026 16:06:37 -0700 Subject: [PATCH] fix(engine): reconcile metadata duration after extraction delivers fewer frames (#4371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the probed video stream duration is inflated (container duration fallback includes a longer audio track), FFmpeg delivers far fewer frames than expected — typically exactly 50% for stock clips whose audio is 2x the video length. The coverage gate then aborts the render because it sees 50% coverage against the inflated expectation. After extraction, if FFmpeg delivered <=60% of the predicted frames, correct `metadata.videoStreamDurationSeconds` to the actual video extent. This reconciliation feeds through to the coverage check via the same `metadata` reference, so `expectedFramesForVideo` sees the true duration without any interface changes. This is a corrective complement to the preventive nb_frames cross-check in extractMediaMetadata — it catches cases where nb_frames is unavailable (MKV, live streams, fragmented MP4). Co-authored-by: Claude Opus 4.6 --- .../engine/src/services/videoFrameExtractor.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/engine/src/services/videoFrameExtractor.ts b/packages/engine/src/services/videoFrameExtractor.ts index d456376e5c..2901240b31 100644 --- a/packages/engine/src/services/videoFrameExtractor.ts +++ b/packages/engine/src/services/videoFrameExtractor.ts @@ -923,6 +923,20 @@ export async function extractVideoFramesRange( ); } + // Post-extraction reconciliation: when FFmpeg delivers <=60% of the + // frames predicted by the probed duration, the duration is likely + // inflated (container duration includes a longer audio track). Correct + // it so downstream coverage accounting uses the actual video extent. + if (framePaths.size > 0 && duration > 0 && !options.finalFrameOnly) { + const expectedAtFps = extractionFrameCountForDuration(duration, normalizedFps, metadata.isVFR); + if (expectedAtFps > 0 && framePaths.size <= expectedAtFps * 0.6) { + const correctedDuration = startTime + framePaths.size / fps; + if (correctedDuration < metadata.videoStreamDurationSeconds) { + metadata.videoStreamDurationSeconds = correctedDuration; + } + } + } + return { videoId, srcPath: videoPath,