Skip to content
+

Rust Backend Examples

High-performance server-side video composition using the Rust compositor library.

Installation

# Cargo.toml
[dependencies]
video-compositor = "0.1"
tokio = { version = "1", features = ["full"] }

Basic Frame Composition

Simple Example

use video_compositor::{Compositor, Layer, Transform, Color};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create compositor for 1080p output
    let compositor = Compositor::new(1920, 1080)?;

    // Create layers
    let layers = vec![
        // Background layer
        Layer::solid_color(
            Color::rgb(50, 50, 50),
            Transform::new()
        ),

        // Red overlay
        Layer::solid_color(
            Color::rgb(255, 0, 0),
            Transform::new()
                .with_position(100.0, 100.0)
                .with_scale(0.5)
                .with_opacity(0.8)
        ),
    ];

    // Compose frame
    let frame = compositor.compose(&layers)?;

    // Save output
    frame.save("output.png")?;

    println!("Frame saved successfully!");
    Ok(())
}

Working with Images

Loading and Compositing Images

use video_compositor::{Compositor, Layer, Transform, BlendMode};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let compositor = Compositor::new(1920, 1080)?
        .with_background(Color::black());

    let layers = vec![
        // Background image
        Layer::image(
            "background.jpg",
            Transform::new()
        ),

        // Logo with multiply blend
        Layer::image(
            "logo.png",
            Transform::new()
                .with_position(50.0, 50.0)
                .with_scale(0.3)
        )
        .with_blend_mode(BlendMode::Multiply),

        // Watermark in corner
        Layer::image(
            "watermark.png",
            Transform::new()
                .with_position(1700.0, 950.0)
                .with_scale(0.2)
                .with_opacity(0.5)
        ),
    ];

    let frame = compositor.compose(&layers)?;
    frame.save("composited.png")?;

    Ok(())
}

Blend Modes

All Available Blend Modes

use video_compositor::{Compositor, Layer, Transform, BlendMode, Color};

fn demonstrate_blend_modes() -> Result<(), Box<dyn std::error::Error>> {
    let compositor = Compositor::new(800, 600)?;

    let base = Layer::solid_color(
        Color::rgb(100, 100, 255),
        Transform::new()
    );

    // Try different blend modes
    let blend_modes = [
        BlendMode::Normal,
        BlendMode::Multiply,
        BlendMode::Screen,
        BlendMode::Overlay,
        BlendMode::Add,
        BlendMode::Subtract,
        BlendMode::Lighten,
        BlendMode::Darken,
    ];

    for (i, blend_mode) in blend_modes.iter().enumerate() {
        let layers = vec![
            base.clone(),
            Layer::solid_color(
                Color::rgb(255, 100, 100),
                Transform::new()
                    .with_position(200.0, 200.0)
                    .with_scale(0.5)
            )
            .with_blend_mode(*blend_mode),
        ];

        let frame = compositor.compose(&layers)?;
        frame.save(format!("blend_{:?}.png", blend_mode))?;
    }

    Ok(())
}

Effects

Applying Effects to Layers

use video_compositor::{Compositor, Layer, Transform, Effect};

fn apply_effects() -> Result<(), Box<dyn std::error::Error>> {
    let compositor = Compositor::new(1920, 1080)?;

    // Load base image
    let mut image = image::open("photo.jpg")?.to_rgba8();

    // Apply blur effect
    let blur_effect = Effect::Blur { radius: 10.0 };
    image = blur_effect.apply(&image)?;

    // Apply brightness adjustment
    let brightness_effect = Effect::Brightness { amount: 0.3 };
    image = brightness_effect.apply(&image)?;

    // Apply contrast
    let contrast_effect = Effect::Contrast { amount: 0.2 };
    image = contrast_effect.apply(&image)?;

    // Create layer from processed image
    let (width, height) = image.dimensions();
    let layer = Layer::from_image_data(
        image.into_raw(),
        width,
        height,
        Transform::new()
    );

    let frame = compositor.compose(&vec![layer])?;
    frame.save("effects_applied.png")?;

    Ok(())
}

