Skip to main content
decktalk.toml sits in the project root, and every path in it is relative to that directory. It has two halves, and knowing which half a key is in tells you everything else about it. The document is the content this project is: what it is called, which section plays which page or clip, which voice reads it, what the sound is made of and how the cuts behave. Every value in it changes from presentation to presentation, and this page is its reference. Each number in it still has a range the loader holds it to, which the tables below give and which decktalk schema setting publishes under content with its default and the reason for each edge. The settings are what a project changes. Every settings key publishes its default, its safe range, its unit, its scope and what a value at the edge risks, all from one declaration in the code, so decktalk schema setting, the JSON Schema, decktalk config explain and Configuration are four renderings of one row. This page names the tables and sends you there rather than copying the numbers.

The first line

decktalk.toml
That comment binds the file to the published JSON Schema. An editor that understands it completes every table and every key as you type, offers each key’s own sentence, and marks a value outside a key’s safe range before you save. decktalk init writes the line, and it costs nothing to keep.

Which value wins

A project key is set by its default, then by decktalk.toml, then by the environment variable it publishes, such as DECKTALK_VIDEO_PRESET, then by --set table.key=value for one run. A machine key takes the per-machine file in place of decktalk.toml, and a project file that sets one is refused. Which value wins is the rule in full, and Keys only a machine may set lists the machine keys. --set writes nothing. --set overrides a key for exactly the run it is given to, and the file on disk is untouched, which is what makes it the right way to try a value. It takes the same spelling an environment variable takes, so a value cannot get in through one route that the other would refuse. No flag on any command duplicates a settings key, so reduced motion is --set motion.reduce=true and never a flag of its own. decktalk config explain KEY prints the value that wins and the layer that set it, along with what the key changes, what may be written into it and which findings it moves. Its --json lists every layer that sets the key. decktalk config set KEY VALUE is how you write one into a file. It reads the value under the same range the loader enforces, edits the document in place rather than rewriting it, so the comments you wrote around the key survive, and loads the would-be file whole before a byte lands, so a file no run could use never reaches the disk. --scope project writes decktalk.toml and --scope machine writes the per-machine file. --dry-run reports the change and writes nothing. decktalk config unset KEY takes a key out again so the layer below it wins. A value outside a key’s safe range is refused at load, by the dotted key that wrote it, with the range it missed, the layer it came from and the sentence saying what goes wrong at the edge.

Load rules

Every project command loads this file first. The load fails when any of these is true, and the refusal names what it read and would not accept.
  • A top-level table is neither a document table nor a settings table.
  • The file has no [[section]] table.
  • A required key is missing, such as a [[section]] with no number.
  • A value has the wrong type. seconds = 120.5 under [score.music] fails, because that key is an integer and a mapping carries the type its author wrote.
  • A number is nan or inf, which TOML spells and no range can hold. The same refusal meets one in a DECKTALK_ variable or a --set, and it names the file, the variable or the flag that wrote it.
  • Two [[section]] tables share a number.
  • A section names more than one of page, clip and render_command, or none of them.
  • A render_command is not a list of words, or holds an empty word.
  • A number in a document table is outside its range, such as slate_seconds = -3 or [mix] music_db = 40. The refusal names the line that wrote it.
  • seamless is set on the first section, which has no previous section.
  • A [transition] dips pair or a [[mix.effect]] row names a section that does not exist.
  • A settings value is outside its safe range.
  • A page sits at the project root. The origin serves a page’s whole directory, and the root holds the script, the cue file and the build directory.
  • A path the project names resolves outside the project, which includes a symbolic link inside the project that points out of it. The refusal names the path and never the place it resolved to.
  • A settings key only a machine may set is written here, such as record.concurrency or tools.ffmpeg. The refusal names the key and the config set line that puts it in the machine’s file.
An unknown key inside a known table does not fail the load. DeckTalk warns, names the closest key it does read, ignores the key and carries on.
A key spelled under a table it does not live in is warned about the same way, and the closest key is named with its own table, because no key is read under any name but its own.
A page section key written on a clip section, or the reverse, gets a warning of its own that names the kind of section the key belongs to.

The smallest file that loads

decktalk.toml
decktalk init writes a three-section file with comments and every section a page section. decktalk init --example NAME writes a finished project instead.

The document

The tables below are the content this project is. Every one of them is read by Document, none of them has a settings key in it except where this page says a table is shared, and none of them publishes an environment variable or answers to --set.

[project]

