Skip to main content
Course platforms, company intranets and public sector sites often require captions and a transcript by law. A DeckTalk build writes most of what that takes without being asked, because it already knows the spoken words, where each one falls and what appeared on which second. The parts it cannot write are the ones only you know: what a reveal meant, and what a picture is of. Each of those has a judgement behind it, so a description you did not write is reported rather than missed. This page is the whole of both halves.

What every build writes

Everything below lands in build/final/, beside the film, and <name> is [project] name. The chapters are muxed into the mp4 as well, so a player shows them without the text file, and the text file is there for a platform that wants to be handed one.

One language, stated once

[project] language is the language tag of the narration, and en is the default. The transcript page takes it as its lang attribute, and the mp4’s audio and video streams are tagged with its three-letter form, which is the code the container takes rather than the tag a page carries. One value reaches both, so a player naming the audio and a reader opening the transcript cannot be told two different things.

Captions carry the whole soundtrack

Captions owe the audio and not only the dialogue. A viewer who cannot hear the ball bounce has to be told that it bounced. Every cued sound takes a caption string, and the build writes it into the caption files as a cue of its own, wrapped in square brackets.
decktalk.toml
A sound with no caption is in the film and in no caption file, and assemble says so on every run, naming the file, the cue and the section. The frozen list of codes holds none that means this, so it is a sentence on the stream rather than a code that means something else. Put a sound effect on a cue is how a sound gets cued in the first place. A sound’s caption stays on screen for one second. Two captions are never on screen at once, because a player draws overlapping cues twice or drops one of them, so a sound that lands inside a speech cue joins that caption as a line of its own and a sound that lands in the short gap before one joins the cue after it. Only a sound with a whole second to itself keeps a cue of its own. A clip section captions its clip’s own speech from the words file beside it, <clip>.words.json, and nothing captions it otherwise, because the captions are built from the narration’s word times and a clip’s speech is not in the narration. Every DeckTalk build writes that file beside its film and decktalk clip beside every clip it cuts, so a film DeckTalk built is captioned in a clip section as it captioned itself. A clip with no words file beside it adds no caption. Play a clip has the file’s shape. In a section spoken in more than one voice, [output] caption_voices = true names the voice of every caption: a WebVTT voice span such as <v guest> in the .vtt, and guest: before the line in the .srt. It is off by default, so the captions carry the words alone, and the transcript names the voices of a dialogue either way. Write a dialogue has the whole of it.

A captioned copy, for a place that cannot load a caption file

A social feed, a slide embedded in another deck and a screen in a lobby play an mp4 and nothing beside it. Ask for a second film with the captions drawn into its picture:
assemble then writes <name>-captioned.mp4 beside the film. The film itself is the same frames either way, with no caption in it, and it keeps the .srt, the .vtt and the transcript, so a player that can load a caption file still gets captions a viewer can turn off and resize. Your own page draws each caption, so the copy is set in your deck’s fonts. The page is opened with ?caption=1, which clears everything but a band the runtime lays out along the bottom of the frame, and is handed one caption at a time. Each caption is a <p class="dt-caption"> holding one <span class="dt-caption-line"> per line, and every rule the runtime gives them carries no specificity, so a rule of your own restyles them:
A caption carries the voice that says it as data-voice and the section’s turn as data-turn, and never as text, so a dialogue can set each voice’s captions on a side of its own and still name nobody. The speech inside a clip carries the clip’s own voice and turn the same way, when its words file names them. A line that says what is heard, such as [ball bounces], carries data-sound. A caption shows on the frames from the one its start rounds to up to the one its end rounds to, which are the frames a player shows the same caption on from the .vtt. A caption over a clip section, or over a section a command renders, is drawn by the first page the film plays. A film that plays no page has nothing to draw its captions with, so assemble, and a build that reaches it, refuses it before anything runs. Each caption is drawn once and kept in build/stills/ under everything it was drawn from, so a rebuild that changed one line draws one caption again. The sound and the chapter markers are copied from the film untouched, and only the picture is encoded again.

The transcript, and the words a reveal is described in

