ff-preview 0.18.0

Real-time video/audio preview and proxy workflow
Documentation

ff-preview

Real-time video preview and proxy workflow for Rust. Provides frame-accurate seek, audio-master A/V sync, a FrameSink trait for custom renderers, RGBA pixel delivery, and proxy generation with auto-substitution.

ff-preview adds a real-time, A/V-synchronised playback and seek loop on top of the decode primitives, converting frames to RGBA via libswscale for display. Decoding is delegated to ff-decode; this crate owns the playback clock, frame-accurate seek, and a FrameSink trait for custom renderers. Errors are typed and chain their source (PreviewError), so a failure reads as an actionable message rather than a raw FFmpeg return code.

It is an independent crate: use it on its own, or combine it with the other ff-* crates to build any media app or editing model. The ff-* crates are model-free primitives that impose no editing model; avio is one editing engine built on top of them.

Installation

[dependencies]
ff-preview = "0.18"

# Enable async support
ff-preview = { version = "0.18", features = ["tokio"] }

# Enable proxy generation
ff-preview = { version = "0.18", features = ["proxy"] }

Quick Start

Playback with a custom RGBA sink

PreviewPlayer::open probes the file and prepares the pipeline. Call split() to obtain an exclusive PlayerRunner (owns the decode pipeline; register the sink and drive it with run()) and a cloneable PlayerHandle (non-blocking play / pause / seek / stop controls).

use std::thread;
use ff_preview::{PreviewPlayer, RgbaSink};

fn main() -> Result<(), ff_preview::PreviewError> {
    let (mut runner, handle) = PreviewPlayer::open("video.mp4")?.split();

    let sink = RgbaSink::new();
    let frames = sink.frame_handle(); // Arc<Mutex<Option<RgbaFrame>>> for the render thread
    runner.set_sink(Box::new(sink));

    thread::spawn(move || {
        let _ = runner.run();
    });

    handle.play();

    // In the render loop (any thread):
    if let Some(frame) = frames.lock().unwrap().as_ref() {
        // upload_to_gpu(&frame.data, frame.width, frame.height);
        let _ = (&frame.data, frame.width, frame.height, frame.pts);
    }

    Ok(())
}

Frame-accurate seek

use std::path::Path;
use std::time::Duration;
use ff_preview::{DecodeBuffer, FrameResult};

fn main() -> Result<(), ff_preview::PreviewError> {
    let mut buf = DecodeBuffer::open(Path::new("video.mp4")).build()?;
    buf.seek(Duration::from_secs(30))?;

    loop {
        match buf.pop_frame() {
            FrameResult::Frame(f) => {
                println!("pts: {:?}", f.timestamp().as_duration());
                break;
            }
            FrameResult::Seeking(_) => std::thread::sleep(Duration::from_millis(5)),
            FrameResult::Eof => break,
        }
    }

    Ok(())
}

Proxy generation

Requires the proxy feature (--features proxy). A file goes in, a lower-resolution proxy file comes out at {output_dir}/{stem}_proxy_{res}.mp4.

use std::path::Path;
use ff_preview::{ProxyGenerator, ProxyResolution};

fn main() -> Result<(), ff_preview::PreviewError> {
    let proxy_path = ProxyGenerator::new(Path::new("original_1080p.mp4"))?
        .resolution(ProxyResolution::Quarter)
        .output_dir(&std::env::temp_dir())
        .generate()?;

    println!("proxy at {}", proxy_path.display());
    Ok(())
}

Feature Flags

Feature What it enables
(default) PreviewPlayer, PlayerRunner, PlayerHandle, DecodeBuffer, PlaybackClock, FrameSink, RgbaSink, RgbaFrame, seek
tokio AsyncPreviewPlayer
proxy ProxyGenerator, ProxyJob, ProxyResolution
timeline Scene, ScenePlayer, SceneRunner

Error Handling

Common variants (not exhaustive):

Variant When it occurs
PreviewError::FileNotFound The media file was not found
PreviewError::NoVideoStream The file has no video stream
PreviewError::SeekFailed A seek operation failed
PreviewError::Decode Wrapped DecodeError from the decode stage
PreviewError::Io An I/O error during file operations

PreviewError implements ff_format::MediaError, so err.is_recoverable() / err.is_fatal() work uniformly with the other ff-* crates.

MSRV

Rust 1.93.0 (edition 2024).

License

MIT OR Apache-2.0