Skip to content

Add compose_layout() and lower the cornball layout contract - #20

Merged
TroyHernandez merged 3 commits into
mainfrom
feat/layout-compose
Jul 20, 2026
Merged

Add compose_layout() and lower the cornball layout contract#20
TroyHernandez merged 3 commits into
mainfrom
feat/layout-compose

Conversation

@TroyHernandez

Copy link
Copy Markdown
Contributor

The multi-track composition layer for cornductor's two-stream presets (vertical stack, newscast over-shoulder, explainer corner PiP) — and anything else expressible as named pixel-rect slots.

  • compose_layout(sources, output, layout, reference) — paints N named video streams into a layout's slots on a black canvas, in slot order. fill covers (scale-up + center-crop, for heads); fit contains (scale-down + centered pad, for content). Every chain normalizes fps/SAR/yuv420p/timebase, non-reference chains freeze their last frame via tpad, and the output is cut frame-exactly to the reference — no shortest= anywhere, same discipline as crossfade_concat. Output is video-only by construction, so a layout can never hijack the narration bed. (The existing pip/overlay/vstack primitives are deliberately not chained: implicit audio pick + shortest semantics are wrong here.)
  • render_timeline() — tracks whose cornball$role matches a slot in the timeline's cornball$layout are assembled through the same walk/prerender/join pipeline (extracted as .assemble_track()) and composed with the content track bound to the visual slot as timing reference. Framing is suppressed when composing (the layout defines the canvas) but still sizes still rasters, so slides keep full-quality Ken Burns and the compose downscales into the slot. Degradation mirrors the effects namespace: schema-too-new, slot-without-track, or roled-track-without-slot each warn once and fall back to the single-stream render; old compost drops roled tracks silently and renders a correct letterboxed video.
  • Contract doc: inst/schema/cornball-layout.md (pixel rects, paint order, visual binding, muted-by-construction, degradation table), cross-linked with cornball-effects.md.

Tests: 31 layout (exact filtergraphs, validation, rotio JSON round-trip preserving paint order, at_home composes with per-slot pixel-color and freeze assertions on mixed 24/30fps sources) + render degradation paths + an at_home end-to-end bundle with frames locked to the bed. Full suite 223/223 with at_home on.

compose_layout(): paint N named video streams into pixel-rect slots on a
black canvas, in slot order -- fill (cover+crop) for heads, fit
(contain+pad) for content -- every chain normalized (fps, SAR, yuv420p,
timebase), non-reference chains frozen via tpad, output cut frame-exact
to the reference and video-only, so the narration bed mapping stays
authoritative. The existing pip/overlay/vstack primitives are not
chained: their implicit audio pick and shortest semantics are wrong
here.

render_timeline(): tracks with a cornball$role matching a layout slot
are assembled through the same walk/prerender/join pipeline (extracted
as .assemble_track) and composed with the content track bound to the
'visual' slot as timing reference; framing is suppressed when composing
(the layout defines the canvas) but still sizes still rasters. Layouts
degrade like the effects namespace: schema too new, slot without a
track, or roled track without a slot each warn once and fall back to
the single-stream render. Contract: inst/schema/cornball-layout.md.
@TroyHernandez
TroyHernandez merged commit 1b092e6 into main Jul 20, 2026
2 checks passed
@TroyHernandez
TroyHernandez deleted the feat/layout-compose branch July 20, 2026 23:14
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