1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Error types for ff-preview.
use std::path::PathBuf;
use ff_format::{ErrorSeverity, MediaError};
use thiserror::Error;
/// Errors that can occur during preview and proxy operations.
#[derive(Debug, Error)]
pub enum PreviewError {
/// The media file was not found at the specified path.
#[error("file not found: path={path}")]
FileNotFound {
/// Path that was not found.
path: PathBuf,
},
/// The media file has no video stream.
#[error("no video stream found: path={path}")]
NoVideoStream {
/// Path to the media file.
path: PathBuf,
},
/// A seek operation failed.
#[error("seek failed: target={target:?} reason={reason}")]
SeekFailed {
/// Target timestamp of the failed seek.
target: std::time::Duration,
/// Human-readable reason for the failure.
reason: String,
},
/// An underlying decode error occurred.
#[error("decode failed: {0}")]
Decode(#[from] ff_decode::DecodeError),
/// A raw `FFmpeg` error.
///
/// `code` is the negative integer returned by the `FFmpeg` API, or `0` when no
/// numeric code is available. `message` is from `av_strerror` or an internal
/// description.
#[error("ffmpeg error: {message} (code={code})")]
Ffmpeg {
/// Raw `FFmpeg` error code (negative i32). `0` when no numeric code is available.
code: i32,
/// Human-readable message from `av_strerror` or an internal description.
message: String,
},
/// A probe error while analysing the media file.
#[error("probe failed: {0}")]
Probe(#[from] ff_probe::ProbeError),
/// A proxy generation pipeline error.
#[cfg(feature = "proxy")]
#[error("pipeline failed: {0}")]
Pipeline(#[from] ff_pipeline::PipelineError),
/// An I/O error during file operations.
#[error("io error: {0}")]
Io(#[from] std::io::Error),
/// A seek target lies outside the valid range of the timeline.
#[error("seek out of range: pts={pts:?}")]
SeekOutOfRange {
/// The requested presentation timestamp that fell outside all clips.
pts: std::time::Duration,
},
}
impl MediaError for PreviewError {
fn severity(&self) -> ErrorSeverity {
match self {
Self::Decode(e) => e.severity(),
Self::Probe(e) => e.severity(),
#[cfg(feature = "proxy")]
Self::Pipeline(e) => e.severity(),
Self::SeekFailed { .. } => ErrorSeverity::Recoverable,
Self::Ffmpeg { .. } | Self::SeekOutOfRange { .. } => ErrorSeverity::Other,
Self::FileNotFound { .. } | Self::NoVideoStream { .. } | Self::Io(_) => {
ErrorSeverity::Fatal
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn preview_io_should_be_fatal() {
let e: PreviewError = std::io::Error::other("x").into();
assert!(e.is_fatal() && !e.is_recoverable());
}
#[test]
fn preview_seek_failed_should_be_recoverable() {
let e = PreviewError::SeekFailed {
target: std::time::Duration::from_secs(1),
reason: "x".into(),
};
assert!(e.is_recoverable() && !e.is_fatal());
}
#[test]
fn preview_decode_should_delegate_recoverable() {
// A recoverable inner DecodeError must remain recoverable through the wrapper.
let e = PreviewError::Decode(ff_decode::DecodeError::decoding_failed("x"));
assert!(e.is_recoverable() && !e.is_fatal());
}
}