[[section]]

Write one [[section]] per section of the film. Sections are sorted by number, so the order in the file does not matter. A section is a page section, which records one scene of an HTML page, a render section, which is narrated like a page section and drawn by a program you name, or a clip section, which plays a video file you already have. A page key makes a page section, a render_command key a render section and a clip key a clip section, and naming more than one of them or none fails the load.

Every section

A page section

lead_seconds and tail_seconds are placed when the takes are joined and are never sent to the voice, so changing either voices nothing. The section starts where its silence starts, so lead_seconds moves every word and every phrase cue of the section later while a $start cue stays at zero. A change reaches the clock when narrate joins the takes again: until then words, cue and record read the lead the take index holds, which is the lead the narration plays. script.md has the whole rule. record sets t0 itself so that the page starts its clock on the recorder’s signal, and it fills cues, words and turns from what cue and narrate resolved unless the section names them. A frozen frame, which storyboard and check draw, sets cues, t0, freeze, after and before itself, so those five are dropped from a section’s params rather than passed through.
decktalk.toml
A page may load only what this file declares. Every page is opened at a local origin that answers for the directory each page sits in and for the files the document names, which are each clip, every [mix] file, every [[mix.effect]] file and every [score] out. Every other file in the project is refused, so the script, the cue file, build/ and .env stay out of a page’s reach. A declared file is reached by the spelling written here and by no other, so media/Theme.mp3 does not answer a request for media/theme.mp3 on any platform. A declared directory is a place, so every name under it is served however the page spells it.

A render section

A render section names the program that draws its picture in place of a page. It is narrated exactly as a page section is: its take sets its length, cue resolves its cues from its words, and the narration runs through it without a pause. It names no page and no scene, and it takes the page section keys hold_seconds, lead_seconds, tail_seconds and with_ambience with the same meaning. A scene or a params table written on it is ignored, and the load warns that the key applies only to a page section.
decktalk.toml
The program runs only on a machine that allows a project’s programs through record.programs, which refuses them by default, and never under record.page_policy = "untrusted". It runs with your user’s rights and can read every file you can, .env included: the scrubbed environment it is given keeps a credential in the shell from it, and nothing keeps a file on disk from it. It runs in an empty scratch folder, and a word that names a project file by a relative path is handed over as that file’s absolute path. check raises PROGRAM_NAMED for every program a project names. Programs a project names says how to allow them. A section whose program writes no landings is drawn and played, and verify measures none of its cues. A render section shows the whole contract.

A clip section

A clip whose file is not there plays a titled slate for slate_seconds, and decktalk status raises FILE_MISSING unless the section sets optional. The speech inside a clip is captioned from the words file beside it, <clip>.words.json. The voice never reads a clip section, so it needs no heading in the script and it is never voiced. A clip between two page sections pauses the narration, which resumes with the next page section. DeckTalk scales and pads the clip to the frame size, matches its sound to the narration’s loudness, and the chapters include it. Play a clip shows the whole task. Every media file a project names, which is a clip, the music, the ambience and each effect, is read as one file by one of a closed set of formats: MP4, MOV and M4A, WebM and MKV, AVI, MP3, WAV, Ogg, FLAC, AAC, PNG, JPEG and WebP. A file in any other format is refused, and so is a playlist, an HLS or DASH manifest, an ffmpeg concat list or an image sequence, because each of those opens files or hosts the project never named.

[voices.NAME] and [narration] voice

