---
name: Video & Audio Editor
description: Use to build, edit, or render a video/audio project — a timeline of video/image segments, text overlays, music, SFX and voiceover — from generated or existing media, saved as an editable *.mvideo.json the app renders as a visual editor.
version: 2
situations: video editing, timeline, make a video, edit a clip, crop segment, add text overlay, generate SFX, music bed, voiceover, expand a video, regenerate with another model, mix audio, remotion, render video, script, storyboard, shot list, cast, characters, props
requires-secrets:
requires-packages:
---
<!-- Rendering deps (Remotion + react) are NOT installed on skill install: they're a project-scoped
     toolchain that must live in a local node_modules, so the render bundle installs them into its own
     folder on the FIRST render (see reference/rendering.md). Nothing heavy is pulled in until you export. -->


# Video & Audio Editor

You build videos as an **`*.mvideo.json` project** — a single JSON file holding a library of assets and a
set of tracks (video/image, text, audio) laid out over frames. The app renders this file as a visual editor
(live preview + timeline). You never hand-edit the JSON; you use the **`timeline_edit`** tool, which applies
structured operations and keeps the frame math and ids correct.

Times are always in **frames**. `frame = seconds × fps` (default fps 30). A 3-second clip at 30fps is 90 frames.

**Running these tools.** If you're a pi-brained agent they're native tools — call them directly. If you're a
CLI brain (Claude Code / Codex), run each one through the `00` command from your shell — never curl:
`00 media <tool> --json '{…}'`, e.g. `00 media timeline_new --json '{"width":1080,"height":1920}'` or
`00 media storyboard_compile --json '{"path":"projects/spot.storyboard.json"}'`. They edit the project JSON
and return at once. Generation (image/video/music/sfx) has friendly forms: `00 media image "a cat"` (see the
platform guide). Render an `*.mvideo.json` with the bundled script — see [reference/rendering.md](reference/rendering.md).

See [reference/project-schema.md](reference/project-schema.md) for the full format + a worked example, and
[reference/consistency.md](reference/consistency.md) for keeping characters/places consistent across shots.

## Build a base to react to — don't render the finished film

A generated video almost never lands in one shot: the prompt is a guess at what someone pictured, and
they only discover the gap by watching it. So your job is **not** to deliver a finished video from a
one-line brief. It's to build the cheapest artefact that makes the idea concrete enough to correct —
then hand it over and let them steer.

Work up the ladder, stopping at each rung:

| Rung | Artefact | Cost | What it settles |
| --- | --- | --- | --- |
| 1 | `*.script.md` | free | story, beats, voice, length |
| 2 | `*.storyboard.json` + **stills** | cents | shots, framing, look, order |
| 3 | animatic — stills on the timeline with text/music | cents | pacing, rhythm, does it work at all |
| 4 | video for approved shots | **expensive, slow** | the final motion |

**Never generate video assets without the operator's explicit go-ahead.** Images, music and SFX are
cheap and fast — generate those freely to build the base. `video_generate` is minutes per clip and
real money per clip, and a rejected batch is money spent on nothing. Get the stills approved first;
the storyboard is what makes that approval meaningful.

Ask before rung 4, per batch, and say what it will cost in time and clips:

> Storyboard's ready — 6 shots, stills in the grid. Shots 2 and 5 are the ones I'm least sure about.
> Want changes first, or shall I animate all 6? (~2 min each, 6 clips.)

Then animate **only what they approved**. If they ask for "a video" and nothing else, that is a
request for rung 1–3 plus a question, not a licence to spend on rung 4.

Exceptions — go straight to video without asking when: they explicitly said so ("just generate it",
"no need to check"), it's a single short clip they described precisely, or they're re-running one shot
they already approved with a tweak.

## Core loop

Rungs 1–3 first (above); this is the mechanism, not permission to skip ahead.

1. **Create** a project: `timeline_new` (pick aspect via width/height — 1080×1920 vertical is the default).
2. **Generate or gather media** with the tools you already have:
   - `image_generate` → stills, backgrounds, title cards.
   - `video_generate` → clips (text-to-video, or animate an image via `imagePaths`). Pass `model` to pick
     the engine (e.g. `wan-2.2`, `wan-2.7`, `ltx-2`, `fal-ltx`, `grok-imagine-video`).
     **Needs the operator's go-ahead** — see the rung table at the top.
   - `music_generate` → a music bed; `sound_effect_generate` → SFX/foley; `generate_speech` → voiceover.
   Each writes a file into `files/`.
3. **Place it on the timeline** with `timeline_edit`:
   - `add_asset` to register the generated file (record its `prompt` + `model` so it stays regenerable), then
   - `add_clip` onto the right track with `from`/`to` frames.
4. **Refine**: `update_clip` to trim (`trimIn`/`trimOut`), move (`from`/`to`), style text, add transitions;
   `move_clip`, `remove_clip`, `add_track`, etc.
5. **Render** to an MP4 when asked — see [reference/rendering.md](reference/rendering.md).

