Skip to main content
On macOS and Windows, three lines take a machine with nothing on it to a finished film. On Linux, run decktalk install after the first line, for Chromium’s system libraries.
The third line spends nothing and needs no account, because --no-spend buys no take and plays a placeholder wherever a take is missing. Part 3 of this page adds your own voice, which is the one thing in DeckTalk that costs money.

Part 1: install

1

Install the decktalk command

The script installs uv if the machine has none, then runs uv tool install decktalk, and uv brings its own Python. That is all it does. It never runs sudo, and it fetches neither Chromium nor ffmpeg, because on Linux that download reaches apt-get through sudo and an installer you piped into a shell has no business asking for a root password. Read the script before you run it. That is why it is served from a URL rather than hidden in a package.It finishes by naming the version it installed and the three commands to run next. Pipe it into sh -s -- --dry-run to print what it would do and change nothing, and set DECKTALK_VERSION to install one version rather than the latest.It runs on Linux and on macOS. On a musl system such as Alpine it stops and says why, because Playwright, which records the slides, publishes no musl wheels. On Windows, use the uv tab.
Check the install by asking for the version.
If the shell cannot find decktalk, add ~/.local/bin to your PATH and open a new shell. That is where uv puts the command.
2

Read the machine

decktalk doctor reports what is installed and what a run would use. This is what it prints on a machine that has fetched its tools.
The paths, the Python and the platform are this machine’s, so they are written as ... here. API key elevenlabs ELEVENLABS_API_KEY missing means the key the narrator’s provider reads is unset, which is the right state for everything up to part 3. KaTeX is not a download: the pinned release ships inside the package, and DeckTalk serves it to the page.On a machine that has never recorded, the chromium, ffmpeg and ffprobe rows read missing, and each one comes with a finding. doctor also prints Chromium is not installed. Run decktalk install to fetch it. above the table.
The fix is the command decktalk install, so decktalk doctor --fix fetches what is absent. On a terminal, doctor offers once to fetch it too. On macOS and Windows you never have to, because the first command that needs a tool fetches it. On Linux, run decktalk install once first. It is the one command that installs Chromium’s system libraries.

Chromium and ffmpeg

DeckTalk drives headless Chromium to record the slides and ffmpeg to cut and encode them, and fetches both itself, one time per machine, the first time a command needs one. decktalk install makes both downloads up front instead, and does nothing else.
Nothing has to run it on macOS or Windows, and a build works on a machine where it never has. On a fresh Linux machine, run it once before the first build. What decktalk install is for says when it is worth running, and what DeckTalk downloads gives the sizes and the cache paths.

Part 2: build the starter film

1

Write the project

On a terminal init asks for the name, the example and whether to write the skills. Pass --no-input to take every default and ask nothing, which is what it does with no terminal anyway. --example NAME writes a finished project to read rather than the starter to build on, and --no-skills leaves the packaged skills out.The starter is three sections on one page, and none of it is a demo to delete first.
2

Build it without buying

One line per stage, in run order, with the seconds it took. Built is the line to read: the film, what the run spent, and what the run found. Here that is one TAKE_MISSING per section, because no section has a voiced take yet and each plays a placeholder. Each is a warning, so the build still exits 0, and each names the command that buys its take. Build times says how long this build takes on a named machine that already has Chromium and ffmpeg. Recording is the long part. It steps each section one frame at a time, with several recorders sharing the frames as the machine’s CPU allows, so a longer film takes longer.The two middle lines are the run explaining itself. The starter declares no score, so that stage has nothing to do. The loudness pass is skipped because normalising a placeholder to a broadcast target would teach you nothing about the mix your voice will make.Run the same command again and it takes under a second. Nothing changed, so every stage keeps what it made, and assemble and verify say so.If this machine has never run DeckTalk before, the download comes first and the run names it.
3

Find the film

The film and everything that goes with it land in build/final/.
my-lesson.mp4 is the film. The name comes from [project] name in decktalk.toml. The captions, the chapter marks, the transcript page and the poster frame are written from the same words the film was cut against, so they cannot drift from it.
4

Ask what the project is holding

