1use std::path::PathBuf;
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum EncodeError {
9 #[error("Cannot create output file: {path}")]
11 CannotCreateFile {
12 path: PathBuf,
14 },
15
16 #[error("Unsupported codec: {codec}")]
18 UnsupportedCodec {
19 codec: String,
21 },
22
23 #[error("No suitable encoder found for {codec} (tried: {tried:?})")]
25 NoSuitableEncoder {
26 codec: String,
28 tried: Vec<String>,
30 },
31
32 #[error("Encoding failed at frame {frame}: {reason}")]
34 EncodingFailed {
35 frame: u64,
37 reason: String,
39 },
40
41 #[error("Invalid configuration: {reason}")]
43 InvalidConfig {
44 reason: String,
46 },
47
48 #[error("Hardware encoder unavailable: {encoder}")]
50 HwEncoderUnavailable {
51 encoder: String,
53 },
54
55 #[error("encoder unavailable: codec={codec} hint={hint}")]
57 EncoderUnavailable {
58 codec: String,
60 hint: String,
62 },
63
64 #[error("Muxing failed: {reason}")]
66 MuxingFailed {
67 reason: String,
69 },
70
71 #[error("ffmpeg error: {message} (code={code})")]
73 Ffmpeg {
74 code: i32,
76 message: String,
78 },
79
80 #[error("IO error: {0}")]
82 Io(#[from] std::io::Error),
83
84 #[error("Invalid option: {name} — {reason}")]
86 InvalidOption {
87 name: String,
89 reason: String,
91 },
92
93 #[error("codec {codec} is not supported by container {container} — {hint}")]
95 UnsupportedContainerCodecCombination {
96 container: String,
98 codec: String,
100 hint: String,
102 },
103
104 #[error("dimensions {width}x{height} out of range [2, 32768]")]
106 InvalidDimensions {
107 width: u32,
109 height: u32,
111 },
112
113 #[error("bitrate {bitrate} bps exceeds maximum 800 Mbps (800,000,000 bps)")]
115 InvalidBitrate {
116 bitrate: u64,
118 },
119
120 #[error("channel count {count} exceeds maximum 8")]
122 InvalidChannelCount {
123 count: u32,
125 },
126
127 #[error("sample rate {rate} Hz outside supported range [8000, 384000]")]
129 InvalidSampleRate {
130 rate: u32,
132 },
133
134 #[error("Encoding cancelled by user")]
136 Cancelled,
137
138 #[error("Async encoder worker panicked or disconnected")]
140 WorkerPanicked,
141}
142
143impl EncodeError {
144 pub(crate) fn from_ffmpeg_error(errnum: i32) -> Self {
150 EncodeError::Ffmpeg {
151 code: errnum,
152 message: ff_sys::av_error_string(errnum),
153 }
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::EncodeError;
160
161 #[test]
162 fn from_ffmpeg_error_should_return_ffmpeg_variant() {
163 let err = EncodeError::from_ffmpeg_error(ff_sys::error_codes::EINVAL);
164 assert!(matches!(err, EncodeError::Ffmpeg { .. }));
165 }
166
167 #[test]
168 fn from_ffmpeg_error_should_carry_numeric_code() {
169 let err = EncodeError::from_ffmpeg_error(ff_sys::error_codes::EINVAL);
170 match err {
171 EncodeError::Ffmpeg { code, .. } => assert_eq!(code, ff_sys::error_codes::EINVAL),
172 _ => panic!("expected Ffmpeg variant"),
173 }
174 }
175
176 #[test]
177 fn from_ffmpeg_error_should_format_with_code_in_display() {
178 let err = EncodeError::from_ffmpeg_error(ff_sys::error_codes::EINVAL);
179 let msg = err.to_string();
180 assert!(msg.contains("code=-22"), "expected 'code=-22' in '{msg}'");
181 }
182
183 #[test]
184 fn from_ffmpeg_error_message_should_be_nonempty() {
185 let err = EncodeError::from_ffmpeg_error(ff_sys::error_codes::ENOMEM);
186 assert!(!err.to_string().is_empty());
187 }
188
189 #[test]
190 fn from_ffmpeg_error_eof_should_be_constructible() {
191 let err = EncodeError::from_ffmpeg_error(ff_sys::error_codes::EOF);
192 assert!(matches!(err, EncodeError::Ffmpeg { .. }));
193 assert!(!err.to_string().is_empty());
194 }
195
196 #[test]
197 fn invalid_dimensions_display_should_contain_dimension_string() {
198 let err = EncodeError::InvalidDimensions {
199 width: 0,
200 height: 720,
201 };
202 let msg = err.to_string();
203 assert!(msg.contains("0x720"), "expected '0x720' in '{msg}'");
204 }
205
206 #[test]
207 fn invalid_dimensions_display_should_contain_range_hint() {
208 let err = EncodeError::InvalidDimensions {
209 width: 99999,
210 height: 99999,
211 };
212 let msg = err.to_string();
213 assert!(
214 msg.contains("[2, 32768]"),
215 "expected '[2, 32768]' in '{msg}'"
216 );
217 }
218
219 #[test]
220 fn invalid_bitrate_display_should_contain_bitrate_value() {
221 let err = EncodeError::InvalidBitrate {
222 bitrate: 900_000_000,
223 };
224 let msg = err.to_string();
225 assert!(msg.contains("900000000"), "expected '900000000' in '{msg}'");
226 }
227
228 #[test]
229 fn invalid_bitrate_display_should_contain_maximum_hint() {
230 let err = EncodeError::InvalidBitrate {
231 bitrate: 900_000_000,
232 };
233 let msg = err.to_string();
234 assert!(
235 msg.contains("800,000,000"),
236 "expected '800,000,000' in '{msg}'"
237 );
238 }
239
240 #[test]
241 fn invalid_channel_count_display_should_contain_count() {
242 let err = EncodeError::InvalidChannelCount { count: 9 };
243 let msg = err.to_string();
244 assert!(msg.contains('9'), "expected '9' in '{msg}'");
245 }
246
247 #[test]
248 fn invalid_channel_count_display_should_contain_maximum_hint() {
249 let err = EncodeError::InvalidChannelCount { count: 9 };
250 let msg = err.to_string();
251 assert!(msg.contains('8'), "expected '8' in '{msg}'");
252 }
253
254 #[test]
255 fn invalid_sample_rate_display_should_contain_rate() {
256 let err = EncodeError::InvalidSampleRate { rate: 7999 };
257 let msg = err.to_string();
258 assert!(msg.contains("7999"), "expected '7999' in '{msg}'");
259 }
260
261 #[test]
262 fn invalid_sample_rate_display_should_contain_range_hint() {
263 let err = EncodeError::InvalidSampleRate { rate: 7999 };
264 let msg = err.to_string();
265 assert!(
266 msg.contains("[8000, 384000]"),
267 "expected '[8000, 384000]' in '{msg}'"
268 );
269 }
270}