shipped@di-atomic/video-editor · v0.1.0 · beta

Exit 0 is not a finished video.

ffmpeg exits 0 when it writes a file, not when the file is the video you planned. video-editor takes a shot list and its finished assets and hands back one video: cut to the plan, the music ducked under the voice, captions burned in where the platform buttons will not cover them, loudness set for where it will play. Then four gates read the file and print PASS or FAIL with the numbers.

For anyone whose agent assembles video, and who has posted a render that finished cleanly and was still wrong.

A dark chalkboard showing a film strip labelled VIDEO passing along one arrow through four gates labelled TIMELINE, RENDER, DUCKING and CAPTIONS, each with a tick, into a box labelled POST with a gold checkmark.
Built by Di-Atomic Marketing & compliance agency Measured on real renders, not inferred 7-language team Clients incl. ONYX Radiance, Pamit Group
9.96 dBthe gap a mix-level ducking check reads on a video with no ducking
0.01 dBhow close the stem-share gate lands to the exact answer
46 of 46controls fail for their stated reason, on macOS and on Linux
4gates that read the finished file, not the settings
Installopvs-skills install @di-atomic/video-editor

Then say: “cut the clips and the voiceover into one video” or “check this video before we post it”.

Why a render that finishes can still be wrong

What a hand-typed render does
type an ffmpeg command, run it
exit 0, file written, post it

→ xfade offsets ignore overlap: 9.5 s for 13
→ one clip ran short: 11 s for 13
→ the voiceover runs past the last frame
→ a compressor "ducks" and pumps between words
→ captions sit under the platform buttons
→ loudnorm defaults land at -24 LUFS

(nothing errors, so nobody checks)

Every one of those exits 0. The file plays, the length looks plausible, and the last six words of the voiceover are gone.

What video-editor produces
runtime   detected: local ffmpeg or SpiderVideo
timeline  gated BEFORE render, refuses what won't fit
graph     computed from the shot list, never typed
duck      speech envelope, -12 dB, with a hold
captions  safe zone, font checked per character
poster    baked in as frame 0
loudness  two-pass, set for the destination
gates     4, on the finished file, with numbers

A video you can hand to someone with the numbers that prove it, because every check reads the file itself.

Exit code 0 proves nothing.

Four gates, and each one reads the file

verify-timelineBefore render. The plan adds up to the target, every clip is long enough for its slot, and no spoken word falls past the last frame.
verify-renderffprobe on the output: codec, size, frame rate, duration, black and frozen runs, the poster on frame 0, loudness and true peak.
verify-duckingThe music's own share of the mix, solved against the voice and music tracks. Depth, balance and pumping. Never the mix gap.
verify-captionsThe same frame with and without captions. Text at every spoken word, nothing in the pauses, nothing under the buttons.
emit-spidervideoNo ffmpeg on the agent? The SpiderVideo job, plus every field that path cannot honour, printed before you spend a render.

What you actually get

🎚️

Ducking you can prove

The voice is in the mix while it speaks, so the mix is louder during speech whether the music moved or not: a 9.96 dB gap on a render with no ducking. The gate solves out = a·voice + b·music every 50 ms instead. It also catches pumping: the first sidechain recipe a search returns ducks 17.77 dB and climbs back 6.37 dB between words.

💬

Captions checked in pixels

Two moments of one video always differ, so the quick test passes a video with no captions at all (1.24% of pixels changed). The editor compares the same frame with and without captions instead. It checks the font first, too: a request for Noto Sans Hebrew quietly resolved to Verdana, which covers 1 of 11 Hebrew characters.

🖥️

Runs where your agent runs

In Claude Code, Codex or Antigravity it renders on your machine with the local ffmpeg, from a graph computed off the shot list. On an OPVS agent with no ffmpeg it writes the SpiderVideo job and lists what that path drops. It complements SpiderVideo. It does not replace it.

One 13-second render, gated

> "cut the clips and the voiceover into one video"

  detect-runtime ....... local_ready  ffmpeg 8.0.1 · captions via coretext
  mode ................. single_video  (asked, never guessed)

  FIRST PLAN, the picture is shorter than the voiceover
  verify-timeline.mjs plan.json
    FAIL vo_inside_picture        speech ends 12.69 s / picture 11.00 s
    FAIL captions_inside_picture  6 words lost: "checked the mix before it shipped."
  RESULT ............... FAIL, nothing rendered

  CORRECTED PLAN
    Σlen 14 − overlap 1.00 = 13.00 s
  compose.mjs plan.json out.mp4 --reference out.ref.mp4

  verify-render.mjs .... PASS  h264 High · 1080x1920 · 30 fps · 13.000 s
                               -14.1 LUFS · TP -1.7 · poster PSNR 44 dB
  verify-ducking.mjs ... PASS  depth 12.02 · balance 12.01 · pump 0.43
  verify-captions.mjs .. PASS  31/31 words · 0.0000 % in pauses

The first plan would have rendered with exit 0 and cut the last six words. The timeline gate refuses it before a single frame is encoded, and every line above is the scripts' own output.

Two tests that pass when they should fail

A dark chalkboard with three boxes labelled VOICE along the top. A white line labelled THE MIX rises into a plateau under each voice box, marked LOUDER. Below it a flat gold line labelled THE MUSIC is marked NEVER MOVED.
The mix gets louder during speech anyway. The voice is part of the mix, so a speech-versus-pause check finds a gap even when the music never moved. The gate measures the music line instead.
A dark chalkboard split in two. Left, titled TWO MOMENTS: two phones showing different pictures, labelled FOOLED with a cross. Right, titled SAME MOMENT: two phones showing the same mountain, one with a gold caption bar, labelled PROVED with a gold check.
Compare the same frame, not two moments. Any moving picture changes over time, so two timestamps always differ. The same frame with and without captions only differs where the captions are.

What it will tell you it cannot do

  • It assembles, it does not plan or generate. The cut comes from video-director, the footage and voice from media-generator. This bundle ships no font, no music and no audio of its own.
  • Local rendering is per machine, not a service. An OPVS agent on a gateway gets the SpiderVideo path, with its losses printed: one global fade, a fixed duck depth, no per-scene trim, fit, mute or frame rate.
  • Zoom-out transitions are refused. ffmpeg has no matching transition, so the editor says so instead of substituting one.
  • The pump measurement is untested on drum-heavy music. The test bed was a sustained chord. Percussive beds may need a different threshold.
  • A Mac cannot show you a missing glyph. Captions render through CoreText there, which quietly substitutes. The font check runs before render for exactly that reason; on Linux a missing letter becomes a box that a pixel test would accept.
  • It will not guess the mode. One video or a batch of separate posts? If the shot list does not say, it asks.
Why this exists

I wanted proof, not an exit code.

video-editor 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 output, in any of my team's seven languages and including regulated categories, that is the day job.

Book a call with Di-Atomic

Just want the skill? Get @di-atomic/video-editor free.