A Rust CLI tool for generating algorithmic art videos. Create dynamic backgrounds, B-roll footage, and experimental visuals using procedural generation.
- Layer-based compositing - Everything is a layer, composited with alpha blending
- Parallel rendering - Uses all CPU cores for fast frame generation
- Hardware encoding - M1/Apple Silicon accelerated H.264/HEVC via VideoToolbox
- Reproducible output - Same seed always produces identical results
- iMovie compatible - Output works directly in Apple video editors
Requires Rust and ffmpeg:
# Install ffmpeg (macOS)
brew install ffmpeg
# Build the project
cargo build --release
# The binary is at ./target/release/vidgen# Simple gradient background
vidgen render -d 5 -p '{"background":"#1a1a2e","layers":[{"type":"linear","properties":{"colors":["#e94560","#0f3460"],"angle":45},"animate":{"offset":{"cycles":1}}}]}'
# Preview frames (no video encoding)
vidgen preview -c 5 -p config.json
# From config file
vidgen render -p config.json -o background.mp4# Basic usage - 10 second 1080p video
vidgen render -p config.json -o my_video.mp4
# Quick test at SD resolution
vidgen render -r sd -d 3 -p config.json -o test.mp4
# 4K video with custom duration
vidgen render -r uhd4k -d 30 -p config.json -o background_4k.mp4# Generate 5 sample frames as PNGs
vidgen preview -r hd1080 -c 5 -p config.json -o preview/| Option | Description | Default |
|---|---|---|
-o, --output |
Output file path | output.mp4 |
-r, --resolution |
sd, hd720, hd1080, uhd4k | hd1080 |
-d, --duration |
Duration in seconds | 10 |
--fps |
Frames per second | 30 |
-s, --seed |
Random seed for reproducibility | 42 |
-q, --quality |
Quality (18-28, lower=better) | 23 |
-w, --workers |
Parallel workers (reduces memory usage) | CPU cores |
-p, --params |
Layer config (JSON file or inline) | "" |
--codec |
Video codec: h264 (faster) or h265 (smaller) | h264 |
--fast |
Prioritize encoding speed over compression | false |
--software-encode |
Use libx264/libx265 instead of VideoToolbox | false |
# Default (H.264) is already fast; add --fast for maximum speed
vidgen render --fast -p config.json -o fast.mp4
# Use H.265 for smaller files (slower encoding)
vidgen render --codec h265 -p config.json -o smaller.mp4
# Reduce memory usage on machines with limited RAM (e.g., 8GB Mac)
vidgen render -r hd1080 -w 2 -p config.json -o video.mp4| Type | Purpose | Key Properties |
|---|---|---|
solid |
Flat color fill | color, opacity |
linear |
Directional gradient | colors, angle, length, offset, opacity |
radial |
Circular gradient | colors, center, length, offset, opacity |
blur |
Gaussian blur of layers below | radius, amount |
flowfield |
Perlin noise particle trails | particles, scale, cycles, opacity |
{
"background": "#0a0a12",
"layers": [
{
"type": "linear",
"properties": {
"colors": ["#1a1a2e", "#16213e", "#0f3460", "#1a1a2e"],
"angle": 45,
"length": 0.8
},
"animate": {
"offset": { "cycles": 2 }
}
},
{
"type": "radial",
"properties": {
"colors": ["#e9456050", "#53348350", "#e9456050"],
"center": [0.3, 0.5],
"length": 0.5
},
"animate": {
"offset": { "cycles": -1, "curve": "sine" }
}
}
]
}Each layer has three top-level keys:
type- Layer type (see table above)properties- Static propertiesanimate- Animation modifiers for properties
All layers:
| Property | Description | Default |
|---|---|---|
opacity |
Layer opacity 0.0-1.0 | 1.0 |
Solid:
| Property | Description | Default |
|---|---|---|
color |
Fill color: "#RRGGBB" or "#RRGGBBAA" |
#000000 |
Linear gradient:
| Property | Description | Default |
|---|---|---|
colors |
Color stops (see below) | [] |
angle |
Gradient angle in degrees, 0=right, 90=down | 0 |
length |
Gradient cycle length (1.0 = larger image dimension) | 1.0 |
offset |
Position within gradient (0.0-1.0, wraps) | 0.0 |
Radial gradient:
| Property | Description | Default |
|---|---|---|
colors |
Color stops (see below) | [] |
center |
Center point [x, y], 0.5=middle |
[0.5, 0.5] |
length |
Gradient radius (1.0 = larger image dimension) | 1.0 |
offset |
Position within gradient (0.0-1.0, wraps) | 0.0 |
Blur:
| Property | Description | Default |
|---|---|---|
radius |
Blur radius as fraction of width | 0.02 |
amount |
Blur blend amount 0.0-1.0 | 1.0 |
Flowfield:
| Property | Description | Default |
|---|---|---|
particles |
Number of particles per frame | 50000 |
scale |
Noise scale, lower = larger patterns | 0.004 |
cycles |
Animation cycles over video duration | 2.0 |
#RRGGBB- 6-digit hex (opaque)#RRGGBBAA- 8-digit hex with alpha#RRGGBB@30%- with gradient position (0-100%)
The animate dict specifies which properties to animate:
"animate": {
"offset": { "cycles": 1.0, "curve": "linear" },
"opacity": { "cycles": 0.5, "curve": "sine" }
}| Field | Description | Default |
|---|---|---|
cycles |
Complete cycles per video duration (negative = reverse) | 0 |
curve |
Easing: linear, sine, ease-in, ease-out, ease-in-out |
linear |
Animatable properties: offset, angle, length, opacity, radius, amount, cycles
vidgen render -d 10 -p '{
"background": "#0a0a12",
"layers": [{
"type": "linear",
"properties": {"colors": ["#ff6b6b", "#4ecdc4", "#ff6b6b"], "angle": 30, "length": 0.6},
"animate": {"offset": {"cycles": 2, "curve": "sine"}}
}]
}'vidgen render -d 10 -p '{
"background": "#1a1a2e",
"layers": [{
"type": "radial",
"properties": {"colors": ["#e9456080", "#e9456000"], "center": [0.3, 0.4], "length": 0.5},
"animate": {"offset": {"cycles": -1, "curve": "sine"}}
}]
}'vidgen render -d 10 -p '{
"background": "#0a0a14",
"layers": [
{"type": "flowfield", "properties": {"particles": 40000, "scale": 0.003}},
{"type": "blur", "properties": {"radius": 0.005, "amount": 0.3}}
]
}'{
"background": "#0a0a12",
"layers": [
{
"type": "linear",
"properties": {"colors": ["#1a1a2e", "#16213e"], "angle": 45, "length": 1.0},
"animate": {"offset": {"cycles": 0.5}}
},
{
"type": "radial",
"properties": {"colors": ["#e9456040", "#e9456000"], "center": [0.2, 0.3], "length": 0.4},
"animate": {"offset": {"cycles": -1, "curve": "sine"}}
},
{
"type": "radial",
"properties": {"colors": ["#4ecdc440", "#4ecdc400"], "center": [0.8, 0.7], "length": 0.5},
"animate": {"offset": {"cycles": 0.7, "curve": "sine"}}
},
{
"type": "blur",
"properties": {"radius": 0.01, "amount": 0.4}
}
]
}Videos are fully deterministic. Running the same command with the same seed produces byte-identical output:
# These produce identical videos
vidgen render -s 42 -p config.json -o video1.mp4
vidgen render -s 42 -p config.json -o video2.mp4
# Different seed = different video
vidgen render -s 123 -p config.json -o video3.mp4Layers are defined in src/layers/. To add a new layer type:
- Create
src/layers/mylayer.rswith arender()function - Add
mod mylayer;tosrc/layers/mod.rs - Add any new properties to the
Propertiesstruct - Add the type to the match statement in
render_layers() - Document the layer type
The RenderContext provides timing and config info:
impl RenderContext {
// Seamless looping: completes n cycles over video duration
pub fn loop_phase(&self, cycles: f64) -> f64;
// Fixed period animation (may not loop seamlessly)
pub fn periodic_phase(&self, period_secs: f64) -> f64;
// Per-frame seed for reproducible randomness
pub fn frame_seed(&self) -> u64;
}MIT
