> ## 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.

# Python API

> Call the DeckTalk pipeline from Python, with each public name, its parameters, its result, and its errors.

The `decktalk` package runs the same pipeline as the command line. This page lists every public name, with its
signature, its result, and the errors it raises.

```python theme={null}
import decktalk

project = decktalk.Project.load("my-lesson")       # reads and validates decktalk.toml
result = decktalk.build(project, silent=True)
print(result.assembly.final, result.verification.ok)
```

Each stage function takes a `Project` and returns a typed result. It logs progress to the `decktalk` logger. It
raises a `DeckTalkError` subclass when it fails, and it never calls `sys.exit`.

## Public names

The package exports these names. Every other name is internal.

| Kind           | Names                                                                                                                                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Project        | `Project`, `Section`, `PageSection`, `ClipSection`, `Settings`, `load_settings`                                                                                                                          |
| Project tables | `Voice`, `Transition`, `Mix`, `Loudness`, `Sfx`, `Soundscape`, `SoundSpec`, `MusicSpec`                                                                                                                  |
| Artifacts      | `Takes`, `CueTimes`, `Word`, `RecordingLog`, `Cuts`                                                                                                                                                      |
| Stages         | `narrate`, `align`, `preflight`, `record`, `assemble`, `verify`, `screenshots`, `soundscape`, `build`, `status`, `clip`, `words`                                                                         |
| Errors         | `DeckTalkError`, `ConfigError`, `MissingInputError`, `ProviderError`, `ToolError`                                                                                                                        |
| Vocabulary     | `Stage`, `Verdict`, `Certainty`, `SkipReason`, `ErrorCode`                                                                                                                                               |
| Results        | `NarrateResult`, `AlignResult`, `RecordResult`, `AssembleResult`, `VerifyResult`, `PreflightResult`, `ScreenshotsResult`, `WordsResult`, `ClipResult`, `StatusResult`, `SoundscapeResult`, `BuildResult` |
| Speech         | `SpeechProvider`, `SpeechRequest`, `register_speech_provider`                                                                                                                                            |
| Version        | `__version__`                                                                                                                                                                                            |

Note: `Section` is the union `ClipSection | PageSection` rather than a class of its own, so
`isinstance(section, decktalk.PageSection)` is how a caller tells the two apart.

Note: `decktalk.__all__` is the whole supported Python API. One word names the command, the Python call, the type
it returns and its `--json` key, so each command's result class sits in `__all__` beside the function that returns
it. Everything under `decktalk.media`, `decktalk.artifacts`, `decktalk.model`, `decktalk.stages` and
`decktalk.toolchain` is internal, so is everything under `decktalk.speech` except the three speech
names, and so is every name whose first character is `_`. The row classes those results hold, such as
`SectionRecording` and `CueCheck`, are read through the result that carries them.

## `Project`

```python theme={null}
decktalk.Project.load(where=None, *, environ=None) -> Project
decktalk.Project.from_toml(root, doc, *, environ=None) -> Project
```

`Project.load` reads and validates `decktalk.toml`. `Project.from_toml` builds a project from a mapping you have
already parsed.

| Parameter | Default                                        | Meaning                                                  |
| --------- | ---------------------------------------------- | -------------------------------------------------------- |
| `where`   | `DECKTALK_PROJECT`, then the current directory | Project directory, or the path of its `decktalk.toml`.   |
| `root`    | required                                       | Project directory, for `from_toml`.                      |
| `doc`     | required                                       | Parsed `decktalk.toml`, for `from_toml`.                 |
| `environ` | `os.environ`                                   | Mapping that supplies the `DECKTALK_*` tuning variables. |

**Raises** `ConfigError` when `decktalk.toml` is missing, cannot be parsed, or fails validation. The message names
the table and the field.

