Skip to content

fix(producer): stop HDR video windows after the first from repeating one frame - #6575

Merged
jrusso1020 merged 3 commits into
mainfrom
fix/hdr-window-duplicate-frames
Oct 27, 2026
Merged

jrusso1020 merged 3 commits into
mainfrom
fix/hdr-window-duplicate-frames

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

What

HDR video frame windows after the first now hold their own source frames instead of one frame repeated.

Why

Since #6304, each raw HDR window seeks to a whole second, then drops its lead-in frames with trim=start_frame. trim keeps those frames' timestamps, and the raw muxer writes none, so when the input has an audio track FFmpeg's default constant-frame-rate sync pads from pts 0 with copies of the window's first frame, and -frames:v cuts the real frames off the end. Phone footage always carries audio, so HLG clips rendered at about half their frames (the reporter saw 155/300 distinct, 64/302 on iPhone footage). Silent sources and window 0 were unaffected, which is why existing tests passed.

Related work

Fixes #6556. Regressed in #6304 (first released in v0.8.199).

How

Add -fps_mode passthrough to the per-window FFmpeg args in hdrWindowFfmpegArgs, so FFmpeg writes exactly the frames the filter graph emits. -fps_mode is already used in distributed/assemble.ts, so no new FFmpeg requirement. Chosen over setpts=PTS-STARTPTS after the trim because it treats every window the same way.

Since #6304, window slots also counted from the first decoded frame, so a video stream that starts after its audio (an edit-list delay) played early by that lead. A wrapper now shifts leases by the probed first tick: slots count from the extraction start and show the first frame until the stream begins. Before #6304, CFR extraction did exactly this, and Chrome also shows the upcoming first frame during the lead.

Test plan

  • Unit tests added/updated: a real-FFmpeg regression test (4 s, 30 fps clip with an audio track, 8-frame windows) checks all 120 leased frames are distinct. It fails without the fix (22/120) and passes with it. A second test delays the video 0.5 s behind its audio and checks slots 0-15 hold the first frame and 15-134 are 120 distinct frames; it fails without the shift. The tests run on real FFmpeg, and a second one checks that the frames after the lead match the undelayed clip's, in order. Existing HDR tests pass.
  • Regression fixture: hdr-hlg-audio-windows (1080p HLG clips with an audio track, spanning several extraction windows; one clip delays its video 0.5 s behind its audio). Baseline generated in Docker. It fails against main at the visual stage and passes with this PR.
  • Manual testing performed: rendered the issue's composition with a synthetic HLG HEVC 10-bit clip on FFmpeg 8.0.1. Distinct frames went from 149/300 to 300/300, and every blitted frame matched source frame i byte for byte. SDR control 300/300. A 59.94 fps HLG source in a 30 fps comp also gives 300/300.
  • Documentation updated (if applicable)
  • Comments follow CONTRIBUTING.md "Comments"

Not tested: the reporter's real iPhone footage, VFR sources, macOS.

🤖 Generated with Claude Code

…one frame

A raw HDR window seeks to a whole second, then trims its lead-in frames.
trim keeps their timestamps, and the raw muxer writes none, so when the
input has an audio track FFmpeg's default CFR sync pads from pts 0 with
copies of the window's first frame and -frames:v cuts the real ones off.
Phone footage always has audio, so HLG clips rendered at ~half their frames.
Pass frames through as the filter graph emits them.

Fixes #6556

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplicate-window fix is promising, but removing the initial timestamp padding also changes the clock mapping for sources with delayed video. Please preserve the offset and add a delayed-start parity regression before merging. Separately, the Comments check currently fails because the production file's comment share rose. I did not run tests or compare Chrome playback locally.

— Jerrai

...["-vf", filters.join(","), "-frames:v", String(a.frames)],
// The raw muxer has no timestamps, so FFmpeg's default CFR sync would pad from pts 0 up to the
// trimmed window's first frame with copies of it. Write exactly the frames the graph emits.
...["-fps_mode", "passthrough"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we preserve the initial media-time offset while avoiding padding introduced by the per-window trim? For a 30 fps source whose video starts 0.5 s after audio, probeFirstTick is 15 and passthrough puts the first decoded frame in raw slot 0, but resolveHdrVideoFrameIndex still indexes slot 15 at composition time 0.5 s. That advances the moving video relative to audio, rather than only making the first frame visible early. The new uniqueness test starts video at zero and cannot detect this. Please cover a delayed-start source with frame-by-frame or playback parity assertions across window boundaries. I haven't visually reproduced the Chrome comparison.

Window slots counted from the first decoded frame, so a video stream that
starts after its audio played early by its lead. Slots now count from the
extraction start and show the first frame until the stream begins, as they
did before windowed extraction (#6304) and as Chrome does.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frame-loss fix and delayed-start correction check out at 7959d47e4ba243b4427cac5908b25e2a87819fcd.

  • captureHdrResources.ts:586 preserves the filter-emitted raw frames; I reproduced 22/120 distinct frames without passthrough and 120/120 with it.
  • captureHdrResources.ts:590-599 restores the initial clock offset while delegating lease release and close to the existing window owner. I independently reproduced the timing problem Jerrai raised on the earlier head: with audio at zero and video starting at 0.5 s, the old proposal selected source frame 15 at 0.5 s; Chrome selected frame 0. The new mapping selects frame 0. Chrome seek/pixel comparisons at 0.5, 1 and 2 seconds identify source frames 0, 15 and 45; the no-offset control identifies 15, 30 and 60 as expected.

Local verification: 37 tests pass (36 current HDR resource tests plus an independent real-extraction offset witness), using FFmpeg 7.0.2. A separate synthetic 10-bit HEVC/HLG probe preserved all 300 frame buffers byte-for-byte against the decoded source. Its 64x64 source contains one duplicate, so I do not independently claim the description's exact distinct-frame counts for its different fixture. The negative control without passthrough fails the added regression.

Limits: no reporter iPhone footage, VFR, macOS or complete composited HDR render by me. Chrome comparison used a synthetic H.264 timing fixture; the HEVC/HLG check separately exercised raw frame extraction. Existing generated bootstrap artifacts were reused only to collect the tests; no dependency installation. Current CI has no reported failures: producer unit tests, typecheck, build and Comments pass. Required Windows render and several other checks are still running, so this is code approval, not a claim that all CI is green. The earlier review's offset finding is addressed by this successor; its review state remains with that reviewer.

— Magi (desktop-review)

Verdict: APPROVE
Reasoning: the frame-loss regression is reproduced and fixed, and the successor repairs the delayed-video clock mapping with independent browser and extraction evidence. No remaining code blocker found in this bounded review.

…xtraction windows

Neither HDR fixture had an audio track, which is how #6556 shipped. Window A
checks frames after the first extraction window are not repeats; window B
checks a video stream that starts 0.5 s after its audio holds its first frame
until the picture starts. The baseline was generated in Docker and the fixture
fails against main.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first-tick lease mapping addresses my delayed-start clock finding, and the successor compares the delayed sequence against the undelayed source in order while adding an HLG-with-audio golden fixture across windows. I reviewed the fixture definitions and LFS pointers, not the binary baseline itself; I did not run the tests or reproduce Chrome playback. Hosted checks are still running.

— Jerrai

@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 134 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (1)

  • seqplainkeys-none-center-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@jrusso1020
jrusso1020 added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 07b0f2c Oct 27, 2026
78 checks passed
@jrusso1020
jrusso1020 deleted the fix/hdr-window-duplicate-frames branch October 27, 2026 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HDR layered composite repeats video frames: ~50% duplicates when the source <video> is HLG

3 participants