rustmotion
A CLI tool that renders motion design videos from JSON scenarios. No browser, no Node.js — just a single Rust binary.
Install
Requirements: Rust toolchain + C++ compiler (for openh264). Optional: ffmpeg CLI for H.265/VP9/ProRes/WebM/GIF output.
Quick Start
# Render a video
# Render with a specific codec
# Export as PNG sequence
# Export as animated GIF
# Render a single frame for preview
# Validate without rendering
# Export JSON Schema (for editor autocompletion or LLM prompts)
# Show scenario info
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
Video Config
| 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
| 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.
| 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.
| 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:
| 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 |
- The included file's
videoconfig is ignored - Audio tracks from included files are merged
- Includes can be nested (max depth: 8)
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
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
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:
Types: linear, radial.
Embedded text in shapes (text field):
vertical_align: "top", "middle", "bottom" (default: "middle").
Image
| 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
Or with inline 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.
| 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.
| 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.
| 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.
| 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).
| 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.
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:
Grid example (2x2): Grid containers need an explicit height (not "auto") to prevent rows from stretching.
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 0flex-shrink(f32) — default 1flex-basis(f32) — defaults to natural sizealign-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.
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
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
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.
Effect Types
| Effect name | Fields | Description |
|---|---|---|
| preset name | delay, duration, loop |
Any of the 31 presets (e.g. fade_in_up, scale_in) |
glow |
color, radius, intensity |
Luminous halo effect |
wiggle |
property, amplitude, frequency, mode, seed, ... |
Procedural noise animation |
keyframes |
keyframes |
Custom keyframe animations |
motion_blur |
intensity |
Motion blur effect |
Animation Presets
| 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 |
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 spring bounce |
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 |
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 |
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
Animatable properties: opacity, translate_x, translate_y, scale_x, scale_y, scale (both axes), rotation, blur, color
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):
Glow
| 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.
| 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 |
Motion Blur
Renders multiple sub-frames and composites them for physically-correct motion blur.
Output Formats
| Format | Command | Requires |
|---|---|---|
| MP4 (H.264) | rustmotion render in.json -o out.mp4 |
Built-in |
| 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.
Full Example
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