An edit to one sentence of section 2: a plain build voices and records section 2, keeps the take and the recording of sections 1 and 3, and joins every section into one mp4.
Name the sections with --section
--section is the one selector, and it means the same thing on build, narrate, cue, record,
score, assemble, verify, words, storyboard and check. It takes one number, a comma list
or a range, and it repeats, so these four lines name the same kind of thing. clip takes
--section N too, and there it names exactly one section.
INPUT error, which exits 3, before the
command does any work or prices anything. The hint names the sections there are. A selection is
never widened to the whole project: naming a section that is not there never means every section.
A section that is there but is not the kind a command acts on is refused by that command: narrate
and words name the spoken sections, and record names the page sections. build hands each stage
the selected sections it can act on and skips a stage that has none, so build --section N on a clip
section skips narrate and record, and assemble cuts that clip again when it changed.
What the flag does depends on the stage, and the difference is the point of the flag.
- On
recordit is an order. A named section is recorded whether or not its inputs moved, which is how you take a bad recording again. - On every other stage it is a narrowing. A named section still keeps its take, its generated sound or its section video when nothing about it changed, because naming a section says which sections a run is about and never that its work has to be redone.
- On
assembleit never shortens the film. A film is always whole, so every section is in the answer, and every section video is kept only while its key matches, named or not.--sectiondecides which sections the run judges, so a missing file in a section you left out is not reported again.
record whose recording no longer matches the project is reported by
name, with the reason, so a run never leaves an out-of-date picture in the film silently. A section
you leave out that has no recording at all is not this run’s finding, because the run did not touch
it, and decktalk status shows it in its Recorded column.
What makes a take stale
A take is one turn’s speech, the stretch of a section one voice reads, and it is named by the digest of exactly six things:- the speech provider’s name
- the voice id
- the model
- the output format the provider declares, which for ElevenLabs is
[elevenlabs] output_format - the take identity the provider declares, which is
[voices.NAME] speedand the provider’s own voice fields under the names its service reads, such as[elevenlabs] stability - the turn’s text and pauses, written as one canonical text: each paragraph, a
[pause N]as a<break time="Ns" />tag after it and a beat as a dash, which is exactly the text ElevenLabs is sent
- The section number is not in it, so inserting a section or renumbering one voices nothing.
- The heading is not in it, so retitling a section voices nothing. The title is the chapter marker,
which
assemblewrites fresh on every run. - The neighbours’ text is not in it, so an edit to section 2 never voices section 3.
- The lead and the tail are not in it.
[narration] lead_seconds,[narration] tail_secondsand a section’s ownlead_secondsandtail_secondsare placed when the takes are joined, so changing one moves the section on the clock and buys nothing. - Two sections with the same words come to one digest, so the second is kept rather than bought twice.
.mp3 for
every ElevenLabs format. narrate sends nothing for a section whose take and words file are on disk
and agree. It looks in two places, in this order:
- The takes directory,
takes/inside the project unlessnarration.takes_dirnames another. It holds every take the film plays and the words its voice sent with each. Commit it, and a fresh clone builds the real film with no key. Every take this project buys is written there, and a take found in the take store is copied there, so the directory always holds the whole film. - The take store,
narration.store_dir, a folder outside every project where this machine keeps a second copy of every take it buys. It is on by default, in the per-user data folder, anddecktalk config set narration.store_dir DIR --scope machinemoves it. It is written once per take, so a re-buy with--replace-voicedchanges this project’s takes directory and never the take another project plays. It holds the spoken words of every script voiced on this machine, so keep it out of a synced or shared folder. Two projects that need the same take buy it once: a run that finds a good copy in the store once its turn comes copies it in rather than buying it again, and one that finds another run still buying waits as long as one whole take request can run, every attemptnarration.timeout_secondslong with the waits betweennarration.retries. A run told to replace that take with--replace-voicedbuys it anyway. A take bought while the takes directory cannot be written is kept in the take store first, and the run stops and says so, so the next run copies it in for nothing.
<name>.unreadable, or as <name>.<n>.unreadable with the first number free for both
files when that name is taken, when a good copy replaces it. DeckTalk never deletes a take.
A takes_dir that is empty, outside the project, an absolute path, the project directory itself,
inside the build directory or a directory that holds it is refused when the project loads, and so is a store_dir that is relative, inside the project or inside
the tool cache. A placeholder take, the take index and the joined narration are always under
build/narrate/, because a build makes them again for nothing.
A turn that is voiced again costs the characters of its whole text and not only the changed
sentence, and a section one voice reads is one turn, so decktalk check is the call that prices an
edit before you run it.
What makes a recording stale
A recording is one page section stepped through Chromium frame by frame, and its log beside the mp4 keeps what it was made from asmade_from: a list of named parts, each with the digest of what it
held. Together they are everything that decides what those pixels look like:
- each turn’s words and the name of the voice that reads it, as
text:N.Tandvoice:N.T - each turn’s words with their start times, which the page is handed, as
words:N.T - the markup of the one scene the section plays, as
scene - the rest of the page file, which every scene of that page shares, as
page:and the page - the content of every project file the page loaded when it was last recorded, which its log lists
under
assets, each asfile:and its path - the scene and the section’s own
params, every cue resolved to its second, and how long the section runs, which is its frames in the narration plus itshold_seconds - the frame width, the frame height and the output frame rate,
[record] color_scheme, the page policy, andmotion.reduceandmotion.scale, so a reduced render never reuses a full-motion recording - the DeckTalk version, so an upgrade records again what an older engine recorded
status and record --section say which parts moved, in one line per section:
2.1 is the first turn of
section 2. The line names what you edited and not what followed from it: new words are timed again,
so their times, the section’s cue times and its length move with them, and those are named only when
nothing they follow from moved. status reads each turn’s words and voice from the script, because a
build narrates before it records, so a sentence you edited is named before narrate has made its new
take. record reads them from the take it plays, so a record run before narrate keeps the
recording, which would come out the same.
The page joins in two pieces rather than as one file, and that is what makes the loop cheap on a real
deck. A page holds every scene of a film, so a digest of the whole file would call all nine sections
of a nine-scene page stale for one slide’s edit. Editing a slide moves the scene piece and therefore
the sections that play that scene. Editing the head, a style, a script or anything else outside a
scene moves the shared piece and therefore every section of that page. A word that appears on the
slides of two scenes, such as a button both screens show, is an edit to both scenes.
The scene is cut out of the page source as you wrote it and never out of a browser, so status and
record can both answer whether a recording still stands without opening Chromium. A page that
builds its scenes in script instead has no such element to cut out, so every section of it is keyed
on the whole file.
The asset list is what keeps the key honest in the other direction. A page that swaps one picture for
another changes no line of HTML, so the markup alone would say that nothing had moved.
One function answers this question, so what record skips and what status calls stale can never
disagree, and neither of them compares file times.
A section whose words do not read, provider words or estimated words, cannot be judged, because its
words are in the page URL. record --section names it in one line, with the file, when the run
passes it over, and records the sections it was asked for. Only a run that records that section is
refused over them.
What each edit does again
Read it before you run it
decktalk status reads the project and writes only its run’s events file. It is the one call that says what is on disk
and whether that is still true. The one thing it judges is a file the project names and has not got,
which is a FILE_MISSING error and the only way it exits 1.
--json gives an agent the same reading as one flat object, and the sections[] rows are what a
loop dispatches on.
take_stateis what the disk holds for this section, withtake_reasonsaying why. It goesstalewhen the text, the voice, the model or the voice settings change, andvoicedagain once a voiced run buys the take, which is how you find what the next voiced run would buy.recording_staleis true when the recording on disk no longer matches the project. The row carries the fact and the stream carries the sentence, so a reader that dispatches on the boolean is still told which of the several things moved.
next is the whole command to run next, read off the pipeline rather than guessed. It is
decktalk build whenever anything on disk is stale, and decktalk verify when the film on disk has
not been measured. It is null once the film on disk is the one the last build measured, because then
there is nothing to do. It is also null when the takes directory holds none of the takes the project
played, which status says in one warning line, because a build would buy those takes again. runs lists any run whose events file is
still open, which is how a caller finds a build it started in the background.
A build that finds nothing changed
assemble and verify are pure functions of files already on disk, so when nothing either of them
reads has moved since the last build ran them, a build keeps both rather than doing them again. It
reports each with the outcome kept and reports its findings again, so the result reads as the run
that made the film did.
build/kept.json, never a file time. Inside assemble each
section video has a key of its own in build/sections/NN.json, so an edit to one section encodes that
section again and keeps the others.
Ask for work you were not offered
Three flags redo work.--force costs nothing, and the two replace flags buy again what you already
own. On narrate, --turn and --take buy one turn again, which is the next section.
--force builds again from nothing and never spends. It keeps every voiced take and every sound the
score bought, and makes again everything that costs nothing. narrate remakes the placeholders,
record records every section it planned, and assemble and verify run and measure again even when
nothing they read has moved. With --spend or --no-spend, a forced run buys only what is missing,
as an unforced run does.
--replace-voiced spends money on words you already paid for. With
--spend it voices each section it names again and plays the new take. Without it, a run that does
not spend plays every voiced take on disk and puts a placeholder only where a take is missing, reporting
each such section as TAKE_MISSING, so a film you have already
voiced rehearses its new section for nothing. --replace-voiced --no-spend plays placeholders in
place of the voiced takes for that one run, and the voiced takes stay on disk for the next. A voice
that bills nothing, such as dtsp, voices each section it names again with or without --spend.
--replace-score is the same for the score. With --spend it buys every bought sound again,
each part of the music included, and it never buys a take. --replace-voiced never buys a sound.
Without --spend, --replace-score keeps every bought sound on disk, so it never acts on its own.
--max-cost N refuses before the first call when the most a run can cost is over N dollars, and
--spend approves the price without asking. Neither of them is a way to redo work: they only decide
what a run is allowed to buy.
Voice one turn again
A take that reads a word with the wrong stress, or stretches a pause, is bought again one turn at a time.--turn N.T names turn T of section N, counted from one. A section one voice reads is one turn,
so its whole text is N.1, and a dialogue has one turn per voice direction, the
same numbers decktalk --json status lists under each section’s turns.
--take names a turn by the take it plays: its digest, as
build/narrate/takes.json and a narrate --json result name it, or as much of
its start as no other take shares. Both flags repeat. A turn that shares its take with another turn,
because the two say the same words in the same voice, shares the new take too, and it is bought once.
The run voices exactly the turns it names, even though a take of their text is on disk, and nothing
else. Every other turn, of those sections and of the rest of the film, keeps the take it plays, and a
turn of the same section that has no take yet plays a placeholder rather than being bought, so the
price --max-cost holds, the bill a terminal is shown and the price an approval refusal carries
cover the named turns alone. decktalk check --turn 4.2 prices the same run without running it.
--section and --replace-voiced are refused beside them, because the turns already name their
sections.
The new take plays under the take’s own name, so the take index, status and every later stage read
it as they read the old one. The take it replaces is a paid record, so it is never deleted: it is set
aside in the takes directory’s rejected/ folder as <digest>.1.mp3 with its words file
<digest>.1.words.json, then .2 for the next, and the run says where it went. To play a rejected
take again, move its pair back over the take as <digest>.mp3 and <digest>.words.json, after
setting that pair aside the same way if you want to keep it, and run decktalk narrate. The take
store keeps the take it first held, because another project may play it, so it holds the first take
of that turn and not the new one: commit the takes directory once you keep a new take, because a
takes directory that lost it is filled from the store with the first.
--replace-voiced sets aside every voiced take it replaces the same way.
Write with the build running
decktalk build --watch is the draft loop. It serves the project itself, prints the URL, builds once
without buying, and then rebuilds on every save until you stop it.
- It never spends, whatever the settings say. A free provider such as
dtspstill voices each change once a voice is named, because spend gates money and nothing else. Buying from a voice that bills is a different command, because the safe default is the rule and the named escape is a separate act. - It rebuilds the sections the saved file touches. A page belongs to the sections that play it, a
clip to the section that plays it, and
script.md,cues.jsonanddecktalk.tomleach belong to every section at once. A file that belongs to no section, such as a stylesheet, rebuilds all of them, because nothing can say which sections it reached. - A section whose words changed under a voice that bills plays a placeholder until you voice it, and
decktalk statusshows its take asstale.decktalk narrate --section N --spendvoices it. - A save that breaks a file is printed as one error block and the loop keeps watching, so you fix the file and save again rather than restarting the loop.
Run one stage at a time
Each stage is also a command, and a build is those six in order. After an edit that reaches one stage, run that stage alone and the two after it that read what it wrote.--from STAGE and --to STAGE do the same thing through build, which is the way to say it when
the span is more than one stage.
Related
- Read the six stages in order: How it works
- Price an edit before you run it: CLI
- Measure the film after a rebuild: Verify
- Read the flags of every command: CLI