Readme
unbundle
A clean, ergonomic Rust library for extracting video frames, audio tracks, and subtitles from media files using FFmpeg.
New: unbundle-cli MVP
Install:
cargo install unbundle
Commands:
# Probe metadata
unbundle metadata input.mp4
# Extract frames
unbundle extract-frames input.mp4 --out frames --every 10 --start 0 --end 300
# Extract full audio
unbundle extract-audio input.mp4 --format mp3 --out audio.mp3
# Extract subtitle file
unbundle extract-subs input.mkv --format srt --out subs.srt
# Validate media structure
unbundle validate input.mp4
Quick Comparison
Tool
Best for
Level
Notes
unbundle
Rust-native media extraction APIs
High-level
Strong typing, iterators, options, errors
unbundle-cli
Simple extraction workflows
High-level
Friendly commands for common tasks
ffmpeg-next
Building custom low-level media flows
Mid/Low-level
Flexible, but more boilerplate
ffmpeg CLI
One-off shell pipelines
CLI-level
Powerful, but less ergonomic in Rust code
CI is passing on Linux, macOS, and Windows (see the CI badge above).
Contributing guide: CONTRIBUTING.md .
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " video.mp4" ) ? ;
// Extract a frame at 30 seconds
let frame = unbundler. video ( ) . frame_at ( Duration:: from_secs( 30 ) ) ? ;
frame. save ( " frame_30s.png" ) ? ;
// Extract complete audio track
unbundler. audio ( ) . save ( " audio.wav" , AudioFormat:: Wav) ? ;
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
use unbundle:: { MediaFile, ThumbnailHandle, ThumbnailOptions} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let options = ThumbnailOptions:: new( 4 , 3 ) . with_thumbnail_width ( 320 ) ;
let sheet = ThumbnailHandle:: grid( & mut unbundler, & options) ? ;
sheet. save ( " contact_sheet.png" ) ? ;
This creates a 4×3 contact sheet from evenly spaced frames across the video.
Installation
Add to your Cargo.toml :
[ dependencies ]
unbundle = " 5.1.0"
Or with additional features:
[ dependencies ]
unbundle = { version = "5.1.0", features = [ " async" , " rayon" , " hardware" ] }
System Requirements
unbundle requires FFmpeg libraries (4.0+) installed on your system.
Linux (Debian/Ubuntu):
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev \
libswscale-dev libswresample-dev libavdevice-dev pkg-config
macOS:
brew install ffmpeg pkg-config
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"
setx PATH "%PATH%;C:\vcpkg\installed\x64-windows\bin"
Then restart your terminal and run cargo build .
For the current shell only (without setx ):
$ env: VCPKG_ROOT = "C:\vcpkg"
$ env: VCPKGRS_DYNAMIC = "1"
$ env: FFMPEG_DIR = "C:\vcpkg\installed\x64-windows"
$ env: PATH = "C:\vcpkg\installed\x64-windows\bin;" + $ env: PATH
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
use std:: time:: Duration;
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Extract the first frame
let frame = unbundler. video ( ) . frame ( 0 ) ? ;
frame. save ( " first_frame.png" ) ? ;
// Extract a frame at 30 seconds
let frame = unbundler. video ( ) . frame_at ( Duration:: from_secs( 30 ) ) ? ;
frame. save ( " frame_30s.png" ) ? ;
use std:: time:: Duration;
use unbundle:: { FrameRange, MediaFile} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Every 30th frame
let frames = unbundler. video ( ) . frames ( FrameRange:: Interval( 30 ) ) ? ;
// Frames between two timestamps
let frames = unbundler. video ( ) . frames (
FrameRange:: TimeRange( Duration:: from_secs( 10 ) , Duration:: from_secs( 20 ) )
) ? ;
// Specific frame numbers
let frames = unbundler. video ( ) . frames (
FrameRange:: Specific( vec! [ 0 , 50 , 100 , 150 ] )
) ? ;
Open from URLs or Streams
use unbundle:: MediaFile;
// Works with paths, file:// URLs, and network URLs supported by your FFmpeg build.
let mut unbundler = MediaFile:: open_url( " https://example.com/video.mp4" ) ? ;
let metadata = unbundler. metadata ( ) ;
println! ( " Format: {} " , metadata. format) ;
open_url ( ) accepts any FFmpeg input string, including http:// , https:// , rtsp: // , and local path-like sources.
Apply FFmpeg Filters to Frames
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Chain multiple filters incrementally.
let frame = unbundler
. video ( )
. filter ( " scale=320:240" )
. filter ( " eq=contrast=1.1" )
. frame ( 0 ) ? ;
frame. save ( " filtered.png" ) ? ;
Streaming Frame Iteration
use unbundle:: { FrameRange, MediaFile} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Push-based: process each frame without buffering
unbundler. video ( ) . for_each_frame (
FrameRange:: Range( 0 , 99 ) ,
| frame_number , image | {
image. save ( format! ( " frame_{frame_number} .png" ) ) ? ;
Ok ( ( ) )
} ,
) ? ;
// Pull-based: lazy iterator with early exit
let iter = unbundler. video ( ) . frame_iter ( FrameRange:: Range( 0 , 99 ) ) ? ;
for result in iter {
let ( frame_number, image) = result? ;
image. save ( format! ( " frame_{frame_number} .png" ) ) ? ;
}
use std:: time:: Duration;
use unbundle:: { AudioFormat, MediaFile} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Save complete audio track to WAV
unbundler. audio ( ) . save ( " output.wav" , AudioFormat:: Wav) ? ;
// Extract a 30-second segment as MP3
unbundler. audio ( ) . save_range (
" segment.mp3" ,
Duration:: from_secs( 30 ) ,
Duration:: from_secs( 60 ) ,
AudioFormat:: Mp3,
) ? ;
// Extract audio to memory
let audio_bytes = unbundler. audio ( ) . extract ( AudioFormat:: Wav) ? ;
// Multi-track: extract the second audio track
let audio_bytes = unbundler. audio_track ( 1 ) ? . extract ( AudioFormat:: Wav) ? ;
use unbundle:: { MediaFile, SubtitleFormat} ;
let mut unbundler = MediaFile:: open( " input.mkv" ) ? ;
// Extract subtitle events with timing
let events = unbundler. subtitle ( ) . extract ( ) ? ;
for event in & events {
println! ( " [{:?} → {:?} ] {} " , event. start_time, event. end_time, event. text) ;
}
// Save as SRT file
unbundler. subtitle ( ) . save ( " output.srt" , SubtitleFormat:: Srt) ? ;
// Multi-track: extract the second subtitle track
unbundler. subtitle_track ( 1 ) ? . save ( " track2.vtt" , SubtitleFormat:: WebVtt) ? ;
Raw Stream Copy (No Re-encode)
use std:: time:: Duration;
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Copy video packets exactly as-is (codec preserved)
unbundler. video ( ) . stream_copy ( " video_copy.mp4" ) ? ;
// Copy a time segment without decoding/re-encoding
unbundler. video ( ) . stream_copy_range (
" segment.mp4" ,
Duration:: from_secs( 10 ) ,
Duration:: from_secs( 20 ) ,
) ? ;
// In-memory stream copy
let mkv_bytes = unbundler. video ( ) . stream_copy_to_memory ( " matroska" ) ? ;
// Audio stream copy (codec preserved)
unbundler. audio ( ) . stream_copy ( " audio_copy.aac" ) ? ;
let adts_bytes = unbundler. audio ( ) . stream_copy_to_memory ( " adts" ) ? ;
// Subtitle stream copy (codec preserved)
unbundler. subtitle ( ) . stream_copy ( " subs_copy.mkv" ) ? ;
let subtitle_bytes = unbundler. subtitle ( ) . stream_copy_to_memory ( " matroska" ) ? ;
Advanced Filter Graph Recipes
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Complex chain: resize -> crop -> mirror -> color adjust
let frame = unbundler. video ( ) . frame_with_filter (
42 ,
" scale=640:480,crop=320:240:10:20,hflip,transpose=1" ,
) ? ;
frame. save ( " filtered_advanced.png" ) ? ;
// Another chain: downscale + grayscale + mirror
let frame = unbundler
. video ( )
. frame_with_filter ( 42 , " scale=iw/2:ih/2,format=gray,hflip" ) ? ;
frame. save ( " filtered_denoise_gray.png" ) ? ;
Progress & Cancellation
use std:: sync:: Arc;
use unbundle:: {
CancellationToken, ExtractOptions, FrameRange,
MediaFile, ProgressCallback, ProgressInfo,
} ;
struct PrintProgress ;
impl ProgressCallback for PrintProgress {
fn on_progress ( & self , info : & ProgressInfo) {
println! ( " Frame {} /{} " , info. current, info. total. unwrap_or ( 0 ) ) ;
}
}
let token = CancellationToken:: new( ) ;
let config = ExtractOptions:: new( )
. with_progress ( Arc:: new( PrintProgress) )
. with_cancellation ( token. clone ( ) ) ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let frames = unbundler. video ( ) . frames_with_options (
FrameRange:: Range( 0 , 99 ) ,
& config,
) ? ;
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 CancellationToken for long-running operations
Streaming iteration — lazy FrameIterator (pull-based) and for_each_frame (push-based) without buffering entire frame sets
Audio sample iteration — lazy AudioIterator yields 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 MediaProbe for 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
[ dependencies ]
unbundle = { version = "5.1.0", features = [ " full" ] }
Feature Usage Guide
Use async when integrating with async web servers or when processing multiple videos concurrently
Use rayon for CPU-intensive batch frame extraction (e.g., generating thousands of thumbnails)
Use hardware when processing high-resolution video (4K+) or when CPU is a bottleneck
Use scene for video analysis, automatic chapter detection, or intelligent thumbnail selection
Use gif for creating preview animations or social media content
Use waveform and loudness for audio visualization or normalization workflows
Use transcode for audio format conversion in media pipelines
Use encode for 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:
cargo run -- example metadata -- path/to/video.mp4
Changelog
Release notes are tracked in CHANGELOG.md .
Advanced Usage
Container Remuxing
use unbundle:: Remuxer;
// Convert MKV to MP4 without re-encoding
Remuxer:: new( " input.mkv" , " output.mp4" ) ? . run ( ) ? ;
// Exclude subtitles during remux
Remuxer:: new( " input.mkv" , " output.mp4" ) ?
. exclude_subtitles ( )
. run ( ) ? ;
use unbundle:: { ExtractOptions, FrameRange, MediaFile, PixelFormat} ;
let config = ExtractOptions:: new( )
. with_pixel_format ( PixelFormat:: Rgba8)
. with_resolution ( Some ( 1280 ) , Some ( 720 ) ) ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let frames = unbundler. video ( ) . frames_with_options (
FrameRange:: Interval( 30 ) ,
& config,
) ? ;
use unbundle:: MediaFile;
let unbundler = MediaFile:: open( " input.mp4" ) ? ;
let metadata = unbundler. metadata ( ) ;
println! ( " Duration: {:?} " , metadata. duration) ;
println! ( " Format: {} " , metadata. format) ;
if let Some ( video) = & metadata. video {
println! ( " Video: {} x{} , {:.2} fps, {} frames" ,
video. width, video. height,
video. frames_per_second, video. frame_count) ;
println! ( " Codec: {} " , video. codec) ;
}
if let Some ( audio) = & metadata. audio {
println! ( " Audio: {} Hz, {} channels, codec: {} " ,
audio. sample_rate, audio. channels, audio. codec) ;
}
// List all audio and subtitle tracks
if let Some ( tracks) = & metadata. audio_tracks {
println! ( " {} audio track(s)" , tracks. len ( ) ) ;
}
if let Some ( tracks) = & metadata. subtitle_tracks {
println! ( " {} subtitle track(s)" , tracks. len ( ) ) ;
}
use unbundle:: MediaFile;
let unbundler = MediaFile:: open( " input.mp4" ) ? ;
let report = unbundler. validate ( ) ;
if report. is_valid ( ) {
println! ( " File is valid" ) ;
} else {
println! ( " {report} " ) ;
}
use unbundle:: MediaProbe;
// Quick metadata inspection without keeping the file open
let metadata = MediaProbe:: probe( " input.mp4" ) ? ;
println! ( " Duration: {:?} " , metadata. duration) ;
// Probe multiple files at once
let results = MediaProbe:: probe_many( & [ " video1.mp4" , " video2.mkv" ] ) ;
use unbundle:: MediaFile;
let unbundler = MediaFile:: open( " input.mkv" ) ? ;
let metadata = unbundler. metadata ( ) ;
if let Some ( chapters) = & metadata. chapters {
for chapter in chapters {
println! ( " [{:?} → {:?} ] {} " ,
chapter. start, chapter. end,
chapter. title. as_deref ( ) . unwrap_or ( " Untitled" ) ) ;
}
}
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Get a frame with its decode metadata
let ( image, info) = unbundler. video ( ) . frame_and_metadata ( 0 ) ? ;
println! ( " Frame {} : keyframe={} , type={:?} , pts={:?} " ,
info. frame_number, info. is_keyframe, info. frame_type, info. pts) ;
Thumbnail Generation
use std:: time:: Duration;
use unbundle:: { MediaFile, ThumbnailHandle, ThumbnailOptions} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Single thumbnail at a timestamp
let thumb = ThumbnailHandle:: at_timestamp( & mut unbundler, Duration:: from_secs( 5 ) , 320 ) ? ;
// Contact-sheet grid
let config = ThumbnailOptions:: new( 4 , 3 ) ; // 4 columns × 3 rows
let grid = ThumbnailHandle:: grid( & mut unbundler, & config) ? ;
grid. save ( " contact_sheet.png" ) ? ;
// Smart thumbnail (picks frame with highest visual variance)
let smart = ThumbnailHandle:: smart( & mut unbundler, 10 , 320 ) ? ;
GIF Export
use std:: time:: Duration;
use unbundle:: { FrameRange, GifOptions, MediaFile} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let config = GifOptions:: new( ) . width ( 320 ) . frame_delay ( 10 ) ;
unbundler. video ( ) . export_gif (
" output.gif" ,
FrameRange:: TimeRange( Duration:: from_secs( 0 ) , Duration:: from_secs( 5 ) ) ,
& config,
) ? ;
// Or export to memory
let bytes = unbundler. video ( ) . export_gif_to_memory (
FrameRange:: Interval( 10 ) ,
& config,
) ? ;
use unbundle:: { MediaFile, WaveformOptions} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let waveform = unbundler. audio ( ) . generate_waveform (
& WaveformOptions:: new( ) . bins ( 1000 ) ,
) ? ;
for bin in & waveform. bins {
println! ( " min={:.3} max={:.3} rms={:.3} " , bin. min, bin. max, bin. rms) ;
}
Loudness Analysis
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let loudness = unbundler. audio ( ) . analyze_loudness ( ) ? ;
println! ( " Peak: {:.1} dBFS, RMS: {:.1} dBFS" , loudness. peak_dbfs, loudness. rms_dbfs) ;
Audio Sample Iteration
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let iter = unbundler. audio ( ) . sample_iter ( ) ? ;
let mut total_samples = 0 u64 ;
for chunk in iter {
let chunk = chunk? ;
total_samples += chunk. samples. len ( ) as u64 ;
}
println! ( " Total mono samples: {total_samples} " ) ;
Audio Transcoding
use unbundle:: { AudioFormat, MediaFile, Transcoder} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
// Re-encode audio from the source format to MP3
Transcoder:: new( & mut unbundler)
. format ( AudioFormat:: Mp3)
. run ( " output.mp3" ) ? ;
Video Writing
use unbundle:: { MediaFile, FrameRange, VideoEncoder, VideoEncoderOptions, VideoCodec} ;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let frames = unbundler. video ( ) . frames ( FrameRange:: Interval( 30 ) ) ? ;
let config = VideoEncoderOptions:: default( )
. resolution ( 1920 , 1080 )
. frames_per_second ( 24 )
. codec ( VideoCodec:: H264 ) ;
VideoEncoder:: new( config) . write ( " output.mp4" , & frames) ? ;
Keyframe & Group of Pictures Analysis
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let group_of_pictures = unbundler. video ( ) . analyze_group_of_pictures ( ) ? ;
println! (
" Keyframes: {} , Average Group of Pictures size: {:.1} " ,
group_of_pictures. keyframes. len ( ) ,
group_of_pictures. average_group_of_pictures_size
) ;
VFR Detection
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
let analysis = unbundler. video ( ) . analyze_variable_framerate ( ) ? ;
println! ( " VFR: {} , mean FPS: {:.2} " , analysis. is_variable_frame_rate, analysis. mean_frames_per_second) ;
Packet Inspection
use unbundle:: MediaFile;
let mut unbundler = MediaFile:: open( " input.mp4" ) ? ;
for packet in unbundler. packet_iter ( ) ? {
let packet = packet? ;
println! ( " stream={} pts={:?} size={} key={} " ,
packet. stream_index, packet. pts, packet. size, packet. is_keyframe) ;
}
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 )
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.)
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:: Specific sorts requested frame numbers and processes them in order to minimize seeks.
Memory-efficient streaming — for_each_frame and FrameIterator process frames one at a time without buffering entire frame sets.
Parallel extraction — frames_parallel ( ) (feature rayon ) 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 image buffers.
Zero-copy audio — Uses avio_open_dyn_buf for in-memory audio encoding without temporary files.
Testing
Generate test fixtures first:
# Linux / macOS
bash tests/fixtures/generate_fixtures.sh
# Windows
tests\fixtures\generate_fixtures.bat
Then run tests:
cargo test -- all-features
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:
cargo bench -- all-features
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_PATH to point to FFmpeg's . pc files
On Windows, set FFMPEG_DIR environment 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 - codecs to 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 - hwaccels to 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 ( ) or for_each_frame ( )
Process frames in smaller batches
Use AudioIterator for large audio files instead of loading entire tracks
Problem: Frame extraction is slower than expected
Solution:
Use frames_parallel ( ) (feature rayon ) 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:: Interval instead 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.