| Member                                                                                                                 | Kind     | Meaning                                                                                                                                                                              |
| ---------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `root`, `document`, `workspace`, `env`, `settings`                                                                     | field    | The project directory and the four parts it composes: the parsed `decktalk.toml`, the paths under `build/`, the secrets, and the tuning.                                             |
| `name`, `script`, `cues`, `build`                                                                                      | property | The project name and the paths from `[project]`, each read from the document.                                                                                                        |
| `sections`                                                                                                             | property | Every `PageSection` and `ClipSection`, in number order.                                                                                                                              |
| `voice`, `transition`, `mix`, `soundscape`                                                                             | property | The project tables, as frozen dataclasses with the keys of [decktalk.toml](/reference/decktalk-toml).                                                                                |
| `build`, `narration_dir`, `takes_dir`, `recordings_dir`, `sections_dir`, `out_dir`, `screenshots_dir`, `preflight_dir` | property | The `build/` directory and its subdirectories. `takes_dir` is `narration_dir` unless `[narration] cache_dir` moves the take files out of `build/`.                                   |
| `takes_path`, `narration_path`, `cue_times_path`, `cuts_path`, `progress_path`, `final`                                | property | Paths of the take index, which is also the narration clock, the joined narration track, the cue times file, the cut list, the progress log, and the final mp4.                       |
| `takes()`, `cue_times()`, `cuts()`                                                                                     | method   | Loads the artifact. `takes()` and `cuts()` return `None` when the file does not exist, and `cue_times()` returns an empty `CueTimes`.                                                |
| `section(number)`                                                                                                      | method   | Returns the section with that number, or `None`.                                                                                                                                     |
| `page_sections`, `clip_sections`, `clip_numbers`, `page_files`                                                         | property | The page sections, the clip sections, the clip numbers, and the page files in first-use order.                                                                                       |
| `recording(section)`, `recording_log(section)`, `section_video(section)`                                               | method   | Returns the path of the section's webm, of its recording log, or of its `sections/NN.mp4`.                                                                                           |
| `recording_log_of(section)`                                                                                            | method   | Loads that section's `RecordingLog`, or `None` when it has none.                                                                                                                     |
| `stray_section_videos(keys)`, `stray_section_warnings(command)`                                                        | method   | The `sections/NN.mp4` files that belong to no section, and one warning sentence per file.                                                                                            |
| `lead_seconds(key)`, `tail_seconds(key)`                                                                               | method   | The silence placed before a section's first word and after its last sound: the section's own key, else `[narration] lead_seconds` or `min_tail_seconds`. A clip section has neither. |
| `path(rel)`                                                                                                            | method   | Returns `rel` as an absolute path under `root`.                                                                                                                                      |
| `chapters()`                                                                                                           | method   | One chapter title per section: its own `chapter`, its script heading, or its number.                                                                                                 |
| `env.get(key)`                                                                                                         | method   | Returns the variable as an opaque value that prints as its own name. Call `reveal()` for the string. It is falsy when the value is unset or starts with `<`.                         |
| `require_env(*keys)`                                                                                                   | method   | Returns those same opaque values in order. Raises `ConfigError` that names every missing key and no value.                                                                           |
| `section_words(key, words_file)`, `narration_words(key, words_file, *, at)`                                            | method   | A take's words in seconds after its section starts, or shifted to `at` on the narration clock.                                                                                       |
| `script_sections()`, `cue_specs()`, `markers()`                                                                        | method   | Parses `script.md`, `cues.json` and the markers file, each checked against `decktalk.toml`.                                                                                          |

`load_settings(root=None, *, toml=None, environ=None, user=None)` builds a `Settings` from the defaults, the
per-machine file, the project's tables, and the environment.

## `build`

```python theme={null}
decktalk.build(project, *, silent=False, force=False, only=None, soundscape=True, loudness=True,
               strict=False, allow_unresolved_cues=False, allow_unknown_cues=False,
               from_stage=None, to_stage=None, progress_path=None, report=None) -> BuildResult
```

`build` runs the five stages in order: `narrate`, `align`, `record`, `assemble`, and `verify`.

