Skip to main content

ez_ffmpeg/
error.rs

1use ffmpeg_next::ffi::AVERROR;
2use ffmpeg_sys_next::*;
3use std::ffi::NulError;
4use std::{io, result};
5
6// The `opengl` module path is deprecated as a whole (superseded by
7// `wgpu_filter`), but the crate error enum must still name its typed error;
8// importing it here, with the module-path deprecation silenced, keeps the
9// variant and thiserror's generated `From` impl warning-free.
10#[cfg(feature = "opengl")]
11#[allow(deprecated)]
12use crate::opengl::OpenGLFilterError;
13
14/// Result type of all ez-ffmpeg library calls.
15pub type Result<T, E = Error> = result::Result<T, E>;
16
17/// Top-level error type for all ez-ffmpeg operations.
18///
19/// Most variants wrap a stage-specific error enum (opening inputs and
20/// outputs, demuxing, decoding, filtering, encoding, muxing, ...), so
21/// callers can match on the pipeline stage first and inspect the typed
22/// cause when they need to.
23#[derive(thiserror::Error, Debug)]
24#[non_exhaustive]
25pub enum Error {
26    /// Returned when an operation requires a scheduler that has been
27    /// started, but it has not been.
28    #[error("Scheduler is not started")]
29    NotStarted,
30
31    /// A URL or path string could not be converted into a C string for
32    /// FFmpeg (see [`UrlError`]).
33    #[error("URL error: {0}")]
34    Url(#[from] UrlError),
35
36    /// Opening an input file, stream, device, or custom input source failed.
37    #[error("Open input stream error: {0}")]
38    OpenInputStream(#[from] OpenInputError),
39
40    /// Probing stream information from an opened input failed.
41    #[error("Find stream info error: {0}")]
42    FindStream(#[from] FindStreamError),
43
44    /// Resolving a decoder failed (see [`DecoderError`]).
45    #[error("Decoder error: {0}")]
46    Decoder(#[from] DecoderError),
47
48    /// Parsing a filtergraph description failed.
49    #[error("Filter graph parse error: {0}")]
50    FilterGraphParse(#[from] FilterGraphParseError),
51
52    /// Returned when a filtergraph link label could not be converted to a
53    /// UTF-8 string.
54    #[error("Filter description converted to utf8 string error")]
55    FilterDescUtf8,
56
57    /// Returned when a filter name could not be converted to a UTF-8 string.
58    #[error("Filter name converted to utf8 string error")]
59    FilterNameUtf8,
60
61    /// Returned when a filtergraph declares zero outputs, which is not
62    /// supported.
63    #[error("A filtergraph has zero outputs, this is not supported")]
64    FilterZeroOutputs,
65
66    /// Returned when a filtergraph declares zero inputs, which is not
67    /// supported.
68    #[error("A filtergraph has zero inputs, this is not supported")]
69    FilterZeroInputs,
70
71    /// Returned when a numeric field — such as a file index in a stream
72    /// specifier or link label — could not be parsed as an integer.
73    #[error("Input is not a valid number")]
74    ParseInteger,
75
76    /// Allocating an output format context failed.
77    #[error("Alloc output context error: {0}")]
78    AllocOutputContext(#[from] AllocOutputContextError),
79
80    /// Opening or configuring an output failed.
81    #[error("Open output error: {0}")]
82    OpenOutput(#[from] OpenOutputError),
83
84    /// Returned when an output URL is identical to one of the input URLs;
85    /// the payload is the offending path. In-place editing is not supported.
86    #[error("Output file '{0}' is the same as an input file")]
87    FileSameAsInput(String),
88
89    /// Enumerating capture devices failed.
90    #[error("Find devices error: {0}")]
91    FindDevices(#[from] FindDevicesError),
92
93    /// Allocating an `AVFrame` failed.
94    #[error("Alloc frame error: {0}")]
95    AllocFrame(#[from] AllocFrameError),
96
97    /// Allocating an `AVPacket` failed.
98    #[error("Alloc packet error: {0}")]
99    AllocPacket(#[from] AllocPacketError),
100
101    /// Making a frame's data buffers writable failed (see
102    /// [`FrameWritableError`]).
103    #[error("Frame writable error: {0}")]
104    FrameWritable(#[from] FrameWritableError),
105
106    // ---- Muxing ----
107    /// A muxing operation failed while writing the output container.
108    #[error("Muxing operation failed {0}")]
109    Muxing(#[from] MuxingOperationError),
110
111    // ---- Open Encoder ----
112    /// Opening or configuring an encoder failed.
113    #[error("Open encoder operation failed {0}")]
114    OpenEncoder(#[from] OpenEncoderOperationError),
115
116    // ---- Encoding ----
117    /// An encoding operation failed.
118    #[error("Encoding operation failed {0}")]
119    Encoding(#[from] EncodingOperationError),
120
121    // ---- FilterGraph ----
122    /// A filtergraph runtime operation failed.
123    #[error("Filter graph operation failed {0}")]
124    FilterGraph(#[from] FilterGraphOperationError),
125
126    // ---- Open Decoder ----
127    /// Opening or configuring a decoder failed.
128    #[error("Open decoder operation failed {0}")]
129    OpenDecoder(#[from] OpenDecoderOperationError),
130
131    // ---- Decoding ----
132    /// A decoding operation failed.
133    #[error("Decoding operation failed {0}")]
134    Decoding(#[from] DecodingOperationError),
135
136    // ---- Demuxing ----
137    /// A demuxing operation failed.
138    #[error("Demuxing operation failed {0}")]
139    Demuxing(#[from] DemuxingOperationError),
140
141    // ---- Packet Scanner ----
142    /// A packet-scanning operation failed (see [`PacketScannerError`]).
143    #[error("Packet scanner error: {0}")]
144    PacketScanner(#[from] PacketScannerError),
145
146    // ---- Frame Filter ----
147    /// A frame filter failed to initialize; carries the error returned by
148    /// the filter's `init`.
149    #[error("Frame filter init failed: {0}")]
150    FrameFilterInit(Box<dyn std::error::Error + Send + Sync>),
151
152    /// A frame filter failed while processing a frame; carries the error
153    /// returned by the filter's `filter_frame`.
154    #[error("Frame filter process failed: {0}")]
155    FrameFilterProcess(Box<dyn std::error::Error + Send + Sync>),
156
157    /// A frame filter failed while generating a frame; carries the error
158    /// returned by the filter's `request_frame`.
159    #[error("Frame filter request failed: {0}")]
160    FrameFilterRequest(Box<dyn std::error::Error + Send + Sync>),
161
162    /// Returned while building a frame pipeline when no stream of the
163    /// required media type exists at the named pipeline end; fields are the
164    /// pipeline end (input/output) and the media type.
165    #[error("No {0} stream of the type:{1} were found while build frame pipeline")]
166    FrameFilterTypeNoMatched(String, String),
167
168    /// Returned while building a frame pipeline when no stream at the named
169    /// pipeline end matches both the requested stream index and media type;
170    /// fields are the pipeline end, the stream index, and the media type.
171    #[error("{0} stream:{1} of the type:{2} were mismatched while build frame pipeline")]
172    FrameFilterStreamTypeNoMatched(String, usize, String),
173
174    /// Returned when a frame pipeline tries to deliver a frame to a
175    /// destination that has already finished.
176    #[error("Frame filter pipeline destination already finished")]
177    FrameFilterDstFinished,
178
179    /// Returned when a frame pipeline fails to duplicate a frame required
180    /// for an additional destination.
181    #[error("Frame filter pipeline failed to duplicate a frame for an additional destination")]
182    FrameFilterFrameDuplicateFailed,
183
184    /// Returned when spawning a frame pipeline's worker thread fails, so
185    /// the pipeline never ran.
186    #[error("Frame filter pipeline thread exited")]
187    FrameFilterThreadExited,
188
189    /// A worker thread panicked; the payload is the worker's thread name.
190    /// Output may be incomplete.
191    #[error("Worker thread '{0}' panicked; output may be incomplete")]
192    WorkerPanicked(String),
193
194    /// Recorded as the scheduler result when `start()` fails after some
195    /// worker threads were already launched. `start()` itself returns the
196    /// actual init error to its caller; this recorded value is what
197    /// concurrent observers (packet-sink terminal callbacks) report, so a
198    /// sink can never mistake a torn-down startup for a settled-Ok job.
199    #[error("Scheduler start failed; the job was torn down during startup")]
200    StartFailed,
201
202    /// Returned when publishing to the embedded RTMP server with a stream
203    /// key that is already in use; the payload is the key.
204    #[cfg(feature = "rtmp")]
205    #[error("Rtmp stream already exists with key: {0}")]
206    RtmpStreamAlreadyExists(String),
207
208    /// Returned when a stream could not be created on the embedded RTMP
209    /// server, typically because the server has stopped.
210    #[cfg(feature = "rtmp")]
211    #[error("Rtmp create stream failed. Check whether the server is stopped.")]
212    RtmpCreateStream,
213
214    /// Returned when too many streams are waiting to be registered on the
215    /// embedded RTMP server.
216    #[cfg(feature = "rtmp")]
217    #[error("Rtmp registration queue is full: too many streams are waiting to be registered")]
218    RtmpRegistrationQueueFull,
219
220    /// Returned when the embedded RTMP server's thread has exited.
221    #[cfg(feature = "rtmp")]
222    #[error("Rtmp server thread exited")]
223    RtmpThreadExited,
224
225    /// Returned when the embedded RTMP server is no longer consuming a
226    /// published stream.
227    #[cfg(feature = "rtmp")]
228    #[error("Rtmp stream closed: the server is no longer consuming this stream")]
229    RtmpStreamClosed,
230
231    /// Returned when starting an embedded RTMP server that was already
232    /// started; clones of one server share a single lifecycle that can be
233    /// started only once.
234    #[cfg(feature = "rtmp")]
235    #[error("Rtmp server already started: clones of one server share a single lifecycle, which can be started only once")]
236    RtmpServerAlreadyStarted,
237
238    /// A subtitle processing operation failed.
239    #[cfg(feature = "subtitle")]
240    #[error("Subtitle error: {0}")]
241    Subtitle(#[from] crate::subtitle::SubtitleError),
242
243    /// A wgpu GPU filter operation failed.
244    #[cfg(feature = "wgpu")]
245    #[error("Wgpu filter error: {0}")]
246    WgpuFilter(#[from] crate::wgpu_filter::WgpuFilterError),
247
248    // The allow covers the deprecation that OpenGLFilterError inherits from
249    // the deprecated `opengl` module; the variant must still carry the type.
250    // From is hand-written below the enum (a derived #[from] would re-name
251    // the type in generated code that no #[allow] on the variant reaches).
252    /// An OpenGL filter operation failed (deprecated `opengl` feature).
253    #[cfg(feature = "opengl")]
254    #[allow(deprecated)]
255    #[error("OpenGL filter error: {0}")]
256    OpenGLFilter(#[source] OpenGLFilterError),
257
258    /// An I/O error from the standard library.
259    #[error("IO error:{0}")]
260    IO(#[from] io::Error),
261
262    /// Internal end-of-stream marker passed between pipeline stages; a
263    /// normal end of input is consumed internally rather than reported as
264    /// a job failure.
265    #[error("EOF")]
266    EOF,
267    /// Internal control-flow marker instructing pipeline stages to shut
268    /// down; normally consumed internally.
269    #[error("Exit")]
270    Exit,
271    /// Internal invariant violation that should never occur; indicates a
272    /// bug in this crate rather than a problem with user input.
273    #[error("Bug")]
274    Bug,
275
276    /// Returned when a recipe or analysis option is invalid (out of range,
277    /// malformed, or inconsistent); the payload describes the problem.
278    #[error("Invalid recipe argument: {0}")]
279    InvalidRecipeArg(String),
280
281    /// A container-info query was called with an out-of-range index.
282    #[error("Container info error: {0}")]
283    ContainerInfo(#[from] ContainerInfoError),
284
285    /// A frame-export operation failed.
286    #[error("Frame export error: {0}")]
287    FrameExport(#[from] crate::core::frame_export::FrameExportError),
288
289    /// Building or opening a video writer failed.
290    #[error("Video writer error: {0}")]
291    Writer(#[from] crate::core::writer::WriterError),
292
293    /// Pushing a frame into a video writer failed.
294    #[error("Video writer push error: {0}")]
295    Push(#[from] crate::core::writer::PushError),
296
297    /// Returned when a frame-source input's worker thread failed to start.
298    #[error("Frame source thread failed to start")]
299    FrameSourceThreadExited,
300
301    /// A packet-sink output failed (see [`PacketSinkError`]).
302    #[error("Packet sink error: {0}")]
303    PacketSink(#[from] PacketSinkError),
304
305    /// CLI-compat pipelines only: a `-vf` command was lowered onto an input
306    /// whose OPENED demuxer does not carry exactly one video stream. The
307    /// check runs on the demuxer instance the pipeline actually executes
308    /// with (no separate probe opening, no TOCTOU window). The facade maps
309    /// this to its public `AmbiguousFilterSource` diagnostic.
310    #[cfg(feature = "cli")]
311    #[error("the per-output video filter requires exactly one video stream in the input; the opened input has {video_streams}")]
312    AmbiguousVideoSource {
313        /// Number of video streams carried by the opened input.
314        video_streams: usize,
315    },
316
317    /// Strict AVOption handling (CLI-compat pipelines): an option the caller
318    /// supplied was not consumed by the component it targeted. The default
319    /// builder path only WARNS about such leftovers; pipelines built through
320    /// the `cli` feature's entry points fail instead, mirroring fftools'
321    /// `check_avoptions` abort. Only exists with the `cli` feature — the
322    /// feature-off API surface is unchanged.
323    #[cfg(feature = "cli")]
324    #[error("option '{option}' was not consumed by {site}; CLI-compat strict mode treats leftover AVOptions as errors")]
325    UnconsumedCliOption {
326        /// Human-readable description of the component that should have
327        /// consumed the option (e.g. "the muxer of output 0").
328        site: String,
329        /// The option key that was left unconsumed.
330        option: String,
331    },
332}
333
334// `Error` rides in every hot-path `Result` — the per-frame encoder and filter
335// calls return `Result<(), Error>` / `Result<bool, Error>` — so its size is a
336// layout contract, not an implementation detail: one oversized payload grows
337// every such `Result` crate-wide. 64 bytes is the long-standing layout; keep
338// new payloads inside it (use static labels for fixed vocabulary, or box a
339// genuinely large variant). A const assertion rather than a #[test] so that
340// merely compiling the crate enforces the bound for whichever feature-gated
341// variants that build carries — including feature combinations whose tests
342// are compiled but never run.
343const _: () = assert!(
344    std::mem::size_of::<Error>() <= 64,
345    "Error grew past its 64-byte layout: shrink the new payload (static labels) or box the variant"
346);
347
348/// Builder/open-time validation errors for [`crate::VideoWriter`]. Exported here
349/// (not from the crate root) to mirror the existing `OpenInputError` /
350/// `OpenOutputError` organization; the root surface stays the settled writer
351/// types (the writer itself, its builder, and the push error pair).
352pub use crate::core::writer::WriterError;
353
354// Hand-written counterpart of the #[from] the sibling variants derive: the
355// error type inherits deprecation from the deprecated `opengl` module, so
356// the conversion is spelled out where the lint can be silenced.
357#[cfg(feature = "opengl")]
358#[allow(deprecated)]
359impl From<OpenGLFilterError> for Error {
360    fn from(err: OpenGLFilterError) -> Self {
361        Error::OpenGLFilter(err)
362    }
363}
364
365/// Errors from the `container_info` queries where the caller asked for an index
366/// outside the container's range. These are caller/argument errors — a bad index
367/// into an otherwise valid container — kept distinct from an open/probe failure
368/// (`OpenInputError` / `FindStreamError`) so retry logic, telemetry, and user
369/// messages can tell "you asked for chapter 5 of a 3-chapter file" apart from
370/// "the file is corrupt or unreadable". Each variant carries the offending
371/// `index` and the container's actual `count`.
372#[derive(thiserror::Error, Debug)]
373#[non_exhaustive]
374pub enum ContainerInfoError {
375    /// Returned when the requested chapter index exceeds the number of
376    /// chapters in the container.
377    #[error("chapter index {index} out of range: the container has {count} chapter(s)")]
378    ChapterIndexOutOfRange {
379        /// The chapter index that was requested.
380        index: usize,
381        /// Number of chapters the container actually has.
382        count: usize,
383    },
384
385    /// Returned when the requested stream index exceeds the number of
386    /// streams in the container.
387    #[error("stream index {index} out of range: the container has {count} stream(s)")]
388    StreamIndexOutOfRange {
389        /// The stream index that was requested.
390        index: usize,
391        /// Number of streams the container actually has.
392        count: usize,
393    },
394}
395
396/// Error type for RTMP streaming operations using StreamBuilder
397#[cfg(feature = "rtmp")]
398#[derive(thiserror::Error, Debug)]
399#[non_exhaustive]
400pub enum StreamError {
401    /// Returned when a required builder parameter was never set; the
402    /// payload is the parameter name (e.g. "address", "stream_key").
403    #[error("missing required parameter: {0}")]
404    MissingParameter(&'static str),
405
406    /// Returned when the configured input path does not point to an
407    /// existing file.
408    #[error("input path is not a valid file: {path}")]
409    InputNotFound {
410        /// The input path that failed validation.
411        path: std::path::PathBuf,
412    },
413
414    /// An underlying ez-ffmpeg error raised while building or running the
415    /// stream.
416    #[error("ffmpeg error: {0}")]
417    Ffmpeg(#[from] crate::error::Error),
418}
419
420impl PartialEq for Error {
421    /// Structural equality for payload-less variants only. Variants carrying
422    /// an inner error compare unequal even to themselves — use matches! on
423    /// the variant when that is what you mean.
424    fn eq(&self, other: &Self) -> bool {
425        use Error::*;
426        match (self, other) {
427            (NotStarted, NotStarted)
428            | (FilterDescUtf8, FilterDescUtf8)
429            | (FilterNameUtf8, FilterNameUtf8)
430            | (FilterZeroOutputs, FilterZeroOutputs)
431            | (FilterZeroInputs, FilterZeroInputs)
432            | (ParseInteger, ParseInteger)
433            | (FrameFilterDstFinished, FrameFilterDstFinished)
434            | (FrameFilterFrameDuplicateFailed, FrameFilterFrameDuplicateFailed)
435            | (FrameFilterThreadExited, FrameFilterThreadExited)
436            | (FrameSourceThreadExited, FrameSourceThreadExited)
437            | (EOF, EOF)
438            | (Exit, Exit)
439            | (Bug, Bug) => true,
440            #[cfg(feature = "rtmp")]
441            (RtmpCreateStream, RtmpCreateStream)
442            | (RtmpRegistrationQueueFull, RtmpRegistrationQueueFull)
443            | (RtmpThreadExited, RtmpThreadExited)
444            | (RtmpStreamClosed, RtmpStreamClosed)
445            | (RtmpServerAlreadyStarted, RtmpServerAlreadyStarted) => true,
446            _ => false,
447        }
448    }
449}
450
451// No Eq impl: variants carrying payloads are not equal to themselves, so
452// the relation is not reflexive and claiming Eq would be a lie.
453
454/// Errors from the demuxer stage while reading packets from an input.
455/// Variants carrying a [`DemuxingError`] embed the mapped FFmpeg error.
456#[derive(thiserror::Error, Debug)]
457#[non_exhaustive]
458pub enum DemuxingOperationError {
459    /// Returned when reading the next packet from the input fails
460    /// (`av_read_frame`).
461    #[error("while reading frame: {0}")]
462    ReadFrameError(DemuxingError),
463
464    /// Returned when creating an additional reference to a demuxed packet
465    /// fails (`av_packet_ref`).
466    #[error("while referencing packet: {0}")]
467    PacketRefError(DemuxingError),
468
469    /// Returned when seeking in the input fails (`avformat_seek_file`).
470    #[error("while seeking file: {0}")]
471    SeekFileError(DemuxingError),
472
473    /// Returned when spawning the demuxer thread fails, so demuxing never
474    /// started.
475    #[error("Thread exited")]
476    ThreadExited,
477}
478
479/// Errors from the decoder stage while turning packets into frames.
480/// Variants carrying a [`DecodingError`] embed the mapped FFmpeg error.
481#[derive(thiserror::Error, Debug)]
482#[non_exhaustive]
483pub enum DecodingOperationError {
484    /// Returned when creating a new reference to a decoded frame fails
485    /// (`av_frame_ref`).
486    #[error("during frame reference creation: {0}")]
487    FrameRefError(DecodingError),
488
489    /// Returned when copying frame metadata fails (`av_frame_copy_props`).
490    #[error("during frame properties copy: {0}")]
491    FrameCopyPropsError(DecodingError),
492
493    /// Returned when decoding a subtitle packet fails
494    /// (`avcodec_decode_subtitle2`).
495    #[error("during subtitle decoding: {0}")]
496    DecodeSubtitleError(DecodingError),
497
498    /// Returned when copying a decoded subtitle for delivery fails.
499    #[error("during subtitle copy: {0}")]
500    CopySubtitleError(DecodingError),
501
502    /// Returned when submitting a packet to the decoder fails
503    /// (`avcodec_send_packet`).
504    #[error("during packet submission to decoder: {0}")]
505    SendPacketError(DecodingError),
506
507    /// Returned when receiving a decoded frame from the decoder fails
508    /// (`avcodec_receive_frame`).
509    #[error("during frame reception from decoder: {0}")]
510    ReceiveFrameError(DecodingError),
511
512    /// Returned when allocating a frame during decoding fails.
513    #[error("during frame allocation: {0}")]
514    FrameAllocationError(DecodingError),
515
516    /// Returned when allocating a packet during decoding fails.
517    #[error("during packet allocation: {0}")]
518    PacketAllocationError(DecodingError),
519
520    /// Returned when allocating an `AVSubtitle` during decoding fails.
521    #[error("during AVSubtitle allocation: {0}")]
522    SubtitleAllocationError(DecodingError),
523
524    /// Returned when the decoder emits a frame flagged as corrupt and
525    /// corrupt frames are treated as errors.
526    #[error("corrupt decoded frame")]
527    CorruptFrame,
528
529    /// Returned when the ratio of decode errors to decoded frames exceeds
530    /// the maximum allowed rate.
531    #[error("decode error rate exceeded the maximum allowed")]
532    ErrorRateExceeded,
533
534    /// Returned when downloading a hardware-decoded frame to system memory
535    /// fails (`av_hwframe_transfer_data`).
536    #[error("during retrieve data on hw: {0}")]
537    HWRetrieveDataError(DecodingError),
538
539    /// Returned when applying codec cropping metadata to a decoded frame
540    /// fails.
541    #[error("during cropping: {0}")]
542    CroppingError(DecodingError),
543}
544
545/// Errors from opening and configuring a decoder.
546/// Variants carrying an [`OpenDecoderError`] embed the mapped FFmpeg error.
547#[derive(thiserror::Error, Debug)]
548#[non_exhaustive]
549pub enum OpenDecoderOperationError {
550    /// Returned when allocating the decoder context fails
551    /// (`avcodec_alloc_context3`).
552    #[error("during context allocation: {0}")]
553    ContextAllocationError(OpenDecoderError),
554
555    /// Returned when applying the stream's codec parameters to the decoder
556    /// context fails (`avcodec_parameters_to_context`).
557    #[error("while applying parameters to context: {0}")]
558    ParameterApplicationError(OpenDecoderError),
559
560    /// Returned when opening the decoder fails (`avcodec_open2`).
561    #[error("while opening decoder: {0}")]
562    DecoderOpenError(OpenDecoderError),
563
564    /// Returned when copying the audio channel layout into the decoder
565    /// context fails.
566    #[error("while copying channel layout: {0}")]
567    ChannelLayoutCopyError(OpenDecoderError),
568
569    /// Returned when setting up hardware acceleration for the decoder
570    /// fails.
571    #[error("while Hw setup: {0}")]
572    HwSetupError(OpenDecoderError),
573
574    /// Returned when the configured decoder name is invalid.
575    #[error("Invalid decoder name")]
576    InvalidName,
577
578    /// Returned when spawning the decoder thread fails, so the decoder
579    /// never opened.
580    #[error("Thread exited")]
581    ThreadExited,
582}
583
584/// Errors from running frames through a configured filtergraph.
585/// Variants carrying a [`FilterGraphError`] embed the mapped FFmpeg error.
586#[derive(thiserror::Error, Debug)]
587#[non_exhaustive]
588pub enum FilterGraphOperationError {
589    /// Returned when requesting the next frame from the graph fails
590    /// (`avfilter_graph_request_oldest`).
591    #[error("during requesting oldest frame: {0}")]
592    RequestOldestError(FilterGraphError),
593
594    /// Returned when processing frames through the filtergraph fails.
595    #[error("during process frames: {0}")]
596    ProcessFramesError(FilterGraphError),
597
598    /// Returned when sending frames into the filtergraph fails.
599    #[error("during send frames: {0}")]
600    SendFramesError(FilterGraphError),
601
602    /// Returned when copying an audio channel layout while configuring the
603    /// graph fails.
604    #[error("during copying channel layout: {0}")]
605    ChannelLayoutCopyError(FilterGraphError),
606
607    /// Returned when pushing a frame into a graph input fails
608    /// (`av_buffersrc_add_frame`).
609    #[error("during buffer source add frame: {0}")]
610    BufferSourceAddFrameError(FilterGraphError),
611
612    /// Returned when closing a graph input at end of stream fails
613    /// (`av_buffersrc_close`).
614    #[error("during closing buffer source: {0}")]
615    BufferSourceCloseError(FilterGraphError),
616
617    /// Returned when replacing a frame's buffer reference fails
618    /// (`av_buffer_replace`).
619    #[error("during replace buffer: {0}")]
620    BufferReplaceoseError(FilterGraphError),
621
622    /// Returned when cloning frame side data for the graph fails.
623    #[error("during cloning frame side data: {0}")]
624    FrameSideDataCloneError(FilterGraphError),
625
626    /// Returned when parsing or configuring the filtergraph description
627    /// fails.
628    #[error("during parse: {0}")]
629    ParseError(FilterGraphParseError),
630
631    /// Returned when a frame entering the graph carries invalid or
632    /// corrupted data.
633    #[error("The data in the frame is invalid or corrupted")]
634    InvalidData,
635
636    /// Returned before the graph is configured when one input has buffered
637    /// frames past the admission limit while another input has not yet
638    /// delivered its first frame; fields are the input label, the buffered
639    /// frame count, and the estimated retained memory in bytes.
640    #[error(
641        "graph input '{0}' already holds {1} buffered frames and admitting the next \
642         one would raise the best-effort retained-memory estimate to ~{2} bytes, \
643         while another input has not yet delivered its first frame, so the filter \
644         graph cannot be configured; check that every graph input actually produces \
645         data (or produces it within the buffering window)"
646    )]
647    PreConfigQueueOverflow(String, usize, usize),
648
649    // Only constructed on the FFmpeg 8+ buffersrc side-data clone path.
650    /// Returned when a frame's combined side-data metadata is too large to
651    /// deep-copy into the buffersrc parameters; fields are the input label
652    /// and the estimated size in bytes.
653    #[cfg_attr(not(ffmpeg_8_0), allow(dead_code))]
654    #[error(
655        "graph input '{0}' would deep-copy an estimated {1} bytes of side-data \
656         metadata into the buffersrc parameters, exceeding the side-data clone \
657         estimate threshold; the frame's combined side-data metadata (across its \
658         global and downmix entries) is pathologically large"
659    )]
660    OversizedSideDataClone(String, usize),
661
662    /// Returned when spawning the filtergraph thread fails, so the graph
663    /// never ran.
664    #[error("Thread exited")]
665    ThreadExited,
666}
667
668/// Errors from the encoder stage while turning frames into packets.
669/// Variants carrying an [`EncodingError`] embed the mapped FFmpeg error.
670#[derive(thiserror::Error, Debug)]
671#[non_exhaustive]
672pub enum EncodingOperationError {
673    /// Returned when submitting a frame to the encoder fails
674    /// (`avcodec_send_frame`).
675    #[error("during frame submission: {0}")]
676    SendFrameError(EncodingError),
677
678    /// Returned when receiving an encoded packet from the encoder fails
679    /// (`avcodec_receive_packet`).
680    #[error("during packet retrieval: {0}")]
681    ReceivePacketError(EncodingError),
682
683    /// Returned when re-chunking buffered audio samples into encoder-sized
684    /// frames fails.
685    #[error("during audio frame receive: {0}")]
686    ReceiveAudioError(EncodingError),
687
688    /// Returned when a subtitle packet reaches the encoder without a
689    /// presentation timestamp.
690    #[error(": Subtitle packets must have a pts")]
691    SubtitleNotPts,
692
693    /// Returned when an encoded packet cannot be delivered because the
694    /// muxer has already finished.
695    #[error(": Muxer already finished")]
696    MuxerFinished,
697
698    /// An output stream buffered more packets before the muxer started than the
699    /// pre-mux queue admits (fftools `AVERROR_BUFFER_TOO_SMALL`, "Too many
700    /// packets buffered for output stream"). Unlike `MuxerFinished` this is a
701    /// hard failure — never a silent truncation — so it must reach the
702    /// scheduler error, not the graceful stop path.
703    #[error(": too many packets buffered for an output stream before the muxer started; raise Output::set_max_muxing_queue_size / Output::set_muxing_queue_data_threshold, or check that every mapped output stream receives data")]
704    MuxQueueFull,
705
706    /// Returned when encoding a subtitle fails (see
707    /// [`EncodeSubtitleError`]).
708    #[error("Encode subtitle error: {0}")]
709    EncodeSubtitle(#[from] EncodeSubtitleError),
710
711    /// Returned when allocating a packet for encoder output fails.
712    #[error(": {0}")]
713    AllocPacket(AllocPacketError),
714}
715
716/// Errors from the muxer stage while writing the output container.
717/// Variants carrying a [`MuxingError`] embed the mapped FFmpeg error.
718#[derive(thiserror::Error, Debug)]
719#[non_exhaustive]
720pub enum MuxingOperationError {
721    /// Returned when writing the container header fails (see
722    /// [`WriteHeaderError`]).
723    #[error("during write header: {0}")]
724    WriteHeader(WriteHeaderError),
725
726    /// Returned when initializing a bitstream filter chain for an output
727    /// stream fails; fields are the chain description and the underlying
728    /// error.
729    #[error("while initializing bitstream filter chain '{0}': {1}")]
730    BitstreamFilterInit(String, MuxingError),
731
732    /// Returned when writing an interleaved packet to the container fails
733    /// (`av_interleaved_write_frame`).
734    #[error("during interleaved write: {0}")]
735    InterleavedWriteError(MuxingError),
736
737    /// Returned when writing the container trailer fails
738    /// (`av_write_trailer`).
739    #[error("during trailer write: {0}")]
740    TrailerWriteError(MuxingError),
741
742    /// Returned when closing the output I/O context fails.
743    #[error("during closing IO: {0}")]
744    IOCloseError(MuxingError),
745
746    /// Returned when spawning the muxer (or mux-init) thread fails, so
747    /// muxing never started.
748    #[error("Thread exited")]
749    ThreadExited,
750}
751
752/// Errors specific to packet-sink outputs (`Output::new_by_packet_sink`).
753///
754/// The strict tier fails fast: configuration problems surface from `build()`
755/// or from the job **before any sink callback runs**; per-packet violations
756/// stop the job with the offending packet never delivered. `Clone` is
757/// deliberate — for delivery-path errors the same value is recorded as the
758/// job error and handed to the sink's `on_delivery_error` callback.
759/// [`JobFailed`](Self::JobFailed) is the exception: it is synthesized for
760/// that callback only, while first-error-wins may leave the job result owned
761/// by a sibling worker's error.
762#[derive(thiserror::Error, Debug, Clone)]
763#[non_exhaustive]
764pub enum PacketSinkError {
765    /// A builder option the packet sink cannot honor was set: either a
766    /// container-only option (no container is written, so it could never
767    /// take effect) or a pipeline feature outside the strict tier's
768    /// delivery contract (filters, bitstream filters, subtitle codecs —
769    /// rejected as policy, not for lack of a container).
770    #[error("{0} is not supported on packet-sink outputs")]
771    UnsupportedOption(&'static str),
772
773    /// A stream was configured as `copy`; packet sinks require encoded
774    /// streams.
775    #[error("stream copy is not supported on packet-sink outputs (strict tier requires encoded streams)")]
776    StreamCopyUnsupported,
777
778    /// The output mapped a stream the strict tier cannot deliver (non-H.264
779    /// video, non-AAC audio, or a non-audio/video kind).
780    #[error("{kind} streams are not supported on packet-sink outputs (strict tier)")]
781    UnsupportedStream {
782        /// Label describing the rejected stream kind (e.g. "non-H.264
783        /// video", "non-AAC audio").
784        kind: &'static str,
785    },
786
787    /// The configured encoder is outside the strict-tier v1 whitelist.
788    #[error("encoder '{encoder}' is not on the strict-tier whitelist for {kind} (v1 accepts: {allowed})")]
789    EncoderNotWhitelisted {
790        /// Media kind of the stream ("video" or "audio").
791        kind: &'static str,
792        /// The encoder name that was configured.
793        encoder: String,
794        /// The encoder names the strict tier accepts for this kind.
795        allowed: &'static str,
796    },
797
798    /// No stream was mapped to the packet-sink output.
799    #[error("packet-sink output has no streams")]
800    NoStreams,
801
802    /// An encoder finalized without the out-of-band codec configuration the
803    /// strict tier delivers via `on_stream_info`.
804    #[error("output stream {stream_index}: encoder produced no extradata; the strict tier requires codec configuration (avcC / AudioSpecificConfig) before the first callback")]
805    MissingExtradata {
806        /// Index of the offending output stream.
807        stream_index: usize,
808    },
809
810    /// The encoder's codec configuration failed strict-tier validation.
811    #[error("output stream {stream_index}: invalid codec configuration: {reason}")]
812    InvalidExtradata {
813        /// Index of the offending output stream.
814        stream_index: usize,
815        /// Why the codec configuration failed validation.
816        reason: String,
817    },
818
819    /// A stream's time base is not a positive rational.
820    #[error("output stream {stream_index}: invalid time base {num}/{den} (positive numerator and denominator required)")]
821    InvalidTimeBase {
822        /// Index of the offending output stream.
823        stream_index: usize,
824        /// Numerator of the rejected time base.
825        num: i32,
826        /// Denominator of the rejected time base.
827        den: i32,
828    },
829
830    /// A packet was stamped in a time base other than its stream's.
831    #[error("output stream {stream_index}: packet time base {packet_num}/{packet_den} differs from the stream time base {stream_num}/{stream_den}")]
832    PacketTimeBaseMismatch {
833        /// Index of the offending output stream.
834        stream_index: usize,
835        /// Numerator of the packet's time base.
836        packet_num: i32,
837        /// Denominator of the packet's time base.
838        packet_den: i32,
839        /// Numerator of the stream's time base.
840        stream_num: i32,
841        /// Denominator of the stream's time base.
842        stream_den: i32,
843    },
844
845    /// A packet carries no pts or dts (`AV_NOPTS_VALUE`).
846    #[error("output stream {stream_index}: packet carries no {which} (strict tier rejects AV_NOPTS_VALUE)")]
847    MissingTimestamp {
848        /// Index of the offending output stream.
849        stream_index: usize,
850        /// Which timestamp is missing: "pts" or "dts".
851        which: &'static str,
852    },
853
854    /// A packet's dts did not strictly increase within its stream.
855    #[error("output stream {stream_index}: non-monotonic dts (previous {prev}, current {current})")]
856    NonMonotonicDts {
857        /// Index of the offending output stream.
858        stream_index: usize,
859        /// dts of the previous packet, in stream time-base units.
860        prev: i64,
861        /// dts of the offending packet, in stream time-base units.
862        current: i64,
863    },
864
865    /// A packet's pts collided with a still-pending pts on the same stream.
866    #[error("output stream {stream_index}: duplicate pts {pts}")]
867    DuplicatePts {
868        /// Index of the offending output stream.
869        stream_index: usize,
870        /// The duplicated pts value, in stream time-base units.
871        pts: i64,
872    },
873
874    /// A packet's pts is earlier than its dts.
875    #[error("output stream {stream_index}: pts {pts} is earlier than dts {dts}")]
876    PtsBeforeDts {
877        /// Index of the offending output stream.
878        stream_index: usize,
879        /// The packet's pts, in stream time-base units.
880        pts: i64,
881        /// The packet's dts, in stream time-base units.
882        dts: i64,
883    },
884
885    /// Rescaling a timestamp onto the shared time origin overflowed.
886    #[error("output stream {stream_index}: timestamp overflow while applying the shared time origin")]
887    TimestampOverflow {
888        /// Index of the offending output stream.
889        stream_index: usize,
890    },
891
892    /// A packet has no positive duration and none could be derived from the
893    /// stream configuration (frame rate / codec frame size).
894    #[error("output stream {stream_index}: packet duration is absent and cannot be derived (strict tier requires a positive duration)")]
895    MissingDuration {
896        /// Index of the offending output stream.
897        stream_index: usize,
898    },
899
900    /// The packet payload failed bitstream validation.
901    #[error("output stream {stream_index}: malformed packet payload: {reason}")]
902    MalformedPacket {
903        /// Index of the offending output stream.
904        stream_index: usize,
905        /// Description of the bitstream validation failure.
906        reason: String,
907    },
908
909    /// Internal sequencing violation: a packet surfaced outside the delivery
910    /// phase.
911    #[error("output stream {stream_index}: packet processed outside the delivery phase (internal sequencing violation)")]
912    PhaseViolation {
913        /// Index of the offending output stream.
914        stream_index: usize,
915    },
916
917    /// The stream configuration changed after `on_stream_info` delivered it.
918    #[error("output stream {stream_index}: mid-stream configuration change ({what}); the strict tier requires an immutable stream configuration")]
919    ConfigChange {
920        /// Index of the offending output stream.
921        stream_index: usize,
922        /// Description of the configuration change that was detected.
923        what: String,
924    },
925
926    /// An H.264 access unit carried in-band SPS/PPS parameter sets.
927    #[error("output stream {stream_index}: in-band SPS/PPS parameter sets are not supported in the strict tier (WebCodecs avc requires out-of-band configuration)")]
928    InBandParameterSets {
929        /// Index of the offending output stream.
930        stream_index: usize,
931    },
932
933    /// The sink's `on_stream_info` callback rejected the configuration.
934    #[error("on_stream_info callback rejected the stream configuration: {error}")]
935    StreamInfoCallbackFailed {
936        /// The error the callback returned.
937        #[source]
938        error: crate::core::packet_sink::PacketCallbackError,
939    },
940
941    /// The sink's `on_packet` callback returned an error.
942    #[error("on_packet callback failed on output stream {stream_index}: {error}")]
943    PacketCallbackFailed {
944        /// Index of the offending output stream.
945        stream_index: usize,
946        /// The error the callback returned.
947        #[source]
948        error: crate::core::packet_sink::PacketCallbackError,
949    },
950
951    /// The channel adapter's receiver was dropped, cancelling delivery and
952    /// the job.
953    #[error("the packet-sink channel receiver was dropped; delivery cancelled")]
954    ChannelDisconnected,
955
956    /// The job failed outside this sink's delivery path; handed to
957    /// `on_delivery_error` only, while `wait()` keeps the original error.
958    #[error("the job failed outside this packet sink; delivery may have been truncated: {message}")]
959    JobFailed {
960        /// Display rendering of the error that actually failed the job.
961        message: String,
962    },
963}
964
965/// Errors from opening and configuring an encoder.
966/// Variants carrying an [`OpenEncoderError`] embed the mapped FFmpeg error.
967#[derive(thiserror::Error, Debug)]
968#[non_exhaustive]
969pub enum OpenEncoderOperationError {
970    /// Returned when cloning frame side data into the encoder context
971    /// fails.
972    #[error("during frame side data cloning: {0}")]
973    FrameSideDataCloneError(OpenEncoderError),
974
975    /// Returned when copying the audio channel layout into the encoder
976    /// context fails.
977    #[error("during channel layout copying: {0}")]
978    ChannelLayoutCopyError(OpenEncoderError),
979
980    /// Returned when opening the encoder fails (`avcodec_open2`).
981    #[error("during codec opening: {0}")]
982    CodecOpenError(OpenEncoderError),
983
984    /// Returned when exporting encoder parameters to the output stream
985    /// fails (`avcodec_parameters_from_context`).
986    #[error("while setting codec parameters: {0}")]
987    CodecParametersError(OpenEncoderError),
988
989    /// Returned when the format of the frame to encode is unknown.
990    #[error(": unknown format of the frame")]
991    UnknownFrameFormat,
992
993    /// Returned when configuring subtitle encoding parameters fails.
994    #[error("while setting subtitle: {0}")]
995    SettingSubtitleError(OpenEncoderError),
996
997    /// Returned when setting up hardware acceleration for the encoder
998    /// fails.
999    #[error("while Hw setup: {0}")]
1000    HwSetupError(OpenEncoderError),
1001
1002    /// Returned when allocating the encoder context fails
1003    /// (`avcodec_alloc_context3`).
1004    #[error("during context allocation: {0}")]
1005    ContextAllocationError(OpenEncoderError),
1006
1007    /// Returned when the frame stream ends (EOF or upstream disconnect)
1008    /// before the encoder received any frame, so the encoder was never
1009    /// opened.
1010    #[error(": no frames were received before EOF; encoder never opened")]
1011    NoFramesReceived,
1012
1013    /// Returned when the stream's media type cannot be encoded (not video,
1014    /// audio, or subtitle).
1015    #[error(": unsupported media type for encoding")]
1016    UnsupportedMediaType,
1017
1018    /// Returned when spawning the encoder thread fails, so the encoder
1019    /// never started.
1020    #[error("Thread exited")]
1021    ThreadExited,
1022}
1023
1024/// Errors from converting URL or path strings for FFmpeg.
1025#[derive(thiserror::Error, Debug)]
1026#[non_exhaustive]
1027pub enum UrlError {
1028    /// Returned when the string contains an interior NUL byte, which C
1029    /// strings cannot represent; the payload is the byte position.
1030    #[error("Null byte found in string at position {0}")]
1031    NullByteError(usize),
1032}
1033
1034impl From<NulError> for Error {
1035    fn from(err: NulError) -> Self {
1036        Error::Url(UrlError::NullByteError(err.nul_position()))
1037    }
1038}
1039
1040/// Errors from opening an input file, stream, device, or custom input
1041/// source. Most variants are mapped from the FFmpeg error code returned by
1042/// `avformat_open_input`.
1043#[derive(thiserror::Error, Debug)]
1044#[non_exhaustive]
1045pub enum OpenInputError {
1046    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1047    #[error("Memory allocation error")]
1048    OutOfMemory,
1049
1050    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1051    #[error("Invalid argument provided")]
1052    InvalidArgument,
1053
1054    /// The file, URL, or device does not exist (`AVERROR(ENOENT)`).
1055    #[error("File or stream not found")]
1056    NotFound,
1057
1058    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1059    #[error("I/O error occurred while opening the file or stream")]
1060    IOError,
1061
1062    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1063    #[error("Pipe error, possibly the stream or data connection was broken")]
1064    PipeError,
1065
1066    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1067    #[error("Invalid file descriptor")]
1068    BadFileDescriptor,
1069
1070    /// The functionality or input format is not supported by the linked
1071    /// FFmpeg build (`AVERROR(ENOSYS)`).
1072    #[error("Functionality not implemented or unsupported input format")]
1073    NotImplemented,
1074
1075    /// The operation was not permitted (`AVERROR(EPERM)`).
1076    #[error("Operation not permitted to access the file or stream")]
1077    OperationNotPermitted,
1078
1079    /// The file or stream contains invalid or corrupted data
1080    /// (`AVERROR_INVALIDDATA`).
1081    #[error("The data in the file or stream is invalid or corrupted")]
1082    InvalidData,
1083
1084    /// The connection timed out (`AVERROR(ETIMEDOUT)`).
1085    #[error("The connection timed out while trying to open the stream")]
1086    Timeout,
1087
1088    /// A builder option carried an invalid value (e.g. a non-positive
1089    /// `set_framerate`, a non-finite `set_ts_scale`, an out-of-range
1090    /// `set_io_buffer_size`). Setters store values as given and defer
1091    /// validation to open time, so a bad value surfaces here instead of
1092    /// panicking in the setter.
1093    #[error("Invalid input option: {0}")]
1094    InvalidOption(String),
1095
1096    /// Any other failure; the payload is the raw FFmpeg error code
1097    /// (rendered with `av_err2str` in the message).
1098    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1099    UnknownError(i32),
1100
1101    /// Returned when the input has no usable source: neither a URL nor a
1102    /// custom read callback was configured.
1103    #[error("Invalid source provided")]
1104    InvalidSource,
1105
1106    /// Returned when the explicitly requested input format name is unknown
1107    /// to FFmpeg; the payload is the requested name.
1108    #[error("Invalid source format:{0}")]
1109    InvalidFormat(String),
1110
1111    /// Returned when the input requires seeking but the custom input source
1112    /// provides no seek callback.
1113    #[error("No seek callback is provided")]
1114    SeekFunctionMissing,
1115}
1116
1117impl From<i32> for OpenInputError {
1118    fn from(err_code: i32) -> Self {
1119        match err_code {
1120            AVERROR_OUT_OF_MEMORY => OpenInputError::OutOfMemory,
1121            AVERROR_INVALID_ARGUMENT => OpenInputError::InvalidArgument,
1122            AVERROR_NOT_FOUND => OpenInputError::NotFound,
1123            AVERROR_IO_ERROR => OpenInputError::IOError,
1124            AVERROR_PIPE_ERROR => OpenInputError::PipeError,
1125            AVERROR_BAD_FILE_DESCRIPTOR => OpenInputError::BadFileDescriptor,
1126            AVERROR_NOT_IMPLEMENTED => OpenInputError::NotImplemented,
1127            AVERROR_OPERATION_NOT_PERMITTED => OpenInputError::OperationNotPermitted,
1128            AVERROR_INVALIDDATA => OpenInputError::InvalidData,
1129            AVERROR_TIMEOUT => OpenInputError::Timeout,
1130            _ => OpenInputError::UnknownError(err_code),
1131        }
1132    }
1133}
1134
1135const AVERROR_OUT_OF_MEMORY: i32 = AVERROR(ENOMEM);
1136const AVERROR_INVALID_ARGUMENT: i32 = AVERROR(EINVAL);
1137const AVERROR_NOT_FOUND: i32 = AVERROR(ENOENT);
1138const AVERROR_IO_ERROR: i32 = AVERROR(EIO);
1139const AVERROR_PIPE_ERROR: i32 = AVERROR(EPIPE);
1140const AVERROR_BAD_FILE_DESCRIPTOR: i32 = AVERROR(EBADF);
1141const AVERROR_NOT_IMPLEMENTED: i32 = AVERROR(ENOSYS);
1142const AVERROR_OPERATION_NOT_PERMITTED: i32 = AVERROR(EPERM);
1143const AVERROR_PERMISSION_DENIED: i32 = AVERROR(EACCES);
1144const AVERROR_TIMEOUT: i32 = AVERROR(ETIMEDOUT);
1145const AVERROR_NOT_SOCKET: i32 = AVERROR(ENOTSOCK);
1146const AVERROR_AGAIN: i32 = AVERROR(EAGAIN);
1147
1148/// Errors from probing stream information after an input is opened
1149/// (`avformat_find_stream_info`).
1150#[derive(thiserror::Error, Debug)]
1151#[non_exhaustive]
1152pub enum FindStreamError {
1153    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1154    #[error("Memory allocation error")]
1155    OutOfMemory,
1156
1157    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1158    #[error("Invalid argument provided")]
1159    InvalidArgument,
1160
1161    /// Reached end of file before stream information could be determined
1162    /// (`AVERROR_EOF`).
1163    #[error("Reached end of file while looking for stream info")]
1164    EndOfFile,
1165
1166    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
1167    #[error("Timeout occurred while reading stream info")]
1168    Timeout,
1169
1170    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1171    #[error("I/O error occurred while reading stream info")]
1172    IOError,
1173
1174    /// The stream contains invalid or corrupted data
1175    /// (`AVERROR_INVALIDDATA`).
1176    #[error("The data in the stream is invalid or corrupted")]
1177    InvalidData,
1178
1179    /// The functionality or stream format is not supported by the linked
1180    /// FFmpeg build (`AVERROR(ENOSYS)`).
1181    #[error("Functionality not implemented or unsupported stream format")]
1182    NotImplemented,
1183
1184    /// The operation was not permitted (`AVERROR(EPERM)`).
1185    #[error("Operation not permitted to access the file or stream")]
1186    OperationNotPermitted,
1187
1188    /// Returned when the input contains no streams, or no stream of the
1189    /// requested kind.
1190    #[error("No Stream found")]
1191    NoStreamFound,
1192
1193    /// Any other failure; the payload is the raw FFmpeg error code
1194    /// (rendered with `av_err2str` in the message).
1195    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1196    UnknownError(i32),
1197}
1198
1199impl From<i32> for FindStreamError {
1200    fn from(err_code: i32) -> Self {
1201        match err_code {
1202            AVERROR_OUT_OF_MEMORY => FindStreamError::OutOfMemory,
1203            AVERROR_INVALID_ARGUMENT => FindStreamError::InvalidArgument,
1204            AVERROR_EOF => FindStreamError::EndOfFile,
1205            AVERROR_TIMEOUT => FindStreamError::Timeout,
1206            AVERROR_IO_ERROR => FindStreamError::IOError,
1207            AVERROR_INVALIDDATA => FindStreamError::InvalidData,
1208            AVERROR_NOT_IMPLEMENTED => FindStreamError::NotImplemented,
1209            AVERROR_OPERATION_NOT_PERMITTED => FindStreamError::OperationNotPermitted,
1210            _ => FindStreamError::UnknownError(err_code),
1211        }
1212    }
1213}
1214
1215/// Errors from parsing a filtergraph description and wiring its inputs and
1216/// outputs.
1217#[derive(thiserror::Error, Debug)]
1218#[non_exhaustive]
1219pub enum FilterGraphParseError {
1220    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1221    #[error("Memory allocation error")]
1222    OutOfMemory,
1223
1224    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1225    #[error("Invalid argument provided")]
1226    InvalidArgument,
1227
1228    /// End of file was reached during parsing (`AVERROR_EOF`).
1229    #[error("End of file reached during parsing")]
1230    EndOfFile,
1231
1232    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1233    #[error("I/O error occurred during parsing")]
1234    IOError,
1235
1236    /// Invalid data was encountered during parsing
1237    /// (`AVERROR_INVALIDDATA`).
1238    #[error("Invalid data encountered during parsing")]
1239    InvalidData,
1240
1241    /// The functionality or filter is not supported by the linked FFmpeg
1242    /// build (`AVERROR(ENOSYS)`).
1243    #[error("Functionality not implemented or unsupported filter format")]
1244    NotImplemented,
1245
1246    /// Permission was denied — e.g. by a filter that opens files, such as
1247    /// `movie=` (`AVERROR(EACCES)`).
1248    #[error("Permission denied during filter graph parsing")]
1249    PermissionDenied,
1250
1251    /// A socket operation was attempted on a non-socket by a filter
1252    /// touching network resources (`AVERROR(ENOTSOCK)`).
1253    #[error("Socket operation on non-socket during filter graph parsing")]
1254    NotSocket,
1255
1256    /// A filter option named in the description does not exist
1257    /// (`AVERROR_OPTION_NOT_FOUND`).
1258    #[error("Option not found during filter graph configuration")]
1259    OptionNotFound,
1260
1261    /// Returned when a stream reference in the filtergraph description
1262    /// names an input file index that does not exist; fields are the index
1263    /// and the description.
1264    #[error("Invalid file index {0} in filtergraph description {1}")]
1265    InvalidFileIndexInFg(usize, String),
1266
1267    /// Returned when an output URL references an input file index that does
1268    /// not exist; fields are the index and the URL.
1269    #[error("Invalid file index {0} in output url: {1}")]
1270    InvalidFileIndexInOutput(usize, String),
1271
1272    /// Returned when a stream specifier in the filtergraph description is
1273    /// malformed; the payload is the offending text.
1274    #[error("Invalid filter specifier {0}")]
1275    InvalidFilterSpecifier(String),
1276
1277    /// Returned when a filtergraph output pad is not connected to any
1278    /// output; fields are the filter name, the pad index, and its link
1279    /// label.
1280    #[error("Filter '{0}' has output {1} ({2}) unconnected")]
1281    OutputUnconnected(String, usize, String),
1282
1283    /// Any other failure; the payload is the raw FFmpeg error code.
1284    #[error("An unknown error occurred. ret: {0}")]
1285    UnknownError(i32),
1286}
1287
1288impl From<i32> for FilterGraphParseError {
1289    fn from(err_code: i32) -> Self {
1290        match err_code {
1291            AVERROR_OUT_OF_MEMORY => FilterGraphParseError::OutOfMemory,
1292            AVERROR_INVALID_ARGUMENT => FilterGraphParseError::InvalidArgument,
1293            AVERROR_EOF => FilterGraphParseError::EndOfFile,
1294            AVERROR_IO_ERROR => FilterGraphParseError::IOError,
1295            AVERROR_INVALIDDATA => FilterGraphParseError::InvalidData,
1296            AVERROR_NOT_IMPLEMENTED => FilterGraphParseError::NotImplemented,
1297            AVERROR_OPTION_NOT_FOUND => FilterGraphParseError::OptionNotFound,
1298            // EACCES/ENOTSOCK reach here from filters that touch files or
1299            // sockets (e.g. `movie=`); map them to the variants this enum
1300            // already declares instead of degrading to UnknownError.
1301            AVERROR_PERMISSION_DENIED => FilterGraphParseError::PermissionDenied,
1302            AVERROR_NOT_SOCKET => FilterGraphParseError::NotSocket,
1303            _ => FilterGraphParseError::UnknownError(err_code),
1304        }
1305    }
1306}
1307
1308/// Errors from allocating an output format context
1309/// (`avformat_alloc_output_context2`).
1310#[derive(thiserror::Error, Debug)]
1311#[non_exhaustive]
1312pub enum AllocOutputContextError {
1313    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1314    #[error("Memory allocation error")]
1315    OutOfMemory,
1316
1317    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1318    #[error("Invalid argument provided")]
1319    InvalidArgument,
1320
1321    /// The file or stream does not exist (`AVERROR(ENOENT)`).
1322    #[error("File or stream not found")]
1323    NotFound,
1324
1325    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1326    #[error("I/O error occurred while allocating the output context")]
1327    IOError,
1328
1329    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1330    #[error("Pipe error, possibly the stream or data connection was broken")]
1331    PipeError,
1332
1333    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1334    #[error("Invalid file descriptor")]
1335    BadFileDescriptor,
1336
1337    /// The functionality or output format is not supported by the linked
1338    /// FFmpeg build (`AVERROR(ENOSYS)`).
1339    #[error("Functionality not implemented or unsupported output format")]
1340    NotImplemented,
1341
1342    /// The operation was not permitted (`AVERROR(EPERM)`).
1343    #[error("Operation not permitted to allocate the output context")]
1344    OperationNotPermitted,
1345
1346    /// Permission was denied (`AVERROR(EACCES)`).
1347    #[error("Permission denied while allocating the output context")]
1348    PermissionDenied,
1349
1350    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
1351    #[error("The connection timed out while trying to allocate the output context")]
1352    Timeout,
1353
1354    /// Any other failure; the payload is the raw FFmpeg error code
1355    /// (rendered with `av_err2str` in the message).
1356    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1357    UnknownError(i32),
1358}
1359
1360impl From<i32> for AllocOutputContextError {
1361    fn from(err_code: i32) -> Self {
1362        match err_code {
1363            AVERROR_OUT_OF_MEMORY => AllocOutputContextError::OutOfMemory,
1364            AVERROR_INVALID_ARGUMENT => AllocOutputContextError::InvalidArgument,
1365            AVERROR_NOT_FOUND => AllocOutputContextError::NotFound,
1366            AVERROR_IO_ERROR => AllocOutputContextError::IOError,
1367            AVERROR_PIPE_ERROR => AllocOutputContextError::PipeError,
1368            AVERROR_BAD_FILE_DESCRIPTOR => AllocOutputContextError::BadFileDescriptor,
1369            AVERROR_NOT_IMPLEMENTED => AllocOutputContextError::NotImplemented,
1370            AVERROR_OPERATION_NOT_PERMITTED => AllocOutputContextError::OperationNotPermitted,
1371            AVERROR_PERMISSION_DENIED => AllocOutputContextError::PermissionDenied,
1372            AVERROR_TIMEOUT => AllocOutputContextError::Timeout,
1373            _ => AllocOutputContextError::UnknownError(err_code),
1374        }
1375    }
1376}
1377
1378/// Errors from opening and configuring an output: resolving formats and
1379/// encoders, mapping streams, validating options, and opening the target
1380/// for writing.
1381#[derive(thiserror::Error, Debug)]
1382#[non_exhaustive]
1383pub enum OpenOutputError {
1384    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1385    #[error("Memory allocation error")]
1386    OutOfMemory,
1387
1388    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1389    #[error("Invalid argument provided")]
1390    InvalidArgument,
1391
1392    /// The file or stream does not exist (`AVERROR(ENOENT)`).
1393    #[error("File or stream not found")]
1394    NotFound,
1395
1396    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1397    #[error("I/O error occurred while opening the file or stream")]
1398    IOError,
1399
1400    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1401    #[error("Pipe error, possibly the stream or data connection was broken")]
1402    PipeError,
1403
1404    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1405    #[error("Invalid file descriptor")]
1406    BadFileDescriptor,
1407
1408    /// The functionality or output format is not supported by the linked
1409    /// FFmpeg build (`AVERROR(ENOSYS)`).
1410    #[error("Functionality not implemented or unsupported output format")]
1411    NotImplemented,
1412
1413    /// The operation was not permitted (`AVERROR(EPERM)`).
1414    #[error("Operation not permitted to open the file or stream")]
1415    OperationNotPermitted,
1416
1417    /// Permission was denied (`AVERROR(EACCES)`).
1418    #[error("Permission denied while opening the file or stream")]
1419    PermissionDenied,
1420
1421    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
1422    #[error("The connection timed out while trying to open the file or stream")]
1423    Timeout,
1424
1425    /// No encoder was found for the requested codec
1426    /// (`AVERROR_ENCODER_NOT_FOUND`).
1427    #[error("encoder not found")]
1428    EncoderNotFound,
1429
1430    /// A named encoder could not be opened because the linked FFmpeg build
1431    /// does not provide it — either it was compiled without that encoder
1432    /// (e.g. no `--enable-libx264`) or the name is not a known encoder at all.
1433    /// Unlike the bare [`EncoderNotFound`](Self::EncoderNotFound) errno
1434    /// mapping, this names the encoder so the fix is actionable. `name` is the
1435    /// encoder the caller requested, or the codec the output format guessed
1436    /// when none was set explicitly.
1437    #[error(
1438        "encoder '{name}' is not available in the linked FFmpeg build — link \
1439         an FFmpeg build that provides it (for example one configured with \
1440         --enable-libx264 for libx264), or select a different encoder via \
1441         Output::set_video_codec / set_audio_codec / set_subtitle_codec \
1442         (list what the build provides with codec::get_encoders)"
1443    )]
1444    EncoderUnavailable {
1445        /// The requested encoder name, or the format's guessed default
1446        /// codec when none was set explicitly.
1447        name: String,
1448    },
1449
1450    /// Returned when a stream map specifier matches no streams; the payload
1451    /// is the specifier.
1452    #[error("Stream map '{0}' matches no streams;")]
1453    MatchesNoStreams(String),
1454
1455    /// A stream map combined stream copy with a per-map re-encoding
1456    /// request ([`StreamMap::codec`] / [`StreamMap::codec_opt`]): copied
1457    /// packets never pass through an encoder, so a per-map codec or
1458    /// per-map codec options could never take effect. Raised at `build()`
1459    /// instead of silently ignoring the request (the FFmpeg CLI merely
1460    /// warns about such unused options).
1461    ///
1462    /// [`StreamMap::codec`]: crate::core::context::output::StreamMap::codec
1463    /// [`StreamMap::codec_opt`]: crate::core::context::output::StreamMap::codec_opt
1464    #[error(
1465        "stream map '{spec}' requests stream copy together with {what}; \
1466         stream copy and per-map re-encoding settings are mutually exclusive"
1467    )]
1468    StreamMapCopyConflict {
1469        /// The offending stream map specifier.
1470        spec: String,
1471        /// The per-map re-encoding setting that conflicts with copy.
1472        what: &'static str,
1473    },
1474
1475    /// Returned when an output references an invalid filtergraph link
1476    /// label; the payload is the label.
1477    #[error("Invalid label {0}")]
1478    InvalidLabel(String),
1479
1480    /// Returned when the output ends up with no streams at all.
1481    #[error("not contain any stream")]
1482    NotContainStream,
1483
1484    /// Returned when the format of the frame feeding an output stream is
1485    /// unknown, so encoder parameters cannot be derived from it.
1486    #[error("unknown format of the frame")]
1487    UnknownFrameFormat,
1488
1489    /// Returned when an input URL references a file index that does not
1490    /// exist; fields are the index and the URL.
1491    #[error("Invalid file index {0} in input url: {1}")]
1492    InvalidFileIndexInIntput(usize, String),
1493
1494    /// Any other failure; the payload is the raw FFmpeg error code
1495    /// (rendered with `av_err2str` in the message).
1496    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1497    UnknownError(i32),
1498
1499    /// Returned when the output has no usable destination: neither a URL
1500    /// nor a custom write callback was configured.
1501    #[error("Invalid sink provided")]
1502    InvalidSink,
1503
1504    /// Returned when the output format requires seeking but the custom
1505    /// output sink provides no seek callback.
1506    #[error("No seek callback is provided")]
1507    SeekFunctionMissing,
1508
1509    /// Returned when the requested output format name is unknown to FFmpeg;
1510    /// the payload is the requested name.
1511    #[error("Format '{0}' is unsupported")]
1512    FormatUnsupported(String),
1513
1514    /// Returned when a pixel format name is not recognized; the payload is
1515    /// the name.
1516    #[error("Unknown pixel format: '{0}'")]
1517    UnknownPixelFormat(String),
1518
1519    /// Returned when a sample format name is not recognized; the payload is
1520    /// the name.
1521    #[error("Unknown sample format: '{0}'")]
1522    UnknownSampleFormat(String),
1523
1524    /// A builder option carried an invalid value (e.g. a malformed
1525    /// `set_force_key_frames` spec, an out-of-range `set_io_buffer_size`).
1526    /// Setters store values as given and defer validation to open time, so
1527    /// a bad value surfaces here instead of panicking in the setter or
1528    /// forcing a `Result` into the middle of a builder chain.
1529    #[error("Invalid output option: {0}")]
1530    InvalidOption(String),
1531
1532    /// Returned when reading an attachment file fails; the payload is the
1533    /// path, with the underlying I/O error as the source.
1534    #[error("Failed to read attachment file '{0}'")]
1535    AttachmentRead(String, #[source] io::Error),
1536
1537    /// Returned when an attachment file is empty; the payload is the path.
1538    #[error("Attachment file '{0}' is empty")]
1539    AttachmentEmpty(String),
1540
1541    /// Returned when an attachment file exceeds the size limit; fields are
1542    /// the path, its size in bytes, and the limit in bytes.
1543    #[error("Attachment file '{0}' is too large ({1} bytes, limit {2} bytes)")]
1544    AttachmentTooLarge(String, u64, u64),
1545
1546    /// Returned when an attachment was configured with an empty mimetype;
1547    /// the payload is the file path.
1548    #[error("Attachment mimetype must not be empty (file '{0}')")]
1549    AttachmentEmptyMimetype(String),
1550
1551    /// A per-output video filter ([`Output::set_video_filter`]) was combined
1552    /// with stream copy for the same output's video — either
1553    /// `set_video_codec("copy")` or a copy stream map covering a video
1554    /// stream. Mirrors the FFmpeg CLI error for `-vf` + `-c:v copy`
1555    /// ("Filtering and streamcopy cannot be used together",
1556    /// ffmpeg_mux_init.c streamcopy_init).
1557    ///
1558    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1559    #[error(
1560        "Filtergraph '{0}' was specified, but codec copy was selected for the \
1561         output's video stream. Filtering and streamcopy cannot be used together"
1562    )]
1563    FilterWithStreamCopy(String),
1564
1565    /// A per-output video filter ([`Output::set_video_filter`]) was set on an
1566    /// output whose video stream is fed by a context-level filtergraph
1567    /// (`FfmpegContextBuilder::filter_desc`). Mirrors the FFmpeg CLI error for
1568    /// `-vf` + `-filter_complex` on the same stream (ffmpeg_mux_init.c
1569    /// ost_get_filters: "Simple and complex filtering cannot be used together
1570    /// for the same stream").
1571    ///
1572    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1573    #[error(
1574        "Filtergraph '{0}' was specified for a video stream fed from a \
1575         context-level filtergraph. Simple and complex filtering cannot be \
1576         used together for the same stream"
1577    )]
1578    SimpleAndComplexFilter(String),
1579
1580    /// A per-output simple filtergraph must be one connected linear chain:
1581    /// exactly one video input pad, one video output pad, a single connected
1582    /// component, and a directed path from the input to the output (fftools
1583    /// fg_create_simple's contract plus the topology rules a simple graph
1584    /// implies — a disconnected or unreachable description would encode
1585    /// unrelated frames or hang instead of filtering the stream). The path
1586    /// requirement is structural: the input pad must be wired into the flow
1587    /// that feeds the output pad, while a filter that may discard it at
1588    /// runtime (`streamselect` whose applied `map` selects another input —
1589    /// rewritable mid-stream via `sendcmd`) is accepted, matching the CLI.
1590    /// `reason` names the violated rule. Descriptions that split, merge or
1591    /// source streams belong in the context-level `filter_desc`.
1592    #[error(
1593        "Simple filtergraph '{desc}' is not a single connected chain: {reason}; \
1594         use FfmpegContextBuilder::filter_desc for complex graphs"
1595    )]
1596    SimpleFilterInvalidShape {
1597        /// The offending filtergraph description, as configured.
1598        desc: String,
1599        /// The topology rule the description violates.
1600        reason: String,
1601    },
1602
1603    /// A configured [`Output::set_video_filter`] chain that no re-encoded
1604    /// video stream ended up consuming: the output has no video stream at all
1605    /// (audio-only input, `disable_video()`, or maps that matched no video
1606    /// stream). The ffmpeg CLI silently ignores `-vf` in that situation; the
1607    /// crate refuses instead of dropping configuration on the floor.
1608    ///
1609    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1610    #[error(
1611        "video filter '{0}' was configured, but the output ended up with no \
1612         re-encoded video stream to run it (audio-only input, disable_video(), \
1613         or maps matching no video stream); remove the filter or map a video \
1614         stream"
1615    )]
1616    VideoFilterUnused(String),
1617
1618    /// A per-output simple filtergraph's pads must match the stream's media
1619    /// type (fftools fg_create_simple: "Filtergraph has a %s output, cannot
1620    /// connect it to %s output stream") — e.g. an audio chain like `anull`
1621    /// cannot be attached as a video filter.
1622    ///
1623    /// The media-type labels are static (`"video"`, `"audio"`, ... — the
1624    /// strings fftools prints), which keeps this variant inside `Error`'s
1625    /// 64-byte layout; three owned `String`s would grow every hot-path
1626    /// `Result` in the crate.
1627    #[error(
1628        "Simple filtergraph '{desc}' has a {found} pad, cannot connect it to \
1629         the {expected} stream of this output"
1630    )]
1631    SimpleFilterMediaTypeMismatch {
1632        /// The offending filtergraph description, as configured.
1633        desc: String,
1634        /// The media type of the mismatched pad.
1635        found: &'static str,
1636        /// The media type the output stream requires.
1637        expected: &'static str,
1638    },
1639}
1640
1641impl From<i32> for OpenOutputError {
1642    fn from(err_code: i32) -> Self {
1643        match err_code {
1644            AVERROR_OUT_OF_MEMORY => OpenOutputError::OutOfMemory,
1645            AVERROR_INVALID_ARGUMENT => OpenOutputError::InvalidArgument,
1646            AVERROR_NOT_FOUND => OpenOutputError::NotFound,
1647            AVERROR_IO_ERROR => OpenOutputError::IOError,
1648            AVERROR_PIPE_ERROR => OpenOutputError::PipeError,
1649            AVERROR_BAD_FILE_DESCRIPTOR => OpenOutputError::BadFileDescriptor,
1650            AVERROR_NOT_IMPLEMENTED => OpenOutputError::NotImplemented,
1651            AVERROR_OPERATION_NOT_PERMITTED => OpenOutputError::OperationNotPermitted,
1652            AVERROR_PERMISSION_DENIED => OpenOutputError::PermissionDenied,
1653            AVERROR_TIMEOUT => OpenOutputError::Timeout,
1654            AVERROR_ENCODER_NOT_FOUND => OpenOutputError::EncoderNotFound,
1655            _ => OpenOutputError::UnknownError(err_code),
1656        }
1657    }
1658}
1659
1660/// Errors from enumerating capture devices (cameras, microphones,
1661/// screens).
1662#[derive(thiserror::Error, Debug)]
1663#[non_exhaustive]
1664pub enum FindDevicesError {
1665    /// Returned on macOS when the `AVCaptureDevice` class is not available.
1666    #[error("AVCaptureDevice class not found in macOS")]
1667    AVCaptureDeviceNotFound,
1668
1669    /// Returned when device enumeration for the requested media type is not
1670    /// supported; the payload is the raw `AVMediaType` value.
1671    #[error("current media_type({0}) is not supported")]
1672    MediaTypeSupported(i32),
1673    /// Returned when device enumeration is not supported on the current
1674    /// operating system.
1675    #[error("current OS is not supported")]
1676    OsNotSupported,
1677    /// Returned when a device description could not be converted to a UTF-8
1678    /// string.
1679    #[error("device_description can not to string")]
1680    UTF8Error,
1681
1682    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1683    #[error("Memory allocation error")]
1684    OutOfMemory,
1685    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1686    #[error("Invalid argument provided")]
1687    InvalidArgument,
1688    /// The device or stream does not exist (`AVERROR(ENOENT)`).
1689    #[error("Device or stream not found")]
1690    NotFound,
1691    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1692    #[error("I/O error occurred while accessing the device or stream")]
1693    IOError,
1694    /// The operation was not permitted (`AVERROR(EPERM)`).
1695    #[error("Operation not permitted for this device or stream")]
1696    OperationNotPermitted,
1697    /// Permission was denied (`AVERROR(EACCES)`).
1698    #[error("Permission denied while accessing the device or stream")]
1699    PermissionDenied,
1700    /// The functionality is not supported by the linked FFmpeg build
1701    /// (`AVERROR(ENOSYS)`).
1702    #[error("This functionality is not implemented")]
1703    NotImplemented,
1704    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1705    #[error("Bad file descriptor")]
1706    BadFileDescriptor,
1707    /// Any other failure; the payload is the raw FFmpeg error code
1708    /// (rendered with `av_err2str` in the message).
1709    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1710    UnknownError(i32),
1711}
1712
1713impl From<i32> for FindDevicesError {
1714    fn from(err_code: i32) -> Self {
1715        match err_code {
1716            AVERROR_OUT_OF_MEMORY => FindDevicesError::OutOfMemory,
1717            AVERROR_INVALID_ARGUMENT => FindDevicesError::InvalidArgument,
1718            AVERROR_NOT_FOUND => FindDevicesError::NotFound,
1719            AVERROR_IO_ERROR => FindDevicesError::IOError,
1720            AVERROR_OPERATION_NOT_PERMITTED => FindDevicesError::OperationNotPermitted,
1721            AVERROR_PERMISSION_DENIED => FindDevicesError::PermissionDenied,
1722            AVERROR_NOT_IMPLEMENTED => FindDevicesError::NotImplemented,
1723            AVERROR_BAD_FILE_DESCRIPTOR => FindDevicesError::BadFileDescriptor,
1724            _ => FindDevicesError::UnknownError(err_code),
1725        }
1726    }
1727}
1728
1729/// Errors from writing the output container header
1730/// (`avformat_write_header`), carried by
1731/// [`MuxingOperationError::WriteHeader`].
1732#[derive(thiserror::Error, Debug)]
1733#[non_exhaustive]
1734pub enum WriteHeaderError {
1735    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1736    #[error("Memory allocation error")]
1737    OutOfMemory,
1738
1739    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1740    #[error("Invalid argument provided")]
1741    InvalidArgument,
1742
1743    /// The file or stream does not exist (`AVERROR(ENOENT)`).
1744    #[error("File or stream not found")]
1745    NotFound,
1746
1747    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1748    #[error("I/O error occurred while writing the header")]
1749    IOError,
1750
1751    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1752    #[error("Pipe error, possibly the stream or data connection was broken")]
1753    PipeError,
1754
1755    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1756    #[error("Invalid file descriptor")]
1757    BadFileDescriptor,
1758
1759    /// The functionality or output format is not supported by the linked
1760    /// FFmpeg build (`AVERROR(ENOSYS)`).
1761    #[error("Functionality not implemented or unsupported output format")]
1762    NotImplemented,
1763
1764    /// The operation was not permitted (`AVERROR(EPERM)`).
1765    #[error("Operation not permitted to write the header")]
1766    OperationNotPermitted,
1767
1768    /// Permission was denied (`AVERROR(EACCES)`).
1769    #[error("Permission denied while writing the header")]
1770    PermissionDenied,
1771
1772    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
1773    #[error("The connection timed out while trying to write the header")]
1774    Timeout,
1775
1776    /// Any other failure; the payload is the raw FFmpeg error code
1777    /// (rendered with `av_err2str` in the message).
1778    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1779    UnknownError(i32),
1780}
1781
1782impl From<i32> for WriteHeaderError {
1783    fn from(err_code: i32) -> Self {
1784        match err_code {
1785            AVERROR_OUT_OF_MEMORY => WriteHeaderError::OutOfMemory,
1786            AVERROR_INVALID_ARGUMENT => WriteHeaderError::InvalidArgument,
1787            AVERROR_NOT_FOUND => WriteHeaderError::NotFound,
1788            AVERROR_IO_ERROR => WriteHeaderError::IOError,
1789            AVERROR_PIPE_ERROR => WriteHeaderError::PipeError,
1790            AVERROR_BAD_FILE_DESCRIPTOR => WriteHeaderError::BadFileDescriptor,
1791            AVERROR_NOT_IMPLEMENTED => WriteHeaderError::NotImplemented,
1792            AVERROR_OPERATION_NOT_PERMITTED => WriteHeaderError::OperationNotPermitted,
1793            AVERROR_PERMISSION_DENIED => WriteHeaderError::PermissionDenied,
1794            AVERROR_TIMEOUT => WriteHeaderError::Timeout,
1795            _ => WriteHeaderError::UnknownError(err_code),
1796        }
1797    }
1798}
1799
1800/// Errors from encoding a subtitle (`avcodec_encode_subtitle`).
1801#[derive(thiserror::Error, Debug)]
1802#[non_exhaustive]
1803pub enum EncodeSubtitleError {
1804    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1805    #[error("Memory allocation error while encoding subtitle")]
1806    OutOfMemory,
1807
1808    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1809    #[error("Invalid argument provided for subtitle encoding")]
1810    InvalidArgument,
1811
1812    /// The operation was not permitted (`AVERROR(EPERM)`).
1813    #[error("Operation not permitted while encoding subtitle")]
1814    OperationNotPermitted,
1815
1816    /// Subtitle encoding is not supported by the linked FFmpeg build
1817    /// (`AVERROR(ENOSYS)`).
1818    #[error("The encoding functionality is not implemented or unsupported")]
1819    NotImplemented,
1820
1821    /// The encoder is temporarily unable to accept input
1822    /// (`AVERROR(EAGAIN)`); retry later.
1823    #[error("Encoder temporarily unable to process, please retry")]
1824    TryAgain,
1825
1826    /// Any other failure; the payload is the raw FFmpeg error code.
1827    #[error("Subtitle encoding failed with unknown error. ret: {0}")]
1828    UnknownError(i32),
1829}
1830
1831impl From<i32> for EncodeSubtitleError {
1832    fn from(err_code: i32) -> Self {
1833        match err_code {
1834            AVERROR_OUT_OF_MEMORY => EncodeSubtitleError::OutOfMemory,
1835            AVERROR_INVALID_ARGUMENT => EncodeSubtitleError::InvalidArgument,
1836            AVERROR_OPERATION_NOT_PERMITTED => EncodeSubtitleError::OperationNotPermitted,
1837            AVERROR_NOT_IMPLEMENTED => EncodeSubtitleError::NotImplemented,
1838            AVERROR_AGAIN => EncodeSubtitleError::TryAgain,
1839            _ => EncodeSubtitleError::UnknownError(err_code),
1840        }
1841    }
1842}
1843
1844/// Errors from allocating an `AVPacket`.
1845#[derive(thiserror::Error, Debug)]
1846#[non_exhaustive]
1847pub enum AllocPacketError {
1848    /// Packet allocation failed (`av_packet_alloc` returned no packet).
1849    #[error("Memory allocation error while alloc packet")]
1850    OutOfMemory,
1851}
1852
1853/// Errors from allocating an `AVFrame`.
1854#[derive(thiserror::Error, Debug)]
1855#[non_exhaustive]
1856pub enum AllocFrameError {
1857    /// Frame allocation failed (`av_frame_alloc` returned no frame).
1858    #[error("Memory allocation error while alloc frame")]
1859    OutOfMemory,
1860}
1861
1862/// Errors from [`make_frame_writable`], the safe wrapper over FFmpeg's
1863/// `av_frame_make_writable`: ensuring exclusive ownership of a frame's data
1864/// buffers may allocate new buffers and copy into them, and that underlying
1865/// call can fail. Common AVERROR codes map to named variants; anything else
1866/// carries the raw code.
1867///
1868/// [`make_frame_writable`]: crate::util::ffmpeg_utils::make_frame_writable
1869#[derive(thiserror::Error, Debug)]
1870#[non_exhaustive]
1871pub enum FrameWritableError {
1872    /// Allocating or copying the frame's data buffers failed
1873    /// (`AVERROR(ENOMEM)`).
1874    #[error("Memory allocation error while copying frame data")]
1875    OutOfMemory,
1876
1877    /// FFmpeg rejected the frame as invalid (`AVERROR(EINVAL)`).
1878    #[error("Invalid argument provided")]
1879    InvalidArgument,
1880
1881    /// Any other failure; the payload is the raw FFmpeg error code
1882    /// (rendered with `av_err2str` in the message).
1883    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1884    UnknownError(i32),
1885}
1886
1887impl From<i32> for FrameWritableError {
1888    fn from(err_code: i32) -> Self {
1889        match err_code {
1890            AVERROR_OUT_OF_MEMORY => FrameWritableError::OutOfMemory,
1891            AVERROR_INVALID_ARGUMENT => FrameWritableError::InvalidArgument,
1892            _ => FrameWritableError::UnknownError(err_code),
1893        }
1894    }
1895}
1896
1897/// FFmpeg-level errors during muxing, carried by [`MuxingOperationError`].
1898#[derive(thiserror::Error, Debug)]
1899#[non_exhaustive]
1900pub enum MuxingError {
1901    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1902    #[error("Memory allocation error")]
1903    OutOfMemory,
1904
1905    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1906    #[error("Invalid argument provided")]
1907    InvalidArgument,
1908
1909    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1910    #[error("I/O error occurred during muxing")]
1911    IOError,
1912
1913    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1914    #[error("Broken pipe during muxing")]
1915    PipeError,
1916
1917    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1918    #[error("Bad file descriptor encountered")]
1919    BadFileDescriptor,
1920
1921    /// The functionality is not supported by the linked FFmpeg build
1922    /// (`AVERROR(ENOSYS)`).
1923    #[error("Functionality not implemented or unsupported")]
1924    NotImplemented,
1925
1926    /// The operation was not permitted (`AVERROR(EPERM)`).
1927    #[error("Operation not permitted")]
1928    OperationNotPermitted,
1929
1930    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
1931    #[error("Resource temporarily unavailable")]
1932    TryAgain,
1933
1934    /// Any other failure; the payload is the raw FFmpeg error code
1935    /// (rendered with `av_err2str` in the message).
1936    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1937    UnknownError(i32),
1938}
1939
1940impl From<i32> for MuxingError {
1941    fn from(err_code: i32) -> Self {
1942        match err_code {
1943            AVERROR_OUT_OF_MEMORY => MuxingError::OutOfMemory,
1944            AVERROR_INVALID_ARGUMENT => MuxingError::InvalidArgument,
1945            AVERROR_IO_ERROR => MuxingError::IOError,
1946            AVERROR_PIPE_ERROR => MuxingError::PipeError,
1947            AVERROR_BAD_FILE_DESCRIPTOR => MuxingError::BadFileDescriptor,
1948            AVERROR_NOT_IMPLEMENTED => MuxingError::NotImplemented,
1949            AVERROR_OPERATION_NOT_PERMITTED => MuxingError::OperationNotPermitted,
1950            AVERROR_AGAIN => MuxingError::TryAgain,
1951            _ => MuxingError::UnknownError(err_code),
1952        }
1953    }
1954}
1955
1956/// FFmpeg-level errors while opening an encoder, carried by
1957/// [`OpenEncoderOperationError`].
1958#[derive(thiserror::Error, Debug)]
1959#[non_exhaustive]
1960pub enum OpenEncoderError {
1961    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1962    #[error("Memory allocation error occurred during encoder initialization")]
1963    OutOfMemory,
1964
1965    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1966    #[error("Invalid argument provided to encoder")]
1967    InvalidArgument,
1968
1969    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1970    #[error("I/O error occurred while opening encoder")]
1971    IOError,
1972
1973    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1974    #[error("Broken pipe encountered during encoder initialization")]
1975    PipeError,
1976
1977    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1978    #[error("Bad file descriptor used in encoder")]
1979    BadFileDescriptor,
1980
1981    /// The functionality is not supported by the linked FFmpeg build
1982    /// (`AVERROR(ENOSYS)`).
1983    #[error("Encoder functionality not implemented or unsupported")]
1984    NotImplemented,
1985
1986    /// The operation was not permitted (`AVERROR(EPERM)`).
1987    #[error("Operation not permitted while configuring encoder")]
1988    OperationNotPermitted,
1989
1990    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
1991    #[error("Resource temporarily unavailable during encoder setup")]
1992    TryAgain,
1993
1994    /// Any other failure; the payload is the raw FFmpeg error code.
1995    #[error("An unknown error occurred in encoder setup. ret:{0}")]
1996    UnknownError(i32),
1997}
1998
1999impl From<i32> for OpenEncoderError {
2000    fn from(err_code: i32) -> Self {
2001        match err_code {
2002            AVERROR_OUT_OF_MEMORY => OpenEncoderError::OutOfMemory,
2003            AVERROR_INVALID_ARGUMENT => OpenEncoderError::InvalidArgument,
2004            AVERROR_IO_ERROR => OpenEncoderError::IOError,
2005            AVERROR_PIPE_ERROR => OpenEncoderError::PipeError,
2006            AVERROR_BAD_FILE_DESCRIPTOR => OpenEncoderError::BadFileDescriptor,
2007            AVERROR_NOT_IMPLEMENTED => OpenEncoderError::NotImplemented,
2008            AVERROR_OPERATION_NOT_PERMITTED => OpenEncoderError::OperationNotPermitted,
2009            AVERROR_AGAIN => OpenEncoderError::TryAgain,
2010            _ => OpenEncoderError::UnknownError(err_code),
2011        }
2012    }
2013}
2014
2015/// FFmpeg-level errors during encoding, carried by
2016/// [`EncodingOperationError`].
2017#[derive(thiserror::Error, Debug)]
2018#[non_exhaustive]
2019pub enum EncodingError {
2020    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2021    #[error("Memory allocation error during encoding")]
2022    OutOfMemory,
2023
2024    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2025    #[error("Invalid argument provided to encoder")]
2026    InvalidArgument,
2027
2028    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2029    #[error("I/O error occurred during encoding")]
2030    IOError,
2031
2032    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2033    #[error("Broken pipe encountered during encoding")]
2034    PipeError,
2035
2036    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2037    #[error("Bad file descriptor encountered during encoding")]
2038    BadFileDescriptor,
2039
2040    /// The functionality is not supported by the linked FFmpeg build
2041    /// (`AVERROR(ENOSYS)`).
2042    #[error("Functionality not implemented or unsupported encoding feature")]
2043    NotImplemented,
2044
2045    /// The operation was not permitted (`AVERROR(EPERM)`).
2046    #[error("Operation not permitted for encoder")]
2047    OperationNotPermitted,
2048
2049    /// The encoder is temporarily unable to accept or produce data
2050    /// (`AVERROR(EAGAIN)`).
2051    #[error("Resource temporarily unavailable, try again later")]
2052    TryAgain,
2053
2054    /// The encoder reached end of stream; no more packets will be produced
2055    /// (`AVERROR_EOF`).
2056    #[error("End of stream reached or no more frames to encode")]
2057    EndOfStream,
2058
2059    /// Any other failure; the payload is the raw FFmpeg error code.
2060    #[error("An unknown error occurred during encoding. ret: {0}")]
2061    UnknownError(i32),
2062}
2063
2064impl From<i32> for EncodingError {
2065    fn from(err_code: i32) -> Self {
2066        match err_code {
2067            AVERROR_OUT_OF_MEMORY => EncodingError::OutOfMemory,
2068            AVERROR_INVALID_ARGUMENT => EncodingError::InvalidArgument,
2069            AVERROR_IO_ERROR => EncodingError::IOError,
2070            AVERROR_PIPE_ERROR => EncodingError::PipeError,
2071            AVERROR_BAD_FILE_DESCRIPTOR => EncodingError::BadFileDescriptor,
2072            AVERROR_NOT_IMPLEMENTED => EncodingError::NotImplemented,
2073            AVERROR_OPERATION_NOT_PERMITTED => EncodingError::OperationNotPermitted,
2074            AVERROR_AGAIN => EncodingError::TryAgain,
2075            AVERROR_EOF => EncodingError::EndOfStream,
2076            _ => EncodingError::UnknownError(err_code),
2077        }
2078    }
2079}
2080
2081/// FFmpeg-level errors during filtergraph processing, carried by
2082/// [`FilterGraphOperationError`].
2083#[derive(thiserror::Error, Debug)]
2084#[non_exhaustive]
2085pub enum FilterGraphError {
2086    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2087    #[error("Memory allocation error during filter graph processing")]
2088    OutOfMemory,
2089
2090    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2091    #[error("Invalid argument provided to filter graph processing")]
2092    InvalidArgument,
2093
2094    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2095    #[error("I/O error occurred during filter graph processing")]
2096    IOError,
2097
2098    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2099    #[error("Broken pipe during filter graph processing")]
2100    PipeError,
2101
2102    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2103    #[error("Bad file descriptor encountered during filter graph processing")]
2104    BadFileDescriptor,
2105
2106    /// The functionality is not supported by the linked FFmpeg build
2107    /// (`AVERROR(ENOSYS)`).
2108    #[error("Functionality not implemented or unsupported during filter graph processing")]
2109    NotImplemented,
2110
2111    /// The operation was not permitted (`AVERROR(EPERM)`).
2112    #[error("Operation not permitted during filter graph processing")]
2113    OperationNotPermitted,
2114
2115    /// The graph is temporarily unable to accept or produce data
2116    /// (`AVERROR(EAGAIN)`).
2117    #[error("Resource temporarily unavailable during filter graph processing")]
2118    TryAgain,
2119
2120    /// The filtergraph reached end of stream (`AVERROR_EOF`).
2121    #[error("EOF")]
2122    EOF,
2123
2124    /// Any other failure; the payload is the raw FFmpeg error code.
2125    #[error("An unknown error occurred during filter graph processing. ret:{0}")]
2126    UnknownError(i32),
2127}
2128
2129impl From<i32> for FilterGraphError {
2130    fn from(err_code: i32) -> Self {
2131        match err_code {
2132            AVERROR_OUT_OF_MEMORY => FilterGraphError::OutOfMemory,
2133            AVERROR_INVALID_ARGUMENT => FilterGraphError::InvalidArgument,
2134            AVERROR_IO_ERROR => FilterGraphError::IOError,
2135            AVERROR_PIPE_ERROR => FilterGraphError::PipeError,
2136            AVERROR_BAD_FILE_DESCRIPTOR => FilterGraphError::BadFileDescriptor,
2137            AVERROR_NOT_IMPLEMENTED => FilterGraphError::NotImplemented,
2138            AVERROR_OPERATION_NOT_PERMITTED => FilterGraphError::OperationNotPermitted,
2139            AVERROR_AGAIN => FilterGraphError::TryAgain,
2140            AVERROR_EOF => FilterGraphError::EOF,
2141            _ => FilterGraphError::UnknownError(err_code),
2142        }
2143    }
2144}
2145
2146/// FFmpeg-level errors while opening a decoder, carried by
2147/// [`OpenDecoderOperationError`].
2148#[derive(thiserror::Error, Debug)]
2149#[non_exhaustive]
2150pub enum OpenDecoderError {
2151    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2152    #[error("Memory allocation error during decoder initialization")]
2153    OutOfMemory,
2154
2155    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2156    #[error("Invalid argument provided during decoder initialization")]
2157    InvalidArgument,
2158
2159    /// The functionality is not supported by the linked FFmpeg build
2160    /// (`AVERROR(ENOSYS)`).
2161    #[error("Functionality not implemented or unsupported during decoder initialization")]
2162    NotImplemented,
2163
2164    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
2165    #[error("Resource temporarily unavailable during decoder initialization")]
2166    TryAgain,
2167
2168    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2169    #[error("I/O error occurred during decoder initialization")]
2170    IOError,
2171
2172    /// Any other failure; the payload is the raw FFmpeg error code.
2173    #[error("An unknown error occurred during decoder initialization: {0}")]
2174    UnknownError(i32),
2175}
2176
2177impl From<i32> for OpenDecoderError {
2178    fn from(err_code: i32) -> Self {
2179        match err_code {
2180            AVERROR_OUT_OF_MEMORY => OpenDecoderError::OutOfMemory,
2181            AVERROR_INVALID_ARGUMENT => OpenDecoderError::InvalidArgument,
2182            AVERROR_NOT_IMPLEMENTED => OpenDecoderError::NotImplemented,
2183            AVERROR_AGAIN => OpenDecoderError::TryAgain,
2184            AVERROR_IO_ERROR => OpenDecoderError::IOError,
2185            _ => OpenDecoderError::UnknownError(err_code),
2186        }
2187    }
2188}
2189
2190/// FFmpeg-level errors during decoding, carried by
2191/// [`DecodingOperationError`].
2192#[derive(thiserror::Error, Debug)]
2193#[non_exhaustive]
2194pub enum DecodingError {
2195    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2196    #[error("Memory allocation error")]
2197    OutOfMemory,
2198
2199    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2200    #[error("Invalid argument provided")]
2201    InvalidArgument,
2202
2203    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2204    #[error("I/O error occurred during decoding")]
2205    IOError,
2206
2207    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
2208    #[error("Timeout occurred during decoding")]
2209    Timeout,
2210
2211    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2212    #[error("Broken pipe encountered during decoding")]
2213    PipeError,
2214
2215    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2216    #[error("Bad file descriptor encountered during decoding")]
2217    BadFileDescriptor,
2218
2219    /// The functionality or format is not supported by the linked FFmpeg
2220    /// build (`AVERROR(ENOSYS)`).
2221    #[error("Unsupported functionality or format encountered")]
2222    NotImplemented,
2223
2224    /// The operation was not permitted (`AVERROR(EPERM)`).
2225    #[error("Operation not permitted")]
2226    OperationNotPermitted,
2227
2228    /// The decoder is temporarily unable to accept or produce data
2229    /// (`AVERROR(EAGAIN)`).
2230    #[error("Resource temporarily unavailable")]
2231    TryAgain,
2232
2233    /// Any other failure; the payload is the raw FFmpeg error code.
2234    #[error("An unknown decoding error occurred. ret:{0}")]
2235    UnknownError(i32),
2236}
2237
2238impl From<i32> for DecodingError {
2239    fn from(err_code: i32) -> Self {
2240        match err_code {
2241            AVERROR_OUT_OF_MEMORY => DecodingError::OutOfMemory,
2242            AVERROR_INVALID_ARGUMENT => DecodingError::InvalidArgument,
2243            AVERROR_IO_ERROR => DecodingError::IOError,
2244            AVERROR_TIMEOUT => DecodingError::Timeout,
2245            AVERROR_PIPE_ERROR => DecodingError::PipeError,
2246            AVERROR_BAD_FILE_DESCRIPTOR => DecodingError::BadFileDescriptor,
2247            AVERROR_NOT_IMPLEMENTED => DecodingError::NotImplemented,
2248            AVERROR_OPERATION_NOT_PERMITTED => DecodingError::OperationNotPermitted,
2249            AVERROR_AGAIN => DecodingError::TryAgain,
2250            _ => DecodingError::UnknownError(err_code),
2251        }
2252    }
2253}
2254
2255/// Errors from resolving a decoder.
2256#[derive(thiserror::Error, Debug)]
2257#[non_exhaustive]
2258pub enum DecoderError {
2259    /// Returned when a decoder requested by name is not provided by the
2260    /// linked FFmpeg build; the payload is the requested name.
2261    #[error("decoder '{0}' not found")]
2262    NotFound(String),
2263}
2264
2265/// FFmpeg-level errors during demuxing, carried by
2266/// [`DemuxingOperationError`] and [`PacketScannerError`].
2267#[derive(thiserror::Error, Debug)]
2268#[non_exhaustive]
2269pub enum DemuxingError {
2270    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2271    #[error("Memory allocation error")]
2272    OutOfMemory,
2273
2274    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2275    #[error("Invalid argument provided")]
2276    InvalidArgument,
2277
2278    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2279    #[error("I/O error occurred during demuxing")]
2280    IOError,
2281
2282    /// End of file was reached during demuxing (`AVERROR_EOF`).
2283    #[error("End of file reached during demuxing")]
2284    EndOfFile,
2285
2286    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
2287    #[error("Resource temporarily unavailable")]
2288    TryAgain,
2289
2290    /// The functionality is not supported by the linked FFmpeg build
2291    /// (`AVERROR(ENOSYS)`).
2292    #[error("Functionality not implemented or unsupported")]
2293    NotImplemented,
2294
2295    /// The operation was not permitted (`AVERROR(EPERM)`).
2296    #[error("Operation not permitted")]
2297    OperationNotPermitted,
2298
2299    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2300    #[error("Bad file descriptor encountered")]
2301    BadFileDescriptor,
2302
2303    /// The input contains invalid or corrupted data
2304    /// (`AVERROR_INVALIDDATA`).
2305    #[error("Invalid data found when processing input")]
2306    InvalidData,
2307
2308    /// Any other failure; the payload is the raw FFmpeg error code
2309    /// (rendered with `av_err2str` in the message).
2310    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
2311    UnknownError(i32),
2312}
2313
2314impl From<i32> for DemuxingError {
2315    fn from(err_code: i32) -> Self {
2316        match err_code {
2317            AVERROR_OUT_OF_MEMORY => DemuxingError::OutOfMemory,
2318            AVERROR_INVALID_ARGUMENT => DemuxingError::InvalidArgument,
2319            AVERROR_IO_ERROR => DemuxingError::IOError,
2320            AVERROR_EOF => DemuxingError::EndOfFile,
2321            AVERROR_AGAIN => DemuxingError::TryAgain,
2322            AVERROR_NOT_IMPLEMENTED => DemuxingError::NotImplemented,
2323            AVERROR_OPERATION_NOT_PERMITTED => DemuxingError::OperationNotPermitted,
2324            AVERROR_BAD_FILE_DESCRIPTOR => DemuxingError::BadFileDescriptor,
2325            AVERROR_INVALIDDATA => DemuxingError::InvalidData,
2326            _ => DemuxingError::UnknownError(err_code),
2327        }
2328    }
2329}
2330
2331/// Errors that can occur during packet scanning operations.
2332#[derive(thiserror::Error, Debug)]
2333#[non_exhaustive]
2334pub enum PacketScannerError {
2335    /// Failed to seek to the requested timestamp.
2336    #[error("while seeking: {0}")]
2337    SeekError(DemuxingError),
2338
2339    /// Failed to read the next packet from the demuxer.
2340    #[error("while reading packet: {0}")]
2341    ReadError(DemuxingError),
2342}
2343
2344#[cfg(test)]
2345mod tests {
2346    // Regression: FrameSourceThreadExited is payload-less, but the manual
2347    // PartialEq whitelist omitted it, so the variant compared unequal to
2348    // itself — breaking the impl's documented "structural equality for
2349    // payload-less variants" contract.
2350    #[test]
2351    fn frame_source_thread_exited_equals_itself() {
2352        use super::Error;
2353        assert_eq!(
2354            Error::FrameSourceThreadExited,
2355            Error::FrameSourceThreadExited
2356        );
2357        assert_ne!(Error::FrameSourceThreadExited, Error::NotStarted);
2358    }
2359
2360    // Regression: FilterGraphParseError declares PermissionDenied and NotSocket,
2361    // but its From<i32> once omitted them, so an EACCES/ENOTSOCK filtergraph
2362    // error degraded to UnknownError and the two declared variants were
2363    // unreachable. Map the codes to the variants the enum already exposes.
2364    #[test]
2365    fn filter_graph_parse_error_maps_permission_and_socket_codes() {
2366        use super::{FilterGraphParseError, AVERROR_NOT_SOCKET, AVERROR_PERMISSION_DENIED};
2367        assert!(matches!(
2368            FilterGraphParseError::from(AVERROR_PERMISSION_DENIED),
2369            FilterGraphParseError::PermissionDenied
2370        ));
2371        assert!(matches!(
2372            FilterGraphParseError::from(AVERROR_NOT_SOCKET),
2373            FilterGraphParseError::NotSocket
2374        ));
2375    }
2376
2377    // make_frame_writable's failure is typed like every other AVERROR-coded
2378    // failure in this file: common codes map to named variants, the rest keep
2379    // the raw code. Pin the mapping and the user-facing Display string.
2380    #[test]
2381    fn frame_writable_error_maps_codes_and_pins_display() {
2382        use super::{Error, FrameWritableError, AVERROR_INVALID_ARGUMENT, AVERROR_OUT_OF_MEMORY};
2383        assert!(matches!(
2384            FrameWritableError::from(AVERROR_OUT_OF_MEMORY),
2385            FrameWritableError::OutOfMemory
2386        ));
2387        assert!(matches!(
2388            FrameWritableError::from(AVERROR_INVALID_ARGUMENT),
2389            FrameWritableError::InvalidArgument
2390        ));
2391        assert!(matches!(
2392            FrameWritableError::from(-99),
2393            FrameWritableError::UnknownError(-99)
2394        ));
2395        let err = Error::from(FrameWritableError::from(AVERROR_OUT_OF_MEMORY));
2396        assert_eq!(
2397            err.to_string(),
2398            "Frame writable error: Memory allocation error while copying frame data"
2399        );
2400    }
2401
2402    // The deprecated OpenGL filter's constructor failures are typed like the
2403    // wgpu successor's: they carry OpenGLFilterError and convert into
2404    // Error::OpenGLFilter. Pin the user-facing Display strings.
2405    #[cfg(feature = "opengl")]
2406    #[test]
2407    fn opengl_filter_error_pins_display() {
2408        use super::{Error, OpenGLFilterError};
2409        let err = Error::from(OpenGLFilterError::InvalidOption(
2410            "fragment shader must declare 'in vec2 TexCoord;'".to_string(),
2411        ));
2412        assert_eq!(
2413            err.to_string(),
2414            "OpenGL filter error: invalid OpenGL filter option: \
2415             fragment shader must declare 'in vec2 TexCoord;'"
2416        );
2417        let err = Error::from(OpenGLFilterError::ContextCreation(
2418            "Failed to create Surfman connection".to_string(),
2419        ));
2420        assert_eq!(
2421            err.to_string(),
2422            "OpenGL filter error: OpenGL context creation failed: \
2423             Failed to create Surfman connection"
2424        );
2425    }
2426}