Skip to content

fix(producer): export places a scene's media where preview plays it - #5454

Open
miguel-heygen wants to merge 4 commits into
mainfrom
fix/export-honours-inpoint
Open

miguel-heygen wants to merge 4 commits into
mainfrom
fix/export-honours-inpoint

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

What changes for a user

Exported video now places a sub-composition's media where Studio preview plays it:

  • A split or head-trimmed scene (a host with data-playback-start) plays its video and audio from its in-point. Since fix(core): a split sub-composition plays its videos from its in-point in preview and timeline #5413, preview did this but export still played from 0, so a split scene exported seconds behind what was previewed.
  • A scene written inline in index.html (a data-composition-id host with no separate scene file) that starts after 0 plays its media at its start. Export placed it at 0.
  • A looping video inside a split scene stays inside its scene's time slot.

Projects whose scenes are separate files with no in-point export exactly as before.

Why

packages/producer/src/services/renderMediaCollector.ts is the one place every exported video, audio and image gets its window on the root timeline; frame extraction and the audio mix both read it. It now follows the rules preview uses, through core's helpers (compositionOriginSeconds, hostInPointSeconds, cutToHostSlots):

  • every composition host counts ([data-composition-id], [data-composition-src], [data-composition-file], the set core's start resolver uses), not only inlined scene files, so an inline scene's start is no longer ignored; the root composition's start counts but its end no longer cuts media;
  • each host's offset is its start minus its in-point; a clip is cut to its in-point hosts' slots, and a clip they cut away entirely is dropped;
  • audio and video that start inside a cut begin at the slot's start, that much further into their source (rate curves shifted to match); a loop moves to its slot's start and keeps its phase;
  • media is matched by the id the parsers report (render id, else author id) for video, audio and img[src] only, and an author id never takes over a render id's window.

Checked

Rendered on Linux through the producer (createRenderJob + executeRenderJob), main vs this branch. Fixtures use a 19 s video with a frame counter and an audio track that plays one tone per source second.

fixture main this branch
scene data-start=0, in-point 7 audio src 0..11, picture src 0..11 audio src 7..18, picture src 7..18 (SSIM 0.875-0.877 vs source t+7)
inline scene data-start=6, no in-point audio src 0..11 from 0 s silent 0-5 s, then src 0..5 at 6-11 s
looping clip in a scene at 6 s with in-point 6 loop visible from 0 s black until 6 s, then the loop
no sub-composition decoded video and audio frame hashes identical to main
  • renderMediaCollector.test.ts: 18 pass, three runs in a row. The new cases fail against main or the earlier head with the values they got (for example an inline scene's video at 0..19, a looping video starting at -1, a scene video moved to 0..12 by a root image sharing its id). Two compile the real document through compileForRender; one checks windows against main's for a document main got right.
  • Producer unit lanes (bun and vitest: 874 pass), producer typecheck, oxlint, oxfmt, fallow and the comment ratchet are clean.
  • 73 producer regression fixtures compiled on main and on this branch give identical media windows; none has an inline scene starting after 0.

Not covered here

  • An image inside a trimmed scene keeps its uncut start, and an image with no id inside an inline scene is not shifted.
  • A looping video keeps its source phase from its original start rather than the cut point.
  • When a render has to probe media in the browser, the probe stage resets a clip's own source offset to its authored value; a trimmed clip inside a trimmed scene could lose the shift there. Not exercised.
  • A clip's fade-in is applied at the cut point, not at its own start.
  • Not run: the Docker regression harness against committed baselines, the hyperframes render CLI path end to end (it calls the producer API above), Studio snapshot, macOS.

… in-point

A sub-composition host with a non-zero data-playback-start now shifts and cuts
the video and audio it holds in render, matching Studio preview. Hosts without
an in-point resolve exactly as before.
… files

Media in a document with no inlined composition files carry no render id, so
the collector never looked up their host and ignored an inline host's in-point.
Key host windows on the id the parsers report, and cut a video's head the same
way as audio so nothing plays before the host starts.
@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 2061 (base branch 2061), smooth 1583 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)

…tart

A scene written inline in index.html (no scene file, no in-point) with a
start after 0 exported its video and audio from 0 s. Every composition
host now shifts its media, as the preview's start resolver does; the root
composition still does not cut media at its end.

Also from review: a looping video in an in-point scene stays inside the
scene's slot, and a media id only takes the window of an element the
parsers report, never displacing a render id.
@miguel-heygen miguel-heygen changed the title fix(producer): export plays a split or trimmed scene's media from its in-point fix(producer): export places a scene's media where preview plays it Oct 11, 2026

This branch has not been deployed

No deployments
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