| Parameter                | Default | Meaning                                                                                                                                                          |
| ------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `silent`                 | `False` | Uses a click track and estimated word times. Needs no API key. Raises `ConfigError` when `build/narration/takes.json` holds voiced takes, unless `force` is set. |
| `force`                  | `False` | Voices every section again, and ignores the narration cache. With `silent`, replaces voiced takes.                                                               |
| `only`                   | `None`  | Section numbers for `record`. The other stages still use every section.                                                                                          |
| `soundscape`             | `True`  | Mixes the music, the ambience bed, and the sound effects. `False` leaves them out.                                                                               |
| `loudness`               | `True`  | Runs the loudness pass.                                                                                                                                          |
| `strict`                 | `False` | Raises on a missing clip, a missing recording, or a loudness miss. A clip section with `optional = true` still plays its slate.                                  |
| `from_stage`, `to_stage` | `None`  | The first and last `Stage` to run, both ends inclusive, such as `decktalk.Stage.RECORD`.                                                                         |
| `progress_path`          | `None`  | Where to write the progress log. The default is `build/progress.jsonl`.                                                                                          |
| `allow_unresolved_cues`  | `False` | Continues when a cue phrase is not found.                                                                                                                        |
| `allow_unknown_cues`     | `False` | Continues when a cue id appears nowhere in its page.                                                                                                             |
| `report`                 | `None`  | A function that `build` calls as `report(stage, None)` when a stage opens and `report(stage, result)` when it closes. `stage` is a `Stage`.                      |

**Returns** a `BuildResult` with `stages`, the `Stage` members this run executed, one field per stage in
`narration`, `align`, `recordings`, `assembly` and `verification`, and `progress`, the path of the
progress log. A field is `None` for a stage the run did not reach. Its `ran` lists the stages that
produced a result, and its `ok` is true when the video was assembled and `verification.ok` is true.
The verification runs all five checks, cues included.

**Raises**

