fframes 1.0.2

A video generation framework
Documentation

fframes renders videos from Rust code. A video is a type that implements [Video]. It declares the size, frame rate and duration once and returns an SVG tree ([Svgr]) for every frame from the [svgr!] macro. The renderer rasterises the frames on all cores, mixes the audio and encodes the result with FFmpeg.

Pipeline

  1. [Video::duration], [Video::define_scenes] and [Video::audio] are resolved into a frame timeline once per render. Scenes ([Scene], [Scenes], [Overlap]) are placed back to back, audio tracks ([AudioMap], [AudioTrack]) are placed at their sample position.
  2. For every frame the renderer builds a [Frame] (index, time, scene offset) and a [FFramesContext] (media lookups, scene info, video size) and calls [Video::render_frame]. Frames are rendered concurrently, so the method must be pure: read precomputed data from self, no I/O, no panics.
  3. The returned [Svgr] becomes a usvgr::Tree. With the compile-time-svgtree feature the macro emits the tree at compile time and every subtree without {} interpolation carries a static hash, which lets the backends cache its rasterisation across frames. Without the feature the markup is a string parsed per frame.
  4. A rendering backend ([FFramesRenderBackend]) rasterises the tree. The built-in [cpu::CpuRenderingBackend] renders one video segment per thread with tiny-skia. The Skia backend in the fframes_skia_renderer crate walks the tree on the GPU and also executes [Shader] layers.
  5. Segments are encoded through FFmpeg ([EncoderOptions]), concatenated, and muxed with the audio mix ([AudioMixOptions]: summing, ducking, fades, master limiter).

Minimal video

use fframes::{AudioMap, Duration, FFramesContext, Frame, RenderOptions, Svgr, Video, animation::Easing};

// Every file in the folder becomes a field; fonts are registered by family name.
fframes::include_media_dir!(pub struct Media, "media");

struct Hello<'a> {
    media: &'a Media,
    title: &'a str,
}

impl Video for Hello<'_> {
    const FPS: usize = 30;
    const WIDTH: usize = 1920;
    const HEIGHT: usize = 1080;

    fn duration(&self) -> Duration<'_> {
        Duration::Seconds(3.0)
    }

    fn audio(&self) -> AudioMap<'_> {
        AudioMap::none()
    }

    fn render_frame<'a>(&'a self, frame: Frame, _ctx: &FFramesContext<'a, '_>) -> Svgr<'a> {
        let opacity = frame.animate(&fframes::timeline!(
            at 0.0 => 0.5, animate 0.0_f32 => 1.0, Easing::EaseOut
        ));
        fframes::svgr!(
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080"
                 width={Self::WIDTH} height={Self::HEIGHT}>
                <text x="120" y="560" font-family="DM Sans" font-size="150" fill="#fff"
                      opacity={opacity}>
                    "Hello " {self.title}
                </text>
            </svg>
        )
    }
}

fn main() -> std::process::ExitCode {
    let media = Media::prepare().expect("embedded media");
    let video = Hello { media: &media, title: "world" };
    // `render`, `frame`, `strip`, `inspect`, `snapshot`, `audio` and more (feature `cli`).
    fframes::cli::new(&video, RenderOptions { media: Some(&media), ..Default::default() }).run()
}

Without the cli feature call [render] with the output path, a backend and [RenderOptions], or render single frames through [Previewer].

Feature flags

  • cpu_renderer (default): the tiny-skia based [cpu::CpuRenderingBackend] and the encoding pipeline. Disable it for a wasm32 build.
  • cli: the [cli] module and its clap dependency.
  • compile-time-svgtree: [svgr!] builds the SVG tree at compile time and hashes static subtrees. Required by the Skia backend and by [Shader].
  • exif: EXIF orientation of loaded images.
  • Codecs h264, h265, aac, mp3lame, opus, vpx: compile the library into the static FFmpeg build. Some of them need libav-agree-gpl, libav-agree-nonfree or libav-agree-version3, which state that you accept the corresponding FFmpeg license terms.
  • Hardware acceleration videotoolbox, audiotoolbox, vaapi, nvidia, qsv, vulkan, mediacodec: enable the platform encoders and decoders in FFmpeg.

FFmpeg

Decoding, encoding and muxing use the FFmpeg libraries through ffmpeg-sys-next, re-exported as [ffmpeg_sys_fframes]. On Linux and macOS FFmpeg is compiled from source during cargo build and linked statically, so the toolchain listed in the repository README (nasm, yasm, clang, the codec dev packages) must be installed. On Windows a prebuilt FFmpeg 9 is linked through FFMPEG_DIR or vcpkg and the codec features are not available.