Video Generator Tag Reference

This document describes all available tags, their parameters, and timing behavior.


Table of Contents

  1. Syntax Rules
  2. Container Tags
  3. Media Tags
  4. Style Tags
  5. Effect Tags
  6. Timing System
  7. Examples
  8. The Cinematic Scene Formula
  9. The Plate Format

Syntax Rules

Block Form

[tag param="value"]
  content
[/tag]

Self-Closing Form

[tag param="value"/]

Comments

# This is a comment (shell-style)

Parameter Types

  • Strings: param="value" (quotes required)
  • Numbers: param="1.5" or param="100"
  • Booleans: param="true" or param="false"
  • Time: param="2s" or param="500ms"
  • Percentages: param="50%"

Container Tags

These tags define scope and contain other elements.


[video]

The root container for video content. Defines the visual layer.

Syntax

[video file="path/to/video.mp4" size="content" fit="none"]
  ...children...
[/video]

Parameters

Parameter Type Default Description
file string required Path to video file
id string auto Element ID for time anchoring
from time 0 Start position in the video file
size enum "content" Duration mode (see below)
fit enum "none" How to fit video to duration

Size Modes

Value Behavior
content Video duration = duration of children (TTS, etc.)
natural Video plays its full natural length

Fit Modes

Value Behavior
none No adjustment
trim Cut video to fit duration
loop Loop video to fill duration

Examples

Basic looping video:

[video file="background.mp4" size="content" fit="loop"]
  [tts]This narration determines video length[/tts]
[/video]

Start video from 30 seconds in:

[video file="long_video.mp4" from="30s" fit="loop"]
  [tts]This starts from the 30 second mark and loops from there[/tts]
[/video]

[tts]

Text-to-speech block. Generates audio and optional subtitles.

Syntax

[tts engine="kokoro_tts" voice="af_heart"]
  Text to speak with [blue]styled words[/blue].
[/tts]

Parameters

