Skip to content

Faster blended stitch, clip sound matched to the narration, and the score's choice during clips - #14

Merged
davior merged 2 commits into
mainfrom
claude/lucid-archimedes-afkjtn
Oct 23, 2026
Merged

davior merged 2 commits into
mainfrom
claude/lucid-archimedes-afkjtn

Conversation

@davior

@davior davior commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Two changes, one commit each.

1. Encode the picture again only around each blend

A blended stitch used to re-encode the whole video just to draw its dissolves. At 1080p on the medium preset ("standard" quality), that took about 12 minutes of a half-hour render for a 14-minute video with 53 shots.

Every shot is encoded with the same encode_args, so the stretch between two of its keyframes can be copied untouched and remuxed next to freshly encoded frames.

  • plan_partial_stitch splits the picture into copied stretches plus one re-encode per blend. Each re-encode runs from the outgoing shot's last keyframe before the blend to the incoming shot's first keyframe after it. A short shot with no keyframe clear of both its blends merges them into one encode, chunked the same way as the full stitch.
  • Blends are anchored to the timeline. The copied stretches are whole frames and the timeline isn't. Without anchoring, each blend gained up to a frame, which added up to 25 frames over 38 blends.
  • Sound is built for the whole video in one pass, exactly as before, then muxed back in. It is never cut at an AAC packet boundary.
  • Blend pieces seek straight to their first frame instead of decoding up to it.
  • Copied pieces use the 90 kHz timescale every encode uses. With the segment muxer's default of 1/15360, ffmpeg 6's concat placed pieces a minute apart.
  • Fallbacks: if the partial stitch fails, the full re-encode runs; if that fails too, the shots are joined with cuts, as before.

Testing

  • 45 shots, 38 blends, against the full stitch on ffmpeg 6.1 and 7.1.1: same frame count (17,308). Minimum 36 dB PSNR, a difference that comes only from the extra encode. The sound sits on the planned timeline to the sample, where the full stitch ran 20 ms late.
  • 1080p medium, 12 shots, 10 blends: 38 s down to 22 s. 16 s of that is the blend encodes, which scale with the number of blends, not the video's length.

2. Clip sound matched to the narration, and what the score does during clips

Clips used to play their sound at whatever level the footage had. Now each one is measured (EBU R128, over the part that plays) against the narration, which is measured once per render, and gets one fixed gain to match it.

  • One gain per clip, so the clip keeps its own loud and soft moments. A limiter at −1 dBFS goes on only when a clip is being raised. Gains are capped at +24 dB, so a near-silent clip doesn't become loud hiss.
  • Measured on ffmpeg 6.1 and 7.1: a clip at −18.4 LUFS lands at −31.4 against narration at −31.3.

Two optional spec fields (S5 in the plan), so existing specs are unchanged and stay "0.2":

  • visual.volume_db (−30 to 20): moves a clip's level from the match. The clip player has a slider for it.
  • audio_tracks[].during_clips: left unset, nothing changes (a ducked track dips under clips as it does under the voice). "mute" fades the track out around each clip and back in after. "play" keeps it at its level through clips and ducks it under the voice only, by compressing it against a key with the clips taken out. The Soundtrack panel has a dropdown for it.

Measured on 6.1 and 7.1, with music under a test voice and one clip:

mode under the voice during the clip
unset −5.7 dB −5.7 dB
mute −5.7 dB gone
play −5.7 dB full

The assistant's prompt names both new fields.

Checks

  • Backend pytest -q: 691 passed on 6.1. The render tests, including one with a clip that plays its sound, also pass against 7.1.1.
  • Frontend: lint, 156 tests, npm run build and Prettier all clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WJkfhgbVRktPf2Lg6XXfNs

A blended stitch re-encoded the whole video to draw its dissolves. At 1080p on
the medium preset that was most of a render: 12 minutes of a half-hour job for
a 14-minute video with 53 shots.

Every shot is encoded with encode_args, so between two keyframes it can be
copied and remuxed beside freshly encoded frames. plan_partial_stitch lays the
picture out as copied stretches and, for each blend, a re-encode from the
outgoing shot's last keyframe before the blend to the incoming shot's first
after it (a short shot with no keyframe clear of both blends joins them into
one encode, chunked like the full stitch). Each blend is anchored to the
timeline, since the copied stretches are whole frames and the timeline is not;
left alone the error added up to 25 frames over 38 blends. The sound is built
for the whole video in one pass, exactly as before, and muxed back in, so it is
never cut at an AAC packet boundary. The copied pieces are written on the
90 kHz timescale every encode uses: on 1/15360, ffmpeg 6's concat laid them
out a minute apart.

Checked against the full stitch on ffmpeg 6.1 and 7.1.1, 45 shots and 38
blends: same frame count, min 36 dB (the encode generations), and the sound
on the planned timeline to the sample, where the full stitch ran 20 ms late.
At 1080p medium, 12 shots and 10 blends: 38 s -> 22 s, of which 16 s is the
blends, a cost that follows the number of blends rather than the length of the
video. If the partial stitch fails, the full one runs, and then the cuts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJkfhgbVRktPf2Lg6XXfNs
…uring clips

Clips played their sound at whatever level the footage had, so one interview
was a whisper and the next drowned the voice. Each clip is now measured (EBU
R128, over the part that plays) against the narration, measured once per
render, and given one static gain to match it: the clip's own dynamics kept, a
limiter at -1 dBFS only when it is raised, and a +24 dB ceiling so near-silence
doesn't become loud hiss. Measured on ffmpeg 6.1 and 7.1: a clip at -18.4 LUFS
lands at -31.4 against narration at -31.3.

Two optional spec fields (S5), so existing specs are unchanged:
- visual.volume_db, -30..20: a clip's level moved from that match. The clip
  player has a slider for it.
- audio_tracks[].during_clips: absent keeps today's behaviour (a ducked track
  dips under clips as under the voice); "mute" fades the track out around each
  clip and back after; "play" keeps it at its level through clips, ducked under
  the voice alone by compressing it against a key with the clips taken out.
  The soundtrack panel has a select for it.

The assistant's prompt names both fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJkfhgbVRktPf2Lg6XXfNs
@davior davior changed the title Encode the picture again only around each blend Faster blended stitch, clip sound matched to the narration, and the score's choice during clips Oct 23, 2026
@davior
davior marked this pull request as ready for review October 23, 2026 04:30
@davior
davior merged commit 6fd89d6 into main Oct 23, 2026
3 checks passed
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.

2 participants