Tell the operator they can open the `.mvideo.json` in the app to scrub, preview, and point at a clip to send
you an instruction about it.

## Pre-production: script → storyboard → timeline
This is the ladder above, in tools. The pipeline is
`*.script.md` → `*.storyboard.json` → `*.mvideo.json` → render (see [reference/script.md](reference/script.md)):
1. **Script** — a Markdown `*.script.md` (scenes, shots, action, VO, cast). Opens in the document view.
   Show it and get a reaction before spending anything.
2. **Storyboard** — `storyboard_new` + `storyboard_edit` (`add_shot`/`update_shot`/`move_shot`) build a shot
   list; generate each shot's still with `image_generate` and record it (`update_shot { still }`). Opens as
   a shot-card grid you can point at — so the operator can redirect a shot instead of rejecting a film.
3. **Compile** — `storyboard_compile` turns the shot list into an `*.mvideo.json` (one clip per shot, stills
   as placeholders, dialogue as text, cast carried over). Add music, SFX and text here: with the stills
   held on screen this already plays as an animatic, and pacing problems show up now, while they cost
   nothing to fix.
4. **Assemble** — **stop and ask first** (see the rung table). Then generate video for the approved shots
   and swap each in (below), and render.

A still that's wrong is a re-roll worth cents; a clip that's wrong is minutes and real money. Every rung
before 4 exists to make rung 4 a smaller bet.

Keep recurring people/places consistent via the library (see below) so every shot matches.

## Key workflows

### Bring in an existing or remote video (URL / YouTube)
Use `import_video` to download a video/image and append it as a segment: pass the project `path` and the
`url`. Direct file URLs and YouTube (and other yt-dlp sites) both work; only public http(s) hosts are
allowed. The asset records its origin URL. The operator can also do this from the editor UI ("add video").
Once imported it's a normal segment — trim it, split it, or lay it out however you like. YouTube requires
`yt-dlp` installed on the host; if it's missing, say so.

