Skip to content

fix(producer): render variable-bound media at the bound clip's length and loudness - #7529

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/variable-bound-media-length-sound
Nov 11, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
fix/variable-bound-media-length-sound

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

A render whose data-var-src video or audio is bound to a clip now takes that clip's length and sound. Before, the film kept the placeholder's length and the swapped clip's sound was mixed in beside the placeholder's.

Why it happened

compileForRender measured the authored src and wrote the placeholder's data-duration, data-end and its sound lane into the page before any variable was read. The browser probe later swapped the <video> to the bound clip, but the film length stayed derived from the baked value, and the extract stage then added a second sound lane for the bound file because its src was not in the first lane's dedupe set.

Fix

One pre-pass in compileForRender, before compileHtmlFile: bindVariableMediaSources sets the src of each data-var-src <video>/<audio> from the render variables over the declared defaults. It uses the runtime's own resolver (boundMediaSrc, split out of variableSrcFor so core stays the one owner, tag allow-list and URL gate included). Every reader after it (length probe, sound lane, extraction) sees the bound clip.

  • A value that fails the URL gate keeps the authored clip and logs the runtime's existing "unsafe URL protocol" warning.
  • Media under a per-instance data-variable-values host is left to the browser, as today.
  • No new variable kind. A film with no data-var-src takes the early return and is untouched.
  • Cost: no extra probe. The one probe per source now runs on the bound file instead of the placeholder.

Verified

Real CLI render on a Linux box, 3 s placeholder with a tone, 5 s bound clip with a tone, --variables '{"clip":"bound5.mp4"}':

length (s) integrated loudness (LUFS)
placeholder source 3.0 -21.8
bound clip source 5.0 -27.3
before, no variable 3.0 -18.8
before, swapped 3.0 -18.3
after, no variable 3.0 -18.8
after, swapped 5.0 -24.3

After the fix the swapped film sits 3 dB over its source, the same lift the unswapped film has over its own source (-21.8 to -18.8); before it was 9 dB over, i.e. two lanes. Frame hashes of the no-variable render are identical before and after.

Tests (htmlCompiler.variableMedia.test.ts, 3 new): length and single sound lane from a bound <video>, declared default with no value passed on an <audio>, unsafe URL keeps the authored clip and warns.

fails without the fix: htmlCompiler.variableMedia.test.ts ("reads the length and the one sound lane from the clip a variable binds", the declared-default audio case and the unsafe-URL warning case) fail against origin/main's htmlCompiler.ts (3 fail, 1 pass); with the fix 4 pass.

htmlCompiler.test.ts (177), naturalDuration (7), probeCount (1) and core applyVariableBindings (15) pass.

Not covered

Bound media inside a sub-composition mount that carries its own data-variable-values still gets its length from the browser probe, as before (first case in htmlCompiler.variableMedia.test.ts). Binding there needs the per-instance table that exists only after inlining.

@heygengenesis heygengenesis Bot 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.

Approve at 364baab6. No blockers.

Fails without the fix — checked. I kept the new htmlCompiler.variableMedia.test.ts and put origin/main's htmlCompiler.ts, applyVariableBindings.ts and compiler/index.ts back. Result: 3 fail, 1 pass, with the expected length/src mismatches and the missing "unsafe URL protocol" warning. On the PR head all 4 pass. htmlCompiler.test.ts passes too (177/177).

Checked against the runtime:

  • The bindVariableMediaSources merge (declared defaults from every [data-composition-variables], then render variables) matches the top-level getVariables(). The skip for [data-variable-values] hosts leaves the per-instance scopes to the browser, as the PR says.
  • boundMediaSrc is a pure extraction. variableSrcFor and unproxiedMediaSrc behave as before, and the tag allow-list and URL gate still have one owner.
  • Bound relative paths are not rebased by rebaseDirectEntryAssetPaths. The browser assigns the same unrebased value, so compile and runtime agree.
  • A film with no data-var-src takes the early return, so its HTML is untouched.

Nits (not blocking):

  • The unsafe-URL test reuses bound.html, which the first new test writes. Running that test alone (-t) will fail. It could write its own fixture.
  • An unsafe value now warns twice, once at compile time and once in the browser. Harmless.
  • A bound <source> inside a <video> still goes through the browser path only. That's worth one line in "Not covered".

@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 102 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 (8)

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 11, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 11, 2026
@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit 789d281 Nov 11, 2026
98 checks passed
@miguel-heygen
miguel-heygen deleted the fix/variable-bound-media-length-sound branch November 11, 2026 03:33
@miguel-heygen miguel-heygen mentioned this pull request Nov 11, 2026
3 of 4 tasks
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.

1 participant