Skip to main content

edgefirst_codec/
error.rs

1// SPDX-FileCopyrightText: Copyright 2026 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4//! Error types for the codec crate.
5
6use std::fmt;
7
8/// A specific image-format feature that the codec does not implement.
9///
10/// Carried by [`CodecError::Unsupported`]. Use this to distinguish "the byte
11/// string is well-formed but uses a feature we don't decode" from "the byte
12/// string is corrupt" ([`CodecError::InvalidData`]).
13#[non_exhaustive]
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum UnsupportedFeature {
16    /// JPEG SOF2 (progressive baseline).
17    ProgressiveJpeg,
18    /// JPEG SOF1 (extended sequential, Huffman coded).
19    ExtendedSequentialJpeg,
20    /// JPEG SOF9/SOF10/SOF11/SOF13/SOF14/SOF15 (arithmetic coding, in all
21    /// sequential/progressive/lossless/hierarchical flavours).
22    ArithmeticCodedJpeg,
23    /// JPEG SOF3 (lossless predictive).
24    LosslessJpeg,
25    /// JPEG SOF5/SOF6/SOF7 (hierarchical).
26    HierarchicalJpeg,
27    /// JPEG sample precision other than 8 bits per channel.
28    JpegPrecision {
29        /// The unsupported precision reported in the SOF marker.
30        bits: u8,
31    },
32    /// JPEG with a component count we do not handle (anything other than 1
33    /// for greyscale or 3 for YCbCr).
34    JpegComponentCount {
35        /// The unsupported component count reported in the SOF marker.
36        components: u8,
37    },
38    /// PNG data was recognised but the `png` feature is disabled in this build.
39    PngNotEnabled,
40    /// JPEG chroma subsampling configuration where a chroma component is
41    /// sampled at a higher rate than luma along some axis (rejected so the
42    /// downstream upsampler does not divide by zero).
43    JpegChromaSubsampling,
44}
45
46impl fmt::Display for UnsupportedFeature {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            Self::ProgressiveJpeg => write!(f, "progressive JPEG (SOF2)"),
50            Self::ExtendedSequentialJpeg => {
51                write!(f, "extended sequential JPEG (SOF1)")
52            }
53            Self::ArithmeticCodedJpeg => write!(
54                f,
55                "arithmetic-coded JPEG (SOF9/SOF10/SOF11/SOF13/SOF14/SOF15)"
56            ),
57            Self::LosslessJpeg => write!(f, "lossless JPEG (SOF3)"),
58            Self::HierarchicalJpeg => write!(f, "hierarchical JPEG (SOF5/SOF6/SOF7)"),
59            Self::JpegPrecision { bits } => {
60                write!(f, "JPEG sample precision {bits} (only 8-bit is supported)")
61            }
62            Self::JpegComponentCount { components } => {
63                write!(
64                    f,
65                    "JPEG component count {components} (only 1 or 3 are supported)"
66                )
67            }
68            Self::PngNotEnabled => write!(
69                f,
70                "PNG decoding is not enabled in this build (enable the `png` feature)"
71            ),
72            Self::JpegChromaSubsampling => {
73                write!(f, "JPEG chroma subsampling that exceeds luma sampling rate")
74            }
75        }
76    }
77}
78
79/// Errors from image decoding and video encoding and decoding.
80#[derive(Debug)]
81pub enum CodecError {
82    /// Image dimensions exceed the tensor's capacity.
83    InsufficientCapacity {
84        /// (width, height) of the decoded image.
85        image: (usize, usize),
86        /// (width, height) capacity of the destination tensor.
87        tensor: (usize, usize),
88    },
89    /// The tensor's element type is not supported for image decoding.
90    UnsupportedDtype(edgefirst_tensor::DType),
91    /// The requested pixel format is not supported for this image type.
92    UnsupportedFormat(edgefirst_tensor::PixelFormat),
93    /// The image data is well-formed but uses a feature this decoder does
94    /// not implement. Callers can pattern-match on the carried
95    /// [`UnsupportedFeature`] to react programmatically (e.g. transcode
96    /// before retry, surface a precise message, or skip gracefully).
97    Unsupported(UnsupportedFeature),
98    /// The image data is corrupted, truncated, or not a recognized format.
99    InvalidData(String),
100    /// I/O error reading image data.
101    Io(std::io::Error),
102    /// An error from the tensor subsystem.
103    Tensor(edgefirst_tensor::Error),
104    /// No device can do what was asked, for example no hardware video
105    /// encoder.
106    NoDevice(String),
107    /// A configuration the codec or the device rejects.
108    InvalidConfig(String),
109    /// A source frame that does not match the configuration.
110    InvalidInput(String),
111    /// A device operation failed.
112    Device {
113        /// The operation, for example `VIDIOC_QBUF`.
114        op: String,
115        /// The operating system error.
116        source: std::io::Error,
117    },
118    /// The device did not finish an operation in time.
119    Timeout(String),
120    /// A video decoder has nowhere to decode to: the caller holds the
121    /// frames in its output buffers. Dropping decoded frames gives their
122    /// buffers back; a larger pool lets the caller hold more at once.
123    PoolExhausted {
124        /// Output buffers held by frames the caller has not dropped.
125        held: usize,
126        /// Output buffers in the pool.
127        pool: usize,
128    },
129}
130
131impl fmt::Display for CodecError {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        match self {
134            Self::InsufficientCapacity { image, tensor } => write!(
135                f,
136                "image dimensions {}×{} exceed tensor capacity {}×{}",
137                image.0, image.1, tensor.0, tensor.1,
138            ),
139            Self::UnsupportedDtype(dt) => {
140                write!(f, "unsupported tensor dtype {dt:?} for image decode")
141            }
142            Self::UnsupportedFormat(pf) => {
143                write!(f, "unsupported pixel format {pf:?} for image decode")
144            }
145            Self::Unsupported(feat) => write!(f, "unsupported image feature: {feat}"),
146            Self::InvalidData(msg) => write!(f, "invalid image data: {msg}"),
147            Self::Io(e) => write!(f, "I/O error: {e}"),
148            Self::Tensor(e) => write!(f, "tensor error: {e}"),
149            Self::NoDevice(what) => write!(f, "no device: {what}"),
150            Self::InvalidConfig(what) => write!(f, "invalid configuration: {what}"),
151            Self::InvalidInput(what) => write!(f, "invalid input: {what}"),
152            Self::Device { op, source } => write!(f, "{op} failed: {source}"),
153            Self::Timeout(what) => write!(f, "timed out: {what}"),
154            Self::PoolExhausted { held, pool } => write!(
155                f,
156                "{held} of {pool} decoder output buffers are held by frames not yet dropped"
157            ),
158        }
159    }
160}
161
162impl std::error::Error for CodecError {
163    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
164        match self {
165            Self::Io(e) => Some(e),
166            Self::Tensor(e) => Some(e),
167            Self::Device { source, .. } => Some(source),
168            _ => None,
169        }
170    }
171}
172
173impl CodecError {
174    /// The [`CpuAccessDenied`](edgefirst_tensor::Error::CpuAccessDenied)
175    /// refusal this error carries, if any.
176    pub fn cpu_access_denied(&self) -> Option<&edgefirst_tensor::Error> {
177        match self {
178            Self::Tensor(e) => e.cpu_access_denied(),
179            _ => None,
180        }
181    }
182}
183
184impl From<std::io::Error> for CodecError {
185    fn from(e: std::io::Error) -> Self {
186        Self::Io(e)
187    }
188}
189
190impl From<edgefirst_tensor::Error> for CodecError {
191    fn from(e: edgefirst_tensor::Error) -> Self {
192        Self::Tensor(e)
193    }
194}
195
196#[cfg(feature = "png")]
197impl From<zune_png::error::PngDecodeErrors> for CodecError {
198    fn from(e: zune_png::error::PngDecodeErrors) -> Self {
199        Self::InvalidData(format!("PNG: {e}"))
200    }
201}