//! Errors the `ffprobe` wrapper can produce. use std::path::PathBuf; /// Result alias for every fallible operation in this crate. pub type Result = std::result::Result; /// Everything that can go wrong probing a file. /// /// Nothing here has a default. A file `ffprobe` cannot read is an error, never /// a `ProbedFile` full of zeroes — importing on top of a default-valued struct /// would silently record a 0-byte SDR file with no audio and pass every policy /// rule that inspects a field it does not have (DESIGN.md §5.6, §5.7). #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { /// `ffprobe` could not be started at all: not installed, or not on `PATH`. #[error("could not run {binary}")] Spawn { /// The binary that was attempted. binary: String, /// The underlying spawn failure. #[source] source: std::io::Error, }, /// `ffprobe` started but exited non-zero — an unreadable or corrupt file. #[error("ffprobe rejected {path}: {stderr}")] Rejected { /// The file that was probed. path: PathBuf, /// Exit status, absent when the process was killed by a signal. status: Option, /// `ffprobe`'s own diagnostics, truncated to something loggable. stderr: String, }, /// `ffprobe` did not finish within the configured timeout. #[error("ffprobe timed out probing {path}")] Timeout { /// The file that was probed. path: PathBuf, }, /// Reading the child process failed after it was spawned. #[error("reading ffprobe output for {path} failed")] Io { /// The file that was probed. path: PathBuf, /// The underlying IO failure. #[source] source: std::io::Error, }, /// `ffprobe` printed something that is not the JSON shape expected. #[error("ffprobe output for {path} did not parse")] Decode { /// The file that was probed. path: PathBuf, /// The deserialisation failure. #[source] source: serde_json::Error, }, /// `ffprobe` read the file, but as a still image or as text. /// /// ffmpeg's `tty` demuxer parses any text file at all as ANSI art video, /// and the image demuxers turn a poster into a one-frame film. Both would /// otherwise pass for importable media. #[error("{path} is not a video file: ffprobe read it as {format}")] NotVideo { /// The file that was probed. path: PathBuf, /// The container format `ffprobe` matched. format: String, }, /// `ffprobe` read the file but it carries no video stream. #[error("{path} has no video stream")] NoVideoStream { /// The file that was probed. path: PathBuf, }, /// A container that reports neither a duration nor a stream long enough to /// derive one. The runtime sanity check has nothing to work with. #[error("{path} reports no duration")] NoDuration { /// The file that was probed. path: PathBuf, }, /// Feature selection was handed a set of paths with no readable video file /// among them. #[error("no video file among the candidates")] NoCandidates, } impl Error { /// Whether this failure is a fact about one file rather than about the /// prober. /// /// Feature selection walks a torrent full of `.nfo`, `.jpg` and `.txt` /// files, so a file that is not importable media is expected and gets /// skipped. A missing binary, a timeout or unparseable output is not about /// the file at all, and swallowing it would report "no video file" for a /// torrent that has one. #[must_use] pub const fn is_about_the_file(&self) -> bool { matches!( self, Self::Rejected { .. } | Self::NotVideo { .. } | Self::NoVideoStream { .. } | Self::NoDuration { .. } ) } }