Expand description
§ez-ffmpeg
ez-ffmpeg provides a safe and ergonomic Rust interface for FFmpeg integration. By abstracting away much of the raw C API complexity, It abstracts the complexity of the raw C API, allowing you to configure media pipelines, perform transcoding and filtering, and inspect streams with ease.
§Crate Layout
-
core: The foundational module that contains the main building blocks for configuring and running FFmpeg pipelines. This includes:Input/Output: Descriptors for where media data comes from and goes to (files, URLs, custom I/O callbacks, etc.).FilterComplexandFrameFilter: Mechanisms for applying FFmpeg filter graphs or custom transformations.container_info: Utilities to extract information about the container, such as duration and format details.stream_info: Utilities to query media metadata (duration, codecs, etc.).hwaccel: Helpers for enumerating and configuring hardware-accelerated video codecs (CUDA, VAAPI, VideoToolbox, etc.).codec: Tools to list and inspect available encoders/decoders.device: Utilities to discover system cameras, microphones, and other input devices.filter: Query FFmpeg’s built-in filters and infrastructure for building custom frame-processing filters.context: HousesFfmpegContextfor assembling an FFmpeg job.scheduler: ProvidesFfmpegSchedulerwhich manages the lifecycle of that job.
-
wgpu_filter(feature"wgpu"): GPU-accelerated frame filters via wgpu (Vulkan/Metal/DX12/GL). Provide a WGSL fragment shader and apply effects with correct color handling, headless operation, and GPU/CPU overlap. -
opengl(feature"opengl", deprecated): The former OpenGL filter path, superseded bywgpu_filter. Kept for backward compatibility; it requires a display connection and will be removed in a future major release. -
rtmp(feature"rtmp"): Embedded RTMP serverEmbedRtmpServerbuilt for production streaming, using native epoll/kqueue/WSAPoll via libc FFI (edge-triggered on Linux/macOS, level-triggered on Windows), zero-copy GOP fanout withArc<[FrameData]>, and tiered backpressure (1/2/4MB) on a 2-thread model; 10,000+ conns on Linux/macOS (8,000 on Windows) with in-process ingest (no TCP between FFmpeg and server). -
flv(feature"flv"): Provides data structures and helpers for handling FLV containers, useful if you’re working with RTMP or other FLV-based workflows. -
subtitle(feature"subtitle"): Burns ASS/SRT subtitles onto video frames inside the frame pipeline with a pure-Rust renderer — independent of whether the linked FFmpeg was built with--enable-libass. Accepts subtitle files or in-memory scripts and explicit font files.
§Basic Usage
For a simple pipeline, you typically do the following:
- Build a
FfmpegContextby specifying at least one input and one output. Optionally, add filter descriptions (filter_desc) or attachFrameFilterpipelines at either the input (post-decode) or the output (pre-encode) stage. - Create an
FfmpegSchedulerfrom that context, then callstart()andwait()(or.awaitif you enable the"async"feature) to run the job.
use ez_ffmpeg::FfmpegContext;
use ez_ffmpeg::FfmpegScheduler;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Build the FFmpeg context
let context = FfmpegContext::builder()
.input("input.mp4")
.filter_desc("hue=s=0") // Example filter: desaturate
.output("output.mov")
.build()?;
// 2. Run it via FfmpegScheduler (sync mode)
let result = FfmpegScheduler::new(context)
.start()?
.wait();
result?; // If any error occurred, propagate it
Ok(())
}§Feature Flags
ez-ffmpeg uses Cargo features to provide optional functionality. By default, no optional
features are enabled, allowing you to keep dependencies minimal. You can enable features as needed
in your Cargo.toml:
[dependencies.ez-ffmpeg]
version = "*"
features = ["wgpu", "rtmp", "flv", "async"]§Core Features
wgpu: Enables wgpu-based GPU filters (WGSL shaders, headless-capable).opengl(deprecated): Enables the former OpenGL-based filters; superseded bywgpu.rtmp: Embedded RTMP server tuned for scale (10,000+ conns on Linux/macOS, 8,000 on Windows), native epoll/kqueue/WSAPoll IO (edge-triggered on Linux/macOS), zero-copy GOP, and in-process ingest that avoids TCP between FFmpeg and server.flv: Adds FLV container parsing and handling.subtitle: Native ASS/SRT subtitle burn-in rendered in pure Rust — no system libraries beyond FFmpeg itself (see thesubtitlemodule docs).async: Adds asynchronous functionality:FfmpegScheduleradditionally implementsFuture, so a running scheduler can be.awaited as a non-blocking alternative to the always-available synchronouswait().static: Uses static linking for FFmpeg libraries (viaffmpeg-next/static).
§Relationship to the FFmpeg CLI
The transcoding pipeline (demux -> decode -> filter -> encode -> mux) is
ported from the FFmpeg CLI sources, fftools/ffmpeg of FFmpeg 7.x:
function names, timestamp handling and scheduling semantics follow that
release, and code comments cite the corresponding fftools file and line
(line numbers refer to the FFmpeg n7.1 tag).
If you know ffmpeg_demux.c or ffmpeg_filter.c, grepping this crate
for the same function names (ts_fixup, video_sync_process,
enc_open, mux_fixup_ts, …) lands in the equivalent Rust.
Bitstream filters (-bsf:v/-bsf:a/-bsf:s) are supported through
Output::set_video_bsf
and its audio/subtitle siblings (single filter or comma-separated chain).
Not every CLI feature is implemented. Notable gaps: progress/stats
reporting (-progress), sub2video (rendering bitmap subtitles into
video), -fix_sub_duration, and two-pass encoding. Unsupported paths
fail with explicit errors rather than approximations.
§CLI-to-API mapping
The table below maps common ffmpeg command-line flags and patterns to their
ez-ffmpeg equivalents. The Kind column tells you how the mapping works:
- typed — a dedicated builder method with a typed signature.
- option — a string key/value forwarded to FFmpeg’s option system
(
AVOption); the same names the CLI accepts, without the leading dash. - filter — expressed as an FFmpeg filtergraph string via
filter_desc. - recipe — a one-shot helper that owns the whole workflow.
- gap — not implemented; the notes state the failure mode and workaround.
One global convention: CLI seconds become microseconds in _us methods
(-ss 10 → 10_000_000).
| FFmpeg CLI | Kind | ez-ffmpeg API | Status / notes |
|---|---|---|---|
-i <file/URL> | typed | Input::from, builder input | Files, network URLs, device strings. |
read/write custom I/O (pipe:) | typed | Input::new_by_read_callback, Output::new_by_write_callback | In-memory / streaming I/O; add seek callbacks for seekable media. |
-f <fmt> (force format) | typed | Input::set_format, Output::set_format | E.g. "lavfi", "hls", "segment", "null". |
-ss / -t / -to (before -i) | typed | Input::set_start_time_us / set_recording_time_us / set_stop_time_us | Input-side seek/trim. See anchor below. |
-ss / -t / -to (before output) | typed | Output::set_start_time_us / set_recording_time_us / set_stop_time_us | Output-side trim (decode-then-discard semantics). |
-stream_loop N | typed | Input::set_stream_loop | -1 loops forever. |
-re | typed | Input::set_readrate | 1.0 reads at native speed. |
-shortest | typed | Output::set_shortest | Frame-accurate for encoded streams; bound the buffering window with set_shortest_buf_duration_us. See anchor below. |
-map <spec> | typed | Output::add_stream_map (re-encode), add_stream_map_with_copy (streamcopy) | Accepts "0:v"-style input specs or filtergraph link labels like "[vout]". |
-vn / -an / -sn / -dn | typed | Output::disable_video / disable_audio / disable_subtitle / disable_data | Per-output stream suppression. |
-frames:v / -frames:a / -frames:s | typed | Output::set_max_video_frames / set_max_audio_frames / set_max_subtitle_frames | Stop after N frames. |
-c:v / -c:a / -c:s <enc> | typed | Output::set_video_codec / set_audio_codec / set_subtitle_codec | Encoder by FFmpeg name ("libx264", "aac", …). |
-c copy (streamcopy) | typed | set_video_codec("copy") and siblings, or add_stream_map_with_copy | No decode/encode; codec must fit the target container. See anchor below. |
-c:v <dec> (before -i, force decoder) | typed | Input::set_video_codec / set_audio_codec | For streams the probe misidentifies. |
-crf, -preset, -profile, x264-params, … | option | Output::set_video_codec_opt / set_audio_codec_opt | Any encoder AVOption, e.g. ("crf", "23"), ("preset", "fast"). |
-b:v / -b:a | typed | Output::set_video_bitrate / set_audio_bitrate | FFmpeg size syntax: "2500k", "5M". |
-q:v / -q:a | typed | Output::set_video_qscale / set_audio_qscale | Fixed quality scale. |
-r / -fpsmax (output) | typed | Output::set_framerate / set_framerate_max | Rational num, den (30, 1; 24000, 1001). |
-fps_mode / -vsync | typed | Output::set_vsync_method | VSyncMethod enum (auto / CFR / VFR / passthrough / vscfr). |
-pix_fmt | typed | Output::set_pix_fmt | By name, e.g. "yuv420p". |
-ar / -ac / -sample_fmt | typed | Output::set_audio_sample_rate / set_audio_channels / set_audio_sample_fmt | Audio resample/layout parameters. |
-force_key_frames 0,5,10 | typed | Output::set_force_key_frames | Comma-separated absolute times in seconds only; the expr: / HH:MM:SS / source forms are rejected. |
-bsf:v / -bsf:a / -bsf:s | typed | Output::set_video_bsf / set_audio_bsf / set_subtitle_bsf | Single filter or comma-separated chain ("h264_mp4toannexb"). |
-tag:v hvc1 (FourCC / codec tag) | gap | — | Not exposed. Setting ("tag", ...) as a codec option does not reach codec_tag; re-tag with an external remux for now. |
-movflags +faststart | option | Output::set_format_opt("movflags", "faststart") | Muxer option. See anchor below. |
muxer options (-hls_time, -segment_time, -hls_list_size, …) | option | Output::set_format_opt / set_format_opts | Container-level AVOptions for the selected muxer. |
demuxer/protocol options (-rtsp_transport tcp, -headers, -loop 1, -probesize) | option | Input::set_format_opt / set_format_opts | Applied at avformat_open_input time; covers protocol, demuxer and device options. |
-metadata, -metadata:s:v, -map_metadata | typed | Output::add_metadata, add_stream_metadata, map_metadata_from_input | Global, per-stream, chapter and program metadata; note add_stream_metadata returns Result. |
-vf / -af / -filter_complex | filter | builder filter_desc | Full filtergraph syntax including labels and multiple inputs. |
-vf scale=1280:-2 (resize) | filter | .filter_desc("scale=1280:-2") | Any scale expression works verbatim. |
watermark (overlay) | filter | .filter_desc("[1:v]scale=100:-1[wm];[0:v][wm]overlay=10:10") | Second input is the watermark; see examples/watermarking. |
| concat several files | filter | multiple .input(...) + .filter_desc("concat=n=3:v=1:a=1") | Re-encodes; see examples/video_merging. |
-hwaccel, -hwaccel_device, -hwaccel_output_format | typed | Input::set_hwaccel / set_hwaccel_device / set_hwaccel_output_format | "cuda", "vaapi", "videotoolbox", … or "auto"; see the hwaccel module. |
silencedetect → parsed output | recipe | Analysis + AudioDetector::Silence | Typed silence ranges instead of scraping logs. See anchor below. |
blackdetect / scdet / cropdetect / ebur128 | recipe | VideoDetector / AudioDetector variants | One decode pass, folded AnalysisReport. |
-f null - (discard output) | typed | Output::from("-").set_format("null") | Run a pipeline for its side effects only. |
ffprobe streams / duration | typed | stream_info::find_all_stream_infos, container_info::get_duration_us | Includes per-stream metadata (title, language, rotation, …). |
ffprobe -show_packets | typed | packet_scanner | Iterate packet pts/dts/size/keyframe flags without decoding. |
| single thumbnail / sprite sheet | recipe | thumbnail, sprite_sheet | Owns seek + scale + select + tile. See anchor below. |
fastest thumbnail (-skip_frame nokey) | option | Input::set_video_codec_opt("skip_frame", "nokey") + input seek | Decodes keyframes only; snaps to the next keyframe. See examples/thumbnail_extraction. |
GIF export (palettegen/paletteuse) | recipe | animated_gif | Two-pass palette workflow in one call. |
HLS VOD (-f hls -hls_time 6 ...) | option | set_format("hls") + set_format_opt | See anchor below. |
| HLS ABR ladder (multi-rendition) | recipe | HlsLadder | One decode, N renditions, master playlist; CFR VOD only. |
split audio into WAV chunks (-f segment) | option | set_format("segment") + ("segment_time", "10") | Numbered output pattern out_%03d.wav; see examples/split_video_to_wav. |
still video from image (-loop 1 -i img -t 10) | option | Input::set_format_opt("loop", "1") + Output::set_recording_time_us | See examples/still_video_from_image. |
burn in subtitles (-vf subtitles=subs.srt) | recipe | SubtitleFilter frame pipeline (feature subtitle) | Pure-Rust renderer, works without libass; filter_desc("subtitles=...") also works if your FFmpeg links libass. See anchor below. |
capture camera/mic (-f avfoundation -i "0:0") | typed | Input::set_format + device queries | Platform device demuxers (avfoundation/dshow/v4l2, …); see examples/capture_camera_mic. |
q keypress (stop early, finalize container) | typed | FfmpegScheduler::stop() | Prompt teardown: the normal path attempts the trailer (usually a valid container), but in-flight and queued frames may be dropped, so the tail can be truncated — not a full drain. pause()/resume()/abort() also available. |
-loglevel / reading FFmpeg’s own messages | typed | set_ffmpeg_log_level, Input::set_log_level_offset | Forwarded to the Rust log facade; see the Logging section below. |
-progress / -stats | gap | — | No stats reporting. Workaround: a FrameFilter observing frame timestamps against total duration (examples/processing_progress) — it sees decoded frames, not the CLI’s encoder statistics. |
two-pass encoding (-pass 1/2) | gap | — | No built-in orchestration for stats files across runs. |
sub2video, -fix_sub_duration | gap | — | Not implemented; such pipelines fail with explicit errors. |
§Compile-checked CLI equivalents
The most-asked translations, as minimal compilable programs. Each block is a doctest, so the code stays in sync with the current API.
Transcode with H.264/CRF —
ffmpeg -i input.mkv -c:v libx264 -crf 23 -c:a aac output.mp4:
use ez_ffmpeg::{FfmpegContext, Output};
FfmpegContext::builder()
.input("input.mkv")
.output(Output::from("output.mp4")
.set_video_codec("libx264")
.set_video_codec_opt("crf", "23")
.set_audio_codec("aac"))
.build()?
.start()?
.wait()?;Clip without re-seeking surprises —
ffmpeg -ss 10 -t 5 -i input.mp4 clip.mp4:
use ez_ffmpeg::{FfmpegContext, Input};
FfmpegContext::builder()
.input(Input::from("input.mp4")
.set_start_time_us(10_000_000) // -ss 10
.set_recording_time_us(5_000_000)) // -t 5
.output("clip.mp4")
.build()?
.start()?
.wait()?;Remux for instant web playback —
ffmpeg -i input.mp4 -c copy -movflags faststart output.mp4:
use ez_ffmpeg::{FfmpegContext, Output};
FfmpegContext::builder()
.input("input.mp4")
.output(Output::from("output.mp4")
.set_video_codec("copy")
.set_audio_codec("copy")
.set_format_opt("movflags", "faststart"))
.build()?
.start()?
.wait()?;Stop when the shortest stream ends —
ffmpeg -i video.mp4 -i audio.mp3 -map 0:v -map 1:a -shortest out.mp4:
use ez_ffmpeg::{FfmpegContext, Output};
FfmpegContext::builder()
.input("video.mp4")
.input("audio.mp3")
.output(Output::from("out.mp4")
.add_stream_map("0:v")
.add_stream_map("1:a")
.set_shortest(true))
.build()?
.start()?
.wait()?;One thumbnail at a timestamp —
ffmpeg -ss 12 -i input.mp4 -frames:v 1 -vf scale=320:-1 thumb.jpg:
use ez_ffmpeg::recipes::{thumbnail, At, ThumbnailOptions};
thumbnail("input.mp4", "thumb.jpg", ThumbnailOptions {
at: At::Sec(12.0),
width: Some(320),
..ThumbnailOptions::default()
})?;Silence detection as typed data —
ffmpeg -i input.mp4 -af silencedetect=noise=-30dB:d=0.5 -f null -:
use ez_ffmpeg::analysis::{Analysis, AudioDetector};
let report = Analysis::new("input.mp4")
.audio_detector(AudioDetector::Silence {
noise_db: -30.0, // noise=-30dB
min_duration_s: 0.5, // d=0.5
mono: false,
})
.run()?;
println!("silence ranges: {:?}", report.silence);HLS VOD packaging —
ffmpeg -i input.mp4 -f hls -hls_time 6 -hls_playlist_type vod out/playlist.m3u8:
use ez_ffmpeg::{FfmpegContext, Output};
FfmpegContext::builder()
.input("input.mp4")
.output(Output::from("out/playlist.m3u8")
.set_format("hls")
.set_format_opt("hls_time", "6")
.set_format_opt("hls_playlist_type", "vod"))
.build()?
.start()?
.wait()?;Burn in subtitles without libass (feature subtitle) —
ffmpeg -i input.mp4 -vf subtitles=subs.srt output.mp4:
use ez_ffmpeg::filter::frame_pipeline_builder::FramePipelineBuilder;
use ez_ffmpeg::subtitle::SubtitleFilter;
use ez_ffmpeg::{AVMediaType, FfmpegContext, Output};
let filter = SubtitleFilter::builder().file("subs.srt").build()?;
let pipeline: FramePipelineBuilder = AVMediaType::AVMEDIA_TYPE_VIDEO.into();
FfmpegContext::builder()
.input("input.mp4")
.output(Output::from("output.mp4")
.add_frame_pipeline(pipeline.filter("subtitles", Box::new(filter))))
.build()?
.start()?
.wait()?;§Logging
FFmpeg’s own diagnostics (av_log) are redirected into the Rust log
facade under the FFMPEG_LOG_TARGET target. Without a logger installed
(env_logger, tracing-log, …) all FFmpeg messages are silently dropped —
including decoder errors that explain a failing job. Use
set_ffmpeg_log_level to bound the forwarded verbosity and
Input::set_log_level_offset to shift it per input.
§License Notice
ez-ffmpeg is licensed under your choice of MIT, Apache-2.0, or MPL-2.0
(matching the license field in Cargo.toml).
Note: FFmpeg itself is subject to its own licensing terms. When enabling features that incorporate FFmpeg components, please ensure that your usage complies with FFmpeg’s license.
Re-exports§
pub use self::core::analysis;pub use self::core::capabilities;pub use self::core::codec;pub use self::core::container_info;pub use self::core::context::ffmpeg_context::FfmpegContext;pub use self::core::context::input::Input;pub use self::core::context::output::Output;pub use self::core::device;pub use self::core::filter;pub use self::core::frame_export;pub use self::core::hwaccel;pub use self::core::packet_scanner;pub use self::core::recipes;pub use self::core::scheduler::ffmpeg_scheduler::FfmpegScheduler;pub use self::core::stream_info;pub use self::core::writer::PushError;pub use self::core::writer::VideoWriter;pub use self::core::writer::VideoWriterBuilder;pub use self::core::set_ffmpeg_log_level;pub use self::core::FfmpegLogLevel;pub use self::core::FFMPEG_LOG_TARGET;
Modules§
- core
- The core module provides the foundational building blocks for configuring and running FFmpeg pipelines. It encompasses:
- error
- flv
- The FLV module contains data structures and tools to parse or handle FLV containers. It helps convert raw media data into FLV tags, making it easier to integrate with FFmpeg-based pipelines or RTMP streaming flows where FLV is the underlying format.
- opengl
Deprecated - Deprecated since 0.11.0 — superseded by the
crate::wgpu_filtermodule (featurewgpu). This module remains functional but will be removed in a future major release; new code should not use it. - rtmp
- The RTMP module includes an embedded RTMP server (
EmbedRtmpServer) built for production-grade streaming with high concurrency support. It receives data directly from memory—bypassing TCP between FFmpeg and the server. Inspired byrml_rtmp’s threaded RTMP server. - subtitle
- Native subtitle burn-in (ASS/SRT) rendered by a pure-Rust engine inside the frame pipeline.
- util
- wgpu_
filter - GPU-accelerated frame filtering backed by wgpu
(Vulkan/Metal/DX12/GL). Successor to the deprecated
openglmodule.