One row per section, with the scene of the page it plays. Take is the section’s take state: voiced once a voice has spoken a take of its current text, voice and settings. A build that does not spend writes a placeholder take instead, so it reads placeholder here while Recorded and Assembled read yes. Stale is the column that matters after an edit: it marks a section whose inputs have moved since the film was cut. status is a reading of the project rather than a third opinion beside check and verify, and the one thing it judges is a file the project names and has not got, which is a FILE_MISSING error.Before the first build the same table reads missing under Take and no in every other column, and a Next line under it reads decktalk narrate --no-spend. Once the film on disk is the one the last build measured, there is nothing next, so the line is gone.

What the starter shows

Three sections on one page, one scene each, every slide written as markup inside a <template data-slide> element. There is no JavaScript in it to read. Open deck/index.html under the origin decktalk serve prints, such as http://127.0.0.1:65298/deck/index.html, for the scene index, which lists every scene and every slide with the cues it declares.
The starter page in index mode, headed My Lesson. Under it are Scene 1 Open, Scene 2 How it works and Scene 3 Close, each with a play link, and under each one its slides with the seconds each slide holds for and the cue ids of its cues, such as 1.1:title and 1.1:files.

The starter's scene index, with every slide and the cue ids it declares.

serve answers from the project directory on 127.0.0.1, which is the origin the recorder uses, so what the browser shows is what the recorder sees. The port is any free one unless --port names one. Press Ctrl-C to stop it. The equation in scene 2 is where the starter shows its one trap. data-tex holds the TeX and the element’s own text is the fallback that shows if KaTeX ever fails to load, so the fallback is written to read correctly out loud rather than left as raw TeX.

Part 3: add your own voice

A voiced build sends each section’s text to ElevenLabs and gets speech back, and that is the only part of DeckTalk that costs money. Stop here if you have no account: the film you built is a complete film.
1

Put the key in .env and the voice in decktalk.toml

Write your key after the = in place of <your-elevenlabs-key>, and write the id of the voice that reads the script as [voices.narrator] id in decktalk.toml.
.env
decktalk.toml
Both values come from your ElevenLabs account. The key is the one credential, and DeckTalk never prints it: no value read from .env or from the environment reaches a --json payload, a log line or an error message. The voice id is a published name rather than a secret, so it is committed with the project. It says which voice to speak in and grants nothing, since a cloned voice is usable only with its own account’s key, and it is part of what names a take, so changing it voices every section again. DECKTALK_VOICES_NARRATOR_ID in the environment overrides it for anyone who keeps it out of the file.A value that still starts with < counts as unset, so the example file as it ships approves nothing. A variable already in the environment wins over the file. The starter’s .gitignore already covers .env, and keeps .env.example.
2

Price the run before you make it

decktalk check judges the written files and the pages and prices the narration a voiced build would buy. It calls no API and writes nothing a stage reads, so it is the one call to make when you want the price of the voice. A project with a score prices its sound with decktalk score --no-spend.
The result is one flat object carrying run, written, judged, pages_opened, frames_compared, cost and storyboard beside the four keys every result reserves. Its cost is the price:
dollars is the price at your rate, and ceiling_dollars is the most the run can reach. ElevenLabs declares that it bills per character, so the rate is the key price_key names, [elevenlabs] dollars_per_1000_characters in your own decktalk.toml, and the figure is your plan’s and not an invention. How the price is worked out gives every field and says why the two dollar figures can differ.
3

Look at what you are about to buy

A storyboard freezes every slide at every cue onto one page. It is the checkpoint before anything is bought: open build/storyboard.html and read the film as stills, because a slide you fix here costs nothing and a slide you fix after a voiced build costs that section again. --section, --slide, --after, --before and --at narrow the sheet.
4

Build with your voice

This command buys from ElevenLabs, at your plan’s rate.
--spend approves the spend. --max-cost N refuses the whole run before the first paid call when the most it can cost is over N US dollars, and the refusal names the figure that would let it through. It approves nothing on its own: it is a ceiling on the spend --spend approves. A ceiling of five cents on this project reads:
The price is held to the ceiling before anything else, so a run the ceiling refuses opens no run and draws nothing. A run within it draws the storyboard next, so it is there to read before you approve.On a terminal, decktalk build with neither flag prints the price and asks. Answer no and the run goes on as --no-spend would, with a placeholder wherever a take is missing and silence where a sound is unbought. With no terminal there is nobody to ask, so it refuses rather than guessing, and the refusal carries the price, so one call prices the run.
A take is named by the digest of its text and its voice settings, so the next build voices only the sections whose words or voice changed and reads the rest from the cache.

Next