1use ff_format::{ErrorSeverity, MediaError};
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum FilterError {
9 #[error("failed to build filter graph")]
12 BuildFailed,
13
14 #[error("failed to process frame")]
16 ProcessFailed,
17
18 #[error("invalid filter configuration: {reason}")]
20 InvalidConfig {
21 reason: String,
23 },
24
25 #[error("invalid input: slot={slot} reason={reason}")]
27 InvalidInput {
28 slot: usize,
30 reason: String,
32 },
33
34 #[error("ffmpeg error: {message} (code={code})")]
36 Ffmpeg {
37 code: i32,
39 message: String,
41 },
42
43 #[error("composition failed: {reason}")]
49 CompositionFailed {
50 reason: String,
52 },
53
54 #[error(
62 "composite operator {op:?} is not implemented on the filter path; \
63 it renders on the GPU compositor only (#1784)"
64 )]
65 UnsupportedCompositeOp {
66 op: crate::CompositeOp,
68 },
69
70 #[error("analysis failed: {reason}")]
76 AnalysisFailed {
77 reason: String,
79 },
80
81 #[error("GPL-licensed feature required: {feature} (enable the `gpl` feature flag)")]
87 GplRequired {
88 feature: &'static str,
90 },
91}
92
93impl MediaError for FilterError {
94 fn severity(&self) -> ErrorSeverity {
95 match self {
96 Self::Ffmpeg { .. } | Self::ProcessFailed | Self::InvalidInput { .. } => {
97 ErrorSeverity::Other
98 }
99 Self::BuildFailed
100 | Self::InvalidConfig { .. }
101 | Self::CompositionFailed { .. }
102 | Self::UnsupportedCompositeOp { .. }
103 | Self::AnalysisFailed { .. }
104 | Self::GplRequired { .. } => ErrorSeverity::Fatal,
105 }
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use ff_format::MediaError;
112
113 use super::FilterError;
114 use std::error::Error;
115
116 #[test]
117 fn build_failed_should_display_correct_message() {
118 let err = FilterError::BuildFailed;
119 assert_eq!(err.to_string(), "failed to build filter graph");
120 }
121
122 #[test]
123 fn process_failed_should_display_correct_message() {
124 let err = FilterError::ProcessFailed;
125 assert_eq!(err.to_string(), "failed to process frame");
126 }
127
128 #[test]
129 fn invalid_input_should_display_slot_and_reason() {
130 let err = FilterError::InvalidInput {
131 slot: 2,
132 reason: "slot out of range".to_string(),
133 };
134 assert_eq!(
135 err.to_string(),
136 "invalid input: slot=2 reason=slot out of range"
137 );
138 }
139
140 #[test]
141 fn ffmpeg_should_display_code_and_message() {
142 let err = FilterError::Ffmpeg {
143 code: -22,
144 message: "Invalid argument".to_string(),
145 };
146 assert_eq!(err.to_string(), "ffmpeg error: Invalid argument (code=-22)");
147 }
148
149 #[test]
150 fn composition_failed_should_display_reason() {
151 let err = FilterError::CompositionFailed {
152 reason: "no layers".to_string(),
153 };
154 assert_eq!(err.to_string(), "composition failed: no layers");
155 }
156
157 #[test]
158 fn analysis_failed_should_display_reason() {
159 let err = FilterError::AnalysisFailed {
160 reason: "file not found".to_string(),
161 };
162 assert_eq!(err.to_string(), "analysis failed: file not found");
163 }
164
165 #[test]
166 fn gpl_required_should_display_feature_name() {
167 let err = FilterError::GplRequired {
168 feature: "rubberband",
169 };
170 assert_eq!(
171 err.to_string(),
172 "GPL-licensed feature required: rubberband (enable the `gpl` feature flag)"
173 );
174 }
175
176 #[test]
177 fn filter_error_should_implement_std_error() {
178 fn assert_error<E: Error>(_: &E) {}
179 assert_error(&FilterError::BuildFailed);
180 assert_error(&FilterError::ProcessFailed);
181 assert_error(&FilterError::InvalidInput {
182 slot: 0,
183 reason: String::new(),
184 });
185 assert_error(&FilterError::Ffmpeg {
186 code: 0,
187 message: String::new(),
188 });
189 }
190
191 #[test]
192 fn filter_build_failed_should_be_fatal() {
193 let e = FilterError::BuildFailed;
194 assert!(e.is_fatal() && !e.is_recoverable());
195 }
196
197 #[test]
198 fn filter_process_failed_should_be_other() {
199 let e = FilterError::ProcessFailed;
200 assert!(!e.is_fatal() && !e.is_recoverable());
201 }
202}