decktalk verify is the last stage and the only one that makes no artifact. It opens the finished
mp4 and measures it against the clock the earlier stages promised it would keep. It changes nothing
a later run reads: the only files it touches are its own events file and the build lock it holds
while it measures. This is the page to read when a number it printed is not the number you expected.
Five things are measured. Every section must open on a real picture past its dip to black. The
narration must be quiet in the window before each cut, so no cut lands on a word. A section that
declares itself seamless must open on the picture the section before it ended on. Every cue must
change the picture, by enough to be seen, within the offset limit of the word it was promised to,
with every declared motion around it masked out. And every declared motion must depart and arrive on
its own lane within that same limit of its words.
Beside those five it repeats what every recording log already judged, rather than measuring it again,
so a page that threw or an equation that was never typeset is still a finding after the build that
recorded it, and after a build somebody else recorded.
Running it
--section N narrows every measurement to those sections, taking 3, 3,5 or 7-9 and repeating.
--fail-on and --allow decide what fails the run, as
Which findings fail a run says. decktalk verify --help
prints every flag.
The stage inside decktalk build is the same call, so a build measures everything a bare verify
would.
Three refusals come before any measurement. A film that is not there is NOT_BUILT, and so is a film
that carries no placements file and has no section files to read a shape from. Both say to run
decktalk assemble first. A film whose recorded page sections have no recording log under
build/recordings/ is NOT_BUILT too, because without the logs verify would repeat none of the
recorder’s findings and measure none of the motion, so a film moved to another machine takes
build/recordings/ with it. A section the film filled with black was never recorded and needs none.
What it prints
cues.json opted out of, which is silent by design. The
starts, the cuts and the seams print no table at all: their numbers are in decktalk verify --json,
and what is wrong about any of them is a finding.
A deck that declares motions prints a second table under the first, with one row per landing that
was measured. Motions gives its columns.
change_percent is in the JSON and not in the table: it is how much of the picture the reported
probe found changed.
The frame of reference
Every second on this page is a second in the finished film.verify reads the film’s own placements,
build/final/placements.json, for where each section starts and how long the whole film runs, because that
file is the film’s own record of its shape. With no placements file it adds up the section files instead and
says on its stream that it did.
Shares are measured on a quarter-size copy of the frame, which is verify.probe_width by
verify.probe_height, both derived from the frame size. A share is the percentage of pixels whose
luma differs by more than a level, so the comparison reads brightness and never colour: a reveal that
changes only colour at the same brightness reads as no change, and the way to keep such a cue is
"verify": false in cues.json.
Section starts
For each section that the film plays,verify reads the frame verify.after_dip_seconds past the
section’s start and takes its brightest luma. A frame whose brightest pixel is at or under
verify.black_max_luma is black, and the section opens on nothing.
The probe sits a fixed distance past the start whatever the dip is, so a dip longer than the default
needs after_dip_seconds raised past it. A clip section gets a row like any other.
Cuts
For each section that has a take,verify measures the level of the narration track alone over the
window before the section’s narration ends. The window is verify.cut_window_seconds or the take’s
own span, whichever is shorter, and the level is an RMS. Above verify.cut_max_dbfs the cut is
falling on a word.
The narration track is read alone on purpose, so music or a sound effect at a boundary is never taken
for speech. A clip section carries its own audio and has no row.
Each row also carries a step, which is how far the level of the finished film jumps across the cut,
measured over a twentieth of a second on each side. It is reported for a reader and no limit is held
against it.
Seams
A section that setsseamless = true in decktalk.toml claims it opens on the picture the section
before it ended on. verify compares the outgoing section’s last whole frame with the incoming
section’s first, both taken outside any dip so that a fade through black is never read as a jump.
Over verify.cut_change_max_percent of the frame changing is a visible seam, and verify raises
CUT_POP against it. A seam is only ever a seamless cut, so a section that does not set seamless
has no row here and its cut is never judged for the picture.
The recorder steps the section clock, so a seamless section opens on the very frame the section
before it ended on, and the first frame alone decides CUT_POP. A picture that arrives even one frame
late is a frame a viewer sees jump. When the two do not match, the next three frames are read as well,
so the row can say how late the outgoing picture arrived: drift_seconds is how far past the cut it
was found, in seconds, and it is zero when the seam matched straight away.
Cues
A cue check asks one question: did the picture change because of this cue, and did it change when the word was said. It is answered in stages, and each stage has its own way of failing.Reference frame
The reference frame is the picture before the cue, and every probe is compared against it. It sits the whole reference lead before the cue.GUARD_FRAMES is 1.5, which is half a frame of rounding guard on each side of the window the offset
limit allows, and video.fps is the rate the recorder steps the section clock at, so the guard is
always one and a half of the film’s own frames. The lead has to be at least that wide: a reveal that lands at the early edge of
the offset limit would otherwise already be showing in the reference frame, and the check would
measure the change only once the reveal had settled.
verify.reference_lead_extra_ms is the escape hatch for a deck whose reference frame is still inside
its own reveal. It defaults to 0 because the lead is already as wide as the limit implies.
The reference may not sit inside the section’s fade-in, where the picture is still coming up from
black, and it must sit at least one frame before the cue. When no frame satisfies both, the cue is
skipped with at_section_start as its reason.
Probes
A probe is the frame a fixed delay after the cue.verify.probe_delays_seconds names the delays, 0.7
and 1.5 seconds by default, and the later one is what catches a slow reveal. A probe that would fall
within 0.05 seconds of the section’s end is dropped, and a cue whose probes are all dropped is
skipped with too_close_to_end as its reason.
Each probe gets a control share, which measures motion that has nothing to do with the cue. The
control is the quieter of two back-to-back spans of the probe’s own length that end at the reference
frame. Steady motion, such as a camera push, shows in both spans. An earlier reveal that is still
settling shows in only one, so taking the smaller of the two leaves it out. A span that would start
before the section’s floor is dropped, and with both dropped the control is zero.
Probes run forward from the reference frame to each delay after the cue, and two back-to-back control spans of the same length run back from the reference.
decktalk verify --json for what your own film measures.
The neighbour allowance
Another cue of the same section can spoil a probe. Its own reveal would count as this cue’s change if it can fall inside the probe’s span, or as motion that was always there if it can fall inside every control span that is measured. The allowance that decides whether a neighbour reaches into a span is deliberately not symmetric.- Backward it is the reference lead, which is
verify.cue_offset_max_ms / 1000plus the guard frames over the capture rate plusverify.reference_lead_extra_ms / 1000. A reveal may legally land that early and still pass, so the check has to assume a neighbour might. - Forward it is the neighbouring effect’s own declared span, taken from the attribute registry
through the catalog the page published and scaled by
motion.scale. An effect stops moving when its own animation ends, and not a fixed distance later.
verify says on its stream which cue was fitted and to what, as a
detail line that -v prints, because a fitted probe is how a close pair is measured and not a
problem.
Change test
verify.probe_diff_luma. A cue that fails the test raises
CUE_NO_CHANGE and nothing further is measured for it. A cue that passes by less than the factor
raises CUE_THIN_CHANGE, which is a warning: the cue did pass, and a slightly smaller reveal or a
little more motion around it would not have been measured at all. Setting
verify.thin_change_factor to 1 turns that second opinion off.
The change_percent in the JSON is the reported probe’s share, which is not always the largest share
measured, because the reported probe is chosen on margin.
PAGE_THIN_DRAW
decktalk check runs the same arithmetic over two frozen frames rather than over the film, and it
has one code of its own for the case a stroke makes. PAGE_THIN_DRAW is a warning, and it says that
an entrance drawn as a stroke sweeps less of the frame than a change has to cross to be seen. A
stroke that thin is a stroke nobody will see rather than a reveal that never happened, which is why it
is not CUE_NO_CHANGE.
It is measured in Python from the frozen frames and never raised by the page itself, because the
floor it is read against is verify.changed_share_min_percent, a settings key no browser can read.
video.crf moves it too, since the encoder’s own ringing competes with a thin stroke.
The onset
Once a cue has passed the change test,verify finds the frame the reveal began on.
- It measures the changed share of every frame from the reference frame up to the reported probe,
each compared with the reference frame at
verify.onset_diff_luma. - It measures the same series again with both frames scaled to
verify.block_widthbyverify.block_height, which is one pixel per H.264 transform block. - The onset is the first frame after the reference whose share rises by at least
verify.onset_rise_pointsover the frame before it, and whose block share is above zero. - When no frame rises that much, the onset is the first frame after the reference whose share passes
a floor, and whose block share is above zero. That floor is the larger of
verify.onset_rise_pointsand the largest share measured up to the point that sits the offset limit plus half a frame before the cue, so a picture that was already moving before the cue cannot pass for the reveal. - When no frame does either, the cue raises
CUE_NO_ONSETand has no row in the table.
Consecutive frames from the reference frame onward, each with its share against the reference, and the first frame that steps up is the onset.
verify.onset_diff_luma, which is a much lower luma difference than the change test’s
verify.probe_diff_luma, so the same cue reads a different share here than in change_percent.
The word the onset is measured against
After a build on placeholders, the narration is click audio with a click at every word start and one where the last word ends, so the film’s own audio can be measured against its own picture.verify
searches verify.click_search_seconds on each side of the cue’s second, inside that cue’s section
alone, and takes the loudest sample. A sample under verify.click_floor_dbfs is not a click, and
DeckTalk generates its own click at -24 dBFS, so a floor at or above that finds nothing at all.
- When a click is found, it becomes the row’s
Spoken, andOffsetis the onset less the click. That measures the picture against the film’s own audio, so it covers the recording, the trim at the start of the section clock, the cut, the join and the mix in one number. - When no click is found, and after a voiced build,
Spokenis the cue’s own second in the film andOffsetis the onset less that.
Limits
verify.av_offset_max_ms may not be set under 80. Both limits
raise CUE_OFF, and each message says which of the two it is.
A whole run that lands a frame or two late is usually a slow machine rather than a bad deck.
Build in CI and offline shows how to widen a limit on one runner.
Motions
A page declares everything that moves for longer than a moment, as a motion, a loop or the region a frame hook draws in, and a render section’s program may write the same declarations as its landings. The recorder keeps one lane for each in the recording log, with the box it swept and the seconds it moved, and verify reads the lanes from there. The lane report gives their fields.The mask
Every cue is measured with every lane alive anywhere from the start of its control spans to its last probe masked out. The mask is the union of those lanes’ boxes, scaled to the probe size, and it is taken out of the changed share, the control share and the onset scan alike, so a ring turning in a corner never reads as a reveal starting early.masked_percent is the share of the frame the mask covered. Over
verify.motion_mask_max_percent, which is 40 by default, verify raises MOTION_MASK_LARGE. It is a
warning, because the cue was still measured, on the part of the picture that was left.
Landings
A cue that is only a motion’s departure or arrival, where the page reveals nothing else, has no step in the frame to read. It is skipped in the cue rows asmotion_landing and measured on the motion’s
own lane instead. A cue where the page also reveals something is measured both ways: as a reveal
with the motion masked, and as a landing on the lane.
Inside the lane, frame by frame, the motion counts as moving while more than
verify.motion_floor_percent of the moving element’s own area changes from one frame to the next,
which is 0.5 percent by default. The comparison is made at verify.onset_diff_luma.
verify.cue_offset_max_ms plus half a frame, as it is for a cue. An arrival that came
to rest early is MOTION_NO_EDGE rather than MOTION_OFF, because the fix is the ease rather than
the cue: a motion that eases to rest has no edge on its word, and firm gives it one.
A landing on a cue that cues.json marks "verify": false has no row and no finding, which is how a
settle or spring arrival is written.
A render section
A render section’s lanes are the landings its program wrote. When it wrote none, nothing says what moves in its picture, so every cue of that section is skipped asrendered and verify says once on
its stream that it measured none of them. Such a cue is never passed.
Every finding verify can raise
verify also raises again every finding the recording logs hold, such as
RECORD_TRUNCATED, PAGE_KATEX_ERROR and every warning the runtime reported while it recorded. Those
are read from the log rather than measured, which is why recording that section again is the only
thing that clears one.
Each finding has a page of its own under /reference/findings/, and
Configuration is the same index in reverse.
decktalk schema prints every code with the keys that decide it as JSON, in its decides list.
Skipped cues
A skipped cue has noshown, no offset_seconds and no change_percent, and its skipped field says why
it has none. Two of the eight reasons carry a finding, and the table says which.
The JSON
decktalk verify --json prints one flat object with the four reserved keys, run, and eight fields
of its own.
A skipped cue has
null in shown, offset_seconds and change_percent. A measured cue has null in
skipped. What is wrong is in findings, never in a row, so a reader dispatches on a finding’s
code and reads a row for the number behind it.
decktalk schema verify prints the whole result model.
Related
- Run it and read every flag: CLI
- Move a limit: Configuration
- Read one code: Findings
- Fix a failed check: Troubleshooting