Start with a recipe that reads like a shot list
A recipe is a plain JSON file. The "schema" line names the format, clips play in the order you list them, and each clip can carry a trim, a camera move, the transition into the next clip, and on-screen text. Add a voiceover, captions, music, and sound effects, and the file describes the whole video. Local file paths in clips[].asset are uploaded for you.
Field names are checked strictly, which works in your favour: a typo fails loudly and names the bad field instead of quietly making the wrong video. Names for transitions, effects, camera moves, caption presets, and voices come from live catalogs the CLI prints, so neither you nor an AI agent ever has to guess.
recipe.json: a 30-second vertical product update
Swap in a voice id from framecompose voices --json, or use "source": "upload" for your own recording.
{
"schema": "framecompose.recipe/v1",
"title": "Product update in 30 seconds",
"aspectRatio": "9:16",
"transitions": { "type": "CROSSFADE", "duration": 0.3 },
"clips": [
{ "asset": "./clips/opening.mp4", "trimEnd": 5, "motion": "ken_burns" },
{ "asset": "./clips/demo.mp4", "trimStart": 2, "trimEnd": 14,
"transition": { "type": "HORIZONTAL_WIPE", "duration": 0.4 } },
{ "asset": "./clips/close.mp4", "trimEnd": 6 }
],
"voiceover": {
"source": "tts",
"voiceId": "<voiceId from framecompose voices>",
"lines": [
{ "clip": 0, "text": "Here is what shipped this week." },
{ "clip": 1, "text": "Captions now stay in sync while you drag scenes around." },
{ "clip": 2, "text": "Try it free and tell us what to build next." }
]
},
"captions": { "mode": "synced", "preset": "hormozi", "maxWords": 3 },
"titles": [{ "text": "What shipped this week", "startSec": 0, "durationSec": 2.5, "position": "top" }],
"music": { "query": "upbeat tech", "volume": 0.35, "duck": true, "fadeOutSec": 2 },
"sfx": [{ "query": "whoosh", "atSec": 4.8, "volume": 0.8 }],
"endCard": { "text": "Made with FrameCompose", "durationSec": 3 }
}