Skip to content

LeadcodeDev/rustmotion

Repository files navigation

rustmotion

A CLI tool that renders motion design videos from JSON scenarios. No browser, no Node.js — just a single Rust binary.

Crates.io docs.rs License: MIT

Install

cargo install rustmotion

Requirements: Rust toolchain + C++ compiler (for openh264). Recommended: ffmpeg CLI for 10-bit H.264 and H.265/VP9/ProRes/WebM/GIF output.

Quick Start

# Render a video
rustmotion render scenario.json -o video.mp4

# Render with a specific codec
rustmotion render scenario.json -o video.webm --codec vp9 --crf 30

# Export as PNG sequence
rustmotion render scenario.json -o frames/ --format png-seq

# Export as animated GIF
rustmotion render scenario.json -o output.gif --format gif

# Render a single frame for preview
rustmotion render scenario.json --frame 42 -o frame.png

# Validate without rendering
rustmotion validate scenario.json

# Export JSON Schema (for editor autocompletion or LLM prompts)
rustmotion schema -o schema.json

# Show scenario info
rustmotion info scenario.json

CLI Reference

rustmotion render

Flag Description Default
input Path to the JSON scenario file (required)
-o, --output Output file path output.mp4
--frame <N> Render a single frame to PNG (0-indexed)
--codec <CODEC> Video codec: h264, h265, vp9, prores h264
--crf <0-51> Constant Rate Factor (lower = better quality) 23
--format <FMT> Output format: mp4, webm, mov, gif, png-seq auto from extension
--transparent Transparent background (PNG sequence, WebM, ProRes 4444) false
--output-format json Machine-readable JSON output for CI pipelines
-q, --quiet Suppress all output except errors

JSON Scenario Format

{
  "version": "1.0",
  "video": { ... },
  "audio": [ ... ],
  "scenes": [ ... ]
}

Video Config

{
  "video": {
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "background": "#0f172a",
    "codec": "h264",
    "crf": 23
  }
}
Field Type Default Description
width u32 (required) Video width in pixels (must be even)
height u32 (required) Video height in pixels (must be even)
fps u32 30 Frames per second
background string "#000000" Default background color (hex)
codec string "h264" Video codec: h264, h265, vp9, prores
crf u8 23 Constant Rate Factor (0-51, lower = better quality)

Audio Tracks

{
  "audio": [
    {
      "src": "music.mp3",
      "start": 0.0,
      "end": 10.0,
      "volume": 0.8,
      "fade_in": 1.0,
      "fade_out": 2.0
    }
  ]
}
Field Type Default Description
src string (required) Path to audio file (MP3, WAV, OGG, FLAC, AAC)
start f64 0.0 Start time in the output video (seconds)
end f64 End time (omit for full track)
volume f32 1.0 Volume multiplier (0.0 - 1.0)
fade_in f64 Fade-in duration (seconds)
fade_out f64 Fade-out duration (seconds)

Scenes

Each scene is an implicit flex container at video dimensions (default direction: column). Children participate in flex flow automatically. Use positioned container for absolute positioning.

{
  "scenes": [
    {
      "duration": 3.0,
      "background": "#1a1a2e",
      "layout": {
        "direction": "column",
        "align_items": "center",
        "justify_content": "center",
        "gap": 24
      },
      "children": [ ... ],
      "transition": {
        "type": "fade",
        "duration": 0.5
      }
    }
  ]
}
Field Type Default Description
duration f64 (required) Scene duration in seconds
background string Scene background (overrides video.background)
freeze_at f64 Freeze the scene at this time (seconds)
children Component[] [] Components rendered bottom-to-top
layout object Scene-level flex layout options
transition Transition Transition effect from the previous scene

layout options: direction (column/row), gap, align_items (start/center/end/stretch), justify_content (start/center/end/space_between/space_around/space_evenly), padding


Transitions

Transitions blend between two consecutive scenes. Set on the second scene.

{
  "transition": {
    "type": "clock_wipe",
    "duration": 0.8
  }
}
Type Description
fade Linear crossfade between scenes
wipe_left Horizontal wipe revealing scene B from the left
wipe_right Horizontal wipe revealing scene B from the right
wipe_up Vertical wipe revealing scene B from the top
wipe_down Vertical wipe revealing scene B from the bottom
zoom_in Scene A zooms in and fades out, revealing scene B
zoom_out Scene B zooms out from larger to normal size
flip 3D Y-axis flip simulation
clock_wipe Circular clockwise sweep from 12 o'clock
iris Expanding circle from the center reveals scene B
slide Scene B pushes scene A to the left
dissolve Per-pixel noise dissolve
none Hard cut at the midpoint
Field Type Default Description
type string (required) One of the transition types above
duration f64 0.5 Transition duration in seconds

Include (Composable Scenarios)

Scene entries can reference external scenario files to inject their scenes inline:

{
  "scenes": [
    { "include": "shared/intro.json" },
    { "duration": 5.0, "children": [ ... ] },
    { "include": "shared/outro.json", "scenes": [0, 2] }
  ]
}
Field Type Default Description
include string (required) Path (relative to parent file) or URL to a scenario JSON
scenes usize[] Only include scenes at these 0-based indices. Omit to include all
config object Config overrides to pass to a structural component (see below)
  • The included file's video config is ignored
  • Audio tracks from included files are merged
  • Includes can be nested (max depth: 8)

Structural Components (Variables)

Structural components are reusable scenario files with declared variables. When rendered standalone, default values apply. When included from a parent, the parent can override any variable.

Defining config

Add a config object at the root of a scenario. Each entry has a type, a default value, and an optional description:

{
  "config": {
    "cta_text": { "type": "string", "default": "Book your demo" },
    "accent_color": { "type": "string", "default": "#5C39EE" },
    "logo_src": { "type": "string", "default": "assets/logo.svg" },
    "counter_target": { "type": "number", "default": 400 },
    "tagline_spans": {
      "type": "array",
      "default": [
        { "text": "Don't ", "color": "#5C39EE" },
        { "text": "miss any lead" }
      ]
    }
  },
  "video": { "width": 1080, "height": 1920, "fps": 30 },
  "scenes": [
    {
      "duration": 7.0,
      "children": [
        { "type": "svg", "src": "$logo_src" },
        { "type": "text", "content": "$cta_text", "style": { "color": "$accent_color" } },
        { "type": "counter", "from": 0, "to": { "$var": "counter_target" } },
        { "type": "rich_text", "spans": { "$var": "tagline_spans" } }
      ]
    }
  ]
}

