| name | opencut-project |
| description | Author, inspect, transform, and validate an OpenCut (classic) project as DATA — the TProject timeline JSON (scenes → tracks → elements, integer-tick MediaTime, canvas/fps settings). Use when the task is "build an OpenCut project/timeline programmatically", "generate an .opencut timeline", "edit OpenCut project JSON", "OpenCut timeline schema", "convert a shot list into an OpenCut timeline", "validate an OpenCut project file", "오픈컷 프로젝트/타임라인 JSON 만들어/편집/검증", "타임라인을 데이터로 생성". This is the gap the video fleet does not cover — it manipulates OpenCut's own project graph, it does not render video. Do NOT use to render/encode/export actual video files (use ffmpeg-toolkit / hyperframes / video-producer), to develop the OpenCut rewrite repo (use opencut-dev), or for non-OpenCut editors (Premiere/FCPXML/CapCut). |
OpenCut Project (timeline-as-data)
Treat an OpenCut classic timeline as a serializable graph you can generate and
edit outside the editor. The schema is real and stable-ish (apps/web/src/project/types.ts,
.../timeline/types.ts); scripts here own the format deterministically.
Honesty gate (read before promising anything)
- Time is integer ticks, not seconds.
MediaTime = integer count of ticks,
TICKS_PER_SECOND = 120000 (defined in rust/crates/time/src/media_time.rs).
Never write a fractional or "seconds" value into a timing field — the editor
rejects non-integers. Convert with round(seconds * 120000). 120000 is highly
composite (÷ 24/25/30/60/120 all exact), so frame-aligned times stay integer.
- Media binary is NOT in the JSON. Elements reference media by
mediaId
(uploads, stored in a separate OPFS/IndexedDB mediaDb) or sourceUrl
(library/remote). A generated project pointing at mediaIds the target
install doesn't have will show missing media. sourceType: "library" +
sourceUrl is the only self-contained reference.
- No public headless importer exists (yet). OpenCut persists projects to
IndexedDB (
projectsDb) as a SerializedProject (dates → ISO strings). To
load a generated project you must inject it into that store via the running
web app (devtools / a small loader) — there is no CLI import today. Say this
plainly; don't imply a generated file "just opens".
- Classic is being replaced. Re-verify the schema/
version against the
target build before shipping a generated project.
Step 1: Detect mode + tooling
command -v python3 || echo "NO_PYTHON"
ls scripts/build_project.py scripts/validate_project.py 2>/dev/null
Decide the mode from the request:
| User wants | Mode | Go to |
|---|
| Turn a shot/clip list into a timeline | Build | Step 2 |
| Check a project JSON is well-formed | Validate | Step 3 |
| Change an existing project (retime, add track/text) | Transform | Step 3 then edit then Step 3 |
| Understand the format | Explain | references/schema.md |
Step 2: Build a project from a spec (Build mode)
Write a small JSON spec (see references/spec-format.md) — seconds allowed, the
script converts to ticks — then run:
python3 scripts/build_project.py spec.json --out project.json
Defaults (override in spec): fps 30, canvasSize 1920x1080, background {type:"color", color:"#000000"}, version 1, one main video track + scenes as
given. The builder assigns ids, converts every seconds/atSeconds/durationSeconds
field to integer ticks, and emits a SerializedProject (ISO-string dates).
Step 3: Validate (always run before delivering any project JSON)
python3 scripts/validate_project.py project.json
Deterministic gate — exit 0 = valid, exit 1 = errors printed. It checks: required
top-level keys (metadata/scenes/currentSceneId/settings/version), exactly one
isMain scene, scene tracks shape (overlay[]/main/audio[]), every timing field
is a non-negative integer (tick invariant), element↔track type compatibility,
and that each element carries a media reference (mediaId or sourceUrl) where
its type requires one. Do not hand off a project that fails this gate —
regenerate or fix.
Step 4: Deliver
Output template
- Artifact — path to the validated
project.json (+ spec if built).
- Timeline summary — scenes, track count by type, total duration (ticks → seconds).
- Media status — which references are
library (self-contained) vs upload
(needs mediaId present in the target install's mediaDb).
- How to load — the honest path: inject the
SerializedProject into the
app's projectsDb IndexedDB store (no CLI importer exists). Link references/loading.md.
- Validation —
validate_project.py exit status.
Gotchas
Operational failure patterns (distinct from the Honesty gate's schema facts):
- Rounding drift — convert each absolute time from its own
seconds value with a
single round(seconds * 120000); never sum already-rounded ticks across many clips,
or per-element error accumulates and elements land off their intended frame.
isMain scene — validate_project.py fails unless exactly one scene is
isMain. A multi-scene spec that marks zero or two mains is rejected at the gate.
- Type mismatch — a text element on a video/audio track (or vice-versa) fails the
element↔track compatibility check. Keep each element's type aligned to its track.
- Empty
sourceUrl on a library element — passes JSON shape but shows missing
media at load time. A library reference is only self-contained with a real URL.
Reference Files
references/schema.md — full TProject / TScene / TimelineTrack / element field reference + tick math + serialized shape + export options.
references/spec-format.md — the simplified build spec (seconds-based) the builder accepts, with an example.
references/loading.md — how a SerializedProject is persisted and the honest options for loading a generated one.