# Cues Source: https://docs.decktalk.app/concepts/cues How a spoken phrase becomes the second at which a visual appears. A cue is a named moment. In `cues.json` it is a spoken phrase to find in a section's narration. In the page it is an id that reveals elements or runs a handler. The `beats` stage joins the two by turning each phrase into seconds after the section starts. ```json cues.json theme={null} { "sections": { "3": { "min_seconds": 25, "cues": [ { "step": "3.1", "on": "$start" }, { "step": "3.1draw", "on": "curve draws" }, { "step": "3.1eq", "on": "equation", "offset": 0.2, "occurrence": 1 } ] } } } ``` ## Keys | Key | Required | Meaning | | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `step` | yes | A cue id the page understands. The key `cue` is accepted as a synonym. | | `on` | yes | A word or short phrase from that section's narration, or `$start`, or `$end`. | | `offset` | no | Seconds added to the match. It may be negative. | | `occurrence` | no | Which repeat of the phrase to use. The default is 1, the first. | | `case_sensitive` | no | Match case as written. The default is false. | | `min_seconds` | no | On the section, not the cue. DeckTalk warns when the section's speech is shorter than this, so you learn early that the visuals will not fit. | Every section key must exist in `decktalk.toml`, and a section may have no cues at all. A page section with no cues plays its autoplay timing from the page. ## Matching rules 1. The phrase is split into words. Case is ignored unless `case_sensitive` is true, and punctuation is ignored on both sides. 2. The section's words are scanned in order for a run that matches the phrase word for word. The `occurrence` key picks which run. 3. The cue time is the start of the first word of that run, plus `offset`. 4. `$start` is zero. `$end` is the end of the last spoken word in the section. The words come from the voice, so a phrase must be written as it is spoken. The script says "two x" and the slide shows `2x`, and the cue says "two x". A number written as digits in the script comes back from ElevenLabs in the form it chose to say, so digits in a cue phrase are fragile. ## Unresolved cues The `beats` stage prints `phrase not found` for a cue it cannot match and exits with status 1. The `build` command stops at that point, because a step whose only cues are unresolved would never appear on screen. Fix the phrase, or pass `--allow-unresolved` to build without that cue. The page then ignores the missing cue, and an element waiting for it reveals at its `data-at` time after its step mounts. A silent build estimates word times from the word count. Cues resolve, but their times are placeholders, and the report says so. ## Cue ownership in the page A cue belongs to one step, and the step mounts at its earliest cue. The runtime resolves the owner in this order. 1. The step with the same id as the cue. 2. The step whose `cues` list or object names the cue. 3. The step whose id is the longest prefix of the cue id. The cue `4.2b1` belongs to step `4.2`, and `9a` belongs to `9`. The first cued step mounts at t=0 regardless of its cue time, so a section never opens on an empty stage. A step with no cue never shows. The last cued step holds until the recorder stops. ## Sound on cues A sound effect in `[[mix.sfx]]` names a `section` and a `cue`, and DeckTalk places the effect where that cue resolved. An underscore marker in the markers file uses the same `on`, `occurrence`, `case_sensitive`, and `offset` keys to swell or mute the music at a phrase. [Sound](/concepts/sound) has both formats. # How it works Source: https://docs.decktalk.app/concepts/how-it-works The pipeline, the artifacts it writes, and why the cuts land on the frame. Write a script; your voice reads it with a time for every word; slides reveal on the words in Chromium; ffmpeg cuts one mp4. Write a script; your voice reads it with a time for every word; slides reveal on the words in Chromium; ffmpeg cuts one mp4. `decktalk build` runs seven stages in order. Each stage is a function that takes the project, writes a typed artifact under `build/`, and can be run on its own by the command of the same name. | Stage | What it does | Writes | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `narrate` | Sends each section of `script.md` to the voice and receives audio with a start and end time for every word. It hashes each section by its text and skips the ones that have not changed. It then joins the sections into one continuous track. | `build/audio/NN-slug.mp3`, `NN-slug.words.json`, `manifest.json`, `narration.mp3`, `timeline.json` | | `beats` | Finds each cue phrase in its section's words and turns it into seconds after the section starts. | `build/audio/beats.json` | | `record` | Opens each page section in headless Chromium with `?scene=N&beats=…&t0=signal`, covers the page in magenta, starts the narration clock, and records for the section's length plus a margin. | `build/rec/NN-scene.webm`, `NN-scene.json` | | `measure` | Finds the first clean frame after the magenta cover in each recording. That frame is narration t=0. | `lead_in_seconds` in each sidecar | | `check` | Flags a recording that is black, shorter than requested, or has no cover. | nothing | | `assemble` | Trims each recording at t=0, cuts it to its section's span, scales clips, renders slates, concatenates, mixes the soundscape, normalizes loudness in two passes, and publishes the film atomically. | `build/out/NN-section.mp4`, `build/out/.mp4` | | `verify` | Confirms that every section opens on a real frame, and that each named cue changed the picture. | nothing | A clip section skips `record` and `measure`. Its own audio replaces the narration for its span. [Build artifacts](/reference/artifacts) gives the shape of every file. ## Why the cuts are exact A strip of recorded frames opens magenta while the page is covered. The first clean frame is narration t=0, and the frames in which the curve draws and the number appears line up with the words curve and number. A strip of recorded frames opens magenta while the page is covered. The first clean frame is narration t=0, and the frames in which the curve draws and the number appears line up with the words curve and number. A browser does not start recording at a known instant, and Chromium on Windows starts later than Chromium on Linux. A timer therefore cannot say where the narration begins inside a recording. DeckTalk does not use one. The recorder covers the page in magenta from its first paint. It waits for the page's `load` event, for `window.__sceneReady`, for the settle time, and for a minimum lead after the recorder was created. Then it removes the cover and starts the page's clock in the same tick. The `measure` stage scans the recording for the magenta run and takes the first clean frame after it as t=0. That frame is t=0 by construction, whenever the capture began. Every cue is a spoken word with a timestamp from the same origin, so the page fires the reveal at the right second, and the assembler cuts the recording at the frame that matches the narration. Nothing in the chain depends on wall-clock time. The `verify` stage closes the loop on the finished mp4. For a cue it compares a frame just before the cue with frames shortly after it, counts the share of pixels that changed by more than a threshold, and compares that share with the same measure over a quiet span just before the cue. The control catches motion that is always there, such as a slow camera push, so only the reveal itself counts. ## Caching and cost The `narrate` stage keys each section by a hash of its text and its voice settings. A build after an edit re-synthesizes only the sections whose words changed, and re-records only those sections. Everything else comes from `build/`. The `--force` flag ignores the cache, and `--only N` limits a build to the sections named. The `--silent` flag replaces the voice with silence and estimates word times from the word count. Every other stage runs as usual, so a silent build proves the cues, the pages, the recording, and the assembly without an API key. ## Concurrency and safety Stages run one after another and never in parallel with themselves. Every JSON artifact is written to a temporary file and renamed into place, so a reader never sees a half-written file. The final mp4 is published the same way, and a timestamped copy sits beside it. Errors raise a `DeckTalkError` subclass with the file or field named, and the command line turns that into exit code 1 with the message on stderr. # The page contract Source: https://docs.decktalk.app/concepts/page-contract How a slide talks to the recorder. A DeckTalk page is an HTML file that the recorder can drive from the narration. The contract consists of a few URL parameters and one global. The file `decktalk-runtime.js` implements it, and a page can also implement it by hand. Slides are plain HTML that you style yourself, and the runtime adds no styling beyond the stage. ## URL parameters | Parameter | Meaning | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `scene=N` | Play scene N. Scene ids are strings, and the scaffold uses the script's section numbers. | | `beats=id@s,id@s,…` | Beat mode. Fire cue `id` at `s` seconds after narration t=0. The times come from `build/audio/beats.json` and are relative to the section's start in the continuous narration. | | `t0=S` | The number of seconds after the page's `load` event at which narration t=0 falls. `t0=signal` makes the page wait for `DeckTalk.startClock()` instead. The recorder uses the signal form: it covers the page in magenta until it starts the clock, and the assembler cuts at the first clean frame. | | `step=ID` | Freeze mode. Mount step ID with every reveal in its end state and its handler cues fired. The `shots` command uses it, and so can you. | | `speed=X` | The autoplay time scale. Beat mode ignores it. | | `hud=1` | Overlay the mode, scene, step, and clock. Never record with it on. | | none | Show the index page, which lists every scene and step with play and freeze links. | ## Globals * `window.__sceneReady` is an optional Promise. The recorder awaits it before it starts the clock, so a page can wait for fonts, images, or data. The runtime sets it to `document.fonts.ready` unless the page has set its own. * `window.__decktalk` exposes `{ mode, scene, step, cues, fired, catalog, now() }`. The `catalog` field lists every scene and its step ids, and the `shots` command reads it. The `fired` field lists the cue ids fired so far. * The runtime sets `document.body.dataset.done = "1"` once the last step has mounted. ## Timing in beat mode 1. The runtime sorts the cues by time. Each cue belongs to a step. The owner is the step with the same id. Failing that, it is the step whose `cues` list names the cue, or the step whose id is the longest prefix of the cue id. For example, `4.2b1` belongs to `4.2`, and `9a` belongs to `9`. 2. A step mounts at the earliest of its cues. The first cued step mounts at t=0 regardless, so a section never opens on an empty stage. A step with no cue never shows. The last cued step holds until the recorder stops. 3. When a step mounts, any element whose `data-cue` is in the list stays hidden until that cue fires. Every other reveal fires `data-at` seconds after the mount, and the default is 0. That covers an element with `data-at`, and an element whose `data-cue` is not in the list. 4. When a cue fires, the matching `data-cue` elements reveal first. Then the step's `on[id]` handler runs, and then every `DeckTalk.on(id, fn)` handler runs. Without `beats=`, the page autoplays. The steps mount in order, and each one holds for `hold` seconds divided by `speed`. Reveals fire at `data-at`, and a step's `cues: { id: seconds }` object fires handler cues at those seconds after the mount. ## Authoring API ```js theme={null} DeckTalk.scene(id, { name: "Shown on the index", camera: "push", // a slow 1.00 -> 1.03 push over the scene; omit for none steps: [{ id: "3.1", // also a cue id; the default is "." hold: 8, // autoplay seconds cues: ["3.1draw"] | { "3.1draw": 0.5 }, // ownership, and autoplay timing for handler cues render: ({ scene, step, frozen }) => "
", enter: (slideEl, { frozen }) => {}, // runs after the mount, for imperative setup on: { "3.1draw": () => {} }, // per-step cue handlers }], }); DeckTalk.on("3.1draw", fn); // a global cue handler DeckTalk.start(); // runs automatically on DOMContentLoaded when scenes exist ``` The markup attributes inside a step are `data-cue`, `data-at`, `data-fx`, `data-dur`, `data-count`, `data-type`, and `data-tex`. The `data-fx` attribute selects the reveal animation. The default is `rise`, and the others are `fade`, `draw` for SVG paths with `pathLength="1"`, `drop`, `pop`, `dim`, and `none`. The `data-dur` attribute sets the animation length in seconds, and a count-up defaults to 0.9. The `data-count` attribute counts the last number in the text up from zero, and `data-count="first"` counts the first number instead. The `data-type` attribute types the text at the given milliseconds per character, with a default of 40. The `data-tex` attribute typesets its value with KaTeX when KaTeX is on the page, and `data-display` selects display math. The runtime creates `#dt-stage`, a 1920 by 1080 stage that scales to fit the window, with `#dt-cam` and `#dt-pan` inside it. Slides are `.dt-slide` children of the pan layer. You style the stage and your slides however you like. The runtime's own CSS uses the `dt-` prefix. ## Build artifacts a page or a script may read | File | Shape | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `build/audio/timeline.json` | `{narration, total_seconds, estimated, sections: {"NN": {title, start, end, duration, speech_end, words: [{word, start, end}]}}}`. The seconds are absolute within `narration.mp3`. | | `build/audio/beats.json` | `{"NN": "cue@seconds,…"}`. The seconds are relative to the section start. | | `build/audio/manifest.json` | One entry per narrated section, with its file, its words file, its hash, and its durations. | | `build/rec/NN-scene.json` | What the recorder did, and where narration t=0 sits in the webm. | ## Recording alignment The recorder covers the page in magenta from its first paint. It opens the page with `t0=signal`, waits for `load`, awaits `__sceneReady`, waits the settle time and at least `min_lead_seconds` after the recorder was created, and then removes the cover and calls `DeckTalk.startClock()` in the same tick. The `measure` command finds the first clean frame after the magenta run and writes it to the sidecar as `lead_in_seconds`. The assembler trims the recording there, so video t=0 equals narration t=0 to within a frame on every platform. [How it works](/concepts/how-it-works#why-the-cuts-are-exact) has the picture. A page that implements the contract by hand must honor `t0=signal` by starting its clock in `DeckTalk.startClock()` or an equivalent global, or it will start early. # The project file Source: https://docs.decktalk.app/concepts/project-file Every table and key in decktalk.toml. Each project has one `decktalk.toml`. Every path in it is relative to the project directory. An unknown top-level table fails the load, so a typo cannot silently do nothing. The file has two kinds of content. The document tables on this page describe the presentation. The [tuning tables](/reference/configuration) change how DeckTalk renders and are rarely needed. ```toml decktalk.toml theme={null} [project] name = "my-lesson" [voice] stability = 0.55 [[section]] number = 1 title = "Open" page = "deck/index.html" [[section]] number = 2 title = "On camera" clip = "media/interview.mp4" [transition] dips = [[1, 2]] [mix] underscore = "build/music/underscore.mp3" ``` ## `[project]` | Key | Default | Meaning | | -------- | ------------------ | ----------------------------------------------------------------------------- | | `name` | the directory name | The output name. The film lands at `build/out/.mp4`. | | `script` | `script.md` | The narration file. | | `cues` | `cues.json` | The cue file. It is optional, and without it pages run their built-in timing. | | `build` | `build` | The directory for generated files. | ## `[voice]` These are the voice settings for this presentation. The `model` key overrides the tool default, which is `eleven_multilingual_v2` unless `[narration]` says otherwise. | Key | Default | Meaning | | ------------------ | ---------------- | ------------------------------------------------------ | | `provider` | `elevenlabs` | The speech provider. ElevenLabs is the only one today. | | `model` | the tool default | The ElevenLabs model id. | | `stability` | 0.55 | Lower values vary more between takes. | | `similarity_boost` | 0.75 | How closely the voice follows the clone. | | `style` | 0.0 | Style exaggeration. Zero is the most stable. | | `speaker_boost` | true | ElevenLabs' speaker boost. | | `speed` | 1.0 | Speaking rate. | The voice id and the API key are secrets, so they live in `.env` as `ELEVENLABS_VOICE_ID` and `ELEVENLABS_API_KEY`, or in the environment. ## `[[section]]` Write one table per `## N.` section of the script. The order does not matter, because DeckTalk sorts them by `number`. Every script section must have one. A section is either a page or a clip. | Key | Applies to | Default | Meaning | | --------------- | ---------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `number` | both | required | The number that matches `## N.` in the script. | | `title` | both | empty | The title shown on slates and in the status report. | | `page` | page | required | The HTML file, for example `deck/index.html`. | | `scene` | page | the section number | The value the page receives as `?scene=`. It may be a number or a string. | | `extra_seconds` | page | 0.3 | How long the recording runs past the narration. The margin keeps the cut off an undrawn frame. | | `hold_seconds` | page | 0 | How long the last frame holds. Only the last page section may hold, because the narration is continuous and an earlier hold would push every later visual off its words. | | `ambience` | page | false | Whether the ambience bed plays under this section. | | `params` | page | none | Extra query parameters for the page, written as a table such as `[section.params]` with `theme = "dark"`. | | `clip` | clip | required | Your video file, with its own audio. | | `slate_seconds` | clip | 5 | How long the titled slate plays when the clip file is missing. | A clip that is not 1920 by 1080 is scaled and padded to fit. A missing clip is not an error unless you pass `--strict`. A titled slate plays for `slate_seconds` in its place, so a project builds before every asset exists. ## `[transition]` | Key | Default | Meaning | | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `dips` | every cut | The boundaries that dip to black, written as `[[from, to], …]` section numbers. Leave the key out to dip at every cut, or set `[]` for no dips. | | `dip_seconds` | 0.15 | The fade length. It applies to video only, and audio never dips. | | `page_fades_in` | true | Pages fade themselves up, so DeckTalk adds no fade-in on their side of a dip. | ## `[mix]` The mix is optional. Without this table the film carries narration and clip audio only. [Sound](/concepts/sound) explains how the parts fit together. | Key | Default | Meaning | | ------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------- | | `underscore` | none | The music bed. DeckTalk loops it and ducks it under speech. | | `underscore_db` | -24 | The bed level. | | `underscore_duck_db` | -6 | The additional attenuation under speech. | | `underscore_fade_in`, `underscore_fade_out` | 2, 3 | The fade lengths in seconds. | | `markers` | none | A JSON file that swells or mutes the bed at spoken phrases. | | `ambience` | none | The ambience bed. It plays under sections that set `ambience = true`. | | `ambience_db` | -20 | The ambience level. | | `slate` | rendered | A PNG to use instead of the rendered titled slate. | | `[[mix.sfx]]` | none | One-shot effects. Each has `file`, `section`, `cue`, `db` (default -16), and `offset` (default 0). | | `[mix.loudnorm]` | `I = -16`, `TP = -1.5`, `LRA = 11` | The EBU R128 targets for the final mix. | ## `[soundscape]` These tables hold the prompts for `decktalk soundscape`, which generates sound with ElevenLabs. Ambience and music write to the `ambience` and `underscore` paths named in `[mix]`. When `[mix]` names none, they write to `build/sfx/ambience.mp3` and `build/music/underscore.mp3`. Each sound effect writes to `build/sfx/.mp3` unless its `out` key says otherwise. | Table | Keys | | ------------------------- | ---------------------------------------------------------------------------------------- | | `[soundscape.ambience]` | `text` (required), `duration_seconds` (25), `prompt_influence` (0.3), `model_id`, `out` | | `[soundscape.sfx.]` | `text` (required), `duration_seconds` (0.5), `prompt_influence` (0.5), `model_id`, `out` | | `[soundscape.music]` | `prompt` (required), `seconds` (360), `force_instrumental` (true), `model_id`, `out` | ## Tuning tables Any settings section can be overridden per project with a table of the same name, such as `[video]` or `[record]`, or per shell with a `DECKTALK_
_` variable. [Configuration](/reference/configuration) lists every field with its default. ## `script.md` The script is markdown with one rule. Each `## N. Title` heading starts section `N`, and the text under it is what the voice says for that section. * Text in square brackets, such as `[Deck scene 3.]`, is a stage direction. It is not spoken, and a short pause is left where it sat. * A bracket in capitals, such as `[NAME]`, is a placeholder. The `narrate` stage refuses to synthesize a section that still has one unless you pass `--allow-placeholders`. * A tag such as `` inserts a pause of that length. * Other markdown, such as emphasis, is stripped before synthesis. ## `cues.json` The cue file names the spoken phrase that each visual lands on. [Cues](/concepts/cues) describes its shape and the matching rules. # Sound Source: https://docs.decktalk.app/concepts/sound The underscore, the ambience, the effects, and the loudness of the final mix. The film always carries the narration and the audio of any clip section. Everything else is optional and stays off until `[mix]` in `decktalk.toml` names a file. You can bring your own files, or generate them with `decktalk soundscape`. ## The parts of the mix | Part | Source | Behaviour | | ---------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Narration | `build/audio/narration.mp3` | Always present. Silent under clip sections. | | Clip audio | each clip file | Replaces the narration for the clip's span. | | Underscore | `mix.underscore` | Loops for the length of the film, fades in and out, and ducks by `underscore_duck_db` whenever speech is present. The duck ramps over `duck_ramp_seconds`. | | Ambience | `mix.ambience` | Plays under sections that set `ambience = true`, with a ramp at each edge. | | Effects | `[[mix.sfx]]` | One-shot files placed at a cue in a section, plus an offset. A cue that did not resolve skips its effect with a warning. | After the parts are summed, DeckTalk measures the loudness of the mix, applies EBU R128 normalization in a second pass to the targets in `[mix.loudnorm]`, and applies a limiter. The report prints the loudness before and after. The `--no-loudnorm` flag skips the normalization, and `--nomix` produces narration only. ## Markers A markers file swells or mutes the underscore at spoken phrases. It uses the same matching keys as a cue. ```json media/markers.json theme={null} { "boost_db": 3, "boost_seconds": 2, "markers": [ { "name": "open", "section": 1, "on": "$start" }, { "name": "zero", "section": 2, "on": "worth nothing", "mute_seconds": 0.6 } ] } ``` A marker swells the bed by `boost_db` for `boost_seconds`. With `mute_seconds`, it silences the bed for that long first. The ramps are tuning fields in `[audio]`. ## Generating sound The `soundscape` command turns the prompts in `[soundscape]` into files with ElevenLabs. It writes ambience and music to the paths named in `[mix]`, and each effect to `build/sfx/.mp3`. ```toml decktalk.toml theme={null} [soundscape.music] prompt = "calm, minimal instrumental underscore for a lesson: soft piano and warm pads, slow, no drums" seconds = 120 [soundscape.ambience] text = "quiet lecture hall room tone, distant HVAC, no voices, steady" [soundscape.sfx.tick] text = "soft interface click, single, short, no reverb" ``` ```console theme={null} decktalk soundscape --dry-run # prints every request without sending it decktalk soundscape # generates what is missing decktalk soundscape --only music # one item; --force regenerates it ``` Music longer than `max_music_chunk_seconds` is requested in chunks and crossfaded. The generated files are cached by their prompt, so a second run sends nothing unless a prompt changed. These calls spend ElevenLabs credits, and the dry run shows exactly what would be sent. ## Bringing your own Point `mix.underscore`, `mix.ambience`, and each `[[mix.sfx]]` at any audio file that ffmpeg can read. The `soundscape` command only writes files that do not exist yet, so a file of your own at the same path is never overwritten. # Your first deck Source: https://docs.decktalk.app/guides/first-deck One section, written from scratch, in all four files. A DeckTalk project is four files that refer to each other by number and by id. This page writes one section, the curve-and-equation scene from the scaffold, and shows every reference as it is made. Start from `decktalk init my-lesson` so that the runtime and the stylesheet are in place. Each `## N. Title` heading starts a section. Text in square brackets is a stage direction. It is not spoken, and it leaves a short pause where it sat. Write numbers and symbols as you want them said, because cue phrases match spoken words. ```md script.md theme={null} ## 3. A curve and an equation [Deck scene 3. A curve draws, then the equation appears.] Now a curve draws across the screen as I talk. And here is an equation: the derivative of x squared is two x. ``` Every script section needs one `[[section]]` table with the same `number`. A section is either a page, which DeckTalk records and cuts, or a clip, which is your own video. The `scene` value is what the page receives as `?scene=`, and it defaults to the section number. ```toml decktalk.toml theme={null} [[section]] number = 3 title = "A curve and an equation" page = "deck/index.html" scene = 3 extra_seconds = 2 # keep recording past the narration, so the last cut is never short hold_seconds = 2 # hold the final frame; allowed only on the last page section ``` A cue pairs a cue id with the spoken phrase it lands on. Matching takes the first occurrence, ignores case, and ignores punctuation. The values `$start` and `$end` are the edges of the section's speech, `offset` shifts the moment in seconds, and `occurrence` picks a later repeat of the same words. ```json cues.json theme={null} { "sections": { "3": { "cues": [ { "step": "3.1", "on": "$start" }, { "step": "3.1draw", "on": "curve draws" }, { "step": "3.1eq", "on": "equation", "offset": 0.2 } ] } } } ``` Run `decktalk beats` at any time to see where each phrase resolved. A phrase that is not in the section's words is reported as `phrase not found`, and `build` stops there until you fix it or pass `--allow-unresolved`. A page registers one scene per section. A scene is a list of steps, and each step renders some HTML. A cue belongs to the step with the same id, or to the step whose `cues` names it, or to the step whose id is the longest prefix of the cue id. Here the step is `3.1`, so `3.1draw` and `3.1eq` belong to it, and the elements marked `data-cue` stay hidden until their cue fires. ```html deck/index.html theme={null} ``` The `cues` object on the step gives autoplay times, which the page uses when you open it in a browser without beats. During a recording the beats replace them. The `data-fx` attribute picks the reveal animation, and `data-tex` typesets the text with KaTeX when KaTeX is on the page. [The page contract](/concepts/page-contract) lists every attribute. Open `deck/index.html?step=3.1` in a browser to see the step with everything revealed, or `?scene=3` to watch it autoplay. Then take screenshots of every step, and of the scene at a given second with its real cues. ```console theme={null} decktalk shots decktalk shots --section 3 --at 6 ``` ```console theme={null} decktalk build --silent # placeholder voice, no key decktalk build # your voice decktalk verify 3:3.1draw 3:3.1eq ``` The verify table shows, for each cue, the second at which it fired in the final mp4, the share of pixels that changed across it, and the same measure over a quiet control span just before it. A cue lands when the change beats the control by a margin. ```text theme={null} check cue at chg % ctl % result 3:3.1draw 0.80 29.90 0.38 0.00 changed 3:3.1eq 5.80 34.90 0.96 0.02 changed ``` ## The naming chain | In | Name | Refers to | | ----------------- | ------------------------------------------------------- | ------------------------------------------------------------ | | `script.md` | `## 3.` | the section number | | `decktalk.toml` | `number = 3`, `scene = 3` | the same section, and the scene the page plays for it | | `cues.json` | `"3"`, `"step": "3.1eq"` | the section, and a cue id the page understands | | `deck/index.html` | `DeckTalk.scene(3, …)`, `id: "3.1"`, `data-cue="3.1eq"` | the scene, its step, and the element that reveals on the cue | ## Next Add a second section by repeating the steps with a new number. Add a clip of your own with `clip = "media/intro.mp4"` in a section table and a matching heading in the script. Then read [Sound](/concepts/sound) to add an underscore and a sound effect. # FAQ Source: https://docs.decktalk.app/help/faq Cost, lock-in, the voice, and what DeckTalk is not. ## What does it cost? You need an ElevenLabs plan with API access. The free tier has the API, but its audio carries a watermark and a non-commercial license, so the Starter plan is the practical floor. A ten-minute narration is roughly 9,000 characters, which fits inside that plan's monthly allowance. Because DeckTalk caches sections by text, an edit costs the sentences you changed rather than the whole script. Everything else is free. Chromium and ffmpeg run on your machine, and `--silent` builds spend nothing. ## Am I locked into ElevenLabs? ElevenLabs is the only provider today because it returns a timestamp for every word. The provider is one module behind a two-method protocol, and anything that returns word times can take its place. A local text-to-speech engine with a forced aligner is the obvious candidate. The `--silent` flag needs no provider at all, so the project format does not depend on any vendor. ## How does the voice sound? Any ElevenLabs voice id works, including a clone of your own. Stability, similarity, style, and speed are settings under `[voice]` in `decktalk.toml`. DeckTalk sends the previous and next text with each section so that the prosody stays continuous across cuts. ## Why not record the screen? You would record it again after every edit, and you would re-time every reveal by hand. Here an edit rebuilds one section, and the reveals follow the words wherever they move. ## Do I have to use generated music? No. The soundscape stays off until `[mix]` names a file, and you can bring your own files. The `soundscape` command exists for when you want ElevenLabs to make the bed and the effects from a prompt. ## How is this different from Remotion, Descript, or Synthesia? Remotion and Motion Canvas are for timing visuals by frame in code. Descript is for editing a recording you already made. Synthesia and HeyGen put an avatar on screen. DeckTalk is for the case where the timing should come from the words and the slides are yours. ## Can I use my own HTML framework? Yes. A page is any HTML that honors the [page contract](/concepts/page-contract). The runtime is one file with no dependencies, and it adds nothing to your markup beyond the stage element. KaTeX is optional. ## Does it run on Windows? Yes. The test suite and a full offline build run on Linux, macOS, and Windows in CI for every push to `main`. ## Where does the name come from? A deck is the slides. The talk is the narration. DeckTalk cuts the one to the other. # Troubleshooting Source: https://docs.decktalk.app/help/troubleshooting What each warning and failure means, and what to do about it. Run `decktalk build --silent` first when something goes wrong. It removes the voice from the picture, costs nothing, and shows whether the problem is in the cues, the pages, or the assembly. The `beats` stage could not find a cue's `on` phrase among the spoken words of its section, and `build` stopped. Compare the phrase with the words in `build/audio/NN-slug.words.json`. The usual causes are a number written as digits, a symbol, or a phrase that spans a stage direction. Write the phrase as the voice says it. The `--allow-unresolved` flag builds without the cue. Copy `.env.example` to `.env` beside `decktalk.toml` and replace both placeholders. A value still wrapped in angle brackets counts as unset. Exported variables work too. A silent build needs neither. A 401 means the key is wrong or was revoked. A 402 or a quota message means the plan has no characters left. The free tier's audio carries a watermark and a non-commercial license, and some voices need a paid plan. `decktalk narrate --dry-run` shows how many characters a build would send. The recording has no magenta run at its start, so `measure` guessed the trim from the first painted frame. The page probably removed the cover itself, or a page written by hand started its clock at load instead of waiting for `DeckTalk.startClock()`. Make sure the page includes the packaged runtime, and run `decktalk runtime` after an upgrade. Raise `min_lead_seconds` in `[record]` if the machine is very slow. A black recording usually means the page threw before it rendered, and the recorder logs page errors as warnings. Open the page with `?scene=N&hud=1` in a browser. A truncated recording is shorter than the section needs, which happens when Chromium was killed or the machine stalled. Re-record that section with `--only N`. The final mp4 opens a section on a dark frame. Check that the first cued step of the scene renders something at t=0, and that `extra_seconds` is not zero. Raise `after_dip_seconds` in `[verify]` if the dip to black is deliberately long. The picture did not change enough across the cue, or changed no more than it was already changing. A very small or very slow reveal can fail this check while looking right. Take a frame with `decktalk shots --section N --at S` just after the cue to see what happened. If the reveal is deliberately subtle, lower `min_changed_percent` in `[verify]`, or lengthen `probe_delays` for a slow animation. Open the section's `build/rec/NN-scene.json` and look at `lead_method`. Anything but `cover (…)` means the alignment was guessed. Re-record with `decktalk record --only N` followed by `decktalk measure --only N`. The file named by `clip` does not exist, so a slate plays for `slate_seconds`. Drop the file in place, or delete the section from both `decktalk.toml` and `script.md`. The `--strict` flag makes this an error instead. The page does not include `decktalk-runtime.js`, or the script tag comes after the scene definitions. Include the runtime first, then define scenes. DeckTalk runs its own test suite on Windows in CI. Paths in `decktalk.toml` use forward slashes on every platform. The `setup` command puts Chromium under `%LOCALAPPDATA%\ms-playwright`. If a corporate proxy blocks that download, set `PLAYWRIGHT_DOWNLOAD_HOST` as Playwright documents. Encoding dominates. Set `DECKTALK_VIDEO_PRESET=veryfast` for drafts, or put `preset = "veryfast"` under `[video]` in the project. The final render can go back to `medium`. A full silent build of the scaffold takes under a minute on a laptop. If none of this fits, open an issue with the output of `decktalk doctor` and the `[[section]]` tables involved. The bug report template asks for exactly that. # Overview Source: https://docs.decktalk.app/index Narrated presentations, cut to the word. A playhead moves along a spoken sentence, one tick per word. The slide reacts on exactly the right words. A playhead moves along a spoken sentence, one tick per word. The slide reacts on exactly the right words. DeckTalk turns a markdown script and plain HTML slides into one narrated mp4. An ElevenLabs voice reads the script and returns a timestamp for every word. Each visual names the phrase it should land on. DeckTalk records the slides in headless Chromium, cuts the recording to the narration, mixes an optional soundscape, and publishes the film. When you change a sentence, only that section renders again. Install DeckTalk, build the scaffold, and add your voice. Ten minutes. Write a section, cue it, and build a slide for it. The four files and how they refer to each other. Every file, command, variable, and exit code on one page. Written for agents and for people in a hurry. Drive the pipeline from your own code. ## Five words The documentation uses five words with fixed meanings. | Word | Meaning | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Section** | One `## N. Title` block of `script.md` together with its `[[section]]` table in `decktalk.toml`. A section is the unit of narration, recording, and cutting. | | **Scene** | What a page plays for one section. The page declares it with `DeckTalk.scene(N, …)`, and the recorder selects it with `?scene=N`. | | **Step** | One slide state inside a scene. The query `?step=ID` freezes it for review. | | **Cue** | A named moment. In `cues.json` it is a spoken phrase to find. In the page it is an id that reveals elements or runs a handler. | | **Beat** | A cue resolved to seconds after the section's narration begins. DeckTalk stores beats in `build/audio/beats.json` and hands them to the page as `?beats=`. | The same number `N` names a section in the script, its `[[section]]` table, its key in `cues.json`, and by default its scene in the page. Step ids and cue ids are strings that the page and `cues.json` agree on, and the scaffold prefixes them with the scene number. ## What DeckTalk is not DeckTalk is not a video editor and never makes a screen recording of you. It is not an avatar generator. It does not time visuals by frame in code, as Remotion and Motion Canvas do. It is for the case where the timing should come from the words and the slides are yours. ## How the documentation is organized * **Get started** installs the tool and walks through authoring one section. * **Concepts** explains each input and the pipeline that turns them into a film. * **Reference** lists every command, key, field, artifact, and Python name. * **Help** covers the errors you can hit and the questions people ask before trying it. Every page is also served as plain text for language models at [docs.decktalk.app/llms.txt](https://docs.decktalk.app/llms.txt) and [llms-full.txt](https://docs.decktalk.app/llms-full.txt). # Quickstart Source: https://docs.decktalk.app/quickstart From an empty directory to a finished mp4. Everything DeckTalk needs arrives through Python packages. Playwright's package carries its own browser driver, and `static-ffmpeg` carries ffmpeg and ffprobe for your platform. You need Python 3.12 or later. You do not need Node. ```console theme={null} uv tool install decktalk # or: pipx install decktalk decktalk setup # fetches headless Chromium (about 100 MB) and ffmpeg, once per machine decktalk doctor # confirms that both work ``` `uv` is a Python package manager from Astral. The command `uv tool install` puts a command-line tool in its own environment and on your PATH. If you do not have it, `pipx install decktalk` does the same job, and so does `pip install decktalk` inside a virtual environment. ```console theme={null} decktalk init my-lesson && cd my-lesson decktalk build --silent # a full render with placeholder narration and no key open build/out/my-lesson.mp4 ``` The `--silent` flag runs every stage with a silent placeholder track and estimated word times. It needs no API key and costs nothing. Use it to prove that a project builds before you spend credits. The scaffold has four sections. Section 0 is a clip section. It expects your own video, with its own audio, at `media/open.mp4`. That file does not exist yet, so a titled slate plays in its place. Sections 1 to 3 are page sections. They are three scenes in `deck/index.html`, and DeckTalk records them and cuts them to the narration. Run the scaffold as it is first. Then either drop a video at `media/open.mp4` or delete section 0 from both `decktalk.toml` and `script.md`. Copy `.env.example` to `.env` and replace the two placeholders. ```dotenv .env theme={null} ELEVENLABS_API_KEY=sk_... # Profile, then API keys, on elevenlabs.io ELEVENLABS_VOICE_ID=... # the id on a voice's card under Voices ``` Both values can also come from the environment. A value left as `` counts as unset. DeckTalk never prints the key and never writes it under `build/`. Then build the project again. ```console theme={null} decktalk build ``` You need an ElevenLabs plan with API access. The free tier has the API, but its audio carries a watermark and a non-commercial license, so the Starter plan is the practical floor. The [FAQ](/help/faq) has the arithmetic. Edit any sentence in `script.md` and run `decktalk build` again. DeckTalk hashes each section by its text, so only the section you touched is re-synthesized and re-recorded. The rest comes from the cache. Open `deck/index.html` in any browser with no query string. The page shows an index of every scene and step. Add `?scene=2` to play a scene, `?step=2.1` to freeze one step with everything revealed, or `&hud=1` to overlay the clock. ```console theme={null} decktalk shots # one PNG per step, under build/shots/ decktalk shots --section 3 --at 4.5 # a frame from section 3 while it plays with its real cues decktalk verify 3:3.1eq # proves that the cue changed the picture in the final mp4 ``` ## What is in a project | Path | What it holds | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `decktalk.toml` | The plan. It has one `[[section]]` per script section, the voice, the transitions, the mix, the soundscape prompts, and any tuning. | | `script.md` | The narration. Each `## N. Title` heading starts a section, and text inside square brackets is a stage direction that is not spoken. | | `cues.json` | The phrase that each visual lands on. | | `deck/index.html` | Your slides, with `decktalk-runtime.js` beside them. | | `media/` | Your own clips and the underscore markers. | | `build/` | Everything DeckTalk generates. Git ignores it. | Write numbers and symbols in the script the way you want them spoken. A cue matches the spoken words "two x", while the slide shows the symbols. ## Next [Your first deck](/guides/first-deck) writes one section from scratch and shows how the four files refer to each other. # Build artifacts Source: https://docs.decktalk.app/reference/artifacts The shape of every file DeckTalk writes under build/. These files are part of the public contract. The page runtime reads `beats.json`, and your own scripts may read any of them. Every JSON file is written atomically, so a reader never sees a partial file. Field names in the JSON match the dataclasses in `decktalk.artifacts`. ## `build/audio/NN-slug.words.json` A list of words with absolute times within that section's mp3. ```json theme={null} [{"word": "Now", "start": 0.71, "end": 0.93}, {"word": "a", "start": 0.93, "end": 1.02}] ``` ## `build/audio/manifest.json` One entry per narrated section. The `hash` is what the cache compares. | Field | Meaning | | --------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | `script`, `model`, `output_format` | What produced the audio. | | `estimated`, `estimate_basis` | True after a silent build, with the pacing used. | | `total_seconds` | The sum of the segments. | | `segments.NN.index`, `.title` | The section. | | `segments.NN.file`, `.words_file` | The mp3 and the words file. | | `segments.NN.hash` | The text and voice hash. | | `segments.NN.words`, `.est_seconds`, `.duration_seconds` | The word count, the pacing estimate, and the real length. | | `segments.NN.target_seconds`, `.speech_end_seconds`, `.tail_padded_seconds` | The requested length, where speech ends, and how much silence was added. | ## `build/audio/timeline.json` Section and word times, absolute within `narration.mp3`. ```json theme={null} { "narration": "narration.mp3", "total_seconds": 36.4, "estimated": false, "sections": { "03": { "title": "A curve and an equation", "start": 26.1, "end": 36.4, "duration": 10.3, "speech_end": 35.9, "words": [{"word": "Now", "start": 26.81, "end": 27.03}] } } } ``` ## `build/audio/beats.json` The resolved cues per section, as the string the page receives in `?beats=`. The seconds are relative to the section's start. ```json theme={null} { "01": "1a@0,1b@2.3,1c@6.3", "03": "3.1@0,3.1draw@0.8,3.1eq@5.8" } ``` ## `build/rec/NN-scene.json` What the recorder did for one section, and where narration t=0 sits in the webm. | Field | Meaning | | ------------------------------ | ---------------------------------------------------------------------------------------------- | | `url` | The page URL the recorder opened, including `scene`, `beats`, and `t0=signal`. | | `requested_seconds` | How long the recording was asked to run. | | `settle_seconds` | How long the recorder waited after load before starting the clock. | | `load_seconds`, `lead_seconds` | Wall-clock estimates from the recorder. | | `lead_in_seconds` | The first clean frame after the magenta cover, written by `measure`. The assembler trims here. | | `lead_method` | How `measure` found it. A value beginning `NO COVER` is a guess. | ## `build/out/` | File | Meaning | | -------------------------- | ---------------------------------------------------------- | | `NN-section.mp4` | Each section cut to its span, at the project's frame size. | | `.mp4` | The film. | | `-YYYYMMDD-HHMM.mp4` | A timestamped copy of the same film. | | `slates/` | Rendered slates for clip sections whose file is missing. | # Reference card Source: https://docs.decktalk.app/reference/card Every file, command, variable, and exit code on one page. This page is the whole contract in the fewest words. It is written for language models and for people who already know what they are looking for. Every item links to the page that explains it. ## Inputs All four live in the project directory. [Your first deck](/guides/first-deck) writes them. | File | Shape | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `decktalk.toml` | `[project]` with `name`; `[voice]`; one `[[section]]` per script section with `number`, `title`, and either `page` + `scene` or `clip`; optional `[transition]`, `[mix]`, `[soundscape]`, and tuning tables. [Project file](/concepts/project-file). | | `script.md` | `## N. Title` starts section `N`. `[bracketed]` text is a direction and is not spoken. `[CAPITALS]` is a placeholder and fails `narrate`. `` pauses. | | `cues.json` | `{"sections": {"N": {"min_seconds"?: s, "cues": [{"step": id, "on": phrase \| "$start" \| "$end", "offset"?: s, "occurrence"?: n, "case_sensitive"?: bool}]}}}`. First occurrence, case and punctuation ignored. [Cues](/concepts/cues). | | `deck/index.html` + `deck/decktalk-runtime.js` | `DeckTalk.scene(N, {name, camera?, steps: [{id, hold?, cues?, render, enter?, on?}]})`. Reveals use `data-cue="id"`, `data-at="s"`, `data-fx`, `data-dur`, `data-count`, `data-type`, `data-tex`. [Page contract](/concepts/page-contract). | | `.env` | `ELEVENLABS_API_KEY`, `ELEVENLABS_VOICE_ID`. Exported variables work too. Not needed with `--silent`. | The number `N` is the same in the script heading, the section table, the cues key, and the scene. A cue belongs to the step with the same id, or the step whose `cues` names it, or the step whose id is its longest prefix. ## Commands ```console theme={null} decktalk setup # once per machine: Chromium and ffmpeg decktalk init DIR [--name NAME] # scaffold decktalk build --silent # free dry run of every stage, no key decktalk build # the film, at build/out/.mp4 decktalk build --only 3 --preset veryfast # re-record one section, fast encode decktalk beats # where each cue phrase resolved; exit 1 if any is missing decktalk verify 3:3.1eq # prove a cue changed the picture in the final mp4 decktalk shots [--section N --at S] # PNG per step, or a frame from a playing section decktalk status # timeline and what is built decktalk narrate --dry-run # what would be sent to the voice, without sending it decktalk soundscape --dry-run # same, for ambience, effects, and music decktalk runtime # refresh deck/decktalk-runtime.js after an upgrade ``` Every project command takes `-p DIR` or `--project DIR` and defaults to the current directory. Global flags `-v` and `-q` go before the command. [CLI](/reference/cli) lists every flag. ## Stages and artifacts `build` = `narrate` → `beats` → `record` → `measure` → `check` → `assemble` → `verify`. | Artifact | Written by | Holds | | -------------------------------------------- | --------------- | --------------------------------------------------------------- | | `build/audio/NN-slug.mp3`, `.words.json` | narrate | one section's audio and `[{word, start, end}]` | | `build/audio/manifest.json` | narrate | one entry per section with its text hash and durations | | `build/audio/narration.mp3`, `timeline.json` | narrate | the continuous track and each section's absolute span and words | | `build/audio/beats.json` | beats | `{"NN": "cue@seconds,…"}` relative to the section start | | `build/rec/NN-scene.webm`, `.json` | record, measure | the recording and where narration t=0 sits in it | | `build/out/NN-section.mp4` | assemble | each section cut to its span | | `build/out/.mp4` | assemble | the film, plus a timestamped copy | | `build/shots/*.png` | shots | review screenshots | [Build artifacts](/reference/artifacts) gives every field. ## Settings Defaults → tables of the same name in `decktalk.toml` → `DECKTALK_
_` environment variables → the flags `--preset`, `--crf`, `--settle`, `--model`. Sections are `video`, `narration`, `record`, `align`, `audio`, `verify`, `elevenlabs`. [Configuration](/reference/configuration) lists every field. ## Exit codes and errors | Code | When | | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 0 | Success. | | 1 | Any `DeckTalkError`, printed to stderr as `error: …`. Also `beats` with an unmatched phrase, `build` unless assembly and verification both pass, `verify` with a failed check, `check --strict` with a suspect recording, and `doctor` with a missing tool. | | 130 | Interrupted. | `ConfigError` means an input is bad or inconsistent. `MissingInputError` means an earlier stage has not run. `ProviderError` means ElevenLabs refused or failed. `ToolError` means ffmpeg or Chromium failed. Progress goes to stderr through the `decktalk` logger. Keys are never printed. ## Python ```python theme={null} import decktalk project = decktalk.Project.load("my-lesson") result = decktalk.build(project, silent=True) print(result.assembly.final, result.verification.ok) ``` [Python API](/reference/python-api) lists every public name. # CLI Source: https://docs.decktalk.app/reference/cli Every command, every flag, and what each one produces. ```text theme={null} decktalk [-v | -q] [-p DIR] [flags] ``` Every project command takes `--project DIR`, or `-p DIR`, and defaults to the current directory. Each one reads `.env` from the project and never prints a key. Progress goes to stderr. The `-v` flag shows debug logging, including every ffmpeg command line, and `-q` shows warnings only. Both go before the command name. `--version` prints the version. ## Machine ### `decktalk setup` Fetches headless Chromium through Playwright and ffmpeg through `static-ffmpeg`, once per machine. Both land in per-user caches, not in the project. ### `decktalk doctor` Reports the Python, Playwright, Chromium, and ffmpeg versions found, and exits 1 when one is missing. ### `decktalk init DIR` Scaffolds a project with a working example deck. | Flag | Meaning | | ------------- | ---------------------------------------------------- | | `--name NAME` | The project name. The default is the directory name. | | `--force` | Write into a directory that is not empty. | ### `decktalk runtime` Copies the packaged `decktalk-runtime.js` over the project's copy. Run it after an upgrade. ## Stages ### `decktalk narrate` Turns the script into per-section audio with word timestamps, then builds the continuous track and `timeline.json`. It caches each section by text. | Flag | Meaning | | ---------------------- | ------------------------------------------------------------------- | | `--only N` | Only these section numbers. Repeat the flag for several. | | `--force` | Ignore the text-hash cache and synthesize every section. | | `--dry-run` | Parse the script and print what would be sent. No API calls. | | `--silent` | Write silent placeholders with estimated word times. No key needed. | | `--allow-placeholders` | Synthesize a section that still has a `[CAPITAL]` placeholder. | | `--model M` | The ElevenLabs model for this run. | ### `decktalk beats` Resolves every cue phrase to a second and prints a table of sections, their speech end, and their resolved cues. It reports each phrase it cannot find and exits 1 if there is one. ### `decktalk soundscape` Generates the ambience, the sound effects, and the underscore from the prompts in `decktalk.toml`. It writes only files that do not exist yet. | Flag | Meaning | | ------------- | ------------------------------------------------- | | `--only NAME` | One item: `ambience`, `music`, or an effect name. | | `--force` | Regenerate even if the file exists. | | `--dry-run` | Print every request without sending it. | ### `decktalk record` Records each page section with headless Chromium. | Flag | Meaning | | ------------- | ---------------------------------------------------- | | `--only N` | Only these sections. | | `--seconds S` | Override every duration. Useful for smoke tests. | | `--settle S` | Seconds after load before the clock starts. | | `--no-beats` | Use the page's autoplay timing instead of `?beats=`. | ### `decktalk measure` Finds narration t=0 in each recording from the magenta cover and writes it to the sidecar. The table shows the trim point, the recorder's own wall-clock estimate, and the method. A method beginning `NO COVER` means the cover was not found and the trim is a guess. ### `decktalk check` Flags a recording that is black, shorter than requested, or has no cover. | Flag | Meaning | | ---------- | ------------------------------ | | `--only N` | Only these sections. | | `--strict` | Exit 1 on a suspect recording. | ### `decktalk assemble` Cuts, concatenates, mixes, normalizes, and publishes the film. | Flag | Meaning | | --------------- | -------------------------------------------------------------------- | | `--nomix` | Narration only. No beds, no effects. | | `--no-loudnorm` | Skip loudness normalization. | | `--strict` | Fail on a missing clip or recording instead of substituting a slate. | | `--preset P` | The x264 preset for this run. `veryfast` for drafts. | | `--crf N` | The x264 quality for this run. | ### `decktalk verify [SECTION:CUE ...]` Confirms that every section opens on a real frame. With arguments, it also confirms that the picture changed at each named cue, and prints the share of pixels that changed against a quiet control span. It exits 1 when any check fails. ### `decktalk shots` Writes one PNG per step, or frames from a section while it plays with its real cues. | Flag | Meaning | | ------------- | -------------------------------------------------------------------- | | `--page F` | One page file. The default is every page in `decktalk.toml`. | | `--step ID` | Only these step ids. | | `--section N` | Play this section with its resolved cues. | | `--at S` | Seconds after narration t=0 at which to capture. Repeat for several. | ## Everything ### `decktalk build` Runs narrate, beats, record, measure, check, assemble, and verify in order. It exits 1 when a cue phrase is not found, unless `--allow-unresolved` is given, and when assembly or verification fails. | Flag | Meaning | | --------------------------------------------------------------- | ---------------------------------------------- | | `--silent` | Placeholder narration. No key needed. | | `--force` | Re-synthesize every section. | | `--only N` | Re-record only these sections. | | `--allow-unresolved` | Build even if some cue phrases were not found. | | `--nomix`, `--no-loudnorm`, `--strict`, `--preset P`, `--crf N` | As for `assemble`. | ### `decktalk status` Prints the project, the timeline, what is built for each section, and the final path. ## Exit codes | Code | Meaning | | ---- | -------------------------------------------------------------------------------- | | 0 | The command succeeded. | | 1 | An error, printed to stderr as `error: …`, or a failed check as described above. | | 130 | The command was interrupted. | # Configuration Source: https://docs.decktalk.app/reference/configuration Every tunable, its default, and the three places you can set it. Tuning is everything about *how* DeckTalk renders that does not change from one presentation to the next. It comes from three layers, and each layer overrides the one before it. 1. The defaults on this page. 2. A table of the same name in the project's `decktalk.toml`. For example, `[video]` with `preset = "veryfast"` changes the x264 preset for that project. 3. An environment variable named `DECKTALK_
_`. For example, `DECKTALK_VIDEO_PRESET=veryfast` changes it for one shell. A list field such as `probe_delays` takes comma-separated values. A few command-line flags override all three for a single run: `--preset`, `--crf`, `--settle`, and `--model`. Content that changes per presentation is the project document, not tuning. Sections, the voice, mix levels, and soundscape prompts live in the same file under their own tables, and [the project file](/concepts/project-file) describes them. Secrets live only in `.env`. You rarely need this page. The scaffold builds with every default. Reach for `[video]` when drafts feel slow, and for `[verify]` when a deliberately subtle reveal fails the cue check. ## `[video]` Frame size and encoding of every recording and of the final mp4. Environment variables start with `DECKTALK_VIDEO_`. | Field | Type | Default | Notes | | --------------- | ----- | ------------ | ------------------------------------------- | | `width` | `int` | `1920` | | | `height` | `int` | `1080` | | | `fps` | `int` | `30` | | | `preset` | `str` | `'medium'` | x264 preset; veryfast for drafts | | `crf` | `int` | `18` | | | `audio_bitrate` | `str` | `'192k'` | | | `sample_rate` | `int` | `44100` | | | `channels` | `int` | `2` | | | `slate_color` | `str` | `'0x0e1116'` | plain frame when a slate cannot be rendered | ## `[narration]` How the script is turned into audio. Environment variables start with `DECKTALK_NARRATION_`. | Field | Type | Default | Notes | | ------------------------- | ------- | -------------------------- | ------------------------------------------------------- | | `model` | `str` | `'eleven_multilingual_v2'` | | | `output_format` | `str` | `'mp3_44100_128'` | | | `mp3_bitrate` | `str` | `'128k'` | | | `words_per_minute` | `int` | `140` | pacing estimate shown in tables | | `silent_words_per_minute` | `int` | `150` | --silent placeholder pacing | | `lead_break_seconds` | `float` | `0.7` | opens the first spoken section | | `direction_break_seconds` | `float` | `0.7` | pause where a bracketed direction sat | | `tail_break_seconds` | `float` | `0.35` | requested at the end of every section | | `min_tail_seconds` | `float` | `0.35` | guaranteed silence after the last word | | `tail_slack_seconds` | `float` | `0.05` | extra padding added when the tail is short | | `context_chars` | `int` | `1500` | previous\_text / next\_text sent for prosody continuity | | `timeout_seconds` | `int` | `180` | | ## `[record]` Headless Chromium recording. Environment variables start with `DECKTALK_RECORD_`. | Field | Type | Default | Notes | | ------------------ | ------- | --------- | ---------------------------------------------------------- | | `settle_seconds` | `float` | `0.5` | after load, before narration t=0 | | `min_lead_seconds` | `float` | `1.5` | t=0 never comes sooner than this after the recorder starts | | `color_scheme` | `str` | `'light'` | | | `shot_settle_ms` | `int` | `400` | wait before a review screenshot | ## `[align]` Finding narration t=0 in a recording, and the recording sanity check. Environment variables start with `DECKTALK_ALIGN_`. | Field | Type | Default | Notes | | ------------------------------ | ------- | ------- | ------------------------------------------ | | `scan_seconds` | `float` | `4.0` | | | `fallback_first_paint_seconds` | `float` | `1.1` | | | `magenta_luma_min` | `float` | `70` | | | `magenta_luma_max` | `float` | `140` | | | `magenta_chroma_min` | `float` | `165` | | | `painted_ymax` | `float` | `60` | something is drawn | | `painted_yavg_max` | `float` | `120` | and it is not a white flash | | `black_ymax` | `float` | `40` | check: a frame darker than this is "black" | | `truncated_slack_seconds` | `float` | `0.5` | | ## `[audio]` Mix mechanics. Levels are per project (decktalk.toml \[mix]). Environment variables start with `DECKTALK_AUDIO_`. | Field | Type | Default | Notes | | --------------------------- | ------- | ------- | ----- | | `duck_ramp_seconds` | `float` | `0.5` | | | `ambience_ramp_seconds` | `float` | `1.0` | | | `ambience_pad_seconds` | `float` | `0.5` | | | `marker_mute_ramp_seconds` | `float` | `0.04` | | | `marker_boost_ramp_seconds` | `float` | `0.3` | | | `limiter` | `float` | `0.95` | | ## `[verify]` Checks on the assembled mp4. Environment variables start with `DECKTALK_VERIFY_`. | Field | Type | Default | Notes | | --------------------- | ------------------- | ------------ | --------------------------------------------------------- | | `after_dip_seconds` | `float` | `0.2` | | | `lead_seconds` | `float` | `0.1` | the reference frame sits this long before the cue | | `probe_delays` | `tuple[float, ...]` | `(0.7, 1.5)` | seconds after the cue; the later one catches slow reveals | | `diff_level` | `int` | `40` | luma steps a pixel must change to count | | `min_changed_percent` | `float` | `0.1` | share of the frame the best probe must change | | `min_margin_percent` | `float` | `0.1` | and by how much it must beat the control span | | `visible_ymax` | `float` | `60` | | | `probe_width` | `int` | `480` | | | `probe_height` | `int` | `270` | | ## `[elevenlabs]` Environment variables start with `DECKTALK_ELEVENLABS_`. | Field | Type | Default | Notes | | --------------------------- | ------- | -------------------------------- | ----- | | `api_base` | `str` | `'https://api.elevenlabs.io/v1'` | | | `sound_model` | `str` | `'eleven_text_to_sound_v2'` | | | `music_model` | `str` | `'music_v2'` | | | `music_bitrate` | `str` | `'192k'` | | | `max_music_chunk_seconds` | `int` | `300` | | | `music_crossfade_seconds` | `int` | `2` | | | `ambience_seconds` | `float` | `25.0` | | | `ambience_prompt_influence` | `float` | `0.3` | | | `sfx_seconds` | `float` | `0.5` | | | `sfx_prompt_influence` | `float` | `0.5` | | | `timeout_seconds` | `int` | `600` | | # Python API Source: https://docs.decktalk.app/reference/python-api The CLI is a thin layer over these names. ```python theme={null} import decktalk project = decktalk.Project.load("my-lesson") # validates decktalk.toml and loads the settings project.settings.video.preset = "veryfast" result = decktalk.build(project, silent=True) print(result.assembly.final, result.verification.ok) ``` ## Functions and classes | Name | What it does | | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `Project.load(dir)` | Parses and validates `decktalk.toml`. It raises `ConfigError` with the table and the field named. | | `Settings` and `load_settings()` | Hold the tuning tree, which is built from the defaults, then the TOML tables, then the `DECKTALK_*` environment. | | `PageSection` and `ClipSection` | Represent the two kinds of section. `project.sections` holds them in number order. | | `Manifest`, `Timeline`, `Beats`, `Sidecar`, and `Word` | Represent the build artifacts, and each one has `load` and `save`. | | `narrate(project, ...)` | Returns a `NarrateResult` with the manifest, the timeline, and the sections that reached the API. | | `resolve_beats(project)` | Returns a `BeatsResult` with the resolved cues and any unresolved notes. | | `record(project, ...)`, `measure(project)`, and `check(project)` | Return the recordings, the lead measurements, and the sanity checks. | | `assemble(project, ...)` | Returns an `AssembleResult` with the final path, the sections, the warnings, and the loudness before and after. | | `verify(project, checks)` | Returns a `VerifyResult`, whose `ok` is true when every start and cue passed. | | `shoot(project, ...)` | Returns the screenshot paths. | | `soundscape(project, ...)` | Returns the generated or planned items. | | `build(project, ..., report=fn)` | Runs everything in order and calls `report(stage, result)` after each stage. Returns a `BuildResult` whose `ok` mirrors the CLI's exit code. | ## Errors and logging Stage functions never call `sys.exit` and never print. They raise subclasses of `DeckTalkError`. A `ConfigError` means the input is bad or inconsistent. A `MissingInputError` means an earlier stage has not run. A `ProviderError` means ElevenLabs refused or failed a request. A `ToolError` means ffmpeg or Chromium failed. Progress goes to the `decktalk` logger at the INFO level, so attach a handler to see it. ## Stability The file formats are stable already. That covers `decktalk.toml`, `cues.json`, the build artifacts, and the page contract. The Python names may move until version 1.0. Modules under `decktalk.media` and `decktalk.providers`, and any name that starts with an underscore, are internal. ## Adding a speech provider DeckTalk needs one thing from a voice: audio plus a start and end time for every word. The boundary is `decktalk.providers.speech.SpeechProvider`, a protocol with two methods. The `speak(request)` method returns the mp3 bytes and a list of `Word` objects. The `cache_key(request)` method returns everything that changes the audio apart from the text. ElevenLabs is the built-in provider and the only one today. There is no plugin loading yet. A new provider is a pull request that adds a module under `src/decktalk/providers/` and registers it with `register("name", factory)`, after which `[voice]` with `provider = "name"` selects it. A local text-to-speech engine paired with a forced aligner is the obvious next candidate, and an issue that proposes one is welcome.