> ## Documentation Index
> Fetch the complete documentation index at: https://docs.decktalk.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# decktalk.toml

> Look up every table and key in the DeckTalk project file, with its type and default.

`decktalk.toml` is the project file. It maps each section to a page or a clip, and it sets the voice,
the transitions, and the sound. Use this page to look up a table or a key and its default.

The file sits in the project root. Every path in it is relative to that directory.

## Minimal file

The smallest file that loads has one page section.

```toml decktalk.toml theme={null}
[[section]]
number = 1
page = "deck/index.html"
```

`decktalk init` writes a three-section file with comments, and every section of it is a page section.
`decktalk init --example lesson` writes a one-section file instead. This fuller example adds a clip
section, a dip to black, and music.

```toml decktalk.toml theme={null}
[project]
name = "my-lesson"

[voice]
stability = 0.55

[[section]]
number = 0
chapter = "On camera"
clip = "media/open.mp4"

[[section]]
number = 1
chapter = "Open"
page = "deck/index.html"
scene = 1

[transition]
dips = [[0, 1]]

[mix]
music = "build/music/music.mp3"
```

## Load rules

Every project command loads `decktalk.toml` first. The load fails in each of these cases.

* A top-level table is unknown.
* The file has no `[[section]]` table.
* A required key is missing.
* A value has the wrong type. For example, `seconds = 120.5` under `[soundscape.music]` fails.
* Two sections share a `number`.
* A section has both `page` and `clip`, or neither.
* `record_margin_seconds`, `hold_seconds`, `lead_seconds`, or `tail_seconds` is below 0.
* `seamless` is set on the first section.
* A `dips` pair or a `[[mix.sfx]]` table names a section that does not exist.

The error names the table and the key.

```text theme={null}
decktalk.toml: [soundscape.music]: 'seconds' must be int, got float
```

An unknown key inside a table does not fail the load. DeckTalk prints a warning, ignores the key, and
continues. The same rule covers the tuning tables and the per-machine file.

```text theme={null}
decktalk.toml: [voice]: ignoring unknown key 'stabilty' (did you mean 'stability'?).
```

A page section key on a clip section, or a clip section key on a page section, gets its own warning.
The warning names the section kind that the key belongs to.

## Which value wins

* `--model` wins over `[voice] model`, which wins over `[narration] model`.
* A tuning table in `decktalk.toml` wins over the per-machine file. A `DECKTALK_<TABLE>_<FIELD>` variable and a command option win over both.
* The project tables on this page have no environment variables.

[Configuration](/reference/configuration) lists the five layers for tuning tables.

## `[project]`

| Key        | Type   | Default            | Meaning                                                                                                                                                       |
| ---------- | ------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`     | string | the directory name | Output name. The video is `build/out/<name>.mp4`.                                                                                                             |
| `script`   | path   | `script.md`        | Script file.                                                                                                                                                  |
| `cues`     | path   | `cues.json`        | Cue file. Optional. Without it, pages use their own timing.                                                                                                   |
| `build`    | path   | `build`            | Directory for generated files.                                                                                                                                |
| `language` | string | `en`               | The BCP 47 tag of the language the film is narrated in. It labels the mp4's two streams and the transcript page, which is what a player names the audio from. |

## `[voice]`

`[voice]` sets how the voice sounds. The API key and the voice id are secrets, so they live in `.env`
as `ELEVENLABS_API_KEY` and `ELEVENLABS_VOICE_ID`, or in the environment.

| Key                         | Type    | Default               | Meaning                                                                                                                                                           |
| --------------------------- | ------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`                  | string  | `elevenlabs`          | Registered speech provider name.                                                                                                                                  |
| `model`                     | string  | `= [narration] model` | Speech model id, such as `eleven_multilingual_v2`.                                                                                                                |
| `stability`                 | number  | 0.55                  | Lower values vary more between takes.                                                                                                                             |
| `similarity_boost`          | number  | 0.75                  | How closely the voice follows its source voice.                                                                                                                   |
| `style`                     | number  | 0.0                   | Style exaggeration. 0 is the most stable.                                                                                                                         |
| `speaker_boost`             | boolean | true                  | ElevenLabs speaker boost.                                                                                                                                         |
| `speed`                     | number  | 1.0                   | Speaking rate.                                                                                                                                                    |
| `price_per_1000_characters` | number  | 0.0                   | What your plan charges per 1,000 characters. `narrate --dry-run` prints dollars from it, and leaves the figure out when it is 0. It is not part of any cache key. |

