ffmpeg_light/
error.rs

1use std::io;
2
3use thiserror::Error;
4
5/// Result alias used throughout the crate.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Errors that can occur when invoking FFmpeg or parsing its output.
9#[derive(Debug, Error)]
10pub enum Error {
11    /// The required binary could not be located on the current PATH.
12    #[error("ffmpeg binary not found on PATH")]
13    FFmpegNotFound {
14        /// Suggestion for resolving the issue.
15        suggestion: Option<String>,
16    },
17
18    /// A spawned command exited with a non-zero status code.
19    #[error("{binary} failed (code: {exit_code:?}): {message}")]
20    ProcessingError {
21        /// Binary that was executed (ffmpeg/ffprobe).
22        binary: String,
23        /// Exit code if provided by the OS.
24        exit_code: Option<i32>,
25        /// Captured stderr output (truncated when large).
26        message: String,
27    },
28
29    /// Invalid input parameters or missing required values.
30    #[error("invalid input: {0}")]
31    InvalidInput(String),
32
33    /// Errors from filter configuration or composition.
34    #[error("filter error: {0}")]
35    FilterError(String),
36
37    /// Timeout waiting for process completion.
38    #[error("timeout: {0}")]
39    TimeoutError(String),
40
41    /// Errors produced by std::process or other IO operations.
42    #[error(transparent)]
43    Io(#[from] io::Error),
44
45    /// Errors produced while parsing ffprobe JSON.
46    #[error(transparent)]
47    Json(#[from] serde_json::Error),
48
49    /// Returned when textual parsing fails (for example, invalid duration strings).
50    #[error("parse error: {0}")]
51    Parse(String),
52
53    /// Placeholder for functionality that has not yet been implemented.
54    #[error("unsupported operation: {0}")]
55    Unsupported(String),
56}
57
58impl Error {
59    /// Utility to build a `ProcessingError` from a binary label and captured output.
60    pub(crate) fn command_failed(binary: &str, exit_code: Option<i32>, stderr: &[u8]) -> Self {
61        let message = truncate(stderr);
62        Error::ProcessingError {
63            binary: binary.to_string(),
64            exit_code,
65            message,
66        }
67    }
68
69    /// Suggestion for resolving this error (if available).
70    pub fn suggestion(&self) -> Option<String> {
71        match self {
72            Error::FFmpegNotFound { suggestion } => suggestion.clone(),
73            Error::InvalidInput(msg) => {
74                if msg.contains("input path") {
75                    Some("ensure input file exists and path is correct".to_string())
76                } else if msg.contains("output path") {
77                    Some("ensure output directory exists".to_string())
78                } else {
79                    Some("check your parameters".to_string())
80                }
81            }
82            Error::ProcessingError { .. } => {
83                Some("check FFmpeg is installed and your parameters are valid".to_string())
84            }
85            Error::FilterError(msg) => {
86                if msg.contains("unsupported") || msg.contains("not supported") {
87                    Some("check FFmpeg version supports this filter".to_string())
88                } else {
89                    Some("review filter parameters and syntax".to_string())
90                }
91            }
92            _ => None,
93        }
94    }
95}
96
97fn truncate(message: &[u8]) -> String {
98    const MAX: usize = 4096;
99    let mut text = String::from_utf8_lossy(message).into_owned();
100    if text.len() > MAX {
101        text.truncate(MAX);
102        text.push_str("…");
103    }
104    text
105}