Your 60-second video is 57 seconds. Your agent now fixes it.

Your 60-second video is 57 seconds. Your agent now fixes it.

Martin Shein · · 6 min read

I planned a 60-second video. The render came back 57 seconds long, followed by three seconds of black. Nothing errored. Here is the breakdown, and what I shipped to stop it happening again.

Today @di-atomic/video-director goes live on the OPVS marketplace. Point your agent at a script and it returns a shot list your renderer accepts without a single edit. Every row names its engine. Every clip length is one the model can actually produce. The timeline adds up to the video you asked for.

The plan a person writes is the plan that fails

Here is what you write when you want sixty seconds. Six eight-second clips, then two tens:

8 + 8 + 8 + 8 + 8 + 10 + 10 = 60

That is correct arithmetic and the wrong arithmetic. Transitions overlap the shots on either side of them. Seven rows means six cuts, and at the default half-second transition you lose three seconds:

content = sum(len) - (n - 1) x transition_seconds
        = 60 - (6 x 0.5)
        = 57.0

The renderer does not complain. It composes your clips, runs out of content at 57 seconds, and fills the rest with black.

I measured it before I believed it. I submitted a 6.000-second job: three two-second scenes, two half-second transitions. Content ended at exactly t=5.000s. Frames 150 to 179 came back pure black. That is one full second, or 16.7% of the output. Then I rendered a control with the frame count corrected to 150, and the tail was gone.

Scale that up. At a half-second transition, a ten-scene video ends with four and a half seconds of black.

The fix is not a longer last clip. It is solving for content instead of for the sum:

need sum = target + (n - 1) x t = 60 + 3.0 = 63
8 x 6 + 15 = 63   ->   content 60.00s   PASS
Two chalk rectangles overlapping, the shared sliver filled gold and labelled ONE CUT at 0.5 seconds, with x 6 = 3.0s written below

You cannot ask a model for 6.3 seconds

This is the second constraint, and it is why the first one is hard to fix by hand.

Generated clips come in fixed lengths. veo3 makes eight seconds. sora2 makes ten or fifteen. You do not get to request 6.3 seconds and you do not get it if you ask. So when your arithmetic comes up three seconds short, you cannot stretch a clip to close the gap. You have to change the shot count.

Screen recordings and motion compositions are different. Those are elastic, any length you like, and they are where the slack belongs. In a mixed video you put the remainder on a screen row. In an all-generated video you are solving a small search problem, not doing subtraction.

That is the reframe. A shot list is not a wish list with durations attached. It is a constraint problem with two coupled variables, and it has to be solved before you pay for a single clip.

Four chalk blocks of increasing width labelled 8s, 10s and 15s, with a fourth gold-outlined block labelled 6.3s crossed out

What your agent now decides for you

The mode, and it will not guess. single_video means each row is a shot inside one timeline. post_batch means each row is a whole separate post. Nothing in a script tells you which one somebody wants. Get it wrong and you ship one 24-second video where three 8-second posts were ordered, with no error anywhere to tell you. So mode is required, it has no default, and a list without it is rejected.

The engine on every row. ai for a generated clip, capped at 5 to 15 seconds. avatar for lipsync. screen for a real screen recording, any length. motion for a composition, any length. The engine is how the asset gets made. The type the renderer sees is derived from it, never chosen.

Every cut, on purpose. The renderer defaults transitions to fade. A crossfade carries nothing across the cut, so the eye has nothing to follow, and the craft rule says never use one. Which means accepting the default is itself the defect. Your agent sets the transition on every row, matching axis and direction and speed across each seam. The gate counts any row left on the default.

Four chalk squares in a row labelled AI, AVATAR, SCREEN and MOTION, with the AI square filled solid gold

Four verifiers, before you spend anything

Rendering costs money. Checking a plan does not. So the shot list gets graded first:

node scripts/verify-shot-list.mjs      shot-list.json --target 60
node scripts/verify-projectability.mjs shot-list.json
node scripts/verify-seam-ledger.mjs    shot-list.json
node scripts/verify-prompt-grammar.mjs shot-list.json

They print PASS or FAIL with numbers. Run them against the naive 60-second plan and you get this:

shot 6: len 10s is NOT native for veo3 (native: 8s)
content 57.00s misses target 60s by 3.00s. Solve for sum len = 63.00s
RESULT: FAIL (2 violations)

Two problems, named, before a clip is bought.

The gate failed my own template first

Worth telling you, because it is the only reason I trust the gate at all.

The first time I ran the seam verifier against the shot list I was shipping as the worked example, it failed. Shot 5 changed its motion axis with no declared reason, while shots 4 and 6 both declared theirs. My template, my rule, my mistake. I fixed the shot list and left the verifier alone.

Then I threw twelve deliberately broken shot lists at it, including an empty object and an empty scene array. All twelve were rejected. A gate that passes garbage is decoration.

What it does not do

It decides and composes. It renders nothing. It does not generate clips, record screens, or write your script. It produces the one document all three of those read.

Two more limits worth stating plainly. The tutorial recipe is reasoned from first principles, because our prompt corpus is short-form only and has no tutorial prior art at all. Its structure is gated; its pacing is not, because there are no examples to grade rhythm against. And v0.1.0 ships no duration solver. It tells you the procedure for hitting a stubborn target and leaves you to run it.

Both are written into the skill where you will read them, not buried here.

Install it

opvs-skills install @di-atomic/video-director

Guidance-only, so there is no backend to configure and nothing to authenticate. Your agent reads it and does the work.

Point it at a script. Ask for sixty seconds. Count the frames.