### Split a clip / edit an existing video
The timeline is non-destructive — trimming and splitting never touch the source file:
- `split_clip { clipId, at }` cuts a clip in two at frame `at` (trims are carried, so it's seamless).
- Or add two clips over the same asset with different `trimIn`/`trimOut`.
So to "edit an existing video": import it, then split/trim/reorder the pieces and drop other tracks on top.

### Side-by-side / picture-in-picture
Give each clip a transform **box** (fractions of the frame) and put them on different tracks so they're
on screen at the same time:
- Left/right split: clip A `transform:{x:0,y:0,width:0.5,height:1}`, clip B `{x:0.5,y:0,width:0.5,height:1}`.
- PiP: a small inset like `{x:0.62,y:0.04,width:0.34,height:0.34}` over a full-frame clip.
`fit` is `cover` (default) or `contain`. Without a box, a clip fills the frame (with `scale` + pixel offset).

### Backgrounds (color / gradient / pattern)
A clip with a `fill` and no asset paints a background (put it on an early video track so it sits behind):
```jsonc
{ "id": "bg", "from": 0, "to": 300, "fill": { "type": "gradient", "angle": 135,
  "stops": [{ "color": "brand/bg", "at": 0 }, { "color": "brand/accent", "at": 1 }] } }
```
`type`: `color` (`color`), `gradient` (`shape` linear/radial, `angle`, `stops`), `pattern` (`pattern`
dots/grid/stripes/checker + `fg`/`bg`/`size`), or `image` (`src`). Colors accept `brand/<key>` refs.

### Fonts
Text renders in a font only if that font is loaded. Call `font_ensure { projectPath, families }` with exact
Google Font names (e.g. `["Inter", "Playfair Display"]`) — it downloads them into files/.fonts and records
them in the nearest `branding.json` (`fontFiles`), so both the editor preview and the render use the same
file. Then reference them via brand roles (`fonts.title`) or directly as the family name in a style.

### Animation (entrance / exit / emphasis / keyframes)
Set a clip's `animation`:
```jsonc
"animation": { "in": { "type": "slide", "from": "left", "dur": 12, "ease": "spring" },
               "out": { "type": "fade", "dur": 8 },
               "emphasis": { "type": "pulse", "amount": 0.04 } }
```
`in`/`out` types: `fade | slide | scale | wipe | none`; `ease`: `linear | in | out | in-out | spring`.
For full control use `keyframes: [{ frame, x?, y?, scale?, rotate?, opacity? }]` (clip-local frames),
which combine with the presets. The operator can also set entrance/exit visually from the clip inspector in
the editor (select a clip → the in/out dropdowns). Legacy `animation.preset` and `transition:{in/out:"fade"}`
still work.

### Reusable cast & assets (the production library)
Keep recurring characters, props, vehicles, locations, wardrobe, etc. in a **library** so they stay
consistent across shots AND across projects. See [reference/library.md](reference/library.md).
- `entity_add { id, category, name, description, promptFragment, seedLock, views }` creates/updates an
  entity in `library.json` (the workspace-root `library.json` by default; pass `libraryPath` for a
  per-folder one). Resolution is **nearest-wins** — a project uses only the single closest `library.json`,
  never a merge, so each folder decides exactly what it reuses.
- Build the entity's **reference views** (angles/poses) with `image_generate`: reuse the entity's
  `seedLock` and pass earlier views as `imagePaths`, then record each result path in `views` via
  `entity_add` (e.g. `views: { front, side, "3q", back }`).
- When a shot features an entity, tag the clip (`entities: ["ana"]`) and feed the entity's matching `view`
  + `promptFragment` + `seedLock` into the generation so the character/place looks the same every time.
- **Consistent voice:** a character can carry a `voice` (`entity_add { id, voice: { provider, voiceId,
  model, settings, sample, description } }`). Use its `voiceId` with `generate_speech` for every line so the
  character always sounds the same; for video-with-audio / lip-sync, pass its `voice.description`/`sample`
  as the voice brief + the shot's `dialogue`. See [reference/library.md](reference/library.md).

### Provisional segments & cost-aware generation
Don't burn cloud credits early. Segments start **provisional** — an un-voiced line's length is estimated
from its text; a compiled shot is a **pending video** shown as its still **draft thumb** — and the real
media is generated **at final render or on request** (the ⚡ button on a draft segment). Local models
(images, TTS/SFX) can generate eagerly; local *video* is slow, so thumb it too. After generating, always
`resize_clip { clipId, durationFrames, ripple:true }` so the provisional length snaps to the real one and
later clips shift. See [reference/generation.md](reference/generation.md).

### Text-based editing (script / transcript / podcast)
A project carries **cues** — timestamped lines of speech/SFX (`project.cues`) shown in the editor's
**script mode**: click a line to seek, the active line highlights during playback, text edits inline. See
[reference/transcript.md](reference/transcript.md).
- **Transcribe (audio → text):** turn an audio track into timed cues (`add_cue`), then navigate/edit by text.
- **Voice it (text → audio):** write cues with a `speaker` (a character entity + its `voice`), `generate_speech`
  each line, place the audio + set the cue's `clip`; add `kind:"sfx"` cues between lines and
  `sound_effect_generate` them. Great for podcasts — editing words is easier than the timeline.
- Cue ops go through `timeline_edit` (`add_cue`/`update_cue`/`move_cue`/`remove_cue`). In the pre-production
  `.script.md`, tag speakers/places with `@entityId` so they link to the library.

### Brand logo
`logo_place { projectPath, variant?, corner?, size? }` drops the brand logo (from the nearest
`branding.json` `logo`, e.g. `light`/`dark`) into a corner as a watermark. Set `branding.logo` first.

### Regenerate a segment with a different model
The asset keeps the `prompt` and `model` it was made with. To try another model:
1. `video_generate` with the **same prompt** and a different `model`.
2. `timeline_edit`: `add_asset` for the new file, then `update_clip { clipId, patch: { asset: "<newId>" } }`.
The old asset stays in the library, so you can A/B or revert.

### Expand a segment (before/after) using its first/last frame
Only possible when the video model supports image-to-video (Wan, LTX, Grok all do). To extend clip C forward:
1. Extract C's last frame to a PNG (ffmpeg is available):
   `ffmpeg -sseof -0.1 -i files/<clip>.mp4 -frames:v 1 files/<clip>-last.png`
   (for a leading extension, extract the first frame: `-i files/<clip>.mp4 -frames:v 1 files/<clip>-first.png`).
2. `video_generate` with `imagePaths: ["files/<clip>-last.png"]` and a prompt continuing the action.
3. `timeline_edit`: `add_asset` + `add_clip` placed right after C (`from` = C's `to`).
If the configured model can't do image-to-video, say so instead of guessing.

### Separate a video's audio into its own track
For a video asset with embedded audio you want to control independently:
1. Extract it: `ffmpeg -i files/<clip>.mp4 -vn -acodec libmp3lame files/<clip>-audio.mp3`.
2. `timeline_edit`: `add_asset` (kind `audio`) + `add_track { type: "audio", role: "source", linkedTo: "<clipId>" }` + `add_clip` on it.
3. Mute the source by setting the video clip's audio gain low, or leave both if that's the intent.

### Text overlays & animation
`add_clip` on a `text` track with `text`, a `style` (inline object or `"brand/<name>"` — see the branding
skill), and an `animation` preset (`fade`, `slide-up`). Position with `transform` (x/y offset, scale).

### Branding
If the `branding` skill's `branding.json` conventions are in use, reference named styles as
`"style": "brand/title"` and set `branding: { "$ref": "auto" }` in the project so the nearest brand applies.

## Notes
- Keep every generated asset's `prompt`/`model`/`params` populated — that's what makes a clip re-editable.
- Prefer reusing an asset across clips over regenerating identical media.
- Don't block on rendering for previews — the operator previews in the app; render only when they want the file.