| Error               | When                                                                                                                                                        |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ConfigError`       | A cue phrase is not found and `allow_unresolved_cues` is false. A page threw an error during its recording. An input file is not valid.                     |
| `MissingInputError` | `strict` is true and a clip or recording is missing, or an earlier output is missing. `build --from` started at a stage past an artifact that is not there. |
| `ToolError`         | ffmpeg or Chromium failed, or `strict` is true and the loudness missed its target.                                                                          |
| `ProviderError`     | The speech provider refused or failed a request.                                                                                                            |

Note: a cue id that appears nowhere in its page is an ordinary finding rather than an exception. The
row carries the `UNKNOWN_CUE` verdict, `align` and `build` exit 1 on it, and `allow_unknown_cues`
lets the run continue.

## `verify`

```python theme={null}
decktalk.verify(project, checks=None, only=None) -> VerifyResult
```

`verify` checks section starts, cuts, and cues on the final mp4. [Verify](/reference/verify) defines every
measurement.

| Parameter | Default | Meaning                                                                                                                                                                             |
| --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `checks`  | `None`  | `None` checks every cue in `cue-times.json` that `cues.json` does not opt out. An empty list checks no cue. A list of `SECTION:CUE` strings checks those cues, even opted-out ones. |
| `only`    | `None`  | Section numbers whose cues to keep. Section starts, cuts, and cut continuity are always checked.                                                                                    |

**Returns** a `VerifyResult`.

| Member                                           | Meaning                                                                                                                                                                                                   |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `final`, `total_seconds`, `silent`               | Path of the final mp4, its length, and whether the build carries placeholder narration.                                                                                                                   |
| `recordings`                                     | One `LoggedRecording` per recorded page section, with `key`, `verdicts`, `page_errors`, `t0_method`, `where`, and `ok`. `verify` repeats these rather than measuring them.                                |
| `starts`                                         | One `StartCheck` per section, with `key`, `start`, `probe_at`, `yavg`, `ymax`, `ok`, and `verdict`.                                                                                                       |
| `cuts`                                           | One `CutCheck` per spoken section, with `key`, `cut_at`, `rms_db`, `ok`, and `verdict`.                                                                                                                   |
| `seams`                                          | One `SeamCheck` per section with `seamless = true`, with `key`, `cut_at`, `last_at`, `first_at`, `changed_percent`, `ok`, and `verdict`.                                                                  |
| `cues`                                           | One `CueCheck` per cue row, with `check`, `section`, `cue`, `cue_seconds`, `final_seconds`, `changed_percent`, `control_percent`, `offset_ms`, `av_ms`, `ok`, `note`, `verdict`, `reason`, and `skipped`. |
| `black_starts`                                   | Count of section starts that read `BLACK`.                                                                                                                                                                |
| `recorded_findings`, `film_findings`, `findings` | What the recording logs judged, what this run measured on the film, and the two added together.                                                                                                           |
| `ok`                                             | True when every recording, start, cut, seam, and measured cue passed. A skipped row never counts against it.                                                                                              |
| `to_dict(root)`                                  | The result as JSON-ready data.                                                                                                                                                                            |

**Raises** `MissingInputError` when the section mp4s or the final mp4 are missing. **Raises** `ConfigError` when a
check does not start with a section number.

## Other stages

These functions run one stage each, or read the project. Every result carries `findings`, the tally of what the
run judged, and `to_dict(root)`, the same thing as JSON-ready data with every path relative to the project root.

| Function                                                                                                         | Returns                                                                                                                                                                                                                                                                                                                                | Raises                                              |
| ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `narrate(project, *, only=None, force=False, allow_placeholders=False, silent=False, model=None, dry_run=False)` | `NarrateResult` with `plans`, each a `TakePlan`, and `voice`, `narration`, `rate`, `rows`, `note`, `takes`, `synthesized`, `cached` and the `segments` property. With `dry_run`, `takes` is `None` and nothing is written.                                                                                                             | `ConfigError`, `MissingInputError`, `ProviderError` |
| `align(project, *, allow_unknown_cues=False)`                                                                    | `AlignResult` with `cue_times`, `sections`, `unresolved`, `estimated`, `unknown`, `uncued`, `cue_times_file`, `allow_unknown_cues`, and the `short`, `problems` and `unknown_problems` properties                                                                                                                                      | `ConfigError`, `MissingInputError`                  |
| `preflight(project, *, only=None, frames=True, model=None, allow_unknown_cues=False)`                            | `PreflightResult` with `voice`, `narration`, `takes`, `note`, `align`, `estimated`, `rate`, `cues`, `seams`, `frames`, `page_warnings`, `root`, `allow_unknown_cues`, and the `placeholders` and `short` properties                                                                                                                    | `ConfigError`, `MissingInputError`, `ToolError`     |
| `record(project, *, only=None, seconds=None, use_cues=True, opening=None, report=None)`                          | `RecordResult` with `sections`, each a `SectionRecording` with `section`, `path`, `log`, `kept`, and the `key`, `verdicts`, `ok` and `label` properties, plus the `kept_sections` and `page_errors` properties                                                                                                                         | `ConfigError`, `MissingInputError`, `ToolError`     |
| `assemble(project, *, soundscape=True, loudness=True, strict=False)`                                             | `AssembleResult` with `final`, `stamped`, which is `None` unless `[output] timestamped_copy` is on, and `duration`, `sections`, `warnings`, `loudness`, `loudness_problems`, `cuts`, `cuts_file`, `captions_srt`, `captions_vtt`, `chapters`, `transcript`, `poster`, plus the `substituted`, `substitutions` and `written` properties | `ConfigError`, `MissingInputError`, `ToolError`     |
| `screenshots(project, *, pages=None, slides=None, section=None, at=None, cues=None)`                             | `ScreenshotsResult` with `files`, each a `Screenshot` with `path`, `page`, `slide`, `cue`, `section`, `at` and `page_errors`, plus the `paths` property for the written files alone                                                                                                                                                    | `ConfigError`                                       |
| `soundscape(project, *, only=None, force=False, dry_run=False)`                                                  | `SoundscapeResult` with `items`, each a `SoundscapeItem` with `name`, `out`, `endpoint`, `requests`, `status`, and `duration_seconds`                                                                                                                                                                                                  | `ConfigError`, `ProviderError`                      |
| `status(project)`                                                                                                | `StatusResult` with `root`, `name`, `script`, `script_exists`, `cues`, `cues_exists`, `sections`, `takes`, `cue_times_exists`, `cue_times_sections`, `final`, `final_exists`, `final_duration`, `outputs`, `problems` and `run`. It reads what exists and runs nothing.                                                                | none                                                |
| `words(project, only=None)`                                                                                      | `WordsResult` with `sections`, each a `SectionWords` with `key`, `title`, `lead_seconds`, `duration`, `estimated`, `words`, `texts` and the `section` property                                                                                                                                                                         | `ConfigError`, `MissingInputError`                  |
| `clip(project, number, *, start, end, out, words_out=None, gain_db=0.0, hold_seconds=0.0)`                       | `ClipResult` with `section`, `video`, `words_file`, `start`, `end`, `first_frame`, `last_frame`, `hold_seconds`, `duration`, `gain_db`, `estimated`, `words`, and `cut_words`                                                                                                                                                          | `ConfigError`, `MissingInputError`, `ToolError`     |

* `record` with `seconds` records every section for that long. With `use_cues=False`, the pages play in preview. One call records, measures and checks each section, and writes its whole log before it opens the next page.
* `screenshots` with `section` takes frames from a playing section at each time in `at`, 0.5 s by default.
* `screenshots` with `cues` needs exactly one slide in `slides`.
* `words` gives each word in seconds after its section starts. `words` keeps the voice's spelling, and `texts`
  gives the same words with the script's punctuation and case.
* `clip` resolves a relative `out` or `words_out` against the project. `words_out` defaults to `out` with
  `.words.json`. [`decktalk clip`](/reference/cli#decktalk-clip) describes the cut.

Every result has `to_dict(root)`. It returns the data
that the command line prints under `--json`. Numbers stay numbers, and paths are relative to `root` with forward
slashes. Pass `project.root`.

```python theme={null}
import json
import decktalk

