diff --git a/Cargo.toml b/Cargo.toml index a0043bbe7..8c11ef96a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "lofty", "lofty_attr", "ogg_pager", + "aud_io", "fuzz", "examples/custom_resolver", ] @@ -14,13 +15,16 @@ edition = "2024" rust-version = "1.85" repository = "https://github.com/Serial-ATA/lofty-rs" license = "MIT OR Apache-2.0" +authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"] [workspace.dependencies] lofty = { version = "0.22.4", path = "lofty" } lofty_attr = { version = "0.11.1", path = "lofty_attr" } ogg_pager = { version = "0.7.0", path = "ogg_pager" } +aud_io = { version = "0.1.0", path = "aud_io" } byteorder = "1.5.0" +log = "0.4.27" [workspace.lints.rust] missing_docs = "deny" diff --git a/aud_io/Cargo.toml b/aud_io/Cargo.toml new file mode 100644 index 000000000..0a06e802d --- /dev/null +++ b/aud_io/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "aud_io" +version = "0.1.0" +description = "" # TODO +keywords = ["audio", "mp4"] +categories = ["multimedia", "multimedia::audio", "parser-implementations"] +readme = "" # TODO +include = ["src", "../LICENSE-APACHE", "../LICENSE-MIT"] +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +byteorder.workspace = true +log.workspace = true + +[dev-dependencies] +tempfile = "3.15.0" +test-log = "0.2.16" + +#[lints] +#workspace = true diff --git a/lofty/src/util/alloc.rs b/aud_io/src/alloc.rs similarity index 91% rename from lofty/src/util/alloc.rs rename to aud_io/src/alloc.rs index 0f7941ae1..a116338cf 100644 --- a/lofty/src/util/alloc.rs +++ b/aud_io/src/alloc.rs @@ -1,6 +1,5 @@ use crate::error::Result; -use crate::macros::err; - +use crate::err; use crate::config::global_options; /// Provides the `fallible_repeat` method on `Vec` @@ -49,7 +48,8 @@ impl VecFallibleRepeat for Vec { /// Creates a `Vec` of the specified length, containing copies of `element`. /// /// This should be used through [`try_vec!`](crate::macros::try_vec) -pub(crate) fn fallible_vec_from_element(element: T, expected_size: usize) -> Result> +#[doc(hidden)] +pub fn fallible_vec_from_element(element: T, expected_size: usize) -> Result> where T: Clone, { @@ -59,7 +59,7 @@ where /// Provides the `try_with_capacity` method on `Vec` /// /// This can be used directly. -pub(crate) trait VecFallibleCapacity: Sized { +pub trait VecFallibleCapacity: Sized { /// Same as `Vec::with_capacity`, but takes `GlobalOptions::allocation_limit` into account. /// /// Named `try_with_capacity_stable` to avoid conflicts with the nightly `Vec::try_with_capacity`. @@ -81,7 +81,7 @@ impl VecFallibleCapacity for Vec { #[cfg(test)] mod tests { - use crate::util::alloc::fallible_vec_from_element; + use super::fallible_vec_from_element; #[test_log::test] fn vec_fallible_repeat() { diff --git a/aud_io/src/config/global.rs b/aud_io/src/config/global.rs new file mode 100644 index 000000000..dba9b6f58 --- /dev/null +++ b/aud_io/src/config/global.rs @@ -0,0 +1,100 @@ +use std::cell::UnsafeCell; + +thread_local! { + static GLOBAL_OPTIONS: UnsafeCell = UnsafeCell::new(GlobalOptions::default()); +} + +pub(crate) unsafe fn global_options() -> &'static GlobalOptions { + GLOBAL_OPTIONS.with(|global_options| unsafe { &*global_options.get() }) +} + +/// Options that control all interactions with Lofty for the current thread +/// +/// # Examples +/// +/// ```rust +/// use aud_io::config::{GlobalOptions, apply_global_options}; +/// +/// // I have a custom resolver that I need checked +/// let global_options = GlobalOptions::new().use_custom_resolvers(true); +/// apply_global_options(global_options); +/// ``` +#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +#[non_exhaustive] +pub struct GlobalOptions { + pub(crate) allocation_limit: usize, +} + +impl GlobalOptions { + /// Default allocation limit for any single allocation + pub const DEFAULT_ALLOCATION_LIMIT: usize = 16 * 1024 * 1024; + + /// Creates a new `GlobalOptions`, alias for `Default` implementation + /// + /// See also: [`GlobalOptions::default`] + /// + /// # Examples + /// + /// ```rust + /// use aud_io::config::GlobalOptions; + /// + /// let global_options = GlobalOptions::new(); + /// ``` + #[must_use] + pub const fn new() -> Self { + Self { + allocation_limit: Self::DEFAULT_ALLOCATION_LIMIT, + } + } + + /// The maximum number of bytes to allocate for any single tag item + /// + /// This is a safety measure to prevent allocating too much memory for a single tag item. If a tag item + /// exceeds this limit, the allocator will return [`AudioError::TooMuchData`]. + /// + /// # Examples + /// + /// ```rust + /// use aud_io::config::{GlobalOptions, apply_global_options}; + /// + /// // I have files with gigantic images, I'll double the allocation limit! + /// let global_options = GlobalOptions::new().allocation_limit(32 * 1024 * 1024); + /// apply_global_options(global_options); + /// ``` + pub fn allocation_limit(&mut self, allocation_limit: usize) -> Self { + self.allocation_limit = allocation_limit; + *self + } +} + +impl Default for GlobalOptions { + /// The default implementation for `GlobalOptions` + /// + /// The defaults are as follows: + /// + /// ```rust,ignore + /// GlobalOptions { + /// allocation_limit: Self::DEFAULT_ALLOCATION_LIMIT, + /// } + /// ``` + fn default() -> Self { + Self::new() + } +} + +/// Applies the given `GlobalOptions` to the current thread +/// +/// # Examples +/// +/// ```rust +/// use aud_io::config::{GlobalOptions, apply_global_options}; +/// +/// // I have a custom resolver that I need checked +/// let global_options = GlobalOptions::new().use_custom_resolvers(true); +/// apply_global_options(global_options); +/// ``` +pub fn apply_global_options(options: GlobalOptions) { + GLOBAL_OPTIONS.with(|global_options| unsafe { + *global_options.get() = options; + }); +} diff --git a/aud_io/src/config/mod.rs b/aud_io/src/config/mod.rs new file mode 100644 index 000000000..dd6d62426 --- /dev/null +++ b/aud_io/src/config/mod.rs @@ -0,0 +1,4 @@ +mod global; +pub use global::*; +mod parse; +pub use parse::*; \ No newline at end of file diff --git a/aud_io/src/config/parse.rs b/aud_io/src/config/parse.rs new file mode 100644 index 000000000..a618b6c15 --- /dev/null +++ b/aud_io/src/config/parse.rs @@ -0,0 +1,52 @@ +/// The parsing strictness mode +/// +/// This can be set with [`Probe::options`](crate::probe::Probe). +/// +/// # Examples +/// +/// ```rust,no_run +/// use lofty::config::{ParseOptions, ParsingMode}; +/// use lofty::probe::Probe; +/// +/// # fn main() -> lofty::error::Result<()> { +/// // We only want to read spec-compliant inputs +/// let parsing_options = ParseOptions::new().parsing_mode(ParsingMode::Strict); +/// let tagged_file = Probe::open("foo.mp3")?.options(parsing_options).read()?; +/// # Ok(()) } +/// ``` +#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Default)] +#[non_exhaustive] +pub enum ParsingMode { + /// Will eagerly error on invalid input + /// + /// This mode will eagerly error on any non spec-compliant input. + /// + /// ## Examples of behavior + /// + /// * Unable to decode text - The parser will error and the entire input is discarded + /// * Unable to determine the sample rate - The parser will error and the entire input is discarded + Strict, + /// Default mode, less eager to error on recoverably malformed input + /// + /// This mode will attempt to fill in any holes where possible in otherwise valid, spec-compliant input. + /// + /// NOTE: A readable input does *not* necessarily make it writeable. + /// + /// ## Examples of behavior + /// + /// * Unable to decode text - If valid otherwise, the field will be replaced by an empty string and the parser moves on + /// * Unable to determine the sample rate - The sample rate will be 0 + #[default] + BestAttempt, + /// Least eager to error, may produce invalid/partial output + /// + /// This mode will discard any invalid fields, and ignore the majority of non-fatal errors. + /// + /// If the input is malformed, the resulting tags may be incomplete, and the properties zeroed. + /// + /// ## Examples of behavior + /// + /// * Unable to decode text - The entire item is discarded and the parser moves on + /// * Unable to determine the sample rate - The sample rate will be 0 + Relaxed, +} diff --git a/aud_io/src/error.rs b/aud_io/src/error.rs new file mode 100644 index 000000000..7fe98bac4 --- /dev/null +++ b/aud_io/src/error.rs @@ -0,0 +1,101 @@ +use std::collections::TryReserveError; +use std::fmt::Display; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum AudioError { + // File data related errors + /// Attempting to read/write an abnormally large amount of data + TooMuchData, + /// Expected the data to be a different size than provided + /// + /// This occurs when the size of an item is written as one value, but that size is either too + /// big or small to be valid within the bounds of that item. + // TODO: Should probably have context + SizeMismatch, + + /// Errors that arise while decoding text + TextDecode(&'static str), + /// Arises when an atom contains invalid data + BadAtom(&'static str), + + // Conversions for external errors + /// Unable to convert bytes to a String + StringFromUtf8(std::string::FromUtf8Error), + /// Unable to convert bytes to a str + StrFromUtf8(std::str::Utf8Error), + /// Represents all cases of [`std::io::Error`]. + Io(std::io::Error), + /// Represents all cases of [`std::fmt::Error`]. + Fmt(std::fmt::Error), + /// Failure to allocate enough memory + Alloc(TryReserveError), + /// This should **never** be encountered + Infallible(std::convert::Infallible), +} + +impl Display for AudioError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + // Conversions + AudioError::StringFromUtf8(err) => write!(f, "{err}"), + AudioError::StrFromUtf8(err) => write!(f, "{err}"), + AudioError::Io(err) => write!(f, "{err}"), + AudioError::Fmt(err) => write!(f, "{err}"), + AudioError::Alloc(err) => write!(f, "{err}"), + AudioError::Infallible(_) => write!(f, "A expected condition was not upheld"), + + AudioError::TextDecode(message) => write!(f, "Text decoding: {message}"), + AudioError::BadAtom(message) => write!(f, "MP4 Atom: {message}"), + + // Files + AudioError::TooMuchData => write!( + f, + "Attempted to read/write an abnormally large amount of data" + ), + AudioError::SizeMismatch => write!( + f, + "Encountered an invalid item size, either too big or too small to be valid" + ), + } + } +} + +impl core::error::Error for AudioError {} + +impl From for AudioError { + fn from(input: std::io::Error) -> Self { + AudioError::Io(input) + } +} + +impl From for AudioError { + fn from(input: std::fmt::Error) -> Self { + AudioError::Fmt(input) + } +} + +impl From for AudioError { + fn from(input: std::string::FromUtf8Error) -> Self { + AudioError::StringFromUtf8(input) + } +} + +impl From for AudioError { + fn from(input: std::str::Utf8Error) -> Self { + AudioError::StrFromUtf8(input) + } +} + +impl From for AudioError { + fn from(input: TryReserveError) -> Self { + AudioError::Alloc(input) + } +} + +impl From for AudioError { + fn from(input: std::convert::Infallible) -> Self { + AudioError::Infallible(input) + } +} diff --git a/lofty/src/util/io.rs b/aud_io/src/io.rs similarity index 58% rename from lofty/src/util/io.rs rename to aud_io/src/io.rs index c6ff46529..dd807f746 100644 --- a/lofty/src/util/io.rs +++ b/aud_io/src/io.rs @@ -1,14 +1,14 @@ //! Various traits for reading and writing to file-like objects -use crate::error::{LoftyError, Result}; -use crate::util::math::F80; +use crate::error::{AudioError, Result}; +use crate::math::F80; use std::collections::VecDeque; use std::fs::File; use std::io::{Cursor, Read, Seek, Write}; // TODO: https://github.com/rust-lang/rust/issues/59359 -pub(crate) trait SeekStreamLen: Seek { +pub trait SeekStreamLen: Seek { fn stream_len_hack(&mut self) -> crate::error::Result { use std::io::SeekFrom; @@ -34,7 +34,7 @@ impl SeekStreamLen for T where T: Seek {} /// # Examples /// /// ```rust -/// use lofty::io::Truncate; +/// use aud_io::io::Truncate; /// /// let mut data = vec![1, 2, 3, 4, 5]; /// data.truncate(3); @@ -43,7 +43,7 @@ impl SeekStreamLen for T where T: Seek {} /// ``` pub trait Truncate { /// The error type of the truncation operation - type Error: Into; + type Error: Into; /// Truncate a storage object to the specified length /// @@ -123,14 +123,14 @@ where /// # Examples /// /// ```rust -/// use lofty::io::Length; +/// use aud_io::io::Length; /// /// let data = vec![1, 2, 3, 4, 5]; /// assert_eq!(data.len(), 5); /// ``` pub trait Length { /// The error type of the length operation - type Error: Into; + type Error: Into; /// Get the length of a storage object /// @@ -217,20 +217,20 @@ where /// trait implementations are correct. If this assumption were to be broken, files **may** become corrupted. pub trait FileLike: Read + Write + Seek + Truncate + Length where - ::Error: Into, - ::Error: Into, + ::Error: Into, + ::Error: Into, { } impl FileLike for T where T: Read + Write + Seek + Truncate + Length, - ::Error: Into, - ::Error: Into, + ::Error: Into, + ::Error: Into, { } -pub(crate) trait ReadExt: Read { +pub trait ReadExt: Read { fn read_f80(&mut self) -> Result; } @@ -245,127 +245,3 @@ where Ok(F80::from_be_bytes(bytes)) } } - -#[cfg(test)] -mod tests { - use crate::config::{ParseOptions, WriteOptions}; - use crate::file::AudioFile; - use crate::mpeg::MpegFile; - use crate::tag::Accessor; - - use std::io::{Cursor, Read, Seek, Write}; - - const TEST_ASSET: &str = "tests/files/assets/minimal/full_test.mp3"; - - fn test_asset_contents() -> Vec { - std::fs::read(TEST_ASSET).unwrap() - } - - fn file() -> MpegFile { - let file_contents = test_asset_contents(); - let mut reader = Cursor::new(file_contents); - MpegFile::read_from(&mut reader, ParseOptions::new()).unwrap() - } - - fn alter_tag(file: &mut MpegFile) { - let tag = file.id3v2_mut().unwrap(); - tag.set_artist(String::from("Bar artist")); - } - - fn revert_tag(file: &mut MpegFile) { - let tag = file.id3v2_mut().unwrap(); - tag.set_artist(String::from("Foo artist")); - } - - #[test_log::test] - fn io_save_to_file() { - // Read the file and change the artist - let mut file = file(); - alter_tag(&mut file); - - let mut temp_file = tempfile::tempfile().unwrap(); - let file_content = std::fs::read(TEST_ASSET).unwrap(); - temp_file.write_all(&file_content).unwrap(); - temp_file.rewind().unwrap(); - - // Save the new artist - file.save_to(&mut temp_file, WriteOptions::new().preferred_padding(0)) - .expect("Failed to save to file"); - - // Read the file again and change the artist back - temp_file.rewind().unwrap(); - let mut file = MpegFile::read_from(&mut temp_file, ParseOptions::new()).unwrap(); - revert_tag(&mut file); - - temp_file.rewind().unwrap(); - file.save_to(&mut temp_file, WriteOptions::new().preferred_padding(0)) - .expect("Failed to save to file"); - - // The contents should be the same as the original file - temp_file.rewind().unwrap(); - let mut current_file_contents = Vec::new(); - temp_file.read_to_end(&mut current_file_contents).unwrap(); - - assert_eq!(current_file_contents, test_asset_contents()); - } - - #[test_log::test] - fn io_save_to_vec() { - // Same test as above, but using a Cursor> instead of a file - let mut file = file(); - alter_tag(&mut file); - - let file_content = std::fs::read(TEST_ASSET).unwrap(); - - let mut reader = Cursor::new(file_content); - file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) - .expect("Failed to save to vec"); - - reader.rewind().unwrap(); - let mut file = MpegFile::read_from(&mut reader, ParseOptions::new()).unwrap(); - revert_tag(&mut file); - - reader.rewind().unwrap(); - file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) - .expect("Failed to save to vec"); - - let current_file_contents = reader.into_inner(); - assert_eq!(current_file_contents, test_asset_contents()); - } - - #[test_log::test] - fn io_save_using_references() { - struct File { - buf: Vec, - } - - let mut f = File { - buf: std::fs::read(TEST_ASSET).unwrap(), - }; - - // Same test as above, but using references instead of owned values - let mut file = file(); - alter_tag(&mut file); - - { - let mut reader = Cursor::new(&mut f.buf); - file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) - .expect("Failed to save to vec"); - } - - { - let mut reader = Cursor::new(&f.buf[..]); - file = MpegFile::read_from(&mut reader, ParseOptions::new()).unwrap(); - revert_tag(&mut file); - } - - { - let mut reader = Cursor::new(&mut f.buf); - file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) - .expect("Failed to save to vec"); - } - - let current_file_contents = f.buf; - assert_eq!(current_file_contents, test_asset_contents()); - } -} diff --git a/aud_io/src/lib.rs b/aud_io/src/lib.rs new file mode 100644 index 000000000..aae76aba6 --- /dev/null +++ b/aud_io/src/lib.rs @@ -0,0 +1,8 @@ +pub mod mp4; +pub mod error; +pub mod config; +pub mod io; +pub mod math; +pub mod text; +pub mod macros; +pub mod alloc; diff --git a/aud_io/src/macros.rs b/aud_io/src/macros.rs new file mode 100644 index 000000000..21f911482 --- /dev/null +++ b/aud_io/src/macros.rs @@ -0,0 +1,19 @@ +#[macro_export] +macro_rules! try_vec { + ($elem:expr; $size:expr) => {{ $crate::alloc::fallible_vec_from_element($elem, $size)? }}; +} + +// Shorthand for return Err(AudioError::Variant) +// +// Usage: +// - err!(Variant) -> return Err(AudioError::Variant) +// - err!(Variant(Message)) -> return Err(AudioError:(Message)) +#[macro_export] +macro_rules! err { + ($variant:ident) => { + return Err($crate::error::AudioError::$variant.into()) + }; + ($variant:ident($reason:literal)) => { + return Err($crate::error::AudioError::$variant($reason).into()) + }; +} diff --git a/lofty/src/util/math.rs b/aud_io/src/math.rs similarity index 98% rename from lofty/src/util/math.rs rename to aud_io/src/math.rs index 229c28f5c..bd40cc689 100644 --- a/lofty/src/util/math.rs +++ b/aud_io/src/math.rs @@ -3,7 +3,7 @@ /// This is implemented for all unsigned integers. /// /// NOTE: If the result is less than 1, it will be rounded up to 1. -pub(crate) trait RoundedDivision { +pub trait RoundedDivision { type Output; fn div_round(self, rhs: Rhs) -> Self::Output; @@ -29,7 +29,7 @@ unsigned_rounded_division!(u8, u16, u32, u64, u128, usize); /// /// This is used in AIFF. #[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub(crate) struct F80 { +pub struct F80 { signed: bool, // 15-bit exponent with a bias of 16383 exponent: u16, diff --git a/lofty/src/mp4/atom_info.rs b/aud_io/src/mp4/atom_info.rs similarity index 78% rename from lofty/src/mp4/atom_info.rs rename to aud_io/src/mp4/atom_info.rs index c7b3a1004..066fc2bcf 100644 --- a/lofty/src/mp4/atom_info.rs +++ b/aud_io/src/mp4/atom_info.rs @@ -1,17 +1,18 @@ +use crate::err; +use crate::try_vec; +use crate::text::utf8_decode; +use crate::error::{AudioError, Result}; use crate::config::ParsingMode; -use crate::error::{ErrorKind, LoftyError, Result}; -use crate::macros::{err, try_vec}; -use crate::tag::{ItemKey, TagType}; -use crate::util::text::utf8_decode; use std::borrow::Cow; use std::io::{Read, Seek, SeekFrom}; use byteorder::{BigEndian, ReadBytesExt}; -pub(super) const FOURCC_LEN: u64 = 4; -pub(super) const IDENTIFIER_LEN: u64 = 4; -pub(super) const ATOM_HEADER_LEN: u64 = FOURCC_LEN + IDENTIFIER_LEN; +/// The byte length of an atom FOURCC identifier +pub const FOURCC_LEN: u64 = 4; +pub const IDENTIFIER_LEN: u64 = 4; +pub const ATOM_HEADER_LEN: u64 = FOURCC_LEN + IDENTIFIER_LEN; /// Represents an `MP4` atom identifier #[derive(Eq, PartialEq, Debug, Clone)] @@ -63,55 +64,12 @@ impl<'a> AtomIdent<'a> { } } -impl<'a> TryFrom<&'a ItemKey> for AtomIdent<'a> { - type Error = LoftyError; - - fn try_from(value: &'a ItemKey) -> std::result::Result { - if let Some(mapped_key) = value.map_key(TagType::Mp4Ilst) { - if mapped_key.starts_with("----") { - let mut split = mapped_key.split(':'); - - split.next(); - - let mean = split.next(); - let name = split.next(); - - if let (Some(mean), Some(name)) = (mean, name) { - return Ok(AtomIdent::Freeform { - mean: Cow::Borrowed(mean), - name: Cow::Borrowed(name), - }); - } - } else { - let fourcc = mapped_key.chars().map(|c| c as u8).collect::>(); - - if let Ok(fourcc) = TryInto::<[u8; 4]>::try_into(fourcc) { - return Ok(AtomIdent::Fourcc(fourcc)); - } - } - } - - err!(TextDecode( - "ItemKey does not map to a freeform or fourcc identifier" - )) - } -} - -impl TryFrom for AtomIdent<'static> { - type Error = LoftyError; - - fn try_from(value: ItemKey) -> std::result::Result { - let ret: AtomIdent<'_> = (&value).try_into()?; - Ok(ret.into_owned()) - } -} - #[derive(Debug)] -pub(crate) struct AtomInfo { - pub(crate) start: u64, - pub(crate) len: u64, - pub(crate) extended: bool, - pub(crate) ident: AtomIdent<'static>, +pub struct AtomInfo { + pub start: u64, + pub len: u64, + pub extended: bool, + pub ident: AtomIdent<'static>, } // The spec permits any characters to be used in atom identifiers. This doesn't @@ -124,7 +82,7 @@ fn is_valid_identifier_byte(b: u8) -> bool { } impl AtomInfo { - pub(crate) fn read( + pub fn read( data: &mut R, mut reader_size: u64, parse_mode: ParsingMode, @@ -216,7 +174,7 @@ impl AtomInfo { })) } - pub(crate) fn header_size(&self) -> u64 { + pub fn header_size(&self) -> u64 { if !self.extended { return ATOM_HEADER_LEN; } @@ -263,10 +221,10 @@ where match atom { Some(AtomInfo { - ident: AtomIdent::Fourcc(ref fourcc), - len, - .. - }) if fourcc == name => { + ident: AtomIdent::Fourcc(ref fourcc), + len, + .. + }) if fourcc == name => { if len < 12 { err!(BadAtom("Found an incomplete freeform identifier chunk")); } @@ -286,9 +244,9 @@ where *reader_size -= len; utf8_decode(content).map_err(|_| { - LoftyError::new(ErrorKind::BadAtom( + AudioError::BadAtom( "Found a non UTF-8 string while reading freeform identifier", - )) + ) }) }, _ => err!(BadAtom( diff --git a/lofty/src/mp4/read/atom_reader.rs b/aud_io/src/mp4/atom_reader.rs similarity index 82% rename from lofty/src/mp4/read/atom_reader.rs rename to aud_io/src/mp4/atom_reader.rs index c24032224..df44d5300 100644 --- a/lofty/src/mp4/read/atom_reader.rs +++ b/aud_io/src/mp4/atom_reader.rs @@ -1,8 +1,8 @@ -use crate::config::ParsingMode; use crate::error::Result; -use crate::macros::err; +use crate::err; use crate::mp4::atom_info::AtomInfo; -use crate::util::io::SeekStreamLen; +use crate::io::SeekStreamLen; +use crate::config::ParsingMode; use std::io::{Read, Seek, SeekFrom}; @@ -15,7 +15,7 @@ use byteorder::{BigEndian, ReadBytesExt}; /// * [`Self::next`] to read atoms. /// * `read_u*` methods to read integers without needing to specify the endianness. /// * Bounds checking on reads and seeks to prevent going outside the file. -pub(crate) struct AtomReader +pub struct AtomReader where R: Read + Seek, { @@ -31,7 +31,7 @@ where R: Read + Seek, { /// Create a new `AtomReader` - pub(crate) fn new(mut reader: R, parse_mode: ParsingMode) -> Result { + pub fn new(mut reader: R, parse_mode: ParsingMode) -> Result { let len = reader.stream_len_hack()?; Ok(Self { reader, @@ -47,38 +47,38 @@ where /// This is useful when reading an atom such as `moov`, where we only want to read it and its /// children. We can read the atom, set the bounds to the atom's length, and then read the children /// without worrying about reading past the atom's end. - pub(crate) fn reset_bounds(&mut self, start_position: u64, len: u64) { + pub fn reset_bounds(&mut self, start_position: u64, len: u64) { self.start = start_position; self.remaining_size = len; self.len = len; } - pub(crate) fn read_u8(&mut self) -> std::io::Result { + pub fn read_u8(&mut self) -> std::io::Result { self.remaining_size = self.remaining_size.saturating_sub(1); self.reader.read_u8() } - pub(crate) fn read_u16(&mut self) -> std::io::Result { + pub fn read_u16(&mut self) -> std::io::Result { self.remaining_size = self.remaining_size.saturating_sub(2); self.reader.read_u16::() } - pub(crate) fn read_u24(&mut self) -> std::io::Result { + pub fn read_u24(&mut self) -> std::io::Result { self.remaining_size = self.remaining_size.saturating_sub(3); self.reader.read_u24::() } - pub(crate) fn read_u32(&mut self) -> std::io::Result { + pub fn read_u32(&mut self) -> std::io::Result { self.remaining_size = self.remaining_size.saturating_sub(4); self.reader.read_u32::() } - pub(crate) fn read_u64(&mut self) -> std::io::Result { + pub fn read_u64(&mut self) -> std::io::Result { self.remaining_size = self.remaining_size.saturating_sub(8); self.reader.read_u64::() } - pub(crate) fn read_uint(&mut self, size: usize) -> std::io::Result { + pub fn read_uint(&mut self, size: usize) -> std::io::Result { self.remaining_size = self.remaining_size.saturating_sub(size as u64); self.reader.read_uint::(size) } @@ -86,7 +86,7 @@ where /// Read the next atom in the file /// /// This will leave the reader at the beginning of the atom content. - pub(crate) fn next(&mut self) -> Result> { + pub fn next(&mut self) -> Result> { if self.remaining_size == 0 { return Ok(None); } @@ -98,7 +98,7 @@ where AtomInfo::read(self, self.remaining_size, self.parse_mode) } - pub(crate) fn into_inner(self) -> R { + pub fn into_inner(self) -> R { self.reader } } diff --git a/aud_io/src/mp4/mod.rs b/aud_io/src/mp4/mod.rs new file mode 100644 index 000000000..88a36b021 --- /dev/null +++ b/aud_io/src/mp4/mod.rs @@ -0,0 +1,4 @@ +mod atom_reader; +pub use atom_reader::*; +mod atom_info; +pub use atom_info::*; diff --git a/lofty/src/util/text.rs b/aud_io/src/text.rs similarity index 80% rename from lofty/src/util/text.rs rename to aud_io/src/text.rs index a482b1ba7..fb316bb7c 100644 --- a/lofty/src/util/text.rs +++ b/aud_io/src/text.rs @@ -1,5 +1,5 @@ -use crate::error::{ErrorKind, LoftyError, Result}; -use crate::macros::err; +use crate::error::{AudioError, Result}; +use crate::err; use std::io::Read; @@ -31,36 +31,20 @@ impl TextEncoding { } } - pub(crate) fn verify_latin1(text: &str) -> bool { + pub fn verify_latin1(text: &str) -> bool { text.chars().all(|c| c as u32 <= 255) } - - /// ID3v2.4 introduced two new text encodings. - /// - /// When writing ID3v2.3, we just substitute with UTF-16. - pub(crate) fn to_id3v23(self) -> Self { - match self { - Self::UTF8 | Self::UTF16BE => { - log::warn!( - "Text encoding {:?} is not supported in ID3v2.3, substituting with UTF-16", - self - ); - Self::UTF16 - }, - _ => self, - } - } } #[derive(Eq, PartialEq, Debug)] -pub(crate) struct DecodeTextResult { - pub(crate) content: String, - pub(crate) bytes_read: usize, - pub(crate) bom: [u8; 2], +pub struct DecodeTextResult { + pub content: String, + pub bytes_read: usize, + pub bom: [u8; 2], } impl DecodeTextResult { - pub(crate) fn text_or_none(self) -> Option { + pub fn text_or_none(self) -> Option { if self.content.is_empty() { return None; } @@ -83,28 +67,28 @@ const EMPTY_DECODED_TEXT: DecodeTextResult = DecodeTextResult { /// * Not expect the text to be null terminated /// * Have no byte order mark #[derive(Copy, Clone, Debug)] -pub(crate) struct TextDecodeOptions { +pub struct TextDecodeOptions { pub encoding: TextEncoding, pub terminated: bool, pub bom: [u8; 2], } impl TextDecodeOptions { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self::default() } - pub(crate) fn encoding(mut self, encoding: TextEncoding) -> Self { + pub fn encoding(mut self, encoding: TextEncoding) -> Self { self.encoding = encoding; self } - pub(crate) fn terminated(mut self, terminated: bool) -> Self { + pub fn terminated(mut self, terminated: bool) -> Self { self.terminated = terminated; self } - pub(crate) fn bom(mut self, bom: [u8; 2]) -> Self { + pub fn bom(mut self, bom: [u8; 2]) -> Self { self.bom = bom; self } @@ -120,7 +104,7 @@ impl Default for TextDecodeOptions { } } -pub(crate) fn decode_text(reader: &mut R, options: TextDecodeOptions) -> Result +pub fn decode_text(reader: &mut R, options: TextDecodeOptions) -> Result where R: Read, { @@ -181,7 +165,7 @@ where }, TextEncoding::UTF16BE => utf16_decode_bytes(raw_bytes.as_slice(), u16::from_be_bytes)?, TextEncoding::UTF8 => utf8_decode(raw_bytes) - .map_err(|_| LoftyError::new(ErrorKind::TextDecode("Expected a UTF-8 string")))?, + .map_err(|_| AudioError::TextDecode("Expected a UTF-8 string"))?, }; if read_string.is_empty() { @@ -195,7 +179,7 @@ where }) } -pub(crate) fn read_to_terminator(reader: &mut R, encoding: TextEncoding) -> (Vec, usize) +pub fn read_to_terminator(reader: &mut R, encoding: TextEncoding) -> (Vec, usize) where R: Read, { @@ -229,13 +213,13 @@ where (text_bytes, terminator_len) } -pub(crate) fn latin1_decode(bytes: &[u8]) -> String { +fn latin1_decode(bytes: &[u8]) -> String { let mut text = bytes.iter().map(|c| *c as char).collect::(); trim_end_nulls(&mut text); text } -pub(crate) fn utf8_decode(bytes: Vec) -> Result { +pub fn utf8_decode(bytes: Vec) -> Result { String::from_utf8(bytes) .map(|mut text| { trim_end_nulls(&mut text); @@ -244,22 +228,22 @@ pub(crate) fn utf8_decode(bytes: Vec) -> Result { .map_err(Into::into) } -pub(crate) fn utf8_decode_str(bytes: &[u8]) -> Result<&str> { +pub fn utf8_decode_str(bytes: &[u8]) -> Result<&str> { std::str::from_utf8(bytes) .map(trim_end_nulls_str) .map_err(Into::into) } -pub(crate) fn utf16_decode(words: &[u16]) -> Result { +pub fn utf16_decode(words: &[u16]) -> Result { String::from_utf16(words) .map(|mut text| { trim_end_nulls(&mut text); text }) - .map_err(|_| LoftyError::new(ErrorKind::TextDecode("Given an invalid UTF-16 string"))) + .map_err(|_| AudioError::TextDecode("Given an invalid UTF-16 string")) } -pub(crate) fn utf16_decode_bytes(bytes: &[u8], endianness: fn([u8; 2]) -> u16) -> Result { +pub fn utf16_decode_bytes(bytes: &[u8], endianness: fn([u8; 2]) -> u16) -> Result { if bytes.is_empty() { return Ok(String::new()); } @@ -278,7 +262,7 @@ pub(crate) fn utf16_decode_bytes(bytes: &[u8], endianness: fn([u8; 2]) -> u16) - utf16_decode(&unverified) } -pub(crate) fn encode_text(text: &str, text_encoding: TextEncoding, terminated: bool) -> Vec { +pub fn encode_text(text: &str, text_encoding: TextEncoding, terminated: bool) -> Vec { match text_encoding { TextEncoding::Latin1 => { let mut out = text.chars().map(|c| c as u8).collect::>(); @@ -303,14 +287,14 @@ pub(crate) fn encode_text(text: &str, text_encoding: TextEncoding, terminated: b } } -pub(crate) fn trim_end_nulls(text: &mut String) { +fn trim_end_nulls(text: &mut String) { if text.ends_with('\0') { let new_len = text.trim_end_matches('\0').len(); text.truncate(new_len); } } -pub(crate) fn trim_end_nulls_str(text: &str) -> &str { +fn trim_end_nulls_str(text: &str) -> &str { text.trim_end_matches('\0') } @@ -339,7 +323,7 @@ fn utf16_encode( #[cfg(test)] mod tests { - use crate::util::text::{TextDecodeOptions, TextEncoding}; + use crate::text::{TextDecodeOptions, TextEncoding}; use std::io::Cursor; const TEST_STRING: &str = "l\u{00f8}ft\u{00a5}"; @@ -353,7 +337,7 @@ mod tests { ], u16::from_be_bytes, ) - .unwrap(); + .unwrap(); assert_eq!(utf16_decode, TEST_STRING.to_string()); @@ -364,14 +348,14 @@ mod tests { ]), TextDecodeOptions::new().encoding(TextEncoding::UTF16), ) - .unwrap(); + .unwrap(); let le_utf16_decode = super::decode_text( &mut Cursor::new(&[ 0xFF, 0xFE, 0x6C, 0x00, 0xF8, 0x00, 0x66, 0x00, 0x74, 0x00, 0xA5, 0x00, 0x00, 0x00, ]), TextDecodeOptions::new().encoding(TextEncoding::UTF16), ) - .unwrap(); + .unwrap(); assert_eq!(be_utf16_decode.content, le_utf16_decode.content); assert_eq!(be_utf16_decode.bytes_read, le_utf16_decode.bytes_read); @@ -381,7 +365,7 @@ mod tests { &mut TEST_STRING.as_bytes(), TextDecodeOptions::new().encoding(TextEncoding::UTF8), ) - .unwrap(); + .unwrap(); assert_eq!(utf8_decode.content, TEST_STRING.to_string()); } diff --git a/lofty/Cargo.toml b/lofty/Cargo.toml index b243ffe9e..ac415f847 100644 --- a/lofty/Cargo.toml +++ b/lofty/Cargo.toml @@ -20,8 +20,9 @@ byteorder = { workspace = true } flate2 = { version = "1.0.30", optional = true } # Proc macros lofty_attr = { workspace = true } +aud_io = { workspace = true } # Debug logging -log = "0.4.22" +log = { workspace = true } # OGG Vorbis/Opus ogg_pager = "0.7.0" # Key maps diff --git a/lofty/src/aac/header.rs b/lofty/src/aac/header.rs index f7411cd90..db3c5503d 100644 --- a/lofty/src/aac/header.rs +++ b/lofty/src/aac/header.rs @@ -1,4 +1,3 @@ -use crate::config::ParsingMode; use crate::error::Result; use crate::macros::decode_err; use crate::mp4::{AudioObjectType, SAMPLE_RATES}; @@ -6,6 +5,8 @@ use crate::mpeg::MpegVersion; use std::io::{Read, Seek, SeekFrom}; +use aud_io::config::ParsingMode; + // Used to compare the headers up to the home bit. // If they aren't equal, something is broken. pub(super) const HEADER_MASK: u32 = 0xFFFF_FFE0; diff --git a/lofty/src/aac/read.rs b/lofty/src/aac/read.rs index 6418f6b92..923c15aff 100644 --- a/lofty/src/aac/read.rs +++ b/lofty/src/aac/read.rs @@ -1,16 +1,18 @@ use super::AacFile; use super::header::{ADTSHeader, HEADER_MASK}; -use crate::config::{ParseOptions, ParsingMode}; +use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v2::header::Id3v2Header; use crate::id3::v2::read::parse_id3v2; use crate::id3::{ID3FindResults, find_id3v1}; -use crate::macros::{decode_err, err, parse_mode_choice}; +use crate::macros::decode_err; use crate::mpeg::header::{HeaderCmpResult, cmp_header, search_for_frame_sync}; use std::io::{Read, Seek, SeekFrom}; use byteorder::ReadBytesExt; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; #[allow(clippy::unnecessary_wraps)] pub(super) fn read_from(reader: &mut R, parse_options: ParseOptions) -> Result @@ -47,7 +49,7 @@ where let skip_footer = header.flags.footer; let Some(new_stream_len) = stream_len.checked_sub(u64::from(header.size)) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_len = new_stream_len; @@ -72,7 +74,7 @@ where log::debug!("Skipping ID3v2 footer"); let Some(new_stream_len) = stream_len.checked_sub(10) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_len = new_stream_len; @@ -108,7 +110,7 @@ where if header.is_some() { let Some(new_stream_len) = stream_len.checked_sub(128) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_len = new_stream_len; @@ -121,15 +123,12 @@ where decode_err!(@BAIL Mpeg, "File contains an invalid frame"); }; - if first_frame_header.sample_rate == 0 { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL Mpeg, "Sample rate is 0"), - ); + if first_frame_header.sample_rate == 0 && parse_mode == ParsingMode::Strict { + decode_err!(@BAIL Mpeg, "Sample rate is 0") } - if first_frame_header.bitrate == 0 { - parse_mode_choice!(parse_mode, STRICT: decode_err!(@BAIL Mpeg, "Bitrate is 0"),); + if first_frame_header.bitrate == 0 && parse_mode == ParsingMode::Strict { + decode_err!(@BAIL Mpeg, "Bitrate is 0") } // Read as many frames as we can to try and find the average bitrate diff --git a/lofty/src/ape/header.rs b/lofty/src/ape/header.rs index 23c0f45f7..aac7cb1e0 100644 --- a/lofty/src/ape/header.rs +++ b/lofty/src/ape/header.rs @@ -1,11 +1,11 @@ use crate::error::Result; use crate::macros::decode_err; -use crate::util::io::SeekStreamLen; use std::io::{Read, Seek, SeekFrom}; use std::ops::Neg; use byteorder::{LittleEndian, ReadBytesExt}; +use aud_io::io::SeekStreamLen; #[derive(Copy, Clone)] pub(crate) struct ApeHeader { diff --git a/lofty/src/ape/properties.rs b/lofty/src/ape/properties.rs index fcaf47cb6..7b77e9f9c 100644 --- a/lofty/src/ape/properties.rs +++ b/lofty/src/ape/properties.rs @@ -1,4 +1,3 @@ -use crate::config::ParsingMode; use crate::error::Result; use crate::macros::decode_err; use crate::properties::FileProperties; @@ -7,6 +6,7 @@ use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; +use aud_io::config::ParsingMode; /// An APE file's audio properties #[derive(Clone, Debug, PartialEq, Eq, Default)] diff --git a/lofty/src/ape/read.rs b/lofty/src/ape/read.rs index d6d3e620b..93502ff3a 100644 --- a/lofty/src/ape/read.rs +++ b/lofty/src/ape/read.rs @@ -8,10 +8,12 @@ use crate::id3::v1::tag::Id3v1Tag; use crate::id3::v2::read::parse_id3v2; use crate::id3::v2::tag::Id3v2Tag; use crate::id3::{FindId3v2Config, ID3FindResults, find_id3v1, find_id3v2, find_lyrics3v2}; -use crate::macros::{decode_err, err}; +use crate::macros::decode_err; use std::io::{Read, Seek, SeekFrom}; +use aud_io::err as io_err; + pub(crate) fn read_from(data: &mut R, parse_options: ParseOptions) -> Result where R: Read + Seek, @@ -39,7 +41,7 @@ where let Some(new_stream_length) = stream_len.checked_sub(u64::from(header.full_tag_size())) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_len = new_stream_length; @@ -88,7 +90,7 @@ where let ape_header = read_ape_header(data, false)?; let Some(new_stream_length) = stream_len.checked_sub(u64::from(ape_header.size)) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_len = new_stream_length; @@ -113,7 +115,7 @@ where if id3v1_header.is_some() { id3v1_tag = id3v1; let Some(new_stream_length) = stream_len.checked_sub(128) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_len = new_stream_length; @@ -122,7 +124,7 @@ where // Next, check for a Lyrics3v2 tag, and skip over it, as it's no use to us let ID3FindResults(_, lyrics3v2_size) = find_lyrics3v2(data)?; let Some(new_stream_length) = stream_len.checked_sub(u64::from(lyrics3v2_size)) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_len = new_stream_length; diff --git a/lofty/src/ape/tag/mod.rs b/lofty/src/ape/tag/mod.rs index bf779a0af..046c5307a 100644 --- a/lofty/src/ape/tag/mod.rs +++ b/lofty/src/ape/tag/mod.rs @@ -11,13 +11,13 @@ use crate::tag::{ Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, try_parse_year, }; use crate::util::flag_item; -use crate::util::io::{FileLike, Truncate}; use std::borrow::Cow; use std::io::Write; use std::ops::Deref; use lofty_attr::tag; +use aud_io::io::{FileLike, Truncate}; macro_rules! impl_accessor { ($($name:ident => $($key:literal)|+;)+) => { diff --git a/lofty/src/ape/tag/read.rs b/lofty/src/ape/tag/read.rs index 6608896f6..300292425 100644 --- a/lofty/src/ape/tag/read.rs +++ b/lofty/src/ape/tag/read.rs @@ -5,13 +5,15 @@ use crate::ape::constants::{APE_PREAMBLE, INVALID_KEYS}; use crate::ape::header::{self, ApeHeader}; use crate::config::ParseOptions; use crate::error::Result; -use crate::macros::{decode_err, err, try_vec}; +use crate::macros::decode_err; use crate::tag::ItemValue; -use crate::util::text::utf8_decode; use std::io::{Read, Seek, SeekFrom}; use byteorder::{LittleEndian, ReadBytesExt}; +use aud_io::text::utf8_decode; +use aud_io::try_vec; +use aud_io::err as io_err; pub(crate) fn read_ape_tag_with_header( data: &mut R, @@ -31,7 +33,7 @@ where let value_size = data.read_u32::()?; if value_size > remaining_size { - err!(SizeMismatch); + io_err!(SizeMismatch); } remaining_size -= 4; diff --git a/lofty/src/ape/tag/write.rs b/lofty/src/ape/tag/write.rs index 7e0eaac24..9104999a1 100644 --- a/lofty/src/ape/tag/write.rs +++ b/lofty/src/ape/tag/write.rs @@ -8,11 +8,12 @@ use crate::id3::{FindId3v2Config, find_id3v1, find_id3v2, find_lyrics3v2}; use crate::macros::{decode_err, err}; use crate::probe::Probe; use crate::tag::item::ItemValueRef; -use crate::util::io::{FileLike, Truncate}; use std::io::{Cursor, Seek, SeekFrom, Write}; use byteorder::{LittleEndian, WriteBytesExt}; +use aud_io::io::{FileLike, Truncate}; +use aud_io::err as io_err; #[allow(clippy::shadow_unrelated)] pub(crate) fn write_to<'a, F, I>( @@ -205,7 +206,7 @@ where let size = tag_write.get_ref().len(); if size as u64 + 32 > u64::from(u32::MAX) { - err!(TooMuchData); + io_err!(TooMuchData); } let mut footer = [0_u8; 32]; diff --git a/lofty/src/config/global_options.rs b/lofty/src/config/global_options.rs index 4f08e1dfb..e514e78be 100644 --- a/lofty/src/config/global_options.rs +++ b/lofty/src/config/global_options.rs @@ -28,8 +28,8 @@ pub struct GlobalOptions { } impl GlobalOptions { - /// Default allocation limit for any single tag item - pub const DEFAULT_ALLOCATION_LIMIT: usize = 16 * 1024 * 1024; + /// Default allocation limit for any single allocation + pub const DEFAULT_ALLOCATION_LIMIT: usize = aud_io::config::GlobalOptions::DEFAULT_ALLOCATION_LIMIT; /// Creates a new `GlobalOptions`, alias for `Default` implementation /// @@ -72,7 +72,7 @@ impl GlobalOptions { /// The maximum number of bytes to allocate for any single tag item /// /// This is a safety measure to prevent allocating too much memory for a single tag item. If a tag item - /// exceeds this limit, the allocator will return [`ErrorKind::TooMuchData`](crate::error::ErrorKind::TooMuchData). + /// exceeds this limit, the allocator will return [`TooMuchData`](aud_io::error::AudioError::TooMuchData). /// /// # Examples /// @@ -143,6 +143,8 @@ impl Default for GlobalOptions { /// apply_global_options(global_options); /// ``` pub fn apply_global_options(options: GlobalOptions) { + aud_io::config::apply_global_options(aud_io::config::GlobalOptions::new().allocation_limit(options.allocation_limit)); + GLOBAL_OPTIONS.with(|global_options| unsafe { *global_options.get() = options; }); diff --git a/lofty/src/config/mod.rs b/lofty/src/config/mod.rs index 16d19fc27..3c7438152 100644 --- a/lofty/src/config/mod.rs +++ b/lofty/src/config/mod.rs @@ -5,7 +5,7 @@ mod parse_options; mod write_options; pub use global_options::{GlobalOptions, apply_global_options}; -pub use parse_options::{ParseOptions, ParsingMode}; +pub use parse_options::*; pub use write_options::WriteOptions; pub(crate) use global_options::global_options; diff --git a/lofty/src/config/parse_options.rs b/lofty/src/config/parse_options.rs index e6178d22f..6a45745dd 100644 --- a/lofty/src/config/parse_options.rs +++ b/lofty/src/config/parse_options.rs @@ -1,3 +1,5 @@ +pub use aud_io::config::ParsingMode; + /// Options to control how Lofty parses a file #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[non_exhaustive] @@ -163,56 +165,3 @@ impl ParseOptions { *self } } - -/// The parsing strictness mode -/// -/// This can be set with [`Probe::options`](crate::probe::Probe). -/// -/// # Examples -/// -/// ```rust,no_run -/// use lofty::config::{ParseOptions, ParsingMode}; -/// use lofty::probe::Probe; -/// -/// # fn main() -> lofty::error::Result<()> { -/// // We only want to read spec-compliant inputs -/// let parsing_options = ParseOptions::new().parsing_mode(ParsingMode::Strict); -/// let tagged_file = Probe::open("foo.mp3")?.options(parsing_options).read()?; -/// # Ok(()) } -/// ``` -#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Default)] -#[non_exhaustive] -pub enum ParsingMode { - /// Will eagerly error on invalid input - /// - /// This mode will eagerly error on any non spec-compliant input. - /// - /// ## Examples of behavior - /// - /// * Unable to decode text - The parser will error and the entire input is discarded - /// * Unable to determine the sample rate - The parser will error and the entire input is discarded - Strict, - /// Default mode, less eager to error on recoverably malformed input - /// - /// This mode will attempt to fill in any holes where possible in otherwise valid, spec-compliant input. - /// - /// NOTE: A readable input does *not* necessarily make it writeable. - /// - /// ## Examples of behavior - /// - /// * Unable to decode text - If valid otherwise, the field will be replaced by an empty string and the parser moves on - /// * Unable to determine the sample rate - The sample rate will be 0 - #[default] - BestAttempt, - /// Least eager to error, may produce invalid/partial output - /// - /// This mode will discard any invalid fields, and ignore the majority of non-fatal errors. - /// - /// If the input is malformed, the resulting tags may be incomplete, and the properties zeroed. - /// - /// ## Examples of behavior - /// - /// * Unable to decode text - The entire item is discarded and the parser moves on - /// * Unable to determine the sample rate - The sample rate will be 0 - Relaxed, -} diff --git a/lofty/src/error.rs b/lofty/src/error.rs index 9b172489d..94b573405 100644 --- a/lofty/src/error.rs +++ b/lofty/src/error.rs @@ -24,14 +24,8 @@ pub enum ErrorKind { UnknownFormat, // File data related errors - /// Attempting to read/write an abnormally large amount of data - TooMuchData, - /// Expected the data to be a different size than provided - /// - /// This occurs when the size of an item is written as one value, but that size is either too - /// big or small to be valid within the bounds of that item. - // TODO: Should probably have context - SizeMismatch, + /// Represents all cases of [`aud_io::error::AudioError`]. + AudioIo(aud_io::error::AudioError), /// Errors that occur while decoding a file FileDecoding(FileDecodingError), /// Errors that occur while encoding a file @@ -48,15 +42,11 @@ pub enum ErrorKind { UnsupportedTag, /// Arises when a tag is expected (Ex. found an "ID3 " chunk in a WAV file), but isn't found FakeTag, - /// Errors that arise while decoding text - TextDecode(&'static str), /// Arises when decoding OR encoding a problematic [`Timestamp`](crate::tag::items::Timestamp) BadTimestamp(&'static str), /// Errors that arise while reading/writing ID3v2 tags Id3v2(Id3v2Error), - /// Arises when an atom contains invalid data - BadAtom(&'static str), /// Arises when attempting to use [`Atom::merge`](crate::mp4::Atom::merge) with mismatching identifiers AtomMismatch, @@ -450,6 +440,14 @@ impl From for LoftyError { } } +impl From for LoftyError { + fn from(input: aud_io::error::AudioError) -> Self { + Self { + kind: ErrorKind::AudioIo(input), + } + } +} + impl From for LoftyError { fn from(input: FileDecodingError) -> Self { Self { @@ -545,26 +543,17 @@ impl Display for LoftyError { "Attempted to write a tag to a format that does not support it" ), ErrorKind::FakeTag => write!(f, "Reading: Expected a tag, found invalid data"), - ErrorKind::TextDecode(message) => write!(f, "Text decoding: {message}"), ErrorKind::BadTimestamp(message) => { write!(f, "Encountered an invalid timestamp: {message}") }, ErrorKind::Id3v2(ref id3v2_err) => write!(f, "{id3v2_err}"), - ErrorKind::BadAtom(message) => write!(f, "MP4 Atom: {message}"), ErrorKind::AtomMismatch => write!( f, "MP4 Atom: Attempted to use `Atom::merge()` with mismatching identifiers" ), // Files - ErrorKind::TooMuchData => write!( - f, - "Attempted to read/write an abnormally large amount of data" - ), - ErrorKind::SizeMismatch => write!( - f, - "Encountered an invalid item size, either too big or too small to be valid" - ), + ErrorKind::AudioIo(ref err) => write!(f, "{err}"), ErrorKind::FileDecoding(ref file_decode_err) => write!(f, "{file_decode_err}"), ErrorKind::FileEncoding(ref file_encode_err) => write!(f, "{file_encode_err}"), diff --git a/lofty/src/file/audio_file.rs b/lofty/src/file/audio_file.rs index 4541d1c4d..c30f60e77 100644 --- a/lofty/src/file/audio_file.rs +++ b/lofty/src/file/audio_file.rs @@ -3,11 +3,12 @@ use crate::config::{ParseOptions, WriteOptions}; use crate::error::{LoftyError, Result}; use crate::tag::TagType; -use crate::util::io::{FileLike, Length, Truncate}; use std::fs::OpenOptions; use std::io::{Read, Seek}; use std::path::Path; +use aud_io::io::{FileLike, Length, Truncate}; + /// Provides various methods for interaction with a file pub trait AudioFile: Into { /// The struct the file uses for audio properties diff --git a/lofty/src/file/tagged_file.rs b/lofty/src/file/tagged_file.rs index eb28bae36..2384bd20c 100644 --- a/lofty/src/file/tagged_file.rs +++ b/lofty/src/file/tagged_file.rs @@ -5,10 +5,11 @@ use crate::error::{LoftyError, Result}; use crate::properties::FileProperties; use crate::tag::{Tag, TagExt, TagType}; -use crate::util::io::{FileLike, Length, Truncate}; use std::fs::File; use std::io::{Read, Seek}; +use aud_io::io::{FileLike, Length, Truncate}; + /// Provides a common interface between [`TaggedFile`] and [`BoundTaggedFile`] pub trait TaggedFileExt { /// Returns the file's [`FileType`] diff --git a/lofty/src/flac/block.rs b/lofty/src/flac/block.rs index ee2a596ad..b3943fff3 100644 --- a/lofty/src/flac/block.rs +++ b/lofty/src/flac/block.rs @@ -1,11 +1,11 @@ #![allow(dead_code)] use crate::error::Result; -use crate::macros::try_vec; use std::io::{Read, Seek, SeekFrom}; use byteorder::{BigEndian, ReadBytesExt}; +use aud_io::try_vec; pub(in crate::flac) const BLOCK_ID_STREAMINFO: u8 = 0; pub(in crate::flac) const BLOCK_ID_PADDING: u8 = 1; diff --git a/lofty/src/flac/mod.rs b/lofty/src/flac/mod.rs index 11349b4a2..1cd6ff025 100644 --- a/lofty/src/flac/mod.rs +++ b/lofty/src/flac/mod.rs @@ -17,11 +17,11 @@ use crate::ogg::tag::VorbisCommentsRef; use crate::ogg::{OggPictureStorage, VorbisComments}; use crate::picture::{Picture, PictureInformation}; use crate::tag::TagExt; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use lofty_attr::LoftyFile; +use aud_io::io::{FileLike, Length, Truncate}; // Exports pub use properties::FlacProperties; diff --git a/lofty/src/flac/read.rs b/lofty/src/flac/read.rs index 1a7b6e714..87fb199a5 100644 --- a/lofty/src/flac/read.rs +++ b/lofty/src/flac/read.rs @@ -1,16 +1,18 @@ use super::FlacFile; use super::block::Block; use super::properties::FlacProperties; -use crate::config::{ParseOptions, ParsingMode}; +use crate::config::ParseOptions; use crate::error::Result; use crate::flac::block::{BLOCK_ID_PICTURE, BLOCK_ID_STREAMINFO, BLOCK_ID_VORBIS_COMMENTS}; use crate::id3::v2::read::parse_id3v2; use crate::id3::{FindId3v2Config, ID3FindResults, find_id3v2}; -use crate::macros::{decode_err, err}; +use crate::macros::decode_err; use crate::ogg::read::read_comments; use crate::picture::Picture; use std::io::{Read, Seek, SeekFrom}; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; pub(super) fn verify_flac(data: &mut R) -> Result where @@ -137,7 +139,7 @@ where // In the event that a block lies about its size, the current position could be // completely wrong. if current > end { - err!(SizeMismatch); + io_err!(SizeMismatch); } (end - current, end) diff --git a/lofty/src/flac/write.rs b/lofty/src/flac/write.rs index 333e061d6..9e754dcd1 100644 --- a/lofty/src/flac/write.rs +++ b/lofty/src/flac/write.rs @@ -2,17 +2,19 @@ use super::block::{BLOCK_ID_PADDING, BLOCK_ID_PICTURE, BLOCK_ID_VORBIS_COMMENTS, use super::read::verify_flac; use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; -use crate::macros::{err, try_vec}; +use crate::macros::err; use crate::ogg::tag::VorbisCommentsRef; use crate::ogg::write::create_comments; use crate::picture::{Picture, PictureInformation}; use crate::tag::{Tag, TagType}; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::try_vec; +use aud_io::err as io_err; const BLOCK_HEADER_SIZE: usize = 4; const MAX_BLOCK_SIZE: u32 = 16_777_215; @@ -195,7 +197,7 @@ fn create_comment_block( let len = (writer.get_ref().len() - 1) as u32; if len > MAX_BLOCK_SIZE { - err!(TooMuchData); + io_err!(TooMuchData); } let comment_end = writer.stream_position()?; @@ -232,7 +234,7 @@ fn create_picture_blocks( let pic_len = pic_bytes.len() as u32; if pic_len > MAX_BLOCK_SIZE { - err!(TooMuchData); + io_err!(TooMuchData); } writer.write_all(&pic_len.to_be_bytes()[1..])?; diff --git a/lofty/src/id3/mod.rs b/lofty/src/id3/mod.rs index 507e1d041..5889f3f9d 100644 --- a/lofty/src/id3/mod.rs +++ b/lofty/src/id3/mod.rs @@ -6,14 +6,16 @@ pub mod v1; pub mod v2; -use crate::error::{ErrorKind, LoftyError, Result}; -use crate::macros::try_vec; -use crate::util::text::utf8_decode_str; +use crate::error::Result; use v2::header::Id3v2Header; use std::io::{Read, Seek, SeekFrom}; use std::ops::Neg; +use aud_io::text::utf8_decode_str; +use aud_io::try_vec; +use aud_io::err as io_err; + pub(crate) struct ID3FindResults(pub Option
, pub Content); pub(crate) fn find_lyrics3v2(data: &mut R) -> Result> @@ -36,11 +38,14 @@ where header = Some(()); let lyrics_size = utf8_decode_str(&lyrics3v2[..7])?; - let lyrics_size = lyrics_size.parse::().map_err(|_| { - LoftyError::new(ErrorKind::TextDecode( - "Lyrics3v2 tag has an invalid size string", - )) - })?; + let lyrics_size = match lyrics_size.parse::() { + Ok(size) => size, + Err(_) => { + io_err!(TextDecode( + "Lyrics3v2 tag has an invalid size string" + )); + } + }; size += lyrics_size; diff --git a/lofty/src/id3/v1/tag.rs b/lofty/src/id3/v1/tag.rs index 1e5b937b3..266089329 100644 --- a/lofty/src/id3/v1/tag.rs +++ b/lofty/src/id3/v1/tag.rs @@ -2,13 +2,13 @@ use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::id3::v1::constants::GENRES; use crate::tag::{Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType}; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use std::io::Write; use std::path::Path; use lofty_attr::tag; +use aud_io::io::{FileLike, Length, Truncate}; macro_rules! impl_accessor { ($($name:ident,)+) => { diff --git a/lofty/src/id3/v1/write.rs b/lofty/src/id3/v1/write.rs index 3ee434dba..1545ed35f 100644 --- a/lofty/src/id3/v1/write.rs +++ b/lofty/src/id3/v1/write.rs @@ -4,11 +4,11 @@ use crate::error::{LoftyError, Result}; use crate::id3::{ID3FindResults, find_id3v1}; use crate::macros::err; use crate::probe::Probe; -use crate::util::io::{FileLike, Length, Truncate}; use std::io::{Cursor, Seek, Write}; use byteorder::WriteBytesExt; +use aud_io::io::{FileLike, Length, Truncate}; #[allow(clippy::shadow_unrelated)] pub(crate) fn write_id3v1( diff --git a/lofty/src/id3/v2/frame/content.rs b/lofty/src/id3/v2/frame/content.rs index 84f4253ed..9caa26fd4 100644 --- a/lofty/src/id3/v2/frame/content.rs +++ b/lofty/src/id3/v2/frame/content.rs @@ -1,4 +1,3 @@ -use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::header::Id3v2Version; use crate::id3::v2::items::{ @@ -8,11 +7,13 @@ use crate::id3::v2::items::{ UrlLinkFrame, }; use crate::id3::v2::{BinaryFrame, Frame, FrameFlags, FrameId}; -use crate::macros::err; -use crate::util::text::TextEncoding; use std::io::Read; +use aud_io::text::TextEncoding; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; + #[rustfmt::skip] pub(super) fn parse_content( reader: &mut R, @@ -61,7 +62,7 @@ pub(in crate::id3::v2) fn verify_encoding( } match TextEncoding::from_u8(encoding) { - None => err!(TextDecode("Found invalid encoding")), + None => io_err!(TextDecode("Found invalid encoding")), Some(e) => Ok(e), } } diff --git a/lofty/src/id3/v2/frame/conversion.rs b/lofty/src/id3/v2/frame/conversion.rs index 1162d7094..b2139d5b0 100644 --- a/lofty/src/id3/v2/frame/conversion.rs +++ b/lofty/src/id3/v2/frame/conversion.rs @@ -9,11 +9,12 @@ use crate::id3::v2::{ ExtendedTextFrame, ExtendedUrlFrame, Frame, FrameFlags, FrameId, PopularimeterFrame, UniqueFileIdentifierFrame, }; -use crate::macros::err; use crate::tag::{ItemKey, ItemValue, TagItem, TagType}; use std::borrow::Cow; +use aud_io::err as io_err; + fn frame_from_unknown_item(id: FrameId<'_>, item_value: ItemValue) -> Result> { match item_value { ItemValue::Text(text) => Ok(new_text_frame(id, text)), @@ -21,7 +22,7 @@ fn frame_from_unknown_item(id: FrameId<'_>, item_value: ItemValue) -> Result Ok(new_binary_frame(id, binary.clone())), diff --git a/lofty/src/id3/v2/frame/header/parse.rs b/lofty/src/id3/v2/frame/header/parse.rs index a361cae09..394954b93 100644 --- a/lofty/src/id3/v2/frame/header/parse.rs +++ b/lofty/src/id3/v2/frame/header/parse.rs @@ -3,12 +3,13 @@ use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::FrameId; use crate::id3::v2::util::synchsafe::SynchsafeInteger; use crate::id3::v2::util::upgrade::{upgrade_v2, upgrade_v3}; -use crate::util::text::utf8_decode_str; - use crate::config::ParseOptions; + use std::borrow::Cow; use std::io::Read; +use aud_io::text::utf8_decode_str; + pub(crate) fn parse_v2_header( reader: &mut R, size: &mut u32, diff --git a/lofty/src/id3/v2/frame/mod.rs b/lofty/src/id3/v2/frame/mod.rs index 787eaec6f..86cf0e385 100644 --- a/lofty/src/id3/v2/frame/mod.rs +++ b/lofty/src/id3/v2/frame/mod.rs @@ -12,13 +12,14 @@ use super::items::{ }; use crate::error::Result; use crate::id3::v2::FrameHeader; -use crate::util::text::TextEncoding; use header::FrameId; use std::borrow::Cow; use std::hash::Hash; use std::ops::Deref; +use aud_io::text::TextEncoding; + pub(super) const MUSICBRAINZ_UFID_OWNER: &str = "http://musicbrainz.org"; /// Empty content descriptor in text frame diff --git a/lofty/src/id3/v2/frame/read.rs b/lofty/src/id3/v2/frame/read.rs index 9bed13ccc..29d44584e 100644 --- a/lofty/src/id3/v2/frame/read.rs +++ b/lofty/src/id3/v2/frame/read.rs @@ -1,17 +1,18 @@ use super::Frame; use super::header::parse::{parse_header, parse_v2_header}; -use crate::config::{ParseOptions, ParsingMode}; +use crate::config::ParseOptions; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::frame::content::parse_content; use crate::id3::v2::header::Id3v2Version; use crate::id3::v2::tag::ATTACHED_PICTURE_ID; use crate::id3::v2::util::synchsafe::{SynchsafeInteger, UnsynchronizedStream}; use crate::id3::v2::{BinaryFrame, FrameFlags, FrameHeader, FrameId}; -use crate::macros::try_vec; use std::io::Read; use byteorder::{BigEndian, ReadBytesExt}; +use aud_io::config::ParsingMode; +use aud_io::try_vec; pub(crate) enum ParsedFrame<'a> { Next(Frame<'a>), @@ -43,14 +44,14 @@ impl ParsedFrame<'_> { }, Ok(Some(some)) => some, Err(err) => { - match parse_options.parsing_mode { - ParsingMode::Strict => return Err(err), - ParsingMode::BestAttempt | ParsingMode::Relaxed => { - log::warn!("Failed to read frame header, skipping: {}", err); + return match parse_options.parsing_mode { + ParsingMode::Strict => Err(err), + _ => { + log::warn!("Failed to read frame header, skipping: {err}"); // Skip this frame and continue reading skip_frame(reader, size)?; - return Ok(Self::Skip); + Ok(Self::Skip) }, } }, diff --git a/lofty/src/id3/v2/items/attached_picture_frame.rs b/lofty/src/id3/v2/items/attached_picture_frame.rs index ad0278c24..4d52ac5a9 100644 --- a/lofty/src/id3/v2/items/attached_picture_frame.rs +++ b/lofty/src/id3/v2/items/attached_picture_frame.rs @@ -1,14 +1,15 @@ use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::header::Id3v2Version; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use crate::macros::err; use crate::picture::{MimeType, Picture, PictureType}; -use crate::util::text::{TextDecodeOptions, TextEncoding, encode_text}; use std::borrow::Cow; use std::io::{Read, Write as _}; use byteorder::{ReadBytesExt as _, WriteBytesExt as _}; +use aud_io::text::{encode_text, TextDecodeOptions, TextEncoding}; +use aud_io::err as io_err; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("APIC")); @@ -86,7 +87,7 @@ impl AttachedPictureFrame<'_> { }, } } else { - let mime_type_str = crate::util::text::decode_text( + let mime_type_str = aud_io::text::decode_text( reader, TextDecodeOptions::new() .encoding(TextEncoding::Latin1) @@ -98,7 +99,7 @@ impl AttachedPictureFrame<'_> { let pic_type = PictureType::from_u8(reader.read_u8()?); - let description = crate::util::text::decode_text( + let description = aud_io::text::decode_text( reader, TextDecodeOptions::new().encoding(encoding).terminated(true), )? @@ -180,7 +181,7 @@ impl AttachedPictureFrame<'_> { data.write_all(&self.picture.data)?; if data.len() as u64 > max_size { - err!(TooMuchData); + io_err!(TooMuchData); } Ok(data) diff --git a/lofty/src/id3/v2/items/audio_text_frame.rs b/lofty/src/id3/v2/items/audio_text_frame.rs index ca12a5da2..7c0093945 100644 --- a/lofty/src/id3/v2/items/audio_text_frame.rs +++ b/lofty/src/id3/v2/items/audio_text_frame.rs @@ -1,10 +1,11 @@ -use crate::error::{ErrorKind, Id3v2Error, Id3v2ErrorKind, LoftyError, Result}; +use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; +use aud_io::err as io_err; +use aud_io::text::{decode_text, encode_text, TextDecodeOptions, TextEncoding}; use byteorder::ReadBytesExt as _; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("ATXT")); @@ -132,8 +133,12 @@ impl AudioTextFrame<'_> { } let content = &mut &bytes[..]; - let encoding = TextEncoding::from_u8(content.read_u8()?) - .ok_or_else(|| LoftyError::new(ErrorKind::TextDecode("Found invalid encoding")))?; + let encoding = match TextEncoding::from_u8(content.read_u8()?) { + Some(encoding) => encoding, + None => { + io_err!(TextDecode("Found invalid encoding")) + } + }; let mime_type = decode_text( content, @@ -234,8 +239,8 @@ pub fn scramble(audio_data: &mut [u8]) { #[cfg(test)] mod tests { - use crate::TextEncoding; use crate::id3::v2::{AudioTextFrame, AudioTextFrameFlags, FrameFlags}; + use crate::TextEncoding; fn expected() -> AudioTextFrame<'static> { AudioTextFrame { diff --git a/lofty/src/id3/v2/items/encapsulated_object.rs b/lofty/src/id3/v2/items/encapsulated_object.rs index 35874e6b0..1f23729f0 100644 --- a/lofty/src/id3/v2/items/encapsulated_object.rs +++ b/lofty/src/id3/v2/items/encapsulated_object.rs @@ -1,9 +1,11 @@ -use crate::error::{ErrorKind, Id3v2Error, Id3v2ErrorKind, LoftyError, Result}; +use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::io::{Cursor, Read}; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; +use aud_io::err as io_err; + const FRAME_ID: FrameId<'static> = FrameId::Valid(std::borrow::Cow::Borrowed("GEOB")); /// Allows for encapsulation of any file type inside an ID3v2 tag @@ -69,8 +71,12 @@ impl GeneralEncapsulatedObject<'_> { return Err(Id3v2Error::new(Id3v2ErrorKind::BadFrameLength).into()); } - let encoding = TextEncoding::from_u8(data[0]) - .ok_or_else(|| LoftyError::new(ErrorKind::TextDecode("Found invalid encoding")))?; + let encoding = match TextEncoding::from_u8(data[0]) { + Some(encoding) => encoding, + None => { + io_err!(TextDecode("Found invalid encoding")) + } + }; let mut cursor = Cursor::new(&data[1..]); @@ -129,7 +135,7 @@ impl GeneralEncapsulatedObject<'_> { #[cfg(test)] mod tests { use crate::id3::v2::{FrameFlags, FrameHeader, GeneralEncapsulatedObject}; - use crate::util::text::TextEncoding; + use aud_io::text::TextEncoding; fn expected() -> GeneralEncapsulatedObject<'static> { GeneralEncapsulatedObject { diff --git a/lofty/src/id3/v2/items/extended_text_frame.rs b/lofty/src/id3/v2/items/extended_text_frame.rs index d19357115..29dd08e46 100644 --- a/lofty/src/id3/v2/items/extended_text_frame.rs +++ b/lofty/src/id3/v2/items/extended_text_frame.rs @@ -1,17 +1,15 @@ use crate::error::{Id3v2Error, Id3v2ErrorKind, LoftyError, Result}; use crate::id3::v2::frame::content::verify_encoding; use crate::id3::v2::header::Id3v2Version; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::macros::err; -use crate::util::text::{ - TextDecodeOptions, TextEncoding, decode_text, encode_text, utf16_decode_bytes, -}; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::io::Read; use byteorder::ReadBytesExt; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text, utf16_decode_bytes}; +use aud_io::err as io_err; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("TXXX")); @@ -142,7 +140,7 @@ impl ExtendedTextFrame<'_> { }, [0x00, 0x00] => { debug_assert!(description.content.is_empty()); - err!(TextDecode("UTF-16 string has no BOM")); + io_err!(TextDecode("UTF-16 string has no BOM")); }, [0xFF, 0xFE] => u16::from_le_bytes, [0xFE, 0xFF] => u16::from_be_bytes, diff --git a/lofty/src/id3/v2/items/extended_url_frame.rs b/lofty/src/id3/v2/items/extended_url_frame.rs index 9fde67afb..138948e57 100644 --- a/lofty/src/id3/v2/items/extended_url_frame.rs +++ b/lofty/src/id3/v2/items/extended_url_frame.rs @@ -1,14 +1,14 @@ use crate::error::Result; use crate::id3::v2::frame::content::verify_encoding; use crate::id3::v2::header::Id3v2Version; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::io::Read; use byteorder::ReadBytesExt; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("WXXX")); diff --git a/lofty/src/id3/v2/items/key_value_frame.rs b/lofty/src/id3/v2/items/key_value_frame.rs index dc5b65191..b9ff2bee4 100644 --- a/lofty/src/id3/v2/items/key_value_frame.rs +++ b/lofty/src/id3/v2/items/key_value_frame.rs @@ -1,13 +1,13 @@ use crate::error::Result; use crate::id3::v2::frame::content::verify_encoding; use crate::id3::v2::header::Id3v2Version; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; - -use byteorder::ReadBytesExt; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use std::io::Read; +use byteorder::ReadBytesExt; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; + /// An `ID3v2` key-value frame #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct KeyValueFrame<'a> { diff --git a/lofty/src/id3/v2/items/language_frame.rs b/lofty/src/id3/v2/items/language_frame.rs index 3cde0a5c0..2e9434b98 100644 --- a/lofty/src/id3/v2/items/language_frame.rs +++ b/lofty/src/id3/v2/items/language_frame.rs @@ -1,15 +1,15 @@ use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::frame::content::verify_encoding; use crate::id3::v2::header::Id3v2Version; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use crate::tag::items::Lang; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::io::Read; use byteorder::ReadBytesExt; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; // Generic struct for a text frame that has a language // diff --git a/lofty/src/id3/v2/items/ownership_frame.rs b/lofty/src/id3/v2/items/ownership_frame.rs index af53707d7..9dd0ab3c0 100644 --- a/lofty/src/id3/v2/items/ownership_frame.rs +++ b/lofty/src/id3/v2/items/ownership_frame.rs @@ -1,13 +1,14 @@ -use crate::error::{ErrorKind, Id3v2Error, Id3v2ErrorKind, LoftyError, Result}; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::text::{ - TextDecodeOptions, TextEncoding, decode_text, encode_text, utf8_decode_str, -}; +use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use std::borrow::Cow; use std::hash::Hash; use std::io::Read; +use aud_io::err as io_err; +use aud_io::text::{ + decode_text, encode_text, utf8_decode_str, TextDecodeOptions, TextEncoding, +}; use byteorder::ReadBytesExt; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("OWNE")); @@ -82,8 +83,11 @@ impl OwnershipFrame<'_> { return Ok(None); }; - let encoding = TextEncoding::from_u8(encoding_byte) - .ok_or_else(|| LoftyError::new(ErrorKind::TextDecode("Found invalid encoding")))?; + let encoding = match TextEncoding::from_u8(encoding_byte) { + Some(encoding) => encoding, + None => io_err!(TextDecode("Found invalid encoding")), + }; + let price_paid = decode_text( reader, TextDecodeOptions::new() @@ -138,8 +142,8 @@ impl OwnershipFrame<'_> { #[cfg(test)] mod tests { - use crate::TextEncoding; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, OwnershipFrame}; + use crate::TextEncoding; use std::borrow::Cow; diff --git a/lofty/src/id3/v2/items/popularimeter.rs b/lofty/src/id3/v2/items/popularimeter.rs index 193c5c9f0..ee58a35be 100644 --- a/lofty/src/id3/v2/items/popularimeter.rs +++ b/lofty/src/id3/v2/items/popularimeter.rs @@ -1,13 +1,13 @@ use crate::error::Result; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::alloc::VecFallibleCapacity; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::io::Read; use byteorder::ReadBytesExt; +use aud_io::alloc::VecFallibleCapacity; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("POPM")); diff --git a/lofty/src/id3/v2/items/private_frame.rs b/lofty/src/id3/v2/items/private_frame.rs index 73c018eea..f92b05bf6 100644 --- a/lofty/src/id3/v2/items/private_frame.rs +++ b/lofty/src/id3/v2/items/private_frame.rs @@ -1,11 +1,12 @@ use crate::error::Result; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::alloc::VecFallibleCapacity; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::borrow::Cow; use std::io::Read; +use aud_io::alloc::VecFallibleCapacity; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; + const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("PRIV")); /// An `ID3v2` private frame diff --git a/lofty/src/id3/v2/items/relative_volume_adjustment_frame.rs b/lofty/src/id3/v2/items/relative_volume_adjustment_frame.rs index 7725b07e5..16002b2f8 100644 --- a/lofty/src/id3/v2/items/relative_volume_adjustment_frame.rs +++ b/lofty/src/id3/v2/items/relative_volume_adjustment_frame.rs @@ -1,8 +1,5 @@ -use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::macros::try_vec; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::borrow::Cow; use std::collections::HashMap; @@ -10,6 +7,9 @@ use std::hash::{Hash, Hasher}; use std::io::Read; use byteorder::{BigEndian, ReadBytesExt}; +use aud_io::config::ParsingMode; +use aud_io::try_vec; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("RVA2")); @@ -238,7 +238,6 @@ impl RelativeVolumeAdjustmentFrame<'_> { #[cfg(test)] mod tests { - use crate::config::ParsingMode; use crate::id3::v2::{ ChannelInformation, ChannelType, FrameFlags, FrameHeader, RelativeVolumeAdjustmentFrame, }; @@ -246,6 +245,8 @@ mod tests { use std::collections::HashMap; use std::io::Read; + use aud_io::config::ParsingMode; + fn expected() -> RelativeVolumeAdjustmentFrame<'static> { let mut channels = HashMap::new(); diff --git a/lofty/src/id3/v2/items/sync_text.rs b/lofty/src/id3/v2/items/sync_text.rs index 7d3511634..dcdec1b28 100644 --- a/lofty/src/id3/v2/items/sync_text.rs +++ b/lofty/src/id3/v2/items/sync_text.rs @@ -1,14 +1,14 @@ -use crate::error::{ErrorKind, Id3v2Error, Id3v2ErrorKind, LoftyError, Result}; +use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::macros::err; -use crate::util::text::{ - TextDecodeOptions, TextEncoding, decode_text, encode_text, read_to_terminator, - utf16_decode_bytes, -}; use std::borrow::Cow; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; +use aud_io::err as io_err; +use aud_io::text::{ + decode_text, encode_text, read_to_terminator, utf16_decode_bytes, TextDecodeOptions, + TextEncoding, +}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("SYLT")); @@ -136,8 +136,11 @@ impl SynchronizedTextFrame<'_> { return Err(Id3v2Error::new(Id3v2ErrorKind::BadFrameLength).into()); } - let encoding = TextEncoding::from_u8(data[0]) - .ok_or_else(|| LoftyError::new(ErrorKind::TextDecode("Found invalid encoding")))?; + let encoding = match TextEncoding::from_u8(data[0]) { + Some(encoding) => encoding, + None => io_err!(TextDecode("Found invalid encoding")), + }; + let language: [u8; 3] = data[1..4].try_into().unwrap(); if language.iter().any(|c| !c.is_ascii_alphabetic()) { return Err(Id3v2Error::new(Id3v2ErrorKind::BadSyncText).into()); @@ -148,7 +151,7 @@ impl SynchronizedTextFrame<'_> { .ok_or_else(|| Id3v2Error::new(Id3v2ErrorKind::BadSyncText))?; let mut cursor = Cursor::new(&data[6..]); - let description = crate::util::text::decode_text( + let description = decode_text( &mut cursor, TextDecodeOptions::new().encoding(encoding).terminated(true), ) @@ -251,7 +254,7 @@ impl SynchronizedTextFrame<'_> { } if data.len() as u64 > u64::from(u32::MAX) { - err!(TooMuchData); + io_err!(TooMuchData); } return Ok(data); @@ -266,7 +269,7 @@ mod tests { use crate::id3::v2::{ FrameFlags, FrameHeader, SyncTextContentType, SynchronizedTextFrame, TimestampFormat, }; - use crate::util::text::TextEncoding; + use aud_io::text::TextEncoding; fn expected(encoding: TextEncoding) -> SynchronizedTextFrame<'static> { SynchronizedTextFrame { diff --git a/lofty/src/id3/v2/items/text_information_frame.rs b/lofty/src/id3/v2/items/text_information_frame.rs index b27781969..6cd91be11 100644 --- a/lofty/src/id3/v2/items/text_information_frame.rs +++ b/lofty/src/id3/v2/items/text_information_frame.rs @@ -1,14 +1,14 @@ use crate::error::Result; use crate::id3::v2::frame::content::verify_encoding; use crate::id3::v2::header::Id3v2Version; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; - -use byteorder::ReadBytesExt; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use std::hash::Hash; use std::io::Read; +use byteorder::ReadBytesExt; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; + /// An `ID3v2` text frame #[derive(Clone, Debug, Eq)] pub struct TextInformationFrame<'a> { diff --git a/lofty/src/id3/v2/items/timestamp_frame.rs b/lofty/src/id3/v2/items/timestamp_frame.rs index 3f186c775..317486552 100644 --- a/lofty/src/id3/v2/items/timestamp_frame.rs +++ b/lofty/src/id3/v2/items/timestamp_frame.rs @@ -1,13 +1,14 @@ -use crate::config::ParsingMode; use crate::error::Result; -use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; +use crate::id3::v2::{FrameFlags, FrameHeader, FrameId, Id3TextEncodingExt}; use crate::macros::err; use crate::tag::items::Timestamp; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::io::Read; use byteorder::ReadBytesExt; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; /// An `ID3v2` timestamp frame #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -78,7 +79,7 @@ impl<'a> TimestampFrame<'a> { }; let Some(encoding) = TextEncoding::from_u8(encoding_byte) else { if parse_mode != ParsingMode::Relaxed { - err!(TextDecode("Found invalid encoding")) + io_err!(TextDecode("Found invalid encoding")) } return Ok(None); }; diff --git a/lofty/src/id3/v2/items/unique_file_identifier.rs b/lofty/src/id3/v2/items/unique_file_identifier.rs index 8b25d42fb..18a1ba166 100644 --- a/lofty/src/id3/v2/items/unique_file_identifier.rs +++ b/lofty/src/id3/v2/items/unique_file_identifier.rs @@ -1,13 +1,13 @@ -use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::macros::parse_mode_choice; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::io::Read; +use aud_io::config::ParsingMode; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; + const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("UFID")); /// An `ID3v2` unique file identifier frame (UFID). @@ -82,11 +82,12 @@ impl UniqueFileIdentifierFrame<'_> { match owner_decode_result.text_or_none() { Some(valid) => owner = valid, None => { - parse_mode_choice!( - parse_mode, - BESTATTEMPT: owner = String::new(), - DEFAULT: return Err(Id3v2Error::new(Id3v2ErrorKind::MissingUfidOwner).into()) - ); + if parse_mode == ParsingMode::BestAttempt { + owner = String::new(); + } else { + // ParsingMode::Relaxed will ignore this error and discard the whole frame + return Err(Id3v2Error::new(Id3v2ErrorKind::MissingUfidOwner).into()) + } }, } @@ -123,7 +124,7 @@ mod tests { #[test_log::test] fn issue_204_invalid_ufid_parsing_mode_best_attempt() { - use crate::config::ParsingMode; + use aud_io::config::ParsingMode; use crate::id3::v2::UniqueFileIdentifierFrame; let ufid_no_owner = UniqueFileIdentifierFrame { diff --git a/lofty/src/id3/v2/items/url_link_frame.rs b/lofty/src/id3/v2/items/url_link_frame.rs index 36c251a5f..881583133 100644 --- a/lofty/src/id3/v2/items/url_link_frame.rs +++ b/lofty/src/id3/v2/items/url_link_frame.rs @@ -1,10 +1,11 @@ use crate::error::Result; use crate::id3::v2::{FrameFlags, FrameHeader, FrameId}; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; use std::hash::Hash; use std::io::Read; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text}; + /// An `ID3v2` URL frame #[derive(Clone, Debug, Eq)] pub struct UrlLinkFrame<'a> { diff --git a/lofty/src/id3/v2/mod.rs b/lofty/src/id3/v2/mod.rs index e8193e61b..27d5472ad 100644 --- a/lofty/src/id3/v2/mod.rs +++ b/lofty/src/id3/v2/mod.rs @@ -16,6 +16,8 @@ pub(crate) mod tag; pub mod util; pub(crate) mod write; +use aud_io::text::TextEncoding; + // Exports pub use header::{Id3v2TagFlags, Id3v2Version}; @@ -31,3 +33,26 @@ pub use frame::{Frame, FrameFlags}; pub use restrictions::{ ImageSizeRestrictions, TagRestrictions, TagSizeRestrictions, TextSizeRestrictions, }; + +/// ID3v2 [`TextEncoding`] extensions +pub trait Id3TextEncodingExt { + /// ID3v2.4 introduced two new text encodings. + /// + /// When writing ID3v2.3, we just substitute with UTF-16. + fn to_id3v23(self) -> Self; +} + +impl Id3TextEncodingExt for TextEncoding { + fn to_id3v23(self) -> Self { + match self { + Self::UTF8 | Self::UTF16BE => { + log::warn!( + "Text encoding {:?} is not supported in ID3v2.3, substituting with UTF-16", + self + ); + Self::UTF16 + }, + _ => self, + } + } +} diff --git a/lofty/src/id3/v2/read.rs b/lofty/src/id3/v2/read.rs index 82800c41a..ef6f8bb19 100644 --- a/lofty/src/id3/v2/read.rs +++ b/lofty/src/id3/v2/read.rs @@ -182,9 +182,10 @@ mod tests { use super::parse_id3v2; use crate::config::ParseOptions; + use aud_io::config::ParsingMode; + #[test_log::test] fn zero_size_id3v2() { - use crate::config::ParsingMode; use crate::id3::v2::header::Id3v2Header; use std::io::Cursor; @@ -203,7 +204,6 @@ mod tests { #[test_log::test] fn bad_frame_id_relaxed_id3v2() { - use crate::config::ParsingMode; use crate::id3::v2::header::Id3v2Header; use crate::prelude::*; diff --git a/lofty/src/id3/v2/tag.rs b/lofty/src/id3/v2/tag.rs index ccc52966a..0d8b1a6b0 100644 --- a/lofty/src/id3/v2/tag.rs +++ b/lofty/src/id3/v2/tag.rs @@ -22,8 +22,6 @@ use crate::tag::companion_tag::CompanionTag; use crate::tag::items::{Lang, Timestamp, UNKNOWN_LANGUAGE}; use crate::tag::{Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType}; use crate::util::flag_item; -use crate::util::io::{FileLike, Length, Truncate}; -use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text}; use std::borrow::Cow; use std::io::{Cursor, Write}; @@ -32,6 +30,8 @@ use std::ops::Deref; use std::str::FromStr; use lofty_attr::tag; +use aud_io::text::{TextDecodeOptions, TextEncoding, decode_text}; +use aud_io::io::{FileLike, Length, Truncate}; const INVOLVED_PEOPLE_LIST_ID: &str = "TIPL"; diff --git a/lofty/src/id3/v2/tag/tests.rs b/lofty/src/id3/v2/tag/tests.rs index 97f461e9a..43f9dc3da 100644 --- a/lofty/src/id3/v2/tag/tests.rs +++ b/lofty/src/id3/v2/tag/tests.rs @@ -1,4 +1,4 @@ -use crate::config::{ParseOptions, ParsingMode}; +use crate::config::ParseOptions; use crate::id3::v2::header::Id3v2Header; use crate::id3::v2::items::PopularimeterFrame; use crate::id3::v2::util::pairs::DEFAULT_NUMBER_IN_PAIR; @@ -8,11 +8,12 @@ use crate::id3::v2::{ use crate::picture::MimeType; use crate::tag::items::{ENGLISH, Timestamp}; use crate::tag::utils::test_utils::read_path; - use super::*; use std::collections::HashMap; +use aud_io::config::ParsingMode; + const COMMENT_FRAME_ID: &str = "COMM"; fn read_tag(path: &str) -> Id3v2Tag { diff --git a/lofty/src/id3/v2/util/synchsafe.rs b/lofty/src/id3/v2/util/synchsafe.rs index 019208a02..2441390d1 100644 --- a/lofty/src/id3/v2/util/synchsafe.rs +++ b/lofty/src/id3/v2/util/synchsafe.rs @@ -243,7 +243,7 @@ macro_rules! impl_synchsafe { }; if self > MAXIMUM_INTEGER { - crate::macros::err!(TooMuchData); + aud_io::err!(TooMuchData); } let $n = self; diff --git a/lofty/src/id3/v2/write/chunk_file.rs b/lofty/src/id3/v2/write/chunk_file.rs index 3cd76b575..ddb95d595 100644 --- a/lofty/src/id3/v2/write/chunk_file.rs +++ b/lofty/src/id3/v2/write/chunk_file.rs @@ -1,12 +1,12 @@ use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::iff::chunk::Chunks; -use crate::macros::err; -use crate::util::io::{FileLike, Length, Truncate}; use std::io::{Cursor, Seek, SeekFrom, Write}; use byteorder::{ByteOrder, WriteBytesExt}; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::err as io_err; const CHUNK_NAME_UPPER: [u8; 4] = [b'I', b'D', b'3', b' ']; const CHUNK_NAME_LOWER: [u8; 4] = [b'i', b'd', b'3', b' ']; @@ -37,7 +37,7 @@ where file.read_to_end(file_bytes.get_mut())?; if file_bytes.get_ref().len() < (actual_stream_size as usize + RIFF_CHUNK_HEADER_SIZE) { - err!(SizeMismatch); + io_err!(SizeMismatch); } // The first chunk format is RIFF....WAVE @@ -91,7 +91,7 @@ where } let Ok(tag_size): std::result::Result = tag_bytes.get_ref().len().try_into() else { - err!(TooMuchData) + io_err!(TooMuchData) }; let tag_position = actual_stream_size as usize + RIFF_CHUNK_HEADER_SIZE; diff --git a/lofty/src/id3/v2/write/frame.rs b/lofty/src/id3/v2/write/frame.rs index 453c0aeed..57ca71f34 100644 --- a/lofty/src/id3/v2/write/frame.rs +++ b/lofty/src/id3/v2/write/frame.rs @@ -1,12 +1,12 @@ use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::frame::{FrameFlags, FrameRef}; use crate::id3::v2::util::synchsafe::SynchsafeInteger; -use crate::id3::v2::{Frame, FrameId, KeyValueFrame, TextInformationFrame}; +use crate::id3::v2::{Frame, FrameId, Id3TextEncodingExt, KeyValueFrame, TextInformationFrame}; use crate::tag::items::Timestamp; +use crate::id3::v2::tag::GenresIter; use std::io::Write; -use crate::id3::v2::tag::GenresIter; use byteorder::{BigEndian, WriteBytesExt}; pub(in crate::id3::v2) fn create_items( diff --git a/lofty/src/id3/v2/write/mod.rs b/lofty/src/id3/v2/write/mod.rs index b9e169fdd..3896603af 100644 --- a/lofty/src/id3/v2/write/mod.rs +++ b/lofty/src/id3/v2/write/mod.rs @@ -10,15 +10,16 @@ use crate::id3::v2::frame::FrameRef; use crate::id3::v2::tag::Id3v2TagRef; use crate::id3::v2::util::synchsafe::SynchsafeInteger; use crate::id3::{FindId3v2Config, find_id3v2}; -use crate::macros::{err, try_vec}; +use crate::macros::err; use crate::probe::Probe; -use crate::util::io::{FileLike, Length, Truncate}; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::ops::Not; use std::sync::OnceLock; use byteorder::{BigEndian, LittleEndian, WriteBytesExt}; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::try_vec; // In the very rare chance someone wants to write a CRC in their extended header fn crc_32_table() -> &'static [u32; 256] { diff --git a/lofty/src/iff/aiff/properties.rs b/lofty/src/iff/aiff/properties.rs index 9b1236760..3a49f619d 100644 --- a/lofty/src/iff/aiff/properties.rs +++ b/lofty/src/iff/aiff/properties.rs @@ -1,15 +1,16 @@ use super::read::CompressionPresent; use crate::error::Result; -use crate::macros::{decode_err, try_vec}; +use crate::macros::decode_err; use crate::properties::FileProperties; -use crate::util::text::utf8_decode; use std::borrow::Cow; use std::io::Read; use std::time::Duration; -use crate::io::ReadExt; use byteorder::{BigEndian, ReadBytesExt}; +use aud_io::io::ReadExt; +use aud_io::text::utf8_decode; +use aud_io::try_vec; /// The AIFC compression type /// diff --git a/lofty/src/iff/aiff/tag.rs b/lofty/src/iff/aiff/tag.rs index 8b0c17f1f..38a96e952 100644 --- a/lofty/src/iff/aiff/tag.rs +++ b/lofty/src/iff/aiff/tag.rs @@ -1,9 +1,7 @@ use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::iff::chunk::Chunks; -use crate::macros::err; use crate::tag::{Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType}; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use std::convert::TryFrom; @@ -11,6 +9,8 @@ use std::io::{SeekFrom, Write}; use byteorder::BigEndian; use lofty_attr::tag; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::err as io_err; /// Represents an AIFF `COMT` chunk /// @@ -371,7 +371,7 @@ where let comt_len = comt.text.len(); if comt_len > u16::MAX as usize { - err!(TooMuchData); + io_err!(TooMuchData); } text_chunks.extend((comt_len as u16).to_be_bytes()); @@ -394,7 +394,7 @@ where i += 1; } } else { - err!(TooMuchData); + io_err!(TooMuchData); } if (text_chunks.len() - 4) % 2 != 0 { diff --git a/lofty/src/iff/chunk.rs b/lofty/src/iff/chunk.rs index 9b80ee8a3..bf7886a3b 100644 --- a/lofty/src/iff/chunk.rs +++ b/lofty/src/iff/chunk.rs @@ -1,13 +1,14 @@ use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v2::tag::Id3v2Tag; -use crate::macros::{err, try_vec}; -use crate::util::text::utf8_decode; use std::io::{Read, Seek, SeekFrom}; use std::marker::PhantomData; use byteorder::{ByteOrder, ReadBytesExt}; +use aud_io::text::utf8_decode; +use aud_io::try_vec; +use aud_io::err as io_err; const RIFF_CHUNK_HEADER_SIZE: u64 = 8; @@ -55,7 +56,7 @@ impl Chunks { let cont = self.content(data)?; self.correct_position(data)?; - utf8_decode(cont) + utf8_decode(cont).map_err(Into::into) } pub fn read_pstring(&mut self, data: &mut R, size: Option) -> Result @@ -72,7 +73,7 @@ impl Chunks { data.seek(SeekFrom::Current(1))?; } - utf8_decode(cont) + utf8_decode(cont).map_err(Into::into) } pub fn content(&mut self, data: &mut R) -> Result> @@ -87,7 +88,7 @@ impl Chunks { R: Read, { if size > self.remaining_size { - err!(SizeMismatch); + io_err!(SizeMismatch); } let mut content = try_vec![0; size as usize]; diff --git a/lofty/src/iff/wav/properties.rs b/lofty/src/iff/wav/properties.rs index ff3114cbd..ffba778bf 100644 --- a/lofty/src/iff/wav/properties.rs +++ b/lofty/src/iff/wav/properties.rs @@ -1,11 +1,11 @@ use crate::error::Result; use crate::macros::decode_err; use crate::properties::{ChannelMask, FileProperties}; -use crate::util::math::RoundedDivision; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; +use aud_io::math::RoundedDivision; const PCM: u16 = 0x0001; const IEEE_FLOAT: u16 = 0x0003; diff --git a/lofty/src/iff/wav/read.rs b/lofty/src/iff/wav/read.rs index 637357e82..d6dbbf903 100644 --- a/lofty/src/iff/wav/read.rs +++ b/lofty/src/iff/wav/read.rs @@ -5,11 +5,12 @@ use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v2::tag::Id3v2Tag; use crate::iff::chunk::Chunks; -use crate::macros::{decode_err, err}; +use crate::macros::decode_err; use std::io::{Read, Seek, SeekFrom}; use byteorder::{LittleEndian, ReadBytesExt}; +use aud_io::err as io_err; // Verifies that the stream is a WAV file and returns the stream length pub(crate) fn verify_wav(data: &mut T) -> Result @@ -91,7 +92,7 @@ where // to avoid the seeks. let end = data.stream_position()? + u64::from(size); if end > file_len { - err!(SizeMismatch); + io_err!(SizeMismatch); } super::tag::read::parse_riff_info( diff --git a/lofty/src/iff/wav/tag/mod.rs b/lofty/src/iff/wav/tag/mod.rs index 93b9b4cb1..00c7f9b81 100644 --- a/lofty/src/iff/wav/tag/mod.rs +++ b/lofty/src/iff/wav/tag/mod.rs @@ -6,12 +6,12 @@ use crate::error::{LoftyError, Result}; use crate::tag::{ Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, try_parse_year, }; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use std::io::Write; use lofty_attr::tag; +use aud_io::io::{FileLike, Length, Truncate}; macro_rules! impl_accessor { ($($name:ident => $key:literal;)+) => { @@ -344,16 +344,17 @@ pub(crate) fn tagitems_into_riff<'a>( #[cfg(test)] mod tests { - use crate::config::{ParsingMode, WriteOptions}; + use crate::config::WriteOptions; use crate::iff::chunk::Chunks; use crate::iff::wav::RiffInfoList; use crate::prelude::*; use crate::tag::{Tag, TagType}; - use byteorder::LittleEndian; - use std::io::Cursor; + use byteorder::LittleEndian; + use aud_io::config::ParsingMode; + #[test_log::test] fn parse_riff_info() { let mut expected_tag = RiffInfoList::default(); diff --git a/lofty/src/iff/wav/tag/read.rs b/lofty/src/iff/wav/tag/read.rs index 32dcf853c..8df48da7d 100644 --- a/lofty/src/iff/wav/tag/read.rs +++ b/lofty/src/iff/wav/tag/read.rs @@ -1,13 +1,13 @@ use super::RiffInfoList; -use crate::config::ParsingMode; use crate::error::{ErrorKind, Result}; use crate::iff::chunk::Chunks; use crate::macros::decode_err; -use crate::util::text::utf8_decode_str; use std::io::{Read, Seek}; use byteorder::LittleEndian; +use aud_io::text::utf8_decode_str; +use aud_io::config::ParsingMode; pub(in crate::iff::wav) fn parse_riff_info( data: &mut R, diff --git a/lofty/src/iff/wav/tag/write.rs b/lofty/src/iff/wav/tag/write.rs index b8c22804e..12d3f18d4 100644 --- a/lofty/src/iff/wav/tag/write.rs +++ b/lofty/src/iff/wav/tag/write.rs @@ -3,12 +3,12 @@ use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::iff::chunk::Chunks; use crate::iff::wav::read::verify_wav; -use crate::macros::err; -use crate::util::io::{FileLike, Length, Truncate}; use std::io::{Cursor, Read, Seek, SeekFrom}; use byteorder::{LittleEndian, WriteBytesExt}; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::err as io_err; const RIFF_CHUNK_HEADER_SIZE: usize = 8; @@ -34,7 +34,7 @@ where file.read_to_end(file_bytes.get_mut())?; if file_bytes.get_ref().len() < (stream_length as usize + RIFF_CHUNK_HEADER_SIZE) { - err!(SizeMismatch); + io_err!(SizeMismatch); } // The first chunk format is RIFF....WAVE @@ -154,7 +154,7 @@ pub(super) fn create_riff_info( let packet_size = Vec::len(bytes) - 4; if packet_size > u32::MAX as usize { - err!(TooMuchData); + io_err!(TooMuchData); } log::debug!("Created RIFF INFO list, size: {} bytes", packet_size); diff --git a/lofty/src/lib.rs b/lofty/src/lib.rs index 22ef81ca3..fd37dce8a 100644 --- a/lofty/src/lib.rs +++ b/lofty/src/lib.rs @@ -130,11 +130,11 @@ pub mod wavpack; pub use crate::probe::{read_from, read_from_path}; -pub use util::text::TextEncoding; +pub use aud_io::text::TextEncoding; pub use lofty_attr::LoftyFile; -pub use util::io; +pub use aud_io::io; pub mod prelude { //! A prelude for commonly used items in the library. diff --git a/lofty/src/macros.rs b/lofty/src/macros.rs index 4468b7563..f66695504 100644 --- a/lofty/src/macros.rs +++ b/lofty/src/macros.rs @@ -1,7 +1,3 @@ -macro_rules! try_vec { - ($elem:expr; $size:expr) => {{ $crate::util::alloc::fallible_vec_from_element($elem, $size)? }}; -} - // Shorthand for return Err(LoftyError::new(ErrorKind::Foo)) // // Usage: @@ -48,49 +44,4 @@ macro_rules! decode_err { }; } -// A macro for handling the different `ParsingMode`s -// -// NOTE: All fields are optional, if `STRICT` or `RELAXED` are missing, it will -// fall through to `DEFAULT`. If `DEFAULT` is missing, it will fall through -// to an empty block. -// -// Usage: -// -// - parse_mode_choice!( -// ident_of_parsing_mode, -// STRICT: some_expr, -// RELAXED: some_expr, -// DEFAULT: some_expr, -// ) -macro_rules! parse_mode_choice { - ( - $parse_mode:ident, - $(STRICT: $strict_handler:expr,)? - $(BESTATTEMPT: $best_attempt_handler:expr,)? - $(RELAXED: $relaxed_handler:expr,)? - DEFAULT: $default:expr - ) => { - match $parse_mode { - $(crate::config::ParsingMode::Strict => { $strict_handler },)? - $(crate::config::ParsingMode::BestAttempt => { $best_attempt_handler },)? - $(crate::config::ParsingMode::Relaxed => { $relaxed_handler },)? - _ => { $default } - } - }; - ( - $parse_mode:ident, - $(STRICT: $strict_handler:expr,)? - $(BESTATTEMPT: $best_attempt_handler:expr,)? - $(RELAXED: $relaxed_handler:expr $(,)?)? - ) => { - match $parse_mode { - $(crate::config::ParsingMode::Strict => { $strict_handler },)? - $(crate::config::ParsingMode::BestAttempt => { $best_attempt_handler },)? - $(crate::config::ParsingMode::Relaxed => { $relaxed_handler },)? - #[allow(unreachable_patterns)] - _ => {} - } - }; -} - -pub(crate) use {decode_err, err, parse_mode_choice, try_vec}; +pub(crate) use {decode_err, err}; diff --git a/lofty/src/mp4/ilst/atom.rs b/lofty/src/mp4/ilst/atom.rs index b06e70134..f437af701 100644 --- a/lofty/src/mp4/ilst/atom.rs +++ b/lofty/src/mp4/ilst/atom.rs @@ -1,11 +1,12 @@ use crate::error::Result; use crate::macros::err; -use crate::mp4::AtomIdent; use crate::mp4::ilst::data_type::DataType; use crate::picture::Picture; use std::fmt::{Debug, Formatter}; +use aud_io::mp4::AtomIdent; + // Atoms with multiple values aren't all that common, // so there's no need to create a bunch of single-element Vecs #[derive(PartialEq, Clone)] diff --git a/lofty/src/mp4/ilst/constants.rs b/lofty/src/mp4/ilst/constants.rs index 79f1579ed..173e28972 100644 --- a/lofty/src/mp4/ilst/constants.rs +++ b/lofty/src/mp4/ilst/constants.rs @@ -6,7 +6,7 @@ /// [`AtomData::Bool`]: crate::mp4::AtomData::Bool /// [`Ilst::set_flag`]: crate::mp4::Ilst::set_flag pub mod flags { - use crate::mp4::AtomIdent; + use aud_io::mp4::AtomIdent; /// Podcast flag (`pcst`) pub const PODCAST: AtomIdent<'_> = AtomIdent::Fourcc(*b"pcst"); diff --git a/lofty/src/mp4/ilst/mod.rs b/lofty/src/mp4/ilst/mod.rs index d7e86af82..f54eaa134 100644 --- a/lofty/src/mp4/ilst/mod.rs +++ b/lofty/src/mp4/ilst/mod.rs @@ -6,7 +6,6 @@ pub(super) mod read; mod r#ref; pub(crate) mod write; -use super::AtomIdent; use crate::config::{WriteOptions, global_options}; use crate::error::LoftyError; use crate::mp4::ilst::atom::AtomDataStorage; @@ -15,17 +14,19 @@ use crate::tag::companion_tag::CompanionTag; use crate::tag::{ Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, try_parse_year, }; -use crate::util::flag_item; -use crate::util::io::{FileLike, Length, Truncate}; use advisory_rating::AdvisoryRating; use atom::{Atom, AtomData}; use data_type::DataType; +use crate::util::flag_item; use std::borrow::Cow; use std::io::Write; use std::ops::Deref; use lofty_attr::tag; +use aud_io::mp4::AtomIdent; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::err as io_err; const ARTIST: AtomIdent<'_> = AtomIdent::Fourcc(*b"\xa9ART"); const TITLE: AtomIdent<'_> = AtomIdent::Fourcc(*b"\xa9nam"); @@ -889,13 +890,55 @@ impl From for Ilst { } } +impl<'a> TryFrom<&'a ItemKey> for AtomIdent<'a> { + type Error = LoftyError; + + fn try_from(value: &'a ItemKey) -> std::result::Result { + if let Some(mapped_key) = value.map_key(TagType::Mp4Ilst) { + if mapped_key.starts_with("----") { + let mut split = mapped_key.split(':'); + + split.next(); + + let mean = split.next(); + let name = split.next(); + + if let (Some(mean), Some(name)) = (mean, name) { + return Ok(AtomIdent::Freeform { + mean: Cow::Borrowed(mean), + name: Cow::Borrowed(name), + }); + } + } else { + let fourcc = mapped_key.chars().map(|c| c as u8).collect::>(); + + if let Ok(fourcc) = TryInto::<[u8; 4]>::try_into(fourcc) { + return Ok(AtomIdent::Fourcc(fourcc)); + } + } + } + + io_err!(TextDecode( + "ItemKey does not map to a freeform or fourcc identifier" + )) + } +} + +impl TryFrom for AtomIdent<'static> { + type Error = LoftyError; + + fn try_from(value: ItemKey) -> std::result::Result { + let ret: AtomIdent<'_> = (&value).try_into()?; + Ok(ret.into_owned()) + } +} + #[cfg(test)] mod tests { - use crate::config::{ParseOptions, ParsingMode, WriteOptions}; + use crate::config::{ParseOptions, WriteOptions}; use crate::mp4::ilst::TITLE; use crate::mp4::ilst::atom::AtomDataStorage; - use crate::mp4::read::AtomReader; - use crate::mp4::{AdvisoryRating, Atom, AtomData, AtomIdent, DataType, Ilst, Mp4File}; + use crate::mp4::{AdvisoryRating, Atom, AtomData, DataType, Ilst, Mp4File}; use crate::picture::{MimeType, Picture, PictureType}; use crate::prelude::*; use crate::tag::utils::test_utils; @@ -904,6 +947,9 @@ mod tests { use std::io::{Cursor, Read as _, Seek as _, Write as _}; + use aud_io::config::ParsingMode; + use aud_io::mp4::{AtomIdent, AtomReader}; + fn read_ilst(path: &str, parse_options: ParseOptions) -> Ilst { let tag = std::fs::read(path).unwrap(); read_ilst_raw(&tag, parse_options) diff --git a/lofty/src/mp4/ilst/read.rs b/lofty/src/mp4/ilst/read.rs index 9ef297b88..9e729e1e3 100644 --- a/lofty/src/mp4/ilst/read.rs +++ b/lofty/src/mp4/ilst/read.rs @@ -1,20 +1,23 @@ use super::constants::WELL_KNOWN_TYPE_SET; use super::data_type::DataType; use super::{Atom, AtomData, AtomIdent, Ilst}; -use crate::config::{ParseOptions, ParsingMode}; +use crate::config::ParseOptions; use crate::error::{LoftyError, Result}; use crate::id3::v1::constants::GENRES; -use crate::macros::{err, try_vec}; -use crate::mp4::atom_info::{ATOM_HEADER_LEN, AtomInfo}; use crate::mp4::ilst::atom::AtomDataStorage; -use crate::mp4::read::{AtomReader, skip_atom}; +use crate::mp4::read::skip_atom; use crate::picture::{MimeType, Picture, PictureType}; use crate::tag::TagExt; -use crate::util::text::{utf8_decode, utf16_decode_bytes}; use std::borrow::Cow; use std::io::{Cursor, Read, Seek, SeekFrom}; +use aud_io::mp4::{ATOM_HEADER_LEN, AtomInfo, AtomReader}; +use aud_io::text::{utf8_decode, utf16_decode_bytes}; +use aud_io::try_vec; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; + pub(in crate::mp4) fn parse_ilst( reader: &mut AtomReader, parse_options: ParseOptions, @@ -183,8 +186,8 @@ where let handle_error = |err: LoftyError, parsing_mode: ParsingMode| -> Result<()> { match parsing_mode { ParsingMode::Strict => Err(err), - ParsingMode::BestAttempt | ParsingMode::Relaxed => { - log::warn!("Skipping atom with invalid content: {}", err); + _ => { + log::warn!("Skipping atom with invalid content: {err}"); Ok(()) }, } @@ -259,7 +262,7 @@ where next_atom.len ); if parsing_mode == ParsingMode::Strict { - err!(BadAtom("Data atom is too small")) + io_err!(BadAtom("Data atom is too small")) } break; @@ -267,7 +270,7 @@ where if next_atom.ident != DATA_ATOM_IDENT { if parsing_mode == ParsingMode::Strict { - err!(BadAtom("Expected atom \"data\" to follow name")) + io_err!(BadAtom("Expected atom \"data\" to follow name")) } log::warn!( @@ -323,7 +326,7 @@ where let type_set = reader.read_u8()?; if type_set != WELL_KNOWN_TYPE_SET { if parsing_mode == ParsingMode::Strict { - err!(BadAtom("Unknown type set in data atom")) + io_err!(BadAtom("Unknown type set in data atom")) } return Ok(None); @@ -338,7 +341,7 @@ fn parse_uint(bytes: &[u8]) -> Result { 2 => u32::from(u16::from_be_bytes([bytes[0], bytes[1]])), 3 => u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]), 4 => u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), - _ => err!(BadAtom( + _ => io_err!(BadAtom( "Unexpected atom size for type \"BE unsigned integer\"" )), }) @@ -350,7 +353,7 @@ fn parse_int(bytes: &[u8]) -> Result { 2 => i32::from(i16::from_be_bytes([bytes[0], bytes[1]])), 3 => i32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]), 4 => i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), - _ => err!(BadAtom( + _ => io_err!(BadAtom( "Unexpected atom size for type \"BE signed integer\"" )), }) @@ -380,7 +383,7 @@ where DataType::Bmp => Some(MimeType::Bmp), _ => { if parsing_mode == ParsingMode::Strict { - err!(BadAtom("\"covr\" atom has an unknown type")) + io_err!(BadAtom("\"covr\" atom has an unknown type")) } log::warn!( diff --git a/lofty/src/mp4/ilst/ref.rs b/lofty/src/mp4/ilst/ref.rs index 929099b94..7f34dc671 100644 --- a/lofty/src/mp4/ilst/ref.rs +++ b/lofty/src/mp4/ilst/ref.rs @@ -4,11 +4,13 @@ use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; -use crate::mp4::{Atom, AtomData, AtomIdent, Ilst}; -use crate::util::io::{FileLike, Length, Truncate}; +use crate::mp4::{Atom, AtomData, Ilst}; use std::io::Write; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::mp4::AtomIdent; + impl Ilst { pub(crate) fn as_ref(&self) -> IlstRef<'_, impl IntoIterator> { IlstRef { diff --git a/lofty/src/mp4/ilst/write.rs b/lofty/src/mp4/ilst/write.rs index 2dc10b59a..ffd92f821 100644 --- a/lofty/src/mp4/ilst/write.rs +++ b/lofty/src/mp4/ilst/write.rs @@ -3,19 +3,21 @@ use super::r#ref::IlstRef; use crate::config::{ParseOptions, WriteOptions}; use crate::error::{FileEncodingError, LoftyError, Result}; use crate::file::FileType; -use crate::macros::{decode_err, err, try_vec}; +use crate::macros::decode_err; use crate::mp4::AtomData; -use crate::mp4::atom_info::{ATOM_HEADER_LEN, AtomIdent, AtomInfo, FOURCC_LEN}; use crate::mp4::ilst::r#ref::AtomRef; -use crate::mp4::read::{AtomReader, atom_tree, find_child_atom, meta_is_full, verify_mp4}; +use crate::mp4::read::{atom_tree, find_child_atom, meta_is_full, verify_mp4}; use crate::mp4::write::{AtomWriter, AtomWriterCompanion, ContextualAtom}; use crate::picture::{MimeType, Picture}; -use crate::util::alloc::VecFallibleCapacity; -use crate::util::io::{FileLike, Length, Truncate}; use std::io::{Cursor, Seek, SeekFrom, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use aud_io::mp4::{AtomIdent, AtomInfo, AtomReader, ATOM_HEADER_LEN, FOURCC_LEN}; +use aud_io::alloc::VecFallibleCapacity; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::try_vec; +use aud_io::err as io_err; // A "full" atom is a traditional length + identifier, followed by a version (1) and flags (3) const FULL_ATOM_SIZE: u64 = ATOM_HEADER_LEN + 4; @@ -298,7 +300,7 @@ fn save_to_existing( let remaining_space = available_space - ilst_len; if remaining_space > u64::from(u32::MAX) { - err!(TooMuchData); + io_err!(TooMuchData); } let remaining_space = remaining_space as u32; diff --git a/lofty/src/mp4/mod.rs b/lofty/src/mp4/mod.rs index 0a9617b9c..90e5c5c67 100644 --- a/lofty/src/mp4/mod.rs +++ b/lofty/src/mp4/mod.rs @@ -3,7 +3,6 @@ //! ## File notes //! //! The only supported tag format is [`Ilst`]. -mod atom_info; pub(crate) mod ilst; mod moov; mod properties; @@ -22,11 +21,11 @@ pub mod constants { } pub use crate::mp4::properties::{AudioObjectType, Mp4Codec, Mp4Properties}; -pub use atom_info::AtomIdent; pub use ilst::Ilst; pub use ilst::advisory_rating::AdvisoryRating; pub use ilst::atom::{Atom, AtomData}; pub use ilst::data_type::DataType; +pub use aud_io::mp4::AtomIdent; pub(crate) use properties::SAMPLE_RATES; diff --git a/lofty/src/mp4/moov.rs b/lofty/src/mp4/moov.rs index 6e43fbb64..4a4152d7f 100644 --- a/lofty/src/mp4/moov.rs +++ b/lofty/src/mp4/moov.rs @@ -1,12 +1,12 @@ -use super::atom_info::{AtomIdent, AtomInfo}; use super::ilst::Ilst; use super::ilst::read::parse_ilst; -use super::read::{AtomReader, find_child_atom, meta_is_full, skip_atom}; +use super::read::{find_child_atom, meta_is_full, skip_atom}; use crate::config::ParseOptions; use crate::error::Result; use crate::macros::decode_err; use std::io::{Read, Seek}; +use aud_io::mp4::{AtomIdent, AtomInfo, AtomReader}; pub(crate) struct Moov { // Represents the trak.mdia atom diff --git a/lofty/src/mp4/properties.rs b/lofty/src/mp4/properties.rs index ded05fb03..59728852a 100644 --- a/lofty/src/mp4/properties.rs +++ b/lofty/src/mp4/properties.rs @@ -1,16 +1,18 @@ -use super::atom_info::{AtomIdent, AtomInfo}; -use super::read::{AtomReader, find_child_atom, skip_atom}; -use crate::config::ParsingMode; +use super::read::{find_child_atom, skip_atom}; use crate::error::{LoftyError, Result}; -use crate::macros::{decode_err, err, try_vec}; +use crate::macros::decode_err; use crate::properties::FileProperties; -use crate::util::alloc::VecFallibleCapacity; -use crate::util::math::RoundedDivision; use std::io::{Cursor, Read, Seek, SeekFrom}; use std::time::Duration; use byteorder::{BigEndian, ReadBytesExt}; +use aud_io::mp4::{AtomIdent, AtomInfo, AtomReader}; +use aud_io::config::ParsingMode; +use aud_io::try_vec; +use aud_io::alloc::VecFallibleCapacity; +use aud_io::math::RoundedDivision; +use aud_io::err as io_err; /// An MP4 file's audio codec #[allow(missing_docs)] @@ -285,7 +287,7 @@ where } let Some(mdhd) = mdhd else { - err!(BadAtom("Expected atom \"trak.mdia.mdhd\"")); + io_err!(BadAtom("Expected atom \"trak.mdia.mdhd\"")); }; Ok(AudioTrak { mdhd, minf }) @@ -433,11 +435,11 @@ where for _ in 0..num_sample_entries { let Some(atom) = reader.next()? else { - err!(BadAtom("Expected sample entry atom in `stsd` atom")) + io_err!(BadAtom("Expected sample entry atom in `stsd` atom")) }; let AtomIdent::Fourcc(ref fourcc) = atom.ident else { - err!(BadAtom("Expected fourcc atom in `stsd` atom")) + io_err!(BadAtom("Expected fourcc atom in `stsd` atom")) }; match fourcc { diff --git a/lofty/src/mp4/read/mod.rs b/lofty/src/mp4/read/mod.rs index 396e915f3..201e90524 100644 --- a/lofty/src/mp4/read/mod.rs +++ b/lofty/src/mp4/read/mod.rs @@ -1,20 +1,18 @@ -mod atom_reader; - use super::Mp4File; -use super::atom_info::{AtomIdent, AtomInfo}; use super::moov::Moov; use super::properties::Mp4Properties; -use crate::config::{ParseOptions, ParsingMode}; -use crate::error::{ErrorKind, LoftyError, Result}; +use crate::config::ParseOptions; +use crate::error::Result; use crate::macros::{decode_err, err}; -use crate::util::io::SeekStreamLen; -use crate::util::text::utf8_decode_str; use std::io::{Read, Seek, SeekFrom}; use byteorder::{BigEndian, ReadBytesExt}; - -pub(super) use atom_reader::AtomReader; +use aud_io::mp4::{AtomIdent, AtomInfo, AtomReader}; +use aud_io::io::SeekStreamLen; +use aud_io::text::utf8_decode_str; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; pub(in crate::mp4) fn verify_mp4(reader: &mut AtomReader) -> Result where @@ -39,11 +37,13 @@ where reader.seek(SeekFrom::Current((atom.len - 12) as i64))?; - let major_brand = utf8_decode_str(&major_brand) - .map(ToOwned::to_owned) - .map_err(|_| { - LoftyError::new(ErrorKind::BadAtom("Unable to parse \"ftyp\"'s major brand")) - })?; + let major_brand = match utf8_decode_str(&major_brand) + .map(ToOwned::to_owned) { + Ok(value) => value, + Err(_) => { + io_err!(BadAtom("Unable to parse \"ftyp\"'s major brand")) + } + }; log::debug!("Verified to be an MP4 file. Major brand: {}", major_brand); Ok(major_brand) @@ -107,7 +107,7 @@ where if let (pos, false) = pos.overflowing_add(len - 8) { reader.seek(SeekFrom::Start(pos))?; } else { - err!(TooMuchData); + io_err!(TooMuchData); } Ok(()) diff --git a/lofty/src/mp4/write.rs b/lofty/src/mp4/write.rs index 83251257a..3a11e0214 100644 --- a/lofty/src/mp4/write.rs +++ b/lofty/src/mp4/write.rs @@ -1,8 +1,4 @@ -use crate::config::ParsingMode; use crate::error::{LoftyError, Result}; -use crate::io::{FileLike, Length, Truncate}; -use crate::macros::err; -use crate::mp4::atom_info::{AtomIdent, AtomInfo, IDENTIFIER_LEN}; use crate::mp4::read::{meta_is_full, skip_atom}; use std::cell::{RefCell, RefMut}; @@ -10,6 +6,10 @@ use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::ops::RangeBounds; use byteorder::{BigEndian, WriteBytesExt}; +use aud_io::mp4::{AtomIdent, AtomInfo, IDENTIFIER_LEN}; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; /// A wrapper around [`AtomInfo`] that allows us to track all of the children of containers we deem important #[derive(Debug)] @@ -75,7 +75,7 @@ impl ContextualAtom { if len != 0 { // TODO: Print the container ident - err!(BadAtom("Unable to read entire container")); + io_err!(BadAtom("Unable to read entire container")); } *reader_len = reader_len.saturating_sub(info.len); diff --git a/lofty/src/mpeg/properties.rs b/lofty/src/mpeg/properties.rs index 12209e6a5..112a663f7 100644 --- a/lofty/src/mpeg/properties.rs +++ b/lofty/src/mpeg/properties.rs @@ -2,11 +2,12 @@ use super::header::{ChannelMode, Emphasis, Header, Layer, MpegVersion, VbrHeader use crate::error::Result; use crate::mpeg::header::rev_search_for_frame_header; use crate::properties::{ChannelMask, FileProperties}; -use crate::util::math::RoundedDivision; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; +use aud_io::math::RoundedDivision; + /// An MPEG file's audio properties #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[non_exhaustive] diff --git a/lofty/src/mpeg/read.rs b/lofty/src/mpeg/read.rs index 7a84065bc..316392ee3 100644 --- a/lofty/src/mpeg/read.rs +++ b/lofty/src/mpeg/read.rs @@ -1,7 +1,7 @@ use super::header::{Header, HeaderCmpResult, VbrHeader, cmp_header, search_for_frame_sync}; use super::{MpegFile, MpegProperties}; use crate::ape::header::read_ape_header; -use crate::config::{ParseOptions, ParsingMode}; +use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v2::header::Id3v2Header; use crate::id3::v2::read::parse_id3v2; @@ -13,6 +13,8 @@ use crate::mpeg::header::HEADER_MASK; use std::io::{Read, Seek, SeekFrom}; use byteorder::{BigEndian, ReadBytesExt}; +use aud_io::err as io_err; +use aud_io::config::ParsingMode; pub(super) fn read_from(reader: &mut R, parse_options: ParseOptions) -> Result where @@ -164,7 +166,7 @@ where // Seek back to the start of the tag let pos = reader.stream_position()?; let Some(start_of_tag) = pos.checked_sub(u64::from(header.size)) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; reader.seek(SeekFrom::Start(start_of_tag))?; diff --git a/lofty/src/musepack/read.rs b/lofty/src/musepack/read.rs index fbb8dbcca..1be6f4921 100644 --- a/lofty/src/musepack/read.rs +++ b/lofty/src/musepack/read.rs @@ -6,11 +6,12 @@ use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v2::read::parse_id3v2; use crate::id3::{FindId3v2Config, ID3FindResults, find_id3v1, find_id3v2, find_lyrics3v2}; -use crate::macros::err; -use crate::util::io::SeekStreamLen; use std::io::{Read, Seek, SeekFrom}; +use aud_io::io::SeekStreamLen; +use aud_io::err as io_err; + pub(super) fn read_from(reader: &mut R, parse_options: ParseOptions) -> Result where R: Read + Seek, @@ -34,7 +35,7 @@ where if let ID3FindResults(Some(header), Some(content)) = find_id3v2(reader, find_id3v2_config)? { let Some(new_stream_length) = stream_length.checked_sub(u64::from(header.full_tag_size())) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_length = new_stream_length; @@ -54,7 +55,7 @@ where if header.is_some() { file.id3v1_tag = id3v1; let Some(new_stream_length) = stream_length.checked_sub(128) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_length = new_stream_length; @@ -62,7 +63,7 @@ where let ID3FindResults(_, lyrics3v2_size) = find_lyrics3v2(reader)?; let Some(new_stream_length) = stream_length.checked_sub(u64::from(lyrics3v2_size)) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_length = new_stream_length; @@ -77,13 +78,13 @@ where let tag_size = u64::from(header.size); let Some(tag_start) = pos.checked_sub(tag_size) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; reader.seek(SeekFrom::Start(tag_start))?; let Some(new_stream_length) = stream_length.checked_sub(tag_size) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_length = new_stream_length; } diff --git a/lofty/src/musepack/sv4to6/properties.rs b/lofty/src/musepack/sv4to6/properties.rs index 1dc388685..b0a4d9bd8 100644 --- a/lofty/src/musepack/sv4to6/properties.rs +++ b/lofty/src/musepack/sv4to6/properties.rs @@ -1,14 +1,14 @@ -use crate::config::ParsingMode; use crate::error::Result; use crate::macros::decode_err; use crate::musepack::constants::{MPC_DECODER_SYNTH_DELAY, MPC_FRAME_LENGTH}; use crate::properties::FileProperties; -use crate::util::math::RoundedDivision; use std::io::Read; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; +use aud_io::math::RoundedDivision; +use aud_io::config::ParsingMode; /// MPC stream versions 4-6 audio properties #[derive(Debug, Clone, PartialEq, Default)] diff --git a/lofty/src/musepack/sv8/properties.rs b/lofty/src/musepack/sv8/properties.rs index c8a4b633b..490718690 100644 --- a/lofty/src/musepack/sv8/properties.rs +++ b/lofty/src/musepack/sv8/properties.rs @@ -1,15 +1,15 @@ use super::read::PacketReader; -use crate::config::ParsingMode; use crate::error::Result; use crate::macros::decode_err; use crate::musepack::constants::FREQUENCY_TABLE; use crate::properties::FileProperties; -use crate::util::math::RoundedDivision; use std::io::Read; use std::time::Duration; use byteorder::{BigEndian, ReadBytesExt}; +use aud_io::math::RoundedDivision; +use aud_io::config::ParsingMode; /// MPC stream version 8 audio properties #[derive(Debug, Clone, PartialEq, Default)] diff --git a/lofty/src/musepack/sv8/read.rs b/lofty/src/musepack/sv8/read.rs index 10e8452f4..ce884ae1d 100644 --- a/lofty/src/musepack/sv8/read.rs +++ b/lofty/src/musepack/sv8/read.rs @@ -1,11 +1,12 @@ use super::properties::{EncoderInfo, MpcSv8Properties, ReplayGain, StreamHeader}; -use crate::config::ParsingMode; -use crate::error::{ErrorKind, LoftyError, Result}; -use crate::macros::{decode_err, parse_mode_choice}; +use crate::error::Result; +use crate::macros::decode_err; use std::io::Read; use byteorder::ReadBytesExt; +use aud_io::err as io_err; +use aud_io::config::ParsingMode; // TODO: Support chapter packets? const STREAM_HEADER_KEY: [u8; 2] = *b"SH"; @@ -48,22 +49,22 @@ where let stream_header = match stream_header { Some(stream_header) => stream_header, None => { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL Mpc, "File is missing a Stream Header packet"), - DEFAULT: StreamHeader::default() - ) + if parse_mode == ParsingMode::Strict { + decode_err!(@BAIL Mpc, "File is missing a Stream Header packet"); + } + + StreamHeader::default() }, }; let replay_gain = match replay_gain { Some(replay_gain) => replay_gain, None => { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL Mpc, "File is missing a ReplayGain packet"), - DEFAULT: ReplayGain::default() - ) + if parse_mode == ParsingMode::Strict { + decode_err!(@BAIL Mpc, "File is missing a ReplayGain packet"); + } + + ReplayGain::default() }, }; @@ -145,7 +146,7 @@ impl PacketReader { // Sizes cannot go above 9 bytes if bytes_read > 9 { - return Err(LoftyError::new(ErrorKind::TooMuchData)); + io_err!(TooMuchData); } size = (size << 7) | u64::from(current & 0x7F); diff --git a/lofty/src/ogg/opus/properties.rs b/lofty/src/ogg/opus/properties.rs index 4475c761c..ec8063862 100644 --- a/lofty/src/ogg/opus/properties.rs +++ b/lofty/src/ogg/opus/properties.rs @@ -2,13 +2,13 @@ use super::find_last_page; use crate::error::Result; use crate::macros::decode_err; use crate::properties::{ChannelMask, FileProperties}; -use crate::util::math::RoundedDivision; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; use ogg_pager::{Packets, PageHeader}; +use aud_io::math::RoundedDivision; /// An Opus file's audio properties #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] diff --git a/lofty/src/ogg/read.rs b/lofty/src/ogg/read.rs index 8b4ec010c..5528b24d6 100644 --- a/lofty/src/ogg/read.rs +++ b/lofty/src/ogg/read.rs @@ -1,11 +1,10 @@ use super::tag::VorbisComments; use super::verify_signature; -use crate::config::{ParseOptions, ParsingMode}; +use crate::config::ParseOptions; use crate::error::{ErrorKind, LoftyError, Result}; -use crate::macros::{decode_err, err, parse_mode_choice}; +use crate::macros::decode_err; use crate::picture::{MimeType, Picture, PictureInformation, PictureType}; use crate::tag::Accessor; -use crate::util::text::{utf8_decode, utf8_decode_str, utf16_decode}; use std::borrow::Cow; use std::io::{Read, Seek, SeekFrom}; @@ -13,6 +12,11 @@ use std::io::{Read, Seek, SeekFrom}; use byteorder::{LittleEndian, ReadBytesExt}; use data_encoding::BASE64; use ogg_pager::{Packets, PageHeader}; +use aud_io::text::{utf8_decode, utf8_decode_str, utf16_decode}; +use aud_io::config::ParsingMode; +use aud_io::try_vec; +use aud_io::err as io_err; +use aud_io::error::AudioError; pub type OGGTags = (Option, PageHeader, Packets); @@ -24,13 +28,11 @@ pub(crate) fn read_comments( where R: Read, { - use crate::macros::try_vec; - let parse_mode = parse_options.parsing_mode; let vendor_len = data.read_u32::()?; if u64::from(vendor_len) > len { - err!(SizeMismatch); + io_err!(SizeMismatch); } let mut vendor_bytes = try_vec![0; vendor_len as usize]; @@ -45,7 +47,7 @@ where // The actions following this are not spec-compliant in the slightest, so // we need to short circuit if strict. if parse_mode == ParsingMode::Strict { - return Err(e); + return Err(e.into()); } log::warn!("Possibly corrupt vendor string, attempting to recover"); @@ -53,11 +55,9 @@ where // Some vendor strings have invalid mixed UTF-8 and UTF-16 encodings. // This seems to work, while preserving the string opposed to using // the replacement character - let LoftyError { - kind: ErrorKind::StringFromUtf8(e), - } = e + let AudioError::StringFromUtf8(e) = e else { - return Err(e); + return Err(e.into()); }; let s = e .as_bytes() @@ -77,7 +77,7 @@ where let number_of_items = data.read_u32::()?; if number_of_items > (len >> 2) as u32 { - err!(SizeMismatch); + io_err!(SizeMismatch); } let mut tag = VorbisComments { @@ -89,7 +89,7 @@ where for _ in 0..number_of_items { let comment_len = data.read_u32::()?; if u64::from(comment_len) > len { - err!(SizeMismatch); + io_err!(SizeMismatch); } let mut comment_bytes = try_vec![0; comment_len as usize]; @@ -193,7 +193,7 @@ where }, Err(e) => { if parse_mode == ParsingMode::Strict { - return Err(e); + return Err(e.into()); } log::warn!("Non UTF-8 value found, discarding field {key:?}"); @@ -210,7 +210,7 @@ where Ok(value) => tag.items.push((key, value.to_owned())), Err(e) => { if parse_mode == ParsingMode::Strict { - return Err(e); + return Err(e.into()); } log::warn!("Non UTF-8 value found, discarding field {key:?}"); @@ -219,11 +219,11 @@ where } }, _ => { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL "OGG: Vorbis comments contain an invalid key"), - // Otherwise discard invalid keys - ) + if parse_mode == ParsingMode::Strict { + decode_err!(@BAIL "OGG: Vorbis comments contain an invalid key"); + } + + // Otherwise discard invalid keys }, } } diff --git a/lofty/src/ogg/speex/properties.rs b/lofty/src/ogg/speex/properties.rs index 9f7189691..0b829ef91 100644 --- a/lofty/src/ogg/speex/properties.rs +++ b/lofty/src/ogg/speex/properties.rs @@ -2,13 +2,13 @@ use crate::error::Result; use crate::macros::decode_err; use crate::ogg::find_last_page; use crate::properties::FileProperties; -use crate::util::math::RoundedDivision; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; use ogg_pager::{Packets, PageHeader}; +use aud_io::math::RoundedDivision; /// A Speex file's audio properties #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] diff --git a/lofty/src/ogg/tag.rs b/lofty/src/ogg/tag.rs index 396dc72ea..036c90575 100644 --- a/lofty/src/ogg/tag.rs +++ b/lofty/src/ogg/tag.rs @@ -10,13 +10,13 @@ use crate::tag::{ Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, try_parse_year, }; use crate::util::flag_item; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use std::io::Write; use std::ops::Deref; use lofty_attr::tag; +use aud_io::io::{FileLike, Length, Truncate}; macro_rules! impl_accessor { ($($name:ident => $key:literal;)+) => { @@ -700,13 +700,16 @@ pub(crate) fn create_vorbis_comments_ref( #[cfg(test)] mod tests { - use crate::config::{ParseOptions, ParsingMode, WriteOptions}; + use crate::config::{ParseOptions, WriteOptions}; use crate::ogg::{OggPictureStorage, VorbisComments}; use crate::picture::{MimeType, Picture, PictureType}; use crate::prelude::*; use crate::tag::{ItemValue, Tag, TagItem, TagType}; + use std::io::Cursor; + use aud_io::config::ParsingMode; + fn read_tag(tag: &[u8]) -> VorbisComments { let mut reader = std::io::Cursor::new(tag); diff --git a/lofty/src/ogg/vorbis/properties.rs b/lofty/src/ogg/vorbis/properties.rs index caa69abec..391c9ae89 100644 --- a/lofty/src/ogg/vorbis/properties.rs +++ b/lofty/src/ogg/vorbis/properties.rs @@ -1,13 +1,13 @@ use super::find_last_page; use crate::error::Result; use crate::properties::FileProperties; -use crate::util::math::RoundedDivision; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; use ogg_pager::{Packets, PageHeader}; +use aud_io::math::RoundedDivision; /// An OGG Vorbis file's audio properties #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] diff --git a/lofty/src/ogg/write.rs b/lofty/src/ogg/write.rs index b90ba0dfb..1ddb2e578 100644 --- a/lofty/src/ogg/write.rs +++ b/lofty/src/ogg/write.rs @@ -2,18 +2,20 @@ use super::verify_signature; use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::file::FileType; -use crate::macros::{decode_err, err, try_vec}; +use crate::macros::{decode_err, err}; use crate::ogg::constants::{OPUSTAGS, VORBIS_COMMENT_HEAD}; use crate::ogg::tag::{VorbisCommentsRef, create_vorbis_comments_ref}; use crate::picture::{Picture, PictureInformation}; use crate::tag::{Tag, TagType}; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use ogg_pager::{CONTAINS_FIRST_PAGE_OF_BITSTREAM, Packets, Page, PageHeader}; +use aud_io::io::{FileLike, Length, Truncate}; +use aud_io::try_vec; +use aud_io::err as io_err; #[derive(PartialEq, Copy, Clone)] pub(crate) enum OGGFormat { @@ -213,7 +215,7 @@ pub(crate) fn create_comments( let comment_bytes = comment.as_bytes(); let Ok(bytes_len) = u32::try_from(comment_bytes.len()) else { - err!(TooMuchData); + io_err!(TooMuchData); }; *count += 1; @@ -236,7 +238,7 @@ fn create_pictures( let picture = pic.as_flac_bytes(info, true); let Ok(bytes_len) = u32::try_from(picture.len() + PICTURE_KEY.len()) else { - err!(TooMuchData); + io_err!(TooMuchData); }; *count += 1; diff --git a/lofty/src/picture.rs b/lofty/src/picture.rs index faf1c7ba4..fa813a23e 100644 --- a/lofty/src/picture.rs +++ b/lofty/src/picture.rs @@ -1,9 +1,7 @@ //! Format-agnostic picture handling -use crate::config::ParsingMode; use crate::error::{ErrorKind, LoftyError, Result}; use crate::macros::err; -use crate::util::text::utf8_decode_str; use std::borrow::Cow; use std::fmt::{Debug, Display, Formatter}; @@ -11,6 +9,10 @@ use std::io::{Cursor, Read, Seek, SeekFrom}; use byteorder::{BigEndian, ReadBytesExt as _}; use data_encoding::BASE64; +use aud_io::text::utf8_decode_str; +use aud_io::config::ParsingMode; +use aud_io::try_vec; +use aud_io::err as io_err; /// Common picture item keys for APE pub const APE_PICTURE_TYPES: [&str; 21] = [ @@ -665,8 +667,6 @@ impl Picture { content: &[u8], parse_mode: ParsingMode, ) -> Result<(Self, PictureInformation)> { - use crate::macros::try_vec; - let mut size = content.len(); let mut reader = Cursor::new(content); @@ -688,7 +688,7 @@ impl Picture { size -= 4; if mime_len > size { - err!(SizeMismatch); + io_err!(SizeMismatch); } let mime_type_str = utf8_decode_str(&content[8..8 + mime_len])?; diff --git a/lofty/src/tag/items/timestamp.rs b/lofty/src/tag/items/timestamp.rs index d632bb5a4..c2f23425e 100644 --- a/lofty/src/tag/items/timestamp.rs +++ b/lofty/src/tag/items/timestamp.rs @@ -1,4 +1,3 @@ -use crate::config::ParsingMode; use crate::error::{ErrorKind, LoftyError, Result}; use crate::macros::err; @@ -7,6 +6,7 @@ use std::io::Read; use std::str::FromStr; use byteorder::ReadBytesExt; +use aud_io::config::ParsingMode; /// A subset of the ISO 8601 timestamp format #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] @@ -276,7 +276,7 @@ impl Timestamp { #[cfg(test)] mod tests { - use crate::config::ParsingMode; + use aud_io::config::ParsingMode; use crate::tag::items::timestamp::Timestamp; fn expected() -> Timestamp { diff --git a/lofty/src/tag/mod.rs b/lofty/src/tag/mod.rs index 4d0201855..4fbcd4885 100644 --- a/lofty/src/tag/mod.rs +++ b/lofty/src/tag/mod.rs @@ -14,12 +14,13 @@ use crate::error::{LoftyError, Result}; use crate::macros::err; use crate::picture::{Picture, PictureType}; use crate::probe::Probe; -use crate::util::io::{FileLike, Length, Truncate}; use std::borrow::Cow; use std::io::Write; use std::path::Path; +use aud_io::io::{FileLike, Length, Truncate}; + // Exports pub use accessor::Accessor; pub use item::{ItemKey, ItemValue, TagItem}; diff --git a/lofty/src/tag/utils.rs b/lofty/src/tag/utils.rs index 5065aae0d..86d4accd1 100644 --- a/lofty/src/tag/utils.rs +++ b/lofty/src/tag/utils.rs @@ -3,9 +3,7 @@ use crate::error::{LoftyError, Result}; use crate::file::FileType; use crate::macros::err; use crate::tag::{Tag, TagType}; -use crate::util::io::{FileLike, Length, Truncate}; use crate::{aac, ape, flac, iff, mpeg, musepack, wavpack}; - use crate::id3::v1::tag::Id3v1TagRef; use crate::id3::v2::tag::Id3v2TagRef; use crate::id3::v2::{self, Id3v2TagFlags}; @@ -18,6 +16,8 @@ use iff::wav::tag::RIFFInfoListRef; use std::borrow::Cow; use std::io::Write; +use aud_io::io::{FileLike, Length, Truncate}; + #[allow(unreachable_patterns)] pub(crate) fn write_tag( tag: &Tag, diff --git a/lofty/src/util/mod.rs b/lofty/src/util/mod.rs index 99aeb9064..5733b5ba9 100644 --- a/lofty/src/util/mod.rs +++ b/lofty/src/util/mod.rs @@ -1,8 +1,3 @@ -pub(crate) mod alloc; -pub mod io; -pub(crate) mod math; -pub(crate) mod text; - pub(crate) fn flag_item(item: &str) -> Option { match item { "1" | "true" => Some(true), diff --git a/lofty/src/wavpack/properties.rs b/lofty/src/wavpack/properties.rs index 72aa55384..800f948ad 100644 --- a/lofty/src/wavpack/properties.rs +++ b/lofty/src/wavpack/properties.rs @@ -1,12 +1,14 @@ -use crate::config::ParsingMode; use crate::error::Result; -use crate::macros::{decode_err, err, parse_mode_choice, try_vec}; +use crate::macros::{decode_err, err}; use crate::properties::{ChannelMask, FileProperties}; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; +use aud_io::try_vec; +use aud_io::config::ParsingMode; +use aud_io::err as io_err; /// A WavPack file's audio properties #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -148,30 +150,30 @@ where if sample_rate_idx == 15 || flags & FLAG_DSD == FLAG_DSD { let mut block_contents = try_vec![0; (block_header.block_size - 24) as usize]; if reader.read_exact(&mut block_contents).is_err() { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL WavPack, "Block size mismatch"), - DEFAULT: break - ); + if parse_mode == ParsingMode::Strict { + decode_err!(@BAIL WavPack, "Block size mismatch"); + } + + break; } if let Err(e) = get_extended_meta_info(parse_mode, &block_contents, &mut properties) { - parse_mode_choice!( - parse_mode, - STRICT: return Err(e), - DEFAULT: break - ); + if parse_mode == ParsingMode::Strict { + return Err(e); + } + + break; } // A sample rate index of 15 indicates a custom sample rate, which should have been found // when we just parsed the metadata blocks if sample_rate_idx == 15 && properties.sample_rate == 0 { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL WavPack, "Expected custom sample rate"), - DEFAULT: break - ) + if parse_mode == ParsingMode::Strict { + decode_err!(@BAIL WavPack, "Expected custom sample rate"); + } + + break; } } @@ -179,11 +181,11 @@ where if block_header.version < MIN_STREAM_VERSION || block_header.version > MAX_STREAM_VERSION { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL WavPack, "Unsupported stream version encountered"), - DEFAULT: break - ); + if parse_mode == ParsingMode::Strict { + decode_err!(@BAIL WavPack, "Unsupported stream version encountered"); + } + + break; } total_samples = block_header.total_samples; @@ -324,7 +326,7 @@ fn get_extended_meta_info( } if (size as usize) > reader.len() { - err!(SizeMismatch); + io_err!(SizeMismatch); } if id & ID_FLAG_ODD_SIZE > 0 { @@ -349,11 +351,11 @@ fn get_extended_meta_info( size -= 1; if rate_multiplier > 30 { - parse_mode_choice!( - parse_mode, - STRICT: decode_err!(@BAIL WavPack, "Encountered an invalid sample rate multiplier"), - DEFAULT: break - ) + if parse_mode == ParsingMode::Strict { + decode_err!(@BAIL WavPack, "Encountered an invalid sample rate multiplier"); + } + + break; } rate_multiplier = 1 << rate_multiplier; @@ -415,7 +417,7 @@ fn get_extended_meta_info( // Skip over any remaining block size if (size as usize) > reader.len() { - err!(SizeMismatch); + io_err!(SizeMismatch); } let (_, rem) = reader.split_at(size as usize); diff --git a/lofty/src/wavpack/read.rs b/lofty/src/wavpack/read.rs index 0dcb2b724..6e96f22bb 100644 --- a/lofty/src/wavpack/read.rs +++ b/lofty/src/wavpack/read.rs @@ -4,9 +4,10 @@ use crate::config::ParseOptions; use crate::error::Result; use crate::id3::{ID3FindResults, find_id3v1, find_lyrics3v2}; -use crate::macros::err; use std::io::{Read, Seek, SeekFrom}; +use aud_io::err as io_err; + pub(super) fn read_from(reader: &mut R, parse_options: ParseOptions) -> Result where R: Read + Seek, @@ -23,7 +24,7 @@ where if id3v1_header.is_some() { id3v1_tag = id3v1; let Some(new_stream_length) = stream_length.checked_sub(128) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_length = new_stream_length; @@ -32,7 +33,7 @@ where // Next, check for a Lyrics3v2 tag, and skip over it, as it's no use to us let ID3FindResults(_, lyrics3v2_size) = find_lyrics3v2(reader)?; let Some(new_stream_length) = stream_length.checked_sub(u64::from(lyrics3v2_size)) else { - err!(SizeMismatch); + io_err!(SizeMismatch); }; stream_length = new_stream_length; diff --git a/lofty/tests/files/flac.rs b/lofty/tests/files/flac.rs index fdf1bd772..0c05e30eb 100644 --- a/lofty/tests/files/flac.rs +++ b/lofty/tests/files/flac.rs @@ -3,10 +3,11 @@ use crate::temp_file; use std::fs::File; use std::io::Seek; -use lofty::config::{ParseOptions, ParsingMode, WriteOptions}; +use lofty::config::{ParseOptions, WriteOptions}; use lofty::flac::FlacFile; use lofty::ogg::VorbisComments; use lofty::prelude::*; +use aud_io::config::ParsingMode; #[test_log::test] fn multiple_vorbis_comments() { diff --git a/lofty/tests/files/zero_sized.rs b/lofty/tests/files/zero_sized.rs index bab012e9c..bb8987775 100644 --- a/lofty/tests/files/zero_sized.rs +++ b/lofty/tests/files/zero_sized.rs @@ -1,11 +1,12 @@ use lofty::ape::ApeFile; -use lofty::config::{ParseOptions, ParsingMode}; +use lofty::config::ParseOptions; use lofty::flac::FlacFile; use lofty::iff::aiff::AiffFile; use lofty::iff::wav::WavFile; use lofty::mp4::Mp4File; use lofty::mpeg::MpegFile; use lofty::prelude::*; +use aud_io::config::ParsingMode; fn read_file_with_properties(path: &str) -> bool { let res = ::read_from( diff --git a/lofty/tests/fuzz/id3v2.rs b/lofty/tests/fuzz/id3v2.rs index 098956c37..9cf7d602c 100644 --- a/lofty/tests/fuzz/id3v2.rs +++ b/lofty/tests/fuzz/id3v2.rs @@ -24,7 +24,7 @@ fn overflow1() { 50, 5, 5, 5, 26, 5, 5, 25, 6, 6, 25, 26, 246, 25, 25, 129, 6, 151, 3, 252, 56, 0, 53, 56, 55, 52, ]; - let _local0 = ::default(); + let _local0 = ::default(); let _local1_param0_helper1 = &mut (&data[..]); let _: lofty::error::Result< std::option::Option>, diff --git a/lofty/tests/io.rs b/lofty/tests/io.rs new file mode 100644 index 000000000..49d8b3dc4 --- /dev/null +++ b/lofty/tests/io.rs @@ -0,0 +1,122 @@ +//! Tests to verify that the `FileLike` traits work for the expected types + +use lofty::config::{ParseOptions, WriteOptions}; +use lofty::file::AudioFile; +use lofty::mpeg::MpegFile; +use lofty::tag::Accessor; + +use std::io::{Cursor, Read, Seek, Write}; + +const TEST_ASSET: &str = "tests/files/assets/minimal/full_test.mp3"; + +fn test_asset_contents() -> Vec { + std::fs::read(TEST_ASSET).unwrap() +} + +fn file() -> MpegFile { + let file_contents = test_asset_contents(); + let mut reader = Cursor::new(file_contents); + MpegFile::read_from(&mut reader, ParseOptions::new()).unwrap() +} + +fn alter_tag(file: &mut MpegFile) { + let tag = file.id3v2_mut().unwrap(); + tag.set_artist(String::from("Bar artist")); +} + +fn revert_tag(file: &mut MpegFile) { + let tag = file.id3v2_mut().unwrap(); + tag.set_artist(String::from("Foo artist")); +} + +#[test_log::test] +fn io_save_to_file() { + // Read the file and change the artist + let mut file = file(); + alter_tag(&mut file); + + let mut temp_file = tempfile::tempfile().unwrap(); + let file_content = std::fs::read(TEST_ASSET).unwrap(); + temp_file.write_all(&file_content).unwrap(); + temp_file.rewind().unwrap(); + + // Save the new artist + file.save_to(&mut temp_file, WriteOptions::new().preferred_padding(0)) + .expect("Failed to save to file"); + + // Read the file again and change the artist back + temp_file.rewind().unwrap(); + let mut file = MpegFile::read_from(&mut temp_file, ParseOptions::new()).unwrap(); + revert_tag(&mut file); + + temp_file.rewind().unwrap(); + file.save_to(&mut temp_file, WriteOptions::new().preferred_padding(0)) + .expect("Failed to save to file"); + + // The contents should be the same as the original file + temp_file.rewind().unwrap(); + let mut current_file_contents = Vec::new(); + temp_file.read_to_end(&mut current_file_contents).unwrap(); + + assert_eq!(current_file_contents, test_asset_contents()); +} + +#[test_log::test] +fn io_save_to_vec() { + // Same test as above, but using a Cursor> instead of a file + let mut file = file(); + alter_tag(&mut file); + + let file_content = std::fs::read(TEST_ASSET).unwrap(); + + let mut reader = Cursor::new(file_content); + file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) + .expect("Failed to save to vec"); + + reader.rewind().unwrap(); + let mut file = MpegFile::read_from(&mut reader, ParseOptions::new()).unwrap(); + revert_tag(&mut file); + + reader.rewind().unwrap(); + file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) + .expect("Failed to save to vec"); + + let current_file_contents = reader.into_inner(); + assert_eq!(current_file_contents, test_asset_contents()); +} + +#[test_log::test] +fn io_save_using_references() { + struct File { + buf: Vec, + } + + let mut f = File { + buf: std::fs::read(TEST_ASSET).unwrap(), + }; + + // Same test as above, but using references instead of owned values + let mut file = file(); + alter_tag(&mut file); + + { + let mut reader = Cursor::new(&mut f.buf); + file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) + .expect("Failed to save to vec"); + } + + { + let mut reader = Cursor::new(&f.buf[..]); + file = MpegFile::read_from(&mut reader, ParseOptions::new()).unwrap(); + revert_tag(&mut file); + } + + { + let mut reader = Cursor::new(&mut f.buf); + file.save_to(&mut reader, WriteOptions::new().preferred_padding(0)) + .expect("Failed to save to vec"); + } + + let current_file_contents = f.buf; + assert_eq!(current_file_contents, test_asset_contents()); +} \ No newline at end of file diff --git a/lofty/tests/picture/format_parsers.rs b/lofty/tests/picture/format_parsers.rs index 64401189b..6663e8047 100644 --- a/lofty/tests/picture/format_parsers.rs +++ b/lofty/tests/picture/format_parsers.rs @@ -1,7 +1,7 @@ use lofty::TextEncoding; -use lofty::config::ParsingMode; use lofty::id3::v2::{AttachedPictureFrame, FrameFlags, Id3v2Version}; use lofty::picture::{Picture, PictureInformation, PictureType}; +use aud_io::config::ParsingMode; use std::fs::File; use std::io::Read; diff --git a/lofty/tests/taglib/test_flacpicture.rs b/lofty/tests/taglib/test_flacpicture.rs index 41a5ea65b..3e7b548c0 100644 --- a/lofty/tests/taglib/test_flacpicture.rs +++ b/lofty/tests/taglib/test_flacpicture.rs @@ -1,5 +1,5 @@ -use lofty::config::ParsingMode; use lofty::picture::{MimeType, Picture, PictureType}; +use aud_io::config::ParsingMode; const DATA: &[u8] = &[ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x09, 0x69, 0x6D, 0x61, 0x67, 0x65, 0x2F, 0x70, 0x6E, diff --git a/lofty/tests/taglib/test_id3v2.rs b/lofty/tests/taglib/test_id3v2.rs index b3fbe8df7..da01e2a6e 100644 --- a/lofty/tests/taglib/test_id3v2.rs +++ b/lofty/tests/taglib/test_id3v2.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use std::io::{Read, Seek}; use lofty::TextEncoding; -use lofty::config::{ParseOptions, ParsingMode, WriteOptions}; +use lofty::config::{ParseOptions, WriteOptions}; use lofty::file::AudioFile; use lofty::id3::v2::{ AttachedPictureFrame, ChannelInformation, ChannelType, CommentFrame, Event, @@ -19,6 +19,7 @@ use lofty::mpeg::MpegFile; use lofty::picture::{MimeType, Picture, PictureType}; use lofty::tag::items::Timestamp; use lofty::tag::{Accessor, TagExt}; +use aud_io::config::ParsingMode; #[test_log::test] fn test_unsynch_decode() { diff --git a/lofty/tests/taglib/test_mp4.rs b/lofty/tests/taglib/test_mp4.rs index 7df7738b8..589ade078 100644 --- a/lofty/tests/taglib/test_mp4.rs +++ b/lofty/tests/taglib/test_mp4.rs @@ -6,9 +6,10 @@ use std::io::{Read, Seek}; use lofty::config::{ParseOptions, WriteOptions}; use lofty::file::AudioFile; -use lofty::mp4::{Atom, AtomData, AtomIdent, Ilst, Mp4Codec, Mp4File}; +use lofty::mp4::{Atom, AtomData, Ilst, Mp4Codec, Mp4File}; use lofty::picture::{MimeType, Picture, PictureType}; use lofty::tag::{Accessor, TagExt, TagType}; +use aud_io::mp4::AtomIdent; #[test_log::test] fn test_properties_aac() {