Each voice a project reads in is one [voices.NAME] table, and [narration] voice names the default voice, which reads every section no [voice NAME] line in the script names. A voice name is a lower-case letter followed by lower-case letters and digits, such as narrator, guest or host2, so it spells one environment variable. Everything a provider is particular about is in that provider’s own table, [elevenlabs] for ElevenLabs and [dtsp] for the local voice, and a voice may set any of that table’s voice settings for itself alone.
decktalk.toml
Any other key of a voice table is a voice setting of its provider’s table, such as model, stability or output_format, and its value wins over that table’s for this voice, whatever layer set either. A key of another provider’s table is refused when the project loads, and so is the rate a provider charges, which every voice of that provider is billed at, and a key only the machine may set, such as base_url. A project that writes no [voices.NAME] table is read by the default voice at every default. Once one table is written, every voice the project reads in needs its own, the default included, and [narration] voice must name one of them. A variable or a --set for a voice the project does not declare is refused, because a voice renamed in the file would otherwise leave its variable buying in the wrong voice. A voice’s name is never part of a take’s digest, so renaming a voice, with its [voice NAME] lines, voices nothing. Write a dialogue shows a section read by two voices. The ElevenLabs API key is the one secret, so it lives in .env as ELEVENLABS_API_KEY, or in the environment. The local voice needs none. [elevenlabs] holds model, stability, similarity_boost, style, speaker_boost, dollars_per_1000_characters, output_format and base_url. ElevenLabs bills per character, so its rate is stated per thousand characters. model is the model every ElevenLabs voice reads in unless its own table sets one. The score stage buys from the same API with the same key and base_url, and its own request settings and its rates per minute of audio are in [score]. Configuration has each one with its range. The voice id, the model, speed, the four voice fields and output_format are part of a take’s input digest, so changing one makes the next voiced build voice again every turn that voice reads. The price and the base URL are not. base_url is machine-scoped, so it is set in this machine’s file and a decktalk.toml that sets it is refused. [dtsp] holds model, and the machine-scoped base_url and handshake_seconds, for the local voice, which speaks the DeckTalk speech protocol to decktalk-voice, a separate local server that is not part of this repository and is not yet public. base_url is machine-scoped like ElevenLabs’s, so only this machine names where the script is sent, and handshake_seconds is how long the server has to say which aligner it serves. The local voice bills nothing, so buying its takes asks for no --spend, and a build still asks about a score it would buy. Its takes are named by dtsp, the voice id, the model and speed, so they never share a name with an ElevenLabs take of the same words. Configuration has all three keys.

[score]

[score] holds the prompts that decktalk score generates from. It is optional, and no packaged project writes one. Every sound it buys is written to the score directory, [score] dir, which is score/ by default and which you commit, unless the item names another path. A generated file that [mix] then names is what the film actually plays. Each item’s table is shared: its prompt and its file are the document, and beside them sit the settings that size its request, spelled the same way in every table. Configuration has every setting with its range, and [score] timeout_seconds is how long one request may take.
decktalk.toml

[score.ambience]

[score.effects] and [score.effects.<name>]

[score.effects] holds four settings for every effect: duration_seconds, which is 0.5, prompt_influence, which is 0.5, model, and dollars_per_minute, which is what your plan charges for a minute of effect audio. One [score.effects.<name>] table per effect names it, and <name> is the name the file is written under. It takes prompt, which is required, and out, which is score/<name>.mp3 by default, and it may set duration_seconds, prompt_influence or model for itself. A [[mix.effect]] row is what places the finished sound in the film.

[score.music]

A piece longer than chunk_max_seconds is asked for in parts and joined with a crossfade, because the service will not write a long piece in one answer.

[mix]

[mix] is shared too. The keys below are the document, naming the sound files the film plays and the level each one plays at. The settings half is how each layer is ramped, and Configuration has its five keys. The loudness the final mix is mastered to is [audio]. Without [mix] the film carries the narration and the audio of any clip, and nothing else. A missing music or ambience file does not fail the build. assemble warns and plays the film without it.

[[mix.effect]]

One table per sound effect, each of which plays at a cue. A missing file or a cue that never resolved does not fail the build. assemble skips that effect and says so.

markers.json

[mix] music_markers names a JSON file that shapes the music under the film. No packaged project writes one, so the block below is the shape rather than a starter’s values.
media/markers.json
A marker resolves exactly as a cue does, against the words of its own section. A marker whose phrase is not spoken is reported by assemble and skipped.

[transition]

Audio never dips. A dip shows on screen, so leave a pair out of dips for a cut that should not show, which is also what a seamless section needs.

The settings

A settings table may be written here with its project keys. Its machine keys go in the per-machine file instead, and [tools] holds nothing else, so it is never written here. These are the tables, and every key of each one is published with its default, its safe range, its unit, its scope and its hazard by decktalk schema setting and documented in full on Configuration. decktalk config list prints every key with the layer that set it. The captions of a section spoken in more than one voice carry the words alone by default. Turn on caption_voices to name the voice of every one of those captions, as a WebVTT voice span in the .vtt and as the name before the line in the .srt:
The .srt writes the same caption as engineer: Says who? Every demo says that., and those turns are wrapped short enough that the name and the line still fit on one caption line. captioned = true also writes <name>-captioned.mp4, the film with its captions drawn into the picture by your page, for a place that cannot load a caption file. The film itself is the same frames either way. Accessible video says how a page styles the captions and places them by voice.