project = decktalk.Project.load("my-lesson")
result = decktalk.verify(project)
print(json.dumps(result.to_dict(project.root), indent=2))
```

## Vocabulary

A verdict, its certainty, a skip reason, a stage and an error code are members of plain enums, so a
string never compares equal to one. Compare a row with a member, as in
`row.verdict is decktalk.Verdict.OFF_CUE`, and read a word from JSON into a member before comparing it.

| Name         | Members                                                                                                                                                                                                                                                                                                                                                                                 |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Verdict`    | One member per verdict [Verify](/reference/verify) lists. `name` is the code, `label` is what a table prints, `certain` and `passing` say which kind of verdict it is, `to_dict()` gives the `{code, label, certain}` object every payload carries, and `Verdict.from_dict(data)` reads one back and raises `ValueError` on an object that names no verdict or disagrees with its code. |
| `SkipReason` | Why a row measured nothing, such as `SkipReason.OPTED_OUT`. `value` is the code.                                                                                                                                                                                                                                                                                                        |
| `Stage`      | The five stages, in the order a build runs them. `value` is the name that `--from`, `--to`, the progress log and the `build` payload use, and `Stage("record")` reads one.                                                                                                                                                                                                              |
| `Certainty`  | How sure a verdict is, which `Verdict.certainty` holds: `CERTAIN`, `UNCERTAIN` or `PASSING`.                                                                                                                                                                                                                                                                                            |
| `ErrorCode`  | What an error's `code` is, such as `decktalk.ConfigError.code is decktalk.ErrorCode.CONFIG`. `value` is the code the command line prints and the envelope's `error.code`.                                                                                                                                                                                                               |

A take plan's `status`, a soundscape item's `status`, and a `Cut`'s `kind` and `substitute` are
members of plain enums too. Their enums are not exported, so read the word with `.value`, which is
the word the JSON carries: `synthesize`, `cached` or `unknown`, `planned`, `unchanged` or
`generated`, `page` or `clip`, and `slate` or `black`. A `Cut` with nothing standing in for it has
`substitute` set to `None`.

