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 decoded frame could not be analyzed (unsupported pixel format,
282    /// interlaced fields, or a hardware surface). This is a runtime frame
283    /// condition, not a recipe/config error — those stay
284    /// [`InvalidRecipeArg`](Error::InvalidRecipeArg). Boxed so [`Error`]
285    /// stays within the 64-byte layout contract.
286    #[error("analysis frame error: {0}")]
287    AnalysisFrame(Box<str>),
288
289    /// HLS ladder video-encoder selection failed. Boxed so the payload stays
290    /// inside the crate-wide [`Error`] size contract.
291    #[error("{0}")]
292    HlsEncoderSelection(Box<HlsEncoderSelectionError>),
293
294    /// HLS master playlist write failed after every rendition transcode
295    /// succeeded. Boxed so the payload stays inside the crate-wide [`Error`]
296    /// size contract.
297    #[error("{0}")]
298    HlsMasterWrite(Box<HlsMasterWriteError>),
299
300    /// A container-info query was called with an out-of-range index.
301    #[error("Container info error: {0}")]
302    ContainerInfo(#[from] ContainerInfoError),
303
304    /// A frame-export operation failed.
305    #[error("Frame export error: {0}")]
306    FrameExport(#[from] crate::core::frame_export::FrameExportError),
307
308    /// Building or opening a video writer failed.
309    #[error("Video writer error: {0}")]
310    Writer(#[from] crate::core::writer::WriterError),
311
312    /// Pushing a frame into a video writer failed.
313    #[error("Video writer push error: {0}")]
314    Push(#[from] crate::core::writer::PushError),
315
316    /// Returned when a frame-source input's worker thread failed to start.
317    #[error("Frame source thread failed to start")]
318    FrameSourceThreadExited,
319
320    /// A packet-sink output failed (see [`PacketSinkError`]).
321    #[error("Packet sink error: {0}")]
322    PacketSink(#[from] PacketSinkError),
323
324    /// CLI-compat pipelines only: a `-vf` command was lowered onto an input
325    /// whose OPENED demuxer does not carry exactly one video stream. The
326    /// check runs on the demuxer instance the pipeline actually executes
327    /// with (no separate probe opening, no TOCTOU window). The facade maps
328    /// this to its public `AmbiguousFilterSource` diagnostic.
329    #[cfg(feature = "cli")]
330    #[error("the per-output video filter requires exactly one video stream in the input; the opened input has {video_streams}")]
331    AmbiguousVideoSource {
332        /// Number of video streams carried by the opened input.
333        video_streams: usize,
334    },
335
336    /// Strict AVOption handling (CLI-compat pipelines): an option the caller
337    /// supplied was not consumed by the component it targeted. The default
338    /// builder path only WARNS about such leftovers; pipelines built through
339    /// the `cli` feature's entry points fail instead, mirroring fftools'
340    /// `check_avoptions` abort. Only exists with the `cli` feature — the
341    /// feature-off API surface is unchanged.
342    #[cfg(feature = "cli")]
343    #[error("option '{option}' was not consumed by {site}; CLI-compat strict mode treats leftover AVOptions as errors")]
344    UnconsumedCliOption {
345        /// Human-readable description of the component that should have
346        /// consumed the option (e.g. "the muxer of output 0").
347        site: String,
348        /// The option key that was left unconsumed.
349        option: String,
350    },
351
352    // HTTP_INPUT_ERROR_VARIANT
353    /// Boxed so [`Error`] stays within the 64-byte layout contract.
354    #[cfg(feature = "http-input")]
355    #[error("HTTP input error: {0}")]
356    HttpInput(Box<crate::http_input::HttpInputError>),
357}
358
359// `Error` rides in every hot-path `Result` — the per-frame encoder and filter
360// calls return `Result<(), Error>` / `Result<bool, Error>` — so its size is a
361// layout contract, not an implementation detail: one oversized payload grows
362// every such `Result` crate-wide. 64 bytes is the long-standing layout; keep
363// new payloads inside it (use static labels for fixed vocabulary, or box a
364// genuinely large variant). A const assertion rather than a #[test] so that
365// merely compiling the crate enforces the bound for whichever feature-gated
366// variants that build carries — including feature combinations whose tests
367// are compiled but never run.
368// 72, not 64: on the 1.80 MSRV toolchain the compiler does not yet fold the
369// outer discriminant into the niche of the 64-byte nested enums
370// (`FilterGraphParse` / `FilterGraph` / `PacketSink` payloads), so the same
371// type lays out as 72 bytes there. Modern toolchains fit 64; the companion
372// test below pins that tighter bound where the tests run.
373const _: () = assert!(
374    std::mem::size_of::<Error>() <= 72,
375    "Error grew past its layout contract: shrink the new payload (static labels) or box the variant"
376);
377
378#[cfg(test)]
379mod layout_tests {
380    /// On current stable the niche-folded layout is 64 bytes; a new variant
381    /// that grows it shows up here (every CI test lane) even though the MSRV
382    /// const bound above must stay at the 1.80 figure.
383    #[test]
384    fn error_fits_the_modern_64_byte_layout() {
385        assert!(
386            std::mem::size_of::<super::Error>() <= 64,
387            "Error grew past 64 bytes on a modern toolchain: box the new payload"
388        );
389    }
390}
391
392impl From<HlsEncoderSelectionError> for Error {
393    fn from(err: HlsEncoderSelectionError) -> Self {
394        Error::HlsEncoderSelection(Box::new(err))
395    }
396}
397
398impl From<HlsMasterWriteError> for Error {
399    fn from(err: HlsMasterWriteError) -> Self {
400        Error::HlsMasterWrite(Box::new(err))
401    }
402}
403
404/// Failure to select a video encoder for [`crate::recipes::HlsLadder`].
405///
406/// The historical default (`libx264`) is never silently replaced. Callers
407/// either get this typed error or opt in with
408/// [`HlsLadder::video_codec_auto`](crate::recipes::HlsLadder::video_codec_auto).
409#[derive(Debug, Clone)]
410#[non_exhaustive]
411pub enum HlsEncoderSelectionError {
412    /// [`HlsLadder`](crate::recipes::HlsLadder) was left on its historical
413    /// `libx264` default, and that encoder is not registered in the linked
414    /// FFmpeg build. No fallback was selected.
415    HistoricalDefaultUnavailable {
416        /// Auto-admitted H.264 encoder names that are registered. Presence
417        /// here is not runtime-ready and does not prove HLS alignment.
418        registered_auto_candidates: Vec<String>,
419        /// Other registered H.264 encoders that can only be chosen with
420        /// [`.video_codec(...)`](crate::recipes::HlsLadder::video_codec);
421        /// this recipe does not manage their alignment.
422        registered_explicit_h264_encoders: Vec<String>,
423    },
424    /// [`HlsLadder::video_codec_auto`](crate::recipes::HlsLadder::video_codec_auto)
425    /// ran after `libx264` was unavailable and no candidate could be opened
426    /// for every rendition.
427    AutoSelectionFailed {
428        /// One entry per auto-priority encoder, in selection order.
429        attempts: Vec<HlsEncoderAttempt>,
430    },
431    /// A pinned auto-admitted encoder (`.video_codec("h264_qsv")` and the
432    /// other AUTO_PRIORITY names) failed trial-open (rungs are opened one by
433    /// one; all sessions are held concurrently to prove the ladder's session
434    /// count). No other encoder was tried, and output directories were not
435    /// created.
436    ExplicitOpenFailed {
437        /// Encoder name the caller pinned.
438        encoder: String,
439        /// Width of the rendition that failed to open.
440        width: u32,
441        /// Height of the rendition that failed to open.
442        height: u32,
443        /// Raw FFmpeg `AVERROR` code from `avcodec_open2` (or setup).
444        raw_code: i32,
445        /// `av_strerror` text for [`raw_code`](Self::ExplicitOpenFailed::raw_code).
446        message: String,
447    },
448}
449
450/// One auto-selection attempt recorded in
451/// [`HlsEncoderSelectionError::AutoSelectionFailed`].
452#[derive(Debug, Clone)]
453#[non_exhaustive]
454pub struct HlsEncoderAttempt {
455    /// Encoder name that was considered.
456    pub encoder: String,
457    /// Why this encoder was not used.
458    pub outcome: HlsEncoderAttemptOutcome,
459}
460
461/// Outcome of one auto-selection attempt.
462#[derive(Debug, Clone)]
463#[non_exhaustive]
464pub enum HlsEncoderAttemptOutcome {
465    /// `avcodec_find_encoder_by_name` returned null.
466    NotRegistered,
467    /// Trial `avcodec_open2` failed for a rendition of this ladder.
468    OpenFailed {
469        /// Width of the rendition that failed to open.
470        width: u32,
471        /// Height of the rendition that failed to open.
472        height: u32,
473        /// Raw FFmpeg `AVERROR` code from `avcodec_open2` (or setup).
474        raw_code: i32,
475        /// `av_strerror` text for [`raw_code`](Self::OpenFailed::raw_code).
476        message: String,
477    },
478}
479
480fn format_encoder_name_list(names: &[String]) -> String {
481    if names.is_empty() {
482        "none".to_string()
483    } else {
484        names.join(", ")
485    }
486}
487
488impl std::fmt::Display for HlsEncoderSelectionError {
489    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490        match self {
491            HlsEncoderSelectionError::HistoricalDefaultUnavailable {
492                registered_auto_candidates,
493                registered_explicit_h264_encoders,
494            } => write!(
495                f,
496                "HlsLadder's historical default encoder 'libx264' is not available in the linked \
497                 FFmpeg build. No fallback was selected automatically because encoder quality, \
498                 hardware use, and HLS keyframe behavior differ. For an LGPL-only FFmpeg build, \
499                 opt in to runtime selection with `.video_codec_auto()`, or pin a registered \
500                 encoder with `.video_codec(\"...\")`. Named auto-admitted encoders \
501                 (h264_videotoolbox, h264_nvenc, h264_qsv, libopenh264) use this recipe's \
502                 HLS-safe option set; other explicit names do not. Registered auto candidates \
503                 (runtime readiness and this host's output alignment not verified): {}. \
504                 Other registered H.264 encoders (explicit only; HLS alignment unmanaged): {}. \
505                 List all registered encoders with `codec::get_encoders()`. \
506                 See docs/INSTALL.md#ffmpeg-capability-and-licensing-matrix.",
507                format_encoder_name_list(registered_auto_candidates),
508                format_encoder_name_list(registered_explicit_h264_encoders),
509            ),
510            HlsEncoderSelectionError::AutoSelectionFailed { attempts } => {
511                let tried = attempts
512                    .iter()
513                    .map(|attempt| match &attempt.outcome {
514                        HlsEncoderAttemptOutcome::NotRegistered => {
515                            format!("{} (not registered)", attempt.encoder)
516                        }
517                        HlsEncoderAttemptOutcome::OpenFailed {
518                            width,
519                            height,
520                            message,
521                            ..
522                        } => format!(
523                            "{} ({}x{} encoder open failed: {message})",
524                            attempt.encoder, width, height
525                        ),
526                    })
527                    .collect::<Vec<_>>()
528                    .join(", ");
529                write!(
530                    f,
531                    "HlsLadder could not select a runtime-ready H.264 encoder after 'libx264' was \
532                     unavailable. Tried: {tried}. Enable one of these encoders in the linked \
533                     FFmpeg build, choose one explicitly with `.video_codec(\"...\")`, or see \
534                     docs/INSTALL.md#ffmpeg-capability-and-licensing-matrix."
535                )
536            }
537            HlsEncoderSelectionError::ExplicitOpenFailed {
538                encoder,
539                width,
540                height,
541                message,
542                ..
543            } => write!(
544                f,
545                "HlsLadder could not trial-open pinned encoder '{encoder}' for {width}x{height}: \
546                 {message}. Output directories were not created. Pin a different encoder with \
547                 `.video_codec(\"...\")`, use `.video_codec_auto()`, or see \
548                 docs/INSTALL.md#ffmpeg-capability-and-licensing-matrix."
549            ),
550        }
551    }
552}
553
554impl std::error::Error for HlsEncoderSelectionError {}
555
556/// Master playlist write failed after every HLS rendition transcode succeeded.
557///
558/// The media playlists may already be on disk; only the master file (or the
559/// BANDWIDTH measurement that feeds it) failed. Display always starts with
560/// `transcode succeeded` so operators do not treat this as an encoder miss.
561#[derive(Debug, Clone)]
562#[non_exhaustive]
563pub struct HlsMasterWriteError {
564    /// Master playlist file name the recipe tried to write.
565    pub master_name: String,
566    /// Why the write, or the BANDWIDTH measurement that feeds it, failed.
567    pub detail: String,
568}
569
570impl std::fmt::Display for HlsMasterWriteError {
571    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
572        write!(
573            f,
574            "transcode succeeded but {} was not written: {}",
575            self.master_name, self.detail
576        )
577    }
578}
579
580impl std::error::Error for HlsMasterWriteError {}
581
582/// Builder/open-time validation errors for [`crate::VideoWriter`]. Exported here
583/// (not from the crate root) to mirror the existing `OpenInputError` /
584/// `OpenOutputError` organization; the root surface stays the settled writer
585/// types (the writer itself, its builder, and the push error pair).
586pub use crate::core::writer::WriterError;
587
588#[cfg(feature = "http-input")]
589impl From<crate::http_input::HttpInputError> for Error {
590    fn from(err: crate::http_input::HttpInputError) -> Self {
591        Error::HttpInput(Box::new(err))
592    }
593}
594
595// Hand-written counterpart of the #[from] the sibling variants derive: the
596// error type inherits deprecation from the deprecated `opengl` module, so
597// the conversion is spelled out where the lint can be silenced.
598#[cfg(feature = "opengl")]
599#[allow(deprecated)]
600impl From<OpenGLFilterError> for Error {
601    fn from(err: OpenGLFilterError) -> Self {
602        Error::OpenGLFilter(err)
603    }
604}
605
606/// Errors from the `container_info` queries where the caller asked for an index
607/// outside the container's range. These are caller/argument errors — a bad index
608/// into an otherwise valid container — kept distinct from an open/probe failure
609/// (`OpenInputError` / `FindStreamError`) so retry logic, telemetry, and user
610/// messages can tell "you asked for chapter 5 of a 3-chapter file" apart from
611/// "the file is corrupt or unreadable". Each variant carries the offending
612/// `index` and the container's actual `count`.
613#[derive(thiserror::Error, Debug)]
614#[non_exhaustive]
615pub enum ContainerInfoError {
616    /// Returned when the requested chapter index exceeds the number of
617    /// chapters in the container.
618    #[error("chapter index {index} out of range: the container has {count} chapter(s)")]
619    ChapterIndexOutOfRange {
620        /// The chapter index that was requested.
621        index: usize,
622        /// Number of chapters the container actually has.
623        count: usize,
624    },
625
626    /// Returned when the requested stream index exceeds the number of
627    /// streams in the container.
628    #[error("stream index {index} out of range: the container has {count} stream(s)")]
629    StreamIndexOutOfRange {
630        /// The stream index that was requested.
631        index: usize,
632        /// Number of streams the container actually has.
633        count: usize,
634    },
635}
636
637/// Error type for RTMP streaming operations using StreamBuilder
638#[cfg(feature = "rtmp")]
639#[derive(thiserror::Error, Debug)]
640#[non_exhaustive]
641pub enum StreamError {
642    /// Returned when a required builder parameter was never set; the
643    /// payload is the parameter name (e.g. "address", "stream_key").
644    #[error("missing required parameter: {0}")]
645    MissingParameter(&'static str),
646
647    /// Returned when the configured input path does not point to an
648    /// existing file.
649    #[error("input path is not a valid file: {path}")]
650    InputNotFound {
651        /// The input path that failed validation.
652        path: std::path::PathBuf,
653    },
654
655    /// An underlying ez-ffmpeg error raised while building or running the
656    /// stream.
657    #[error("ffmpeg error: {0}")]
658    Ffmpeg(#[from] crate::error::Error),
659}
660
661impl PartialEq for Error {
662    /// Structural equality for payload-less variants only. Variants carrying
663    /// an inner error compare unequal even to themselves — use matches! on
664    /// the variant when that is what you mean.
665    fn eq(&self, other: &Self) -> bool {
666        use Error::*;
667        match (self, other) {
668            (NotStarted, NotStarted)
669            | (FilterDescUtf8, FilterDescUtf8)
670            | (FilterNameUtf8, FilterNameUtf8)
671            | (FilterZeroOutputs, FilterZeroOutputs)
672            | (FilterZeroInputs, FilterZeroInputs)
673            | (ParseInteger, ParseInteger)
674            | (FrameFilterDstFinished, FrameFilterDstFinished)
675            | (FrameFilterFrameDuplicateFailed, FrameFilterFrameDuplicateFailed)
676            | (FrameFilterThreadExited, FrameFilterThreadExited)
677            | (FrameSourceThreadExited, FrameSourceThreadExited)
678            | (EOF, EOF)
679            | (Exit, Exit)
680            | (Bug, Bug) => true,
681            #[cfg(feature = "rtmp")]
682            (RtmpCreateStream, RtmpCreateStream)
683            | (RtmpRegistrationQueueFull, RtmpRegistrationQueueFull)
684            | (RtmpThreadExited, RtmpThreadExited)
685            | (RtmpStreamClosed, RtmpStreamClosed)
686            | (RtmpServerAlreadyStarted, RtmpServerAlreadyStarted) => true,
687            _ => false,
688        }
689    }
690}
691
692// No Eq impl: variants carrying payloads are not equal to themselves, so
693// the relation is not reflexive and claiming Eq would be a lie.
694
695/// Errors from the demuxer stage while reading packets from an input.
696/// Variants carrying a [`DemuxingError`] embed the mapped FFmpeg error.
697#[derive(thiserror::Error, Debug)]
698#[non_exhaustive]
699pub enum DemuxingOperationError {
700    /// Returned when reading the next packet from the input fails
701    /// (`av_read_frame`).
702    #[error("while reading frame: {0}")]
703    ReadFrameError(DemuxingError),
704
705    /// Returned when creating an additional reference to a demuxed packet
706    /// fails (`av_packet_ref`).
707    #[error("while referencing packet: {0}")]
708    PacketRefError(DemuxingError),
709
710    /// Returned when seeking in the input fails (`avformat_seek_file`).
711    #[error("while seeking file: {0}")]
712    SeekFileError(DemuxingError),
713
714    /// Returned when spawning the demuxer thread fails, so demuxing never
715    /// started.
716    #[error("Thread exited")]
717    ThreadExited,
718}
719
720/// Errors from the decoder stage while turning packets into frames.
721/// Variants carrying a [`DecodingError`] embed the mapped FFmpeg error.
722#[derive(thiserror::Error, Debug)]
723#[non_exhaustive]
724pub enum DecodingOperationError {
725    /// Returned when creating a new reference to a decoded frame fails
726    /// (`av_frame_ref`).
727    #[error("during frame reference creation: {0}")]
728    FrameRefError(DecodingError),
729
730    /// Returned when copying frame metadata fails (`av_frame_copy_props`).
731    #[error("during frame properties copy: {0}")]
732    FrameCopyPropsError(DecodingError),
733
734    /// Returned when decoding a subtitle packet fails
735    /// (`avcodec_decode_subtitle2`).
736    #[error("during subtitle decoding: {0}")]
737    DecodeSubtitleError(DecodingError),
738
739    /// Returned when copying a decoded subtitle for delivery fails.
740    #[error("during subtitle copy: {0}")]
741    CopySubtitleError(DecodingError),
742
743    /// Returned when submitting a packet to the decoder fails
744    /// (`avcodec_send_packet`).
745    #[error("during packet submission to decoder: {0}")]
746    SendPacketError(DecodingError),
747
748    /// Returned when receiving a decoded frame from the decoder fails
749    /// (`avcodec_receive_frame`).
750    #[error("during frame reception from decoder: {0}")]
751    ReceiveFrameError(DecodingError),
752
753    /// Returned when allocating a frame during decoding fails.
754    #[error("during frame allocation: {0}")]
755    FrameAllocationError(DecodingError),
756
757    /// Returned when allocating a packet during decoding fails.
758    #[error("during packet allocation: {0}")]
759    PacketAllocationError(DecodingError),
760
761    /// Returned when allocating an `AVSubtitle` during decoding fails.
762    #[error("during AVSubtitle allocation: {0}")]
763    SubtitleAllocationError(DecodingError),
764
765    /// Returned when the decoder emits a frame flagged as corrupt and
766    /// corrupt frames are treated as errors.
767    #[error("corrupt decoded frame")]
768    CorruptFrame,
769
770    /// Returned when the ratio of decode errors to decoded frames exceeds
771    /// the maximum allowed rate.
772    #[error("decode error rate exceeded the maximum allowed")]
773    ErrorRateExceeded,
774
775    /// Returned when downloading a hardware-decoded frame to system memory
776    /// fails (`av_hwframe_transfer_data`).
777    #[error("during retrieve data on hw: {0}")]
778    HWRetrieveDataError(DecodingError),
779
780    /// Returned when applying codec cropping metadata to a decoded frame
781    /// fails.
782    #[error("during cropping: {0}")]
783    CroppingError(DecodingError),
784}
785
786/// Errors from opening and configuring a decoder.
787/// Variants carrying an [`OpenDecoderError`] embed the mapped FFmpeg error.
788#[derive(thiserror::Error, Debug)]
789#[non_exhaustive]
790pub enum OpenDecoderOperationError {
791    /// Returned when allocating the decoder context fails
792    /// (`avcodec_alloc_context3`).
793    #[error("during context allocation: {0}")]
794    ContextAllocationError(OpenDecoderError),
795
796    /// Returned when applying the stream's codec parameters to the decoder
797    /// context fails (`avcodec_parameters_to_context`).
798    #[error("while applying parameters to context: {0}")]
799    ParameterApplicationError(OpenDecoderError),
800
801    /// Returned when opening the decoder fails (`avcodec_open2`).
802    #[error("while opening decoder: {0}")]
803    DecoderOpenError(OpenDecoderError),
804
805    /// Returned when copying the audio channel layout into the decoder
806    /// context fails.
807    #[error("while copying channel layout: {0}")]
808    ChannelLayoutCopyError(OpenDecoderError),
809
810    /// Returned when setting up hardware acceleration for the decoder
811    /// fails.
812    #[error("while Hw setup: {0}")]
813    HwSetupError(OpenDecoderError),
814
815    /// Returned when the configured decoder name is invalid.
816    #[error("Invalid decoder name")]
817    InvalidName,
818
819    /// Returned when spawning the decoder thread fails, so the decoder
820    /// never opened.
821    #[error("Thread exited")]
822    ThreadExited,
823}
824
825/// Errors from running frames through a configured filtergraph.
826/// Variants carrying a [`FilterGraphError`] embed the mapped FFmpeg error.
827#[derive(thiserror::Error, Debug)]
828#[non_exhaustive]
829pub enum FilterGraphOperationError {
830    /// Returned when requesting the next frame from the graph fails
831    /// (`avfilter_graph_request_oldest`).
832    #[error("during requesting oldest frame: {0}")]
833    RequestOldestError(FilterGraphError),
834
835    /// Returned when processing frames through the filtergraph fails.
836    #[error("during process frames: {0}")]
837    ProcessFramesError(FilterGraphError),
838
839    /// Returned when sending frames into the filtergraph fails.
840    #[error("during send frames: {0}")]
841    SendFramesError(FilterGraphError),
842
843    /// Returned when copying an audio channel layout while configuring the
844    /// graph fails.
845    #[error("during copying channel layout: {0}")]
846    ChannelLayoutCopyError(FilterGraphError),
847
848    /// Returned when pushing a frame into a graph input fails
849    /// (`av_buffersrc_add_frame`).
850    #[error("during buffer source add frame: {0}")]
851    BufferSourceAddFrameError(FilterGraphError),
852
853    /// Returned when closing a graph input at end of stream fails
854    /// (`av_buffersrc_close`).
855    #[error("during closing buffer source: {0}")]
856    BufferSourceCloseError(FilterGraphError),
857
858    /// Returned when replacing a frame's buffer reference fails
859    /// (`av_buffer_replace`).
860    #[error("during replace buffer: {0}")]
861    BufferReplaceoseError(FilterGraphError),
862
863    /// Returned when cloning frame side data for the graph fails.
864    #[error("during cloning frame side data: {0}")]
865    FrameSideDataCloneError(FilterGraphError),
866
867    /// Returned when parsing or configuring the filtergraph description
868    /// fails.
869    #[error("during parse: {0}")]
870    ParseError(FilterGraphParseError),
871
872    /// Returned when a frame entering the graph carries invalid or
873    /// corrupted data.
874    #[error("The data in the frame is invalid or corrupted")]
875    InvalidData,
876
877    /// Returned before the graph is configured when one input has buffered
878    /// frames past the admission limit while another input has not yet
879    /// delivered its first frame; fields are the input label, the buffered
880    /// frame count, and the estimated retained memory in bytes.
881    #[error(
882        "graph input '{0}' already holds {1} buffered frames and admitting the next \
883         one would raise the best-effort retained-memory estimate to ~{2} bytes, \
884         while another input has not yet delivered its first frame, so the filter \
885         graph cannot be configured; check that every graph input actually produces \
886         data (or produces it within the buffering window)"
887    )]
888    PreConfigQueueOverflow(String, usize, usize),
889
890    // Only constructed on the FFmpeg 8+ buffersrc side-data clone path.
891    /// Returned when a frame's combined side-data metadata is too large to
892    /// deep-copy into the buffersrc parameters; fields are the input label
893    /// and the estimated size in bytes.
894    #[cfg_attr(not(ffmpeg_8_0), allow(dead_code))]
895    #[error(
896        "graph input '{0}' would deep-copy an estimated {1} bytes of side-data \
897         metadata into the buffersrc parameters, exceeding the side-data clone \
898         estimate threshold; the frame's combined side-data metadata (across its \
899         global and downmix entries) is pathologically large"
900    )]
901    OversizedSideDataClone(String, usize),
902
903    /// Returned when spawning the filtergraph thread fails, so the graph
904    /// never ran.
905    #[error("Thread exited")]
906    ThreadExited,
907}
908
909/// Errors from the encoder stage while turning frames into packets.
910/// Variants carrying an [`EncodingError`] embed the mapped FFmpeg error.
911#[derive(thiserror::Error, Debug)]
912#[non_exhaustive]
913pub enum EncodingOperationError {
914    /// Returned when submitting a frame to the encoder fails
915    /// (`avcodec_send_frame`).
916    #[error("during frame submission: {0}")]
917    SendFrameError(EncodingError),
918
919    /// Returned when receiving an encoded packet from the encoder fails
920    /// (`avcodec_receive_packet`).
921    #[error("during packet retrieval: {0}")]
922    ReceivePacketError(EncodingError),
923
924    /// Returned when re-chunking buffered audio samples into encoder-sized
925    /// frames fails.
926    #[error("during audio frame receive: {0}")]
927    ReceiveAudioError(EncodingError),
928
929    /// Returned when a subtitle packet reaches the encoder without a
930    /// presentation timestamp.
931    #[error(": Subtitle packets must have a pts")]
932    SubtitleNotPts,
933
934    /// Returned when an encoded packet cannot be delivered because the
935    /// muxer has already finished.
936    #[error(": Muxer already finished")]
937    MuxerFinished,
938
939    /// An output stream buffered more packets before the muxer started than the
940    /// pre-mux queue admits (fftools `AVERROR_BUFFER_TOO_SMALL`, "Too many
941    /// packets buffered for output stream"). Unlike `MuxerFinished` this is a
942    /// hard failure — never a silent truncation — so it must reach the
943    /// scheduler error, not the graceful stop path.
944    #[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")]
945    MuxQueueFull,
946
947    /// Returned when encoding a subtitle fails (see
948    /// [`EncodeSubtitleError`]).
949    #[error("Encode subtitle error: {0}")]
950    EncodeSubtitle(#[from] EncodeSubtitleError),
951
952    /// Returned when allocating a packet for encoder output fails.
953    #[error(": {0}")]
954    AllocPacket(AllocPacketError),
955}
956
957/// Errors from the muxer stage while writing the output container.
958/// Variants carrying a [`MuxingError`] embed the mapped FFmpeg error.
959#[derive(thiserror::Error, Debug)]
960#[non_exhaustive]
961pub enum MuxingOperationError {
962    /// Returned when writing the container header fails (see
963    /// [`WriteHeaderError`]).
964    #[error("during write header: {0}")]
965    WriteHeader(WriteHeaderError),
966
967    /// Returned when initializing a bitstream filter chain for an output
968    /// stream fails; fields are the chain description and the underlying
969    /// error.
970    #[error("while initializing bitstream filter chain '{0}': {1}")]
971    BitstreamFilterInit(String, MuxingError),
972
973    /// Returned when writing an interleaved packet to the container fails
974    /// (`av_interleaved_write_frame`).
975    #[error("during interleaved write: {0}")]
976    InterleavedWriteError(MuxingError),
977
978    /// Returned when writing the container trailer fails
979    /// (`av_write_trailer`).
980    #[error("during trailer write: {0}")]
981    TrailerWriteError(MuxingError),
982
983    /// Returned when closing the output I/O context fails.
984    #[error("during closing IO: {0}")]
985    IOCloseError(MuxingError),
986
987    /// Returned when spawning the muxer (or mux-init) thread fails, so
988    /// muxing never started.
989    #[error("Thread exited")]
990    ThreadExited,
991}
992
993/// Errors specific to packet-sink outputs (`Output::new_by_packet_sink`).
994///
995/// The strict tier fails fast: configuration problems surface from `build()`
996/// or from the job **before any sink callback runs**; per-packet violations
997/// stop the job with the offending packet never delivered. `Clone` is
998/// deliberate — for delivery-path errors the same value is recorded as the
999/// job error and handed to the sink's `on_delivery_error` callback.
1000/// [`JobFailed`](Self::JobFailed) is the exception: it is synthesized for
1001/// that callback only, while first-error-wins may leave the job result owned
1002/// by a sibling worker's error.
1003#[derive(thiserror::Error, Debug, Clone)]
1004#[non_exhaustive]
1005pub enum PacketSinkError {
1006    /// A builder option the packet sink cannot honor was set: either a
1007    /// container-only option (no container is written, so it could never
1008    /// take effect) or a pipeline feature outside the strict tier's
1009    /// delivery contract (filters, bitstream filters, subtitle codecs —
1010    /// rejected as policy, not for lack of a container).
1011    #[error("{0} is not supported on packet-sink outputs")]
1012    UnsupportedOption(&'static str),
1013
1014    /// A stream was configured as `copy`; packet sinks require encoded
1015    /// streams.
1016    #[error("stream copy is not supported on packet-sink outputs (strict tier requires encoded streams)")]
1017    StreamCopyUnsupported,
1018
1019    /// The output mapped a stream the strict tier cannot deliver (non-H.264
1020    /// video, non-AAC audio, or a non-audio/video kind).
1021    #[error("{kind} streams are not supported on packet-sink outputs (strict tier)")]
1022    UnsupportedStream {
1023        /// Label describing the rejected stream kind (e.g. "non-H.264
1024        /// video", "non-AAC audio").
1025        kind: &'static str,
1026    },
1027
1028    /// The configured encoder is outside the strict-tier v1 whitelist.
1029    #[error("encoder '{encoder}' is not on the strict-tier whitelist for {kind} (v1 accepts: {allowed})")]
1030    EncoderNotWhitelisted {
1031        /// Media kind of the stream ("video" or "audio").
1032        kind: &'static str,
1033        /// The encoder name that was configured.
1034        encoder: String,
1035        /// The encoder names the strict tier accepts for this kind.
1036        allowed: &'static str,
1037    },
1038
1039    /// An admitted video encoder was given an explicit B-frame option
1040    /// outside the strict-tier verified scope (`bf=0` / `max_b_frames=0`).
1041    ///
1042    /// Unset keys are not this error: they keep the wrapper default. This
1043    /// crate does not rewrite the caller's options.
1044    #[error(
1045        "encoder '{encoder}' rejected explicit B-frames on a packet-sink output \
1046         (every present bf / max_b_frames key must be integer 0 or removed; \
1047         unset keeps the wrapper default)"
1048    )]
1049    BFramesUnsupported {
1050        /// The encoder name that was configured.
1051        encoder: String,
1052    },
1053
1054    /// No stream was mapped to the packet-sink output.
1055    #[error("packet-sink output has no streams")]
1056    NoStreams,
1057
1058    /// An encoder finalized without the out-of-band codec configuration the
1059    /// strict tier delivers via `on_stream_info`.
1060    #[error("output stream {stream_index}: encoder produced no extradata; the strict tier requires codec configuration (avcC / AudioSpecificConfig) before the first callback")]
1061    MissingExtradata {
1062        /// Index of the offending output stream.
1063        stream_index: usize,
1064    },
1065
1066    /// The encoder's codec configuration failed strict-tier validation.
1067    #[error("output stream {stream_index}: invalid codec configuration: {reason}")]
1068    InvalidExtradata {
1069        /// Index of the offending output stream.
1070        stream_index: usize,
1071        /// Why the codec configuration failed validation.
1072        reason: String,
1073    },
1074
1075    /// A stream's time base is not a positive rational.
1076    #[error("output stream {stream_index}: invalid time base {num}/{den} (positive numerator and denominator required)")]
1077    InvalidTimeBase {
1078        /// Index of the offending output stream.
1079        stream_index: usize,
1080        /// Numerator of the rejected time base.
1081        num: i32,
1082        /// Denominator of the rejected time base.
1083        den: i32,
1084    },
1085
1086    /// A packet was stamped in a time base other than its stream's.
1087    #[error("output stream {stream_index}: packet time base {packet_num}/{packet_den} differs from the stream time base {stream_num}/{stream_den}")]
1088    PacketTimeBaseMismatch {
1089        /// Index of the offending output stream.
1090        stream_index: usize,
1091        /// Numerator of the packet's time base.
1092        packet_num: i32,
1093        /// Denominator of the packet's time base.
1094        packet_den: i32,
1095        /// Numerator of the stream's time base.
1096        stream_num: i32,
1097        /// Denominator of the stream's time base.
1098        stream_den: i32,
1099    },
1100
1101    /// A packet carries no pts or dts (`AV_NOPTS_VALUE`).
1102    #[error("output stream {stream_index}: packet carries no {which} (strict tier rejects AV_NOPTS_VALUE)")]
1103    MissingTimestamp {
1104        /// Index of the offending output stream.
1105        stream_index: usize,
1106        /// Which timestamp is missing: "pts" or "dts".
1107        which: &'static str,
1108    },
1109
1110    /// A packet's dts did not strictly increase within its stream.
1111    #[error(
1112        "output stream {stream_index}: non-monotonic dts (previous {prev}, current {current})"
1113    )]
1114    NonMonotonicDts {
1115        /// Index of the offending output stream.
1116        stream_index: usize,
1117        /// dts of the previous packet, in stream time-base units.
1118        prev: i64,
1119        /// dts of the offending packet, in stream time-base units.
1120        current: i64,
1121    },
1122
1123    /// A packet's pts collided with a still-pending pts on the same stream.
1124    #[error("output stream {stream_index}: duplicate pts {pts}")]
1125    DuplicatePts {
1126        /// Index of the offending output stream.
1127        stream_index: usize,
1128        /// The duplicated pts value, in stream time-base units.
1129        pts: i64,
1130    },
1131
1132    /// A packet's pts is earlier than its dts.
1133    #[error("output stream {stream_index}: pts {pts} is earlier than dts {dts}")]
1134    PtsBeforeDts {
1135        /// Index of the offending output stream.
1136        stream_index: usize,
1137        /// The packet's pts, in stream time-base units.
1138        pts: i64,
1139        /// The packet's dts, in stream time-base units.
1140        dts: i64,
1141    },
1142
1143    /// Rescaling a timestamp onto the shared time origin overflowed.
1144    #[error(
1145        "output stream {stream_index}: timestamp overflow while applying the shared time origin"
1146    )]
1147    TimestampOverflow {
1148        /// Index of the offending output stream.
1149        stream_index: usize,
1150    },
1151
1152    /// A packet has no positive duration and none could be derived from the
1153    /// stream configuration (frame rate / codec frame size).
1154    #[error("output stream {stream_index}: packet duration is absent and cannot be derived (strict tier requires a positive duration)")]
1155    MissingDuration {
1156        /// Index of the offending output stream.
1157        stream_index: usize,
1158    },
1159
1160    /// The packet payload failed bitstream validation.
1161    #[error("output stream {stream_index}: malformed packet payload: {reason}")]
1162    MalformedPacket {
1163        /// Index of the offending output stream.
1164        stream_index: usize,
1165        /// Description of the bitstream validation failure.
1166        reason: String,
1167    },
1168
1169    /// Internal sequencing violation: a packet surfaced outside the delivery
1170    /// phase.
1171    #[error("output stream {stream_index}: packet processed outside the delivery phase (internal sequencing violation)")]
1172    PhaseViolation {
1173        /// Index of the offending output stream.
1174        stream_index: usize,
1175    },
1176
1177    /// The stream configuration changed after `on_stream_info` delivered it.
1178    #[error("output stream {stream_index}: mid-stream configuration change ({what}); the strict tier requires an immutable stream configuration")]
1179    ConfigChange {
1180        /// Index of the offending output stream.
1181        stream_index: usize,
1182        /// Description of the configuration change that was detected.
1183        what: String,
1184    },
1185
1186    /// An H.264 access unit carried in-band SPS/PPS parameter sets.
1187    #[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)")]
1188    InBandParameterSets {
1189        /// Index of the offending output stream.
1190        stream_index: usize,
1191    },
1192
1193    /// The sink's `on_stream_info` callback rejected the configuration.
1194    #[error("on_stream_info callback rejected the stream configuration: {error}")]
1195    StreamInfoCallbackFailed {
1196        /// The error the callback returned.
1197        #[source]
1198        error: crate::core::packet_sink::PacketCallbackError,
1199    },
1200
1201    /// The sink's `on_packet` callback returned an error.
1202    #[error("on_packet callback failed on output stream {stream_index}: {error}")]
1203    PacketCallbackFailed {
1204        /// Index of the offending output stream.
1205        stream_index: usize,
1206        /// The error the callback returned.
1207        #[source]
1208        error: crate::core::packet_sink::PacketCallbackError,
1209    },
1210
1211    /// The channel adapter's receiver was dropped, cancelling delivery and
1212    /// the job.
1213    #[error("the packet-sink channel receiver was dropped; delivery cancelled")]
1214    ChannelDisconnected,
1215
1216    /// The job failed outside this sink's delivery path; handed to
1217    /// `on_delivery_error` only, while `wait()` keeps the original error.
1218    #[error(
1219        "the job failed outside this packet sink; delivery may have been truncated: {message}"
1220    )]
1221    JobFailed {
1222        /// Display rendering of the error that actually failed the job.
1223        message: String,
1224    },
1225}
1226
1227/// Errors from opening and configuring an encoder.
1228/// Variants carrying an [`OpenEncoderError`] embed the mapped FFmpeg error.
1229#[derive(thiserror::Error, Debug)]
1230#[non_exhaustive]
1231pub enum OpenEncoderOperationError {
1232    /// Returned when cloning frame side data into the encoder context
1233    /// fails.
1234    #[error("during frame side data cloning: {0}")]
1235    FrameSideDataCloneError(OpenEncoderError),
1236
1237    /// Returned when copying the audio channel layout into the encoder
1238    /// context fails.
1239    #[error("during channel layout copying: {0}")]
1240    ChannelLayoutCopyError(OpenEncoderError),
1241
1242    /// Returned when opening the encoder fails (`avcodec_open2`).
1243    #[error("during codec opening: {0}")]
1244    CodecOpenError(OpenEncoderError),
1245
1246    /// Returned when exporting encoder parameters to the output stream
1247    /// fails (`avcodec_parameters_from_context`).
1248    #[error("while setting codec parameters: {0}")]
1249    CodecParametersError(OpenEncoderError),
1250
1251    /// Returned when the format of the frame to encode is unknown.
1252    #[error(": unknown format of the frame")]
1253    UnknownFrameFormat,
1254
1255    /// Returned when configuring subtitle encoding parameters fails.
1256    #[error("while setting subtitle: {0}")]
1257    SettingSubtitleError(OpenEncoderError),
1258
1259    /// Returned when setting up hardware acceleration for the encoder
1260    /// fails.
1261    #[error("while Hw setup: {0}")]
1262    HwSetupError(OpenEncoderError),
1263
1264    /// Returned when allocating the encoder context fails
1265    /// (`avcodec_alloc_context3`).
1266    #[error("during context allocation: {0}")]
1267    ContextAllocationError(OpenEncoderError),
1268
1269    /// Returned when the frame stream ends (EOF or upstream disconnect)
1270    /// before the encoder received any frame, so the encoder was never
1271    /// opened.
1272    #[error(": no frames were received before EOF; encoder never opened")]
1273    NoFramesReceived,
1274
1275    /// Returned when the stream's media type cannot be encoded (not video,
1276    /// audio, or subtitle).
1277    #[error(": unsupported media type for encoding")]
1278    UnsupportedMediaType,
1279
1280    /// Returned when spawning the encoder thread fails, so the encoder
1281    /// never started.
1282    #[error("Thread exited")]
1283    ThreadExited,
1284}
1285
1286/// Errors from converting URL or path strings for FFmpeg.
1287#[derive(thiserror::Error, Debug)]
1288#[non_exhaustive]
1289pub enum UrlError {
1290    /// Returned when the string contains an interior NUL byte, which C
1291    /// strings cannot represent; the payload is the byte position.
1292    #[error("Null byte found in string at position {0}")]
1293    NullByteError(usize),
1294}
1295
1296impl From<NulError> for Error {
1297    fn from(err: NulError) -> Self {
1298        Error::Url(UrlError::NullByteError(err.nul_position()))
1299    }
1300}
1301
1302/// Errors from opening an input file, stream, device, or custom input
1303/// source. Most variants are mapped from the FFmpeg error code returned by
1304/// `avformat_open_input`.
1305#[derive(thiserror::Error, Debug)]
1306#[non_exhaustive]
1307pub enum OpenInputError {
1308    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1309    #[error("Memory allocation error")]
1310    OutOfMemory,
1311
1312    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1313    #[error("Invalid argument provided")]
1314    InvalidArgument,
1315
1316    /// The file, URL, or device does not exist (`AVERROR(ENOENT)`).
1317    #[error("File or stream not found")]
1318    NotFound,
1319
1320    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1321    #[error("I/O error occurred while opening the file or stream")]
1322    IOError,
1323
1324    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1325    #[error("Pipe error, possibly the stream or data connection was broken")]
1326    PipeError,
1327
1328    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1329    #[error("Invalid file descriptor")]
1330    BadFileDescriptor,
1331
1332    /// The functionality or input format is not supported by the linked
1333    /// FFmpeg build (`AVERROR(ENOSYS)`).
1334    #[error("Functionality not implemented or unsupported input format")]
1335    NotImplemented,
1336
1337    /// The operation was not permitted (`AVERROR(EPERM)`).
1338    #[error("Operation not permitted to access the file or stream")]
1339    OperationNotPermitted,
1340
1341    /// The file or stream contains invalid or corrupted data
1342    /// (`AVERROR_INVALIDDATA`).
1343    #[error("The data in the file or stream is invalid or corrupted")]
1344    InvalidData,
1345
1346    /// The connection timed out (`AVERROR(ETIMEDOUT)`).
1347    #[error("The connection timed out while trying to open the stream")]
1348    Timeout,
1349
1350    /// A builder option carried an invalid value (e.g. a non-positive
1351    /// `set_framerate`, a non-finite `set_ts_scale`, an out-of-range
1352    /// `set_io_buffer_size`). Setters store values as given and defer
1353    /// validation to open time, so a bad value surfaces here instead of
1354    /// panicking in the setter.
1355    #[error("Invalid input option: {0}")]
1356    InvalidOption(String),
1357
1358    /// Any other failure; the payload is the raw FFmpeg error code
1359    /// (rendered with `av_err2str` in the message).
1360    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1361    UnknownError(i32),
1362
1363    /// Returned when the input has no usable source: neither a URL nor a
1364    /// custom read callback was configured.
1365    #[error("Invalid source provided")]
1366    InvalidSource,
1367
1368    /// Returned when the explicitly requested input format name is unknown
1369    /// to FFmpeg; the payload is the requested name.
1370    #[error("Invalid source format:{0}")]
1371    InvalidFormat(String),
1372
1373    /// Returned when the input requires seeking but the custom input source
1374    /// provides no seek callback.
1375    #[error("No seek callback is provided")]
1376    SeekFunctionMissing,
1377
1378    /// `Input::from("https://…")` failed because the linked FFmpeg has no
1379    /// HTTPS protocol. This does **not** route the URL through rustls.
1380    #[cfg(not(feature = "http-input"))]
1381    #[error(
1382        "FFmpeg HTTPS input is unavailable. Enable the ez-ffmpeg \"http-input\" feature \
1383         and use HttpInput, or link an FFmpeg build with an HTTPS/TLS backend \
1384         (GnuTLS or OpenSSL)."
1385    )]
1386    HttpsProtocolUnavailable,
1387
1388    /// Same failure with the feature already enabled: still not hijacked.
1389    #[cfg(feature = "http-input")]
1390    #[error(
1391        "FFmpeg HTTPS input is unavailable. The \"http-input\" feature is enabled; use \
1392         HttpInput::builder(url), or link an FFmpeg build with an HTTPS/TLS backend."
1393    )]
1394    HttpsProtocolUnavailable,
1395}
1396
1397impl From<i32> for OpenInputError {
1398    fn from(err_code: i32) -> Self {
1399        match err_code {
1400            AVERROR_OUT_OF_MEMORY => OpenInputError::OutOfMemory,
1401            AVERROR_INVALID_ARGUMENT => OpenInputError::InvalidArgument,
1402            AVERROR_NOT_FOUND => OpenInputError::NotFound,
1403            AVERROR_IO_ERROR => OpenInputError::IOError,
1404            AVERROR_PIPE_ERROR => OpenInputError::PipeError,
1405            AVERROR_BAD_FILE_DESCRIPTOR => OpenInputError::BadFileDescriptor,
1406            AVERROR_NOT_IMPLEMENTED => OpenInputError::NotImplemented,
1407            AVERROR_OPERATION_NOT_PERMITTED => OpenInputError::OperationNotPermitted,
1408            AVERROR_INVALIDDATA => OpenInputError::InvalidData,
1409            AVERROR_TIMEOUT => OpenInputError::Timeout,
1410            _ => OpenInputError::UnknownError(err_code),
1411        }
1412    }
1413}
1414
1415const AVERROR_OUT_OF_MEMORY: i32 = AVERROR(ENOMEM);
1416const AVERROR_INVALID_ARGUMENT: i32 = AVERROR(EINVAL);
1417const AVERROR_NOT_FOUND: i32 = AVERROR(ENOENT);
1418const AVERROR_IO_ERROR: i32 = AVERROR(EIO);
1419const AVERROR_PIPE_ERROR: i32 = AVERROR(EPIPE);
1420const AVERROR_BAD_FILE_DESCRIPTOR: i32 = AVERROR(EBADF);
1421const AVERROR_NOT_IMPLEMENTED: i32 = AVERROR(ENOSYS);
1422const AVERROR_OPERATION_NOT_PERMITTED: i32 = AVERROR(EPERM);
1423const AVERROR_PERMISSION_DENIED: i32 = AVERROR(EACCES);
1424const AVERROR_TIMEOUT: i32 = AVERROR(ETIMEDOUT);
1425const AVERROR_NOT_SOCKET: i32 = AVERROR(ENOTSOCK);
1426const AVERROR_AGAIN: i32 = AVERROR(EAGAIN);
1427
1428/// Errors from probing stream information after an input is opened
1429/// (`avformat_find_stream_info`).
1430#[derive(thiserror::Error, Debug)]
1431#[non_exhaustive]
1432pub enum FindStreamError {
1433    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1434    #[error("Memory allocation error")]
1435    OutOfMemory,
1436
1437    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1438    #[error("Invalid argument provided")]
1439    InvalidArgument,
1440
1441    /// Reached end of file before stream information could be determined
1442    /// (`AVERROR_EOF`).
1443    #[error("Reached end of file while looking for stream info")]
1444    EndOfFile,
1445
1446    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
1447    #[error("Timeout occurred while reading stream info")]
1448    Timeout,
1449
1450    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1451    #[error("I/O error occurred while reading stream info")]
1452    IOError,
1453
1454    /// The stream contains invalid or corrupted data
1455    /// (`AVERROR_INVALIDDATA`).
1456    #[error("The data in the stream is invalid or corrupted")]
1457    InvalidData,
1458
1459    /// The functionality or stream format is not supported by the linked
1460    /// FFmpeg build (`AVERROR(ENOSYS)`).
1461    #[error("Functionality not implemented or unsupported stream format")]
1462    NotImplemented,
1463
1464    /// The operation was not permitted (`AVERROR(EPERM)`).
1465    #[error("Operation not permitted to access the file or stream")]
1466    OperationNotPermitted,
1467
1468    /// Returned when the input contains no streams, or no stream of the
1469    /// requested kind.
1470    #[error("No Stream found")]
1471    NoStreamFound,
1472
1473    /// Any other failure; the payload is the raw FFmpeg error code
1474    /// (rendered with `av_err2str` in the message).
1475    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1476    UnknownError(i32),
1477}
1478
1479impl From<i32> for FindStreamError {
1480    fn from(err_code: i32) -> Self {
1481        match err_code {
1482            AVERROR_OUT_OF_MEMORY => FindStreamError::OutOfMemory,
1483            AVERROR_INVALID_ARGUMENT => FindStreamError::InvalidArgument,
1484            AVERROR_EOF => FindStreamError::EndOfFile,
1485            AVERROR_TIMEOUT => FindStreamError::Timeout,
1486            AVERROR_IO_ERROR => FindStreamError::IOError,
1487            AVERROR_INVALIDDATA => FindStreamError::InvalidData,
1488            AVERROR_NOT_IMPLEMENTED => FindStreamError::NotImplemented,
1489            AVERROR_OPERATION_NOT_PERMITTED => FindStreamError::OperationNotPermitted,
1490            _ => FindStreamError::UnknownError(err_code),
1491        }
1492    }
1493}
1494
1495/// Errors from parsing a filtergraph description and wiring its inputs and
1496/// outputs.
1497#[derive(thiserror::Error, Debug)]
1498#[non_exhaustive]
1499pub enum FilterGraphParseError {
1500    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1501    #[error("Memory allocation error")]
1502    OutOfMemory,
1503
1504    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1505    #[error("Invalid argument provided")]
1506    InvalidArgument,
1507
1508    /// End of file was reached during parsing (`AVERROR_EOF`).
1509    #[error("End of file reached during parsing")]
1510    EndOfFile,
1511
1512    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1513    #[error("I/O error occurred during parsing")]
1514    IOError,
1515
1516    /// Invalid data was encountered during parsing
1517    /// (`AVERROR_INVALIDDATA`).
1518    #[error("Invalid data encountered during parsing")]
1519    InvalidData,
1520
1521    /// The functionality or filter is not supported by the linked FFmpeg
1522    /// build (`AVERROR(ENOSYS)`).
1523    #[error("Functionality not implemented or unsupported filter format")]
1524    NotImplemented,
1525
1526    /// Permission was denied — e.g. by a filter that opens files, such as
1527    /// `movie=` (`AVERROR(EACCES)`).
1528    #[error("Permission denied during filter graph parsing")]
1529    PermissionDenied,
1530
1531    /// A socket operation was attempted on a non-socket by a filter
1532    /// touching network resources (`AVERROR(ENOTSOCK)`).
1533    #[error("Socket operation on non-socket during filter graph parsing")]
1534    NotSocket,
1535
1536    /// A filter option named in the description does not exist
1537    /// (`AVERROR_OPTION_NOT_FOUND`).
1538    #[error("Option not found during filter graph configuration")]
1539    OptionNotFound,
1540
1541    /// Returned when a stream reference in the filtergraph description
1542    /// names an input file index that does not exist; fields are the index
1543    /// and the description.
1544    #[error("Invalid file index {0} in filtergraph description {1}")]
1545    InvalidFileIndexInFg(usize, String),
1546
1547    /// Returned when an output URL references an input file index that does
1548    /// not exist; fields are the index and the URL.
1549    #[error("Invalid file index {0} in output url: {1}")]
1550    InvalidFileIndexInOutput(usize, String),
1551
1552    /// Returned when a stream specifier in the filtergraph description is
1553    /// malformed; the payload is the offending text.
1554    #[error("Invalid filter specifier {0}")]
1555    InvalidFilterSpecifier(String),
1556
1557    /// Returned when a filtergraph output pad is not connected to any
1558    /// output; fields are the filter name, the pad index, and its link
1559    /// label.
1560    #[error("Filter '{0}' has output {1} ({2}) unconnected")]
1561    OutputUnconnected(String, usize, String),
1562
1563    /// Any other failure; the payload is the raw FFmpeg error code.
1564    #[error("An unknown error occurred. ret: {0}")]
1565    UnknownError(i32),
1566}
1567
1568impl From<i32> for FilterGraphParseError {
1569    fn from(err_code: i32) -> Self {
1570        match err_code {
1571            AVERROR_OUT_OF_MEMORY => FilterGraphParseError::OutOfMemory,
1572            AVERROR_INVALID_ARGUMENT => FilterGraphParseError::InvalidArgument,
1573            AVERROR_EOF => FilterGraphParseError::EndOfFile,
1574            AVERROR_IO_ERROR => FilterGraphParseError::IOError,
1575            AVERROR_INVALIDDATA => FilterGraphParseError::InvalidData,
1576            AVERROR_NOT_IMPLEMENTED => FilterGraphParseError::NotImplemented,
1577            AVERROR_OPTION_NOT_FOUND => FilterGraphParseError::OptionNotFound,
1578            // EACCES/ENOTSOCK reach here from filters that touch files or
1579            // sockets (e.g. `movie=`); map them to the variants this enum
1580            // already declares instead of degrading to UnknownError.
1581            AVERROR_PERMISSION_DENIED => FilterGraphParseError::PermissionDenied,
1582            AVERROR_NOT_SOCKET => FilterGraphParseError::NotSocket,
1583            _ => FilterGraphParseError::UnknownError(err_code),
1584        }
1585    }
1586}
1587
1588/// Errors from allocating an output format context
1589/// (`avformat_alloc_output_context2`).
1590#[derive(thiserror::Error, Debug)]
1591#[non_exhaustive]
1592pub enum AllocOutputContextError {
1593    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1594    #[error("Memory allocation error")]
1595    OutOfMemory,
1596
1597    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1598    #[error("Invalid argument provided")]
1599    InvalidArgument,
1600
1601    /// The file or stream does not exist (`AVERROR(ENOENT)`).
1602    #[error("File or stream not found")]
1603    NotFound,
1604
1605    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1606    #[error("I/O error occurred while allocating the output context")]
1607    IOError,
1608
1609    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1610    #[error("Pipe error, possibly the stream or data connection was broken")]
1611    PipeError,
1612
1613    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1614    #[error("Invalid file descriptor")]
1615    BadFileDescriptor,
1616
1617    /// The functionality or output format is not supported by the linked
1618    /// FFmpeg build (`AVERROR(ENOSYS)`).
1619    #[error("Functionality not implemented or unsupported output format")]
1620    NotImplemented,
1621
1622    /// The operation was not permitted (`AVERROR(EPERM)`).
1623    #[error("Operation not permitted to allocate the output context")]
1624    OperationNotPermitted,
1625
1626    /// Permission was denied (`AVERROR(EACCES)`).
1627    #[error("Permission denied while allocating the output context")]
1628    PermissionDenied,
1629
1630    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
1631    #[error("The connection timed out while trying to allocate the output context")]
1632    Timeout,
1633
1634    /// Any other failure; the payload is the raw FFmpeg error code
1635    /// (rendered with `av_err2str` in the message).
1636    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1637    UnknownError(i32),
1638}
1639
1640impl From<i32> for AllocOutputContextError {
1641    fn from(err_code: i32) -> Self {
1642        match err_code {
1643            AVERROR_OUT_OF_MEMORY => AllocOutputContextError::OutOfMemory,
1644            AVERROR_INVALID_ARGUMENT => AllocOutputContextError::InvalidArgument,
1645            AVERROR_NOT_FOUND => AllocOutputContextError::NotFound,
1646            AVERROR_IO_ERROR => AllocOutputContextError::IOError,
1647            AVERROR_PIPE_ERROR => AllocOutputContextError::PipeError,
1648            AVERROR_BAD_FILE_DESCRIPTOR => AllocOutputContextError::BadFileDescriptor,
1649            AVERROR_NOT_IMPLEMENTED => AllocOutputContextError::NotImplemented,
1650            AVERROR_OPERATION_NOT_PERMITTED => AllocOutputContextError::OperationNotPermitted,
1651            AVERROR_PERMISSION_DENIED => AllocOutputContextError::PermissionDenied,
1652            AVERROR_TIMEOUT => AllocOutputContextError::Timeout,
1653            _ => AllocOutputContextError::UnknownError(err_code),
1654        }
1655    }
1656}
1657
1658/// Errors from opening and configuring an output: resolving formats and
1659/// encoders, mapping streams, validating options, and opening the target
1660/// for writing.
1661#[derive(thiserror::Error, Debug)]
1662#[non_exhaustive]
1663pub enum OpenOutputError {
1664    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1665    #[error("Memory allocation error")]
1666    OutOfMemory,
1667
1668    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1669    #[error("Invalid argument provided")]
1670    InvalidArgument,
1671
1672    /// The file or stream does not exist (`AVERROR(ENOENT)`).
1673    #[error("File or stream not found")]
1674    NotFound,
1675
1676    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1677    #[error("I/O error occurred while opening the file or stream")]
1678    IOError,
1679
1680    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
1681    #[error("Pipe error, possibly the stream or data connection was broken")]
1682    PipeError,
1683
1684    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
1685    #[error("Invalid file descriptor")]
1686    BadFileDescriptor,
1687
1688    /// The functionality or output format is not supported by the linked
1689    /// FFmpeg build (`AVERROR(ENOSYS)`).
1690    #[error("Functionality not implemented or unsupported output format")]
1691    NotImplemented,
1692
1693    /// The operation was not permitted (`AVERROR(EPERM)`).
1694    #[error("Operation not permitted to open the file or stream")]
1695    OperationNotPermitted,
1696
1697    /// Permission was denied (`AVERROR(EACCES)`).
1698    #[error("Permission denied while opening the file or stream")]
1699    PermissionDenied,
1700
1701    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
1702    #[error("The connection timed out while trying to open the file or stream")]
1703    Timeout,
1704
1705    /// No encoder was found for the requested codec
1706    /// (`AVERROR_ENCODER_NOT_FOUND`).
1707    #[error("encoder not found")]
1708    EncoderNotFound,
1709
1710    /// A named encoder could not be opened because the linked FFmpeg build
1711    /// does not provide it — either it was compiled without that encoder
1712    /// (e.g. no `--enable-libx264`) or the name is not a known encoder at all.
1713    /// Unlike the bare [`EncoderNotFound`](Self::EncoderNotFound) errno
1714    /// mapping, this names the encoder so the fix is actionable. `name` is the
1715    /// encoder the caller requested, or the codec the output format guessed
1716    /// when none was set explicitly.
1717    #[error(
1718        "encoder '{name}' is not available in the linked FFmpeg build — link \
1719         an FFmpeg build that provides it (for example one configured with \
1720         --enable-libx264 for libx264), or select a different encoder via \
1721         Output::set_video_codec / set_audio_codec / set_subtitle_codec \
1722         (list what the build provides with codec::get_encoders)"
1723    )]
1724    EncoderUnavailable {
1725        /// The requested encoder name, or the format's guessed default
1726        /// codec when none was set explicitly.
1727        name: String,
1728    },
1729
1730    /// Returned when a stream map specifier matches no streams; the payload
1731    /// is the specifier.
1732    #[error("Stream map '{0}' matches no streams;")]
1733    MatchesNoStreams(String),
1734
1735    /// A stream map combined stream copy with a per-map re-encoding
1736    /// request ([`StreamMap::codec`] / [`StreamMap::codec_opt`]): copied
1737    /// packets never pass through an encoder, so a per-map codec or
1738    /// per-map codec options could never take effect. Raised at `build()`
1739    /// instead of silently ignoring the request (the FFmpeg CLI merely
1740    /// warns about such unused options).
1741    ///
1742    /// [`StreamMap::codec`]: crate::core::context::output::StreamMap::codec
1743    /// [`StreamMap::codec_opt`]: crate::core::context::output::StreamMap::codec_opt
1744    #[error(
1745        "stream map '{spec}' requests stream copy together with {what}; \
1746         stream copy and per-map re-encoding settings are mutually exclusive"
1747    )]
1748    StreamMapCopyConflict {
1749        /// The offending stream map specifier.
1750        spec: String,
1751        /// The per-map re-encoding setting that conflicts with copy.
1752        what: &'static str,
1753    },
1754
1755    /// Returned when an output references an invalid filtergraph link
1756    /// label; the payload is the label.
1757    #[error("Invalid label {0}")]
1758    InvalidLabel(String),
1759
1760    /// Returned when the output ends up with no streams at all.
1761    #[error("not contain any stream")]
1762    NotContainStream,
1763
1764    /// Returned when the format of the frame feeding an output stream is
1765    /// unknown, so encoder parameters cannot be derived from it.
1766    #[error("unknown format of the frame")]
1767    UnknownFrameFormat,
1768
1769    /// Returned when an input URL references a file index that does not
1770    /// exist; fields are the index and the URL.
1771    #[error("Invalid file index {0} in input url: {1}")]
1772    InvalidFileIndexInIntput(usize, String),
1773
1774    /// Any other failure; the payload is the raw FFmpeg error code
1775    /// (rendered with `av_err2str` in the message).
1776    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1777    UnknownError(i32),
1778
1779    /// Returned when the output has no usable destination: neither a URL
1780    /// nor a custom write callback was configured.
1781    #[error("Invalid sink provided")]
1782    InvalidSink,
1783
1784    /// Returned when the output format requires seeking but the custom
1785    /// output sink provides no seek callback.
1786    #[error("No seek callback is provided")]
1787    SeekFunctionMissing,
1788
1789    /// Returned when the requested output format name is unknown to FFmpeg;
1790    /// the payload is the requested name.
1791    #[error("Format '{0}' is unsupported")]
1792    FormatUnsupported(String),
1793
1794    /// Returned when a pixel format name is not recognized; the payload is
1795    /// the name.
1796    #[error("Unknown pixel format: '{0}'")]
1797    UnknownPixelFormat(String),
1798
1799    /// Returned when a sample format name is not recognized; the payload is
1800    /// the name.
1801    #[error("Unknown sample format: '{0}'")]
1802    UnknownSampleFormat(String),
1803
1804    /// A builder option carried an invalid value (e.g. a malformed
1805    /// `set_force_key_frames` spec, an out-of-range `set_io_buffer_size`).
1806    /// Setters store values as given and defer validation to open time, so
1807    /// a bad value surfaces here instead of panicking in the setter or
1808    /// forcing a `Result` into the middle of a builder chain.
1809    #[error("Invalid output option: {0}")]
1810    InvalidOption(String),
1811
1812    /// Returned when reading an attachment file fails; the payload is the
1813    /// path, with the underlying I/O error as the source.
1814    #[error("Failed to read attachment file '{0}'")]
1815    AttachmentRead(String, #[source] io::Error),
1816
1817    /// Returned when an attachment file is empty; the payload is the path.
1818    #[error("Attachment file '{0}' is empty")]
1819    AttachmentEmpty(String),
1820
1821    /// Returned when an attachment file exceeds the size limit; fields are
1822    /// the path, its size in bytes, and the limit in bytes.
1823    #[error("Attachment file '{0}' is too large ({1} bytes, limit {2} bytes)")]
1824    AttachmentTooLarge(String, u64, u64),
1825
1826    /// Returned when an attachment was configured with an empty mimetype;
1827    /// the payload is the file path.
1828    #[error("Attachment mimetype must not be empty (file '{0}')")]
1829    AttachmentEmptyMimetype(String),
1830
1831    /// A per-output simple filter ([`Output::set_video_filter`] or
1832    /// [`Output::set_audio_filter`]) was combined with stream copy for the
1833    /// same output stream — either `set_video_codec("copy")` /
1834    /// `set_audio_codec("copy")` or a copy stream map covering that stream.
1835    /// Mirrors the FFmpeg CLI error for `-vf`/`-af` + `-c copy`
1836    /// ("Filtering and streamcopy cannot be used together",
1837    /// ffmpeg_mux_init.c streamcopy_init).
1838    ///
1839    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1840    /// [`Output::set_audio_filter`]: crate::core::context::output::Output::set_audio_filter
1841    #[error(
1842        "Filtergraph '{0}' was specified, but codec copy was selected for the \
1843         matching output stream. Filtering and streamcopy cannot be used together"
1844    )]
1845    FilterWithStreamCopy(String),
1846
1847    /// A per-output simple filter ([`Output::set_video_filter`] or
1848    /// [`Output::set_audio_filter`]) was set on an output whose matching
1849    /// stream is fed by a context-level filtergraph
1850    /// (`FfmpegContextBuilder::filter_desc`). Mirrors the FFmpeg CLI error for
1851    /// `-vf`/`-af` + `-filter_complex` on the same stream (ffmpeg_mux_init.c
1852    /// ost_get_filters: "Simple and complex filtering cannot be used together
1853    /// for the same stream").
1854    ///
1855    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1856    /// [`Output::set_audio_filter`]: crate::core::context::output::Output::set_audio_filter
1857    #[error(
1858        "Filtergraph '{0}' was specified for a stream fed from a \
1859         context-level filtergraph. Simple and complex filtering cannot be \
1860         used together for the same stream"
1861    )]
1862    SimpleAndComplexFilter(String),
1863
1864    /// A per-output simple filtergraph must be one connected linear chain:
1865    /// exactly one video input pad, one video output pad, a single connected
1866    /// component, and a directed path from the input to the output (fftools
1867    /// fg_create_simple's contract plus the topology rules a simple graph
1868    /// implies — a disconnected or unreachable description would encode
1869    /// unrelated frames or hang instead of filtering the stream). The path
1870    /// requirement is structural: the input pad must be wired into the flow
1871    /// that feeds the output pad, while a filter that may discard it at
1872    /// runtime (`streamselect` whose applied `map` selects another input —
1873    /// rewritable mid-stream via `sendcmd`) is accepted, matching the CLI.
1874    /// `reason` names the violated rule. Descriptions that split, merge or
1875    /// source streams belong in the context-level `filter_desc`.
1876    #[error(
1877        "Simple filtergraph '{desc}' is not a single connected chain: {reason}; \
1878         use FfmpegContextBuilder::filter_desc for complex graphs"
1879    )]
1880    SimpleFilterInvalidShape {
1881        /// The offending filtergraph description, as configured.
1882        desc: String,
1883        /// The topology rule the description violates.
1884        reason: String,
1885    },
1886
1887    /// A configured [`Output::set_video_filter`] chain that no re-encoded
1888    /// video stream ended up consuming: the output has no video stream at all
1889    /// (audio-only input, `disable_video()`, or maps that matched no video
1890    /// stream). The ffmpeg CLI silently ignores `-vf` in that situation; the
1891    /// crate refuses instead of dropping configuration on the floor.
1892    ///
1893    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1894    #[error(
1895        "video filter '{0}' was configured, but the output ended up with no \
1896         re-encoded video stream to run it (audio-only input, disable_video(), \
1897         or maps matching no video stream); remove the filter or map a video \
1898         stream"
1899    )]
1900    VideoFilterUnused(String),
1901
1902    /// A configured [`Output::set_audio_filter`] chain that no re-encoded
1903    /// audio stream ended up consuming: the output has no audio stream at all
1904    /// (video-only input, `disable_audio()`, or maps that matched no audio
1905    /// stream). The ffmpeg CLI silently ignores `-af` in that situation; the
1906    /// crate refuses instead of dropping configuration on the floor.
1907    ///
1908    /// [`Output::set_audio_filter`]: crate::core::context::output::Output::set_audio_filter
1909    #[error(
1910        "audio filter '{0}' was configured, but the output ended up with no \
1911         re-encoded audio stream to run it (video-only input, disable_audio(), \
1912         or maps matching no audio stream); remove the filter or map an audio \
1913         stream"
1914    )]
1915    AudioFilterUnused(String),
1916
1917    /// A per-output simple filtergraph's pads must match the stream's media
1918    /// type (fftools fg_create_simple: "Filtergraph has a %s output, cannot
1919    /// connect it to %s output stream") — e.g. an audio chain like `anull`
1920    /// cannot be attached as a video filter.
1921    ///
1922    /// The media-type labels are static (`"video"`, `"audio"`, ... — the
1923    /// strings fftools prints), which keeps this variant inside `Error`'s
1924    /// 64-byte layout; three owned `String`s would grow every hot-path
1925    /// `Result` in the crate.
1926    #[error(
1927        "Simple filtergraph '{desc}' has a {found} pad, cannot connect it to \
1928         the {expected} stream of this output"
1929    )]
1930    SimpleFilterMediaTypeMismatch {
1931        /// The offending filtergraph description, as configured.
1932        desc: String,
1933        /// The media type of the mismatched pad.
1934        found: &'static str,
1935        /// The media type the output stream requires.
1936        expected: &'static str,
1937    },
1938}
1939
1940impl From<i32> for OpenOutputError {
1941    fn from(err_code: i32) -> Self {
1942        match err_code {
1943            AVERROR_OUT_OF_MEMORY => OpenOutputError::OutOfMemory,
1944            AVERROR_INVALID_ARGUMENT => OpenOutputError::InvalidArgument,
1945            AVERROR_NOT_FOUND => OpenOutputError::NotFound,
1946            AVERROR_IO_ERROR => OpenOutputError::IOError,
1947            AVERROR_PIPE_ERROR => OpenOutputError::PipeError,
1948            AVERROR_BAD_FILE_DESCRIPTOR => OpenOutputError::BadFileDescriptor,
1949            AVERROR_NOT_IMPLEMENTED => OpenOutputError::NotImplemented,
1950            AVERROR_OPERATION_NOT_PERMITTED => OpenOutputError::OperationNotPermitted,
1951            AVERROR_PERMISSION_DENIED => OpenOutputError::PermissionDenied,
1952            AVERROR_TIMEOUT => OpenOutputError::Timeout,
1953            AVERROR_ENCODER_NOT_FOUND => OpenOutputError::EncoderNotFound,
1954            _ => OpenOutputError::UnknownError(err_code),
1955        }
1956    }
1957}
1958
1959/// Errors from enumerating capture devices (cameras, microphones,
1960/// screens).
1961#[derive(thiserror::Error, Debug)]
1962#[non_exhaustive]
1963pub enum FindDevicesError {
1964    /// Returned on macOS when the `AVCaptureDevice` class is not available.
1965    #[error("AVCaptureDevice class not found in macOS")]
1966    AVCaptureDeviceNotFound,
1967
1968    /// Returned when device enumeration for the requested media type is not
1969    /// supported; the payload is the raw `AVMediaType` value.
1970    #[error("current media_type({0}) is not supported")]
1971    MediaTypeSupported(i32),
1972    /// Returned when device enumeration is not supported on the current
1973    /// operating system.
1974    #[error("current OS is not supported")]
1975    OsNotSupported,
1976    /// Returned when a device description could not be converted to a UTF-8
1977    /// string.
1978    #[error("device_description can not to string")]
1979    UTF8Error,
1980
1981    /// Memory allocation failed (`AVERROR(ENOMEM)`).
1982    #[error("Memory allocation error")]
1983    OutOfMemory,
1984    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
1985    #[error("Invalid argument provided")]
1986    InvalidArgument,
1987    /// The device or stream does not exist (`AVERROR(ENOENT)`).
1988    #[error("Device or stream not found")]
1989    NotFound,
1990    /// A low-level I/O error occurred (`AVERROR(EIO)`).
1991    #[error("I/O error occurred while accessing the device or stream")]
1992    IOError,
1993    /// The operation was not permitted (`AVERROR(EPERM)`).
1994    #[error("Operation not permitted for this device or stream")]
1995    OperationNotPermitted,
1996    /// Permission was denied (`AVERROR(EACCES)`).
1997    #[error("Permission denied while accessing the device or stream")]
1998    PermissionDenied,
1999    /// The functionality is not supported by the linked FFmpeg build
2000    /// (`AVERROR(ENOSYS)`).
2001    #[error("This functionality is not implemented")]
2002    NotImplemented,
2003    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2004    #[error("Bad file descriptor")]
2005    BadFileDescriptor,
2006    /// Any other failure; the payload is the raw FFmpeg error code
2007    /// (rendered with `av_err2str` in the message).
2008    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
2009    UnknownError(i32),
2010}
2011
2012impl From<i32> for FindDevicesError {
2013    fn from(err_code: i32) -> Self {
2014        match err_code {
2015            AVERROR_OUT_OF_MEMORY => FindDevicesError::OutOfMemory,
2016            AVERROR_INVALID_ARGUMENT => FindDevicesError::InvalidArgument,
2017            AVERROR_NOT_FOUND => FindDevicesError::NotFound,
2018            AVERROR_IO_ERROR => FindDevicesError::IOError,
2019            AVERROR_OPERATION_NOT_PERMITTED => FindDevicesError::OperationNotPermitted,
2020            AVERROR_PERMISSION_DENIED => FindDevicesError::PermissionDenied,
2021            AVERROR_NOT_IMPLEMENTED => FindDevicesError::NotImplemented,
2022            AVERROR_BAD_FILE_DESCRIPTOR => FindDevicesError::BadFileDescriptor,
2023            _ => FindDevicesError::UnknownError(err_code),
2024        }
2025    }
2026}
2027
2028/// Errors from writing the output container header
2029/// (`avformat_write_header`), carried by
2030/// [`MuxingOperationError::WriteHeader`].
2031#[derive(thiserror::Error, Debug)]
2032#[non_exhaustive]
2033pub enum WriteHeaderError {
2034    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2035    #[error("Memory allocation error")]
2036    OutOfMemory,
2037
2038    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2039    #[error("Invalid argument provided")]
2040    InvalidArgument,
2041
2042    /// The file or stream does not exist (`AVERROR(ENOENT)`).
2043    #[error("File or stream not found")]
2044    NotFound,
2045
2046    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2047    #[error("I/O error occurred while writing the header")]
2048    IOError,
2049
2050    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2051    #[error("Pipe error, possibly the stream or data connection was broken")]
2052    PipeError,
2053
2054    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2055    #[error("Invalid file descriptor")]
2056    BadFileDescriptor,
2057
2058    /// The functionality or output format is not supported by the linked
2059    /// FFmpeg build (`AVERROR(ENOSYS)`).
2060    #[error("Functionality not implemented or unsupported output format")]
2061    NotImplemented,
2062
2063    /// The operation was not permitted (`AVERROR(EPERM)`).
2064    #[error("Operation not permitted to write the header")]
2065    OperationNotPermitted,
2066
2067    /// Permission was denied (`AVERROR(EACCES)`).
2068    #[error("Permission denied while writing the header")]
2069    PermissionDenied,
2070
2071    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
2072    #[error("The connection timed out while trying to write the header")]
2073    Timeout,
2074
2075    /// Any other failure; the payload is the raw FFmpeg error code
2076    /// (rendered with `av_err2str` in the message).
2077    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
2078    UnknownError(i32),
2079}
2080
2081impl From<i32> for WriteHeaderError {
2082    fn from(err_code: i32) -> Self {
2083        match err_code {
2084            AVERROR_OUT_OF_MEMORY => WriteHeaderError::OutOfMemory,
2085            AVERROR_INVALID_ARGUMENT => WriteHeaderError::InvalidArgument,
2086            AVERROR_NOT_FOUND => WriteHeaderError::NotFound,
2087            AVERROR_IO_ERROR => WriteHeaderError::IOError,
2088            AVERROR_PIPE_ERROR => WriteHeaderError::PipeError,
2089            AVERROR_BAD_FILE_DESCRIPTOR => WriteHeaderError::BadFileDescriptor,
2090            AVERROR_NOT_IMPLEMENTED => WriteHeaderError::NotImplemented,
2091            AVERROR_OPERATION_NOT_PERMITTED => WriteHeaderError::OperationNotPermitted,
2092            AVERROR_PERMISSION_DENIED => WriteHeaderError::PermissionDenied,
2093            AVERROR_TIMEOUT => WriteHeaderError::Timeout,
2094            _ => WriteHeaderError::UnknownError(err_code),
2095        }
2096    }
2097}
2098
2099/// Errors from encoding a subtitle (`avcodec_encode_subtitle`).
2100#[derive(thiserror::Error, Debug)]
2101#[non_exhaustive]
2102pub enum EncodeSubtitleError {
2103    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2104    #[error("Memory allocation error while encoding subtitle")]
2105    OutOfMemory,
2106
2107    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2108    #[error("Invalid argument provided for subtitle encoding")]
2109    InvalidArgument,
2110
2111    /// The operation was not permitted (`AVERROR(EPERM)`).
2112    #[error("Operation not permitted while encoding subtitle")]
2113    OperationNotPermitted,
2114
2115    /// Subtitle encoding is not supported by the linked FFmpeg build
2116    /// (`AVERROR(ENOSYS)`).
2117    #[error("The encoding functionality is not implemented or unsupported")]
2118    NotImplemented,
2119
2120    /// The encoder is temporarily unable to accept input
2121    /// (`AVERROR(EAGAIN)`); retry later.
2122    #[error("Encoder temporarily unable to process, please retry")]
2123    TryAgain,
2124
2125    /// Any other failure; the payload is the raw FFmpeg error code.
2126    #[error("Subtitle encoding failed with unknown error. ret: {0}")]
2127    UnknownError(i32),
2128}
2129
2130impl From<i32> for EncodeSubtitleError {
2131    fn from(err_code: i32) -> Self {
2132        match err_code {
2133            AVERROR_OUT_OF_MEMORY => EncodeSubtitleError::OutOfMemory,
2134            AVERROR_INVALID_ARGUMENT => EncodeSubtitleError::InvalidArgument,
2135            AVERROR_OPERATION_NOT_PERMITTED => EncodeSubtitleError::OperationNotPermitted,
2136            AVERROR_NOT_IMPLEMENTED => EncodeSubtitleError::NotImplemented,
2137            AVERROR_AGAIN => EncodeSubtitleError::TryAgain,
2138            _ => EncodeSubtitleError::UnknownError(err_code),
2139        }
2140    }
2141}
2142
2143/// Errors from allocating an `AVPacket`.
2144#[derive(thiserror::Error, Debug)]
2145#[non_exhaustive]
2146pub enum AllocPacketError {
2147    /// Packet allocation failed (`av_packet_alloc` returned no packet).
2148    #[error("Memory allocation error while alloc packet")]
2149    OutOfMemory,
2150}
2151
2152/// Errors from allocating an `AVFrame`.
2153#[derive(thiserror::Error, Debug)]
2154#[non_exhaustive]
2155pub enum AllocFrameError {
2156    /// Frame allocation failed (`av_frame_alloc` returned no frame).
2157    #[error("Memory allocation error while alloc frame")]
2158    OutOfMemory,
2159}
2160
2161/// Errors from [`make_frame_writable`], the safe wrapper over FFmpeg's
2162/// `av_frame_make_writable`: ensuring exclusive ownership of a frame's data
2163/// buffers may allocate new buffers and copy into them, and that underlying
2164/// call can fail. Common AVERROR codes map to named variants; anything else
2165/// carries the raw code.
2166///
2167/// [`make_frame_writable`]: crate::util::ffmpeg_utils::make_frame_writable
2168#[derive(thiserror::Error, Debug)]
2169#[non_exhaustive]
2170pub enum FrameWritableError {
2171    /// Allocating or copying the frame's data buffers failed
2172    /// (`AVERROR(ENOMEM)`).
2173    #[error("Memory allocation error while copying frame data")]
2174    OutOfMemory,
2175
2176    /// FFmpeg rejected the frame as invalid (`AVERROR(EINVAL)`).
2177    #[error("Invalid argument provided")]
2178    InvalidArgument,
2179
2180    /// Any other failure; the payload is the raw FFmpeg error code
2181    /// (rendered with `av_err2str` in the message).
2182    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
2183    UnknownError(i32),
2184}
2185
2186impl From<i32> for FrameWritableError {
2187    fn from(err_code: i32) -> Self {
2188        match err_code {
2189            AVERROR_OUT_OF_MEMORY => FrameWritableError::OutOfMemory,
2190            AVERROR_INVALID_ARGUMENT => FrameWritableError::InvalidArgument,
2191            _ => FrameWritableError::UnknownError(err_code),
2192        }
2193    }
2194}
2195
2196/// FFmpeg-level errors during muxing, carried by [`MuxingOperationError`].
2197#[derive(thiserror::Error, Debug)]
2198#[non_exhaustive]
2199pub enum MuxingError {
2200    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2201    #[error("Memory allocation error")]
2202    OutOfMemory,
2203
2204    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2205    #[error("Invalid argument provided")]
2206    InvalidArgument,
2207
2208    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2209    #[error("I/O error occurred during muxing")]
2210    IOError,
2211
2212    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2213    #[error("Broken pipe during muxing")]
2214    PipeError,
2215
2216    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2217    #[error("Bad file descriptor encountered")]
2218    BadFileDescriptor,
2219
2220    /// The functionality is not supported by the linked FFmpeg build
2221    /// (`AVERROR(ENOSYS)`).
2222    #[error("Functionality not implemented or unsupported")]
2223    NotImplemented,
2224
2225    /// The operation was not permitted (`AVERROR(EPERM)`).
2226    #[error("Operation not permitted")]
2227    OperationNotPermitted,
2228
2229    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
2230    #[error("Resource temporarily unavailable")]
2231    TryAgain,
2232
2233    /// Any other failure; the payload is the raw FFmpeg error code
2234    /// (rendered with `av_err2str` in the message).
2235    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
2236    UnknownError(i32),
2237}
2238
2239impl From<i32> for MuxingError {
2240    fn from(err_code: i32) -> Self {
2241        match err_code {
2242            AVERROR_OUT_OF_MEMORY => MuxingError::OutOfMemory,
2243            AVERROR_INVALID_ARGUMENT => MuxingError::InvalidArgument,
2244            AVERROR_IO_ERROR => MuxingError::IOError,
2245            AVERROR_PIPE_ERROR => MuxingError::PipeError,
2246            AVERROR_BAD_FILE_DESCRIPTOR => MuxingError::BadFileDescriptor,
2247            AVERROR_NOT_IMPLEMENTED => MuxingError::NotImplemented,
2248            AVERROR_OPERATION_NOT_PERMITTED => MuxingError::OperationNotPermitted,
2249            AVERROR_AGAIN => MuxingError::TryAgain,
2250            _ => MuxingError::UnknownError(err_code),
2251        }
2252    }
2253}
2254
2255/// FFmpeg-level errors while opening an encoder, carried by
2256/// [`OpenEncoderOperationError`].
2257#[derive(thiserror::Error, Debug)]
2258#[non_exhaustive]
2259pub enum OpenEncoderError {
2260    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2261    #[error("Memory allocation error occurred during encoder initialization")]
2262    OutOfMemory,
2263
2264    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2265    #[error("Invalid argument provided to encoder")]
2266    InvalidArgument,
2267
2268    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2269    #[error("I/O error occurred while opening encoder")]
2270    IOError,
2271
2272    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2273    #[error("Broken pipe encountered during encoder initialization")]
2274    PipeError,
2275
2276    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2277    #[error("Bad file descriptor used in encoder")]
2278    BadFileDescriptor,
2279
2280    /// The functionality is not supported by the linked FFmpeg build
2281    /// (`AVERROR(ENOSYS)`).
2282    #[error("Encoder functionality not implemented or unsupported")]
2283    NotImplemented,
2284
2285    /// The operation was not permitted (`AVERROR(EPERM)`).
2286    #[error("Operation not permitted while configuring encoder")]
2287    OperationNotPermitted,
2288
2289    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
2290    #[error("Resource temporarily unavailable during encoder setup")]
2291    TryAgain,
2292
2293    /// Any other failure; the payload is the raw FFmpeg error code.
2294    #[error("An unknown error occurred in encoder setup. ret:{0}")]
2295    UnknownError(i32),
2296}
2297
2298impl From<i32> for OpenEncoderError {
2299    fn from(err_code: i32) -> Self {
2300        match err_code {
2301            AVERROR_OUT_OF_MEMORY => OpenEncoderError::OutOfMemory,
2302            AVERROR_INVALID_ARGUMENT => OpenEncoderError::InvalidArgument,
2303            AVERROR_IO_ERROR => OpenEncoderError::IOError,
2304            AVERROR_PIPE_ERROR => OpenEncoderError::PipeError,
2305            AVERROR_BAD_FILE_DESCRIPTOR => OpenEncoderError::BadFileDescriptor,
2306            AVERROR_NOT_IMPLEMENTED => OpenEncoderError::NotImplemented,
2307            AVERROR_OPERATION_NOT_PERMITTED => OpenEncoderError::OperationNotPermitted,
2308            AVERROR_AGAIN => OpenEncoderError::TryAgain,
2309            _ => OpenEncoderError::UnknownError(err_code),
2310        }
2311    }
2312}
2313
2314/// FFmpeg-level errors during encoding, carried by
2315/// [`EncodingOperationError`].
2316#[derive(thiserror::Error, Debug)]
2317#[non_exhaustive]
2318pub enum EncodingError {
2319    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2320    #[error("Memory allocation error during encoding")]
2321    OutOfMemory,
2322
2323    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2324    #[error("Invalid argument provided to encoder")]
2325    InvalidArgument,
2326
2327    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2328    #[error("I/O error occurred during encoding")]
2329    IOError,
2330
2331    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2332    #[error("Broken pipe encountered during encoding")]
2333    PipeError,
2334
2335    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2336    #[error("Bad file descriptor encountered during encoding")]
2337    BadFileDescriptor,
2338
2339    /// The functionality is not supported by the linked FFmpeg build
2340    /// (`AVERROR(ENOSYS)`).
2341    #[error("Functionality not implemented or unsupported encoding feature")]
2342    NotImplemented,
2343
2344    /// The operation was not permitted (`AVERROR(EPERM)`).
2345    #[error("Operation not permitted for encoder")]
2346    OperationNotPermitted,
2347
2348    /// The encoder is temporarily unable to accept or produce data
2349    /// (`AVERROR(EAGAIN)`).
2350    #[error("Resource temporarily unavailable, try again later")]
2351    TryAgain,
2352
2353    /// The encoder reached end of stream; no more packets will be produced
2354    /// (`AVERROR_EOF`).
2355    #[error("End of stream reached or no more frames to encode")]
2356    EndOfStream,
2357
2358    /// Any other failure; the payload is the raw FFmpeg error code.
2359    #[error("An unknown error occurred during encoding. ret: {0}")]
2360    UnknownError(i32),
2361}
2362
2363impl From<i32> for EncodingError {
2364    fn from(err_code: i32) -> Self {
2365        match err_code {
2366            AVERROR_OUT_OF_MEMORY => EncodingError::OutOfMemory,
2367            AVERROR_INVALID_ARGUMENT => EncodingError::InvalidArgument,
2368            AVERROR_IO_ERROR => EncodingError::IOError,
2369            AVERROR_PIPE_ERROR => EncodingError::PipeError,
2370            AVERROR_BAD_FILE_DESCRIPTOR => EncodingError::BadFileDescriptor,
2371            AVERROR_NOT_IMPLEMENTED => EncodingError::NotImplemented,
2372            AVERROR_OPERATION_NOT_PERMITTED => EncodingError::OperationNotPermitted,
2373            AVERROR_AGAIN => EncodingError::TryAgain,
2374            AVERROR_EOF => EncodingError::EndOfStream,
2375            _ => EncodingError::UnknownError(err_code),
2376        }
2377    }
2378}
2379
2380/// FFmpeg-level errors during filtergraph processing, carried by
2381/// [`FilterGraphOperationError`].
2382#[derive(thiserror::Error, Debug)]
2383#[non_exhaustive]
2384pub enum FilterGraphError {
2385    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2386    #[error("Memory allocation error during filter graph processing")]
2387    OutOfMemory,
2388
2389    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2390    #[error("Invalid argument provided to filter graph processing")]
2391    InvalidArgument,
2392
2393    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2394    #[error("I/O error occurred during filter graph processing")]
2395    IOError,
2396
2397    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2398    #[error("Broken pipe during filter graph processing")]
2399    PipeError,
2400
2401    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2402    #[error("Bad file descriptor encountered during filter graph processing")]
2403    BadFileDescriptor,
2404
2405    /// The functionality is not supported by the linked FFmpeg build
2406    /// (`AVERROR(ENOSYS)`).
2407    #[error("Functionality not implemented or unsupported during filter graph processing")]
2408    NotImplemented,
2409
2410    /// The operation was not permitted (`AVERROR(EPERM)`).
2411    #[error("Operation not permitted during filter graph processing")]
2412    OperationNotPermitted,
2413
2414    /// The graph is temporarily unable to accept or produce data
2415    /// (`AVERROR(EAGAIN)`).
2416    #[error("Resource temporarily unavailable during filter graph processing")]
2417    TryAgain,
2418
2419    /// The filtergraph reached end of stream (`AVERROR_EOF`).
2420    #[error("EOF")]
2421    EOF,
2422
2423    /// Any other failure; the payload is the raw FFmpeg error code.
2424    #[error("An unknown error occurred during filter graph processing. ret:{0}")]
2425    UnknownError(i32),
2426}
2427
2428impl From<i32> for FilterGraphError {
2429    fn from(err_code: i32) -> Self {
2430        match err_code {
2431            AVERROR_OUT_OF_MEMORY => FilterGraphError::OutOfMemory,
2432            AVERROR_INVALID_ARGUMENT => FilterGraphError::InvalidArgument,
2433            AVERROR_IO_ERROR => FilterGraphError::IOError,
2434            AVERROR_PIPE_ERROR => FilterGraphError::PipeError,
2435            AVERROR_BAD_FILE_DESCRIPTOR => FilterGraphError::BadFileDescriptor,
2436            AVERROR_NOT_IMPLEMENTED => FilterGraphError::NotImplemented,
2437            AVERROR_OPERATION_NOT_PERMITTED => FilterGraphError::OperationNotPermitted,
2438            AVERROR_AGAIN => FilterGraphError::TryAgain,
2439            AVERROR_EOF => FilterGraphError::EOF,
2440            _ => FilterGraphError::UnknownError(err_code),
2441        }
2442    }
2443}
2444
2445/// FFmpeg-level errors while opening a decoder, carried by
2446/// [`OpenDecoderOperationError`].
2447#[derive(thiserror::Error, Debug)]
2448#[non_exhaustive]
2449pub enum OpenDecoderError {
2450    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2451    #[error("Memory allocation error during decoder initialization")]
2452    OutOfMemory,
2453
2454    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2455    #[error("Invalid argument provided during decoder initialization")]
2456    InvalidArgument,
2457
2458    /// The functionality is not supported by the linked FFmpeg build
2459    /// (`AVERROR(ENOSYS)`).
2460    #[error("Functionality not implemented or unsupported during decoder initialization")]
2461    NotImplemented,
2462
2463    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
2464    #[error("Resource temporarily unavailable during decoder initialization")]
2465    TryAgain,
2466
2467    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2468    #[error("I/O error occurred during decoder initialization")]
2469    IOError,
2470
2471    /// Any other failure; the payload is the raw FFmpeg error code.
2472    #[error("An unknown error occurred during decoder initialization: {0}")]
2473    UnknownError(i32),
2474}
2475
2476impl From<i32> for OpenDecoderError {
2477    fn from(err_code: i32) -> Self {
2478        match err_code {
2479            AVERROR_OUT_OF_MEMORY => OpenDecoderError::OutOfMemory,
2480            AVERROR_INVALID_ARGUMENT => OpenDecoderError::InvalidArgument,
2481            AVERROR_NOT_IMPLEMENTED => OpenDecoderError::NotImplemented,
2482            AVERROR_AGAIN => OpenDecoderError::TryAgain,
2483            AVERROR_IO_ERROR => OpenDecoderError::IOError,
2484            _ => OpenDecoderError::UnknownError(err_code),
2485        }
2486    }
2487}
2488
2489/// FFmpeg-level errors during decoding, carried by
2490/// [`DecodingOperationError`].
2491#[derive(thiserror::Error, Debug)]
2492#[non_exhaustive]
2493pub enum DecodingError {
2494    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2495    #[error("Memory allocation error")]
2496    OutOfMemory,
2497
2498    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2499    #[error("Invalid argument provided")]
2500    InvalidArgument,
2501
2502    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2503    #[error("I/O error occurred during decoding")]
2504    IOError,
2505
2506    /// The operation timed out (`AVERROR(ETIMEDOUT)`).
2507    #[error("Timeout occurred during decoding")]
2508    Timeout,
2509
2510    /// The stream or data connection was broken (`AVERROR(EPIPE)`).
2511    #[error("Broken pipe encountered during decoding")]
2512    PipeError,
2513
2514    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2515    #[error("Bad file descriptor encountered during decoding")]
2516    BadFileDescriptor,
2517
2518    /// The functionality or format is not supported by the linked FFmpeg
2519    /// build (`AVERROR(ENOSYS)`).
2520    #[error("Unsupported functionality or format encountered")]
2521    NotImplemented,
2522
2523    /// The operation was not permitted (`AVERROR(EPERM)`).
2524    #[error("Operation not permitted")]
2525    OperationNotPermitted,
2526
2527    /// The decoder is temporarily unable to accept or produce data
2528    /// (`AVERROR(EAGAIN)`).
2529    #[error("Resource temporarily unavailable")]
2530    TryAgain,
2531
2532    /// Any other failure; the payload is the raw FFmpeg error code.
2533    #[error("An unknown decoding error occurred. ret:{0}")]
2534    UnknownError(i32),
2535}
2536
2537impl From<i32> for DecodingError {
2538    fn from(err_code: i32) -> Self {
2539        match err_code {
2540            AVERROR_OUT_OF_MEMORY => DecodingError::OutOfMemory,
2541            AVERROR_INVALID_ARGUMENT => DecodingError::InvalidArgument,
2542            AVERROR_IO_ERROR => DecodingError::IOError,
2543            AVERROR_TIMEOUT => DecodingError::Timeout,
2544            AVERROR_PIPE_ERROR => DecodingError::PipeError,
2545            AVERROR_BAD_FILE_DESCRIPTOR => DecodingError::BadFileDescriptor,
2546            AVERROR_NOT_IMPLEMENTED => DecodingError::NotImplemented,
2547            AVERROR_OPERATION_NOT_PERMITTED => DecodingError::OperationNotPermitted,
2548            AVERROR_AGAIN => DecodingError::TryAgain,
2549            _ => DecodingError::UnknownError(err_code),
2550        }
2551    }
2552}
2553
2554/// Errors from resolving a decoder.
2555#[derive(thiserror::Error, Debug)]
2556#[non_exhaustive]
2557pub enum DecoderError {
2558    /// Returned when a decoder requested by name is not provided by the
2559    /// linked FFmpeg build; the payload is the requested name.
2560    #[error("decoder '{0}' not found")]
2561    NotFound(String),
2562}
2563
2564/// FFmpeg-level errors during demuxing, carried by
2565/// [`DemuxingOperationError`] and [`PacketScannerError`].
2566#[derive(thiserror::Error, Debug)]
2567#[non_exhaustive]
2568pub enum DemuxingError {
2569    /// Memory allocation failed (`AVERROR(ENOMEM)`).
2570    #[error("Memory allocation error")]
2571    OutOfMemory,
2572
2573    /// FFmpeg rejected an argument as invalid (`AVERROR(EINVAL)`).
2574    #[error("Invalid argument provided")]
2575    InvalidArgument,
2576
2577    /// A low-level I/O error occurred (`AVERROR(EIO)`).
2578    #[error("I/O error occurred during demuxing")]
2579    IOError,
2580
2581    /// End of file was reached during demuxing (`AVERROR_EOF`).
2582    #[error("End of file reached during demuxing")]
2583    EndOfFile,
2584
2585    /// The resource is temporarily unavailable (`AVERROR(EAGAIN)`).
2586    #[error("Resource temporarily unavailable")]
2587    TryAgain,
2588
2589    /// The functionality is not supported by the linked FFmpeg build
2590    /// (`AVERROR(ENOSYS)`).
2591    #[error("Functionality not implemented or unsupported")]
2592    NotImplemented,
2593
2594    /// The operation was not permitted (`AVERROR(EPERM)`).
2595    #[error("Operation not permitted")]
2596    OperationNotPermitted,
2597
2598    /// An invalid file descriptor was used (`AVERROR(EBADF)`).
2599    #[error("Bad file descriptor encountered")]
2600    BadFileDescriptor,
2601
2602    /// The input contains invalid or corrupted data
2603    /// (`AVERROR_INVALIDDATA`).
2604    #[error("Invalid data found when processing input")]
2605    InvalidData,
2606
2607    /// Any other failure; the payload is the raw FFmpeg error code
2608    /// (rendered with `av_err2str` in the message).
2609    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
2610    UnknownError(i32),
2611}
2612
2613impl From<i32> for DemuxingError {
2614    fn from(err_code: i32) -> Self {
2615        match err_code {
2616            AVERROR_OUT_OF_MEMORY => DemuxingError::OutOfMemory,
2617            AVERROR_INVALID_ARGUMENT => DemuxingError::InvalidArgument,
2618            AVERROR_IO_ERROR => DemuxingError::IOError,
2619            AVERROR_EOF => DemuxingError::EndOfFile,
2620            AVERROR_AGAIN => DemuxingError::TryAgain,
2621            AVERROR_NOT_IMPLEMENTED => DemuxingError::NotImplemented,
2622            AVERROR_OPERATION_NOT_PERMITTED => DemuxingError::OperationNotPermitted,
2623            AVERROR_BAD_FILE_DESCRIPTOR => DemuxingError::BadFileDescriptor,
2624            AVERROR_INVALIDDATA => DemuxingError::InvalidData,
2625            _ => DemuxingError::UnknownError(err_code),
2626        }
2627    }
2628}
2629
2630/// Errors that can occur during packet scanning operations.
2631#[derive(thiserror::Error, Debug)]
2632#[non_exhaustive]
2633pub enum PacketScannerError {
2634    /// Failed to seek to the requested timestamp.
2635    #[error("while seeking: {0}")]
2636    SeekError(DemuxingError),
2637
2638    /// Failed to read the next packet from the demuxer.
2639    #[error("while reading packet: {0}")]
2640    ReadError(DemuxingError),
2641}
2642
2643#[cfg(test)]
2644mod tests {
2645    // Regression: FrameSourceThreadExited is payload-less, but the manual
2646    // PartialEq whitelist omitted it, so the variant compared unequal to
2647    // itself — breaking the impl's documented "structural equality for
2648    // payload-less variants" contract.
2649    #[test]
2650    fn frame_source_thread_exited_equals_itself() {
2651        use super::Error;
2652        assert_eq!(
2653            Error::FrameSourceThreadExited,
2654            Error::FrameSourceThreadExited
2655        );
2656        assert_ne!(Error::FrameSourceThreadExited, Error::NotStarted);
2657    }
2658
2659    // Regression: FilterGraphParseError declares PermissionDenied and NotSocket,
2660    // but its From<i32> once omitted them, so an EACCES/ENOTSOCK filtergraph
2661    // error degraded to UnknownError and the two declared variants were
2662    // unreachable. Map the codes to the variants the enum already exposes.
2663    #[test]
2664    fn filter_graph_parse_error_maps_permission_and_socket_codes() {
2665        use super::{FilterGraphParseError, AVERROR_NOT_SOCKET, AVERROR_PERMISSION_DENIED};
2666        assert!(matches!(
2667            FilterGraphParseError::from(AVERROR_PERMISSION_DENIED),
2668            FilterGraphParseError::PermissionDenied
2669        ));
2670        assert!(matches!(
2671            FilterGraphParseError::from(AVERROR_NOT_SOCKET),
2672            FilterGraphParseError::NotSocket
2673        ));
2674    }
2675
2676    // make_frame_writable's failure is typed like every other AVERROR-coded
2677    // failure in this file: common codes map to named variants, the rest keep
2678    // the raw code. Pin the mapping and the user-facing Display string.
2679    #[test]
2680    fn frame_writable_error_maps_codes_and_pins_display() {
2681        use super::{Error, FrameWritableError, AVERROR_INVALID_ARGUMENT, AVERROR_OUT_OF_MEMORY};
2682        assert!(matches!(
2683            FrameWritableError::from(AVERROR_OUT_OF_MEMORY),
2684            FrameWritableError::OutOfMemory
2685        ));
2686        assert!(matches!(
2687            FrameWritableError::from(AVERROR_INVALID_ARGUMENT),
2688            FrameWritableError::InvalidArgument
2689        ));
2690        assert!(matches!(
2691            FrameWritableError::from(-99),
2692            FrameWritableError::UnknownError(-99)
2693        ));
2694        let err = Error::from(FrameWritableError::from(AVERROR_OUT_OF_MEMORY));
2695        assert_eq!(
2696            err.to_string(),
2697            "Frame writable error: Memory allocation error while copying frame data"
2698        );
2699    }
2700
2701    // The deprecated OpenGL filter's constructor failures are typed like the
2702    // wgpu successor's: they carry OpenGLFilterError and convert into
2703    // Error::OpenGLFilter. Pin the user-facing Display strings.
2704    #[cfg(feature = "opengl")]
2705    #[test]
2706    fn opengl_filter_error_pins_display() {
2707        use super::{Error, OpenGLFilterError};
2708        let err = Error::from(OpenGLFilterError::InvalidOption(
2709            "fragment shader must declare 'in vec2 TexCoord;'".to_string(),
2710        ));
2711        assert_eq!(
2712            err.to_string(),
2713            "OpenGL filter error: invalid OpenGL filter option: \
2714             fragment shader must declare 'in vec2 TexCoord;'"
2715        );
2716        let err = Error::from(OpenGLFilterError::ContextCreation(
2717            "Failed to create Surfman connection".to_string(),
2718        ));
2719        assert_eq!(
2720            err.to_string(),
2721            "OpenGL filter error: OpenGL context creation failed: \
2722             Failed to create Surfman connection"
2723        );
2724    }
2725
2726    #[test]
2727    fn hls_encoder_selection_error_is_boxed_under_size_cap() {
2728        use super::{Error, HlsEncoderSelectionError};
2729        assert!(std::mem::size_of::<Error>() <= 64);
2730        let err = Error::from(HlsEncoderSelectionError::HistoricalDefaultUnavailable {
2731            registered_auto_candidates: Vec::new(),
2732            registered_explicit_h264_encoders: Vec::new(),
2733        });
2734        match &err {
2735            Error::HlsEncoderSelection(_) => {}
2736            other => panic!("expected boxed HLS error, got {other}"),
2737        }
2738        let text = err.to_string();
2739        assert!(text.contains("libx264"));
2740        assert!(text.contains("LGPL"));
2741        assert!(text.contains(".video_codec_auto()"));
2742        assert!(text.contains(": none."));
2743    }
2744
2745    #[test]
2746    fn hls_explicit_open_failed_pins_display() {
2747        use super::{Error, HlsEncoderSelectionError};
2748        let err = Error::from(HlsEncoderSelectionError::ExplicitOpenFailed {
2749            encoder: "h264_qsv".into(),
2750            width: 1920,
2751            height: 1080,
2752            raw_code: -1,
2753            message: "device busy".into(),
2754        });
2755        let text = err.to_string();
2756        assert!(text.contains("pinned encoder 'h264_qsv'"), "{text}");
2757        assert!(text.contains("1920x1080"), "{text}");
2758        assert!(text.contains("device busy"), "{text}");
2759        assert!(
2760            text.contains("Output directories were not created"),
2761            "{text}"
2762        );
2763    }
2764
2765    #[test]
2766    fn hls_master_write_error_is_boxed_under_size_cap() {
2767        use super::{Error, HlsMasterWriteError};
2768        assert!(std::mem::size_of::<Error>() <= 64);
2769        let err = Error::from(HlsMasterWriteError {
2770            master_name: "custom.m3u8".into(),
2771            detail: "failed to write master playlist".into(),
2772        });
2773        match &err {
2774            Error::HlsMasterWrite(_) => {}
2775            other => panic!("expected boxed HLS master-write error, got {other}"),
2776        }
2777        let text = err.to_string();
2778        assert!(text.contains("transcode succeeded"), "{text}");
2779        assert!(text.contains("custom.m3u8"), "{text}");
2780        assert!(text.contains("failed to write master playlist"), "{text}");
2781    }
2782
2783    #[test]
2784    fn analysis_frame_error_is_boxed_under_size_cap() {
2785        use super::Error;
2786        assert!(std::mem::size_of::<Error>() <= 64);
2787        let err = Error::AnalysisFrame("interlaced".into());
2788        match &err {
2789            Error::AnalysisFrame(_) => {}
2790            other => panic!("expected boxed analysis-frame error, got {other}"),
2791        }
2792        assert_eq!(err.to_string(), "analysis frame error: interlaced");
2793    }
2794
2795    #[test]
2796    fn packet_sink_b_frames_unsupported_pins_display() {
2797        use super::{Error, PacketSinkError};
2798        let err = Error::from(PacketSinkError::BFramesUnsupported {
2799            encoder: "h264_videotoolbox".into(),
2800        });
2801        assert_eq!(
2802            err.to_string(),
2803            "Packet sink error: encoder 'h264_videotoolbox' rejected explicit \
2804             B-frames on a packet-sink output (every present bf / max_b_frames \
2805             key must be integer 0 or removed; unset keeps the wrapper default)"
2806        );
2807    }
2808}