Your 60-second video is 57 seconds.
Transitions overlap the shots either side of them, so a plan whose clip lengths add up to sixty renders fifty-seven seconds of video and three seconds of black. Nothing errors. video-director turns a script into a shot list your renderer accepts unmodified: it solves for content rather than for the raw sum, quantizes generated clips to the lengths models actually produce, and names the engine on every row.
For anyone whose agent makes video — and who has watched a render succeed, come back the right file size, and still be wrong.

opvs-skills install @di-atomic/video-directorThen say: “turn this script into a shot list” or “why does my video end in black?”.
Why the plan a person writes is the plan that fails
want 60s, so pick clips that add to 60 8 + 8 + 8 + 8 + 8 + 10 + 10 = 60 send it to the renderer → six cuts overlap; where did 3s go? → 10s is not a length veo3 makes → is a row a shot, or a whole post? → transition left at the default (a crossfade) → nothing errors, so nobody checks (ship it, find out later)
Every one of those is silent. The render succeeds, the file is the right size, and the last three seconds are black.
mode REQUIRED, no default, rejected if absent engine named on every row, never inferred len only a length the model actually makes content sum(len) − (n−1) × transition_seconds target solved for content, so 8×6 + 15 = 63 seam axis, direction and speed per cut default a defaulted transition is counted gate 4 verifiers, before a clip is bought
A plan you can check before you pay for it, because the arithmetic that produced it is printed alongside it.
A render that succeeds is not a render that is right.
Every row names how its asset is made, and the type follows
aiA generated clip. Capped at 5 to 15 seconds, and only on a length the model natively produces.avatarA lipsync take. Driven by the length of the line it speaks.screenA real screen recording. Any length, and elastic, so it absorbs the remainder.motionA composition with props. Any length, and where a title card or logo hold costs no generation.typeWhat the renderer does with a finished asset. Derived from the engine, never chosen by hand.What you actually get
Arithmetic that accounts for overlap
Transitions overlap their neighbours, so content = sum(len) − (n−1) × transition_seconds. Seven clips totalling sixty seconds with six half-second cuts render fifty-seven. The planner solves for content, and the verifier prints the black tail you would have shipped.
Durations quantized to what models make
veo3 returns eight seconds. sora2 returns ten or fifteen. No model honours a request for 6.3. So in an all-generated package you close a gap by changing the shot count, not by stretching a clip — and in a mixed one, the slack goes on a screen or motion row.
Cuts you can check before rendering
The schema defaults transition to fade, a crossfade carries nothing across the cut, and the craft rule forbids crossfades — so accepting the default is itself the defect. Seams are graded in the renderer’s own transition vocabulary, on the plan, with no animation library shipped.
One 60-second short, planned and gated
> "turn this script into a 60-second short"
mode ........... single_video (asked, never guessed)
aspect ......... 9:16 fps 30
FIRST ATTEMPT — the plan a person writes
8 + 8 + 8 + 8 + 8 + 10 + 10 = 60
verify-shot-list.mjs --target 60
shot 6: len 10s is NOT native for veo3 (native: 8s)
content 57.00s misses target 60s by 3.00s
black tail a naive sum would ship: 3.00s
solve for sum len = 60 + 3.00 = 63.00s
RESULT ......... FAIL (2 violations)
CORRECTED
8 × 6 + 15 = 63
n = 7, overlap = 6 × 0.5 = 3.0
content = 60.00s
verify-shot-list.mjs ............ PASS
verify-projectability.mjs ....... PASS 0 unmapped fields
verify-seam-ledger.mjs .......... PASS 0 defaulted transitions
verify-prompt-grammar.mjs ....... PASS
→ handed to media-generator: 7 rows, 0 edits
Checking a plan is free. Rendering it is not. So the whole list is graded before a single clip is generated — and the gate reports the black tail even on a PASS, as a reminder of what the raw sum would have cost.
Two constraints, and they are coupled


What it will tell you it cannot do
- It renders nothing. It decides and composes. Generation goes to media-generator, screen capture to a recorder, compositing to the renderer. This produces the one document all three read.
- No duration solver ships in v0.1.0. The procedure for hitting a stubborn target is written down, but you run it. A 45-second target has exactly one solution in the space, and a person will not find it by trial and error.
- Tutorial pacing is not gated. The tutorial recipe is reasoned from first principles, because the prompt corpus is short-form only and holds zero tutorial prior art. Its structure is checked; its rhythm has no exemplars to be graded against.
- Captions and background music do not currently arrive. The receiving worker forwards them untranslated, so they are stripped. The shot list emits them anyway and never claims they work.
- Music does not duck under the voiceover. The field is declared three times and read zero times; two renders that differ only in that setting are byte-identical.
- It will not guess the mode. Nothing in a script says whether a row is a shot or a whole post. If you did not say, it asks rather than assumes.
I got tired of renders that succeeded and were still wrong.
video-director is one skill in the system behind Di-Atomic — the marketing and compliance agency that runs cognitoAI, SpiderIQ and OPVS. If you want video produced on a pipeline that checks its own arithmetic, in any of our seven languages and including regulated categories, that is the day job.
Book a 30-min call with Di-AtomicJust want the skill? Get @di-atomic/video-director free.