Supported types: string, number, boolean, object, array. Array and object types allow passing full components (e.g. rich_text spans, children arrays).

Referencing config values

Syntax Context Behavior
"$var_name" Entire string value Replaced by the config value (any type)
"prefix $var_name suffix" String interpolation Replaced inline (value must be string/number/boolean)
{ "$var": "var_name" } Any position Replaced by the config value (for non-string types in object position)
"$$literal" Escape Produces the literal string "$literal"

Including with overrides

{
  "scenes": [
    { "duration": 5.0, "children": [ ... ] },
    {
      "include": "components/outro.json",
      "config": {
        "cta_text": "Try WhatsApp",
        "accent_color": "#25D366",
        "tagline_spans": [
          { "text": "Stop losing " },
          { "text": "customers", "color": "#25D366" }
        ]
      }
    }
  ]
}

Config entries not listed in overrides keep their default values. Referencing an undefined config key produces an error.

Standalone rendering

When rendering a structural component directly (rustmotion render components/outro.json), all default values are applied automatically.


Components

All components are discriminated by "type". Rendered in array order (first = bottom, last = top).

Common Fields

Available on all component types:

Field Type Default Description
start_at f64 Component appears at this time (seconds within scene)
end_at f64 Component disappears after this time

Common Style Fields

All visual properties are inside a "style" object:

Style field Type Default Description
opacity f32 1.0 0.0 to 1.0
padding f32 | {top, right, bottom, left} Inner spacing
margin f32 | {top, right, bottom, left} Outer spacing
animation array | object [] Animation effects (see Animations)

Text

{
  "type": "text",
  "content": "Hello World",
  "max_width": 800,
  "style": {
    "font-size": 48,
    "color": "#FFFFFF",
    "font-family": "Inter",
    "font-weight": "bold",
    "text-align": "center",
    "line-height": 1.2,
    "letter-spacing": 2.0,
    "animation": [{ "name": "fade_in_up", "delay": 0.3, "duration": 0.6 }]
  }
}

Root fields: content (required), max_width

Style field Type Default Description
font-size f32 48.0 Font size in pixels
color string "#FFFFFF" Text color (hex)
font-family string "Inter" Font family name
font-weight enum "normal" "normal" or "bold"
font-style enum "normal" "normal", "italic", "oblique"
text-align enum "left" "left", "center", "right"
line-height f32 Line height multiplier
letter-spacing f32 Additional spacing between characters
text-shadow object { "color": "#000", "offset_x": 2, "offset_y": 2, "blur": 4 }
stroke object { "color": "#000", "width": 2 }
text-background object { "color": "#000", "padding": 4, "corner_radius": 4 }

Shape

{
  "type": "shape",
  "shape": "rounded_rect",
  "size": { "width": 300, "height": 200 },
  "style": {
    "fill": "#3b82f6",
    "border-radius": 16,
    "stroke": { "color": "#ffffff", "width": 2 },
    "animation": [{ "name": "scale_in", "duration": 0.6 }]
  }
}

Root fields: shape (required), size, text

Style field Type Default Description
fill string | gradient Fill color (hex) or gradient object
stroke {color, width} Stroke outline
border-radius f32 Corner radius (for rounded_rect)

Shape types: rect, circle, rounded_rect, ellipse, triangle, star (with points, default 5), polygon (with sides, default 6), path (with data SVG path string)

Gradient fill:

{
  "fill": {
    "type": "linear",
    "colors": ["#667eea", "#764ba2"],
    "angle": 135,
    "stops": [0.0, 1.0]
  }
}

Types: linear, radial.

Embedded text in shapes (text field):

{
  "type": "shape",
  "shape": "circle",
  "size": { "width": 56, "height": 56 },
  "style": { "fill": "#2A74FF" },
  "text": {
    "content": "1",
    "font_size": 22,
    "color": "#FFFFFF",
    "font_weight": "bold",
    "align": "center",
    "vertical_align": "middle"
  }
}

vertical_align: "top", "middle", "bottom" (default: "middle").


Image

{
  "type": "image",
  "src": "photo.png",
  "size": { "width": 1080, "height": 1080 },
  "fit": "cover",
  "style": {
    "animation": [{ "name": "fade_in", "duration": 0.5 }]
  }
}
Field Type Default Description
src string (required) Path to image file (PNG, JPEG, WebP)
size {width, height} Target size (uses native image size if omitted)
fit string "cover" "cover", "contain", "fill", "none"

SVG

{
  "type": "svg",
  "src": "logo.svg",
  "size": { "width": 200, "height": 200 }
}

Or with inline SVG:

{
  "type": "svg",
  "data": "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='red'/></svg>"
}
Field Type Default Description
src string Path to .svg file
data string Inline SVG markup
size {width, height} Target size (uses SVG intrinsic size if omitted)

One of src or data is required.


Icon

Renders an icon from the Iconify open-source framework (200,000+ icons from 150+ sets). Icons are fetched from the Iconify API at render time.

Browse all available icons at icon-sets.iconify.design.

{
  "type": "icon",
  "icon": "lucide:home",
  "size": { "width": 64, "height": 64 },
  "style": { "color": "#38bdf8" }
}
Field Type Default Description
icon string (required) Iconify identifier "prefix:name" (e.g. "lucide:home", "mdi:account")
size {width, height} 24x24 Icon size in pixels

Style: color (default "#FFFFFF")

Common icon sets:

Prefix Name Best for
lucide Lucide Clean UI icons (default choice)
mdi Material Design Icons Material UI, Android
heroicons Heroicons Tailwind projects
ph Phosphor Modern UI
tabler Tabler Icons Dashboards
simple-icons Simple Icons Brand/company logos
devicon Devicon Programming language logos

Video

Embeds a video clip as a component. Requires ffmpeg on PATH.

