project.json
The whole edit in one file: canvas, markers, tracks, media and clips.
{
"name": "My Edit",
"width": 1280, "height": 720, "fps": 30, // composition canvas + timeline/export rate — sole FPS source (UI: FPS select)
"exportFrame": { "x": 438, "y": 0, "w": 404, "h": 720 }, // optional delivery crop (even w/h for H.264)
// ^ omit = export the full canvas. Preview dims outside this rect; Fast export crops
// JPEGs to w×h. Clip x/y/scale stay relative to the composition center, not the frame.
// Frame size is always even (yuv420p / libx264); 9:16 on 1280×720 → 404×720, not 405.
"background": "#000000", // canvas color behind all clips (optional)
"revision": 7, // bump on every write!
"panSchema": 1, // 1 = pan-aware; omit only on pre-pan projects (UI migrates once)
"markers": [ { "t": 2.5 }, { "t": 5.0, "label": "drop", "color": "red" } ],
// ^ beat/cue markers: diamonds on the ruler (label shown beside them), snap
// targets for clip edges. color: gold (default) · red · orange · green ·
// cyan · blue · purple · pink. Kept sorted by t; the UI rounds t to 1 ms.
"inPoint": 10.023, // IN work-area marker (Limit playback + optional export range)
"outPoint": 21.500, // OUT work-area marker; omit = timeline end
"folders": [
// Project-bin virtual folders (tree). Media reference them via folderId.
{ "id": "f_broll", "name": "B-roll", "parentId": null, "open": true }
],
"disabledTracks": [ "A2" ],
// ^ optional — track ids (e.g. V4 V3 V2 V1 A1 A2 A3) omitted from preview/export when listed
"lockedTracks": [ "A3" ],
// ^ optional — locked lanes: nothing on them may be edited or moved (see Semantics)
"untargetedTracks": [ "V2" ],
// ^ optional — lanes edits skip (split, insert, ripple, close gap…); default: all targeted
"encodeProfile": "hq", // optional — fast-export profile id (see encoding-profiles.json)
"tracks": [ // optional — timeline lanes (default V3…V1 + A1…A4)
{ "id": "V3", "kind": "video" }, // video: higher number drawn on top (V1 under V2 under V3…)
{ "id": "V2", "kind": "video" },
{ "id": "V1", "kind": "video" },
{ "id": "A1", "kind": "audio" }, // audio: A1…An top→bottom; +V/+A in the UI appends Vn+1 / An+1
{ "id": "A2", "kind": "audio" },
{ "id": "A3", "kind": "audio" },
{ "id": "A4", "kind": "audio" }
],
"media": [
{ "id": "m_abc", "name": "intro.mp4", "kind": "video", // video|audio|image|svg
"src": "/media/intro.mp4", // path under ./media or ./library (never a raw https:// URL)
"duration": 12.4, "width": 1920, "height": 1080,
"folderId": null } // optional: id of a folders[] entry
],
"clips": [
{
"id": "c_xyz", // unique string
"mediaId": "m_abc", // null for kind:"text" and kind:"adjust"
"kind": "video", // video | audio | image | svg | text | adjust
"track": "V1", // Vn…V1 (video) | A1…An (audio); extras via UI +V/+A or tracks[]
"start": 0, // timeline position, seconds
"in": 2.5, // offset into source media, seconds (0 for image/svg/text)
"duration": 5, // clip length on timeline, seconds
"name": "intro",
"linkGroup": "lg_abc", // OPTIONAL — AV link: video + per-channel audio stems share one id
"locked": true, // OPTIONAL — locked by the user: leave it alone (MCP patch refuses edits)
"disabled": true, // OPTIONAL — stays on the timeline, hidden from preview / audio / export
"unlinked": true, // OPTIONAL — the user unlinked this clip; never auto-relinked on load
"props": { /* all optional — see the props reference below */ },
// OPTIONAL — keyframe animation. Times are seconds RELATIVE TO CLIP START.
// "ease" sits on the DESTINATION keyframe of each segment:
// linear | ease-in | ease-out | ease-in-out (default ease-in-out).
"keyframes": {
"scale": [ { "t": 0, "v": 1 }, { "t": 5, "v": 1.25, "ease": "linear" } ],
"opacity": [ { "t": 0, "v": 0 }, { "t": 0.8, "v": 1 } ]
},
// OPTIONAL — transitions at the clip's head/tail.
"transitionIn": { "type": "fade", "duration": 0.8 },
"transitionOut": { "type": "slide-left", "duration": 0.6 }
}
]
}