use std::fmt;
use thiserror::Error;
use crate::{PixelFormat, Rational, SampleFormat};
#[derive(Error, Debug, Clone, PartialEq)]
pub enum FormatError {
#[error("Invalid pixel format: {format}")]
InvalidPixelFormat {
format: String,
},
#[error("Invalid sample format: {format}")]
InvalidSampleFormat {
format: String,
},
#[error("Invalid timestamp: pts={pts}, time_base={time_base:?}")]
InvalidTimestamp {
pts: i64,
time_base: Rational,
},
#[error("Plane index {index} out of bounds (max: {max})")]
PlaneIndexOutOfBounds {
index: usize,
max: usize,
},
#[error("Format conversion failed: {from:?} -> {to:?}")]
ConversionFailed {
from: PixelFormat,
to: PixelFormat,
},
#[error("Audio conversion failed: {from:?} -> {to:?}")]
AudioConversionFailed {
from: SampleFormat,
to: SampleFormat,
},
#[error("Invalid frame data: {0}")]
InvalidFrameData(String),
}
impl FormatError {
#[inline]
#[must_use]
pub fn invalid_pixel_format(format: impl Into<String>) -> Self {
Self::InvalidPixelFormat {
format: format.into(),
}
}
#[inline]
#[must_use]
pub fn invalid_sample_format(format: impl Into<String>) -> Self {
Self::InvalidSampleFormat {
format: format.into(),
}
}
#[inline]
#[must_use]
pub fn invalid_frame_data(reason: impl Into<String>) -> Self {
Self::InvalidFrameData(reason.into())
}
#[inline]
#[must_use]
pub fn plane_out_of_bounds(index: usize, max: usize) -> Self {
Self::PlaneIndexOutOfBounds { index, max }
}
#[inline]
#[must_use]
pub fn conversion_failed(from: PixelFormat, to: PixelFormat) -> Self {
Self::ConversionFailed { from, to }
}
#[inline]
#[must_use]
pub fn audio_conversion_failed(from: SampleFormat, to: SampleFormat) -> Self {
Self::AudioConversionFailed { from, to }
}
#[inline]
#[must_use]
pub fn invalid_timestamp(pts: i64, time_base: Rational) -> Self {
Self::InvalidTimestamp { pts, time_base }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameError {
MismatchedPlaneStride {
planes: usize,
strides: usize,
},
UnsupportedPixelFormat(PixelFormat),
UnsupportedSampleFormat(SampleFormat),
InvalidPlaneCount {
expected: usize,
actual: usize,
},
}
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MismatchedPlaneStride { planes, strides } => {
write!(
f,
"planes and strides length mismatch: {planes} planes, {strides} strides"
)
}
Self::UnsupportedPixelFormat(format) => {
write!(
f,
"cannot allocate frame for unsupported pixel format: {format:?}"
)
}
Self::UnsupportedSampleFormat(format) => {
write!(
f,
"cannot allocate frame for unsupported sample format: {format:?}"
)
}
Self::InvalidPlaneCount { expected, actual } => {
write!(f, "invalid plane count: expected {expected}, got {actual}")
}
}
}
}
impl std::error::Error for FrameError {}
#[derive(Debug, Error)]
pub enum SubtitleError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("unsupported subtitle format: {extension}")]
UnsupportedFormat {
extension: String,
},
#[error("parse error at line {line}: {reason}")]
ParseError {
line: usize,
reason: String,
},
#[error("no valid subtitle events found")]
NoEvents,
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_format_error_invalid_pixel_format() {
let err = FormatError::InvalidPixelFormat {
format: "unknown_xyz".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Invalid pixel format"));
assert!(msg.contains("unknown_xyz"));
let err = FormatError::invalid_pixel_format("bad_format");
let msg = format!("{err}");
assert!(msg.contains("bad_format"));
}
#[test]
fn test_format_error_invalid_sample_format() {
let err = FormatError::InvalidSampleFormat {
format: "unknown_audio".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("Invalid sample format"));
assert!(msg.contains("unknown_audio"));
let err = FormatError::invalid_sample_format("bad_audio");
let msg = format!("{err}");
assert!(msg.contains("bad_audio"));
}
#[test]
fn test_format_error_invalid_timestamp() {
let time_base = Rational::new(1, 90000);
let err = FormatError::InvalidTimestamp {
pts: -100,
time_base,
};
let msg = format!("{err}");
assert!(msg.contains("Invalid timestamp"));
assert!(msg.contains("pts=-100"));
assert!(msg.contains("time_base"));
let err = FormatError::invalid_timestamp(-50, Rational::new(1, 1000));
let msg = format!("{err}");
assert!(msg.contains("-50"));
}
#[test]
fn test_format_error_plane_out_of_bounds() {
let err = FormatError::PlaneIndexOutOfBounds { index: 5, max: 3 };
let msg = format!("{err}");
assert!(msg.contains("Plane index 5"));
assert!(msg.contains("out of bounds"));
assert!(msg.contains("max: 3"));
let err = FormatError::plane_out_of_bounds(10, 2);
let msg = format!("{err}");
assert!(msg.contains("10"));
assert!(msg.contains("2"));
}
#[test]
fn test_format_error_conversion_failed() {
let err = FormatError::ConversionFailed {
from: PixelFormat::Yuv420p,
to: PixelFormat::Rgba,
};
let msg = format!("{err}");
assert!(msg.contains("Format conversion failed"));
assert!(msg.contains("Yuv420p"));
assert!(msg.contains("Rgba"));
let err = FormatError::conversion_failed(PixelFormat::Nv12, PixelFormat::Bgra);
let msg = format!("{err}");
assert!(msg.contains("Nv12"));
assert!(msg.contains("Bgra"));
}
#[test]
fn test_format_error_audio_conversion_failed() {
let err = FormatError::AudioConversionFailed {
from: SampleFormat::I16,
to: SampleFormat::F32,
};
let msg = format!("{err}");
assert!(msg.contains("Audio conversion failed"));
assert!(msg.contains("I16"));
assert!(msg.contains("F32"));
let err = FormatError::audio_conversion_failed(SampleFormat::U8, SampleFormat::F64);
let msg = format!("{err}");
assert!(msg.contains("U8"));
assert!(msg.contains("F64"));
}
#[test]
fn test_format_error_invalid_frame_data() {
let err = FormatError::InvalidFrameData("buffer too small".to_string());
let msg = format!("{err}");
assert!(msg.contains("Invalid frame data"));
assert!(msg.contains("buffer too small"));
let err = FormatError::invalid_frame_data("corrupted header");
let msg = format!("{err}");
assert!(msg.contains("corrupted header"));
}
#[test]
fn test_format_error_is_std_error() {
let err: Box<dyn std::error::Error> = Box::new(FormatError::InvalidPixelFormat {
format: "test".to_string(),
});
assert!(err.to_string().contains("test"));
}
#[test]
fn test_format_error_equality() {
let err1 = FormatError::InvalidPixelFormat {
format: "test".to_string(),
};
let err2 = FormatError::InvalidPixelFormat {
format: "test".to_string(),
};
let err3 = FormatError::InvalidPixelFormat {
format: "other".to_string(),
};
assert_eq!(err1, err2);
assert_ne!(err1, err3);
}
#[test]
fn test_format_error_clone() {
let err1 = FormatError::ConversionFailed {
from: PixelFormat::Yuv420p,
to: PixelFormat::Rgba,
};
let err2 = err1.clone();
assert_eq!(err1, err2);
}
#[test]
fn test_format_error_debug() {
let err = FormatError::PlaneIndexOutOfBounds { index: 3, max: 2 };
let debug_str = format!("{err:?}");
assert!(debug_str.contains("PlaneIndexOutOfBounds"));
assert!(debug_str.contains("index"));
assert!(debug_str.contains("max"));
}
#[test]
fn test_frame_error_display() {
let err = FrameError::MismatchedPlaneStride {
planes: 1,
strides: 2,
};
let msg = format!("{err}");
assert!(msg.contains("planes"));
assert!(msg.contains("strides"));
assert!(msg.contains("mismatch"));
let err = FrameError::UnsupportedPixelFormat(PixelFormat::Other(42));
let msg = format!("{err}");
assert!(msg.contains("unsupported"));
assert!(msg.contains("pixel format"));
let err = FrameError::UnsupportedSampleFormat(SampleFormat::Other(42));
let msg = format!("{err}");
assert!(msg.contains("unsupported"));
assert!(msg.contains("sample format"));
let err = FrameError::InvalidPlaneCount {
expected: 2,
actual: 1,
};
let msg = format!("{err}");
assert!(msg.contains("expected 2"));
assert!(msg.contains("got 1"));
}
}