{
  "type": "video",
  "src": "clip.mp4",
  "size": { "width": 1080, "height": 1920 },
  "trim_start": 2.0,
  "trim_end": 8.0,
  "playback_rate": 0.5,
  "fit": "cover",
  "volume": 0.0
}
Field Type Default Description
src string (required) Path to video file
size {width, height} (required) Display size
trim_start f64 0.0 Start offset in the source clip (seconds)
trim_end f64 End offset in the source clip (seconds)
playback_rate f64 1.0 Playback speed (0.5 = half speed, 2.0 = double)
fit string "cover" "cover", "contain", "fill"
volume f32 1.0 Audio volume (0.0 = mute)
loop_video bool Loop the clip

GIF

Displays an animated GIF, synced to the scene timeline.

{
  "type": "gif",
  "src": "animation.gif",
  "size": { "width": 300, "height": 300 },
  "fit": "cover"
}
Field Type Default Description
src string (required) Path to .gif file
size {width, height} Display size (uses GIF native size if omitted)
fit string "cover" "cover", "contain", "fill"
loop_gif bool true Loop the GIF animation

Caption

Timed word-by-word captions with active word highlighting.

{
  "type": "caption",
  "words": [
    { "text": "Hello", "start": 0.0, "end": 0.5 },
    { "text": "world!", "start": 0.5, "end": 1.0 }
  ],
  "mode": "highlight",
  "active_color": "#FFD700",
  "max_width": 900,
  "style": { "font-size": 48, "color": "#FFFFFF" }
}
Field Type Default Description
words array (required) [{ "text", "start", "end" }]
mode enum "default" "default", "highlight", "karaoke", "bounce"
active_color string "#FFD700" Active word color
max_width f32 Maximum width before word-wrapping

