8 releases
Uses new Rust 2024
| 0.15.0 | Jun 15, 2026 |
|---|---|
| 0.14.4 | Jun 4, 2026 |
| 0.14.3 | May 4, 2026 |
| 0.14.2 | Apr 22, 2026 |
| 0.13.1 | Apr 16, 2026 |
#956 in Video
40 downloads per month
Used in 2 crates
2MB
34K
SLoC
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 transparent auto-substitution.
Project status (as of 2026-06-04): The library foundation is in place. Development continues through avio-editor-demo, a real-world video editing application built on
avio, which surfaces bugs and drives API improvements. Pull requests, bug reports, and feature requests are welcome — see the main repository for full context.
Installation
[dependencies]
ff-preview = "0.15"
# Enable async support
ff-preview = { version = "0.15", features = ["tokio"] }
# Enable proxy generation
ff-preview = { version = "0.15", 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};
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,
}
}
Proxy generation
use std::path::Path;
use ff_preview::{ProxyGenerator, ProxyResolution};
let proxy_path = ProxyGenerator::new(Path::new("original_1080p.mp4"))?
.resolution(ProxyResolution::Quarter)
.output_dir(Path::new("/tmp"))
.generate()?;
println!("proxy at {}", proxy_path.display());
Feature Flags
| Feature | What it enables |
|---|---|
| (default) | PreviewPlayer, PlayerRunner, PlayerHandle, DecodeBuffer, PlaybackClock, FrameSink, RgbaSink, RgbaFrame, seek |
tokio |
AsyncPreviewPlayer |
proxy |
ProxyGenerator, ProxyJob, ProxyResolution |
timeline |
TimelinePlayer, TimelineRunner |
MSRV
Rust 1.93.0 (edition 2024).
License
MIT OR Apache-2.0
Dependencies
~1–4MB
~80K SLoC