decktalk build turns a Markdown script and HTML pages into one mp4 in six stages. To build your
first video, follow the Quickstart.
Each panel names the stages it runs, in order.
script.mdholds what the voice says, where each## N.heading starts section N.decktalk.tomlmaps each section to a page and a scene, to a clip of your own video, or to a program that draws it.cues.jsongives every moment the page declares the spoken phrase it lands on.- The page, such as
deck/index.html, holds the scenes and their slides.
The six stages
Each stage reads what the stages before it wrote.decktalk build runs all six in order, and each
one is also a command of its own with the same name.
The script, the voice, the cues, the slides and the film, and the file each one is. The cues are the join.
narrateturns the script into one spoken take per turn, the stretch of a section one voice reads, and a start and an end for every word in it, then joins a section’s turns on the section’s clock. A take is found by content alone, so renumbering a section, retitling one or renaming a voice voices nothing.cueturns each cue phrase incues.jsoninto a second on its own section’s clock.recordrecords each page section in headless Chromium, playing the page against those seconds.scoregenerates the music, the ambience bed and the effects the project describes.assemblecuts each section to its span, joins them, mixes the whole soundtrack and encodes one film, with captions, chapters, the placements, a transcript page and a poster beside it.verifymeasures the finished film: every section start, every cut, every seam and every landing.
Why the order is what it is
The order is not a preference. Each stage needs something the stage before it wrote, and the two stages that can spend money sit as late as they can.narrateis first because the words are what every later measurement is made against. A second means nothing until the voice has said the words it counts from.cueis second because a phrase becomes a second only once there are words with times on them.recordis third because the recorder plays the page to those seconds, so the picture lands on its word before anything is cut.scoreis fourth because it is the last stage that can buy anything. Running it afterrecordmeans thatdecktalk build --to recordstops before it. A draft loop ofdecktalk build --no-spend --to recordtherefore spends nothing and still exercises the cues, the pages, the recording and the checks.assembleis fifth because it consumes what all four stages before it wrote. It needs the take index for the lengths, the cue times for the sounds on their cues, the recordings for the picture, the score for the mix and the narration for the voice.verifyis last because it measures the film, and there is no film untilassemblehas written one.
--from STAGE and --to STAGE run a span of the six, and --skip STAGE runs every stage but one.
decktalk assemble --skip score mixes the narration and the clip audio and leaves the generated
sound out.
What judges, and what produces
Two commands exist to be run before the pipeline, and neither one makes a film.decktalk check judges script.md, cues.json and the pages, and prices the narration a voiced
build would buy. It produces nothing a viewer receives, so an agent that wants the price of the
voice makes this one call, and decktalk score --no-spend prices the sound. check --no-pages
judges the written files with no browser at all and says which judgements it could not reach.
check --no-frames keeps the browser and drops the freeze comparison. check --fix applies every
safe fix, and an unsafe fix is printed either way and never applied.
decktalk storyboard freezes every slide at every cue onto one page, build/storyboard.html. It is
the human checkpoint before anything is bought, because it draws what the film will show in the
order it will show it. Five selectors narrow the sheet: --section, --slide, --after, --before
and --at.
Why the cuts are exact
Every reveal starts on its word because the recorder decides what second each frame shows, rather than finding out afterwards when a browser happened to paint it.Frame k of a recording is the page at second k / fps, so frame 0 is the start of the section clock.
The recorder steps at
video.fps, and nothing here depends on wall-clock time.
The handshake shows each wait the recorder makes, and
the decision note says why time belongs to the recorder.
The build that does not spend
--no-spend plays every take already on disk, voiced or placeholder, and gives each section whose take
is missing a placeholder instead of a spoken one, so the run needs no API key and buys nothing. Spend
gates money and nothing else, so a free provider such as dtsp, which speaks through a server on this machine, still makes
each missing take once a voice is named. The
voice is built only when a take must be made, so a project that commits its takes and its
[voices.narrator] id builds its real film on a machine that holds no key and no environment at all. Each section that plays a placeholder is
reported once as TAKE_MISSING, with the command that buys its
take. It is a warning, so the default threshold still builds the film.
- The placeholder is click audio with a soft click at the start of every estimated word and one where the last word ends, at -24 dBFS.
narratespaces the estimated words atnarration.placeholder_words_per_minute, which is 150 by default, and adds each declared pause, beat, lead and tail.- Every other stage runs as usual, so a build that does not spend exercises the cues, the pages, the recording, the encode and the checks.
cuemarks a section whose words are estimated, because its seconds are estimated too.- The clicks are what lets
verifycompare the picture with the sound, soCUE_OFFstill reports a reveal that misses its word. - A sound the score describes and has not bought plays as silence, reported once as
SOUND_MISSING. It is a warning too, so the film is still made. assembleskips the loudness pass, because a placeholder has no programme loudness worth matching and normalising it would move the clicks the check listens for.
--spend.
What spends and what does not says when a run with
neither flag asks, and the ceiling is how --max-cost refuses one.
Caching
A stage whose inputs have not changed is skipped, so an edit to one sentence rebuilds one section.narrate names each take by a digest of the content that produced it: the turn’s words, the
voice in force, its model and output format, [voices.NAME] speed and the provider’s own voice fields,
such as [elevenlabs] stability. A turn whose digest has not moved plays the
take it already has. Every take a voice spoke lives in the takes directory, takes/ inside the
project, which you commit, so a fresh clone finds them. narration.store_dir is the take store, a
second copy of every take this machine bought, which every project looks in second.
record keys each recording on the URL it would open, which already carries the scene, the resolved
cues and the spoken words with their seconds, on the frame geometry, the colour scheme and the motion
the render asks for, and on what the page puts on screen for that one section. That last part is the
markup of the scene the section plays, the rest of the page file that every scene shares, and the
content of every project file the page loaded. So an edit inside one scene records the sections that
play it and leaves the other sections of that page alone, an edit to the page’s head or its styles
records every section of it, and swapping one picture for another records the sections that show it
although no line of HTML changed.
--section N records section N whether it changed or not. A build that finds nothing moved keeps
the film and its measurement, and reports assemble and verify as kept. --force builds again
from nothing, measures again, and keeps every voiced take and every bought sound. Rebuild one
section says what each edit runs again.
Next
- Build your first video: Quickstart
- Learn how a phrase becomes a second: Cues
- Learn what a page may declare: The page contract