The transcript page carries, for every chapter in the order it plays: a heading, the span in the finished film, the spoken text of each section under it, the speech inside every clip the captions carry, and a list of that chapter’s described reveals with the second each one fired. The paragraphs of a section are in the order the film plays them. A section spoken in more than one voice is one paragraph per turn, each opening with the name of its voice, and a clip’s speech is one paragraph that names nobody. What it cannot carry on its own is what a reveal meant. Three attributes are where you write that, and each one is read by the runtime and reported into the recording log, which is what the transcript is built from.
Describe the reveals that carry meaning. A decorative flourish needs no description, and an empty data-describe is how you say so on purpose rather than by leaving the attribute off. A description of every flourish only makes the transcript harder to read. A slide writes each sentence once, where it first applies. When a later cue of the same slide would write a sentence the slide has already written, such as its tick lights on every tick of a row, the transcript leaves it out there, so the reader meets it at the first tick and not at every one. The next slide starts with nothing said. The starter writes a description on every cued element, so a first project’s transcript already reads end to end. The page contract is every attribute in context, and decktalk schema page prints every one with their values, their defaults and their codes.

What holds each of them honest

Every promise on this page has a judgement behind it, so nothing here depends on remembering to look. --fail-on error is the default, so the errors fail a run already. --fail-on warning adds the warnings, which is how a project says it wants the second opinions too, and --allow CODE carries one code past the threshold and repeats. A cue that landed on the wrong word is an accessibility failure and not only a timing one: a viewer reading the captions meets the reveal at the wrong moment, and a viewer reading the transcript reads the wrong second beside it.

The loudness the mix is mastered to

A film that is quieter than everything else on a platform is a film a hard-of-hearing viewer turns up and then gets startled by. The mix is therefore mastered rather than left where the layers landed. assemble measures the whole soundtrack, applies one gain to reach [audio] target_lufs, runs a true-peak limiter at true_peak_max_dbtp, and measures the result again. A plain gain keeps the mix’s dynamics intact and the limiter touches only the peaks that would cross the ceiling, and it runs oversampled so that inter-sample peaks are caught, which is what a true-peak ceiling promises. The second measurement is reported, and a true peak over the ceiling or a programme loudness away from the target is MIX_LOUDNESS. It is a warning, because the pass reports what it could not fix and a reader decides. Sound says exactly when it is raised.

Reduced motion

Some viewers ask their own machine for less motion, and some cannot watch a film that moves at all. DeckTalk renders that film rather than asking the viewer’s player to. No flag duplicates a settings key, so both of these are --set on any command.
  • motion.reduce=true renders every slide as its reduced-motion variant. Chromium is asked for prefers-reduced-motion, which is a media feature rather than an attribute, so the film is exactly what a person who asked their own machine for less motion would see in a preview. A class change whose stylesheet animates anyway is reported as PAGE_CLASS_NOT_REDUCED, because a page can declare reduced motion and then not honour it.
  • motion.scale=N multiplies every declared motion span and duration in the deck, from 0.25 to 4. It is put on :root as --dt-motion-scale, which the deck’s own stylesheet reads. A span slowed past the half second a cue may still move is clamped at that ceiling rather than obeyed, because a span that covers its own cue cannot be measured, and a span that overruns it is PAGE_MOTION_OVERRUN.
Both keys are part of what a recording is keyed on, so a reduced build never reuses a full-motion recording and the calmer film is a second build rather than a second project. The film is written to the same name, so copy the one you want to keep out of build/final/ before you build the other, or give the calm build its own [project] build directory.

What the author still owes

  • Give every image its alt text. A slide image is ordinary HTML, and nothing writes its alt attribute for you.
  • Change lightness, not only hue. decktalk verify measures luma alone, so a reveal that changes colour without changing lightness fails the check and also fails a viewer with low vision. The two failures have one fix.
  • Say the thing you show. A reveal the narration never names has no word to start on and no place in the transcript, which is the same rule Writing for the ear gives for a different reason.
  • Keep what matters out of the bottom of the frame. A player draws captions across the bottom of the picture, and DeckTalk does not measure that, so it is yours to watch for.

How to check it

1

Judge the pages before you build

check opens the pages and writes nothing a stage reads, so this is where a missing description, an undescribed class change and a class that animates under reduced motion are met. --fail-on warning adds the warnings to the ones that already fail.
2

Measure the finished film

Every section start, every cut, every seam and every cue landing is measured against the clock the earlier stages promised. A reveal that did not land on its word is a reveal a caption reader meets at the wrong moment.
3

Read the transcript with the film closed

Open build/final/<name>-transcript.html and read it on its own. A chapter whose list of reveals is short or missing is a chapter whose page needs its phrases written.
4

Watch it with the sound off

Play the film with the captions on and the sound off. Anything you learn only from the audio is a caption line that is not there.

Publishing

Serve the film from a plain <video> element with a <track> for the captions, so the captions and the chapters stay under your control rather than a third-party player’s. Put the poster on the element, link the transcript page next to the film, and give that link text that says what it is rather than the word “transcript” on its own.