Storyboard reference
The storyboard.json format: segments, holds, zooms, captions, highlights, narration, cards, overlays, music, extends, theme and output, with a full example.
A storyboard is a JSON file that describes the edit. The AI app writes it for you, but you can read it, change it and render it yourself. screenpilot schema (or the storyboard_schema tool) prints the authoritative JSON Schema. This page explains each field.
The three rules#
- Times are source seconds. They count from the start of the raw recording, never from the rendered video. Cuts and speed changes do not move them. Only
screenpilot checkand thecheck_outputtool use output seconds. - Positions are normalized 0..1.
xruns from the left edge andyfrom the top edge of the recording. Read them from a frame withframes --grid. - Paths are relative to the storyboard file. This applies to
source,events,extends,music.srcandoverlays[].src.
Every field except source is optional. Run screenpilot validate storyboard.json after each edit. It reports schema errors and warnings: overlapping segments, items past the end of the source, views that name an unknown screen, and callouts without x and y. It also gives style advice: see Style warnings.
Top-level fields#
| Field | Default | What it is |
|---|---|---|
version | 1 | Format version. Always 1. |
extends | none | A base storyboard to take the look from. See Share a look with extends. |
source | required | Path to the raw recording. |
events | none | events.jsonl from record. It enables click ripples. |
keepSourceAudio | false | Keep the audio of the recording. Segments can then set volume or mute. |
output | 1920x1080 at 30 fps | Size and frame rate. See Output and voice. |
theme | defaults | The look. See Theme. |
voice | defaults | The narration voice. See Output and voice. |
segments | [] | Parts of the source to keep. |
holds | [] | Freeze frames. |
zooms | [] | Camera zooms. |
screens, views | [] | Multi-monitor recordings. See Screens and views. |
captions | [] | On-screen text. |
highlights | [] | Boxes around parts of the screen. |
narration | [] | Voiceover lines. |
intro, outro | none | Title cards before and after the video. |
cards | [] | Chapter cards in the middle of the video. See Chapter cards. |
overlays | [] | Images, video and logos on top of the frame. See Overlays. |
music | none | Background music, optionally ducked under the voice. |
Segments#
segments lists the parts of the source to keep, in order. Anything between segments is cut. An empty list keeps the whole video. Segments must not overlap.
| Field | Default | Meaning |
|---|---|---|
in | required | Start, in source seconds. |
out | required | End. Must be greater than in. |
speed | 1 | Playback speed, 0.25 to 16. Use 4 to 8 to fast-forward loading and waiting. |
volume | 1 | Level of this segment's source audio, 0 to 4. Needs keepSourceAudio. |
mute | false | Silence this segment's source audio. Same as volume 0. |
A narration line placed inside a cut region is dropped, with a warning. volume and mute only matter when the source audio is kept: without keepSourceAudio: true and a recording that has sound, the render warns that they have no effect.
Holds#
holds freeze the video on one frame, to give a caption or a line room.
| Field | Meaning |
|---|---|
at | Source time to freeze on. |
dur | Freeze length in seconds. |
You rarely need to write holds. When a narration line is longer than its slot, the render adds a hold automatically so lines never overlap. The result lists them as autoHolds.
Zooms#
| Field | Default | Meaning |
|---|---|---|
at | required | Source time at which the zoom is fully in. |
dur | required | How long to stay zoomed, in seconds. |
x, y | required | Focus point, 0..1. |
scale | 1.8 | Zoom factor, 1 to 4. |
Captions#
| Field | Default | Meaning |
|---|---|---|
at | required | Source time the caption appears. |
dur | 3 | Seconds on screen. |
text | required | The text. Use \n for a second line, for example a bilingual caption. |
position | bottom | bottom, top or center. |
style | pill | pill, title or callout. |
x, y | none | Anchor point for callout, 0..1. |
motion | theme default | How this caption moves in and out. See Motion. |
Caption styles#
pill: a subtitle-like box. Use it for most captions.title: big text for a chapter-like moment.callout: a bubble that points atx,y. Always give both. Without them the bubble points at the center andvalidatewarns you.
Highlights#
A highlight draws a box around part of the screen.
| Field | Default | Meaning |
|---|---|---|
at | required | Source time it appears. |
dur | 2 | Seconds on screen. |
x, y | required | Top-left corner, 0..1. |
w, h | required | Width and height, 0..1. |
label | none | Optional text on the box. |
Narration#
Each line is spoken by the storyboard's voice and mixed into the video. By default that is an Edge voice; see Voices for local models and your own programs. Narration never overlaps: when a line overruns its slot, the video freezes to make room.
| Field | Default | Meaning |
|---|---|---|
at | required | Source time the line starts. |
text | required | What the voice says. |
voice | voice.name | Voice for this line only, for example ar-EG-ShakirNeural. It replaces voice.name and keeps voice.provider, so it must be a voice of the same provider. Use it to mix languages. |
caption | voice.subtitles | Also show this line as a subtitle (true) or not (false). |
List the voices with screenpilot voices --lang ar-EG. Examples: ar-EG-SalmaNeural, ar-EG-ShakirNeural, en-US-AndrewNeural, en-US-AriaNeural.
Intro and outro#
intro and outro are title cards before and after the video. Both take the same fields.
| Field | Default | Meaning |
|---|---|---|
title | required | Main text. |
subtitle | none | Second line. |
dur | 3 | Seconds on screen. |
Chapter cards#
cards are title cards in the middle of the video, for a new chapter. Each one freezes the picture at at and shows a full card with a fade in and out, then the video carries on from the same frame.
| Field | Default | Meaning |
|---|---|---|
at | required | Source time the video freezes and the card shows. |
dur | 2.5 | Seconds the card stays. The video is frozen meanwhile. |
title | required | Main text. |
subtitle | none | Second line. |
A card whose at is in a cut region is dropped, and validate warns. Cards use the same look as the intro and outro (theme.motion.cards and the .card CSS).
Overlays#
overlays put an image or a video on top of the output frame: a logo, a watermark, a picture-in-picture clip. Unlike zooms and highlights, the position is a fraction of the output frame, not of the recording, so a logo stays in the same corner however the camera moves.
| Field | Default | Meaning |
|---|---|---|
src | required | An image or a video, relative to the storyboard. |
x, y | required | Left and top edge, 0..1 of the output frame. |
w, h | required | Width and height, 0..1 of the output frame. Must be above 0. |
at | none | Source time it appears. |
dur | none | Seconds it stays. With at and no dur, it stays until the end. With neither at nor dur, it shows for the whole video. |
fit | contain | contain shows the whole image inside the box. cover fills the box and trims the overflow. |
opacity | 1 | 0 to 1. |
radius | none | Corner radius in output pixels. |
shadow | none | Draw a shadow under it. |
motion | fade | Entrance and exit. See Motion. words, lines and wipe fall back to fade. |
mute | true | For a video: false plays its sound too. |
loop | false | For a video: repeat it until the overlay ends. |
"overlays": [
{ "src": "assets/logo.png", "x": 0.88, "y": 0.04, "w": 0.08, "h": 0.08, "opacity": 0.8 },
{ "src": "assets/webcam.mp4", "at": 5, "dur": 12, "x": 0.72, "y": 0.66, "w": 0.24, "h": 0.28, "fit": "cover", "radius": 16, "shadow": true }
]
Music#
| Field | Default | Meaning |
|---|---|---|
src | required | Path to an audio file, relative to the storyboard. |
volume | 0.12 | 0 to 1. Keep it low so it does not cover the voice. |
duck | none | Lowers the music while someone speaks. See below. |
Ducking makes the music quieter under each narration line and brings it back after. Give duck any of these fields, or an empty object {} for the defaults:
| Field | Default | Meaning |
|---|---|---|
amount | 0.6 | How much quieter the music gets, 0 to 1. 0.6 means 60% lower. |
attack | 0.2 | Seconds the music takes to go down, finishing as the line starts. 0 to 5. |
release | 0.5 | Seconds the music takes to come back after the line. 0 to 5. |
Share a look with extends#
extends points at a base storyboard, relative to this file. Use it when several videos should look and sound the same: each one extends the same base and adds its own recording. It is how a feature clip takes the look of the demo it joins in a sequence.
- Five fields are inherited:
theme,voice,output,musicandkeepSourceAudio. Everything else (source, segments, zooms, captions, narration, intro, outro and the rest) is never inherited. - The child wins. Objects merge key by key, so a child that sets only
theme.accentkeeps the base's other theme fields. Arrays replace instead of merging, for exampletheme.fonts. - Chains work. The base can extend another storyboard. A cycle is an error that lists the chain.
- Paths stay valid.
music.srcfrom the base is rewritten to point at the same file from the child's folder.
{
"extends": "../demo.json",
"source": "rec.mp4",
"narration": [{ "at": 1, "text": "Invoices can now repeat on a schedule." }]
}
Screens and views#
For a recording of several monitors, screens says where each monitor sits in the recording and views says which part the camera shows from each time on. init --suggest fills both for you.
| Item | Fields |
|---|---|
screens[] | name (monitor name, for example eDP-1), x, y, w, h (0..1 of the source; w must be above 0) |
views[] | at, then exactly one of screen (a screens[].name, or "all") or rect (x, y, w, h, any region); optional label and transition (pan by default, or cut) |
Output and voice#
| Object | Fields and defaults |
|---|---|
output | width 1920, height 1080, fps 30 (1 to 120) |
voice | provider edge (or local or command), name en-US-AndrewNeural, rate +0% (for example +10% or -5%), subtitles true (show narration lines as subtitles), gap 0.3 (minimum silence between lines; the video freezes to make room), volume 1 (0 to 4) |
voice.provider | What voice.name means |
|---|---|
edge (default) | An Edge voice, for example ar-EG-SalmaNeural. Needs the internet. |
local | A model on this computer: voicetut, voicetut:<Voice> such as voicetut:Sarah, or namaa. rate becomes a speed factor for voicetut; namaa ignores it. |
command | The key of a voice you defined in voices.json. |
A narration line's own voice works inside the provider. For example, with "provider": "local", one line can use namaa and another voicetut:Sarah. Setup, limits and examples are on Voices.
Pick the output size from where the video will be shown: 1920x1080 for YouTube, a README or slides; 1080x1920 for Reels, TikTok and Shorts; 1080x1080 for feeds.
Theme#
The theme sets the look of the whole video. There is no house style: choose it for the audience, and take colors and type from the product you show.
| Field | Default | Meaning |
|---|---|---|
background | indigo-to-blue gradient | Any CSS background: a color, a gradient or an image. |
padding | 0.05 | Space around the recording, as a fraction of the output width (0 to 0.2). |
radius | 16 | Corner radius of the recording, 0 to 60. |
shadow | true | Shadow under the recording. |
accent | #facc15 | Accent color. |
font | Inter, Cairo, system-ui | CSS font family. |
captionColor | #ffffff | Caption text color. |
captionBackground | dark, translucent | Caption box color. |
clickRipples | true | Draw a ripple on every recorded click. Needs events. |
fonts | [] | Extra Google Fonts to load, as css2 family specs such as "Tajawal:wght@400;700" or "Space Grotesk:wght@500;700". Then use them in font or css. |
css | empty | Your own CSS, applied after the built-in styles. |
motion | {} | Default text motion: captions (captions and subtitles) and cards (intro and outro). |
Custom CSS#
The elements you can style with theme.css:
#root: the whole frame and its background.#frame: the box that holds the recording..card,.card-inner,.card h1,.card p: the intro and outro cards..text: captions. Variants are.text.pill,.text.subtitleand.text.title. The visible box is.text.<style> > span, and.text .lnis one line of a multi-line caption..pos-bottom,.pos-top,.pos-center: caption positions..hland.hl span: highlights..callout span: callout bubbles..ripple: click ripples.
Motion#
Motion controls how text enters and leaves. Set the default in theme.motion.captions and theme.motion.cards, and override it with motion on one caption. Every field is optional.
| Field | Meaning |
|---|---|
in | Entrance preset. |
out | Exit preset. words and lines exit like fade. |
duration | Seconds per entrance, 0 to 3. Default 0.3. |
ease | Any GSAP ease, for example "power3.out", "back.out(1.7)", "expo.out" or "elastic.out(1,0.5)". |
stagger | Seconds between words or lines for words and lines, 0 to 1. Default 0.08. |
Presets: none, fade, rise, drop, slide-left, slide-right, pop, zoom, blur, wipe, words, lines. wipe uncovers text in the reading direction. words reveals one word at a time and lines one line at a time; both work in any script, Arabic included.
fade and rise. Energetic social clips suit pop or words with a bouncy ease. Titles suit wipe or words.A complete example#
This storyboard keeps one recording, speeds up a loading stretch, zooms on two clicks, adds three captions and a highlight, and speaks three lines (the last one in Arabic). The paths are relative to the storyboard file. It passes screenpilot validate; the warning you see without the files is only that the source video is missing.
{
"version": 1,
"source": "sessions/2026-10-27_14-02/video.mkv",
"events": "sessions/2026-10-27_14-02/events.jsonl",
"output": { "width": 1920, "height": 1080, "fps": 30 },
"voice": { "name": "en-US-AndrewNeural", "rate": "+0%", "subtitles": false, "gap": 0.3 },
"theme": {
"background": "linear-gradient(135deg, #0f172a 0%, #1d4ed8 100%)",
"padding": 0.05,
"radius": 16,
"accent": "#facc15",
"font": "'Space Grotesk', 'Cairo', sans-serif",
"fonts": ["Space Grotesk:wght@500;700"],
"css": ".text.pill > span { letter-spacing: 0.01em; }",
"motion": {
"captions": { "in": "rise", "out": "fade", "duration": 0.35, "ease": "power3.out" },
"cards": { "in": "words", "stagger": 0.08 }
}
},
"intro": { "title": "Acme Invoices", "subtitle": "Send an invoice in 20 seconds", "dur": 3 },
"outro": { "title": "Try it today", "subtitle": "acme.example", "dur": 3 },
"segments": [
{ "in": 0, "out": 6, "speed": 1 },
{ "in": 6, "out": 19, "speed": 6 },
{ "in": 19, "out": 41, "speed": 1 }
],
"holds": [{ "at": 30.5, "dur": 1.5 }],
"zooms": [
{ "at": 2.5, "dur": 3, "x": 0.18, "y": 0.22, "scale": 2 },
{ "at": 24, "dur": 4, "x": 0.82, "y": 0.9, "scale": 1.8 }
],
"captions": [
{ "at": 1, "dur": 3, "text": "Start from the dashboard", "position": "bottom", "style": "pill" },
{ "at": 22, "dur": 3, "text": "One click to send", "style": "callout", "x": 0.82, "y": 0.9 },
{ "at": 36, "dur": 3, "text": "Done", "position": "center", "style": "title", "motion": { "in": "pop", "ease": "back.out(1.7)" } }
],
"highlights": [{ "at": 31, "dur": 2.5, "x": 0.3, "y": 0.4, "w": 0.3, "h": 0.12, "label": "Total" }],
"narration": [
{ "at": 1, "text": "We start on the dashboard." },
{ "at": 22, "text": "One click and the invoice is sent." },
{ "at": 36, "text": "هذا كل شيء.", "voice": "ar-EG-SalmaNeural", "caption": true }
],
"music": { "src": "assets/bgm.mp3", "volume": 0.12 }
}
caption: true, while voice.subtitles is off for the others. The click near 24 s gets a zoom and a callout; the highlight sits on a different moment, so no single click carries every effect.Style warnings#
validate (and the storyboard_validate tool) also warns when the storyboard breaks the style guidance below. These are warnings only: they never block a render. Each message names the item, for example captions[2].
| Warning | Limit |
|---|---|
| Narration too long | More than 12 words in a line. |
| Narration too fast | More than about 3 words per second before the next line. The render would freeze the video to fit. |
| Caption too long | More than 6 words. |
| Captions overlap | Two captions at the same position at the same time. Callouts are left out of this check. |
| Zooms overlap | Two zooms at the same time. |
| Zoom scale | Outside 1.4 to 2.5. |
| Stacked effects | A zoom, a highlight and a callout on the same point within one second. |
| Segment speed | Above 8x. Cut it instead. |
| Video length | Output over 90 seconds or under 15 seconds. |
| Cards in cut regions | A chapter card whose at is cut away. |
Style guidance#
- Narration: one sentence, under about 12 words, one idea. Say what the viewer sees or why it matters, not which button to press. Keep lines short: a long line freezes the video.
- Zooms: zoom on what is about to be clicked. Start the zoom about 0.5 seconds before the click, hold it for 2 to 4 seconds, and use a scale of 1.6 to 2.2. Take
xandyfrom a--gridframe. Return to the full view between zooms, and do not zoom when the whole screen matters. - Captions: 2 to 6 words, one idea each. Use
calloutonly to point at one small element,titlefor chapter-like beats andpillfor the rest. Do not repeat in a caption what the narration already says. - Idle time: cut it, or speed it up 4 to 8 times. Loading, waiting and mistakes do not belong at normal speed.
- Pace: start a narration line together with the action it describes, and leave about 0.3 seconds between lines. Aim for 30 to 90 seconds in total.
- Do not stack effects: a zoom, a highlight and a callout on one click is too much. Choose the one that points best.
- Bilingual captions: use one caption with two lines (
"English\nعربي"). Avoid a lone Arabic waw between Latin words; use an Arabic comma instead.
-q draft, look at it with check, then add zooms and captions. Change one kind of thing at a time.