Parameter Type Default Description
engine string config default TTS engine to use
voice string config default Voice ID
id string auto Element ID for time anchoring
subtitles boolean "true" Show subtitles
sub-position enum "center" Vertical position: top, center, bottom. Keep center for social video: TikTok, Shorts and Reels cover the bottom with captions and buttons, and the top with search and tabs
sub-align enum "center" Horizontal alignment: left, center, right
highlight boolean "true" Highlight current word in subtitles
lang string engine default Narration language: en-US, en-GB, es, fr, it, pt-BR, hi, ja, zh, de, nl, pl, tr, ru, ko, ar, id, vi, sv. Without a voice, a native voice for that language is chosen.
sub-style enum "default" Caption style: default (line with the spoken word highlighted), karaoke (each word fills with colour as it is spoken), pop (one big word at a time that pops in, TikTok style)
sub-highlight-color color red Colour of the spoken word (#RRGGBB); for pop, the word colour
sub-emoji boolean "false" Show a matching emoji above captions that mention a keyword (money πŸ’°, fire πŸ”₯, love ❀️, cat 🐱, …)
sub-mode enum word chunks "line" puts one source line per subtitle (verse)
sub-font-size number 48 Subtitle font size in pixels
sub-weight enum bold "regular" or "bold"
sub-margin-l number 20 Left edge of the text column, in 1920-wide pixels
sub-margin-r number 20 Right inset of the text column
sub-margin-v number 30 Vertical inset (ignored for center positions)
sub-page-lines number 0 >1 makes lines accumulate, then clear together
sub-line-height number 1.55x size Row spacing in paged mode
sub-fade number 0 Fade-in for each line, in milliseconds
sub-page-hold time 0 Beat on a full page before it clears
sub-final-hold time 0 How long the last page stays on screen after the narration ends
font string default font Subtitle font file
instruct string none Delivery direction for mimo_tts, e.g. "Tense and hushed, like a ghost story". Not read aloud. qwen3_tts accepts it too, but in our tests it had no reliable effect
audio-file string none With engine="upload_audio": your own recording to use instead of TTS
subtitle-file string none With engine="upload_audio": an SRT file with the timing for your recording. Without it, the text inside the tag is spread across the recording

Subtitle Position Grid

7 (top-left)      8 (top-center)      9 (top-right)
4 (middle-left)   5 (middle-center)   6 (middle-right)
1 (bottom-left)   2 (bottom-center)   3 (bottom-right)

Subtitle layout

By default subtitles are grouped into roughly three-word chunks, set bold, centred, and near the frame edges, the short-form caption look. The sub-* attributes above retune that without affecting any script that leaves them unset.

Paged subtitles

By default each line replaces the last. sub-page-lines instead lets them accumulate: every line fades in beneath the previous one until the page is full, then the whole block clears at once and the next page starts empty.

[tts ... sub-mode="line" sub-page-lines="6" sub-fade="450" sub-page-hold="0.8s"]

The block is positioned for its full height from the first line, so lines are added beneath one another without the earlier ones shifting up. A final short page centres itself. Six lines at sub-font-size="42" fills the space between the plate's ornament rules; raise sub-page-lines and lower sub-line-height together if you want more.

sub-mode="line" is the important one for verse: instead of chunking by word count it puts one line of your script on screen at a time, so a poem's lineation survives to the video. Combine it with the margins to confine the text to a column:

[tts engine="mimo_tts_voicedesign" voice="..." sub-mode="line"
     sub-position="center" sub-align="right" font="noto-serif"
     sub-font-size="42" sub-weight="regular"
     sub-margin-l="860" sub-margin-r="88" highlight="false"]
  If you can keep your head when all about you
  Are losing theirs and blaming it on you,
[/tts]

That reserves the left 860px of the frame for a portrait and sets the verse in the column beside it. See The Plate Format.

TTS engines

Engine voice is… Use for
kokoro_tts a preset voice ID (af_heart, am_michael, …) the default for most videos
qwen3_tts a preset voice ID non-English and multilingual voices
mimo_tts a preset voice ID expressive English/Chinese with instruct style control
mimo_tts_voicedesign a free-form description of the speaker literary, cinematic, and dramatic narration
Voice design (mimo_tts_voicedesign)

This engine has no preset voices. The voice attribute is a description of the speaker in English, and the model builds that voice. Following Xiaomi's guidance:

  • 1 to 4 sentences. Cover gender and age, voice texture, mood or tone, and pace. Optionally add a role (storyteller, radio host), a speaking style, a scene or an era.
  • Describe what you would hear, not vague qualities: avoid "normal", "ordinary" or "foreign".
  • No contradictions (a "soothing" voice that is also "shouting in rage" comes out erratic).
  • No audio-effect terms such as reverb, echo, EQ or compression.
  • Match the text to the voice. An excited sports commentator description reading a ghost story races through it.
  • Delivery directions in the description ("pauses for a long beat before the final question") have a real but modest effect. Takes of the same description also vary, so listen before you publish.

The house narrator voice, used by every script in poem_video_examples/:

An aristocratic elderly male in his 80s, speaking with a resonant, dark British accent that
carries a subtle, textured gravel. The voice is deep but perfectly clear and legible,
maintaining a slow, dramatic pace as if an elegant, calculating villain is speaking directly
to you.
  • Repeat the full description on every [tts] tag in the script, character for character. A reworded description on one block produces a different voice and the cut is audible.
  • Do not combine it with instruct. The description already carries the delivery.
  • Pair with font="roboto-black" and sub-position="center" for the cinematic look.

Example

[tts engine="kokoro_tts" voice="am_michael" sub-position="center" sub-align="center"]
  Hello world! This text will be spoken and displayed.
[/tts]

[music]

Background music track.

Syntax

[music file="song.mp3" fit="loop"]
  ...children...
[/music]

Parameters

Parameter Type Default Description
file string required Path to audio file
id string auto Element ID for time anchoring
from time 0 Start position in the audio file
volume number 1.0 Volume level (0.0 = silent, 1.0 = normal, 2.0 = 2x)
start anchor none When to start on timeline (see Time Anchors)
end anchor none When to end on timeline
fit enum "loop" How to fit audio: none, trim, loop
duck level none Lower the music by this much while narration plays, e.g. "-12db". It fades down just before speech and back up after.
fade-in time 0 Fade up from silence over this long at the start, e.g. "2s"
fade-out time 0 Fade down to silence over this long, ending where the music ends (the end of what it wraps, or the file's end when self-closing)

Examples

Music that fades in and out around the narration:

[music file="song.mp3" volume="0.4" fade-in="2s" fade-out="3s"]
  [tts]Once upon a time...[/tts]
[/music]

Music that ducks under the narration:

[music file="song.mp3" volume="0.6" duck="-12db"/]

Basic looping music:

[music file="background.mp3" fit="loop"]
  [tts]Music plays throughout this narration[/tts]
  [tts]And continues through this one too[/tts]
[/music]

Skip intro and start from chorus (at 45 seconds):

[music file="song.mp3" from="45s" fit="loop"]
  [tts]Music starts at the chorus and loops from there[/tts]
[/music]

Quiet background music at 30% volume:

[music file="ambient.mp3" volume="0.3" fit="loop"]
  [tts]The music is subtle behind the narration[/tts]
[/music]

[social-post] / [social-comment]

Reddit-style cards. Each card is narrated like a [tts] block, and its text is revealed line by line on screen as it is read. Put them inside a background [video] or [image], one [social-post] followed by any number of [social-comment] tags. This is what the Reddit Stories template produces.

Syntax

[video file="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="loop"]
  [social-post title="AITA for labelling my coworker's lunch?" subtitle="r/AmItheAsshole" author="u/tupperware_tired" pfp="preset:red" engine="kokoro_tts" voice="af_heart"]
    Every Monday I meal prep five lunches and put them in the office fridge.
    Three weeks in a row, my Wednesday lunch vanished.
  [/social-post]
  [social-comment author="u/fridge_justice" pfp="preset:blue" engine="kokoro_tts" voice="am_michael"]
    NTA. You fixed the containers.
  [/social-comment]
[/video]

Parameters

Parameter Tags Default Description
title post none Post title, shown in large text at the top of the card
subtitle post none Subreddit or community name, e.g. r/AskReddit
author both none Username shown on the card
pfp both none Profile picture: a file, or a built-in avatar preset:red, preset:blue, preset:green, preset:yellow, preset:orange, preset:purple, preset:cyan, preset:light_blue
narrate-title post "true" Read the title aloud before the body
engine / voice / instruct both config default Voice for this card, as for [tts]. Each card can have its own voice
subtitles both "false" Also show subtitles (the card text is usually enough)
highlight both "false" Highlight the current word

Notes

  • Line breaks in the card text become line breaks on the card. Each line appears when its first word is spoken.
  • Reddit acronyms (AITA, NTA, YTA, ESH, NAH, TIFU and similar) are spoken in full but shown as written.
  • Each finished card stays on screen for one second before the next one appears.
  • Every card is its own narration block, so multi-voice stories cost the same per character as single-voice ones.

[quiz-card]

A quiz question card with a small label, the question, and up to four option boxes (A–D), drawn over the background like the social cards. The narration inside it normally has subtitles="false", since the card shows the text. A round countdown timer appears in the middle of the frame, then the answer is revealed: the correct option turns green and the others fade. Use it for trivia, multiple-choice and "would you rather" videos.

Syntax

[quiz-card question="..." options="A|B|C" answer="2" countdown="5"]
  [tts subtitles="false"]The question, read aloud.[/tts]
  [pause duration="5s"/]
  [tts subtitles="false"]The answer is B.[/tts]
[/quiz-card]

Parameters

Parameter Type Default Description
question string "" Question shown in bold on the card
options string "" Up to 4 options separated by |
answer number 0 1-based index of the correct option; 0 = none (e.g. would you rather)
countdown time 5 Length of the countdown timer, in seconds
label string "" Small heading above the question, e.g. Question 2 of 5 or Would you rather
reveal string "" Text shown in a green box at the reveal (the answer for open questions, or a follow-up line)
theme string "dark" dark (matches the Reddit card) or light
card-color hex theme Card and timer background
text-color hex theme Question and option text
accent-color hex theme Label, letter badges and timer ring
option-color hex theme Option box background
correct-color hex green Correct option and reveal box
id string auto Element ID

Attribute text can't contain double quotes, and options can't contain |.

Custom colours are applied on top of theme; borders, faded options and the timer track are blended from them. Example: theme="dark" card-color="#1e1b4b" accent-color="#f59e0b".

Timing Behavior

  • A container: the card stays on screen while its children play in order.
  • The countdown starts when the first [tts] inside the card ends (the question has been read) and runs for countdown seconds.
  • The reveal happens when the countdown ends and lasts until the card ends.
  • Put a [pause] as long as the countdown right after the question so the timer has time to run. Ticks go inside it: [sound file="sfx/tick.mp3"/][pause duration="1s"/] once per second.
  • [sound file="sfx/ding.mp3"/] right after the countdown marks the reveal.
  • End with a short [pause duration="1s"/] so the revealed card is seen before the next one.

Layout

  • Portrait (9:16): the card sits in the upper half, the timer in the middle of the frame, clear of the platforms' top tabs and bottom caption area.
  • Landscape (16:9): the card is on the left and the timer on the right.

Examples

Multiple choice, with ticks and a chime:

[video file="minecraft_1.mp4" size="content" fit="loop"]
  [quiz-card label="Question 1 of 3" question="What is the capital of Australia?" options="Sydney|Canberra|Melbourne" answer="2" countdown="3"]
    [tts subtitles="false"]What is the capital of Australia? Sydney, Canberra, or Melbourne?[/tts]
    [sound file="sfx/tick.mp3"/][pause duration="1s"/]
    [sound file="sfx/tick.mp3"/][pause duration="1s"/]
    [sound file="sfx/tick.mp3"/][pause duration="1s"/]
    [sound file="sfx/ding.mp3"/]
    [tts subtitles="false"]The answer is Canberra.[/tts]
    [pause duration="1s"/]
  [/quiz-card]
[/video]

Open question (answer shown in the reveal box):

[quiz-card label="Question 2 of 3" question="How many hearts does an octopus have?" reveal="Three" countdown="5"]
  [tts subtitles="false"]How many hearts does an octopus have?[/tts]
  [pause duration="5s"/]
  [tts subtitles="false"]The answer is three.[/tts]
  [pause duration="1s"/]
[/quiz-card]

Would you rather (no correct answer):

[quiz-card label="Would you rather" options="Be able to fly|Be invisible" reveal="Plot twist: only one foot off the ground" countdown="5"]
  [tts subtitles="false"]Would you rather be able to fly, or be invisible?[/tts]
  [pause duration="5s"/]
  [tts subtitles="false"]Plot twist: you can only fly one foot off the ground.[/tts]
  [pause duration="1s"/]
[/quiz-card]

Media Tags

These tags insert media at specific points.


[image]

Display an image overlay with positioning, sizing, and opacity controls.

Syntax

Self-closing (inline in TTS):

[image file="photo.png" duration="2.0"/]

Wrapping (with children):

[image file="photo.png"]
  [tts]Text spoken while image displays[/tts]
[/image]

Parameters

Parameter Type Default Description
file string required Path to image file
id string auto Element ID
duration time 1.0 Display duration (self-closing only)
position enum "center" Preset position (see below)
x dimension none Custom X position (overrides position)
y dimension none Custom Y position (overrides position)
width dimension none Target width
height dimension none Target height
scale number 1.0 Scale factor (e.g., 0.5 = half size)
fit enum "contain" How image fits target size
opacity number 1.0 Transparency (0.0 = invisible, 1.0 = opaque)

Dimension Values

Dimensions can be specified as:

  • Pixels: "200px" or "200"
  • Percentage: "50%" (relative to frame width/height)

Position Presets

Value Location
center Dead center (default)
top Top center
bottom Bottom center
left Left center
right Right center
top-left Top left corner
top-right Top right corner
bottom-left Bottom left corner
bottom-right Bottom right corner

Fit Modes

Value Behavior
contain Scale to fit within target, preserve aspect ratio (default)
cover Scale to cover target, may crop
fill Stretch to fill target exactly
none Use original size (only scale applies)

Timing Behavior

  • Self-closing inside TTS: Image appears at that word position, displays for duration seconds
  • Wrapping: Image displays for duration of children

Examples

Basic centered image:

[image file="logo.png" duration="3s"/]

Corner watermark at 50% opacity:

[image file="watermark.png" position="bottom-right" scale="0.3" opacity="0.5"/]

Centered with 10% padding on all sides:

[image file="photo.png" width="80%" height="80%" fit="contain"/]

Custom position:

[image file="icon.png" x="100px" y="50%"/]

Cover a region (crops to fill):

[image file="background.jpg" width="50%" height="50%" fit="cover" position="top-left"/]

Inline in TTS:

[tts]
  Look at this [image file="cat.png" duration="3s" position="center" scale="0.8"/] cute cat!
[/tts]

[ai-image]

Generate a still image at render time from a text prompt, using an AI image engine. Resolves to an [image] at render time, so it can be a base background layer or an overlay just like [image]. Add a [zoom] inside it for cinematic motion.

Syntax

Wrapping (base layer for a scene):

[ai-image prompt="misty mountain valley at sunrise" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="cover"]
  [zoom from="100%" to="115%" easing="ease-in-out"]
    [tts]Narration for this scene[/tts]
  [/zoom]
[/ai-image]

Self-closing (swap background mid-narration, inside TTS):

[ai-image prompt="close-up street level, neon lights" fallback="video/minecraft/BXUA2FncVPI.mp4" duration="4s"/]

Parameters

Attribute Type Default Description
prompt string required Text description of the image to generate
fallback string recommended Library file path used if generation fails (e.g. out of credits or moderation block). Must be an exact library path, with no spaces, no display names
fallback-prompt string none Alternate prompt tried if the main prompt fails
fallback-prompt-2 string none Second alternate prompt tried if both above fail
engine string "flux-pro" Force a specific engine. Leave unset to auto-try the chain: flux-klein β†’ flux-pro β†’ sdxl-turbo
id string auto Element ID for time anchoring
duration time 5.0 Display duration (self-closing only)
size enum "content" Duration mode: content or natural
position enum "center" Preset position (same presets as [image])
fit enum "cover" How the image fits the frame: contain, cover, fill, none
width dimension none Target width (px or %)
height dimension none Target height (px or %)

Notes

  • Costs 0.3 credits per generated image.
  • Always include a fallback. If AI generation fails and no fallback is set, rendering fails. The fallback must be an exact path from the asset library.
  • On a content-moderation block the system automatically rewrites the prompt to a safe variant and retries before falling back.
  • Always pair with a [zoom] for visual motion, and alternate zoom direction between consecutive images (in, then out) for a natural rhythm.
  • For multiple images in one script, append a shared style suffix to every prompt (art style, color palette, lighting) so the scenes look consistent.

Examples

Scene-by-scene backgrounds, alternating zoom direction:

[ai-image prompt="misty mountain valley at sunrise, cinematic, warm tones" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="cover"]
  [zoom from="100%" to="122%" easing="ease-in-out"]
    [tts]First scene zooms in.[/tts]
  [/zoom]
[/ai-image]
[ai-image prompt="dense pine forest path, cinematic, warm tones" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="cover"]
  [zoom from="122%" to="100%" easing="ease-in-out"]
    [tts]Next scene zooms out in the opposite direction.[/tts]
  [/zoom]
[/ai-image]

Swap the background mid-narration without splitting the audio:

[ai-image prompt="establishing wide shot, city skyline" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="cover"]
  [zoom from="100%" to="110%" easing="ease-in-out"]
    [tts]
      Opening narration over the first image.
      [ai-image prompt="close-up street level, neon lights" fallback="video/minecraft/BXUA2FncVPI.mp4"/]
      Background swaps here while narration continues uninterrupted.
    [/tts]
  [/zoom]
[/ai-image]

Force a specific engine:

[ai-image prompt="watercolor koi pond" fallback="video/minecraft/BXUA2FncVPI.mp4" engine="sdxl-turbo" fit="cover"/]

[ai-video]

Generate a real video clip at render time from a text prompt, using an AI video engine. Resolves to a [video] at render time. Use only when genuine motion is essential (flowing water, fire, crowds, weather, action); for static or atmospheric scenes prefer [ai-image] with a [zoom] instead, since it is faster and far cheaper.

Syntax

[ai-video prompt="time lapse of clouds moving over mountains" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="loop"]
  [tts]Narration for this scene[/tts]
[/ai-video]

Parameters

Attribute Type Default Description
prompt string required Text description of the clip to generate
fallback string recommended Library file path used if generation fails (e.g. out of credits or moderation block). Must be an exact library path, with no spaces, no display names
fallback-prompt string none Alternate prompt tried if the main prompt fails
fallback-prompt-2 string none Second alternate prompt tried if both above fail
engine string "veo" AI video engine to use
id string auto Element ID for time anchoring
size enum "content" Duration mode: content or natural
fit enum "loop" How the clip fits the container duration: none, trim, loop

Notes

  • Costs roughly $0.50 per clip and adds 1-2 minutes of extra render time, so reserve it for scenes where motion is the point.
  • Generated clips are about 8 seconds long; use fit="loop" to fill longer narration.
  • Always include a fallback. If AI generation fails and no fallback is set, rendering fails. The fallback must be an exact path from the asset library.
  • On a content-moderation block the system automatically rewrites the prompt to a safe variant and retries before falling back.
  • Unlike [ai-image], this tag has no position, width, height, or duration attributes.

Examples

Motion scene that needs real video:

[ai-video prompt="crashing ocean waves against rocks, slow motion" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="loop"]
  [tts]The storm rolled in from the sea.[/tts]
[/ai-video]

Mostly images with one video highlight:

[ai-image prompt="quiet harbor at dawn, cinematic" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="cover"]
  [zoom from="100%" to="112%" easing="ease-in-out"]
    [tts]The town was still asleep.[/tts]
  [/zoom]
[/ai-image]
[ai-video prompt="flock of birds taking flight over the water" fallback="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="loop"]
  [tts]Then, all at once, the sky came alive.[/tts]
[/ai-video]

[sound]

Play a sound effect.

Syntax

[sound file="explosion.wav"/]

Parameters

Parameter Type Default Description
file string required Path to audio file
id string auto Element ID
fade-in time 0 Fade up from silence over this long, e.g. "300ms"
fade-out time 0 Fade down to silence over this long, ending where the sound ends (for a [sound] that wraps content, the end of what it wraps)

If fade-in and fade-out together are longer than the sound, they are shortened in proportion.

Timing Behavior

  • Inside TTS: Sound plays at that word position in the narration
  • Outside TTS: Sound plays at that point in sequence

Example

[tts]
  The door slammed [sound file="slam.wav"/] shut.
[/tts]

[pause]

Add silence/time without speech. Useful for letting video/music continue after TTS ends.

Syntax

[pause duration="3s"/]

Parameters

Parameter Type Default Description
duration time 1.0 Duration of the pause
id string auto Element ID (for anchoring)

Timing Behavior

  • Advances the timeline by the specified duration
  • Video and music continue playing during the pause
  • No audio is generated (silence)

Examples

Add 3 seconds after narration:

[video file="bg.mp4" fit="loop"]
  [music file="song.mp3" fit="loop"]
    [tts]Here's the content[/tts]
    [pause duration="3s"/]
  [/music]
[/video]

Use as anchor point:

[tts id="narration"]Main content[/tts]
[pause duration="2s" id="outro"/]
[music file="outro.mp3" start="outro:start"/]

Style Tags

Style tags modify subtitle appearance. They only work inside [tts] blocks.


[style]

Generic styling tag with full control.

Syntax

[style color="#FF0000" bold="true" italic="false" underline="false" size="48"]
  styled text
[/style]

Parameters

Parameter Type Default Description
color hex color none Text color (#RRGGBB or #RGB)
bold boolean "false" Bold text
italic boolean "false" Italic text
underline boolean "false" Underlined text
size number none Font size in pixels

Example

[tts]
  This is [style color="#FF6600" bold="true"]orange and bold[/style] text.
[/tts]

Color Shortcuts

Convenience tags for common colors.

[red]

[red]This text is red[/red]

[blue]

[blue]This text is blue[/blue]

[green]

[green]This text is green[/green]

Nesting

Styles can be nested. Inner styles override outer styles:

[tts]
  [blue]Blue text with [red]red word[/red] inside[/blue]
[/tts]

Effect Tags

Effect tags add animated camera movements to video/image content. They can be used in two ways:

  1. Wrapping containers - Wrap other elements (TTS, images) and inherit their duration
  2. Inline within TTS - Positioned by word timing for precise synchronization (see Inline Effect Timing)

[zoom]

Ken Burns style zoom effect. Creates smooth zoom in or zoom out animation.

Syntax

[zoom from="100%" to="120%" easing="ease-out" focus-x="50%" focus-y="50%"]
  ...children...
[/zoom]

Parameters

Parameter Type Default Description
from percentage "100%" Starting zoom level
to percentage "120%" Ending zoom level
easing enum "linear" Animation curve (see below)
focus-x percentage "50%" Horizontal focus point
focus-y percentage "50%" Vertical focus point
id string auto Element ID

Easing Modes

Value Behavior
linear Constant speed (default)
ease-in Start slow, accelerate
ease-out Start fast, decelerate
ease-in-out Slow at both ends

Examples

Subtle zoom in on image:

[image file="photo.png"]
  [zoom from="100%" to="110%"]
    [tts]Look at this beautiful scenery[/tts]
  [/zoom]
[/image]

Zoom out with focus on top-left:

[zoom from="150%" to="100%" focus-x="25%" focus-y="25%" easing="ease-out"]
  [tts]Revealing the full picture[/tts]
[/zoom]

Inline zoom for emphasis:

[tts]
  This is normal, but [zoom from="100%" to="120%"]this part is important[/zoom] and back to normal.
[/tts]

[pan]

Horizontal camera movement. Scales up content 1.5x to provide room for movement.

Syntax

[pan from="left" to="right" easing="linear"]
  ...children...
[/pan]

Parameters

Parameter Type Default Description
from position "0%" Starting horizontal position
to position "100%" Ending horizontal position
easing enum "linear" Animation curve
id string auto Element ID

Position Values

Keyword Percentage
left 0%
center 50%
right 100%

Or use any percentage value like "25%", "75%", etc.

Examples

Pan from left to right:

[pan from="left" to="right"]
  [tts]Panning across the landscape[/tts]
[/pan]

Pan from center to right with easing:

[pan from="center" to="right" easing="ease-in-out"]
  [tts]Following the action[/tts]
[/pan]

[tilt]

Vertical camera movement. Scales up content 1.5x to provide room for movement.

Syntax

[tilt from="top" to="bottom" easing="linear"]
  ...children...
[/tilt]

Parameters

Parameter Type Default Description
from position "0%" Starting vertical position
to position "100%" Ending vertical position
easing enum "linear" Animation curve
id string auto Element ID

Position Values

Keyword Percentage
top 0%
center 50%
bottom 100%

Examples

Tilt from top to bottom:

[tilt from="top" to="bottom"]
  [tts]Scanning down the building[/tts]
[/tilt]

Tilt up with ease-out:

[tilt from="bottom" to="top" easing="ease-out"]
  [tts]Looking up at the sky[/tts]
[/tilt]

[shake]

Camera shake effect for impact, tension, or emphasis. Uses oscillating motion with optional decay.

Syntax

Wrapping form:

[shake intensity="5" frequency="30" decay="true"]
  ...children...
[/shake]

Self-closing form:

[shake intensity="10" frequency="20" duration="0.5s"/]

Parameters

Parameter Type Default Description
intensity number 5.0 Shake magnitude in pixels
frequency number 30.0 Oscillation frequency (Hz)
decay boolean "true" Shake diminishes over time
duration time 0.5s Duration (self-closing only)
id string auto Element ID

Examples

Self-closing shake during impact (inline in TTS):

[tts]
  And then [shake intensity="8" duration="0.3s"/] the explosion hit!
[/tts]

The shake starts exactly when "the" is spoken and lasts 0.3 seconds.

Wrapping shake timed to words (inline in TTS):

[tts]
  Stay calm during [shake intensity="3"]the earthquake[/shake] warning.
[/tts]

The shake spans from when "the" is spoken until "earthquake" finishes.

Wrapping shake around TTS:

[shake intensity="6" frequency="25" decay="true"]
  [tts]The earthquake rumbled beneath us[/tts]
[/shake]

Intense persistent shake:

[shake intensity="12" frequency="40" decay="false"]
  [tts]The machine vibrated violently[/tts]
[/shake]

[filter]

A colour look on the footage and images: black and white, warm, vintage and so on. Captions, on-screen text and cards are drawn on top and keep their colours. Looks are free.

Syntax

Wrapping form (the look covers what's inside):

[filter look="bw"]
  ...children...
[/filter]

Self-closing form (from here to the end, or for duration):

[filter look="warm"/]
[filter look="vintage" duration="3s"/]

Parameters

Parameter Type Default Description
look string required bw, noir, sepia, vintage, warm, cool, vivid, faded, dark
duration time to the end How long a self-closing look lasts
id string auto Element ID

Examples

A flashback in black and white:

[video file="story.mp4"]
  [tts]We were happy back then.[/tts]
  [filter look="bw"][tts]It was the summer of 1999.[/tts][/filter]
  [tts]Then everything changed.[/tts]
[/video]

A few words, inside narration:

[tts]It all went [filter look="noir"]dark and cold[/filter] after that.[/tts]

[split]

A second video with the main picture: stacked (split screen, like gameplay under a story) or as a small inset in a corner (picture-in-picture). The second video is muted and loops. Captions sit in the middle by default, which is the seam of a 50/50 split.

Syntax

Wrapping form (for what's inside):

[split file="parkour.mp4"]
  ...children...
[/split]

Self-closing form (from here to the end, or for duration):

[split file="parkour.mp4"/]
[split file="facecam.mp4" mode="pip" position="top-right" size="0.3"/]

Parameters

Parameter Type Default Description
file string required The second video
mode string split split (stacked) or pip (corner inset)
position string bottom / bottom-right split: top or bottom; pip: top-left, top-right, bottom-left, bottom-right
ratio number 0.5 split: the main picture's share of the height (0.25–0.75)
size number 0.35 pip: the inset's share of the width (0.15–0.6)
duration time to the end How long a self-closing split lasts
id string auto Element ID

Example

[video file="story-background.mp4" size="content" fit="loop"]
  [split file="minecraft-parkour.mp4" ratio="0.5"/]
  [tts]Let me tell you about the worst day of my life.[/tts]
[/video]

[transition]

How the next [video] replaces the one before it. Put it between the two clips; it takes no time of its own and does not change the video's length (the new clip starts duration early and animates in over the end of the previous one).

[video file="video/a.mp4" size="content" fit="loop"][tts]First part.[/tts][/video]
[transition type="crossfade" duration="0.6s"/]
[video file="video/b.mp4" size="content" fit="loop"][tts]Second part.[/tts][/video]
Parameter Type Default Description
type enum "crossfade" crossfade, fade (dip to black), slide-left, slide-right, slide-up, slide-down, wipe-left, wipe-right, wipe-up, wipe-down
duration time 0.5s Between 0.05s and 3s

slide-* moves the new clip in (slide-left enters from the right edge, moving left); wipe-* keeps both clips still and sweeps an edge across in that direction.


[text]

On-screen text, such as a title, a list number or a label. Wrap narration to show it while those words are spoken, or use it self-closing with a duration. Inside [tts], a self-closing [text/] appears on the word where it is written.

[text value="Fact #1" position="top" background="#00000099"]
  [tts]Octopuses have three hearts.[/tts]
[/text]

[tts]And the best part [text value="πŸ™ 3 hearts" duration="2s"/] is how they use them.[/tts]
Parameter Type Default Description
value string required The text. Long lines wrap; \n forces a line break.
position enum "top" top, bottom, center, left, right, top-left, top-right, bottom-left, bottom-right. bottom stays above the captions.
x, y px or % none Exact position instead of position
size number 72 Font size in pixels at 1080 px wide (scaled for other sizes)
color color "#FFFFFF" #RRGGBB, #RRGGBBAA or a basic name (white, yellow, red, …)
background color none Box behind the text, e.g. "#00000099"
font string arial arial, noto-sans, noto-serif, roboto-black, verdana-italic, felt-condolences
duration time 3s Self-closing only; wrapping text lasts as long as its content
fade time 0.2s Fade in and out

Emoji are not drawn in [text]; use them in captions (see sub-style on tts).


[progress-bar]

A thin bar that fills from left to right over the whole video. It helps viewers see how much is left, which keeps them watching short videos. Put it anywhere, once.

[progress-bar color="#FFD60A" height="12" position="bottom"/]
Parameter Type Default Description
color color "#FFFFFF" Bar colour
height number 10 Pixels at 1920 px tall (scaled for other sizes)
position enum "bottom" top or bottom
opacity number 0.9 0–1

[chapter]

Marks the start of a chapter. Chapters are added to the description when the video is published to YouTube (as 00:00 Title lines), and are returned in the video.completed webhook. Inside [tts] it marks the word where it is written.

[chapter title="Intro"/]
[tts]Welcome back.[/tts]
[chapter title="The science"/]
[tts]Here is how it works…[/tts]

YouTube shows chapters only when there are at least three, the first is at 0:00 and each is at least 10 seconds long. InkSlop adds a 0:00 "Intro" if needed and merges chapters that are too short; if fewer than three are left, no chapters are added.


Nesting Effects

Effects can be combined by nesting them:

[zoom from="100%" to="115%" easing="ease-in"]
  [pan from="left" to="center"]
    [tts]A dramatic reveal with zoom and pan[/tts]
  [/pan]
[/zoom]

Effects apply from inside out - the innermost effect is applied first, then outer effects transform the result.


Timing System

The timing system is content-driven, not timestamp-driven.

Core Principle

Speech drives timing. Media reacts to content, not seconds.

Sequential Processing

Elements inside a container are processed sequentially:

[video file="bg.mp4"]
  [tts]First narration[/tts]      # 0.0s - 2.5s
  [tts]Second narration[/tts]     # 2.5s - 5.0s
  [tts]Third narration[/tts]      # 5.0s - 8.0s
[/video]

Inline Media Timing

Media inside TTS is positioned by word location, not timestamps:

[tts]
  Hello [image file="wave.png" duration="1"/] world!
[/tts]

The image appears when "world" is spoken, calculated from TTS word timings.

Inline Effect Timing

Effects can also be placed inside TTS blocks to get word-level timing:

Self-closing effects (like shake with duration) start at their text position:

[tts]
  And then [shake intensity="5" duration="0.3s"/] the explosion hit!
[/tts]

The shake effect starts exactly when "the" would be spoken (between "then" and "the").

Wrapping effects span the duration of the wrapped text:

[tts]
  Hello world, [zoom from="100%" to="120%"]this is important[/zoom] okay bye
[/tts]

The zoom effect starts when "this" is spoken and ends when "important" finishes.

This allows precise synchronization of camera effects with specific words:

[tts]
  Stay calm during [shake intensity="3"]the earthquake[/shake] warning.
  Now [pan from="left" to="right"]look across the horizon[/pan] slowly.
[/tts]

Time Anchors

Reference other elements' timing using anchors:

element_id:start       # When element starts
element_id:end         # When element ends
element_id:start+2s    # 2 seconds after element starts
element_id:end-0.5s    # 0.5 seconds before element ends

Example

[tts id="narration"]The main content[/tts]
[music file="outro.mp3" start="narration:end-1s"]
  # Music starts 1 second before narration ends
[/music]

Container Duration Modes

size="content" (Default)

Container duration = sum of children durations

[video file="bg.mp4" size="content"]
  [tts]3 seconds of speech[/tts]  # Video is 3 seconds
[/video]

size="natural"

Container uses its natural file duration

[video file="intro.mp4" size="natural"]
  # Video plays full 10 seconds regardless of children
[/video]

Fit Modes

When container duration differs from file duration:

Mode Behavior
none No adjustment (may have black frames or cut off)
trim Cut file to fit duration
loop Repeat file to fill duration

Examples

Basic Video with Narration

[video file="nature.mp4" size="content" fit="loop"]
  [tts engine="kokoro_tts" voice="af_heart"]
    Welcome to our nature documentary.
    Today we explore the forest.
  [/tts]
[/video]

Styled Subtitles

[video file="presentation.mp4"]
  [tts sub-position="bottom" sub-align="center"]
    The answer is [red]42[/red].
    This is [style color="#FFD700" bold="true"]golden[/style] knowledge.
  [/tts]
[/video]

Multiple TTS with Sound Effects

[video file="action.mp4"]
  [tts voice="narrator"]
    The hero approaches the door.
    [sound file="footsteps.wav"/]
  [/tts]

  [tts voice="hero"]
    [sound file="door_knock.wav"/]
    Is anyone there?
  [/tts]
[/video]

Background Music with Narration

[video file="intro.mp4"]
  [music file="ambient.mp3" fit="loop"]
    [tts]
      In the beginning, there was silence.
    [/tts]
    [tts]
      Then came the music.
    [/tts]
  [/music]
[/video]

Image Overlay During Speech

[video file="lecture.mp4"]
  [tts]
    Let me show you a diagram.
    [image file="diagram.png" duration="5"/]
    As you can see, the process flows left to right.
  [/tts]
[/video]

Anchored Timing

[video file="scene.mp4"]
  [tts id="main"]
    The main story content goes here.
    It could be quite long.
  [/tts]

  [music file="credits.mp3" start="main:end-2s" fit="trim"]
    [tts]
      Thanks for watching.
    [/tts]
  [/music]
[/video]


The Cinematic Scene Formula

The highest-quality structure this tool produces. Use it when the source text deserves to be treated as writing rather than as a caption: poems, monologues, scripture, myth, folklore, history, horror. Do not use it for explainers, listicles, or gameplay commentary; those want one [tts] over one background.

Working examples of every rule below live in poem_video_examples/.

Structure

One [music] wraps the whole script. Inside it, one scene per stanza or beat. Each scene is an [ai-image] for that beat, a camera move wrapping its [tts], and a short held beat after the narration so the move lands before the cut.

# resolution: 16:9
# Title: Author (year, public domain)
# STYLE SUFFIX (repeated verbatim on every prompt):
#   gothic engraving rendered as oil painting, single-candle chiaroscuro,
#   ink-black and deep umber with cold blue window light, ominous, high contrast, cinematic

[music file="music/Ghost_Story.opus" volume="0.20" fit="loop"]

  # Scene 1: the scholar over his books. Slow push in.
  # (one line, wrapped here only so it fits the page; see rule 9)
  [ai-image prompt="A weary scholar slumped over ancient books in a cramped midnight study, one guttering candle, wide shot, <STYLE SUFFIX>" fallback-prompt="An old study at midnight, a single candle over stacked leather books, <STYLE SUFFIX>" fallback-prompt-2="A single candle burning on a stack of old books in a dark room" fallback="video/satisfying/e1emEay0ink.mp4" size="content" fit="cover"]
    [zoom from="100%" to="114%" easing="ease-in-out"]
      [tts engine="mimo_tts_voicedesign" voice="<full voice description>" sub-position="bottom" font="roboto-black"]
        Once upon a midnight dreary, while I pondered, weak and weary,
        Over many a quaint and curious volume of forgotten loreβ€”
      [/tts]
      [pause duration="0.6s"/]
    [/zoom]
  [/ai-image]

  # ... one more scene per stanza, alternating the camera move ...

  [pause duration="3s"/]

[/music]

The nine rules that make it work

1. One image per beat, never per line. A stanza, a sentence group, or a single hammer-blow line that earns its own frame: two to five lines of narration per scene. Six or more scenes reads as a film; two or three reads as a slideshow.

2. Write the style suffix once, in a comment at the top, then paste it onto every prompt unchanged. It must lock art style, palette, light, and mood. Naming a specific medium (oil painting, engraving, tenebrist portrait) holds far better across scenes than naming a genre.

3. Never repeat a camera move back to back. Cycle push-in β†’ pull-out β†’ pan β†’ push-in β†’ tilt β†’ pull-out. Zooms carry the ordinary beats; a [pan] is for something the eye travels across (a tide, a corridor, a horizon); a [tilt] is for a reveal above or below the frame. Keep the amplitude modest: 100%β†’114%, or 122%β†’100%. In 9:16 keep it gentler still, because a tall crop magnifies motion.

4. Aim the push. On a close-up, set focus-x / focus-y on the thing being pushed onto: an eye sits around focus-y="42%", not at the frame centre. A push that drifts off its subject is worse than no push at all.

5. Shake only on violence. Inline, on the exact words that break, strike, or collapse: [shake intensity="6" frequency="24" decay="true"]things fall apart[/shake]. Use decay="true" for a single impact; leave it off for sustained menace. Two or three in a whole script; past that it reads as a broken camera.

6. One colour accent per scene at most, and only on the text's own key word. [style color="#a11f1f" bold="true"]blood-dimmed tide[/style]. Take the hex from the same palette as the style suffix so the subtitle belongs to the picture. Accenting every other phrase destroys the effect entirely.

7. Hold the beat. Put [pause duration="0.6s"/] inside the camera move, after [/tts]. Because wrapping effects take their duration from their children, the pause extends the move: the image stays, the camera finishes travelling, and the line lands before the cut. Give the closing line 1.2s–1.4s, then a final [pause duration="3s"/] under the music.

8. Give every [ai-image] a two-step prompt fallback before the library fallback. fallback-prompt is the same image in plainer words (keep the style suffix); fallback-prompt-2 is the simplest literal version with no style suffix at all, which is what survives a moderation block. The library fallback video is a last resort that will look wrong in a cinematic piece, so the job of the prompt chain is never to reach it.

9. Keep every opening tag on a single line, however long it gets. An [ai-image] carrying a prompt plus two fallback prompts makes for a very long line, and that is expected. Never wrap a tag's attributes onto continuation lines. The renderer's parser tolerates it, but the in-app DSL editor validates tags line by line and will report a wrapped tag as unclosed, flagging every matching [/ai-image] as an unexpected closing tag.

Music

One track, wrapping everything, at volume="0.20"–"0.24" so it sits under the voice rather than beside it. Public-domain classical (Beethoven, Chopin, Satie) needs no attribution at all and suits this register; Kevin MacLeod tracks require CC BY attribution in the video description.



The Plate Format

The long-form counterpart to the Cinematic scene formula, for reading a poem or passage in full. Everything that never moves (the author portrait, the ornament rules, the name block) is baked into one static PNG, and the verse is rendered over it a line at a time as subtitles.

Nothing is generated at render time except the voice. No [ai-image], no [ai-video]. A plate video costs TTS plus render, which is what makes it viable for long readings where a scene-per-stanza film would be expensive.

# resolution: 16:9

[music file="music/Chopin_Nocturne_Op9_No1.opus" volume="0.18" fit="loop"]
  [image file="user/plates/kipling-plate.png" fit="cover"]
    [tts engine="mimo_tts_voicedesign" voice="<full voice description>"
         sub-mode="line" sub-position="center" sub-align="right"
         font="noto-serif" sub-font-size="42" sub-weight="regular"
         sub-margin-l="860" sub-margin-r="88" highlight="false"]
      <the whole poem, one line per line>
    [/tts]
    [pause duration="3s"/]
  [/image]
[/music]

Rules

1. Build the plate with scripts/make_author_plate.py. It renders a 1920x1080 PNG: portrait faded into the black at the left, two ornament rules bracketing the text area, and the author name and dates set bottom-right. Upload it and reference the returned path.

2. The margins and the plate must agree. sub-margin-l is the left edge of the text column in 1920-wide pixels, and it has to clear the portrait. The default pairing is a portrait across the left ~45% and sub-margin-l="860".

3. Keep lines inside the column. At sub-font-size="42" in Noto Serif, about 58 characters fit between sub-margin-l and the right inset. A longer line wraps and breaks the one-line-at-a-time effect.

4. sub-weight="regular" and highlight="false". The default subtitle face is bold with a moving word highlight: right for captions, wrong for verse. Both are needed for the calm engraved look.

5. Set the verse, not a paragraph. sub-mode="line" breaks on the newlines in your script, so the script's lineation is what appears on screen. If your source has been flattened to one line, re-line it first with _reline_verse() in content_pipeline/dsl_generator.py does this from capitalisation and punctuation.

6. Let the verse accumulate. sub-page-lines="6" with sub-fade="450" fills the bracketed space a line at a time and then clears, which reads as turning a page. One line at a time on an otherwise empty frame reads as a caption instead.

7. One track, no camera move. The composition is still; a zoom would drift the portrait off its intended position. Public-domain classical suits the register and carries no attribution obligation.

Generating one

The format is wired into the content pipeline, which reads the text from a CSV rather than having it retyped:

python scripts/content_pipeline/batch.py --preset poems --type plate --dry-run

generate_plate_dsl(row, config) builds the script; the plate PNG comes from plate_bg_map[author] in the preset.


Layer System

The renderer organizes elements into layers:

Layer Elements Behavior
video VideoTag Base visual layer
image ImageTag Overlaid on video
effect ZoomTag, PanTag, TiltTag, ShakeTag Visual effects applied to video/image
primary TTSTag Main audio + subtitles
music MusicTag Background audio
sfx SoundTag Sound effects

Audio layers are mixed together. Video/image layers are composited. Effects are applied as FFmpeg filters.


Not Yet Implemented

The following features from the design spec are not yet implemented:

  • [meta] - Global project settings
  • [scene] - Scene grouping
  • [bg] - Background (use [video] instead)
  • [emotion] - Emotional TTS modifiers for part of a narration (for a whole [tts] block, use its instruct parameter with mimo_tts)
  • [filter] - Visual filters
  • [fade] - Fade to or from black at the start or end of the video (between two clips, use [transition type="fade"])

Asset Library

Built-in assets you can reference directly by file path. The full library is available in the video and music selectors on the create page.

Tip: right-click any asset in the selector

Right-clicking a video clip or music track gives you quick options to copy its file path (paste straight into your script), copy a direct link to the file, and copy the attribution text if the asset requires credit.

Video Clips

CategoryFile pathDuration
Minecraftvideo/minecraft/BXUA2FncVPI.mp410:30
Subway Surfersvideo/subway_surfers/i0M4ARe9v0Y.mp45:11
Satisfyingvideo/satisfying/JvI-02Q69ms.mp43:00
CS Surfingvideo/cs_surfing/kuPPZCtLX4w.mp44:48

Browse all clips in the video selector on the create page.

Music Tracks

NameFile pathMood
Sneaky Snitch
Kevin MacLeod
music/Sneaky_Snitch.opus
Scheming Weasel (faster version)
Kevin MacLeod
music/Scheming_Weasel_(faster_version).opus
Monkeys Spinning Monkeys
Kevin MacLeod
music/Monkeys_Spinning_Monkeys.opus
Fluffing a Duck
Kevin MacLeod
music/Fluffing_a_Duck.opus

Browse all tracks in the music selector on the create page.

How to use assets

Video: [video file="video/minecraft/BXUA2FncVPI.mp4" size="content" fit="loop"]

Music: [music file="music/Sneaky_Snitch.opus" volume="0.3" fit="loop"]

More from InkSlop