Skip to main content

ffmpeg_the_third/util/
error.rs

1use std::error;
2use std::ffi::CStr;
3use std::fmt;
4use std::io;
5use std::str::from_utf8_unchecked;
6
7use crate::ffi::*;
8use libc::{c_char, c_int};
9#[cfg(feature = "serialize")]
10use serde::{Deserialize, Serialize};
11
12#[derive(Copy, Clone, PartialEq, Eq)]
13#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
14pub enum Error {
15    Bug,
16    Bug2,
17    Unknown,
18    Experimental,
19    BufferTooSmall,
20    Eof,
21    Exit,
22    External,
23    InvalidData,
24    PatchWelcome,
25
26    InputChanged,
27    OutputChanged,
28
29    BsfNotFound,
30    DecoderNotFound,
31    DemuxerNotFound,
32    EncoderNotFound,
33    OptionNotFound,
34    MuxerNotFound,
35    FilterNotFound,
36    ProtocolNotFound,
37    StreamNotFound,
38
39    HttpBadRequest,
40    HttpUnauthorized,
41    HttpForbidden,
42    HttpNotFound,
43    #[cfg(feature = "ffmpeg_7_1")]
44    HttpTooManyRequests,
45    HttpOther4xx,
46    HttpServerError,
47
48    /// For AVERROR(e) wrapping POSIX error codes, e.g. AVERROR(EAGAIN).
49    Other {
50        errno: c_int,
51    },
52}
53
54impl From<c_int> for Error {
55    fn from(value: c_int) -> Error {
56        match value {
57            AVERROR_BSF_NOT_FOUND => Error::BsfNotFound,
58            AVERROR_BUG => Error::Bug,
59            AVERROR_BUFFER_TOO_SMALL => Error::BufferTooSmall,
60            AVERROR_DECODER_NOT_FOUND => Error::DecoderNotFound,
61            AVERROR_DEMUXER_NOT_FOUND => Error::DemuxerNotFound,
62            AVERROR_ENCODER_NOT_FOUND => Error::EncoderNotFound,
63            AVERROR_EOF => Error::Eof,
64            AVERROR_EXIT => Error::Exit,
65            AVERROR_EXTERNAL => Error::External,
66            AVERROR_FILTER_NOT_FOUND => Error::FilterNotFound,
67            AVERROR_INVALIDDATA => Error::InvalidData,
68            AVERROR_MUXER_NOT_FOUND => Error::MuxerNotFound,
69            AVERROR_OPTION_NOT_FOUND => Error::OptionNotFound,
70            AVERROR_PATCHWELCOME => Error::PatchWelcome,
71            AVERROR_PROTOCOL_NOT_FOUND => Error::ProtocolNotFound,
72            AVERROR_STREAM_NOT_FOUND => Error::StreamNotFound,
73            AVERROR_BUG2 => Error::Bug2,
74            AVERROR_UNKNOWN => Error::Unknown,
75            AVERROR_EXPERIMENTAL => Error::Experimental,
76            AVERROR_INPUT_CHANGED => Error::InputChanged,
77            AVERROR_OUTPUT_CHANGED => Error::OutputChanged,
78            AVERROR_HTTP_BAD_REQUEST => Error::HttpBadRequest,
79            AVERROR_HTTP_UNAUTHORIZED => Error::HttpUnauthorized,
80            AVERROR_HTTP_FORBIDDEN => Error::HttpForbidden,
81            AVERROR_HTTP_NOT_FOUND => Error::HttpNotFound,
82            #[cfg(feature = "ffmpeg_7_1")]
83            AVERROR_HTTP_TOO_MANY_REQUESTS => Error::HttpTooManyRequests,
84            AVERROR_HTTP_OTHER_4XX => Error::HttpOther4xx,
85            AVERROR_HTTP_SERVER_ERROR => Error::HttpServerError,
86            e => Error::Other {
87                errno: AVUNERROR(e),
88            },
89        }
90    }
91}
92
93impl From<Error> for c_int {
94    fn from(value: Error) -> c_int {
95        match value {
96            Error::BsfNotFound => AVERROR_BSF_NOT_FOUND,
97            Error::Bug => AVERROR_BUG,
98            Error::BufferTooSmall => AVERROR_BUFFER_TOO_SMALL,
99            Error::DecoderNotFound => AVERROR_DECODER_NOT_FOUND,
100            Error::DemuxerNotFound => AVERROR_DEMUXER_NOT_FOUND,
101            Error::EncoderNotFound => AVERROR_ENCODER_NOT_FOUND,
102            Error::Eof => AVERROR_EOF,
103            Error::Exit => AVERROR_EXIT,
104            Error::External => AVERROR_EXTERNAL,
105            Error::FilterNotFound => AVERROR_FILTER_NOT_FOUND,
106            Error::InvalidData => AVERROR_INVALIDDATA,
107            Error::MuxerNotFound => AVERROR_MUXER_NOT_FOUND,
108            Error::OptionNotFound => AVERROR_OPTION_NOT_FOUND,
109            Error::PatchWelcome => AVERROR_PATCHWELCOME,
110            Error::ProtocolNotFound => AVERROR_PROTOCOL_NOT_FOUND,
111            Error::StreamNotFound => AVERROR_STREAM_NOT_FOUND,
112            Error::Bug2 => AVERROR_BUG2,
113            Error::Unknown => AVERROR_UNKNOWN,
114            Error::Experimental => AVERROR_EXPERIMENTAL,
115            Error::InputChanged => AVERROR_INPUT_CHANGED,
116            Error::OutputChanged => AVERROR_OUTPUT_CHANGED,
117            Error::HttpBadRequest => AVERROR_HTTP_BAD_REQUEST,
118            Error::HttpUnauthorized => AVERROR_HTTP_UNAUTHORIZED,
119            Error::HttpForbidden => AVERROR_HTTP_FORBIDDEN,
120            Error::HttpNotFound => AVERROR_HTTP_NOT_FOUND,
121            #[cfg(feature = "ffmpeg_7_1")]
122            Error::HttpTooManyRequests => AVERROR_HTTP_TOO_MANY_REQUESTS,
123            Error::HttpOther4xx => AVERROR_HTTP_OTHER_4XX,
124            Error::HttpServerError => AVERROR_HTTP_SERVER_ERROR,
125            Error::Other { errno } => AVERROR(errno),
126        }
127    }
128}
129
130impl error::Error for Error {}
131
132impl From<Error> for io::Error {
133    fn from(value: Error) -> io::Error {
134        io::Error::other(value)
135    }
136}
137
138impl fmt::Display for Error {
139    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
140        let mut buf = [0; AV_ERROR_MAX_STRING_SIZE];
141
142        unsafe {
143            let error_text = match *self {
144                Error::Other { errno } => os_strerror(errno, &mut buf),
145                av_err => {
146                    if 0 == av_strerror(av_err.into(), buf.as_mut_ptr(), buf.len()) {
147                        CStr::from_ptr(buf.as_ptr())
148                    } else {
149                        c"Unknown error"
150                    }
151                }
152            };
153
154            f.write_str(from_utf8_unchecked(error_text.to_bytes()))
155        }
156    }
157}
158
159// SAFETY: The buffer passed to os_strerror must be 0-initialized
160// in order to satisfy the safety invariants for CStr::from_ptr.
161
162#[cfg(unix)]
163unsafe fn os_strerror(errno: c_int, buf: &mut [c_char; AV_ERROR_MAX_STRING_SIZE]) -> &CStr {
164    let _err = libc::strerror_r(errno, buf.as_mut_ptr(), buf.len());
165    // _err can be either ERANGE or EINVAL
166    // in the second case "Unknown error: <errno>" has been written to the buf
167    #[cfg(test)]
168    {
169        if _err == libc::ERANGE {
170            panic!("Insufficient buffer size")
171        }
172    }
173    CStr::from_ptr(buf.as_ptr())
174}
175
176#[cfg(windows)]
177unsafe fn os_strerror(errno: c_int, _buf: &mut [c_char; AV_ERROR_MAX_STRING_SIZE]) -> &CStr {
178    CStr::from_ptr(libc::strerror(errno))
179}
180
181#[cfg(all(not(windows), not(unix)))]
182unsafe fn os_strerror(errno: c_int, buf: &mut [c_char; AV_ERROR_MAX_STRING_SIZE]) -> &CStr {
183    static MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
184    let guard = MUTEX.lock();
185    libc::strncpy(
186        buf.as_mut_ptr(),
187        libc::strerror(errno),
188        AV_ERROR_MAX_STRING_SIZE - 1,
189    );
190    drop(guard);
191    CStr::from_ptr(buf.as_ptr())
192}
193
194impl fmt::Debug for Error {
195    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
196        f.write_str("ffmpeg::Error(")?;
197        f.write_str(&format!("{}: ", AVUNERROR((*self).into())))?;
198        fmt::Display::fmt(self, f)?;
199        f.write_str(")")
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use libc::EAGAIN;
207
208    #[test]
209    fn test_error_roundtrip() {
210        assert_eq!(Into::<c_int>::into(Error::from(AVERROR_EOF)), AVERROR_EOF);
211        assert_eq!(
212            Into::<c_int>::into(Error::from(AVERROR(EAGAIN))),
213            AVERROR(EAGAIN)
214        );
215        assert_eq!(Error::from(AVERROR(EAGAIN)), Error::Other { errno: EAGAIN });
216    }
217
218    #[cfg(unix)]
219    #[test]
220    fn test_posix_error_string_range() {
221        let mut buf = [0; AV_ERROR_MAX_STRING_SIZE];
222        for e in 1..255 {
223            let _ = unsafe { os_strerror(e, &mut buf) };
224        }
225    }
226
227    #[cfg(unix)]
228    #[test]
229    fn test_posix_error_string() {
230        assert_eq!(
231            Error::from(AVERROR(EAGAIN)).to_string(),
232            "Resource temporarily unavailable"
233        )
234    }
235
236    #[test]
237    fn test_error_fmt() {
238        use std::fmt::Write;
239
240        let mut s = String::new();
241        write!(&mut s, "{}", Error::InvalidData).expect("can write into string");
242        assert_eq!(s, "Invalid data found when processing input");
243
244        s.clear();
245
246        write!(&mut s, "{}", Error::from(AVERROR(EAGAIN))).expect("can write into string");
247        if cfg!(unix) {
248            assert_eq!(s, "Resource temporarily unavailable");
249        }
250
251        #[cfg(feature = "ffmpeg_7_1")]
252        {
253            s.clear();
254
255            write!(&mut s, "{}", Error::HttpTooManyRequests).expect("can write into string");
256            assert_eq!(s, "Server returned 429 Too Many Requests");
257        }
258    }
259}