CRUD for video timelines, plus the render endpoint.
List projects on a board
GET /api/moodboards/{id}/video-projects
Returns an array of project summaries (id, name, last_modified, duration).
Create a project
POST /api/moodboards/{id}/video-projects
{
"name": "Hero reel"
}
Returns the new project’s full state — empty timeline, default settings.
Get a project
GET /api/moodboards/{id}/video-projects/{pid}
Returns the project row with its full editor state in data (stored as an opaque JSON blob):
{
"id": "…",
"name": "…",
"data": {
"version": 3,
"videoSize": { "w": 1920, "h": 1080 },
"motionBlurEnabled": false,
"motionBlurShutter": 180,
"clips": [
{ "id": "…", "source": "REMOTE", "mediaType": "video", "url": "/media/…", "name": "…", "duration": 5 }
],
"channels": [
{
"id": "…",
"name": "Channel 1",
"visible": true,
"items": [
{
"id": "…",
"clipId": "…",
"trimStart": 0,
"trimEnd": 5,
"zoom": 1, "panX": 0, "panY": 0,
"opacity": 100,
"propertyKeyframes": {
"scale": [ { "position": 0, "value": "1", "easing": "Smooth" }, { "position": 100, "value": "1.25", "easing": "Linear" } ]
},
"keyframeTransformOrigin": "center"
}
]
}
],
"activeChannelId": "…",
"currentTime": 0
}
}
Keyframes are stored as per-property tracks: propertyKeyframes maps each property to an independent {position, value, easing} key list, plus an item-level keyframeTransformOrigin. See the Keyframe Builder doc and skills/greenlight-dash/references/video-projects.md for the full schema; the frontend’s sanitizeTimelineItem (frontend/src/components/videoEditor/VideoEditor.jsx) is canonical for the item fields. (Legacy projects may still carry a flat keyframes: [] array from the retired all-properties-per-key mode; it is no longer read.)
Update a project
PUT /api/moodboards/{id}/video-projects/{pid}
{ … full project state, replacing the current … }
The frontend sends the entire project state on each save. The backend stores it as a JSON blob.
Render
POST /api/moodboards/{id}/video-projects/{pid}/render
{
"format": "mp4",
"codec": "h264",
"crf": 23,
"size_preset": "1080p",
"fps": 60
}
Starts the render job. Returns immediately with a job id:
{ "job_id": "…", "status": "queued" }
Poll status:
GET /api/moodboards/{id}/video-projects/{pid}/render/{job_id}
Returns:
{
"status": "running" | "complete" | "error",
"progress": 0.42,
"eta_seconds": 30,
"output_url": "/media/…" // when complete
}
Delete a project
DELETE /api/moodboards/{id}/video-projects/{pid}
Where to go next
Camera preset endpoints
Saved camera moves are global JSON files under the media folder’s camera-presets/ (a separate directory from video-presets/, so names can’t collide).
GET /api/camera-presets → { "presets": [{ "id", "name", "description", "duration", "keyframes", "updated_at" }] }
POST /api/camera-presets { "name", "duration?", "keyframes", "base?" }
DELETE /api/camera-presets/{preset_id}
A preset’s keyframes are whole-frame snapshots at position 0–100 % of its own duration:
{ "position": 100, "easing": "AE Smooth", "properties": { "camZoom": "1.28" } }
The optional base object holds the preset’s un-keyframed camera settings (the depth-of-field rig), e.g. { "camDofEnabled": 1, "camAperture": 40 }. A preset needs either keyframes or base.
See Camera Layer for the property registry and skills/greenlight-dash/references/video-projects.md for how to apply one to a project.
Leave a Reply