scenes/<id>.yaml
One file per scene. The filename is the scene id — scenes/demo.yaml is the scene demo. Do
not put an id: field inside; the loader injects it, and a stray id: is a legible error.
A scene is a background (what fills the frame) + an optional avatar overlay + narration.
# yaml-language-server: $schema=../../../schemas/scene.schema.json
background: { kind: web, source: dashboard_demo } # slide | image | cli | web | video | avatar
avatar: { placement: pip } # off (default) | pip | fullscreen
narration: Here is the live dashboard.
duration: null # null for cli/web/video/avatar; a number for slide/image
chapter: Dashboard
background
What fills the frame. The kind selects the visual; most kinds reference a source id from
sources.yaml:
kind | Source of visual |
|---|---|
slide | An HTML template (no source needed) |
image | An image source |
cli | A cli (terminal) source — executes the tape (records a real terminal via VHS) |
web | A web (browser) source |
video | A video clip source |
avatar | A generated talking-head |
Pasting a CLI transcript to show — not run — a terminal session? Use the
terminalslide template (below), not thisclibackground: theclikind above executes the tape for real (VHS screen-records it);slide/terminalonly renders pasted text, nothing runs.
Video backgrounds
A video background uses a pre-recorded clip. It can carry a frame mat — the clip inset centered
over a solid color or an image source:
background: { kind: video, source: demo_clip, frame: { color: "#0b0f19", scale: 0.8 } }
# or frame: { image: backdrop } where `backdrop` is an image source
A bare fullscreen video scene (no effects, frame, or avatar) renders via an ffmpeg-only
passthrough — it skips the headless-Chrome compositor entirely, so it's faster.
Slide templates
A slide background names a template + its data:
template | data |
|---|---|
title | { title, subtitle? } — a centered statement card. |
bullets | { title?, items: [...] } — an enumerated list. |
diagram-3col | { title?, cols: [...] } (≥2) — boxes connected left-to-right. |
gradient | { from, to, accent, angle?, accentOpacity?, motion?, headline?, subtitle? } — a colored/gradient background with a slow two-layer parallax drift. No image, renders instantly, never fails. |
terminal | { transcript, prompt? } — a pasted CLI transcript rendered as a styled command/output window. A line prefixed by prompt (default $) renders as a command; any other line renders as output. Nothing is executed — this is a static look built from pasted text, not the cli/VHS background below. |
background: { kind: slide, template: gradient, data: { from: "#0A0B10", to: "#12141C", accent: "#818CF8", angle: 135 } }
background: { kind: slide, template: terminal, data: { transcript: "$ npm install\nadded 42 packages" } }
avatar
An optional talking-head overlay:
avatar: { placement: pip } # off (default) | pip | fullscreen
narration
The spoken script for the scene. Decodx generates the voice — the words are the editing surface. Multi-line reads best as a block scalar:
narration: |
Welcome to the dashboard.
Everything you see here updates live.
duration — authored vs. auto
| Scene kind | duration |
|---|---|
slide, image | An authored number (seconds) — you set how long it holds. |
cli, web, video, avatar | null — the length is locked automatically from the recording or clip. |
Retiming a video: speed
A video background can be retimed:
- a number —
speed: 1.5is 1.5× faster (0.8slower); the scene duration becomesclip / speed. speed: auto— let the narration set the pace: the clip is stretched or compressed to last exactly as long as the narration (+ padding), clamped to[0.9, 2.5]×.- project-wide default:
meta.defaults.video_speed(a per-scenespeedstill wins).
If narration covers less than 70% of a fixed-speed clip, decodx narrate prints an underrun
advisory with a words-to-add hint and the speed: auto pointer.
Editing without pre-cutting: trim / cuts / crop
Author edits on the background instead of pre-cutting the file — so one recording can back many trimmed scenes:
| Field | Effect |
|---|---|
trim: [start, end] | Source seconds — drives the scene duration. |
cuts: [[in, out], …] | Remove dead-time spans; kept segments are concatenated. |
crop: [x, y, w, h] | A fractional (0–1) window of the frame. |
All three are applied in one cached ffmpeg pre-pass.
chapter
An optional chapter label — collected into output/chapters.txt (paste-ready YouTube chapters) and
chapters.json.
That's the full authoring surface. To generate a whole scene set from a recording instead of writing
these by hand, see decodx ingest in the Quickstart.