diff --git a/src/content/area.rs b/src/content/area.rs index 9e28c69..4fbcddd 100644 --- a/src/content/area.rs +++ b/src/content/area.rs @@ -1,5 +1,4 @@ -use super::Size; -use crate::SubError; +use super::{ContentError, Size}; /// Location at which to display the subtitle. #[derive(Debug, Clone, PartialEq, Eq)] @@ -53,7 +52,7 @@ impl Area { } impl TryFrom for Area { - type Error = SubError; + type Error = ContentError; fn try_from(coords_value: AreaValues) -> Result { // Check for weird bounding boxes. Ideally we @@ -63,7 +62,7 @@ impl TryFrom for Area { // have non-negative width and height and we'll // crash if they don't. if coords_value.x2 <= coords_value.x1 || coords_value.y2 <= coords_value.y1 { - Err(SubError::Parse("invalid bounding box".into())) + Err(Self::Error::InvalidAreaBounding) } else { Ok(Self(coords_value)) } diff --git a/src/content/mod.rs b/src/content/mod.rs index 3fee384..91c16dd 100644 --- a/src/content/mod.rs +++ b/src/content/mod.rs @@ -4,3 +4,13 @@ mod size; pub use area::{Area, AreaValues}; pub use size::Size; +use thiserror::Error; + +/// Error for content +#[derive(Debug, Error)] +pub enum ContentError { + /// Indicate an invalid bounding box Area + /// Example: If at least one coordinate value of second point are inferior of first point. + #[error("Invalid bounding box for Area")] + InvalidAreaBounding, +} diff --git a/src/errors.rs b/src/errors.rs index adcae46..3992876 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,46 +1,13 @@ //! Custom error types. -use std::io; -use std::path::PathBuf; use thiserror::Error; -use crate::vobsub::NomError; - /// A type representing errors that are specific to `subtile`. Note that we may /// normally return `Error`, not `SubError`, which allows to return other /// kinds of errors from third-party libraries. #[derive(Debug, Error)] pub enum SubError { - /// Our input data ended sooner than we expected. - #[error("Input ended unexpectedly")] - IncompleteInput, - - /// We were unable to find a required key in an `*.idx` file. - #[error("Could not find required key '{0}'")] - MissingKey(&'static str), - - /// We could not parse a value. - #[error("Could not parse: {0}")] - Parse(String), - - /// We could not process a subtitle image. - #[error("Could not process subtitle image: {0}")] - Image(String), - - /// We have leftover input that we didn't expect. - #[error("Unexpected extra input")] - UnexpectedInput, - - /// If an error happen during parsing with `nom`. - #[error("Parsing error.")] - NomParsing(#[from] NomError), - - /// We could not read a file. - #[error("Could not read '{path}'")] - Io { - /// Source error - source: io::Error, - /// Path of the file we tried to read - path: PathBuf, - }, + /// Error with `VobSub` + #[error("Error with VobSub")] + VobSub(#[from] crate::vobsub::VobSubError), } diff --git a/src/vobsub/idx.rs b/src/vobsub/idx.rs index 5c77e0b..01bafca 100644 --- a/src/vobsub/idx.rs +++ b/src/vobsub/idx.rs @@ -9,9 +9,7 @@ use std::io::prelude::*; use std::io::BufReader; use std::path::Path; -use super::{palette, sub, Palette}; -use crate::errors::SubError; -use crate::vobsub::IResultExt; +use super::{palette, sub, IResultExt, Palette, VobSubError}; /// A `*.idx` file describing the subtitles in a `*.sub` file. #[derive(Debug)] @@ -27,9 +25,9 @@ pub struct Index { impl Index { /// Open an `*.idx` file and the associated `*.sub` file. #[profiling::function] - pub fn open>(path: P) -> Result { + pub fn open>(path: P) -> Result { let path = path.as_ref(); - let mkerr_idx = |source| SubError::Io { + let mkerr_idx = |source| VobSubError::Io { source, path: path.into(), }; @@ -42,13 +40,13 @@ impl Index { sub_path.set_extension("sub"); let sub_path = sub_path.as_path(); - let mut sub = fs::File::open(sub_path).map_err(|source| SubError::Io { + let mut sub = fs::File::open(sub_path).map_err(|source| VobSubError::Io { source, path: sub_path.into(), })?; let mut sub_data = vec![]; sub.read_to_end(&mut sub_data) - .map_err(|source| SubError::Io { + .map_err(|source| VobSubError::Io { source, path: sub_path.into(), })?; @@ -77,10 +75,10 @@ impl Index { /// Read the palette in .idx file content #[profiling::function] -pub fn read_palette(mut input: BufReader, mkerr: &Err) -> Result +pub fn read_palette(mut input: BufReader, mkerr: &Err) -> Result where T: std::io::Read, - Err: Fn(io::Error) -> SubError, + Err: Fn(io::Error) -> VobSubError, { static KEY_VALUE: Lazy = Lazy::new(|| Regex::new("^([A-Za-z/ ]+): (.*)").unwrap()); @@ -93,7 +91,11 @@ where let val = cap.get(2).unwrap().as_str(); match key { "palette" => { - palette_val = Some(palette(val.as_bytes()).to_result_no_rest()?); + palette_val = Some( + palette(val.as_bytes()) + .to_result_no_rest() + .map_err(VobSubError::PaletteError)?, + ); } _ => trace!("Unimplemented idx key: {}", key), } @@ -101,7 +103,7 @@ where buf.clear(); } - let palette = palette_val.ok_or(SubError::MissingKey("palette"))?; + let palette = palette_val.ok_or(VobSubError::MissingKey("palette"))?; Ok(palette) } diff --git a/src/vobsub/img.rs b/src/vobsub/img.rs index 77b6ada..399aacd 100644 --- a/src/vobsub/img.rs +++ b/src/vobsub/img.rs @@ -11,7 +11,8 @@ use nom::{ }; use safemem::write_bytes; -use crate::{content::Size, util::BytesFormatter, SubError}; +use super::VobSubError; +use crate::{content::Size, util::BytesFormatter}; /// A run-length encoded value. #[derive(Debug)] @@ -47,7 +48,7 @@ fn rle(input: (&[u8], usize)) -> IResult<(&[u8], usize), Rle> { /// Decompress the scan-line `input` into `output`, returning the number of /// input bytes consumed. -fn scan_line(input: &[u8], output: &mut [u8]) -> Result { +fn scan_line(input: &[u8], output: &mut [u8]) -> Result { trace!("scan line starting with {:?}", BytesFormatter(input)); let width = output.len(); let mut x = 0; @@ -63,25 +64,25 @@ fn scan_line(input: &[u8], output: &mut [u8]) -> Result { cast::usize(run.cnt) }; if x + count > output.len() { - return Err(SubError::Image("scan line is too long".into())); + return Err(VobSubError::Image("scan line is too long".into())); } write_bytes(&mut output[x..x + count], run.val); x += count; } IResult::Err(err) => match err { nom::Err::Incomplete(needed) => { - return Err(SubError::Image(format!( + return Err(VobSubError::Image(format!( "not enough bytes parsing subtitle scan \ line: {needed:?}" ))); } nom::Err::Error(err) => { - return Err(SubError::Image(format!( + return Err(VobSubError::Image(format!( "error parsing subtitle scan line: {err:?}" ))); } nom::Err::Failure(err) => { - return Err(SubError::Image(format!( + return Err(VobSubError::Image(format!( "Failure parsing subtitle scan line: {err:?}" ))); } @@ -89,7 +90,7 @@ fn scan_line(input: &[u8], output: &mut [u8]) -> Result { } } if x > width { - return Err(SubError::Image("decoded scan line is too long".into())); + return Err(VobSubError::Image("decoded scan line is too long".into())); } // Round up to the next full byte. if pos.1 > 0 { @@ -102,7 +103,7 @@ fn scan_line(input: &[u8], output: &mut [u8]) -> Result { /// order, starting at the upper-left and scanning right and down, with one /// byte for each 2-bit value. #[profiling::function] -pub fn decompress(size: Size, data: [&[u8]; 2]) -> Result, SubError> { +pub fn decompress(size: Size, data: [&[u8]; 2]) -> Result, VobSubError> { trace!( "decompressing image {:?}, max: [0x{:x}, 0x{:x}]", &size, diff --git a/src/vobsub/mod.rs b/src/vobsub/mod.rs index b396a3a..fb47ea4 100644 --- a/src/vobsub/mod.rs +++ b/src/vobsub/mod.rs @@ -73,9 +73,98 @@ pub use self::probe::{is_idx_file, is_sub_file}; pub use self::sub::{subtitles, Subtitle, Subtitles}; use nom::{IResult, Needed}; -use std::fmt; +use std::{fmt, io, path::PathBuf}; use thiserror::Error; +/// Error for `VobSub` handling. +#[derive(Debug, Error)] +pub enum VobSubError { + /// We were unable to find a required key in an `*.idx` file. + #[error("Could not find required key '{0}'")] + MissingKey(&'static str), + + /// We could not parse a value. + #[error("Could not parse: {0}")] + Parse(String), + + /// If invalid number of palette entries found. + #[error("Palette must have 16 entries, found '{0}' one")] + PaletteInvalidEntriesNumbers(usize), + + /// TODO + #[error("Error during palette pasing from .idx file")] + PaletteError(#[source] NomError), + + /// TODO + #[error("invalid scan line offsets : start 0 {start_0}, start 1 {start_1}, end {end}")] + InvalidScanLineOffsets { + /// Start 0 + start_0: usize, + /// Start 1 + start_1: usize, + /// End + end: usize, + }, + + /// If the buffer is too Small for parsing a 16-bits value. + #[error("unexpected end of buffer while parsing 16-bit size")] + BufferTooSmallForU16, + + /// If the buffer is too small to parse a subtitle. + #[error("unexpected end of subtitle data")] + UnexpectedEndOfSubtitleData, + + /// If an error happen during `Control sequence` parsing. + #[error("Error with Control sequence parsing.")] + ControlSequence(#[source] NomError), + + /// If the control offset value tried to leads backwards. + #[error("control offset value tried to leads backwards")] + ControlOffsetWentBackwards, + + /// If `control offset` is bigger than packet size. + #[error("control offset is 0x{offset:x}, but packet is only 0x{packet:x} bytes")] + ControlOffsetBiggerThanPacket { + /// Control offset + offset: usize, + /// Packet size + packet: usize, + }, + + /// If an error happen during `PES Packet` parsing. + #[error("PES packet parsing.")] + PESPacket(#[source] NomError), + + /// If the `control packet` is incmplete + #[error("Incomplete control packet")] + IncompleteControlPacket, + + /// Packet is too short, not bigger to read his size. + #[error("Packet is too short")] + PacketTooShort, + + /// If timing info for Subtitle is missing. + #[error("found subtitle without timing into")] + MissingTimingForSubtitle, + + /// We could not process a subtitle image. + #[error("Could not process subtitle image: {0}")] + Image(String), + + /// Content Error + #[error("Error with data")] + Content(#[from] crate::content::ContentError), + + /// Io error on a path. + #[error("Io error on '{path}'")] + Io { + /// Source error + source: io::Error, + /// Path of the file we tried to read + path: PathBuf, + }, +} + /// Error from `nom` handling #[derive(Debug, Error)] pub enum NomError { diff --git a/src/vobsub/mpeg2/ps.rs b/src/vobsub/mpeg2/ps.rs index 1989e75..9a62481 100644 --- a/src/vobsub/mpeg2/ps.rs +++ b/src/vobsub/mpeg2/ps.rs @@ -14,7 +14,7 @@ use nom::{ }; use std::fmt; -use crate::SubError; +use crate::vobsub::{NomError, VobSubError}; use super::clock::{clock_and_ext, Clock}; use super::pes; @@ -111,7 +111,7 @@ pub struct PesPackets<'a> { } impl<'a> Iterator for PesPackets<'a> { - type Item = Result, SubError>; + type Item = Result, VobSubError>; fn next(&mut self) -> Option { loop { @@ -139,7 +139,9 @@ impl<'a> Iterator for PesPackets<'a> { nom::Err::Incomplete(needed) => { self.remaining = &[]; warn!("Incomplete packet, need: {:?}", needed); - return Some(Err(SubError::Parse("Incomplete PES packet".into()))); + return Some(Err(VobSubError::PESPacket(NomError::IncompleteInput( + needed, + )))); } // We got something that looked like a packet but // wasn't parseable. Log it and keep trying. diff --git a/src/vobsub/palette.rs b/src/vobsub/palette.rs index 381253c..471eaf2 100644 --- a/src/vobsub/palette.rs +++ b/src/vobsub/palette.rs @@ -7,7 +7,7 @@ use nom::{ IResult, }; -use crate::SubError; +use super::VobSubError; /// Parse a single hexadecimal digit. fn from_hex(input: &[u8]) -> std::result::Result { @@ -40,7 +40,7 @@ pub type Palette = [Rgb; 16]; pub fn palette(input: &[u8]) -> IResult<&[u8], Palette> { let res = map_res(separated_list0(tag(b", "), hex_rgb), |vec: Vec>| { if vec.len() != 16 { - return Err(SubError::Parse("Palettes must have 16 entries".into())); + return Err(VobSubError::PaletteInvalidEntriesNumbers(vec.len())); } // Coerce vector to known-size slice. Based on // http://stackoverflow.com/q/25428920/12089. diff --git a/src/vobsub/probe.rs b/src/vobsub/probe.rs index 887069e..522b1ec 100644 --- a/src/vobsub/probe.rs +++ b/src/vobsub/probe.rs @@ -1,15 +1,14 @@ //! Try to guess the types of files on disk. +use super::VobSubError; use std::fs; use std::io::Read; use std::path::Path; -use crate::SubError; - /// Internal helper function which looks for "magic" bytes at the start of /// a file. -fn has_magic(path: &Path, magic: &[u8]) -> Result { - let mkerr = |source| SubError::Io { +fn has_magic(path: &Path, magic: &[u8]) -> Result { + let mkerr = |source| VobSubError::Io { source, path: path.into(), }; @@ -24,7 +23,7 @@ fn has_magic(path: &Path, magic: &[u8]) -> Result { /// # Errors /// /// Will return `Err` if the file can't be read. -pub fn is_idx_file>(path: P) -> Result { +pub fn is_idx_file>(path: P) -> Result { has_magic(path.as_ref(), b"# VobSub index file") } @@ -36,7 +35,7 @@ pub fn is_idx_file>(path: P) -> Result { /// # Errors /// /// Will return `Err` if the file can't be read. -pub fn is_sub_file>(path: P) -> Result { +pub fn is_sub_file>(path: P) -> Result { has_magic(path.as_ref(), &[0x00, 0x00, 0x01, 0xba]) } diff --git a/src/vobsub/sub.rs b/src/vobsub/sub.rs index 48a42ea..462cdb1 100644 --- a/src/vobsub/sub.rs +++ b/src/vobsub/sub.rs @@ -19,14 +19,13 @@ use nom::{ }; use std::{cmp::Ordering, fmt}; -use super::img::decompress; use super::mpeg2::ps; use super::Palette; +use super::{img::decompress, VobSubError}; use crate::{ content::{Area, AreaValues}, util::BytesFormatter, vobsub::IResultExt, - SubError, }; use image::{ImageBuffer, Rgba, RgbaImage}; @@ -275,24 +274,22 @@ impl fmt::Debug for Subtitle { /// Parse a single `u16` value from a buffer. We don't use `nom` for this /// because it has an inconvenient error type. -fn parse_be_u16_as_usize(buff: &[u8]) -> Result<(&[u8], usize), SubError> { +fn parse_be_u16_as_usize(buff: &[u8]) -> Result<(&[u8], usize), VobSubError> { if buff.len() < 2 { - Err(SubError::Parse( - "unexpected end of buffer while parsing 16-bit size".into(), - )) + Err(VobSubError::BufferTooSmallForU16) } else { Ok((&buff[2..], usize::from(buff[0]) << 8 | usize::from(buff[1]))) } } /// Parse a subtitle. -fn subtitle(raw_data: &[u8], base_time: f64) -> Result { +fn subtitle(raw_data: &[u8], base_time: f64) -> Result { // This parser is somewhat non-standard, because we need to work with // explicit offsets into `packet` in several places. // Figure out where our control data starts. if raw_data.len() < 2 { - return Err(SubError::Parse("unexpected end of subtitle data".into())); + return Err(VobSubError::UnexpectedEndOfSubtitleData); } let (_, initial_control_offset) = parse_be_u16_as_usize(&raw_data[2..])?; @@ -310,18 +307,16 @@ fn subtitle(raw_data: &[u8], base_time: f64) -> Result { loop { trace!("looking for control sequence at: 0x{:x}", control_offset); if control_offset >= raw_data.len() { - return Err(SubError::Parse(format!( - "control offset is 0x{:x}, but packet is only 0x{:x} \ - bytes", - control_offset, - raw_data.len() - ))); + return Err(VobSubError::ControlOffsetBiggerThanPacket { + offset: control_offset, + packet: raw_data.len(), + }); } let control_data = &raw_data[control_offset..]; let (_, control) = control_sequence(control_data) .to_result() - .map_err(|source| SubError::NomParsing(source))?; + .map_err(VobSubError::ControlSequence)?; trace!("parsed control sequence: {:?}", &control); @@ -362,7 +357,7 @@ fn subtitle(raw_data: &[u8], base_time: f64) -> Result { let next_control_offset = cast::usize(control.next); match control_offset.cmp(&next_control_offset) { Ordering::Greater => { - return Err(SubError::Parse("control offset went backwards".into())); + return Err(VobSubError::ControlOffsetWentBackwards); } Ordering::Equal => { // This points back at us, so we're the last packet. @@ -376,12 +371,12 @@ fn subtitle(raw_data: &[u8], base_time: f64) -> Result { // Make sure we found all the control commands that we expect. let start_time = - start_time.ok_or_else(|| SubError::Parse("no start time for subtitle".into()))?; - let area = area.ok_or_else(|| SubError::Parse("no area coordinates for subtitle".into()))?; - let palette = palette.ok_or_else(|| SubError::Parse("no palette for subtitle".into()))?; - let alpha = alpha.ok_or_else(|| SubError::Parse("no alpha for subtitle".into()))?; + start_time.ok_or_else(|| VobSubError::Parse("no start time for subtitle".into()))?; + let area = area.ok_or_else(|| VobSubError::Parse("no area coordinates for subtitle".into()))?; + let palette = palette.ok_or_else(|| VobSubError::Parse("no palette for subtitle".into()))?; + let alpha = alpha.ok_or_else(|| VobSubError::Parse("no alpha for subtitle".into()))?; let rle_offsets = - rle_offsets.ok_or_else(|| SubError::Parse("no RLE offsets for subtitle".into()))?; + rle_offsets.ok_or_else(|| VobSubError::Parse("no RLE offsets for subtitle".into()))?; // Decompress our image. // @@ -400,7 +395,11 @@ fn subtitle(raw_data: &[u8], base_time: f64) -> Result { let start_1 = cast::usize(rle_offsets[1]); let end = cast::usize(initial_control_offset + 2); if start_0 > start_1 || start_1 > end { - return Err(SubError::Parse("invalid scan line offsets".into())); + return Err(VobSubError::InvalidScanLineOffsets { + start_0, + start_1, + end, + }); } let image = decompress( area.size(), @@ -442,7 +441,7 @@ struct SubtitlesInternal<'a> { } impl<'a> Iterator for SubtitlesInternal<'a> { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { profiling::scope!("SubtitlesInternal next"); @@ -453,11 +452,7 @@ impl<'a> Iterator for SubtitlesInternal<'a> { // Fetch useful information from our first packet. let pts_dts = match first.pes_packet.header_data.pts_dts { Some(v) => v, - None => { - return Some(Err(SubError::Parse( - "found subtitle without timing into".into(), - ))) - } + None => return Some(Err(VobSubError::MissingTimingForSubtitle)), }; let base_time = pts_dts.pts.as_seconds(); let substream_id = first.pes_packet.substream_id; @@ -465,7 +460,7 @@ impl<'a> Iterator for SubtitlesInternal<'a> { // Figure out how many total bytes we'll need to collect from one // or more PES packets, and collect the first chunk into a buffer. if first.pes_packet.data.len() < 2 { - return Some(Err(SubError::Parse("packet is too short".into()))); + return Some(Err(VobSubError::PacketTooShort)); } let wanted = usize::from(first.pes_packet.data[0]) << 8 | usize::from(first.pes_packet.data[1]); @@ -514,7 +509,7 @@ pub struct Subtitles<'a> { } impl<'a> Iterator for Subtitles<'a> { - type Item = Result; + type Item = Result; // This whole routine exists to make sure that `end_time` is set to a // useful value even if the subtitles themselves didn't supply one.