Parallel Processing

Batch Frame Composition

use video_compositor::{Compositor, Layer, Transform, Color};
use rayon::prelude::*;

fn render_video_frames() -> Result<(), Box<dyn std::error::Error>> {
    let compositor = Compositor::new(1920, 1080)?;

    // Generate 300 frames (10 seconds at 30fps)
    let frame_count = 300;

    let all_layers: Vec<Vec<Layer>> = (0..frame_count)
        .map(|frame_num| {
            // Animate position
            let x = (frame_num as f32 * 5.0) % 1920.0;
            let y = 500.0;

            vec![
                Layer::solid_color(
                    Color::rgb(50, 50, 50),
                    Transform::new()
                ),
                Layer::solid_color(
                    Color::rgb(255, 0, 0),
                    Transform::new()
                        .with_position(x, y)
                        .with_scale(0.3)
                ),
            ]
        })
        .collect();

    // Process all frames in parallel
    let frames = compositor.compose_batch(all_layers)?;

    // Save frames
    frames.iter().enumerate().for_each(|(i, frame)| {
        frame.save(format!("frames/frame_{:04}.png", i))
            .expect("Failed to save frame");
    });

    println!("Rendered {} frames", frame_count);
    Ok(())
}

Real-World Example: Video Template

Template-Based Video Generation

use video_compositor::{Compositor, Layer, Transform, BlendMode, Color};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct VideoTemplate {
    width: u32,
    height: u32,
    background_color: [u8; 4],
    elements: Vec<TemplateElement>,
}

#[derive(Serialize, Deserialize)]
struct TemplateElement {
    #[serde(rename = "type")]
    element_type: String,
    image_path: Option<String>,
    color: Option<[u8; 4]>,
    position: (f32, f32),
    scale: f32,
    opacity: f32,
    blend_mode: Option<String>,
}

fn render_from_template(template_json: &str) -> Result<(), Box<dyn std::error::Error>> {
    let template: VideoTemplate = serde_json::from_str(template_json)?;

    let compositor = Compositor::new(template.width, template.height)?
        .with_background(Color::new(
            template.background_color[0],
            template.background_color[1],
            template.background_color[2],
            template.background_color[3],
        ));

    let layers: Vec<Layer> = template.elements.iter().map(|el| {
        let transform = Transform::new()
            .with_position(el.position.0, el.position.1)
            .with_scale(el.scale)
            .with_opacity(el.opacity);

        let layer = match el.element_type.as_str() {
            "image" => Layer::image(
                el.image_path.as_ref().unwrap(),
                transform
            ),
            "color" => Layer::solid_color(
                Color::new(
                    el.color.unwrap()[0],
                    el.color.unwrap()[1],
                    el.color.unwrap()[2],
                    el.color.unwrap()[3],
                ),
                transform
            ),
            _ => panic!("Unknown element type"),
        };

        // Apply blend mode if specified
        if let Some(blend_mode_str) = &el.blend_mode {
            match blend_mode_str.as_str() {
                "multiply" => layer.with_blend_mode(BlendMode::Multiply),
                "screen" => layer.with_blend_mode(BlendMode::Screen),
                "overlay" => layer.with_blend_mode(BlendMode::Overlay),
                _ => layer,
            }
        } else {
            layer
        }
    }).collect();

    let frame = compositor.compose(&layers)?;
    frame.save("template_output.png")?;

    Ok(())
}

