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#[derive(thiserror::Error, Debug)]
18#[non_exhaustive]
19pub enum Error {
20    #[error("Scheduler is not started")]
21    NotStarted,
22
23    #[error("URL error: {0}")]
24    Url(#[from] UrlError),
25
26    #[error("Open input stream error: {0}")]
27    OpenInputStream(#[from] OpenInputError),
28
29    #[error("Find stream info error: {0}")]
30    FindStream(#[from] FindStreamError),
31
32    #[error("Decoder error: {0}")]
33    Decoder(#[from] DecoderError),
34
35    #[error("Filter graph parse error: {0}")]
36    FilterGraphParse(#[from] FilterGraphParseError),
37
38    #[error("Filter description converted to utf8 string error")]
39    FilterDescUtf8,
40
41    #[error("Filter name converted to utf8 string error")]
42    FilterNameUtf8,
43
44    #[error("A filtergraph has zero outputs, this is not supported")]
45    FilterZeroOutputs,
46
47    #[error("A filtergraph has zero inputs, this is not supported")]
48    FilterZeroInputs,
49
50    #[error("Input is not a valid number")]
51    ParseInteger,
52
53    #[error("Alloc output context error: {0}")]
54    AllocOutputContext(#[from] AllocOutputContextError),
55
56    #[error("Open output error: {0}")]
57    OpenOutput(#[from] OpenOutputError),
58
59    #[error("Output file '{0}' is the same as an input file")]
60    FileSameAsInput(String),
61
62    #[error("Find devices error: {0}")]
63    FindDevices(#[from] FindDevicesError),
64
65    #[error("Alloc frame error: {0}")]
66    AllocFrame(#[from] AllocFrameError),
67
68    #[error("Alloc packet error: {0}")]
69    AllocPacket(#[from] AllocPacketError),
70
71    #[error("Frame writable error: {0}")]
72    FrameWritable(#[from] FrameWritableError),
73
74    // ---- Muxing ----
75    #[error("Muxing operation failed {0}")]
76    Muxing(#[from] MuxingOperationError),
77
78    // ---- Open Encoder ----
79    #[error("Open encoder operation failed {0}")]
80    OpenEncoder(#[from] OpenEncoderOperationError),
81
82    // ---- Encoding ----
83    #[error("Encoding operation failed {0}")]
84    Encoding(#[from] EncodingOperationError),
85
86    // ---- FilterGraph ----
87    #[error("Filter graph operation failed {0}")]
88    FilterGraph(#[from] FilterGraphOperationError),
89
90    // ---- Open Decoder ----
91    #[error("Open decoder operation failed {0}")]
92    OpenDecoder(#[from] OpenDecoderOperationError),
93
94    // ---- Decoding ----
95    #[error("Decoding operation failed {0}")]
96    Decoding(#[from] DecodingOperationError),
97
98    // ---- Demuxing ----
99    #[error("Demuxing operation failed {0}")]
100    Demuxing(#[from] DemuxingOperationError),
101
102    // ---- Packet Scanner ----
103    #[error("Packet scanner error: {0}")]
104    PacketScanner(#[from] PacketScannerError),
105
106    // ---- Frame Filter ----
107    #[error("Frame filter init failed: {0}")]
108    FrameFilterInit(Box<dyn std::error::Error + Send + Sync>),
109
110    #[error("Frame filter process failed: {0}")]
111    FrameFilterProcess(Box<dyn std::error::Error + Send + Sync>),
112
113    #[error("Frame filter request failed: {0}")]
114    FrameFilterRequest(Box<dyn std::error::Error + Send + Sync>),
115
116    #[error("No {0} stream of the type:{1} were found while build frame pipeline")]
117    FrameFilterTypeNoMatched(String, String),
118
119    #[error("{0} stream:{1} of the type:{2} were mismatched while build frame pipeline")]
120    FrameFilterStreamTypeNoMatched(String, usize, String),
121
122    #[error("Frame filter pipeline destination already finished")]
123    FrameFilterDstFinished,
124
125    #[error("Frame filter pipeline failed to duplicate a frame for an additional destination")]
126    FrameFilterFrameDuplicateFailed,
127
128    #[error("Frame filter pipeline thread exited")]
129    FrameFilterThreadExited,
130
131    #[error("Worker thread '{0}' panicked; output may be incomplete")]
132    WorkerPanicked(String),
133
134    /// Recorded as the scheduler result when `start()` fails after some
135    /// worker threads were already launched. `start()` itself returns the
136    /// actual init error to its caller; this recorded value is what
137    /// concurrent observers (packet-sink terminal callbacks) report, so a
138    /// sink can never mistake a torn-down startup for a settled-Ok job.
139    #[error("Scheduler start failed; the job was torn down during startup")]
140    StartFailed,
141
142    #[cfg(feature = "rtmp")]
143    #[error("Rtmp stream already exists with key: {0}")]
144    RtmpStreamAlreadyExists(String),
145
146    #[cfg(feature = "rtmp")]
147    #[error("Rtmp create stream failed. Check whether the server is stopped.")]
148    RtmpCreateStream,
149
150    #[cfg(feature = "rtmp")]
151    #[error("Rtmp registration queue is full: too many streams are waiting to be registered")]
152    RtmpRegistrationQueueFull,
153
154    #[cfg(feature = "rtmp")]
155    #[error("Rtmp server thread exited")]
156    RtmpThreadExited,
157
158    #[cfg(feature = "rtmp")]
159    #[error("Rtmp stream closed: the server is no longer consuming this stream")]
160    RtmpStreamClosed,
161
162    #[cfg(feature = "rtmp")]
163    #[error("Rtmp server already started: clones of one server share a single lifecycle, which can be started only once")]
164    RtmpServerAlreadyStarted,
165
166    #[cfg(feature = "subtitle")]
167    #[error("Subtitle error: {0}")]
168    Subtitle(#[from] crate::subtitle::SubtitleError),
169
170    #[cfg(feature = "wgpu")]
171    #[error("Wgpu filter error: {0}")]
172    WgpuFilter(#[from] crate::wgpu_filter::WgpuFilterError),
173
174    // The allow covers the deprecation that OpenGLFilterError inherits from
175    // the deprecated `opengl` module; the variant must still carry the type.
176    // From is hand-written below the enum (a derived #[from] would re-name
177    // the type in generated code that no #[allow] on the variant reaches).
178    #[cfg(feature = "opengl")]
179    #[allow(deprecated)]
180    #[error("OpenGL filter error: {0}")]
181    OpenGLFilter(#[source] OpenGLFilterError),
182
183    #[error("IO error:{0}")]
184    IO(#[from] io::Error),
185
186    #[error("EOF")]
187    EOF,
188    #[error("Exit")]
189    Exit,
190    #[error("Bug")]
191    Bug,
192
193    #[error("Invalid recipe argument: {0}")]
194    InvalidRecipeArg(String),
195
196    #[error("Container info error: {0}")]
197    ContainerInfo(#[from] ContainerInfoError),
198
199    #[error("Frame export error: {0}")]
200    FrameExport(#[from] crate::core::frame_export::FrameExportError),
201
202    #[error("Video writer error: {0}")]
203    Writer(#[from] crate::core::writer::WriterError),
204
205    #[error("Video writer push error: {0}")]
206    Push(#[from] crate::core::writer::PushError),
207
208    #[error("Frame source thread failed to start")]
209    FrameSourceThreadExited,
210
211    #[error("Packet sink error: {0}")]
212    PacketSink(#[from] PacketSinkError),
213
214    /// CLI-compat pipelines only: a `-vf` command was lowered onto an input
215    /// whose OPENED demuxer does not carry exactly one video stream. The
216    /// check runs on the demuxer instance the pipeline actually executes
217    /// with (no separate probe opening, no TOCTOU window). The facade maps
218    /// this to its public `AmbiguousFilterSource` diagnostic.
219    #[cfg(feature = "cli")]
220    #[error("the per-output video filter requires exactly one video stream in the input; the opened input has {video_streams}")]
221    AmbiguousVideoSource { video_streams: usize },
222
223    /// Strict AVOption handling (CLI-compat pipelines): an option the caller
224    /// supplied was not consumed by the component it targeted. The default
225    /// builder path only WARNS about such leftovers; pipelines built through
226    /// the `cli` feature's entry points fail instead, mirroring fftools'
227    /// `check_avoptions` abort. Only exists with the `cli` feature — the
228    /// feature-off API surface is unchanged.
229    #[cfg(feature = "cli")]
230    #[error("option '{option}' was not consumed by {site}; CLI-compat strict mode treats leftover AVOptions as errors")]
231    UnconsumedCliOption { site: String, option: String },
232}
233
234// `Error` rides in every hot-path `Result` — the per-frame encoder and filter
235// calls return `Result<(), Error>` / `Result<bool, Error>` — so its size is a
236// layout contract, not an implementation detail: one oversized payload grows
237// every such `Result` crate-wide. 64 bytes is the long-standing layout; keep
238// new payloads inside it (use static labels for fixed vocabulary, or box a
239// genuinely large variant). A const assertion rather than a #[test] so that
240// merely compiling the crate enforces the bound for whichever feature-gated
241// variants that build carries — including feature combinations whose tests
242// are compiled but never run.
243const _: () = assert!(
244    std::mem::size_of::<Error>() <= 64,
245    "Error grew past its 64-byte layout: shrink the new payload (static labels) or box the variant"
246);
247
248/// Builder/open-time validation errors for [`crate::VideoWriter`]. Exported here
249/// (not from the crate root) to mirror the existing `OpenInputError` /
250/// `OpenOutputError` organization; the root surface stays the three settled
251/// writer types.
252pub use crate::core::writer::WriterError;
253
254// Hand-written counterpart of the #[from] the sibling variants derive: the
255// error type inherits deprecation from the deprecated `opengl` module, so
256// the conversion is spelled out where the lint can be silenced.
257#[cfg(feature = "opengl")]
258#[allow(deprecated)]
259impl From<OpenGLFilterError> for Error {
260    fn from(err: OpenGLFilterError) -> Self {
261        Error::OpenGLFilter(err)
262    }
263}
264
265/// Errors from the `container_info` queries where the caller asked for an index
266/// outside the container's range. These are caller/argument errors — a bad index
267/// into an otherwise valid container — kept distinct from an open/probe failure
268/// (`OpenInputError` / `FindStreamError`) so retry logic, telemetry, and user
269/// messages can tell "you asked for chapter 5 of a 3-chapter file" apart from
270/// "the file is corrupt or unreadable". Each variant carries the offending
271/// `index` and the container's actual `count`.
272#[derive(thiserror::Error, Debug)]
273#[non_exhaustive]
274pub enum ContainerInfoError {
275    #[error("chapter index {index} out of range: the container has {count} chapter(s)")]
276    ChapterIndexOutOfRange { index: usize, count: usize },
277
278    #[error("stream index {index} out of range: the container has {count} stream(s)")]
279    StreamIndexOutOfRange { index: usize, count: usize },
280}
281
282/// Error type for RTMP streaming operations using StreamBuilder
283#[cfg(feature = "rtmp")]
284#[derive(thiserror::Error, Debug)]
285#[non_exhaustive]
286pub enum StreamError {
287    #[error("missing required parameter: {0}")]
288    MissingParameter(&'static str),
289
290    #[error("input path is not a valid file: {path}")]
291    InputNotFound { path: std::path::PathBuf },
292
293    #[error("ffmpeg error: {0}")]
294    Ffmpeg(#[from] crate::error::Error),
295}
296
297impl PartialEq for Error {
298    /// Structural equality for payload-less variants only. Variants carrying
299    /// an inner error compare unequal even to themselves — use matches! on
300    /// the variant when that is what you mean.
301    fn eq(&self, other: &Self) -> bool {
302        use Error::*;
303        match (self, other) {
304            (NotStarted, NotStarted)
305            | (FilterDescUtf8, FilterDescUtf8)
306            | (FilterNameUtf8, FilterNameUtf8)
307            | (FilterZeroOutputs, FilterZeroOutputs)
308            | (FilterZeroInputs, FilterZeroInputs)
309            | (ParseInteger, ParseInteger)
310            | (FrameFilterDstFinished, FrameFilterDstFinished)
311            | (FrameFilterFrameDuplicateFailed, FrameFilterFrameDuplicateFailed)
312            | (FrameFilterThreadExited, FrameFilterThreadExited)
313            | (FrameSourceThreadExited, FrameSourceThreadExited)
314            | (EOF, EOF)
315            | (Exit, Exit)
316            | (Bug, Bug) => true,
317            #[cfg(feature = "rtmp")]
318            (RtmpCreateStream, RtmpCreateStream)
319            | (RtmpRegistrationQueueFull, RtmpRegistrationQueueFull)
320            | (RtmpThreadExited, RtmpThreadExited)
321            | (RtmpStreamClosed, RtmpStreamClosed)
322            | (RtmpServerAlreadyStarted, RtmpServerAlreadyStarted) => true,
323            _ => false,
324        }
325    }
326}
327
328// No Eq impl: variants carrying payloads are not equal to themselves, so
329// the relation is not reflexive and claiming Eq would be a lie.
330
331#[derive(thiserror::Error, Debug)]
332#[non_exhaustive]
333pub enum DemuxingOperationError {
334    #[error("while reading frame: {0}")]
335    ReadFrameError(DemuxingError),
336
337    #[error("while referencing packet: {0}")]
338    PacketRefError(DemuxingError),
339
340    #[error("while seeking file: {0}")]
341    SeekFileError(DemuxingError),
342
343    #[error("Thread exited")]
344    ThreadExited,
345}
346
347#[derive(thiserror::Error, Debug)]
348#[non_exhaustive]
349pub enum DecodingOperationError {
350    #[error("during frame reference creation: {0}")]
351    FrameRefError(DecodingError),
352
353    #[error("during frame properties copy: {0}")]
354    FrameCopyPropsError(DecodingError),
355
356    #[error("during subtitle decoding: {0}")]
357    DecodeSubtitleError(DecodingError),
358
359    #[error("during subtitle copy: {0}")]
360    CopySubtitleError(DecodingError),
361
362    #[error("during packet submission to decoder: {0}")]
363    SendPacketError(DecodingError),
364
365    #[error("during frame reception from decoder: {0}")]
366    ReceiveFrameError(DecodingError),
367
368    #[error("during frame allocation: {0}")]
369    FrameAllocationError(DecodingError),
370
371    #[error("during packet allocation: {0}")]
372    PacketAllocationError(DecodingError),
373
374    #[error("during AVSubtitle allocation: {0}")]
375    SubtitleAllocationError(DecodingError),
376
377    #[error("corrupt decoded frame")]
378    CorruptFrame,
379
380    #[error("decode error rate exceeded the maximum allowed")]
381    ErrorRateExceeded,
382
383    #[error("during retrieve data on hw: {0}")]
384    HWRetrieveDataError(DecodingError),
385
386    #[error("during cropping: {0}")]
387    CroppingError(DecodingError),
388}
389
390#[derive(thiserror::Error, Debug)]
391#[non_exhaustive]
392pub enum OpenDecoderOperationError {
393    #[error("during context allocation: {0}")]
394    ContextAllocationError(OpenDecoderError),
395
396    #[error("while applying parameters to context: {0}")]
397    ParameterApplicationError(OpenDecoderError),
398
399    #[error("while opening decoder: {0}")]
400    DecoderOpenError(OpenDecoderError),
401
402    #[error("while copying channel layout: {0}")]
403    ChannelLayoutCopyError(OpenDecoderError),
404
405    #[error("while Hw setup: {0}")]
406    HwSetupError(OpenDecoderError),
407
408    #[error("Invalid decoder name")]
409    InvalidName,
410
411    #[error("Thread exited")]
412    ThreadExited,
413}
414
415#[derive(thiserror::Error, Debug)]
416#[non_exhaustive]
417pub enum FilterGraphOperationError {
418    #[error("during requesting oldest frame: {0}")]
419    RequestOldestError(FilterGraphError),
420
421    #[error("during process frames: {0}")]
422    ProcessFramesError(FilterGraphError),
423
424    #[error("during send frames: {0}")]
425    SendFramesError(FilterGraphError),
426
427    #[error("during copying channel layout: {0}")]
428    ChannelLayoutCopyError(FilterGraphError),
429
430    #[error("during buffer source add frame: {0}")]
431    BufferSourceAddFrameError(FilterGraphError),
432
433    #[error("during closing buffer source: {0}")]
434    BufferSourceCloseError(FilterGraphError),
435
436    #[error("during replace buffer: {0}")]
437    BufferReplaceoseError(FilterGraphError),
438
439    #[error("during cloning frame side data: {0}")]
440    FrameSideDataCloneError(FilterGraphError),
441
442    #[error("during parse: {0}")]
443    ParseError(FilterGraphParseError),
444
445    #[error("The data in the frame is invalid or corrupted")]
446    InvalidData,
447
448    #[error(
449        "graph input '{0}' already holds {1} buffered frames and admitting the next \
450         one would raise the best-effort retained-memory estimate to ~{2} bytes, \
451         while another input has not yet delivered its first frame, so the filter \
452         graph cannot be configured; check that every graph input actually produces \
453         data (or produces it within the buffering window)"
454    )]
455    PreConfigQueueOverflow(String, usize, usize),
456
457    // Only constructed on the FFmpeg 8+ buffersrc side-data clone path.
458    #[cfg_attr(not(ffmpeg_8_0), allow(dead_code))]
459    #[error(
460        "graph input '{0}' would deep-copy an estimated {1} bytes of side-data \
461         metadata into the buffersrc parameters, exceeding the side-data clone \
462         estimate threshold; the frame's combined side-data metadata (across its \
463         global and downmix entries) is pathologically large"
464    )]
465    OversizedSideDataClone(String, usize),
466
467    #[error("Thread exited")]
468    ThreadExited,
469}
470
471#[derive(thiserror::Error, Debug)]
472#[non_exhaustive]
473pub enum EncodingOperationError {
474    #[error("during frame submission: {0}")]
475    SendFrameError(EncodingError),
476
477    #[error("during packet retrieval: {0}")]
478    ReceivePacketError(EncodingError),
479
480    #[error("during audio frame receive: {0}")]
481    ReceiveAudioError(EncodingError),
482
483    #[error(": Subtitle packets must have a pts")]
484    SubtitleNotPts,
485
486    #[error(": Muxer already finished")]
487    MuxerFinished,
488
489    /// An output stream buffered more packets before the muxer started than the
490    /// pre-mux queue admits (fftools `AVERROR_BUFFER_TOO_SMALL`, "Too many
491    /// packets buffered for output stream"). Unlike `MuxerFinished` this is a
492    /// hard failure — never a silent truncation — so it must reach the
493    /// scheduler error, not the graceful stop path.
494    #[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")]
495    MuxQueueFull,
496
497    #[error("Encode subtitle error: {0}")]
498    EncodeSubtitle(#[from] EncodeSubtitleError),
499
500    #[error(": {0}")]
501    AllocPacket(AllocPacketError),
502}
503
504#[derive(thiserror::Error, Debug)]
505#[non_exhaustive]
506pub enum MuxingOperationError {
507    #[error("during write header: {0}")]
508    WriteHeader(WriteHeaderError),
509
510    #[error("while initializing bitstream filter chain '{0}': {1}")]
511    BitstreamFilterInit(String, MuxingError),
512
513    #[error("during interleaved write: {0}")]
514    InterleavedWriteError(MuxingError),
515
516    #[error("during trailer write: {0}")]
517    TrailerWriteError(MuxingError),
518
519    #[error("during closing IO: {0}")]
520    IOCloseError(MuxingError),
521
522    #[error("Thread exited")]
523    ThreadExited,
524}
525
526/// Errors specific to packet-sink outputs (`Output::new_by_packet_sink`).
527///
528/// The strict tier fails fast: configuration problems surface from `build()`
529/// or from the job **before any sink callback runs**; per-packet violations
530/// stop the job with the offending packet never delivered. `Clone` is
531/// deliberate — for delivery-path errors the same value is recorded as the
532/// job error and handed to the sink's `on_delivery_error` callback.
533/// [`JobFailed`](Self::JobFailed) is the exception: it is synthesized for
534/// that callback only, while first-error-wins may leave the job result owned
535/// by a sibling worker's error.
536#[derive(thiserror::Error, Debug, Clone)]
537#[non_exhaustive]
538pub enum PacketSinkError {
539    /// A builder option the packet sink cannot honor was set: either a
540    /// container-only option (no container is written, so it could never
541    /// take effect) or a pipeline feature outside the strict tier's
542    /// delivery contract (filters, bitstream filters, subtitle codecs —
543    /// rejected as policy, not for lack of a container).
544    #[error("{0} is not supported on packet-sink outputs")]
545    UnsupportedOption(&'static str),
546
547    /// A stream was configured as `copy`; packet sinks require encoded
548    /// streams.
549    #[error("stream copy is not supported on packet-sink outputs (strict tier requires encoded streams)")]
550    StreamCopyUnsupported,
551
552    /// The output mapped a stream the strict tier cannot deliver (non-H.264
553    /// video, non-AAC audio, or a non-audio/video kind).
554    #[error("{kind} streams are not supported on packet-sink outputs (strict tier)")]
555    UnsupportedStream { kind: &'static str },
556
557    /// The configured encoder is outside the strict-tier v1 whitelist.
558    #[error("encoder '{encoder}' is not on the strict-tier whitelist for {kind} (v1 accepts: {allowed})")]
559    EncoderNotWhitelisted {
560        kind: &'static str,
561        encoder: String,
562        allowed: &'static str,
563    },
564
565    /// No stream was mapped to the packet-sink output.
566    #[error("packet-sink output has no streams")]
567    NoStreams,
568
569    /// An encoder finalized without the out-of-band codec configuration the
570    /// strict tier delivers via `on_stream_info`.
571    #[error("output stream {stream_index}: encoder produced no extradata; the strict tier requires codec configuration (avcC / AudioSpecificConfig) before the first callback")]
572    MissingExtradata { stream_index: usize },
573
574    /// The encoder's codec configuration failed strict-tier validation.
575    #[error("output stream {stream_index}: invalid codec configuration: {reason}")]
576    InvalidExtradata { stream_index: usize, reason: String },
577
578    /// A stream's time base is not a positive rational.
579    #[error("output stream {stream_index}: invalid time base {num}/{den} (positive numerator and denominator required)")]
580    InvalidTimeBase {
581        stream_index: usize,
582        num: i32,
583        den: i32,
584    },
585
586    /// A packet was stamped in a time base other than its stream's.
587    #[error("output stream {stream_index}: packet time base {packet_num}/{packet_den} differs from the stream time base {stream_num}/{stream_den}")]
588    PacketTimeBaseMismatch {
589        stream_index: usize,
590        packet_num: i32,
591        packet_den: i32,
592        stream_num: i32,
593        stream_den: i32,
594    },
595
596    /// A packet carries no pts or dts (`AV_NOPTS_VALUE`).
597    #[error("output stream {stream_index}: packet carries no {which} (strict tier rejects AV_NOPTS_VALUE)")]
598    MissingTimestamp {
599        stream_index: usize,
600        which: &'static str,
601    },
602
603    /// A packet's dts did not strictly increase within its stream.
604    #[error("output stream {stream_index}: non-monotonic dts (previous {prev}, current {current})")]
605    NonMonotonicDts {
606        stream_index: usize,
607        prev: i64,
608        current: i64,
609    },
610
611    /// A packet's pts collided with a still-pending pts on the same stream.
612    #[error("output stream {stream_index}: duplicate pts {pts}")]
613    DuplicatePts { stream_index: usize, pts: i64 },
614
615    /// A packet's pts is earlier than its dts.
616    #[error("output stream {stream_index}: pts {pts} is earlier than dts {dts}")]
617    PtsBeforeDts {
618        stream_index: usize,
619        pts: i64,
620        dts: i64,
621    },
622
623    /// Rescaling a timestamp onto the shared time origin overflowed.
624    #[error("output stream {stream_index}: timestamp overflow while applying the shared time origin")]
625    TimestampOverflow { stream_index: usize },
626
627    /// A packet has no positive duration and none could be derived from the
628    /// stream configuration (frame rate / codec frame size).
629    #[error("output stream {stream_index}: packet duration is absent and cannot be derived (strict tier requires a positive duration)")]
630    MissingDuration { stream_index: usize },
631
632    /// The packet payload failed bitstream validation.
633    #[error("output stream {stream_index}: malformed packet payload: {reason}")]
634    MalformedPacket {
635        stream_index: usize,
636        reason: String,
637    },
638
639    /// Internal sequencing violation: a packet surfaced outside the delivery
640    /// phase.
641    #[error("output stream {stream_index}: packet processed outside the delivery phase (internal sequencing violation)")]
642    PhaseViolation { stream_index: usize },
643
644    /// The stream configuration changed after `on_stream_info` delivered it.
645    #[error("output stream {stream_index}: mid-stream configuration change ({what}); the strict tier requires an immutable stream configuration")]
646    ConfigChange {
647        stream_index: usize,
648        what: String,
649    },
650
651    /// An H.264 access unit carried in-band SPS/PPS parameter sets.
652    #[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)")]
653    InBandParameterSets { stream_index: usize },
654
655    /// The sink's `on_stream_info` callback rejected the configuration.
656    #[error("on_stream_info callback rejected the stream configuration: {error}")]
657    StreamInfoCallbackFailed {
658        #[source]
659        error: crate::core::packet_sink::PacketCallbackError,
660    },
661
662    /// The sink's `on_packet` callback returned an error.
663    #[error("on_packet callback failed on output stream {stream_index}: {error}")]
664    PacketCallbackFailed {
665        stream_index: usize,
666        #[source]
667        error: crate::core::packet_sink::PacketCallbackError,
668    },
669
670    /// The channel adapter's receiver was dropped, cancelling delivery and
671    /// the job.
672    #[error("the packet-sink channel receiver was dropped; delivery cancelled")]
673    ChannelDisconnected,
674
675    /// The job failed outside this sink's delivery path; handed to
676    /// `on_delivery_error` only, while `wait()` keeps the original error.
677    #[error("the job failed outside this packet sink; delivery may have been truncated: {message}")]
678    JobFailed { message: String },
679}
680
681#[derive(thiserror::Error, Debug)]
682#[non_exhaustive]
683pub enum OpenEncoderOperationError {
684    #[error("during frame side data cloning: {0}")]
685    FrameSideDataCloneError(OpenEncoderError),
686
687    #[error("during channel layout copying: {0}")]
688    ChannelLayoutCopyError(OpenEncoderError),
689
690    #[error("during codec opening: {0}")]
691    CodecOpenError(OpenEncoderError),
692
693    #[error("while setting codec parameters: {0}")]
694    CodecParametersError(OpenEncoderError),
695
696    #[error(": unknown format of the frame")]
697    UnknownFrameFormat,
698
699    #[error("while setting subtitle: {0}")]
700    SettingSubtitleError(OpenEncoderError),
701
702    #[error("while Hw setup: {0}")]
703    HwSetupError(OpenEncoderError),
704
705    #[error("during context allocation: {0}")]
706    ContextAllocationError(OpenEncoderError),
707
708    #[error(": no frames were received before EOF; encoder never opened")]
709    NoFramesReceived,
710
711    #[error(": unsupported media type for encoding")]
712    UnsupportedMediaType,
713
714    #[error("Thread exited")]
715    ThreadExited,
716}
717
718#[derive(thiserror::Error, Debug)]
719#[non_exhaustive]
720pub enum UrlError {
721    #[error("Null byte found in string at position {0}")]
722    NullByteError(usize),
723}
724
725impl From<NulError> for Error {
726    fn from(err: NulError) -> Self {
727        Error::Url(UrlError::NullByteError(err.nul_position()))
728    }
729}
730
731#[derive(thiserror::Error, Debug)]
732#[non_exhaustive]
733pub enum OpenInputError {
734    #[error("Memory allocation error")]
735    OutOfMemory,
736
737    #[error("Invalid argument provided")]
738    InvalidArgument,
739
740    #[error("File or stream not found")]
741    NotFound,
742
743    #[error("I/O error occurred while opening the file or stream")]
744    IOError,
745
746    #[error("Pipe error, possibly the stream or data connection was broken")]
747    PipeError,
748
749    #[error("Invalid file descriptor")]
750    BadFileDescriptor,
751
752    #[error("Functionality not implemented or unsupported input format")]
753    NotImplemented,
754
755    #[error("Operation not permitted to access the file or stream")]
756    OperationNotPermitted,
757
758    #[error("The data in the file or stream is invalid or corrupted")]
759    InvalidData,
760
761    #[error("The connection timed out while trying to open the stream")]
762    Timeout,
763
764    /// A builder option carried an invalid value (e.g. a non-positive
765    /// `set_framerate`, a non-finite `set_ts_scale`, an out-of-range
766    /// `set_io_buffer_size`). Setters store values as given and defer
767    /// validation to open time, so a bad value surfaces here instead of
768    /// panicking in the setter.
769    #[error("Invalid input option: {0}")]
770    InvalidOption(String),
771
772    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
773    UnknownError(i32),
774
775    #[error("Invalid source provided")]
776    InvalidSource,
777
778    #[error("Invalid source format:{0}")]
779    InvalidFormat(String),
780
781    #[error("No seek callback is provided")]
782    SeekFunctionMissing,
783}
784
785impl From<i32> for OpenInputError {
786    fn from(err_code: i32) -> Self {
787        match err_code {
788            AVERROR_OUT_OF_MEMORY => OpenInputError::OutOfMemory,
789            AVERROR_INVALID_ARGUMENT => OpenInputError::InvalidArgument,
790            AVERROR_NOT_FOUND => OpenInputError::NotFound,
791            AVERROR_IO_ERROR => OpenInputError::IOError,
792            AVERROR_PIPE_ERROR => OpenInputError::PipeError,
793            AVERROR_BAD_FILE_DESCRIPTOR => OpenInputError::BadFileDescriptor,
794            AVERROR_NOT_IMPLEMENTED => OpenInputError::NotImplemented,
795            AVERROR_OPERATION_NOT_PERMITTED => OpenInputError::OperationNotPermitted,
796            AVERROR_INVALIDDATA => OpenInputError::InvalidData,
797            AVERROR_TIMEOUT => OpenInputError::Timeout,
798            _ => OpenInputError::UnknownError(err_code),
799        }
800    }
801}
802
803const AVERROR_OUT_OF_MEMORY: i32 = AVERROR(ENOMEM);
804const AVERROR_INVALID_ARGUMENT: i32 = AVERROR(EINVAL);
805const AVERROR_NOT_FOUND: i32 = AVERROR(ENOENT);
806const AVERROR_IO_ERROR: i32 = AVERROR(EIO);
807const AVERROR_PIPE_ERROR: i32 = AVERROR(EPIPE);
808const AVERROR_BAD_FILE_DESCRIPTOR: i32 = AVERROR(EBADF);
809const AVERROR_NOT_IMPLEMENTED: i32 = AVERROR(ENOSYS);
810const AVERROR_OPERATION_NOT_PERMITTED: i32 = AVERROR(EPERM);
811const AVERROR_PERMISSION_DENIED: i32 = AVERROR(EACCES);
812const AVERROR_TIMEOUT: i32 = AVERROR(ETIMEDOUT);
813const AVERROR_NOT_SOCKET: i32 = AVERROR(ENOTSOCK);
814const AVERROR_AGAIN: i32 = AVERROR(EAGAIN);
815
816#[derive(thiserror::Error, Debug)]
817#[non_exhaustive]
818pub enum FindStreamError {
819    #[error("Memory allocation error")]
820    OutOfMemory,
821
822    #[error("Invalid argument provided")]
823    InvalidArgument,
824
825    #[error("Reached end of file while looking for stream info")]
826    EndOfFile,
827
828    #[error("Timeout occurred while reading stream info")]
829    Timeout,
830
831    #[error("I/O error occurred while reading stream info")]
832    IOError,
833
834    #[error("The data in the stream is invalid or corrupted")]
835    InvalidData,
836
837    #[error("Functionality not implemented or unsupported stream format")]
838    NotImplemented,
839
840    #[error("Operation not permitted to access the file or stream")]
841    OperationNotPermitted,
842
843    #[error("No Stream found")]
844    NoStreamFound,
845
846    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
847    UnknownError(i32),
848}
849
850impl From<i32> for FindStreamError {
851    fn from(err_code: i32) -> Self {
852        match err_code {
853            AVERROR_OUT_OF_MEMORY => FindStreamError::OutOfMemory,
854            AVERROR_INVALID_ARGUMENT => FindStreamError::InvalidArgument,
855            AVERROR_EOF => FindStreamError::EndOfFile,
856            AVERROR_TIMEOUT => FindStreamError::Timeout,
857            AVERROR_IO_ERROR => FindStreamError::IOError,
858            AVERROR_INVALIDDATA => FindStreamError::InvalidData,
859            AVERROR_NOT_IMPLEMENTED => FindStreamError::NotImplemented,
860            AVERROR_OPERATION_NOT_PERMITTED => FindStreamError::OperationNotPermitted,
861            _ => FindStreamError::UnknownError(err_code),
862        }
863    }
864}
865
866#[derive(thiserror::Error, Debug)]
867#[non_exhaustive]
868pub enum FilterGraphParseError {
869    #[error("Memory allocation error")]
870    OutOfMemory,
871
872    #[error("Invalid argument provided")]
873    InvalidArgument,
874
875    #[error("End of file reached during parsing")]
876    EndOfFile,
877
878    #[error("I/O error occurred during parsing")]
879    IOError,
880
881    #[error("Invalid data encountered during parsing")]
882    InvalidData,
883
884    #[error("Functionality not implemented or unsupported filter format")]
885    NotImplemented,
886
887    #[error("Permission denied during filter graph parsing")]
888    PermissionDenied,
889
890    #[error("Socket operation on non-socket during filter graph parsing")]
891    NotSocket,
892
893    #[error("Option not found during filter graph configuration")]
894    OptionNotFound,
895
896    #[error("Invalid file index {0} in filtergraph description {1}")]
897    InvalidFileIndexInFg(usize, String),
898
899    #[error("Invalid file index {0} in output url: {1}")]
900    InvalidFileIndexInOutput(usize, String),
901
902    #[error("Invalid filter specifier {0}")]
903    InvalidFilterSpecifier(String),
904
905    #[error("Filter '{0}' has output {1} ({2}) unconnected")]
906    OutputUnconnected(String, usize, String),
907
908    #[error("An unknown error occurred. ret: {0}")]
909    UnknownError(i32),
910}
911
912impl From<i32> for FilterGraphParseError {
913    fn from(err_code: i32) -> Self {
914        match err_code {
915            AVERROR_OUT_OF_MEMORY => FilterGraphParseError::OutOfMemory,
916            AVERROR_INVALID_ARGUMENT => FilterGraphParseError::InvalidArgument,
917            AVERROR_EOF => FilterGraphParseError::EndOfFile,
918            AVERROR_IO_ERROR => FilterGraphParseError::IOError,
919            AVERROR_INVALIDDATA => FilterGraphParseError::InvalidData,
920            AVERROR_NOT_IMPLEMENTED => FilterGraphParseError::NotImplemented,
921            AVERROR_OPTION_NOT_FOUND => FilterGraphParseError::OptionNotFound,
922            // EACCES/ENOTSOCK reach here from filters that touch files or
923            // sockets (e.g. `movie=`); map them to the variants this enum
924            // already declares instead of degrading to UnknownError.
925            AVERROR_PERMISSION_DENIED => FilterGraphParseError::PermissionDenied,
926            AVERROR_NOT_SOCKET => FilterGraphParseError::NotSocket,
927            _ => FilterGraphParseError::UnknownError(err_code),
928        }
929    }
930}
931
932#[derive(thiserror::Error, Debug)]
933#[non_exhaustive]
934pub enum AllocOutputContextError {
935    #[error("Memory allocation error")]
936    OutOfMemory,
937
938    #[error("Invalid argument provided")]
939    InvalidArgument,
940
941    #[error("File or stream not found")]
942    NotFound,
943
944    #[error("I/O error occurred while allocating the output context")]
945    IOError,
946
947    #[error("Pipe error, possibly the stream or data connection was broken")]
948    PipeError,
949
950    #[error("Invalid file descriptor")]
951    BadFileDescriptor,
952
953    #[error("Functionality not implemented or unsupported output format")]
954    NotImplemented,
955
956    #[error("Operation not permitted to allocate the output context")]
957    OperationNotPermitted,
958
959    #[error("Permission denied while allocating the output context")]
960    PermissionDenied,
961
962    #[error("The connection timed out while trying to allocate the output context")]
963    Timeout,
964
965    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
966    UnknownError(i32),
967}
968
969impl From<i32> for AllocOutputContextError {
970    fn from(err_code: i32) -> Self {
971        match err_code {
972            AVERROR_OUT_OF_MEMORY => AllocOutputContextError::OutOfMemory,
973            AVERROR_INVALID_ARGUMENT => AllocOutputContextError::InvalidArgument,
974            AVERROR_NOT_FOUND => AllocOutputContextError::NotFound,
975            AVERROR_IO_ERROR => AllocOutputContextError::IOError,
976            AVERROR_PIPE_ERROR => AllocOutputContextError::PipeError,
977            AVERROR_BAD_FILE_DESCRIPTOR => AllocOutputContextError::BadFileDescriptor,
978            AVERROR_NOT_IMPLEMENTED => AllocOutputContextError::NotImplemented,
979            AVERROR_OPERATION_NOT_PERMITTED => AllocOutputContextError::OperationNotPermitted,
980            AVERROR_PERMISSION_DENIED => AllocOutputContextError::PermissionDenied,
981            AVERROR_TIMEOUT => AllocOutputContextError::Timeout,
982            _ => AllocOutputContextError::UnknownError(err_code),
983        }
984    }
985}
986
987#[derive(thiserror::Error, Debug)]
988#[non_exhaustive]
989pub enum OpenOutputError {
990    #[error("Memory allocation error")]
991    OutOfMemory,
992
993    #[error("Invalid argument provided")]
994    InvalidArgument,
995
996    #[error("File or stream not found")]
997    NotFound,
998
999    #[error("I/O error occurred while opening the file or stream")]
1000    IOError,
1001
1002    #[error("Pipe error, possibly the stream or data connection was broken")]
1003    PipeError,
1004
1005    #[error("Invalid file descriptor")]
1006    BadFileDescriptor,
1007
1008    #[error("Functionality not implemented or unsupported output format")]
1009    NotImplemented,
1010
1011    #[error("Operation not permitted to open the file or stream")]
1012    OperationNotPermitted,
1013
1014    #[error("Permission denied while opening the file or stream")]
1015    PermissionDenied,
1016
1017    #[error("The connection timed out while trying to open the file or stream")]
1018    Timeout,
1019
1020    #[error("encoder not found")]
1021    EncoderNotFound,
1022
1023    /// A named encoder could not be opened because the linked FFmpeg build
1024    /// does not provide it — either it was compiled without that encoder
1025    /// (e.g. no `--enable-libx264`) or the name is not a known encoder at all.
1026    /// Unlike the bare [`EncoderNotFound`](Self::EncoderNotFound) errno
1027    /// mapping, this names the encoder so the fix is actionable. `name` is the
1028    /// encoder the caller requested, or the codec the output format guessed
1029    /// when none was set explicitly.
1030    #[error(
1031        "encoder '{name}' is not available in the linked FFmpeg build — link \
1032         an FFmpeg build that provides it (for example one configured with \
1033         --enable-libx264 for libx264), or select a different encoder via \
1034         Output::set_video_codec / set_audio_codec / set_subtitle_codec \
1035         (list what the build provides with codec::get_encoders)"
1036    )]
1037    EncoderUnavailable { name: String },
1038
1039    #[error("Stream map '{0}' matches no streams;")]
1040    MatchesNoStreams(String),
1041
1042    #[error("Invalid label {0}")]
1043    InvalidLabel(String),
1044
1045    #[error("not contain any stream")]
1046    NotContainStream,
1047
1048    #[error("unknown format of the frame")]
1049    UnknownFrameFormat,
1050
1051    #[error("Invalid file index {0} in input url: {1}")]
1052    InvalidFileIndexInIntput(usize, String),
1053
1054    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1055    UnknownError(i32),
1056
1057    #[error("Invalid sink provided")]
1058    InvalidSink,
1059
1060    #[error("No seek callback is provided")]
1061    SeekFunctionMissing,
1062
1063    #[error("Format '{0}' is unsupported")]
1064    FormatUnsupported(String),
1065
1066    #[error("Unknown pixel format: '{0}'")]
1067    UnknownPixelFormat(String),
1068
1069    #[error("Unknown sample format: '{0}'")]
1070    UnknownSampleFormat(String),
1071
1072    /// A builder option carried an invalid value (e.g. a malformed
1073    /// `set_force_key_frames` spec, an out-of-range `set_io_buffer_size`).
1074    /// Setters store values as given and defer validation to open time, so
1075    /// a bad value surfaces here instead of panicking in the setter or
1076    /// forcing a `Result` into the middle of a builder chain.
1077    #[error("Invalid output option: {0}")]
1078    InvalidOption(String),
1079
1080    #[error("Failed to read attachment file '{0}'")]
1081    AttachmentRead(String, #[source] io::Error),
1082
1083    #[error("Attachment file '{0}' is empty")]
1084    AttachmentEmpty(String),
1085
1086    #[error("Attachment file '{0}' is too large ({1} bytes, limit {2} bytes)")]
1087    AttachmentTooLarge(String, u64, u64),
1088
1089    #[error("Attachment mimetype must not be empty (file '{0}')")]
1090    AttachmentEmptyMimetype(String),
1091
1092    /// A per-output video filter ([`Output::set_video_filter`]) was combined
1093    /// with stream copy for the same output's video — either
1094    /// `set_video_codec("copy")` or a copy stream map covering a video
1095    /// stream. Mirrors the FFmpeg CLI error for `-vf` + `-c:v copy`
1096    /// ("Filtering and streamcopy cannot be used together",
1097    /// ffmpeg_mux_init.c streamcopy_init).
1098    ///
1099    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1100    #[error(
1101        "Filtergraph '{0}' was specified, but codec copy was selected for the \
1102         output's video stream. Filtering and streamcopy cannot be used together"
1103    )]
1104    FilterWithStreamCopy(String),
1105
1106    /// A per-output video filter ([`Output::set_video_filter`]) was set on an
1107    /// output whose video stream is fed by a context-level filtergraph
1108    /// (`FfmpegContextBuilder::filter_desc`). Mirrors the FFmpeg CLI error for
1109    /// `-vf` + `-filter_complex` on the same stream (ffmpeg_mux_init.c
1110    /// ost_get_filters: "Simple and complex filtering cannot be used together
1111    /// for the same stream").
1112    ///
1113    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1114    #[error(
1115        "Filtergraph '{0}' was specified for a video stream fed from a \
1116         context-level filtergraph. Simple and complex filtering cannot be \
1117         used together for the same stream"
1118    )]
1119    SimpleAndComplexFilter(String),
1120
1121    /// A per-output simple filtergraph must be one connected linear chain:
1122    /// exactly one video input pad, one video output pad, a single connected
1123    /// component, and a directed path from the input to the output (fftools
1124    /// fg_create_simple's contract plus the topology rules a simple graph
1125    /// implies — a disconnected or unreachable description would encode
1126    /// unrelated frames or hang instead of filtering the stream). The path
1127    /// requirement is structural: the input pad must be wired into the flow
1128    /// that feeds the output pad, while a filter that may discard it at
1129    /// runtime (`streamselect` whose applied `map` selects another input —
1130    /// rewritable mid-stream via `sendcmd`) is accepted, matching the CLI.
1131    /// `reason` names the violated rule. Descriptions that split, merge or
1132    /// source streams belong in the context-level `filter_desc`.
1133    #[error(
1134        "Simple filtergraph '{desc}' is not a single connected chain: {reason}; \
1135         use FfmpegContextBuilder::filter_desc for complex graphs"
1136    )]
1137    SimpleFilterInvalidShape { desc: String, reason: String },
1138
1139    /// A configured [`Output::set_video_filter`] chain that no re-encoded
1140    /// video stream ended up consuming: the output has no video stream at all
1141    /// (audio-only input, `disable_video()`, or maps that matched no video
1142    /// stream). The ffmpeg CLI silently ignores `-vf` in that situation; the
1143    /// crate refuses instead of dropping configuration on the floor.
1144    ///
1145    /// [`Output::set_video_filter`]: crate::core::context::output::Output::set_video_filter
1146    #[error(
1147        "video filter '{0}' was configured, but the output ended up with no \
1148         re-encoded video stream to run it (audio-only input, disable_video(), \
1149         or maps matching no video stream); remove the filter or map a video \
1150         stream"
1151    )]
1152    VideoFilterUnused(String),
1153
1154    /// A per-output simple filtergraph's pads must match the stream's media
1155    /// type (fftools fg_create_simple: "Filtergraph has a %s output, cannot
1156    /// connect it to %s output stream") — e.g. an audio chain like `anull`
1157    /// cannot be attached as a video filter.
1158    ///
1159    /// The media-type labels are static (`"video"`, `"audio"`, ... — the
1160    /// strings fftools prints), which keeps this variant inside `Error`'s
1161    /// 64-byte layout; three owned `String`s would grow every hot-path
1162    /// `Result` in the crate.
1163    #[error(
1164        "Simple filtergraph '{desc}' has a {found} pad, cannot connect it to \
1165         the {expected} stream of this output"
1166    )]
1167    SimpleFilterMediaTypeMismatch {
1168        /// The offending filtergraph description, as configured.
1169        desc: String,
1170        /// The media type of the mismatched pad.
1171        found: &'static str,
1172        /// The media type the output stream requires.
1173        expected: &'static str,
1174    },
1175}
1176
1177impl From<i32> for OpenOutputError {
1178    fn from(err_code: i32) -> Self {
1179        match err_code {
1180            AVERROR_OUT_OF_MEMORY => OpenOutputError::OutOfMemory,
1181            AVERROR_INVALID_ARGUMENT => OpenOutputError::InvalidArgument,
1182            AVERROR_NOT_FOUND => OpenOutputError::NotFound,
1183            AVERROR_IO_ERROR => OpenOutputError::IOError,
1184            AVERROR_PIPE_ERROR => OpenOutputError::PipeError,
1185            AVERROR_BAD_FILE_DESCRIPTOR => OpenOutputError::BadFileDescriptor,
1186            AVERROR_NOT_IMPLEMENTED => OpenOutputError::NotImplemented,
1187            AVERROR_OPERATION_NOT_PERMITTED => OpenOutputError::OperationNotPermitted,
1188            AVERROR_PERMISSION_DENIED => OpenOutputError::PermissionDenied,
1189            AVERROR_TIMEOUT => OpenOutputError::Timeout,
1190            AVERROR_ENCODER_NOT_FOUND => OpenOutputError::EncoderNotFound,
1191            _ => OpenOutputError::UnknownError(err_code),
1192        }
1193    }
1194}
1195
1196#[derive(thiserror::Error, Debug)]
1197#[non_exhaustive]
1198pub enum FindDevicesError {
1199    #[error("AVCaptureDevice class not found in macOS")]
1200    AVCaptureDeviceNotFound,
1201
1202    #[error("current media_type({0}) is not supported")]
1203    MediaTypeSupported(i32),
1204    #[error("current OS is not supported")]
1205    OsNotSupported,
1206    #[error("device_description can not to string")]
1207    UTF8Error,
1208
1209    #[error("Memory allocation error")]
1210    OutOfMemory,
1211    #[error("Invalid argument provided")]
1212    InvalidArgument,
1213    #[error("Device or stream not found")]
1214    NotFound,
1215    #[error("I/O error occurred while accessing the device or stream")]
1216    IOError,
1217    #[error("Operation not permitted for this device or stream")]
1218    OperationNotPermitted,
1219    #[error("Permission denied while accessing the device or stream")]
1220    PermissionDenied,
1221    #[error("This functionality is not implemented")]
1222    NotImplemented,
1223    #[error("Bad file descriptor")]
1224    BadFileDescriptor,
1225    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1226    UnknownError(i32),
1227}
1228
1229impl From<i32> for FindDevicesError {
1230    fn from(err_code: i32) -> Self {
1231        match err_code {
1232            AVERROR_OUT_OF_MEMORY => FindDevicesError::OutOfMemory,
1233            AVERROR_INVALID_ARGUMENT => FindDevicesError::InvalidArgument,
1234            AVERROR_NOT_FOUND => FindDevicesError::NotFound,
1235            AVERROR_IO_ERROR => FindDevicesError::IOError,
1236            AVERROR_OPERATION_NOT_PERMITTED => FindDevicesError::OperationNotPermitted,
1237            AVERROR_PERMISSION_DENIED => FindDevicesError::PermissionDenied,
1238            AVERROR_NOT_IMPLEMENTED => FindDevicesError::NotImplemented,
1239            AVERROR_BAD_FILE_DESCRIPTOR => FindDevicesError::BadFileDescriptor,
1240            _ => FindDevicesError::UnknownError(err_code),
1241        }
1242    }
1243}
1244
1245#[derive(thiserror::Error, Debug)]
1246#[non_exhaustive]
1247pub enum WriteHeaderError {
1248    #[error("Memory allocation error")]
1249    OutOfMemory,
1250
1251    #[error("Invalid argument provided")]
1252    InvalidArgument,
1253
1254    #[error("File or stream not found")]
1255    NotFound,
1256
1257    #[error("I/O error occurred while writing the header")]
1258    IOError,
1259
1260    #[error("Pipe error, possibly the stream or data connection was broken")]
1261    PipeError,
1262
1263    #[error("Invalid file descriptor")]
1264    BadFileDescriptor,
1265
1266    #[error("Functionality not implemented or unsupported output format")]
1267    NotImplemented,
1268
1269    #[error("Operation not permitted to write the header")]
1270    OperationNotPermitted,
1271
1272    #[error("Permission denied while writing the header")]
1273    PermissionDenied,
1274
1275    #[error("The connection timed out while trying to write the header")]
1276    Timeout,
1277
1278    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1279    UnknownError(i32),
1280}
1281
1282impl From<i32> for WriteHeaderError {
1283    fn from(err_code: i32) -> Self {
1284        match err_code {
1285            AVERROR_OUT_OF_MEMORY => WriteHeaderError::OutOfMemory,
1286            AVERROR_INVALID_ARGUMENT => WriteHeaderError::InvalidArgument,
1287            AVERROR_NOT_FOUND => WriteHeaderError::NotFound,
1288            AVERROR_IO_ERROR => WriteHeaderError::IOError,
1289            AVERROR_PIPE_ERROR => WriteHeaderError::PipeError,
1290            AVERROR_BAD_FILE_DESCRIPTOR => WriteHeaderError::BadFileDescriptor,
1291            AVERROR_NOT_IMPLEMENTED => WriteHeaderError::NotImplemented,
1292            AVERROR_OPERATION_NOT_PERMITTED => WriteHeaderError::OperationNotPermitted,
1293            AVERROR_PERMISSION_DENIED => WriteHeaderError::PermissionDenied,
1294            AVERROR_TIMEOUT => WriteHeaderError::Timeout,
1295            _ => WriteHeaderError::UnknownError(err_code),
1296        }
1297    }
1298}
1299
1300#[derive(thiserror::Error, Debug)]
1301#[non_exhaustive]
1302pub enum EncodeSubtitleError {
1303    #[error("Memory allocation error while encoding subtitle")]
1304    OutOfMemory,
1305
1306    #[error("Invalid argument provided for subtitle encoding")]
1307    InvalidArgument,
1308
1309    #[error("Operation not permitted while encoding subtitle")]
1310    OperationNotPermitted,
1311
1312    #[error("The encoding functionality is not implemented or unsupported")]
1313    NotImplemented,
1314
1315    #[error("Encoder temporarily unable to process, please retry")]
1316    TryAgain,
1317
1318    #[error("Subtitle encoding failed with unknown error. ret: {0}")]
1319    UnknownError(i32),
1320}
1321
1322impl From<i32> for EncodeSubtitleError {
1323    fn from(err_code: i32) -> Self {
1324        match err_code {
1325            AVERROR_OUT_OF_MEMORY => EncodeSubtitleError::OutOfMemory,
1326            AVERROR_INVALID_ARGUMENT => EncodeSubtitleError::InvalidArgument,
1327            AVERROR_OPERATION_NOT_PERMITTED => EncodeSubtitleError::OperationNotPermitted,
1328            AVERROR_NOT_IMPLEMENTED => EncodeSubtitleError::NotImplemented,
1329            AVERROR_AGAIN => EncodeSubtitleError::TryAgain,
1330            _ => EncodeSubtitleError::UnknownError(err_code),
1331        }
1332    }
1333}
1334
1335#[derive(thiserror::Error, Debug)]
1336#[non_exhaustive]
1337pub enum AllocPacketError {
1338    #[error("Memory allocation error while alloc packet")]
1339    OutOfMemory,
1340}
1341
1342#[derive(thiserror::Error, Debug)]
1343#[non_exhaustive]
1344pub enum AllocFrameError {
1345    #[error("Memory allocation error while alloc frame")]
1346    OutOfMemory,
1347}
1348
1349/// Errors from [`make_frame_writable`], the safe wrapper over FFmpeg's
1350/// `av_frame_make_writable`: ensuring exclusive ownership of a frame's data
1351/// buffers may allocate new buffers and copy into them, and that underlying
1352/// call can fail. Common AVERROR codes map to named variants; anything else
1353/// carries the raw code.
1354///
1355/// [`make_frame_writable`]: crate::util::ffmpeg_utils::make_frame_writable
1356#[derive(thiserror::Error, Debug)]
1357#[non_exhaustive]
1358pub enum FrameWritableError {
1359    #[error("Memory allocation error while copying frame data")]
1360    OutOfMemory,
1361
1362    #[error("Invalid argument provided")]
1363    InvalidArgument,
1364
1365    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1366    UnknownError(i32),
1367}
1368
1369impl From<i32> for FrameWritableError {
1370    fn from(err_code: i32) -> Self {
1371        match err_code {
1372            AVERROR_OUT_OF_MEMORY => FrameWritableError::OutOfMemory,
1373            AVERROR_INVALID_ARGUMENT => FrameWritableError::InvalidArgument,
1374            _ => FrameWritableError::UnknownError(err_code),
1375        }
1376    }
1377}
1378
1379#[derive(thiserror::Error, Debug)]
1380#[non_exhaustive]
1381pub enum MuxingError {
1382    #[error("Memory allocation error")]
1383    OutOfMemory,
1384
1385    #[error("Invalid argument provided")]
1386    InvalidArgument,
1387
1388    #[error("I/O error occurred during muxing")]
1389    IOError,
1390
1391    #[error("Broken pipe during muxing")]
1392    PipeError,
1393
1394    #[error("Bad file descriptor encountered")]
1395    BadFileDescriptor,
1396
1397    #[error("Functionality not implemented or unsupported")]
1398    NotImplemented,
1399
1400    #[error("Operation not permitted")]
1401    OperationNotPermitted,
1402
1403    #[error("Resource temporarily unavailable")]
1404    TryAgain,
1405
1406    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1407    UnknownError(i32),
1408}
1409
1410impl From<i32> for MuxingError {
1411    fn from(err_code: i32) -> Self {
1412        match err_code {
1413            AVERROR_OUT_OF_MEMORY => MuxingError::OutOfMemory,
1414            AVERROR_INVALID_ARGUMENT => MuxingError::InvalidArgument,
1415            AVERROR_IO_ERROR => MuxingError::IOError,
1416            AVERROR_PIPE_ERROR => MuxingError::PipeError,
1417            AVERROR_BAD_FILE_DESCRIPTOR => MuxingError::BadFileDescriptor,
1418            AVERROR_NOT_IMPLEMENTED => MuxingError::NotImplemented,
1419            AVERROR_OPERATION_NOT_PERMITTED => MuxingError::OperationNotPermitted,
1420            AVERROR_AGAIN => MuxingError::TryAgain,
1421            _ => MuxingError::UnknownError(err_code),
1422        }
1423    }
1424}
1425
1426#[derive(thiserror::Error, Debug)]
1427#[non_exhaustive]
1428pub enum OpenEncoderError {
1429    #[error("Memory allocation error occurred during encoder initialization")]
1430    OutOfMemory,
1431
1432    #[error("Invalid argument provided to encoder")]
1433    InvalidArgument,
1434
1435    #[error("I/O error occurred while opening encoder")]
1436    IOError,
1437
1438    #[error("Broken pipe encountered during encoder initialization")]
1439    PipeError,
1440
1441    #[error("Bad file descriptor used in encoder")]
1442    BadFileDescriptor,
1443
1444    #[error("Encoder functionality not implemented or unsupported")]
1445    NotImplemented,
1446
1447    #[error("Operation not permitted while configuring encoder")]
1448    OperationNotPermitted,
1449
1450    #[error("Resource temporarily unavailable during encoder setup")]
1451    TryAgain,
1452
1453    #[error("An unknown error occurred in encoder setup. ret:{0}")]
1454    UnknownError(i32),
1455}
1456
1457impl From<i32> for OpenEncoderError {
1458    fn from(err_code: i32) -> Self {
1459        match err_code {
1460            AVERROR_OUT_OF_MEMORY => OpenEncoderError::OutOfMemory,
1461            AVERROR_INVALID_ARGUMENT => OpenEncoderError::InvalidArgument,
1462            AVERROR_IO_ERROR => OpenEncoderError::IOError,
1463            AVERROR_PIPE_ERROR => OpenEncoderError::PipeError,
1464            AVERROR_BAD_FILE_DESCRIPTOR => OpenEncoderError::BadFileDescriptor,
1465            AVERROR_NOT_IMPLEMENTED => OpenEncoderError::NotImplemented,
1466            AVERROR_OPERATION_NOT_PERMITTED => OpenEncoderError::OperationNotPermitted,
1467            AVERROR_AGAIN => OpenEncoderError::TryAgain,
1468            _ => OpenEncoderError::UnknownError(err_code),
1469        }
1470    }
1471}
1472
1473#[derive(thiserror::Error, Debug)]
1474#[non_exhaustive]
1475pub enum EncodingError {
1476    #[error("Memory allocation error during encoding")]
1477    OutOfMemory,
1478
1479    #[error("Invalid argument provided to encoder")]
1480    InvalidArgument,
1481
1482    #[error("I/O error occurred during encoding")]
1483    IOError,
1484
1485    #[error("Broken pipe encountered during encoding")]
1486    PipeError,
1487
1488    #[error("Bad file descriptor encountered during encoding")]
1489    BadFileDescriptor,
1490
1491    #[error("Functionality not implemented or unsupported encoding feature")]
1492    NotImplemented,
1493
1494    #[error("Operation not permitted for encoder")]
1495    OperationNotPermitted,
1496
1497    #[error("Resource temporarily unavailable, try again later")]
1498    TryAgain,
1499
1500    #[error("End of stream reached or no more frames to encode")]
1501    EndOfStream,
1502
1503    #[error("An unknown error occurred during encoding. ret: {0}")]
1504    UnknownError(i32),
1505}
1506
1507impl From<i32> for EncodingError {
1508    fn from(err_code: i32) -> Self {
1509        match err_code {
1510            AVERROR_OUT_OF_MEMORY => EncodingError::OutOfMemory,
1511            AVERROR_INVALID_ARGUMENT => EncodingError::InvalidArgument,
1512            AVERROR_IO_ERROR => EncodingError::IOError,
1513            AVERROR_PIPE_ERROR => EncodingError::PipeError,
1514            AVERROR_BAD_FILE_DESCRIPTOR => EncodingError::BadFileDescriptor,
1515            AVERROR_NOT_IMPLEMENTED => EncodingError::NotImplemented,
1516            AVERROR_OPERATION_NOT_PERMITTED => EncodingError::OperationNotPermitted,
1517            AVERROR_AGAIN => EncodingError::TryAgain,
1518            AVERROR_EOF => EncodingError::EndOfStream,
1519            _ => EncodingError::UnknownError(err_code),
1520        }
1521    }
1522}
1523
1524#[derive(thiserror::Error, Debug)]
1525#[non_exhaustive]
1526pub enum FilterGraphError {
1527    #[error("Memory allocation error during filter graph processing")]
1528    OutOfMemory,
1529
1530    #[error("Invalid argument provided to filter graph processing")]
1531    InvalidArgument,
1532
1533    #[error("I/O error occurred during filter graph processing")]
1534    IOError,
1535
1536    #[error("Broken pipe during filter graph processing")]
1537    PipeError,
1538
1539    #[error("Bad file descriptor encountered during filter graph processing")]
1540    BadFileDescriptor,
1541
1542    #[error("Functionality not implemented or unsupported during filter graph processing")]
1543    NotImplemented,
1544
1545    #[error("Operation not permitted during filter graph processing")]
1546    OperationNotPermitted,
1547
1548    #[error("Resource temporarily unavailable during filter graph processing")]
1549    TryAgain,
1550
1551    #[error("EOF")]
1552    EOF,
1553
1554    #[error("An unknown error occurred during filter graph processing. ret:{0}")]
1555    UnknownError(i32),
1556}
1557
1558impl From<i32> for FilterGraphError {
1559    fn from(err_code: i32) -> Self {
1560        match err_code {
1561            AVERROR_OUT_OF_MEMORY => FilterGraphError::OutOfMemory,
1562            AVERROR_INVALID_ARGUMENT => FilterGraphError::InvalidArgument,
1563            AVERROR_IO_ERROR => FilterGraphError::IOError,
1564            AVERROR_PIPE_ERROR => FilterGraphError::PipeError,
1565            AVERROR_BAD_FILE_DESCRIPTOR => FilterGraphError::BadFileDescriptor,
1566            AVERROR_NOT_IMPLEMENTED => FilterGraphError::NotImplemented,
1567            AVERROR_OPERATION_NOT_PERMITTED => FilterGraphError::OperationNotPermitted,
1568            AVERROR_AGAIN => FilterGraphError::TryAgain,
1569            AVERROR_EOF => FilterGraphError::EOF,
1570            _ => FilterGraphError::UnknownError(err_code),
1571        }
1572    }
1573}
1574
1575#[derive(thiserror::Error, Debug)]
1576#[non_exhaustive]
1577pub enum OpenDecoderError {
1578    #[error("Memory allocation error during decoder initialization")]
1579    OutOfMemory,
1580
1581    #[error("Invalid argument provided during decoder initialization")]
1582    InvalidArgument,
1583
1584    #[error("Functionality not implemented or unsupported during decoder initialization")]
1585    NotImplemented,
1586
1587    #[error("Resource temporarily unavailable during decoder initialization")]
1588    TryAgain,
1589
1590    #[error("I/O error occurred during decoder initialization")]
1591    IOError,
1592
1593    #[error("An unknown error occurred during decoder initialization: {0}")]
1594    UnknownError(i32),
1595}
1596
1597impl From<i32> for OpenDecoderError {
1598    fn from(err_code: i32) -> Self {
1599        match err_code {
1600            AVERROR_OUT_OF_MEMORY => OpenDecoderError::OutOfMemory,
1601            AVERROR_INVALID_ARGUMENT => OpenDecoderError::InvalidArgument,
1602            AVERROR_NOT_IMPLEMENTED => OpenDecoderError::NotImplemented,
1603            AVERROR_AGAIN => OpenDecoderError::TryAgain,
1604            AVERROR_IO_ERROR => OpenDecoderError::IOError,
1605            _ => OpenDecoderError::UnknownError(err_code),
1606        }
1607    }
1608}
1609
1610#[derive(thiserror::Error, Debug)]
1611#[non_exhaustive]
1612pub enum DecodingError {
1613    #[error("Memory allocation error")]
1614    OutOfMemory,
1615
1616    #[error("Invalid argument provided")]
1617    InvalidArgument,
1618
1619    #[error("I/O error occurred during decoding")]
1620    IOError,
1621
1622    #[error("Timeout occurred during decoding")]
1623    Timeout,
1624
1625    #[error("Broken pipe encountered during decoding")]
1626    PipeError,
1627
1628    #[error("Bad file descriptor encountered during decoding")]
1629    BadFileDescriptor,
1630
1631    #[error("Unsupported functionality or format encountered")]
1632    NotImplemented,
1633
1634    #[error("Operation not permitted")]
1635    OperationNotPermitted,
1636
1637    #[error("Resource temporarily unavailable")]
1638    TryAgain,
1639
1640    #[error("An unknown decoding error occurred. ret:{0}")]
1641    UnknownError(i32),
1642}
1643
1644impl From<i32> for DecodingError {
1645    fn from(err_code: i32) -> Self {
1646        match err_code {
1647            AVERROR_OUT_OF_MEMORY => DecodingError::OutOfMemory,
1648            AVERROR_INVALID_ARGUMENT => DecodingError::InvalidArgument,
1649            AVERROR_IO_ERROR => DecodingError::IOError,
1650            AVERROR_TIMEOUT => DecodingError::Timeout,
1651            AVERROR_PIPE_ERROR => DecodingError::PipeError,
1652            AVERROR_BAD_FILE_DESCRIPTOR => DecodingError::BadFileDescriptor,
1653            AVERROR_NOT_IMPLEMENTED => DecodingError::NotImplemented,
1654            AVERROR_OPERATION_NOT_PERMITTED => DecodingError::OperationNotPermitted,
1655            AVERROR_AGAIN => DecodingError::TryAgain,
1656            _ => DecodingError::UnknownError(err_code),
1657        }
1658    }
1659}
1660
1661#[derive(thiserror::Error, Debug)]
1662#[non_exhaustive]
1663pub enum DecoderError {
1664    #[error("decoder '{0}' not found")]
1665    NotFound(String),
1666}
1667
1668#[derive(thiserror::Error, Debug)]
1669#[non_exhaustive]
1670pub enum DemuxingError {
1671    #[error("Memory allocation error")]
1672    OutOfMemory,
1673
1674    #[error("Invalid argument provided")]
1675    InvalidArgument,
1676
1677    #[error("I/O error occurred during demuxing")]
1678    IOError,
1679
1680    #[error("End of file reached during demuxing")]
1681    EndOfFile,
1682
1683    #[error("Resource temporarily unavailable")]
1684    TryAgain,
1685
1686    #[error("Functionality not implemented or unsupported")]
1687    NotImplemented,
1688
1689    #[error("Operation not permitted")]
1690    OperationNotPermitted,
1691
1692    #[error("Bad file descriptor encountered")]
1693    BadFileDescriptor,
1694
1695    #[error("Invalid data found when processing input")]
1696    InvalidData,
1697
1698    #[error("{}. ret:{0}", crate::util::ffmpeg_utils::av_err2str(*.0))]
1699    UnknownError(i32),
1700}
1701
1702impl From<i32> for DemuxingError {
1703    fn from(err_code: i32) -> Self {
1704        match err_code {
1705            AVERROR_OUT_OF_MEMORY => DemuxingError::OutOfMemory,
1706            AVERROR_INVALID_ARGUMENT => DemuxingError::InvalidArgument,
1707            AVERROR_IO_ERROR => DemuxingError::IOError,
1708            AVERROR_EOF => DemuxingError::EndOfFile,
1709            AVERROR_AGAIN => DemuxingError::TryAgain,
1710            AVERROR_NOT_IMPLEMENTED => DemuxingError::NotImplemented,
1711            AVERROR_OPERATION_NOT_PERMITTED => DemuxingError::OperationNotPermitted,
1712            AVERROR_BAD_FILE_DESCRIPTOR => DemuxingError::BadFileDescriptor,
1713            AVERROR_INVALIDDATA => DemuxingError::InvalidData,
1714            _ => DemuxingError::UnknownError(err_code),
1715        }
1716    }
1717}
1718
1719/// Errors that can occur during packet scanning operations.
1720#[derive(thiserror::Error, Debug)]
1721#[non_exhaustive]
1722pub enum PacketScannerError {
1723    /// Failed to seek to the requested timestamp.
1724    #[error("while seeking: {0}")]
1725    SeekError(DemuxingError),
1726
1727    /// Failed to read the next packet from the demuxer.
1728    #[error("while reading packet: {0}")]
1729    ReadError(DemuxingError),
1730}
1731
1732#[cfg(test)]
1733mod tests {
1734    // Regression: FrameSourceThreadExited is payload-less, but the manual
1735    // PartialEq whitelist omitted it, so the variant compared unequal to
1736    // itself — breaking the impl's documented "structural equality for
1737    // payload-less variants" contract.
1738    #[test]
1739    fn frame_source_thread_exited_equals_itself() {
1740        use super::Error;
1741        assert_eq!(
1742            Error::FrameSourceThreadExited,
1743            Error::FrameSourceThreadExited
1744        );
1745        assert_ne!(Error::FrameSourceThreadExited, Error::NotStarted);
1746    }
1747
1748    // Regression: FilterGraphParseError declares PermissionDenied and NotSocket,
1749    // but its From<i32> once omitted them, so an EACCES/ENOTSOCK filtergraph
1750    // error degraded to UnknownError and the two declared variants were
1751    // unreachable. Map the codes to the variants the enum already exposes.
1752    #[test]
1753    fn filter_graph_parse_error_maps_permission_and_socket_codes() {
1754        use super::{FilterGraphParseError, AVERROR_NOT_SOCKET, AVERROR_PERMISSION_DENIED};
1755        assert!(matches!(
1756            FilterGraphParseError::from(AVERROR_PERMISSION_DENIED),
1757            FilterGraphParseError::PermissionDenied
1758        ));
1759        assert!(matches!(
1760            FilterGraphParseError::from(AVERROR_NOT_SOCKET),
1761            FilterGraphParseError::NotSocket
1762        ));
1763    }
1764
1765    // make_frame_writable's failure is typed like every other AVERROR-coded
1766    // failure in this file: common codes map to named variants, the rest keep
1767    // the raw code. Pin the mapping and the user-facing Display string.
1768    #[test]
1769    fn frame_writable_error_maps_codes_and_pins_display() {
1770        use super::{Error, FrameWritableError, AVERROR_INVALID_ARGUMENT, AVERROR_OUT_OF_MEMORY};
1771        assert!(matches!(
1772            FrameWritableError::from(AVERROR_OUT_OF_MEMORY),
1773            FrameWritableError::OutOfMemory
1774        ));
1775        assert!(matches!(
1776            FrameWritableError::from(AVERROR_INVALID_ARGUMENT),
1777            FrameWritableError::InvalidArgument
1778        ));
1779        assert!(matches!(
1780            FrameWritableError::from(-99),
1781            FrameWritableError::UnknownError(-99)
1782        ));
1783        let err = Error::from(FrameWritableError::from(AVERROR_OUT_OF_MEMORY));
1784        assert_eq!(
1785            err.to_string(),
1786            "Frame writable error: Memory allocation error while copying frame data"
1787        );
1788    }
1789
1790    // The deprecated OpenGL filter's constructor failures are typed like the
1791    // wgpu successor's: they carry OpenGLFilterError and convert into
1792    // Error::OpenGLFilter. Pin the user-facing Display strings.
1793    #[cfg(feature = "opengl")]
1794    #[test]
1795    fn opengl_filter_error_pins_display() {
1796        use super::{Error, OpenGLFilterError};
1797        let err = Error::from(OpenGLFilterError::InvalidOption(
1798            "fragment shader must declare 'in vec2 TexCoord;'".to_string(),
1799        ));
1800        assert_eq!(
1801            err.to_string(),
1802            "OpenGL filter error: invalid OpenGL filter option: \
1803             fragment shader must declare 'in vec2 TexCoord;'"
1804        );
1805        let err = Error::from(OpenGLFilterError::ContextCreation(
1806            "Failed to create Surfman connection".to_string(),
1807        ));
1808        assert_eq!(
1809            err.to_string(),
1810            "OpenGL filter error: OpenGL context creation failed: \
1811             Failed to create Surfman connection"
1812        );
1813    }
1814}