Skip to main content

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}