unbundle
A clean, ergonomic Rust library for extracting video frames, audio tracks, and subtitles from media files using FFmpeg.
CI is passing on Linux, macOS, and Windows (see the CI badge above). Contributing guide: CONTRIBUTING.md.
Project metadata
- Homepage: https://docs.rs/unbundle
- Repository: https://github.com/skanderjeddi/unbundle
- Changelog: https://github.com/skanderjeddi/unbundle/releases
- Topics: rust, ffmpeg, video, audio, subtitles, media-processing
Keep this section in sync with the GitHub About panel (description, homepage, and topics).
use MediaFile;
let mut unbundler = open?;
// Extract a frame at 30 seconds
let frame = unbundler.video.frame_at?;
frame.save?;
// Extract complete audio track
unbundler.audio.save?;
Why unbundle?
- Type-safe API — frames as
image::DynamicImage, audio as bytes or files, subtitles as structured events - Flexible extraction — by frame number, timestamp, range, interval, or custom frame lists
- Streaming support — lazy iterators and async streams avoid buffering entire frame sets
- Rich metadata — dimensions, frame rates, codecs, chapters, per-frame decode info
- Production-ready — progress callbacks, cancellation tokens, hardware acceleration, parallel processing
Use Cases
- Video thumbnails — contact sheets, smart frame selection, chapter previews
- Media processing — format conversion, audio extraction, subtitle manipulation
- Analysis tools — scene detection, keyframe analysis, Group of Pictures structure inspection
- Content indexing — frame extraction for search, waveform visualization
- Transcoding pipelines — lossless remuxing, audio re-encoding
Contact-sheet mini-guide
use ;
let mut unbundler = open?;
let options = new.with_thumbnail_width;
let sheet = grid?;
sheet.save?;
This creates a 4×3 contact sheet from evenly spaced frames across the video.
Installation
Add to your Cargo.toml:
[]
= "5.0.0"
Or with additional features:
[]
= { = "5.0.0", = ["async", "rayon", "hardware"] }
System Requirements
unbundle requires FFmpeg libraries (4.0+) installed on your system.
Linux (Debian/Ubuntu):
macOS:
Windows:
Use vcpkg (recommended for headers + libs):
# Install vcpkg if needed
git clone https://github.com/microsoft/vcpkg.git C:\vcpkg
C:\vcpkg\bootstrap-vcpkg.bat
# Install FFmpeg for MSVC x64
vcpkg install ffmpeg:x64-windows
# Configure environment for build scripts
setx VCPKG_ROOT "C:\vcpkg"
setx VCPKGRS_DYNAMIC "1"
setx FFMPEG_DIR "C:\vcpkg\installed\x64-windows"
Then restart your terminal and run cargo build.
The crate includes a Windows build helper (build.rs) that emits guidance when
FFMPEG_DIR is missing and a vcpkg install is detected.
Quick Start
Extract Video Frames
use Duration;
use MediaFile;
let mut unbundler = open?;
// Extract the first frame
let frame = unbundler.video.frame?;
frame.save?;
// Extract a frame at 30 seconds
let frame = unbundler.video.frame_at?;
frame.save?;
Extract Multiple Frames
use Duration;
use ;
let mut unbundler = open?;
// Every 30th frame
let frames = unbundler.video.frames?;
// Frames between two timestamps
let frames = unbundler.video.frames?;
// Specific frame numbers
let frames = unbundler.video.frames?;
Open from URLs or Streams
use MediaFile;
// Works with paths, file:// URLs, and network URLs supported by your FFmpeg build.
let mut unbundler = open_url?;
let metadata = unbundler.metadata;
println!;
open_url() accepts any FFmpeg input string, including http://, https://, rtsp://, and local path-like sources.
Apply FFmpeg Filters to Frames
use MediaFile;
let mut unbundler = open?;
// Chain multiple filters incrementally.
let frame = unbundler
.video
.filter
.filter
.frame?;
frame.save?;
Streaming Frame Iteration
use ;
let mut unbundler = open?;
// Push-based: process each frame without buffering
unbundler.video.for_each_frame?;
// Pull-based: lazy iterator with early exit
let iter = unbundler.video.frame_iter?;
for result in iter
Extract Audio
use Duration;
use ;
let mut unbundler = open?;
// Save complete audio track to WAV
unbundler.audio.save?;
// Extract a 30-second segment as MP3
unbundler.audio.save_range?;
// Extract audio to memory
let audio_bytes = unbundler.audio.extract?;
// Multi-track: extract the second audio track
let audio_bytes = unbundler.audio_track?.extract?;
Extract Subtitles
use ;
let mut unbundler = open?;
// Extract subtitle events with timing
let events = unbundler.subtitle.extract?;
for event in &events
// Save as SRT file
unbundler.subtitle.save?;
// Multi-track: extract the second subtitle track
unbundler.subtitle_track?.save?;
Raw Stream Copy (No Re-encode)
use Duration;
use MediaFile;
let mut unbundler = open?;
// Copy video packets exactly as-is (codec preserved)
unbundler.video.stream_copy?;
// Copy a time segment without decoding/re-encoding
unbundler.video.stream_copy_range?;
// In-memory stream copy
let mkv_bytes = unbundler.video.stream_copy_to_memory?;
// Audio stream copy (codec preserved)
unbundler.audio.stream_copy?;
let adts_bytes = unbundler.audio.stream_copy_to_memory?;
// Subtitle stream copy (codec preserved)
unbundler.subtitle.stream_copy?;
let subtitle_bytes = unbundler.subtitle.stream_copy_to_memory?;
Advanced Filter Graph Recipes
use MediaFile;
let mut unbundler = open?;
// Complex chain: resize -> crop -> mirror -> color adjust
let frame = unbundler.video.frame_with_filter?;
frame.save?;
// Another chain: downscale + grayscale + mirror
let frame = unbundler
.video
.frame_with_filter?;
frame.save?;
Progress & Cancellation
use Arc;
use ;
;
let token = new;
let config = new
.with_progress
.with_cancellation;
let mut unbundler = open?;
let frames = unbundler.video.frames_with_options?;
Features
Core Capabilities
- Frame extraction — by frame number, timestamp, range, interval, or specific frame list
- Audio extraction — to WAV, MP3, FLAC, or AAC (file or in-memory)
- Subtitle extraction — decode text-based subtitles to SRT, WebVTT, or raw text
- Container remuxing — lossless format conversion (e.g. MKV → MP4) without re-encoding
- Rich metadata — video dimensions, frame rate, frame count, audio sample rate, channels, codec info, multi-track audio/subtitle metadata
- Configurable output — pixel format (RGB8, RGBA8, GRAY8), target resolution with aspect ratio preservation
- Custom FFmpeg filters — apply filter graphs during frame extraction (e.g. scale, crop, eq, hflip)
- Progress & cancellation — cooperative progress callbacks and
CancellationTokenfor long-running operations - Streaming iteration — lazy
FrameIterator(pull-based) andfor_each_frame(push-based) without buffering entire frame sets - Audio sample iteration — lazy
AudioIteratoryields mono f32 chunks for incremental audio processing - Validation — inspect media files for structural issues before extraction
- Chapter support — extract chapter metadata (titles, timestamps) from containers
- Frame metadata — per-frame decode info (PTS, keyframe flag, picture type) via
frame_and_metadata/frames_and_metadata - Segmented extraction — extract frames from multiple disjoint time ranges in a single call with
FrameRange::Segments - Stream probing — lightweight
MediaProbefor quick metadata inspection without keeping the demuxer open - Thumbnail helpers — single-frame thumbnails, contact-sheet grids, and variance-based "smart" thumbnail selection
- Keyframe & Group of Pictures analysis — scan video packets for keyframe positions and Group of Pictures structure without decoding
- VFR detection — detect variable frame rate streams and analyze PTS distributions
- Packet iteration — raw packet-level demuxer iteration for advanced inspection
- Raw stream copy — copy video/audio/subtitle packets directly to file or memory without re-encoding
- Efficient seeking — seeks to the nearest keyframe, then decodes forward
- Zero-copy in-memory audio — uses FFmpeg's dynamic buffer I/O
Optional Features
Enable additional functionality through Cargo features:
| Feature | Description |
|---|---|
async |
FrameStream (async frame iteration) and AudioFuture via Tokio |
rayon |
frames_parallel() distributes decoding across rayon threads |
hardware |
Hardware-accelerated decoding (CUDA, VAAPI, DXVA2, D3D11VA, VideoToolbox, QSV) |
scene |
Scene change detection via FFmpeg's scdet filter |
gif |
Animated GIF export from video frames |
waveform |
Audio waveform visualization data (min/max/RMS per bin) |
loudness |
Peak/RMS loudness analysis with dBFS conversion |
transcode |
Audio re-encoding between formats (e.g. AAC → MP3) |
encode |
Encode DynamicImage sequences into video files (H.264, H.265, MPEG-4) |
full |
Enables all of the above |
[]
= { = "4.3.8", = ["full"] }
Feature Usage Guide
- Use
asyncwhen integrating with async web servers or when processing multiple videos concurrently - Use
rayonfor CPU-intensive batch frame extraction (e.g., generating thousands of thumbnails) - Use
hardwarewhen processing high-resolution video (4K+) or when CPU is a bottleneck - Use
scenefor video analysis, automatic chapter detection, or intelligent thumbnail selection - Use
giffor creating preview animations or social media content - Use
waveformandloudnessfor audio visualization or normalization workflows - Use
transcodefor audio format conversion in media pipelines - Use
encodefor creating time-lapses, slideshows, or re-encoding frame sequences
Examples
The examples/ directory contains complete, runnable examples:
| Example | Description |
|---|---|
extract_frames |
Extract frames by number, timestamp, range, interval |
extract_audio |
Extract the complete audio track |
extract_audio_segment |
Extract a specific time range as MP3 |
open_url |
Open from URL/path-like source strings |
thumbnail |
Create a thumbnail grid from evenly-spaced frames |
metadata |
Display all media metadata |
video_iterator |
Lazy frame iteration with early exit |
pixel_formats |
Demonstrate RGB8/RGBA8/GRAY8 output |
progress |
Progress callbacks and cancellation |
subtitle |
Extract subtitles as SRT/WebVTT/raw text |
remux |
Lossless container format conversion |
validate |
Media file validation report |
async_extraction |
Async frame streaming and audio extraction (async) |
rayon |
Parallel frame extraction across threads (rayon) |
scene |
Scene change detection (scene) |
hardware_acceleration |
Hardware-accelerated decoding (hardware) |
gif_export |
Export video frames as animated GIF (gif) |
waveform |
Generate audio waveform data (waveform) |
loudness |
Analyze audio loudness levels (loudness) |
audio_iterator |
Lazy audio sample iteration |
video_encoder |
Encode image sequences into video files (encode) |
transcode |
Re-encode audio between formats (transcode) |
keyframe |
Group of Pictures/keyframe structure analysis |
variable_framerate |
Variable frame rate detection |
packet_iterator |
Raw packet-level demuxer inspection |
subtitle_search |
Search subtitle text content |
Run an example:
CLI (MVP)
This crate now ships a minimal CLI binary:
The CLI is intentionally small and focused on common operations.
Changelog
Release notes are tracked in CHANGELOG.md.
Advanced Usage
Container Remuxing
use Remuxer;
// Convert MKV to MP4 without re-encoding
new?.run?;
// Exclude subtitles during remux
new?
.exclude_subtitles
.run?;
Custom Output Format
use ;
let config = new
.with_pixel_format
.with_resolution;
let mut unbundler = open?;
let frames = unbundler.video.frames_with_options?;
Read Metadata
use MediaFile;
let unbundler = open?;
let metadata = unbundler.metadata;
println!;
println!;
if let Some = &metadata.video
if let Some = &metadata.audio
// List all audio and subtitle tracks
if let Some = &metadata.audio_tracks
if let Some = &metadata.subtitle_tracks
Validate Media Files
use MediaFile;
let unbundler = open?;
let report = unbundler.validate;
if report.is_valid else
Probe Media Files
use MediaProbe;
// Quick metadata inspection without keeping the file open
let metadata = probe?;
println!;
// Probe multiple files at once
let results = probe_many;
Chapter Metadata
use MediaFile;
let unbundler = open?;
let metadata = unbundler.metadata;
if let Some = &metadata.chapters
Frame Metadata
use MediaFile;
let mut unbundler = open?;
// Get a frame with its decode metadata
let = unbundler.video.frame_and_metadata?;
println!;
Thumbnail Generation
use Duration;
use ;
let mut unbundler = open?;
// Single thumbnail at a timestamp
let thumb = at_timestamp?;
// Contact-sheet grid
let config = new; // 4 columns × 3 rows
let grid = grid?;
grid.save?;
// Smart thumbnail (picks frame with highest visual variance)
let smart = smart?;
GIF Export
use Duration;
use ;
let mut unbundler = open?;
let config = new.width.frame_delay;
unbundler.video.export_gif?;
// Or export to memory
let bytes = unbundler.video.export_gif_to_memory?;
Audio Waveform
use ;
let mut unbundler = open?;
let waveform = unbundler.audio.generate_waveform?;
for bin in &waveform.bins
Loudness Analysis
use MediaFile;
let mut unbundler = open?;
let loudness = unbundler.audio.analyze_loudness?;
println!;
Audio Sample Iteration
use MediaFile;
let mut unbundler = open?;
let iter = unbundler.audio.sample_iter?;
let mut total_samples = 0u64;
for chunk in iter
println!;
Audio Transcoding
use ;
let mut unbundler = open?;
// Re-encode audio from the source format to MP3
new
.format
.run?;
Video Writing
use ;
let mut unbundler = open?;
let frames = unbundler.video.frames?;
let config = default
.resolution
.frames_per_second
.codec;
new.write?;
Keyframe & Group of Pictures Analysis
use MediaFile;
let mut unbundler = open?;
let group_of_pictures = unbundler.video.analyze_group_of_pictures?;
println!;
VFR Detection
use MediaFile;
let mut unbundler = open?;
let analysis = unbundler.video.analyze_variable_framerate?;
println!;
Packet Inspection
use MediaFile;
let mut unbundler = open?;
for packet in unbundler.packet_iter?
API Documentation
Complete API documentation is available at docs.rs/unbundle.
Essential Types
| Type | Description |
|---|---|
MediaFile |
Main entry point — opens a media file and provides access to media handles |
VideoHandle |
Extracts video frames as DynamicImage |
AudioHandle |
Extracts audio tracks as bytes or files |
SubtitleHandle |
Extracts text-based subtitle tracks |
FrameRange |
Specifies which frames to extract (range, interval, timestamps, etc.) |
ExtractOptions |
Configure threading, progress callbacks, cancellation, pixel format, resolution, hardware acceleration |
Stream & Iteration Types
| Type | Description |
|---|---|
FrameIterator |
Lazy, pull-based frame iterator |
AudioIterator |
Lazy pull-based audio sample iterator (mono f32) |
AudioChunk |
A chunk of decoded audio samples with timing |
PacketIterator |
Lazy raw-packet-level demuxer iterator |
FrameStream |
Async stream of decoded frames via Tokio (feature: async) |
AudioFuture |
Async audio extraction future (feature: async) |
Configuration Types
| Type | Description |
|---|---|
FrameOutputOptions |
Pixel format and resolution settings for frame output |
PixelFormat |
Output pixel format (RGB8, RGBA8, GRAY8) |
AudioFormat |
Output audio format (WAV, MP3, FLAC, AAC) |
SubtitleFormat |
Output subtitle format (SRT, WebVTT, Raw) |
ThumbnailOptions |
Grid thumbnail options (columns, rows, width) |
GifOptions |
Animated GIF export configuration (width, delay, repeat) (feature: gif) |
WaveformOptions |
Waveform generation settings (bin count, time range) (feature: waveform) |
SceneDetectionOptions |
Scene detection threshold configuration (feature: scene) |
VideoEncoderOptions |
Video encoder settings (FPS, resolution, codec, CRF) (feature: encode) |
HardwareAccelerationMode |
Hardware acceleration mode selection (feature: hardware) |
Metadata Types
| Type | Description |
|---|---|
MediaMetadata |
Container-level metadata (duration, format) |
VideoMetadata |
Video stream metadata (dimensions, frame rate, codec) |
AudioMetadata |
Audio stream metadata (sample rate, channels, codec) |
SubtitleMetadata |
Subtitle stream metadata (codec, language) |
ChapterMetadata |
Chapter information (title, start/end times) |
FrameMetadata |
Per-frame decode metadata (PTS, keyframe flag, picture type) |
FrameType |
Picture type enum (I, P, B, etc.) |
KeyFrameMetadata |
Keyframe position metadata (packet number, PTS, timestamp) |
GroupOfPicturesInfo |
Group of Pictures structure analysis result (keyframes, sizes, statistics) |
VariableFrameRateAnalysis |
Variable frame rate detection result (min/max/mean FPS) |
PacketInfo |
Per-packet metadata (stream index, PTS, DTS, size, keyframe) |
Utility Types
| Type | Description |
|---|---|
MediaProbe |
Lightweight stateless media file probing |
ThumbnailHandle |
Thumbnail generation helpers (single, grid, smart) |
Remuxer |
Lossless container format conversion |
ValidationReport |
Result of media file validation |
ProgressCallback |
Trait for receiving progress updates |
ProgressInfo |
Progress event data (current, total, percentage, ETA) |
CancellationToken |
Cooperative cancellation via Arc<AtomicBool> |
OperationType |
Identifies the operation being tracked |
UnbundleError |
Error type with rich context |
SubtitleEvent |
A single decoded subtitle event (text, start/end time) |
BitmapSubtitleEvent |
A bitmap subtitle event with image and timing |
Feature-Specific Types
| Type | Feature | Description |
|---|---|---|
Transcoder |
transcode |
Audio re-encoding builder (format, range, bitrate) |
VideoEncoder |
encode |
Encodes image sequences into video files |
VideoCodec |
encode |
Supported video codecs (H.264, H.265, MPEG-4) |
WaveformData |
waveform |
Generated waveform result with per-bin statistics |
WaveformBin |
waveform |
Single waveform bin (min, max, RMS amplitude) |
LoudnessInfo |
loudness |
Peak/RMS loudness with dBFS equivalents |
SceneChange |
scene |
Detected scene change with timestamp and score |
HardwareDeviceType |
hardware |
Supported hardware device types (CUDA, VAAPI, etc.) |
Performance
unbundle is designed for efficiency in both single-file and batch processing scenarios:
- Smart seeking — Uses FFmpeg's keyframe-based seeking. For sequential access (ranges, intervals), frames are decoded without redundant seeks.
- Lightweight decoders — Each extraction call creates a fresh decoder. FFmpeg decoder creation is fast relative to actual decoding work.
- Batch optimization —
FrameRange::Specificsorts requested frame numbers and processes them in order to minimize seeks. - Memory-efficient streaming —
for_each_frameandFrameIteratorprocess frames one at a time without buffering entire frame sets. - Parallel extraction —
frames_parallel()(featurerayon) splits frames across rayon threads, each with its own demuxer for true parallelism. - Hardware acceleration — When enabled (feature
hardware), attempts GPU-accelerated decoding with automatic fallback to software. - Correct stride handling — Properly handles FFmpeg's row padding when converting frames to
imagebuffers. - Zero-copy audio — Uses
avio_open_dyn_buffor in-memory audio encoding without temporary files.
Testing
Generate test fixtures first:
# Linux / macOS
# Windows
Then run tests:
Test Coverage
The test suite includes comprehensive coverage:
| Test Module | Coverage |
|---|---|
video |
Single frames, ranges, intervals, timestamps, specific lists, pixel formats, resolution scaling |
audio |
WAV/MP3/FLAC/AAC extraction, ranges, file output, multi-track |
subtitle |
Subtitle decoding, SRT/WebVTT export, multi-track |
metadata |
Container metadata, video/audio/subtitle stream properties |
configuration |
ExtractOptions builder, pixel formats, resolution, cancellation |
progress |
ProgressCallback, ProgressInfo fields, CancellationToken |
error_handling |
Error variants, context, invalid inputs, missing streams |
video_iterator |
FrameIterator, lazy iteration, early exit |
conversion |
Remuxer, stream exclusion, lossless format conversion |
validation |
ValidationReport, warnings, errors, valid files |
chapters |
Chapter metadata extraction, titles, timestamps, ordering |
frame_metadata |
FrameMetadata, FrameType, keyframe detection, PTS values |
segmented_extraction |
FrameRange::Segments, multiple disjoint time ranges |
probing |
MediaProbe, probe/probe_many, error handling |
thumbnail |
ThumbnailHandle, grid, smart selection, aspect ratio |
audio_iterator |
AudioIterator, chunk iteration, sample rates |
keyframe |
GroupOfPicturesInfo, KeyFrameMetadata, Group of Pictures statistics |
variable_framerate |
VariableFrameRateAnalysis, constant vs variable frame rate |
packet_iterator |
PacketIterator, PacketInfo, stream filtering |
subtitle_search |
Subtitle search, case-insensitive matching |
metadata_extended |
Extended metadata: video tracks, colorspace, HDR |
Feature-specific tests (require corresponding features enabled):
| Test Module | Feature Required | Coverage |
|---|---|---|
scene |
scene |
Scene change detection, threshold configuration |
async_extraction |
async |
FrameStream, AudioFuture, async streaming |
rayon |
rayon |
frames_parallel, sequential parity, interval mode |
hardware_acceleration |
hardware |
Hardware device enumeration, Auto/Software modes |
gif_export |
gif |
GIF encoding, file and in-memory output |
waveform |
waveform |
WaveformOptions, bin statistics, time ranges |
loudness |
loudness |
Peak/RMS loudness, dBFS values |
video_encoder |
encode |
VideoEncoder, codec selection, frame encoding |
transcode |
transcode |
Transcoder, format conversion, time ranges |
Benchmarks
Run performance benchmarks:
Criterion benchmarks are located in benches/ and measure:
- Frame extraction throughput (single vs parallel)
- Seek performance (sequential vs random access)
- Audio extraction speed across formats
- Iterator overhead vs batch extraction
Troubleshooting
FFmpeg Linking Errors
Problem: error: linking with 'cc' failed or cannot find -lavcodec
Solution:
- Ensure FFmpeg development libraries are installed (see Installation)
- Set
PKG_CONFIG_PATHto point to FFmpeg's.pcfiles - On Windows, set
FFMPEG_DIRenvironment variable - Verify with:
pkg-config --libs --cflags libavcodec
Codec Not Supported
Problem: UnbundleError::UnsupportedAudioFormat
Solution:
- Check that your FFmpeg build includes the required codec
- Run
ffmpeg -codecsto list available codecs - Some codecs require FFmpeg to be built with specific flags (e.g.,
--enable-libx264)
Hardware Acceleration Fails
Problem: Hardware decoding falls back to software or fails entirely
Solution:
- Verify GPU drivers are up to date
- Check available hardware devices:
unbundle::hardware_acceleration::available_hardware_devices() - Use
ExtractOptions::with_hardware_acceleration(HardwareAccelerationMode::Auto)for automatic fallback - Not all codecs/formats support hardware acceleration
- Try
ffmpeg -hwaccelsto list available hardware acceleration methods
Out of Memory
Problem: High memory usage when extracting many frames
Solution:
- Use streaming iteration instead of batch extraction:
frame_iter()orfor_each_frame() - Process frames in smaller batches
- Use
AudioIteratorfor large audio files instead of loading entire tracks
Slow Frame Extraction
Problem: Frame extraction is slower than expected
Solution:
- Use
frames_parallel()(featurerayon) for CPU-bound workloads - Enable hardware acceleration (feature
hardware) for high-resolution video - Avoid extracting specific frames in random order — sorted access is much faster
- Consider using
FrameRange::Intervalinstead of many individual frame numbers
Permission Denied / File Not Found
Problem: Cannot open media file
Solution:
- Verify file path is correct and file exists
- Check file permissions (readable by current user)
- Ensure file is not locked by another process
- On Windows, use raw string literals for paths:
r"C:\path\to\video.mp4"
Contributing
Contributions are welcome! Please see the GitHub repository for:
- Bug reports and feature requests
- Pull requests
- Discussions and questions
License
MIT — see LICENSE file for details.