--- name: nodetool-video-post description: "Repair or re-generate footage already on a NodeTool timeline: cutouts, tracking, outpainting, upscaling, generated sound, voice replacement, lip sync. Covers the candidate review protocol these return." featured: true --- # Post on a saved timeline Footage already exists and something about it has to change. These tools act on one clip of a **saved** timeline sequence, not on a storyboard shot and not on a loose asset. Directing a new piece belongs to `storyboard-core` and its job skills. Cutting, animating, transitions and titles belong to the `motion-graphics` system skill: load it with `load_skill {name: "motion-graphics"}` before the first `edit_timeline` call, as it carries the full op list, the presets and the preview contract. ## Pick the operation | The problem | Tool | What it writes | |---|---|---| | Replace what is behind the subject | `isolate_subject` | A `generatedMatte` on that clip | | Make another clip follow a moving subject | `track_object` | A document-level track others `bind_to_track` | | The frame is the wrong aspect ratio | `expand_frame` | An inactive video candidate | | The footage is too soft or too small | `upscale_video` | An inactive video candidate | | The shot has no sound | `video_to_audio` | An aligned inactive audio candidate | | The recorded voice is wrong | `recorded_voice_replacement` | An inactive audio candidate | | The mouth does not match the new audio | `lip_sync` | An inactive video candidate | | A title or shape should move with the music | `bake_audio_animation` | A `custom` animation on the target clip | Every tool above except `bake_audio_animation` takes `provider` and `model` from `find_model`. There is no default: an unset model fails the call rather than spending on a model nobody chose. ## Nothing is applied until it is accepted `expand_frame`, `upscale_video`, `video_to_audio`, `recorded_voice_replacement` and `lip_sync` all return an **inactive candidate** with its provenance. None of them changes the accepted take, the sequence dimensions, or the existing framing. Applying the result is a separate, explicit step: 1. `inspect_video_production_candidates {candidates, preview?}` refreshes their state and optionally builds a Preview take or draft from an explicit destination-to-candidate `selection`. Inspection writes nothing. 2. `accept_video_production_candidates {action, batch_id, document_id, project_id, candidates, selection, targets}` applies one atomic manifest. `action` is `use_take` or `use_draft`. Each entry in `targets` carries `expected_target_revision`, which the server rechecks before the apply, so a document that moved underneath is refused rather than overwritten. Report the candidate ids and what each one is, then let the user choose. Never describe a candidate as applied before an `accept` returned. ## Generating a new take for a slot A slot that needs fresh footage rather than a repair goes through the reviewed production path, which covers all three document kinds: `timeline_clip`, `storyboard_shot` and `script_line`. `prepare_video_production` → `submit_video_production` → inspect → accept. `prepare` is non-mutating. It resolves linked script speech, verifies owner, project, target, references, capability route and timing, then freezes one to three stable candidate identities **before** any provider spend. It requires `review: {status: "reviewed", plan_fingerprint}` — the fingerprint of every authoring input the reviewed plan read. The `route` is `reference_to_video`, `text_to_video`, or `audio_driven_performance`, and on-camera speech (`speech_mode: "on_camera"`) requires `audio_driven_performance`. `submit` re-runs preflight and authorization and recovers an existing request id instead of resubmitting it, so a retry after a dropped connection does not pay twice. ## Per-tool notes **`isolate_subject`** sends the clip's *whole* source asset and reads the mask back at the clip's own source time, which is what lets a later trim, split or speed change keep the cutout aligned. A clip with a current cutout is handed it back for free unless `regenerate: true`, and a failed regenerate leaves the working matte in place. Tune the result with the free `set_generated_matte` op (invert, strength, feather, or an earlier version). Use `set_matte` instead for a keyhole authored from another clip. **`track_object`** needs `initial_region` normalized 0..1 over the source frame plus `start_ms` and `end_ms` in absolute source milliseconds. The initial box sits at `start_ms` for `forward` and `both`, at `end_ms` for `backward`. Box tracks only in this build. A new run is refused before saving or spending when the provider does not implement `track_object`. **`lip_sync`** consumes an **accepted** `recorded_voice_replacement` candidate: `replacement_audio {asset_id, status, provenance}`. Run the voice replacement, accept it, then sync. **`bake_audio_animation`** measures a clip's audio, with an envelope follower or one pulse per onset in `mode: "beats"`, and writes a source-anchored `custom` animation on another clip's property, commonly `scale`. The write is additive: a hand-authored `pop` or `slide` on the same channel composes with it rather than being replaced. A re-bake replaces only the curve it produced before. **`source_asset_id`** on the native media tools is an optional pin captured before the call. A mismatch is stale and the call is refused. Pass it when anything else may be editing the document. ## Check the result - `validate_timeline {timeline_id}` after every edit pass. - `preview_timeline_frame {timeline_id, ...}` composites one or more timecodes and returns a per-layer report. It is the cheap way to see a matte, a bake or an accepted take without rendering. A `model3d` layer needs headless Chromium: with none on the host the layer is left out and reported as a `model3d_unavailable` degradation, never as an empty clip. - `compare_timeline_frames` puts before and after side by side. - `render_timeline` is the paid, final pass. Do not render to check an edit. - Snapshot before a destructive pass with `create_timeline_version`. `restore_timeline_version` snapshots the pre-restore state first and re-validates against today's schema, so an old document may fail what it used to pass. ## Verify from a shell These are the `nodetool` CLI. From a NodeTool checkout the same commands run as `npm run dev:nodetool -- `. ```bash nodetool timeline validate --json nodetool timeline debug --interact '[{"tool":"add_track","input":{"type":"audio","name":"Music"}}]' nodetool timeline versions list --save-type manual ``` Rendering, playback, decode and generation are not simulated by the harness. The report says so under `notSimulated`. ## Reference - The `storyboard-core` system skill, § Timeline of its tool contract — `load_skill` it for the board-to-timeline calls. In a NodeTool checkout, these repository sources go further: - `docs/harnesses.md` § nodetool timeline validate / debug - `docs/harnesses.md` § Generated mattes - `docs/harnesses.md` § Audio-driven timeline motion