## Artifacts

These classes read and write the files under `build/`. [Build artifacts](/reference/artifacts) gives every field,
and names the files that no public class reads.

| Class          | Members                                                                                                                                                                                                        |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Word`         | `word`, `start`, `end`                                                                                                                                                                                         |
| `Takes`        | The take index and the narration clock. `load(path)` returns `None` when the file does not exist, `save(path)`, `keys`, `voiced_keys`, `span(key)`, `start(key)`, `end(key)`, `starts`, and `speech_end(key)`. |
| `CueTimes`     | The resolved cues per section, as `CueTime` rows. `load(path)`, `save(path)`, `query(key)` for the `?cues=` string, `times(key)`, `get(key, cue)`, and `word_at(key, cue)`.                                    |
| `RecordingLog` | What the recorder did for one section. `load(path)` returns `None` when the file does not exist, `save(path)`, `trim_seconds`, and `worst_stall_ms`.                                                           |
| `Cuts`         | The cut list, as `Cut` rows. `load(path)` returns `None` when the file does not exist, `save(path)`, `to_dict()`, `substituted`, and `at(seconds)` for the section playing at a second of the film.            |

## Errors and logging

Every error that DeckTalk raises on purpose is a `DeckTalkError`.

Each one carries `hint`, the smallest next action, `path`, the project file it is about, and `line`,
the line inside that file, and each of the three is `None` when nothing knows it. The command line
prints an error as `error[CODE]: <message>` on stderr and exits 3, and under `--json` it fills the
envelope's `error` slot. `error.code` is an `ErrorCode`, and the table gives its `value`.

| Error               | `error.code`    | Raised when                                                                                 |
| ------------------- | --------------- | ------------------------------------------------------------------------------------------- |
| `DeckTalkError`     | `INTERNAL`      | Base class of every error below. Raised on its own it is unplanned.                         |
| `ConfigError`       | `CONFIG`        | `decktalk.toml`, `cues.json`, or `.env` is missing, cannot be parsed, or is not consistent. |
| `MissingInputError` | `MISSING_INPUT` | A stage needs an output that an earlier stage has not written yet.                          |
| `ProviderError`     | `PROVIDER`      | An external API, such as ElevenLabs, refused or failed a request.                           |
| `ToolError`         | `TOOL`          | ffmpeg, ffprobe, or Chromium is not available or failed.                                    |

The stages log progress to the `decktalk` logger at the INFO level. Attach a handler to see it. `__version__` is
the installed version string.

## Speech providers

A speech provider returns audio and a start and end time for every word. ElevenLabs is the built-in provider.

| Name                                      | Meaning                                                                                                                                                                                          |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SpeechProvider`                          | A protocol with `name`, `speak(request)`, and `cache_key(request)`. `speak` returns the audio bytes and a list of `Word`. `cache_key` returns everything except the text that changes the audio. |
| `SpeechRequest`                           | A frozen dataclass with `text`, `model`, `voice_settings`, `output_format`, `previous_text`, and `next_text`. `text` holds break tags.                                                           |
| `register_speech_provider(name, factory)` | Makes a provider selectable as `[voice] provider = "name"`. The factory receives a `VoiceContext`, which carries the settings and the project's secrets, and returns a provider.                 |

Registering a name again replaces the earlier factory. DeckTalk loads no plugins. For a provider that the command
line can use, open a pull request that adds a module under `src/decktalk/speech/` and names it in the registry.

## Stability

The file formats and the page contract are the stable surface: `decktalk.toml`, `cues.json`, the build artifacts,
and the page runtime. A change to them raises the minor version. The Python names can still change.

## Related

* **Look up the files that the stages write:** [Build artifacts](/reference/artifacts)
* **Look up a tuning field:** [Configuration](/reference/configuration)
* **Run the same stages from a shell:** [CLI](/reference/cli)
