ff_probe/probe/builder.rs
1//! Safe public API for media file probing.
2//!
3//! This module provides the [`open`] function for extracting metadata from media files
4//! using `FFmpeg`. It creates a [`MediaInfo`] struct containing all relevant information
5//! about the media file, including container format, duration, file size, and stream details.
6//!
7//! # Examples
8//!
9//! ## Basic Usage
10//!
11//! ```no_run
12//! use ff_probe::open;
13//!
14//! fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! let info = open("video.mp4")?;
16//!
17//! println!("Format: {}", info.format());
18//! println!("Duration: {:?}", info.duration());
19//!
20//! // Access video stream information
21//! if let Some(video) = info.primary_video() {
22//! println!("Video: {} {}x{} @ {:.2} fps",
23//! video.codec_name(),
24//! video.width(),
25//! video.height(),
26//! video.fps()
27//! );
28//! }
29//!
30//! Ok(())
31//! }
32//! ```
33//!
34//! ## Checking for Video vs Audio-Only Files
35//!
36//! ```no_run
37//! use ff_probe::open;
38//!
39//! fn main() -> Result<(), Box<dyn std::error::Error>> {
40//! let info = open("media_file.mp4")?;
41//!
42//! if info.has_video() {
43//! println!("This is a video file");
44//! } else if info.has_audio() {
45//! println!("This is an audio-only file");
46//! }
47//!
48//! Ok(())
49//! }
50//! ```
51
52use std::path::Path;
53
54use ff_format::MediaInfo;
55
56use crate::error::ProbeError;
57
58use super::probe_inner;
59
60/// Opens a media file and extracts its metadata.
61///
62/// This function opens the file at the given path using `FFmpeg`, reads the container
63/// format information, and returns a [`MediaInfo`] struct containing all extracted
64/// metadata.
65///
66/// # Arguments
67///
68/// * `path` - Path to the media file to probe. Accepts anything that can be converted
69/// to a [`Path`], including `&str`, `String`, `PathBuf`, etc.
70///
71/// # Returns
72///
73/// Returns `Ok(MediaInfo)` on success, or a [`ProbeError`] on failure.
74///
75/// # Errors
76///
77/// - [`ProbeError::FileNotFound`] if the file does not exist
78/// - [`ProbeError::CannotOpen`] if `FFmpeg` cannot open the file
79/// - [`ProbeError::InvalidMedia`] if stream information cannot be read
80/// - [`ProbeError::Io`] if there's an I/O error accessing the file
81///
82/// # Examples
83///
84/// ## Opening a Video File
85///
86/// ```no_run
87/// use ff_probe::open;
88/// use std::path::Path;
89///
90/// fn main() -> Result<(), Box<dyn std::error::Error>> {
91/// // Open by string path
92/// let info = open("video.mp4")?;
93///
94/// // Or by Path
95/// let path = Path::new("/path/to/video.mkv");
96/// let info = open(path)?;
97///
98/// if let Some(video) = info.primary_video() {
99/// println!("Resolution: {}x{}", video.width(), video.height());
100/// }
101///
102/// Ok(())
103/// }
104/// ```
105///
106/// ## Handling Errors
107///
108/// ```
109/// use ff_probe::{open, ProbeError};
110///
111/// // Non-existent file returns FileNotFound
112/// let result = open("/this/file/does/not/exist.mp4");
113/// assert!(matches!(result, Err(ProbeError::FileNotFound { .. })));
114/// ```
115pub fn open(path: impl AsRef<Path>) -> Result<MediaInfo, ProbeError> {
116 let path = path.as_ref();
117
118 log::debug!("probing media file path={}", path.display());
119
120 // Check if file exists
121 if !path.exists() {
122 return Err(ProbeError::FileNotFound {
123 path: path.to_path_buf(),
124 });
125 }
126
127 // Get file size - propagate error since file may exist but be inaccessible (permission denied, etc.)
128 let file_size = std::fs::metadata(path).map(|m| m.len())?;
129
130 probe_inner::probe_file(path, file_size)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn open_should_return_file_not_found_for_nonexistent_path() {
139 let result = open("/nonexistent/path/to/video.mp4");
140 assert!(result.is_err());
141 match result {
142 Err(ProbeError::FileNotFound { path }) => {
143 assert!(path.to_string_lossy().contains("video.mp4"));
144 }
145 _ => panic!("Expected FileNotFound error"),
146 }
147 }
148
149 #[test]
150 fn open_should_return_error_for_invalid_media() {
151 // Create a temporary file with invalid content
152 let temp_dir = std::env::temp_dir();
153 let temp_file = temp_dir.join("ff_probe_test_invalid.mp4");
154 std::fs::write(&temp_file, b"not a valid video file").ok();
155
156 let result = open(&temp_file);
157
158 // Clean up
159 std::fs::remove_file(&temp_file).ok();
160
161 // FFmpeg should fail to open this as a valid media file
162 assert!(result.is_err());
163 match result {
164 Err(ProbeError::CannotOpen { .. }) | Err(ProbeError::InvalidMedia { .. }) => {}
165 _ => panic!("Expected CannotOpen or InvalidMedia error"),
166 }
167 }
168}