// Example template JSON
const TEMPLATE: &str = r#"
{
  "width": 1920,
  "height": 1080,
  "background_color": [30, 30, 30, 255],
  "elements": [
    {
      "type": "image",
      "image_path": "background.jpg",
      "position": [0, 0],
      "scale": 1.0,
      "opacity": 1.0
    },
    {
      "type": "image",
      "image_path": "logo.png",
      "position": [50, 50],
      "scale": 0.3,
      "opacity": 0.9,
      "blend_mode": "multiply"
    },
    {
      "type": "color",
      "color": [255, 100, 0, 200],
      "position": [100, 100],
      "scale": 0.5,
      "opacity": 0.7,
      "blend_mode": "overlay"
    }
  ]
}
"#;

Integration with FFmpeg

Complete Video Rendering Pipeline

use video_compositor::{Compositor, Layer, Transform};
use std::process::Command;

fn render_complete_video() -> Result<(), Box<dyn std::error::Error>> {
    let compositor = Compositor::new(1920, 1080)?;

    // 1. Generate all frames
    println!("Rendering frames...");
    let frame_count = 300; // 10 seconds at 30fps

    for frame_num in 0..frame_count {
        let progress = (frame_num as f32 / frame_count as f32) * 100.0;

        // Animate layers
        let x = (frame_num as f32 * 5.0) % 1920.0;

        let layers = vec![
            Layer::image("background.jpg", Transform::new()),
            Layer::image(
                "logo.png",
                Transform::new()
                    .with_position(x, 500.0)
                    .with_scale(0.3)
            ),
        ];

        let frame = compositor.compose(&layers)?;
        frame.save(format!("output/frames/frame_{:04}.png", frame_num))?;

        if frame_num % 30 == 0 {
            println!("Progress: {:.1}%", progress);
        }
    }

    // 2. Encode with FFmpeg
    println!("Encoding video with FFmpeg...");
    let output = Command::new("ffmpeg")
        .args(&[
            "-framerate", "30",
            "-i", "output/frames/frame_%04d.png",
            "-c:v", "libx264",
            "-preset", "medium",
            "-crf", "23",
            "-pix_fmt", "yuv420p",
            "output/final_video.mp4",
        ])
        .output()?;

    if output.status.success() {
        println!("Video encoded successfully!");
    } else {
        eprintln!("FFmpeg error: {}", String::from_utf8_lossy(&output.stderr));
    }

    Ok(())
}

Performance Optimization

Parallel Frame Processing

use video_compositor::{Compositor, Layer};
use rayon::prelude::*;
use std::sync::Arc;

fn optimized_batch_rendering() -> Result<(), Box<dyn std::error::Error>> {
    // Create shared compositor (thread-safe)
    let compositor = Arc::new(Compositor::new(1920, 1080)?);

    // Generate frame data
    let frame_data: Vec<Vec<Layer>> = (0..1000)
        .map(|i| {
            // Your layer generation logic
            vec![/* layers */]
        })
        .collect();

    // Process in parallel with rayon
    let results: Vec<_> = frame_data
        .par_iter()
        .enumerate()
        .map(|(i, layers)| {
            let frame = compositor.compose(layers).unwrap();
            (i, frame)
        })
        .collect();

    // Save results
    results.iter().for_each(|(i, frame)| {
        frame.save(format!("output/frame_{:04}.png", i)).unwrap();
    });

    Ok(())
}

Error Handling

Robust Error Handling

use video_compositor::{Compositor, Layer, Transform, Error};

fn safe_composition() -> Result<(), Error> {
    let compositor = Compositor::new(1920, 1080)?;

    // Validate dimensions
    if 1920 == 0 || 1080 == 0 {
        return Err(Error::InvalidDimensions(1920, 1080));
    }

    // Try to load image, handle error
    let layer = match Layer::image("logo.png", Transform::new()) {
        Ok(layer) => layer,
        Err(e) => {
            eprintln!("Failed to load image: {}", e);
            // Fallback to solid color
            Layer::solid_color(
                Color::rgb(100, 100, 100),
                Transform::new()
            )
        }
    };

    let frame = compositor.compose(&vec![layer])?;
    frame.save("output.png")?;

    Ok(())
}

Next Steps