ff_probe/lib.rs
1//! # ff-probe
2//!
3//! Media file metadata extraction - the Rust way.
4//!
5//! This crate provides functionality for extracting metadata from media files,
6//! including video streams, audio streams, and container information. It serves
7//! as the Rust equivalent of ffprobe with a clean, idiomatic API.
8//!
9//! ## Module Structure
10//!
11//! - `error` - Error types ([`ProbeError`])
12//! - `info` - Media info extraction ([`open`])
13//!
14//! ## Quick Start
15//!
16//! ```no_run
17//! use ff_probe::open;
18//!
19//! fn main() -> Result<(), Box<dyn std::error::Error>> {
20//! let info = open("video.mp4")?;
21//!
22//! println!("Format: {}", info.format());
23//! println!("Duration: {:?}", info.duration());
24//!
25//! // Check for video stream
26//! if let Some(video) = info.primary_video() {
27//! println!("Video: {}x{} @ {:.2} fps",
28//! video.width(),
29//! video.height(),
30//! video.fps()
31//! );
32//! }
33//!
34//! // Check for audio stream
35//! if let Some(audio) = info.primary_audio() {
36//! println!("Audio: {} Hz, {} channels",
37//! audio.sample_rate(),
38//! audio.channels()
39//! );
40//! }
41//!
42//! Ok(())
43//! }
44//! ```
45//!
46//! ## Extracting Detailed Information
47//!
48//! ```no_run
49//! use ff_probe::{open, ColorSpace, ColorPrimaries};
50//!
51//! fn main() -> Result<(), Box<dyn std::error::Error>> {
52//! let info = open("hdr_video.mp4")?;
53//!
54//! // Enumerate all video streams
55//! for (i, stream) in info.video_streams().iter().enumerate() {
56//! println!("Video stream {}: {} {}x{}",
57//! i, stream.codec_name(), stream.width(), stream.height());
58//! println!(" Color space: {:?}", stream.color_space());
59//! println!(" Color range: {:?}", stream.color_range());
60//!
61//! // Check for HDR content
62//! if stream.color_primaries() == ColorPrimaries::Bt2020 {
63//! println!(" HDR content detected!");
64//! }
65//!
66//! if let Some(bitrate) = stream.bitrate() {
67//! println!(" Bitrate: {} kbps", bitrate / 1000);
68//! }
69//! }
70//!
71//! // Enumerate all audio streams
72//! for (i, stream) in info.audio_streams().iter().enumerate() {
73//! println!("Audio stream {}: {} {} Hz, {} ch",
74//! i, stream.codec_name(), stream.sample_rate(), stream.channels());
75//! if let Some(lang) = stream.language() {
76//! println!(" Language: {}", lang);
77//! }
78//! }
79//!
80//! // Access container metadata
81//! if let Some(title) = info.title() {
82//! println!("Title: {}", title);
83//! }
84//! if let Some(artist) = info.artist() {
85//! println!("Artist: {}", artist);
86//! }
87//!
88//! Ok(())
89//! }
90//! ```
91//!
92//! ## Error Handling
93//!
94//! The crate provides detailed error types through [`ProbeError`]:
95//!
96//! ```
97//! use ff_probe::{open, ProbeError};
98//!
99//! let result = open("/nonexistent/path.mp4");
100//!
101//! match result {
102//! Err(ProbeError::FileNotFound { path }) => {
103//! println!("File not found: {}", path.display());
104//! }
105//! Err(ProbeError::CannotOpen { path, reason }) => {
106//! println!("Cannot open {}: {}", path.display(), reason);
107//! }
108//! Err(ProbeError::InvalidMedia { path, reason }) => {
109//! println!("Invalid media {}: {}", path.display(), reason);
110//! }
111//! Err(e) => println!("Other error: {}", e),
112//! Ok(info) => println!("Opened: {}", info.format()),
113//! }
114//! ```
115//!
116//! ## Features
117//!
118//! - Extract container format information (MP4, MKV, AVI, etc.)
119//! - List all video and audio streams with detailed properties
120//! - Get codec parameters (codec type, pixel format, sample format)
121//! - Read container and stream metadata (title, artist, etc.)
122//! - Color space and HDR information (BT.709, BT.2020, etc.)
123//! - Bitrate extraction and calculation
124//! - Duration and frame count information
125
126#![warn(missing_docs)]
127#![warn(clippy::all)]
128#![warn(clippy::pedantic)]
129
130mod error;
131mod info;
132
133// Re-export types from ff-format for convenience
134pub use ff_format::{
135 AudioCodec, AudioStreamInfo, ChannelLayout, ChapterInfo, ChapterInfoBuilder, ColorPrimaries,
136 ColorRange, ColorSpace, MediaInfo, PixelFormat, Rational, SampleFormat, Timestamp, VideoCodec,
137 VideoStreamInfo,
138};
139
140pub use error::ProbeError;
141
142// Re-export the open function at the crate level
143pub use info::open;
144
145/// Prelude module for convenient imports.
146///
147/// This module re-exports all commonly used types for easy access:
148///
149/// ```no_run
150/// use ff_probe::prelude::*;
151///
152/// fn main() -> Result<(), Box<dyn std::error::Error>> {
153/// let info = open("video.mp4")?;
154///
155/// // Access stream information
156/// if let Some(video) = info.primary_video() {
157/// let _codec: VideoCodec = video.codec();
158/// let _color: ColorSpace = video.color_space();
159/// }
160///
161/// Ok(())
162/// }
163/// ```
164pub mod prelude {
165 pub use crate::{
166 AudioCodec, AudioStreamInfo, ChannelLayout, ChapterInfo, ChapterInfoBuilder,
167 ColorPrimaries, ColorRange, ColorSpace, MediaInfo, PixelFormat, ProbeError, Rational,
168 SampleFormat, Timestamp, VideoCodec, VideoStreamInfo, open,
169 };
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_prelude_exports() {
178 // Verify prelude exports all expected types
179 let _rational: Rational = Rational::default();
180 let _timestamp: Timestamp = Timestamp::default();
181 let _color_space: ColorSpace = ColorSpace::default();
182 let _color_range: ColorRange = ColorRange::default();
183 let _color_primaries: ColorPrimaries = ColorPrimaries::default();
184 let _video_codec: VideoCodec = VideoCodec::default();
185 let _audio_codec: AudioCodec = AudioCodec::default();
186 let _channel_layout: ChannelLayout = ChannelLayout::default();
187 let _video_stream: VideoStreamInfo = VideoStreamInfo::default();
188 let _audio_stream: AudioStreamInfo = AudioStreamInfo::default();
189 let _media_info: MediaInfo = MediaInfo::default();
190 let _pixel_format: PixelFormat = PixelFormat::default();
191 let _sample_format: SampleFormat = SampleFormat::default();
192
193 // Verify open function is accessible (implicitly via the imports)
194 // The function signature uses impl AsRef<Path>, not &str
195 let _: Result<MediaInfo, ProbeError> = Err(ProbeError::FileNotFound {
196 path: std::path::PathBuf::new(),
197 });
198 // Can't easily verify function pointer due to impl trait, but open is re-exported
199 }
200
201 #[test]
202 fn test_probe_error_display() {
203 use std::path::PathBuf;
204
205 let err = ProbeError::FileNotFound {
206 path: PathBuf::from("test.mp4"),
207 };
208 assert!(err.to_string().contains("test.mp4"));
209 }
210
211 #[test]
212 fn test_open_nonexistent_file() {
213 let result = open("/nonexistent/path/to/video.mp4");
214 assert!(matches!(result, Err(ProbeError::FileNotFound { .. })));
215 }
216}