Fix the 72.7s A/V gap: xfade offsets ran off the end of their input
_xfade_chain positioned every transition using _audio_dur, which probes format=duration, which is max(video, audio). A clip's audio outlasts its video by about a frame, so the offset accumulator crept ahead of the real picture timeline. Once the creep exceeded the transition width, xfade emitted the transition and silently discarded the second input and every clip downstream, exiting 0 with nothing on stderr. That is the whole of the shipped chapter's 436.39s of video over 363.67s of audio. Offsets now come from min(video, audio). Every input is floored to a whole frame count and trimmed on both streams, so the accumulator tracks the real timeline instead of estimating it. _check_assembled verifies each encode against the predicted length and against its own audio, because both assembly branches drop stream time without failing. Verified over the 49 real clips of chapter 7c944dd4: the round that turned 359s of video into 100s now loses 0.85s, and the chapter comes out 358.76s video against 358.76s audio. The single-item passthrough was not the cause. Two round-0 groups of 8 fresh clips collapse without one, recorded void in decisions/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+41
@@ -228,3 +228,44 @@ people who have none, and at least one nameless row is a real recurring person w
|
||||
|
||||
The "26 of 113 detected people carry an identity" figure that framed the roadmap counted mostly
|
||||
background extras. It should not be quoted again.
|
||||
|
||||
## 2026-08-12, chapter assembly, root cause and fix
|
||||
|
||||
Reproduced the A/V collapse offline with 49 synthetic clips at `ASSEMBLE_BATCH=8` and six `fade_black`
|
||||
boundaries. It came out worse than the shipped run: **two round-0 groups of 8 fresh clips collapsed on
|
||||
their own**, so the single-item passthrough theory from yesterday is dead
|
||||
(`decisions/chapter-assembly.md#passthrough-innocent`).
|
||||
|
||||
Bisected one collapsing group by truncating the chain stage by stage:
|
||||
|
||||
```
|
||||
k=7 out= 52.52 correct
|
||||
k=8 out= 52.52 the last xfade contributed nothing
|
||||
[v6][n7]xfade=duration=0.050:offset=52.500 <- [v6] is 52.52s long, 0.02s of margin
|
||||
```
|
||||
|
||||
`_xfade_chain` took its durations from `_audio_dur`, which is `format=duration`, which is
|
||||
`max(video, audio)`. Each clip's audio outlasts its video by about a frame, so the offset accumulator
|
||||
crept ahead of the picture. Once the creep passed the transition width, xfade emitted the transition and
|
||||
threw away the second input and every clip after it, at `rc 0` with nothing on stderr.
|
||||
|
||||
Fix: offsets come from `min(_stream_dur(v), _stream_dur(a))`, every input is floored to a whole frame
|
||||
count and `trim`/`atrim`ed on both streams, and `_check_assembled` now verifies each encode against the
|
||||
predicted timeline instead of trusting the exit code
|
||||
(`decisions/chapter-assembly.md#offsets-from-min-stream`, `#check-assembled`).
|
||||
|
||||
Verified on the 49 real clips of chapter `7c944dd4`, re-downloaded from MinIO:
|
||||
|
||||
```
|
||||
before r1 n=7 XFADE in v=359.29 a=359.60 -> out v= 99.96 a=358.79
|
||||
after r1 n=7 XFADE in v=359.61 a=359.62 -> out v=358.76 a=358.76
|
||||
chapter v=358.76 a=358.76 gap=+0.00 (shipped: v=436.39 a=363.67 gap=+72.72)
|
||||
```
|
||||
|
||||
`worker_render.py` `__main__` passes. Two checks were added there, because the existing 4-clip A/V assert
|
||||
passed all the way through the broken build. One asserts the frame-exact `trim` on both streams, one
|
||||
assembles three clips whose audio outlasts their video by 0.4s. Mutation-tested by putting `_audio_dur`
|
||||
back: the new check fires with `video=1.80 audio=3.56 expected=3.56`.
|
||||
|
||||
Not done: `s3://video/.../chapter.mp4` is still the broken 436s file. Rebuilding it means clearing the
|
||||
`assemble` stage and resuming, which is CPU-only and was not run.
|
||||
|
||||
Reference in New Issue
Block a user