Skip to main content
decktalk build turns a Markdown script and HTML pages into one mp4 in six stages. To build your first video, follow the Quickstart.
Four panels. Write shows a Markdown script, where one heading starts one section. Narrate runs narrate and cue, and shows a tick for every word with 1.25 over one of them. Record runs record and score, and shows a slide whose parts appear on their words. Assemble runs assemble and verify, and shows one mp4 cut to the frame.Four panels. Write shows a Markdown script, where one heading starts one section. Narrate runs narrate and cue, and shows a tick for every word with 1.25 over one of them. Record runs record and score, and shows a slide whose parts appear on their words. Assemble runs assemble and verify, and shows one mp4 cut to the frame.

Each panel names the stages it runs, in order.

A project is four files, and DeckTalk writes everything else.
  1. script.md holds what the voice says, where each ## N. heading starts section N.
  2. decktalk.toml maps each section to a page and a scene, to a clip of your own video, or to a program that draws it.
  3. cues.json gives every moment the page declares the spoken phrase it lands on.
  4. 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.
Five stations from left to right: the script, the voice, the cues, the slides and the video, each with its file. The script is script.md, one heading a section. The voice reads it and every word gets a time, in a take and its words file. The cues are cues.json, where you name the phrase and the picture starts on it, such as the phrase This is DeckTalk for the cue 1.1:title. The slides are plain HTML in deck/index.html, one scene a section. The video is recorded frame by frame, and every reveal is measured. A line from the slides joins the cues. Under the stations: change one sentence, and only that section is voiced and recorded again.Five stations from left to right: the script, the voice, the cues, the slides and the video, each with its file. The script is script.md, one heading a section. The voice reads it and every word gets a time, in a take and its words file. The cues are cues.json, where you name the phrase and the picture starts on it, such as the phrase This is DeckTalk for the cue 1.1:title. The slides are plain HTML in deck/index.html, one scene a section. The video is recorded frame by frame, and every reveal is measured. A line from the slides joins the cues. Under the stations: change one sentence, and only that section is voiced and recorded again.

The script, the voice, the cues, the slides and the film, and the file each one is. The cues are the join.

  • narrate turns 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.
  • cue turns each cue phrase in cues.json into a second on its own section’s clock.
  • record records each page section in headless Chromium, playing the page against those seconds.
  • score generates the music, the ambience bed and the effects the project describes.
  • assemble cuts 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.
  • verify measures the finished film: every section start, every cut, every seam and every landing.
The CLI reference lists every command with its options and its result.

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.
  • narrate is 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.
  • cue is second because a phrase becomes a second only once there are words with times on them.
  • record is third because the recorder plays the page to those seconds, so the picture lands on its word before anything is cut.
  • score is fourth because it is the last stage that can buy anything. Running it after record means that decktalk build --to record stops before it. A draft loop of decktalk build --no-spend --to record therefore spends nothing and still exercises the cues, the pages, the recording and the checks.
  • assemble is 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.
  • verify is last because it measures the film, and there is no film until assemble has 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.
A strip of recorded frames, each labelled with its second on the section clock: 0, 1/fps, 2/fps and on. Frame 0 is the start of the section clock, where the narration "A bowl. A ball. Watch it step down" starts. The bowl first shows in the first frame after the word bowl, and the ball in the first frame after the word ball.A strip of recorded frames, each labelled with its second on the section clock: 0, 1/fps, 2/fps and on. Frame 0 is the start of the section clock, where the narration "A bowl. A ball. Watch it step down" starts. The bowl first shows in the first frame after the word bowl, and the ball in the first frame after the word ball.

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.
  • narrate spaces the estimated words at narration.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.
  • cue marks a section whose words are estimated, because its seconds are estimated too.
  • The clicks are what lets verify compare the picture with the sound, so CUE_OFF still 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.
  • assemble skips the loudness pass, because a placeholder has no programme loudness worth matching and normalising it would move the clicks the check listens for.
A run that buys is the same command with --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