Note: every key here but `price_per_1000_characters` is part of every take's content hash. After a change,
the next voiced build voices every section again. [What spends credits](/requirements#what-spends-credits) lists every change
that costs credits. An unregistered `provider` fails when `narrate` runs, not at load.

## `[[section]]`

Write one `[[section]]` table for each `## N.` section of the script. DeckTalk sorts sections by
`number`, so the order in the file does not matter. Each section is a page section or a clip section.

### Keys for every section

| Key        | Type    | Default                      | Meaning                                                                                                                                               |
| ---------- | ------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `number`   | integer | required                     | Matches `## N.` in the script.                                                                                                                        |
| `chapter`  | string  | the script's `## N.` heading | Chapter title and slate title. A section with neither becomes `Section N`. Consecutive sections with the same chapter share one chapter marker.       |
| `seamless` | boolean | false                        | The section opens on the previous section's last picture. `verify` flags a visible jump at the cut as `POP AT CUT`. Not allowed on the first section. |

### Page section keys

| Key                     | Type              | Default                        | Meaning                                                                                        |
| ----------------------- | ----------------- | ------------------------------ | ---------------------------------------------------------------------------------------------- |
| `page`                  | path              | required                       | HTML file, such as `deck/index.html`.                                                          |
| `scene`                 | integer or string | `= number`                     | Value the page receives as `?scene=`.                                                          |
| `record_margin_seconds` | number            | 0.3                            | Seconds the recording runs past the section span.                                              |
| `hold_seconds`          | number            | 0                              | Seconds the section's last frame holds after its narration. The narration pauses for the hold. |
| `lead_seconds`          | number            | `[narration] lead_seconds`     | Seconds of silence before the section's first word.                                            |
| `tail_seconds`          | number            | `[narration] min_tail_seconds` | Seconds of silence after the section's last word.                                              |
| `ambience`              | boolean           | false                          | Plays the ambience bed under this section.                                                     |
| `params`                | table             | none                           | Extra query parameters for the page, written as `[section.params]`.                            |

Note: `params` values become strings. A `cues` key replaces the cue times, a `scene` key replaces
`scene`, a `words` key replaces the spoken words, and a `prevwords` key replaces the previous section's words. `t0` is always `signal`, whatever `params` says.

### Silence in a section

Three keys set the silence of a page section. None of them is sent to the voice or changes the narration
cache, so a cached take stays cached. Every spoken section has a lead and a tail even when it sets neither,
from `[narration] lead_seconds` and `min_tail_seconds`, so the silence across every cut is one tail and then
one lead.

| Key            | Where the silence goes        | How DeckTalk adds it                                                                                                                                               |
| -------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `lead_seconds` | Before the first word         | `narrate` places it in `narration.mp3` before the take. The take is not changed.                                                                                   |
| `tail_seconds` | After the last word           | `narrate` measures where the take's sound ends and cuts or pads the take in `narration.mp3` so that exactly this much silence follows it. The take is not changed. |
| `hold_seconds` | After the section's narration | `assemble` holds the last frame, and the narration waits.                                                                                                          |

* The section starts when its silence starts. So `lead_seconds` moves every word and every word cue of the
  section later, and `$start` stays at 0.
* The recording covers the lead, because the section's span on the narration clock includes it.
* `tail_seconds` is exact, like `min_tail_seconds`. A pause the voice left after its last word is cut to it or
  padded to it, so every take of the section ends the same way, voiced in this run or reused.
* A hold on any page section pauses the narration, as a clip between page sections does. The next section
  starts after the hold, on its own words.
* A `[pause N]` before a section's first words is removed. Use `lead_seconds` to open a section on silence.

```toml decktalk.toml theme={null}
[[section]]
number = 4
chapter = "The edit"
page = "deck/index.html"
lead_seconds = 1.5    # the page shows 1.5 s before the first word
hold_seconds = 2      # the last frame stays 2 s before section 5
```

Set `seamless = true` when a section continues the previous section's picture, such as a page that
opens where the last page ended. `verify` compares the two frames at the cut, outside any dip. A dip still
fades through black, so leave the pair out of `[transition] dips` for a cut that should not show.
[Cut continuity](/reference/verify#cut-continuity) defines the check.

### Clip section keys

| Key             | Type    | Default  | Meaning                                                                                       |
| --------------- | ------- | -------- | --------------------------------------------------------------------------------------------- |
| `clip`          | path    | required | Your video file. It keeps its own audio.                                                      |
| `slate_seconds` | number  | 5        | Seconds the titled slate plays when the file is missing.                                      |
| `optional`      | boolean | false    | Plays the slate for a missing clip, even under `--strict`.                                    |
| `words`         | path    | none     | A words file of the speech inside the clip. The captions add these words at the clip's start. |

A clip section follows three rules.

1. A clip section can sit before, between, or after page sections.
2. The narration pauses for a clip between page sections, and resumes with the next page section.
3. If the file is missing, a titled slate plays for `slate_seconds`. With `--strict`, `assemble` and `build` fail instead, unless the section sets `optional = true`.

Set `optional = true` while the clip is not ready. Delete that line after you add the clip.

DeckTalk scales and pads the clip to the frame size. A clip section has captions only when it sets `words`, and
chapters include the clip. [Add a clip section](/guides/clip-section) shows the whole task.

## `[transition]`

| Key             | Type                         | Default   | Meaning                                                                 |
| --------------- | ---------------------------- | --------- | ----------------------------------------------------------------------- |
| `dips`          | array of \[integer, integer] | every cut | Boundaries that dip to black, as `[[from, to], …]`.                     |
| `dip_seconds`   | number                       | 0.15      | Fade length, rounded to whole frames. Video only.                       |
| `page_fades_in` | boolean                      | true      | Pages fade themselves in, so the cut adds no fade-in to a page section. |

Note: leave out `dips` to dip at every cut. Set `dips = []` for no dips. Audio never dips.

## `[mix]`

`[mix]` is optional. Without it, the video carries narration and clip audio only.
[Sound](/concepts/sound) explains how the parts fit together.

| Key                      | Type   | Default          | Meaning                                                                                                             |
| ------------------------ | ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------- |
| `music`                  | path   | none             | Music under the whole video. Loops, and ducks under speech and clips.                                               |
| `music_db`               | number | -24              | Music level in dB.                                                                                                  |
| `music_duck_db`          | number | -6               | Extra level change in dB under each spoken span and clip.                                                           |
| `music_fade_in_seconds`  | number | 2                | Fade-in seconds.                                                                                                    |
| `music_fade_out_seconds` | number | 3                | Fade-out seconds.                                                                                                   |
| `music_markers`          | path   | none             | This names a markers file, which swells and mutes the music at spoken phrases. See [`markers.json`](#markers-json). |
| `ambience`               | path   | none             | Ambience bed for sections that set `ambience = true`.                                                               |
| `ambience_db`            | number | -20              | Ambience level in dB.                                                                                               |
| `slate`                  | path   | a rendered slate | PNG for every missing clip, instead of the rendered titled slate.                                                   |

Note: a missing music or ambience file does not fail the build. `assemble` logs a warning, such
as `music missing (<path>); no music`, and plays the video without it.

### `[[mix.sfx]]`

Write one `[[mix.sfx]]` table for each sound effect.
[Put a sound effect on a cue](/guides/sfx-on-a-cue) shows the task.

| Key       | Type    | Default  | Meaning                                                                                                                                      |
| --------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `file`    | path    | required | Audio file.                                                                                                                                  |
| `section` | integer | required | Section whose cue places the sound effect. Must exist.                                                                                       |
| `cue`     | string  | required | Cue id that `align` resolved in that section.                                                                                                |
| `db`      | number  | -16      | Level in dB.                                                                                                                                 |
| `offset`  | number  | 0        | This adds seconds to the cue time.                                                                                                           |
| `caption` | string  | none     | This is the caption line a viewer reads when the sound plays, such as `ball bounces`. `assemble` gives it a line of its own in the captions. |

Note: this `offset` belongs to the sound effect, not to the cue. A missing file or an unresolved cue
does not fail the build. `assemble` logs `skipped` for that sound effect.

### `[mix.loudness]`

`[mix.loudness]` sets the targets of the loudness pass. [Loudness](/concepts/sound#loudness) explains
the pass.

| Key            | Type   | Default | Meaning                                                                 |
| -------------- | ------ | ------- | ----------------------------------------------------------------------- |
| `target_lufs`  | number | -16     | This is the integrated loudness target, in LUFS.                        |
| `true_peak_db` | number | -1.5    | This is the true-peak ceiling, in dBTP.                                 |
| `range_lu`     | number | 11      | This is the loudness range, in LU, and it is passed to the measurement. |

Note: the unit is part of each key name, so no abbreviation has to be looked up. A build without
voice skips the loudness pass, because a click track has no loudness worth matching.

### `markers.json`

A markers file swells or mutes the music at spoken phrases. `[mix] music_markers` names it. No
packaged project writes one, so the block below is a shape that a project adds by hand, and
`media/markers.json` is the usual place for it.

```json media/markers.json theme={null}
{
  "_comment": "Music structure. Each marker swells the bed by boost_db for boost_seconds at a spoken phrase (or $start/$end of a section); mute_seconds silences it for one beat first.",
  "boost_db": 3,
  "boost_seconds": 2,
  "markers": [
    { "name": "open", "section": 1, "on": "$start" }
  ]
}
```

| Key             | Type             | Default | Meaning                            |
| --------------- | ---------------- | ------- | ---------------------------------- |
| `boost_db`      | number           | 3       | dB the music rises at each marker. |
| `boost_seconds` | number           | 2       | Seconds each swell lasts.          |
| `markers`       | array of objects | none    | The markers.                       |

| Marker key       | Type    | Default  | Meaning                                       |
| ---------------- | ------- | -------- | --------------------------------------------- |
| `name`           | string  | none     | Name in warnings.                             |
| `section`        | integer | required | Section number.                               |
| `on`             | string  | `$start` | Spoken phrase, `$start`, or `$end`.           |
| `offset`         | number  | 0        | Seconds added to the marker time.             |
| `occurrence`     | integer | 1        | Which match of the phrase.                    |
| `case_sensitive` | boolean | false    | Matches case when true.                       |
| `mute_seconds`   | number  | 0        | Seconds the music is silent before the swell. |

* Markers apply only when the music file exists. `--no-soundscape` turns them off.
* `$start` works on any section. A phrase or `$end` needs a spoken section.
* A phrase matches by the same rules as a cue phrase.
* If the file is missing, `assemble` logs `markers file missing (<path>); music without structure`.
* If a marker does not resolve, `assemble` logs `marker '<name>' unresolved; skipped`.
* A file that is not valid JSON fails `assemble` with a `ConfigError`.

## `[soundscape]`

`[soundscape]` holds the prompts for `decktalk soundscape`, which generates sound with ElevenLabs.
Each item writes to a default path unless its `out` key names another.

* `[soundscape.ambience]` writes to `[mix] ambience`, else to `build/sfx/ambience.mp3`.
* `[soundscape.music]` writes to `[mix] music`, else to `build/music/music.mp3`.
* `[soundscape.sfx.<name>]` writes to `build/sfx/<name>.mp3`.

### `[soundscape.ambience]`

| Key                | Type   | Default                                          | Meaning                                   |
| ------------------ | ------ | ------------------------------------------------ | ----------------------------------------- |
| `text`             | string | required                                         | Prompt.                                   |
| `duration_seconds` | number | `= [elevenlabs] ambience_seconds` (25)           | Length to request. The bed loops.         |
| `prompt_influence` | number | `= [elevenlabs] ambience_prompt_influence` (0.3) | How closely the sound follows the prompt. |
| `model_id`         | string | `= [elevenlabs] sound_model`                     | Sound model id.                           |
| `out`              | path   | `= [mix] ambience`                               | Output file.                              |

### `[soundscape.sfx.<name>]`

| Key                | Type   | Default                                     | Meaning                                   |
| ------------------ | ------ | ------------------------------------------- | ----------------------------------------- |
| `text`             | string | required                                    | Prompt.                                   |
| `duration_seconds` | number | `= [elevenlabs] sfx_seconds` (0.5)          | Length to request.                        |
| `prompt_influence` | number | `= [elevenlabs] sfx_prompt_influence` (0.5) | How closely the sound follows the prompt. |
| `model_id`         | string | `= [elevenlabs] sound_model`                | Sound model id.                           |
| `out`              | path   | `build/sfx/<name>.mp3`                      | Output file.                              |

### `[soundscape.music]`

| Key                  | Type    | Default                      | Meaning                                                      |
| -------------------- | ------- | ---------------------------- | ------------------------------------------------------------ |
| `prompt`             | string  | required                     | Prompt.                                                      |
| `seconds`            | integer | 360                          | Length to request. A decimal such as `120.5` fails the load. |
| `force_instrumental` | boolean | true                         | No vocals when true.                                         |
| `model_id`           | string  | `= [elevenlabs] music_model` | Music model id.                                              |
| `out`                | path    | `= [mix] music`              | Output file.                                                 |

Note: a length above `[elevenlabs] max_music_chunk_seconds` (300) is requested in parts, and DeckTalk
crossfades the parts. No packaged project sets a `[soundscape]` table, so a project that wants
generated sound writes one itself.

## Tuning tables

`decktalk.toml` can also hold the tuning tables, such as `[video]` or `[record]`. A tuning table in
the project overrides the per-machine file. [Configuration](/reference/configuration) lists every
field with its default.

## Related

* **Write the script:** [script.md](/reference/script-md)
* **Tie a reveal to a phrase:** [cues.json](/reference/cues-json)
* **Tune a build:** [Configuration](/reference/configuration)