Style: font-size (48.0), font-family, color (#FFFFFF)


Counter

Animated number counter. Must be used standalone (not inside a card).

{
  "type": "counter",
  "from": 0,
  "to": 1250,
  "decimals": 0,
  "separator": " ",
  "suffix": "",
  "easing": "ease_out",
  "start_at": 0.5,
  "end_at": 2.5,
  "style": {
    "font-size": 72,
    "color": "#FFFFFF",
    "font-weight": "bold",
    "text-align": "center"
  }
}
Field Type Default Description
from f64 (required) Start value
to f64 (required) End value
decimals u8 0 Number of decimal places
separator string Thousands separator (e.g. " ", ",")
prefix string Text before the number (e.g. "$")
suffix string Text after the number (e.g. "%", "€")
easing string "linear" Easing for the counter interpolation

Style: font-size, color, font-family, font-weight, text-align, letter-spacing, text-shadow, stroke


Positioned

Container that places children at fixed absolute coordinates (like Flutter's Stack/Positioned). Each child uses position: {x, y} relative to the container's top-left.

{
  "type": "positioned",
  "children": [
    { "type": "shape", "shape": "rect", "position": { "x": 0, "y": 0 }, "size": { "width": 400, "height": 300 }, "style": { "fill": "#1E293B", "border-radius": 16 } },
    { "type": "icon", "icon": "lucide:phone-off", "position": { "x": 170, "y": 120 }, "size": { "width": 64, "height": 64 }, "style": { "color": "#FFFFFF" } }
  ]
}

Container

Invisible wrapper that groups children for shared transforms (scale, opacity, rotation, etc.). Like an HTML <div> with no visual styling. When you scale a container, all children scale together from the container's center — no overlap or distortion.

Use container instead of card when you don't need background, border, or shadow.

{
  "type": "container",
  "size": { "width": "auto", "height": "auto" },
  "style": {
    "align-items": "center",
    "gap": 36,
    "timeline": [
      { "at": 3.5, "animation": [{ "name": "keyframes", "keyframes": [
        { "property": "scale", "keyframes": [{ "time": 0, "value": 1 }, { "time": 0.8, "value": 4 }], "easing": "ease_in" },
        { "property": "opacity", "keyframes": [{ "time": 0, "value": 1 }, { "time": 0.7, "value": 0 }], "easing": "ease_in" }
      ]}]}
    ]
  },
  "children": [
    { "type": "icon", "icon": "lucide:zap", "size": { "width": 80, "height": 80 }, "style": { "color": "#25D366" } },
    { "type": "text", "content": "All children scale together", "style": { "font-size": 48, "color": "#FFFFFF" } }
  ]
}

Supports all flex layout properties (flex-direction, align-items, justify-content, gap, padding) and all style properties (animation, timeline, opacity, margin). No background, border, box-shadow, or clipping — children can overflow freely.


Card / Flex

Visual container with CSS-like flex & grid layout. flex is an alias for card. Each dimension of size can be a number or "auto".

Flex example:

{
  "type": "card",
  "size": { "width": 800, "height": "auto" },
  "style": {
    "flex-direction": "row",
    "align-items": "center",
    "gap": 16,
    "padding": 24,
    "background": "#1E293B",
    "border-radius": 16,
    "animation": [{ "name": "fade_in_up", "delay": 0.3, "duration": 0.6 }]
  },
  "children": [
    { "type": "icon", "icon": "lucide:check-circle", "size": { "width": 48, "height": 48 }, "style": { "color": "#22C55E" } },
    { "type": "text", "content": "Feature enabled", "style": { "font-size": 32, "color": "#FFFFFF" } }
  ]
}

Grid example (2x2): Grid containers need an explicit height (not "auto") to prevent rows from stretching.

{
  "type": "card",
  "size": { "width": 600, "height": 400 },
  "style": {
    "display": "grid",
    "grid-template-columns": [{ "fr": 1 }, { "fr": 1 }],
    "grid-template-rows": [{ "fr": 1 }, { "fr": 1 }],
    "gap": 16,
    "padding": 24,
    "background": "#1a1a2e"
  },
  "children": [
    { "type": "text", "content": "Cell 1", "style": { "color": "#FFFFFF" } },
    { "type": "text", "content": "Cell 2", "style": { "color": "#FFFFFF" } },
    { "type": "text", "content": "Cell 3", "style": { "color": "#FFFFFF" } },
    { "type": "text", "content": "Cell 4", "style": { "color": "#FFFFFF" } }
  ]
}

Style fields:

Style field Type Default Description
display enum "flex" "flex" or "grid"
background string Background color (hex)
border-radius f32 12.0 Corner radius
border object { "color": "#E5E7EB", "width": 1 }
box-shadow object { "color": "#00000040", "offset_x": 0, "offset_y": 4, "blur": 12 }
padding f32 | object Inner spacing
flex-direction enum "column" "column", "row", "column_reverse", "row_reverse"
flex-wrap bool false Wrap children to next line
align-items enum "start" "start", "center", "end", "stretch"
justify-content enum "start" "start", "center", "end", "space_between", "space_around", "space_evenly"
gap f32 0 Spacing between children
grid-template-columns array [{"px": N}, {"fr": N}, "auto"]
grid-template-rows array Same format as columns

Per-child layout properties (in child "style"):

  • flex-grow (f32) — default 0
  • flex-shrink (f32) — default 1
  • flex-basis (f32) — defaults to natural size
  • align-self (enum) — "start", "center", "end", "stretch"
  • grid-column (object) — { "start": 1, "span": 2 } (1-indexed)
  • grid-row (object) — { "start": 1, "span": 2 } (1-indexed)

Codeblock

Code block with syntax highlighting, chrome, reveal animations, and animated diff transitions.

{
  "type": "codeblock",
  "code": "fn main() {\n    println!(\"Hello\");\n}",
  "language": "rust",
  "theme": "base16-ocean.dark",
  "show_line_numbers": true,
  "chrome": { "enabled": true, "title": "main.rs" },
  "reveal": { "mode": "typewriter", "start": 0, "duration": 2.5 },
  "style": { "font-size": 18, "border-radius": 12, "padding": 16 },
  "states": [
    {
      "code": "fn main() {\n    println!(\"Hello, world!\");\n}",
      "at": 5.0,
      "duration": 2.0,
      "cursor": { "enabled": true, "blink": true }
    }
  ]
}

Root fields: code (required), language, theme, size, show_line_numbers, chrome, highlights, reveal, states

Style field Type Default
font-family string "JetBrains Mono"
font-size f32 14.0
font-weight enum "normal"
line-height f32 1.5 (multiplier)
background string (uses theme)
border-radius f32 12.0
padding f32 | object 16

Chrome (Title Bar)

Field Type Default Description
chrome.enabled bool true Show the title bar
chrome.title string Title text (e.g. filename)

Line Highlights

{ "highlights": [{ "lines": [2], "color": "#FFFF0022", "start": 3.0, "end": 4.5 }] }

Reveal Animation

Field Type Default Description
reveal.mode string (required) "typewriter" or "line_by_line"
reveal.start f64 0.0 Start time (seconds)
reveal.duration f64 1.0 Duration (seconds)

Code States (Diff Transitions)

Animate between code versions with automatic diff detection.

Field Type Default Description
states[].code string (required) New code content
states[].at f64 (required) Transition start time
states[].duration f64 0.6 Transition duration
states[].cursor.enabled bool true Show editing cursor
states[].cursor.blink bool true Blink the cursor

Available Themes (72)

Syntect built-in: base16-ocean.dark, base16-ocean.light, base16-eighties.dark, base16-mocha.dark, InspiredGitHub, Solarized (dark), Solarized (light)

Catppuccin: catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha

Shiki / VS Code: andromeeda, aurora-x, ayu-dark, ayu-light, ayu-mirage, dark-plus, dracula, dracula-soft, everforest-dark, everforest-light, github-dark, github-dark-default, github-dark-dimmed, github-dark-high-contrast, github-light, github-light-default, github-light-high-contrast, gruvbox-dark-hard, gruvbox-dark-medium, gruvbox-dark-soft, gruvbox-light-hard, gruvbox-light-medium, gruvbox-light-soft, horizon, horizon-bright, houston, kanagawa-dragon, kanagawa-lotus, kanagawa-wave, laserwave, light-plus, material-theme, material-theme-darker, material-theme-lighter, material-theme-ocean, material-theme-palenight, min-dark, min-light, monokai, night-owl, night-owl-light, nord, one-dark-pro, one-light, plastic, poimandres, red, rose-pine, rose-pine-dawn, rose-pine-moon, slack-dark, slack-ochin, snazzy-light, solarized-dark, solarized-light, synthwave-84, tokyo-night, vesper, vitesse-black, vitesse-dark, vitesse-light

Divider

Visual separator line (horizontal or vertical).

{
  "type": "divider",
  "direction": "horizontal",
  "thickness": 2,
  "line_style": "solid",
  "style": { "color": "#4B5563" }
}
Field Type Default Description
direction enum "horizontal" "horizontal" or "vertical"
thickness f32 2.0 Line thickness in pixels
line_style enum "solid" "solid", "dashed", "dotted"
length f32 Fixed length (omit for 100% of parent)

Style: color (default "#FFFFFF")


Badge

Compact label with text and optional icon, pill-shaped.

{
  "type": "badge",
  "text": "New",
  "icon": "lucide:star",
  "variant": "solid",
  "badge_size": "md",
  "style": { "background": "#3B82F6" }
}
Field Type Default Description
text string (required) Badge text
icon string Iconify icon id (e.g. "lucide:star")
variant enum "solid" "solid" (filled) or "outline" (border only)
badge_size enum "md" "sm", "md", "lg"

Style: background (default "#3B82F6") — badge color for solid variant or border color for outline, font-size, font-family


Avatar

Circular image with optional border and status indicator.

{
  "type": "avatar",
  "src": "photo.jpg",
  "size": 80,
  "border_color": "#3B82F6",
  "border_width": 3,
  "status": "online"
}
Field Type Default Description
src string (required) Path to image file
size f32 64.0 Diameter in pixels
border_color string Border color (hex)
border_width f32 0.0 Border thickness
status enum "none" "online", "offline", "away", "none"
status_color string Override status dot color

Status colors: online=#22C55E, offline=#9CA3AF, away=#F59E0B


Callout

Speech bubble with directional arrow.

{
  "type": "callout",
  "text": "Hello!",
  "arrow_direction": "bottom",
  "style": {
    "background": "#333333",
    "color": "#FFFFFF",
    "border-radius": 8,
    "font-size": 16
  }
}
Field Type Default Description
text string (required) Callout text
arrow_direction enum "bottom" "top", "bottom", "left", "right"
arrow_size f32 12.0 Arrow triangle size
size {width, height} Fixed size (auto-sized if omitted)

Style: background (default "#333333"), color (default "#FFFFFF"), border-radius (default 8), font-size (default 16), font-family


Terminal

Terminal/console window with colored lines and optional chrome.

{
  "type": "terminal",
  "title": "Terminal",
  "theme": "dark",
  "reveal": { "mode": "typewriter", "start": 0.5, "duration": 3.0 },
  "lines": [
    { "text": "npm install", "line_type": "prompt" },
    { "text": "added 42 packages", "line_type": "output" }
  ],
  "size": { "width": 600, "height": 300 }
}
Field Type Default Description
lines array (required) [{ "text", "line_type", "color" }]
theme enum "dark" "dark" or "light"
title string Window title
show_chrome bool true Show title bar with traffic light dots
reveal object { "mode": "typewriter"|"line_by_line", "start": 0, "duration": 1, "easing": "linear" }
size {width, height} Terminal size (default 500x auto)

Line types: "prompt" (shows $ prefix in green), "command" (white text), "output" (gray text)

Reveal modes: "typewriter" reveals characters one by one, "line_by_line" fades lines in sequentially.


Table

Data table with headers and styled rows.

{
  "type": "table",
  "headers": ["Name", "Role", "Status"],
  "rows": [
    ["Alice", "Engineer", "Active"],
    ["Bob", "Designer", "Away"]
  ],
  "size": { "width": 600, "height": 200 },
  "style": { "color": "#FFFFFF", "font-size": 14 }
}
Field Type Default Description
headers string[] (required) Column headers
rows string[][] (required) Data rows
header_color string "#374151" Header row background
row_colors string[] ["#1F2937", "#111827"] Alternating row colors
border_color string "#4B5563" Grid line color
header_text_color string "#FFFFFF" Header text color
size {width, height} Table size

Style: color (default "#FFFFFF") — cell text color, font-size (default 14), font-family, border-radius


Chart

Data visualization with bar, line, or pie charts. Animated by default.

{
  "type": "chart",
  "chart_type": "bar",
  "data": [
    { "value": 85, "label": "Q1" },
    { "value": 120, "label": "Q2" },
    { "value": 95, "label": "Q3" },
    { "value": 150, "label": "Q4" }
  ],
  "size": { "width": 400, "height": 300 }
}
Field Type Default Description
chart_type enum (required) "bar", "line", "pie"
data array (required) [{ "value", "label"?, "color"? }]
size {width, height} 300x200 Chart size
animated bool true Animate chart fill/draw
animation_duration f64 1.5 Animation duration in seconds
colors string[] Custom color palette (hex)

Default palette: #3B82F6, #EF4444, #22C55E, #F59E0B, #8B5CF6, #EC4899, #06B6D4, #F97316


Mockup

Device frame (phone, laptop, browser) with image content inside.

{
  "type": "mockup",
  "device": "iphone",
  "src": "screenshot.png",
  "theme": "dark",
  "size": { "width": 375, "height": 812 }
}
Field Type Default Description
device enum (required) "iphone", "android", "laptop", "browser"
src string (required) Path to content image
theme enum "dark" "dark" or "light" bezel color
size {width, height} Device size (defaults: iPhone 375x812, Android 360x800, Laptop 800x550, Browser 800x600)

Particle

Animated particle system for visual effects (confetti, snow, stars, bubbles).

{
  "type": "particle",
  "particle_type": "confetti",
  "count": 80,
  "speed": 1.2,
  "seed": 42
}
Field Type Default Description
particle_type enum (required) "confetti", "snow", "stars", "bubbles", "halo"
count u32 50 Number of particles
colors string[] Custom colors (defaults vary by type)
speed f32 1.0 Speed multiplier
size_range {min, max} {4, 12} Particle size range in pixels
seed u64 42 Random seed for reproducible results

Particle behaviors:

  • confetti: colored rectangles falling with rotation and horizontal wobble
  • snow: white circles falling gently with lateral drift
  • stars: fixed positions with twinkling opacity
  • bubbles: semi-transparent circles rising with oscillation
  • halo: soft glowing circles drifting slowly with pulsing opacity (great for backgrounds)

Arrow

Directional arrow with optional bezier curves. Use draw_in or stroke_reveal animation presets for drawing effects.

{
  "type": "arrow",
  "x1": 100, "y1": 300,
  "x2": 500, "y2": 300,
  "curve": 0.3,
  "width": 3,
  "color": "#58A6FF",
  "arrow_end": true,
  "style": {
    "animation": [{ "name": "draw_in", "duration": 1.0 }]
  }
}
Field Type Default Description
x1, y1 f32 0.0 Start point
x2, y2 f32 (required) End point
cp {x, y} Quadratic bezier control point
cp1, cp2 {x, y} Cubic bezier control points
curve f32 Auto-curve (-1.0 to 1.0)
width f32 3.0 Stroke width
color string "#FFFFFF" Arrow color
arrow_end bool true Arrowhead at end
arrow_start bool false Arrowhead at start
arrow_size f32 12.0 Arrowhead size
dashed f32[] Dash pattern (e.g. [8, 4])

Connector

Connects two points with automatic routing. Great for diagrams and flowcharts.

{
  "type": "connector",
  "from": { "x": 200, "y": 150 },
  "to": { "x": 600, "y": 400 },
  "routing": "curved",
  "color": "#58A6FF",
  "arrow_end": true,
  "style": {
    "animation": [{ "name": "stroke_reveal", "duration": 0.8 }]
  }
}
Field Type Default Description
from {x, y} (required) Start point
to {x, y} (required) End point
routing string "straight" "straight", "curved", "elbow" (L-shaped)
curvature f32 0.4 Curve intensity (for curved routing)
width f32 2.0 Stroke width
color string "#FFFFFF" Line color
arrow_end bool true Arrowhead at end
arrow_start bool false Arrowhead at start
arrow_size f32 10.0 Arrowhead size
dashed f32[] Dash pattern

Timeline

Step-by-step timeline with animated progress bar and node icons.

{
  "type": "timeline",
  "width": 800,
  "direction": "horizontal",
  "fill_progress": 0.75,
  "steps": [
    { "label": "Design", "sublabel": "Week 1", "color": "#58A6FF", "icon": "1" },
    { "label": "Build", "sublabel": "Week 2-3", "color": "#58A6FF", "icon": "2" },
    { "label": "Ship", "sublabel": "Week 4", "color": "#22C55E", "icon": "🚀" }
  ]
}
Field Type Default Description
steps array (required) [{ "label", "sublabel"?, "color"?, "icon"? }]
width f32 800.0 Timeline width
direction string "horizontal" "horizontal" or "vertical"
node_radius f32 24.0 Circle radius
bar_color string "#333333" Background bar color
bar_fill_color string "#58A6FF" Filled bar color
fill_progress f32 1.0 Progress 0.0 to 1.0

Lottie

Renders Lottie animations from pre-rendered PNG frame sequences.

{
  "type": "lottie",
  "src": "animation.json",
  "frames_dir": "/path/to/frames",
  "size": { "width": 300, "height": 300 },
  "speed": 1.0,
  "loop": true
}
Field Type Default Description
src string Path to Lottie JSON (for timing metadata)
data string Inline Lottie JSON data
frames_dir string Directory with numbered PNGs (0000.png, 0001.png, ...)
size {width, height} Display size (falls back to Lottie intrinsic size)
speed f32 1.0 Playback speed multiplier
loop bool true Loop the animation

Generate frames with: npx lottie-to-frames animation.json --output frames/


Cursor

Animated cursor with click effects, blinking, and smooth path animation.

{
  "type": "cursor",
  "color": "#FFFFFF",
  "blink": 0.5,
  "auto_path": [
    { "time": 0.5, "x": 100, "y": 200 },
    { "time": 1.5, "x": 400, "y": 300 },
    { "time": 2.5, "x": 600, "y": 150 }
  ],
  "path_easing": "ease_in_out",
  "position": { "x": 200, "y": 200 }
}
Field Type Default Description
width f32 3.0 Cursor width
height f32 40.0 Cursor height
color string "#FFFFFF" Cursor color
blink f32 0.5 Blink cycle (0 = no blink)
click_at f64[] [] Click animation times (seconds)
auto_path array [] Waypoints: [{ "time", "x", "y" }]
click_duration f32 0.3 Click animation duration
path_easing string "ease_in_out" "linear", "ease_out", "ease_in_out"

When auto_path is set, clicks trigger at each waypoint. Uses Catmull-Rom spline interpolation for smooth curves.


Line

Simple line from point A to point B. Supports draw_in animation.

{
  "type": "line",
  "x1": 0, "y1": 0,
  "x2": 400, "y2": 200,
  "width": 2,
  "color": "#58A6FF",
  "style": { "animation": [{ "name": "draw_in", "duration": 0.8 }] }
}
Field Type Default Description
x1, y1 f32 0.0 Start point
x2, y2 f32 (required) End point
width f32 2.0 Stroke width
color string "#FFFFFF" Line color
dashed f32[] Dash pattern

Rich Text

Multi-styled text with individually styled spans. Each span inherits unset properties from the component's style.

{
  "type": "rich_text",
  "spans": [
    { "text": "Hello ", "color": "#FFFFFF", "font-weight": "bold" },
    { "text": "World", "color": "#58A6FF", "font-size": 64 }
  ],
  "max_width": 800,
  "style": { "font-size": 48, "color": "#FFFFFF" }
}
Field Type Default Description
spans array (required) [{ "text", "color"?, "font-size"?, "font-weight"?, "font-family"?, "font-style"?, "letter-spacing"? }]
max_width f32 Maximum width before wrapping

Scene-Level Features

Virtual Camera

Scenes support a virtual camera with animatable pan, zoom, and rotation.

{
  "duration": 5.0,
  "camera": {
    "x": 0, "y": 0, "zoom": 1.0, "rotation": 0,
    "keyframes": [
      { "property": "zoom", "values": [{ "time": 0, "value": 1.0 }, { "time": 3, "value": 1.5 }], "easing": "ease_in_out" },
      { "property": "x", "values": [{ "time": 0, "value": 0 }, { "time": 3, "value": -100 }], "easing": "ease_out" }
    ]
  },
  "children": [...]
}
Field Type Default Description
x f32 0.0 Camera center X offset
y f32 0.0 Camera center Y offset
zoom f32 1.0 Zoom factor (2.0 = 2x in)
rotation f32 0.0 Rotation in degrees
keyframes array [] [{ "property", "values": [{"time", "value"}], "easing" }]

Animated Background

Scenes can have animated gradient backgrounds. Gradients are interpolated in linear color space with subdivided color stops for smooth dark transitions.

{
  "duration": 5.0,
  "animated-background": {
    "colors": ["#667eea", "#764ba2", "#f093fb"],
    "speed": 30,
    "gradient_type": "linear"
  },
  "children": [...]
}
Field Type Default Description
colors string[] [] Gradient colors (hex)
speed f32 30.0 Animation speed
gradient_type string "linear" "linear" or "radial"
preset string "gradient_shift", "concentric_circles", "grid_dots"
element_size f32 4.0 Dot size for grid_dots; stroke width for concentric_circles
spacing f32 60.0 Element spacing for grid_dots/concentric_circles
count u32 Number of circles for concentric_circles (overrides spacing)

Animations

All animation effects are defined inside style.animation as a typed array, each discriminated by "name". A single effect (without array) is also accepted.

{
  "style": {
    "animation": [
      { "name": "fade_in_up", "delay": 0.2, "duration": 0.8 },
      { "name": "glow", "color": "#6366F1", "radius": 20, "intensity": 2.0 },
      { "name": "wiggle", "property": "translate_y", "amplitude": 5, "frequency": 0.8, "seed": 42 }
    ]
  }
}

Effect Types

Effect name Fields Description
preset name delay, duration, loop, overshoot Any of the 39 presets (e.g. fade_in_up, scale_in)
char preset delay, duration, stagger, granularity, easing, overshoot Per-char/word text animation: char_scale_in, char_fade_in, char_wave, char_bounce, char_rotate_in, char_slide_up
glow color, radius, intensity Luminous halo effect
wiggle property, amplitude, frequency, mode, seed, ... Procedural noise animation
orbit radius_x, radius_y, speed, depth, tilt, ... Elliptical orbital motion with pseudo-3D
keyframes keyframes Custom keyframe animations
motion_blur intensity Motion blur effect

Components also support a timeline field (array of { "at": f64, "animation": [...] } steps) for multi-phase sequential animations within a scene.

Animation Presets

{
  "style": {
    "animation": [{ "name": "fade_in_up", "delay": 0.2, "duration": 0.8, "loop": false }]
  }
}
Field Type Default Description
delay f64 0.0 Delay before animation starts (seconds)
duration f64 0.8 Animation duration (seconds)
loop bool false Loop the animation continuously
overshoot f64 0.08 Overshoot/anticipation intensity for scale_in/scale_out (0.0 = none)

Entrance Presets

Preset Description
fade_in Fade from transparent
fade_in_up Fade in + slide up
fade_in_down Fade in + slide down
fade_in_left Fade in + slide from left
fade_in_right Fade in + slide from right
slide_in_left Slide in from far left
slide_in_right Slide in from far right
slide_in_up Slide in from below
slide_in_down Slide in from above
scale_in Scale up from 0 with overshoot (configurable via overshoot, default 8%)
bounce_in Bouncy scale from small to normal
blur_in Fade in from blurred
rotate_in Rotate + scale from half size
elastic_in Elastic underdamped spring scale

Exit Presets

Preset Description
fade_out Fade to transparent
fade_out_up Fade out + slide up
fade_out_down Fade out + slide down
slide_out_left Slide out to the left
slide_out_right Slide out to the right
slide_out_up Slide out upward
slide_out_down Slide out downward
scale_out Scale down to 0 with anticipation (configurable via overshoot, default 8%)
bounce_out Bouncy scale to small
blur_out Fade out with blur
rotate_out Rotate + scale to half size

Continuous Presets

Use "loop": true for continuous animation:

Preset Description
pulse Gentle scale oscillation
float Vertical floating motion
shake Horizontal shake
spin 360-degree continuous rotation
float_3d Floating + subtle 3D rotation with perspective

3D Presets

Preset Description
flip_in_x 3D flip around X axis (card flip from top)
flip_in_y 3D flip around Y axis (card flip from side)
flip_out_x 3D flip out around X axis
flip_out_y 3D flip out around Y axis
tilt_in 3D tilt entrance (rotate_x + rotate_y)

Stroke Presets

For arrows, connectors, and lines:

Preset Description
draw_in Animate draw_progress from 0 to 1 (stroke drawing effect)
stroke_reveal draw_in + fade-in opacity over first 20% of duration

Special Presets

Preset Description
typewriter Progressive character reveal
wipe_left Slide in from left with fade
wipe_right Slide in from right with fade

Custom Keyframe Animations

{
  "style": {
    "animation": [
      {
        "name": "keyframes",
        "keyframes": [
          {
            "property": "opacity",
            "keyframes": [
              { "time": 0.0, "value": 0.0 },
              { "time": 0.5, "value": 1.0 }
            ],
            "easing": "ease_out"
          }
        ]
      }
    ]
  }
}

Animatable properties: opacity, translate_x, translate_y, scale_x, scale_y, scale (both axes), rotation, blur, color, rotate_x, rotate_y, perspective

11 easing functions: linear, ease_in, ease_out, ease_in_out, ease_in_quad, ease_out_quad, ease_in_cubic, ease_out_cubic, ease_in_expo, ease_out_expo, spring

Spring physics (when easing is spring):

{
  "easing": "spring",
  "spring": { "damping": 15, "stiffness": 100, "mass": 1 }
}

Glow

{
  "style": {
    "animation": [
      { "name": "glow", "color": "#ff00ff", "radius": 20, "intensity": 2.5 }
    ]
  }
}
Field Type Default Description
color string "#FFFFFF" Glow color (hex)
radius f32 10.0 Blur radius
intensity f32 1.0 Brightness multiplier

Wiggle (Procedural Noise)

Wiggle adds continuous organic movement. Offsets are applied additively on top of presets and keyframes.

{
  "style": {
    "animation": [
      { "name": "wiggle", "property": "translate_x", "amplitude": 5, "frequency": 3, "seed": 42 },
      { "name": "wiggle", "property": "rotation", "amplitude": 2, "frequency": 2, "seed": 99, "decay": 0.5 }
    ]
  }
}
Field Type Default Description
property string (required) Property to wiggle (same as animatable properties)
amplitude f64 (required) Maximum deviation (pixels for translate, degrees for rotation)
frequency f64 (required) Cycles per second (Hz)
mode string "noise" "noise" (layered simplex) or "sine" (pure sine wave)
seed u64 0 Random seed for reproducible results (noise mode only)
octaves u32 3 Noise complexity (noise mode only)
phase f64 0.0 Phase offset
decay f64 Exponential decay rate
easing string Remap noise through an easing curve

Orbit (Circular/Elliptical Motion)

Creates continuous circular or elliptical orbital motion with pseudo-3D depth. Applied additively like wiggle.

{
  "style": {
    "animation": [
      { "name": "orbit", "radius_x": 30, "radius_y": 20, "speed": 0.5, "depth": 0.15, "tilt": 20 }
    ]
  }
}
Field Type Default Description
radius_x f64 30.0 Horizontal orbit radius
radius_y f64 30.0 Vertical orbit radius
speed f64 0.5 Revolutions per second
start_angle f64 0.0 Starting angle in degrees
depth f64 0.15 Scale modulation for pseudo-3D
opacity_depth f64 0.0 Opacity modulation for depth
tilt f64 0.0 Orbit plane tilt in degrees
phase f64 0.0 Phase offset (0.0 to 1.0)

Per-Character / Per-Word Text Animation

Animate each character or word independently with staggered timing. Use char_* animation presets inside style.animation on text components.

Char animation presets: char_scale_in, char_fade_in, char_wave, char_bounce, char_rotate_in, char_slide_up

{
  "type": "text",
  "content": "Hello World",
  "style": {
    "font-size": 64, "color": "#FFFFFF",
    "animation": [{ "name": "char_scale_in", "stagger": 0.03, "duration": 0.4, "delay": 0.2 }]
  }
}
Field Type Default Description
stagger f64 0.03 Delay between each unit (seconds)
duration f64 0.4 Each unit's animation duration
delay f64 0.0 Initial delay before first unit
easing string "linear" Easing function
granularity string "char" "char" (per-character) or "word" (per-word)
overshoot f64 0.08 Overshoot intensity for char_scale_in/char_bounce (0.0 = none)

Per-word mode ("granularity": "word") splits text by whitespace and animates each word as a unit. Use larger stagger values (0.1–0.3s) for word reveals:

{
  "type": "text",
  "content": "One platform to rule them all",
  "style": {
    "font-size": 56, "color": "#FFFFFF", "font-weight": "bold",
    "animation": [{ "name": "char_fade_in", "stagger": 0.15, "duration": 0.5, "granularity": "word" }]
  }
}

3D Perspective Transforms

Any component can be rendered with true 3D perspective using keyframe animations on rotate_x, rotate_y, and perspective properties:

{
  "style": {
    "box-shadow": { "color": "#00000060", "offset_x": 0, "offset_y": 20, "blur": 60 },
    "animation": [{
      "name": "keyframes",
      "keyframes": [
        { "property": "rotate_x", "keyframes": [{ "time": 0, "value": 20 }, { "time": 2, "value": 8 }], "easing": "ease_out" },
        { "property": "rotate_y", "keyframes": [{ "time": 0, "value": -15 }, { "time": 2, "value": -5 }], "easing": "ease_out" },
        { "property": "perspective", "keyframes": [{ "time": 0, "value": 800 }, { "time": 2, "value": 800 }], "easing": "linear" }
      ]
    }]
  }
}

Uses a Skia M44 4x4 matrix for real 3D rendering. Components with box-shadow get 3D adaptive shadows — the shadow automatically shifts and scales based on tilt angles.

Timeline Sequencing

Define sequential animation phases within a single scene using the timeline field:

{
  "style": {
    "animation": [{ "name": "fade_in_up", "duration": 0.6 }],
    "timeline": [
      { "at": 2.0, "animation": [{ "name": "shake", "duration": 0.5 }] },
      { "at": 4.0, "animation": [{ "name": "fade_out", "duration": 0.8 }] }
    ]
  }
}

Each step activates at step.at seconds, with animations resolved relative to that time. Steps merge additively with base animations.

Motion Blur

{
  "style": {
    "animation": [
      { "name": "motion_blur", "intensity": 0.8 }
    ]
  }
}

Renders multiple sub-frames and composites them for physically-correct motion blur.


Output Formats

Format Command Requires
MP4 (H.264 10-bit) rustmotion render in.json -o out.mp4 ffmpeg (auto-detected)
MP4 (H.264 8-bit) rustmotion render in.json -o out.mp4 Built-in (fallback without ffmpeg)
MP4 (H.265) rustmotion render in.json -o out.mp4 --codec h265 ffmpeg
WebM (VP9) rustmotion render in.json -o out.webm --codec vp9 ffmpeg
MOV (ProRes) rustmotion render in.json -o out.mov --codec prores ffmpeg
Animated GIF rustmotion render in.json -o out.gif --format gif Built-in
PNG Sequence rustmotion render in.json -o frames/ --format png-seq Built-in
Single Frame rustmotion render in.json --frame 0 -o preview.png Built-in

Transparency is supported with --transparent for PNG sequences, WebM (VP9), and ProRes 4444.

Gradient quality: When ffmpeg is available, H.264 uses 10-bit color depth (yuv420p10le, high10 profile) which greatly reduces banding on dark gradients. For maximum quality, use --codec prores. The built-in openh264 encoder (fallback without ffmpeg) outputs 8-bit only.


Full Example

{
  "version": "1.0",
  "video": {
    "width": 1080,
    "height": 1920,
    "fps": 30,
    "background": "#0f172a"
  },
  "scenes": [
    {
      "duration": 4.0,
      "layout": { "align_items": "center", "justify_content": "center", "gap": 32 },
      "children": [
        {
          "type": "shape",
          "shape": "rounded_rect",
          "size": { "width": 900, "height": 520 },
          "style": {
            "fill": {
              "type": "linear",
              "colors": ["#6366f1", "#8b5cf6"],
              "angle": 135
            },
            "border-radius": 32,
            "animation": [{ "name": "scale_in", "duration": 0.6 }]
          }
        },
        {
          "type": "icon",
          "icon": "lucide:rocket",
          "size": { "width": 80, "height": 80 },
          "style": {
            "color": "#FFFFFF",
            "animation": [{ "name": "fade_in_up", "delay": 0.3, "duration": 0.6 }]
          }
        },
        {
          "type": "text",
          "content": "Ship Faster",
          "style": {
            "font-size": 64,
            "color": "#FFFFFF",
            "font-weight": "bold",
            "text-align": "center",
            "animation": [{ "name": "fade_in_up", "delay": 0.5, "duration": 0.6 }]
          }
        },
        {
          "type": "text",
          "content": "Build motion videos in Rust.\nNo browser needed.",
          "max_width": 700,
          "style": {
            "font-size": 32,
            "color": "#CBD5E1",
            "text-align": "center",
            "line-height": 1.5,
            "animation": [{ "name": "fade_in_up", "delay": 0.7, "duration": 0.6 }]
          }
        }
      ]
    },
    {
      "duration": 3.0,
      "background": "#1e293b",
      "transition": { "type": "iris", "duration": 0.8 },
      "layout": { "align_items": "center", "justify_content": "center" },
      "children": [
        {
          "type": "text",
          "content": "No browser needed.",
          "style": {
            "font-size": 56,
            "color": "#e2e8f0",
            "text-align": "center",
            "animation": [{ "name": "typewriter", "duration": 1.5 }]
          }
        }
      ]
    }
  ]
}

Architecture

  • Rendering: skia-safe (same engine as Chrome/Flutter)
  • Video encoding: openh264 (Cisco BSD, compiled from source) + ffmpeg (optional, for H.265/VP9/ProRes)
  • Audio encoding: AAC via minimp4
  • SVG rendering: resvg + usvg
  • Icon rendering: Iconify API (200k+ icons)
  • GIF decoding/encoding: gif crate
  • MP4 muxing: minimp4
  • JSON Schema: schemars (auto-generated from Rust types)
  • Parallelism: rayon (multi-threaded frame rendering)

License

MIT

About

A CLI tool that renders motion design videos from JSON scenarios. No browser, no Node.js — just a single Rust binary.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages