Clips (Long-form → Vertical)
Turn one long horizontal recording into a series of short, independently-editable vertical (9:16) clip projects — each pre-reframed and ready for the overlays pass.
Clips (Long-form → Vertical)
The clips workflow takes one long horizontal video — a podcast, talk, stream, or interview — and fans it out into N independently-editable vertical (9:16) clip projects. Each output project is a self-contained moment, pre-reframed for portrait, with its own workspace ready for the per-clip overlays pass.
Unlike every other Montaj workflow, clips does not produce a finished video. It produces N new projects. Think of it as the upstream pass that prepares material for per-clip editing, rather than the editing workflow itself.
Use clips when your source is long-form and you want multiple short clips from it. Use overlays or short_captions when you already have individual clips and want to edit each one top to bottom.
Creating a Clips Project
The clips workflow requires a source video (requires_clips: true). Do not use --canvas — the source file is required.
CLI
montaj run --workflow clips --prompt "find the 5 best moments, zoom framing" /path/to/recording.mp4The workflow runs three steps on the source in order: probe → transcribe → find_clips. The transcribe step uses model medium.en. The source pass only transcribes — it does not clean or trim the source video.
How the Agent Works
find_clips is an agent-authored task (not a CLI executable). After the source steps complete, the agent:
- Reads the transcript and
probeoutput. - Identifies N self-contained moments — clean entry and exit, no overlap between clips.
- Picks a vertical framing mode per clip (see Vertical Reframing Modes below).
- Creates one child project per clip by calling
project/init.pydirectly as a subprocess (the HTTP/api/runendpoint does not expose the required flags). Key flags:--symlink-clips(stages the source as a symlink, not a copy — sources are large),--derived-from <source_project_id>,--normalize lazy, and--resolution 1080x1920. - Sets
inPoint/outPointandsourceCropon each child'stracks[0][0]. - Runs
normalize_windowper clip window and setsnormalizedSrcon each clip item. - Relocates the source file into a shared store at
~/Montaj/.sources/<id>/, repoints each child's symlinkedsrcthere, then deletes the source project. Only the N clip projects remain when the workflow finishes. - Asks whether to continue with the per-clip
overlayspass now, or hand off — emitting a ready-to-paste prompt per clip if handing off.
Vertical Reframing Modes
| Mode | Description | Best for |
|---|---|---|
zoom | Centered 9:16 crop filling the full frame | Single speaker, talking-head, tight framing |
thirds | Source floated in the top portion of the 9:16 canvas over a background fill | Demos, wide shots, content where spatial context matters |
mix | Cropped source at ~50% scale in the top half, bottom open for captions/overlays | When you want a visible caption zone below the video |
The agent picks a mode per clip based on the content and the editing prompt. You can override in the per-clip overlays pass.
The sourceCrop Primitive
sourceCrop is a field on video items in tracks[0]. It defines which rectangle of the source footage to use, expressed as fractions of the source's natural dimensions:
{
"id": "clip-0",
"type": "video",
"src": "/abs/path/to/recording.mp4",
"inPoint": 142.0,
"outPoint": 187.5,
"start": 0.0,
"end": 45.5,
"sourceWidth": 1920,
"sourceHeight": 1080,
"sourceCrop": { "x": 0.3418, "y": 0.0, "w": 0.3164, "h": 1.0 },
"normalizedSrc": "/abs/path/to/project/clip-0_norm.mp4"
}All four keys (x, y, w, h) are required when sourceCrop is present. sourceWidth and sourceHeight (source pixel dimensions from the probe step) are required alongside sourceCrop so the renderer can compute the crop correctly.
For a centered 9:16 crop from a 16:9 1920×1080 source: x ≈ 0.3418, y = 0, w ≈ 0.3164, h = 1.0.
Important (v3.2.0): sourceCrop (which rectangle of the footage to use) is separate from position and zoom. offsetX/offsetY/scale control where the clip sits in the output frame — these are distinct fields. The editor's crop tool writes sourceCrop; dragging the clip on the canvas writes offsetX/offsetY/scale.
Source Storage and derivedFrom
Sources are never copied into each clip project — the agent symlinks them. After fan-out, the source file is relocated to ~/Montaj/.sources/<source_project_id>/ and the source project itself is deleted. Only the N clip projects remain.
Each child project carries a derivedFrom field set to the source project's UUID:
{
"derivedFrom": "a3f8c2d1-..."
}This is a provenance tag — informational only. The source project is gone; derivedFrom records where the clip came from.
Lazy Normalization
Clips projects set settings.normalize: "lazy". This means the full source is not re-encoded at import (source files can be gigabytes). Instead, each clip window is normalized on demand via normalize_window and cached as normalizedSrc — a re-encode covering exactly [inPoint, outPoint] starting at t=0.
Render and preview prefer normalizedSrc over src when present. The engine rebases inPoint/outPoint automatically when using normalizedSrc.
This keeps disk usage flat regardless of how many clips are extracted — each window is encoded once, on first use.
Standard Editing vs. Clips Child Project
| Standard editing project | Clips child project | |
|---|---|---|
| Source storage | In project directory | Symlinked to ~/Montaj/.sources/<id>/ |
sourceCrop | Absent (unless manually set) | Always set (by find_clips) |
normalizedSrc | Optional | Always set (by normalize_window) |
derivedFrom | Absent | Source project UUID |
settings.normalize | "eager" (default) | "lazy" |
| Source project | N/A | Deleted after fan-out |