diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ca73aaf1..6ecc5d578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ChannelMask::from_{mp4, opus}_channels` - **Opus**: `OpusProperties` now contains the channel mask - **AAC**: `AacProperties` now contains the channel mask +- **Prelude**: `lofty::prelude` module to make trait imports easier ([PR](https://github.com/Serial-ATA/lofty-rs/pull/374)) ### Changed - **Properties**: `FileProperties` and `ChannelMask` have been moved from the root to the new `lofty::properties` module ([PR](https://github.com/Serial-ATA/lofty-rs/pull/372)) +- **ParseOptions**/**WriteOptions**/**GlobalOptions**: + - ⚠️ Important ⚠️: Moved to `lofty::options` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/374)) +- **AudioFile**/**TaggedFileExt**/**TaggedFile**/**BoundTaggedFile**: + - ⚠️ Important ⚠️: Moved to `lofty::file` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/374)) +- **Tag**: + - ⚠️ Important ⚠️- The following items have been moved to `lofty::tag` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/374)): + - `Tag` + - `Accessor` + - `TagType` + - `TagItem` + - `ItemKey` + - `ItemValue` +- **Probe**: + - ⚠️ Important ⚠️- Moved to `lofty::probe` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/374)): +- **Picture**: + - ⚠️ Important ⚠️- The following items have been moved to `lofty::picture` ([PR](https://github.com/Serial-ATA/lofty-rs/pull/374)): + - `Picture` + - `PictureType` + - `PictureInformation` + - `MimeType` ### Fixed - **Vorbis**: Fix panic when reading properties of zero-length files ([issue](https://github.com/Serial-ATA/lofty-rs/issues/342)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/365)) diff --git a/benches/create_tag.rs b/benches/create_tag.rs index 86b9aebfa..a6d7d7553 100644 --- a/benches/create_tag.rs +++ b/benches/create_tag.rs @@ -1,11 +1,13 @@ use lofty::ape::ApeTag; +use lofty::config::WriteOptions; use lofty::id3::v1::Id3v1Tag; use lofty::id3::v2::Id3v2Tag; use lofty::iff::aiff::AIFFTextChunks; use lofty::iff::wav::RIFFInfoList; use lofty::mp4::Ilst; use lofty::ogg::VorbisComments; -use lofty::{Accessor, MimeType, Picture, PictureType, TagExt, WriteOptions}; +use lofty::picture::{MimeType, Picture, PictureType}; +use lofty::tag::{Accessor, TagExt}; use iai_callgrind::{library_benchmark, library_benchmark_group, main}; @@ -36,7 +38,7 @@ bench_tag_write!([ (aiff_text_chunks, AIFFTextChunks, |tag| {}), (apev2, ApeTag, |tag| { use lofty::ape::ApeItem; - use lofty::ItemValue; + use lofty::tag::ItemValue; let picture = Picture::new_unchecked( PictureType::CoverFront, diff --git a/benches/read_file.rs b/benches/read_file.rs index d567cda1d..488438b2a 100644 --- a/benches/read_file.rs +++ b/benches/read_file.rs @@ -1,4 +1,5 @@ -use lofty::{ParseOptions, Probe}; +use lofty::config::ParseOptions; +use lofty::probe::Probe; use iai_callgrind::{library_benchmark, library_benchmark_group, main}; diff --git a/examples/custom_resolver/src/main.rs b/examples/custom_resolver/src/main.rs index fdd18a5d7..02d9508a4 100644 --- a/examples/custom_resolver/src/main.rs +++ b/examples/custom_resolver/src/main.rs @@ -1,9 +1,11 @@ use lofty::ape::ApeTag; +use lofty::config::{GlobalOptions, ParseOptions}; use lofty::error::Result as LoftyResult; +use lofty::file::FileType; use lofty::id3::v2::Id3v2Tag; use lofty::properties::FileProperties; use lofty::resolve::FileResolver; -use lofty::{FileType, GlobalOptions, ParseOptions, TagType}; +use lofty::tag::TagType; use lofty_attr::LoftyFile; use std::fs::File; @@ -95,7 +97,7 @@ fn main() { // By default, lofty will not check for custom files. // We can enable this by updating our `GlobalOptions`. let global_options = GlobalOptions::new().use_custom_resolvers(true); - lofty::apply_global_options(global_options); + lofty::config::apply_global_options(global_options); // Now when using the following functions, your custom file will be checked diff --git a/examples/tag_reader.rs b/examples/tag_reader.rs index d486241e2..8392e3517 100644 --- a/examples/tag_reader.rs +++ b/examples/tag_reader.rs @@ -1,4 +1,6 @@ -use lofty::{Accessor, AudioFile, Probe, TaggedFileExt}; +use lofty::prelude::*; +use lofty::probe::Probe; + use std::path::Path; fn main() { @@ -31,8 +33,7 @@ fn main() { // import keys from https://docs.rs/lofty/latest/lofty/enum.ItemKey.html println!( "Album Artist: {}", - tag.get_string(&lofty::ItemKey::AlbumArtist) - .unwrap_or("None") + tag.get_string(&ItemKey::AlbumArtist).unwrap_or("None") ); let properties = tagged_file.properties(); diff --git a/examples/tag_stripper.rs b/examples/tag_stripper.rs index 433cd98be..039b04e9e 100644 --- a/examples/tag_stripper.rs +++ b/examples/tag_stripper.rs @@ -1,4 +1,6 @@ -use lofty::{Probe, TaggedFileExt}; +use lofty::file::TaggedFileExt; +use lofty::probe::Probe; + use std::io::Write; fn main() { diff --git a/examples/tag_writer.rs b/examples/tag_writer.rs index f72a9ce7f..2887ee8cb 100644 --- a/examples/tag_writer.rs +++ b/examples/tag_writer.rs @@ -1,4 +1,7 @@ -use lofty::{Accessor, Probe, Tag, TagExt, TaggedFileExt, WriteOptions}; +use lofty::config::WriteOptions; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::Tag; use structopt::StructOpt; diff --git a/lofty_attr/src/internal.rs b/lofty_attr/src/internal.rs index d0f9aaf5e..6123dde7d 100644 --- a/lofty_attr/src/internal.rs +++ b/lofty_attr/src/internal.rs @@ -78,10 +78,10 @@ pub(crate) fn init_write_lookup( insert!(map, AiffText, { lofty::iff::aiff::tag::AiffTextChunksRef { - name: tag.get_string(&lofty::tag::item::ItemKey::TrackTitle), - author: tag.get_string(&lofty::tag::item::ItemKey::TrackArtist), - copyright: tag.get_string(&lofty::tag::item::ItemKey::CopyrightMessage), - annotations: Some(tag.get_strings(&lofty::tag::item::ItemKey::Comment)), + name: tag.get_string(&lofty::prelude::ItemKey::TrackTitle), + author: tag.get_string(&lofty::prelude::ItemKey::TrackArtist), + copyright: tag.get_string(&lofty::prelude::ItemKey::CopyrightMessage), + annotations: Some(tag.get_strings(&lofty::prelude::ItemKey::Comment)), comments: None, } .write_to(data, write_options) @@ -96,11 +96,12 @@ pub(crate) fn write_module( ) -> proc_macro2::TokenStream { let applicable_formats = fields.iter().map(|f| { let tag_ty = - syn::parse_str::(&format!("::lofty::TagType::{}", &f.tag_type)).unwrap(); + syn::parse_str::(&format!("::lofty::tag::TagType::{}", &f.tag_type)) + .unwrap(); let cfg_features = f.get_cfg_features(); - let block = lookup.get(&*tag_ty.segments[2].ident.to_string()).unwrap(); + let block = lookup.get(&*tag_ty.segments[3].ident.to_string()).unwrap(); quote! { #( #cfg_features )* @@ -111,7 +112,7 @@ pub(crate) fn write_module( quote! { pub(crate) mod write { #[allow(unused_variables)] - pub(crate) fn write_to(data: &mut ::std::fs::File, tag: &::lofty::Tag, write_options: ::lofty::WriteOptions) -> ::lofty::error::Result<()> { + pub(crate) fn write_to(data: &mut ::std::fs::File, tag: &::lofty::tag::Tag, write_options: ::lofty::config::WriteOptions) -> ::lofty::error::Result<()> { match tag.tag_type() { #( #applicable_formats )* _ => crate::macros::err!(UnsupportedTag), diff --git a/lofty_attr/src/lofty_file.rs b/lofty_attr/src/lofty_file.rs index 12bd8137a..250151a1c 100644 --- a/lofty_attr/src/lofty_file.rs +++ b/lofty_attr/src/lofty_file.rs @@ -178,7 +178,7 @@ impl LoftyFile { let name = format_ident!("_AssertTagExt{}", i); let field_ty = &f.ty; quote_spanned! {field_ty.span()=> - struct #name where #field_ty: lofty::TagExt; + struct #name where #field_ty: ::lofty::prelude::TagExt; } }); @@ -435,18 +435,18 @@ fn generate_audiofile_impl(file: &LoftyFile) -> syn::Result(reader: &mut R, parse_options: ::lofty::ParseOptions) -> ::lofty::error::Result + fn read_from(reader: &mut R, parse_options: ::lofty::config::ParseOptions) -> ::lofty::error::Result where R: std::io::Read + std::io::Seek, { #read_fn(reader, parse_options) } - fn save_to(&self, file: &mut ::std::fs::File, write_options: ::lofty::WriteOptions) -> ::lofty::error::Result<()> { - use ::lofty::TagExt as _; + fn save_to(&self, file: &mut ::std::fs::File, write_options: ::lofty::config::WriteOptions) -> ::lofty::error::Result<()> { + use ::lofty::tag::TagExt as _; use ::std::io::Seek as _; #save_to_body } @@ -461,9 +461,9 @@ fn generate_audiofile_impl(file: &LoftyFile) -> syn::Result bool { + fn contains_tag_type(&self, tag_type: ::lofty::tag::TagType) -> bool { match tag_type { - #( ::lofty::TagType::#tag_type => { #tag_exists_2 } ),* + #( ::lofty::tag::TagType::#tag_type => { #tag_exists_2 } ),* _ => false } } @@ -523,23 +523,23 @@ fn generate_from_taggedfile_impl(file: &LoftyFile) -> proc_macro2::TokenStream { let file_type = &file.file_type; let file_type_variant = if file.internal_details.has_internal_file_type { - quote! { ::lofty::FileType::#file_type } + quote! { ::lofty::file::FileType::#file_type } } else { let file_ty_str = file_type.to_string(); - quote! { ::lofty::FileType::Custom(#file_ty_str) } + quote! { ::lofty::file::FileType::Custom(#file_ty_str) } }; let struct_name = &file.struct_info.name; quote! { - impl ::std::convert::From<#struct_name> for ::lofty::TaggedFile { + impl ::std::convert::From<#struct_name> for ::lofty::file::TaggedFile { fn from(input: #struct_name) -> Self { - use ::lofty::TaggedFileExt as _; + use ::lofty::prelude::TaggedFileExt as _; - ::lofty::TaggedFile::new( + ::lofty::file::TaggedFile::new( #file_type_variant, ::lofty::properties::FileProperties::from(input.properties), { - let mut tags: Vec<::lofty::Tag> = Vec::new(); + let mut tags: Vec<::lofty::tag::Tag> = Vec::new(); #( #conditions )* tags diff --git a/lofty_attr/src/lofty_tag.rs b/lofty_attr/src/lofty_tag.rs index 8d3712e5a..3e88d45c2 100644 --- a/lofty_attr/src/lofty_tag.rs +++ b/lofty_attr/src/lofty_tag.rs @@ -83,12 +83,12 @@ impl LoftyTag { #input impl #ident { - pub(crate) const SUPPORTED_FORMATS: &'static [::lofty::FileType] = &[ - #( ::lofty::FileType:: #flattened_file_types ),* + pub(crate) const SUPPORTED_FORMATS: &'static [::lofty::file::FileType] = &[ + #( ::lofty::file::FileType:: #flattened_file_types ),* ]; - pub(crate) const READ_ONLY_FORMATS: &'static [::lofty::FileType] = &[ - #( ::lofty::FileType:: #read_only_file_types ),* + pub(crate) const READ_ONLY_FORMATS: &'static [::lofty::file::FileType] = &[ + #( ::lofty::file::FileType:: #read_only_file_types ),* ]; } }) diff --git a/src/aac/header.rs b/src/aac/header.rs index 57c8765a2..5b456eb0b 100644 --- a/src/aac/header.rs +++ b/src/aac/header.rs @@ -1,8 +1,8 @@ +use crate::config::ParsingMode; use crate::error::Result; use crate::macros::decode_err; use crate::mp4::{AudioObjectType, SAMPLE_RATES}; use crate::mpeg::MpegVersion; -use crate::probe::ParsingMode; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/aac/read.rs b/src/aac/read.rs index 4194853da..43c04b75f 100644 --- a/src/aac/read.rs +++ b/src/aac/read.rs @@ -1,12 +1,12 @@ use super::header::{ADTSHeader, HEADER_MASK}; use super::AacFile; +use crate::config::{ParseOptions, ParsingMode}; use crate::error::Result; use crate::id3::v2::header::Id3v2Header; use crate::id3::v2::read::parse_id3v2; use crate::id3::{find_id3v1, ID3FindResults}; use crate::macros::{decode_err, parse_mode_choice}; use crate::mpeg::header::{cmp_header, search_for_frame_sync, HeaderCmpResult}; -use crate::probe::{ParseOptions, ParsingMode}; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/ape/header.rs b/src/ape/header.rs index a093a6924..23c0f45f7 100644 --- a/src/ape/header.rs +++ b/src/ape/header.rs @@ -1,6 +1,6 @@ use crate::error::Result; use crate::macros::decode_err; -use crate::traits::SeekStreamLen; +use crate::util::io::SeekStreamLen; use std::io::{Read, Seek, SeekFrom}; use std::ops::Neg; @@ -44,8 +44,7 @@ where size = size.saturating_add(32); } - #[allow(unstable_name_collisions)] - if u64::from(size) > data.stream_len()? { + if u64::from(size) > data.stream_len_hack()? { decode_err!(@BAIL Ape, "APE tag has an invalid size (> file size)"); } diff --git a/src/ape/properties.rs b/src/ape/properties.rs index 3fa509d02..3836bc750 100644 --- a/src/ape/properties.rs +++ b/src/ape/properties.rs @@ -1,6 +1,6 @@ +use crate::config::ParsingMode; use crate::error::Result; use crate::macros::decode_err; -use crate::probe::ParsingMode; use crate::properties::FileProperties; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/ape/read.rs b/src/ape/read.rs index 02a1e2c81..4b01e996b 100644 --- a/src/ape/read.rs +++ b/src/ape/read.rs @@ -2,13 +2,13 @@ use super::header::read_ape_header; use super::tag::ApeTag; use super::{ApeFile, ApeProperties}; use crate::ape::tag::read::{read_ape_tag, read_ape_tag_with_header}; +use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v1::tag::Id3v1Tag; use crate::id3::v2::read::parse_id3v2; use crate::id3::v2::tag::Id3v2Tag; use crate::id3::{find_id3v1, find_id3v2, find_lyrics3v2, FindId3v2Config, ID3FindResults}; use crate::macros::decode_err; -use crate::probe::ParseOptions; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/ape/tag/item.rs b/src/ape/tag/item.rs index 90b5a5dd7..3cc8118dd 100644 --- a/src/ape/tag/item.rs +++ b/src/ape/tag/item.rs @@ -1,8 +1,8 @@ use crate::ape::constants::INVALID_KEYS; use crate::error::{LoftyError, Result}; use crate::macros::decode_err; -use crate::tag::item::{ItemValue, ItemValueRef, TagItem}; -use crate::tag::TagType; +use crate::tag::item::ItemValueRef; +use crate::tag::{ItemValue, TagItem, TagType}; /// Represents an `APE` tag item /// diff --git a/src/ape/tag/mod.rs b/src/ape/tag/mod.rs index ec2d76f24..6be290c5e 100644 --- a/src/ape/tag/mod.rs +++ b/src/ape/tag/mod.rs @@ -3,12 +3,13 @@ pub(crate) mod read; mod write; use crate::ape::tag::item::{ApeItem, ApeItemRef}; +use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::id3::v2::util::pairs::{format_number_pair, set_number, NUMBER_PAIR_KEYS}; -use crate::tag::item::{ItemKey, ItemValue, ItemValueRef, TagItem}; -use crate::tag::{try_parse_year, Tag, TagType}; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; -use crate::write_options::WriteOptions; +use crate::tag::item::ItemValueRef; +use crate::tag::{ + try_parse_year, Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, +}; use std::borrow::Cow; use std::fs::File; @@ -89,7 +90,7 @@ impl ApeTag { /// /// ```rust /// use lofty::ape::ApeTag; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let ape_tag = ApeTag::new(); /// assert!(ape_tag.is_empty()); @@ -107,7 +108,7 @@ impl ApeTag { /// /// ```rust /// use lofty::ape::ApeTag; - /// use lofty::Accessor; + /// use lofty::tag::Accessor; /// /// let mut ape_tag = ApeTag::new(); /// ape_tag.set_title(String::from("Foo title")); @@ -138,7 +139,7 @@ impl ApeTag { /// /// ```rust /// use lofty::ape::ApeTag; - /// use lofty::Accessor; + /// use lofty::tag::Accessor; /// /// let mut ape_tag = ApeTag::new(); /// ape_tag.set_title(String::from("Foo title")); @@ -544,9 +545,11 @@ pub(crate) fn tagitems_into_ape(tag: &Tag) -> impl Iterator &'static GlobalOptions { /// # Examples /// /// ```rust -/// use lofty::GlobalOptions; +/// use lofty::config::{apply_global_options, GlobalOptions}; /// /// // I have a custom resolver that I need checked /// let global_options = GlobalOptions::new().use_custom_resolvers(true); -/// lofty::apply_global_options(global_options); +/// apply_global_options(global_options); /// ``` #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] #[non_exhaustive] @@ -37,7 +37,7 @@ impl GlobalOptions { /// # Examples /// /// ```rust - /// use lofty::GlobalOptions; + /// use lofty::config::GlobalOptions; /// /// let global_options = GlobalOptions::new(); /// ``` @@ -56,11 +56,11 @@ impl GlobalOptions { /// # Examples /// /// ```rust - /// use lofty::GlobalOptions; + /// use lofty::config::{apply_global_options, GlobalOptions}; /// /// // By default, `use_custom_resolvers` is enabled. Here, we don't want to use them. /// let global_options = GlobalOptions::new().use_custom_resolvers(false); - /// lofty::apply_global_options(global_options); + /// apply_global_options(global_options); /// ``` pub fn use_custom_resolvers(&mut self, use_custom_resolvers: bool) -> Self { self.use_custom_resolvers = use_custom_resolvers; @@ -75,11 +75,11 @@ impl GlobalOptions { /// # Examples /// /// ```rust - /// use lofty::GlobalOptions; + /// use lofty::config::{apply_global_options, GlobalOptions}; /// /// // I have files with gigantic images, I'll double the allocation limit! /// let global_options = GlobalOptions::new().allocation_limit(32 * 1024 * 1024); - /// lofty::apply_global_options(global_options); + /// apply_global_options(global_options); /// ``` pub fn allocation_limit(&mut self, allocation_limit: usize) -> Self { self.allocation_limit = allocation_limit; @@ -108,11 +108,11 @@ impl Default for GlobalOptions { /// # Examples /// /// ```rust -/// use lofty::GlobalOptions; +/// use lofty::config::{apply_global_options, GlobalOptions}; /// /// // I have a custom resolver that I need checked /// let global_options = GlobalOptions::new().use_custom_resolvers(true); -/// lofty::apply_global_options(global_options); +/// apply_global_options(global_options); /// ``` pub fn apply_global_options(options: GlobalOptions) { GLOBAL_OPTIONS.with(|global_options| unsafe { diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 000000000..3a998d2ad --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,11 @@ +//! Various configuration options to control Lofty + +mod global_options; +mod parse_options; +mod write_options; + +pub use global_options::{apply_global_options, GlobalOptions}; +pub use parse_options::{ParseOptions, ParsingMode}; +pub use write_options::WriteOptions; + +pub(crate) use global_options::global_options; diff --git a/src/config/parse_options.rs b/src/config/parse_options.rs new file mode 100644 index 000000000..c11d6cdbd --- /dev/null +++ b/src/config/parse_options.rs @@ -0,0 +1,154 @@ +/// Options to control how Lofty parses a file +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub struct ParseOptions { + pub(crate) read_properties: bool, + pub(crate) parsing_mode: ParsingMode, + pub(crate) max_junk_bytes: usize, +} + +impl Default for ParseOptions { + /// The default implementation for `ParseOptions` + /// + /// The defaults are as follows: + /// + /// ```rust,ignore + /// ParseOptions { + /// read_properties: true, + /// parsing_mode: ParsingMode::BestAttempt, + /// max_junk_bytes: 1024 + /// } + /// ``` + fn default() -> Self { + Self::new() + } +} + +impl ParseOptions { + /// Default parsing mode + pub const DEFAULT_PARSING_MODE: ParsingMode = ParsingMode::BestAttempt; + + /// Default number of junk bytes to read + pub const DEFAULT_MAX_JUNK_BYTES: usize = 1024; + + /// Creates a new `ParseOptions`, alias for `Default` implementation + /// + /// See also: [`ParseOptions::default`] + /// + /// # Examples + /// + /// ```rust + /// use lofty::config::ParseOptions; + /// + /// let parsing_options = ParseOptions::new(); + /// ``` + #[must_use] + pub const fn new() -> Self { + Self { + read_properties: true, + parsing_mode: Self::DEFAULT_PARSING_MODE, + max_junk_bytes: Self::DEFAULT_MAX_JUNK_BYTES, + } + } + + /// Whether or not to read the audio properties + /// + /// # Examples + /// + /// ```rust + /// use lofty::config::ParseOptions; + /// + /// // By default, `read_properties` is enabled. Here, we don't want to read them. + /// let parsing_options = ParseOptions::new().read_properties(false); + /// ``` + pub fn read_properties(&mut self, read_properties: bool) -> Self { + self.read_properties = read_properties; + *self + } + + /// The parsing mode to use, see [`ParsingMode`] for details + /// + /// # Examples + /// + /// ```rust + /// use lofty::config::{ParseOptions, ParsingMode}; + /// + /// // By default, `parsing_mode` is ParsingMode::BestAttempt. Here, we need absolute correctness. + /// let parsing_options = ParseOptions::new().parsing_mode(ParsingMode::Strict); + /// ``` + pub fn parsing_mode(&mut self, parsing_mode: ParsingMode) -> Self { + self.parsing_mode = parsing_mode; + *self + } + + /// The maximum number of allowed junk bytes to search + /// + /// Some information may be surrounded by junk bytes, such as tag padding remnants. This sets the maximum + /// number of junk/unrecognized bytes Lofty will search for required information before giving up. + /// + /// # Examples + /// + /// ```rust + /// use lofty::config::ParseOptions; + /// + /// // I have files full of junk, I'll double the search window! + /// let parsing_options = ParseOptions::new().max_junk_bytes(2048); + /// ``` + pub fn max_junk_bytes(&mut self, max_junk_bytes: usize) -> Self { + self.max_junk_bytes = max_junk_bytes; + *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/src/write_options.rs b/src/config/write_options.rs similarity index 89% rename from src/write_options.rs rename to src/config/write_options.rs index 2fbf2d628..b7c70ebd6 100644 --- a/src/write_options.rs +++ b/src/config/write_options.rs @@ -22,7 +22,7 @@ impl WriteOptions { /// # Examples /// /// ```rust - /// use lofty::WriteOptions; + /// use lofty::config::WriteOptions; /// /// let write_options = WriteOptions::new(); /// ``` @@ -48,7 +48,7 @@ impl WriteOptions { /// # Examples /// /// ```rust - /// use lofty::WriteOptions; + /// use lofty::config::WriteOptions; /// /// // I really don't want my files rewritten, so I'll double the padding size! /// let options = WriteOptions::new().preferred_padding(2048); @@ -71,9 +71,11 @@ impl WriteOptions { /// # Examples /// /// ```rust,no_run - /// use lofty::{Tag, TagExt, TagType, WriteOptions}; + /// use lofty::config::WriteOptions; + /// use lofty::prelude::*; + /// use lofty::tag::{Tag, TagType}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// let mut id3v2_tag = Tag::new(TagType::Id3v2); /// /// // ... @@ -99,9 +101,11 @@ impl WriteOptions { /// # Examples /// /// ```rust,no_run - /// use lofty::{Tag, TagExt, TagType, WriteOptions}; + /// use lofty::config::WriteOptions; + /// use lofty::prelude::*; + /// use lofty::tag::{Tag, TagType}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// let mut id3v2_tag = Tag::new(TagType::Id3v2); /// /// // ... @@ -127,9 +131,11 @@ impl WriteOptions { /// # Examples /// /// ```rust,no_run - /// use lofty::{Tag, TagExt, TagType, WriteOptions}; + /// use lofty::config::WriteOptions; + /// use lofty::prelude::*; + /// use lofty::tag::{Tag, TagType}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// let mut id3v2_tag = Tag::new(TagType::Id3v2); /// /// // ... diff --git a/src/error.rs b/src/error.rs index 35d9dce74..9a6275bf0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use crate::file::FileType; use crate::id3::v2::FrameId; -use crate::tag::item::ItemKey; +use crate::tag::ItemKey; use std::collections::TryReserveError; use std::fmt::{Debug, Display, Formatter}; @@ -94,7 +94,7 @@ pub enum Id3v2ErrorKind { UnsupportedFrameId(ItemKey), /// Arises when a frame doesn't have enough data BadFrameLength, - /// Arises when a frame with no content is parsed with [ParsingMode::Strict](crate::probe::ParsingMode::Strict) + /// Arises when a frame with no content is parsed with [ParsingMode::Strict](crate::config::ParsingMode::Strict) EmptyFrame(FrameId<'static>), /// Arises when reading/writing a compressed or encrypted frame with no data length indicator MissingDataLengthIndicator, @@ -299,7 +299,7 @@ impl FileEncodingError { /// /// ```rust /// use lofty::error::FileEncodingError; - /// use lofty::FileType; + /// use lofty::file::FileType; /// /// // This error is bounded to `FileType::Mpeg`, which will be displayed when the error is formatted /// let mpeg_error = @@ -319,7 +319,7 @@ impl FileEncodingError { /// /// ```rust /// use lofty::error::FileEncodingError; - /// use lofty::FileType; + /// use lofty::file::FileType; /// /// // The error isn't bounded to FileType::Mpeg, only the message will be displayed when the /// // error is formatted @@ -338,7 +338,7 @@ impl FileEncodingError { /// /// ```rust /// use lofty::error::FileEncodingError; - /// use lofty::FileType; + /// use lofty::file::FileType; /// /// let mpeg_error = /// FileEncodingError::new(FileType::Mpeg, "Something went wrong in the MPEG file!"); @@ -355,7 +355,7 @@ impl FileEncodingError { /// /// ```rust /// use lofty::error::FileEncodingError; - /// use lofty::FileType; + /// use lofty::file::FileType; /// /// let mpeg_error = /// FileEncodingError::new(FileType::Mpeg, "Something went wrong in the MPEG file!"); @@ -401,8 +401,7 @@ impl LoftyError { /// # Examples /// /// ```rust - /// use lofty::error::ErrorKind; - /// use lofty::LoftyError; + /// use lofty::error::{ErrorKind, LoftyError}; /// /// let unknown_format = LoftyError::new(ErrorKind::UnknownFormat); /// ``` @@ -416,8 +415,7 @@ impl LoftyError { /// # Examples /// /// ```rust - /// use lofty::error::ErrorKind; - /// use lofty::LoftyError; + /// use lofty::error::{ErrorKind, LoftyError}; /// /// let unknown_format = LoftyError::new(ErrorKind::UnknownFormat); /// if let ErrorKind::UnknownFormat = unknown_format.kind() { diff --git a/src/file/audio_file.rs b/src/file/audio_file.rs new file mode 100644 index 000000000..5b1b4bee7 --- /dev/null +++ b/src/file/audio_file.rs @@ -0,0 +1,88 @@ +use super::tagged_file::TaggedFile; +use crate::config::{ParseOptions, WriteOptions}; +use crate::error::Result; +use crate::tag::TagType; + +use std::fs::{File, OpenOptions}; +use std::io::{Read, Seek}; +use std::path::Path; + +/// Provides various methods for interaction with a file +pub trait AudioFile: Into { + /// The struct the file uses for audio properties + /// + /// Not all formats can use [`FileProperties`](crate::properties::FileProperties) since they may contain additional information + type Properties; + + /// Read a file from a reader + /// + /// # Errors + /// + /// Errors depend on the file and tags being read. See [`LoftyError`](crate::error::LoftyError) + fn read_from(reader: &mut R, parse_options: ParseOptions) -> Result + where + R: Read + Seek, + Self: Sized; + + /// Attempts to write all tags to a path + /// + /// # Errors + /// + /// * `path` does not exist + /// * `path` is not writable + /// * See [`AudioFile::save_to`] + /// + /// # Examples + /// + /// ```rust,no_run + /// use lofty::config::WriteOptions; + /// use lofty::file::{AudioFile, TaggedFileExt}; + /// + /// # fn main() -> lofty::error::Result<()> { + /// # let path = "tests/files/assets/minimal/full_test.mp3"; + /// let mut tagged_file = lofty::read_from_path(path)?; + /// + /// // Edit the tags + /// + /// tagged_file.save_to_path(path, WriteOptions::default())?; + /// # Ok(()) } + /// ``` + fn save_to_path(&self, path: impl AsRef, write_options: WriteOptions) -> Result<()> { + self.save_to( + &mut OpenOptions::new().read(true).write(true).open(path)?, + write_options, + ) + } + + /// Attempts to write all tags to a file + /// + /// # Errors + /// + /// See [`TagExt::save_to`](crate::tag::TagExt::save_to), however this is applicable to every tag in the file. + /// + /// # Examples + /// + /// ```rust,no_run + /// use lofty::config::WriteOptions; + /// use lofty::file::{AudioFile, TaggedFileExt}; + /// use std::fs::OpenOptions; + /// + /// # fn main() -> lofty::error::Result<()> { + /// # let path = "tests/files/assets/minimal/full_test.mp3"; + /// let mut tagged_file = lofty::read_from_path(path)?; + /// + /// // Edit the tags + /// + /// let mut file = OpenOptions::new().read(true).write(true).open(path)?; + /// tagged_file.save_to(&mut file, WriteOptions::default())?; + /// # Ok(()) } + /// ``` + fn save_to(&self, file: &mut File, write_options: WriteOptions) -> Result<()>; + + /// Returns a reference to the file's properties + fn properties(&self) -> &Self::Properties; + /// Checks if the file contains any tags + fn contains_tag(&self) -> bool; + /// Checks if the file contains the given [`TagType`] + fn contains_tag_type(&self, tag_type: TagType) -> bool; +} diff --git a/src/file/file_type.rs b/src/file/file_type.rs new file mode 100644 index 000000000..59e0c4efb --- /dev/null +++ b/src/file/file_type.rs @@ -0,0 +1,322 @@ +use crate::config::global_options; +use crate::resolve::custom_resolvers; +use crate::tag::TagType; + +use std::ffi::OsStr; +use std::path::Path; + +/// The type of file read +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[allow(missing_docs)] +#[non_exhaustive] +pub enum FileType { + Aac, + Aiff, + Ape, + Flac, + Mpeg, + Mp4, + Mpc, + Opus, + Vorbis, + Speex, + Wav, + WavPack, + Custom(&'static str), +} + +impl FileType { + /// Returns the file type's "primary" [`TagType`], or the one most likely to be used in the target format + /// + /// | [`FileType`] | [`TagType`] | + /// |-----------------------------------|------------------| + /// | `Aac`, `Aiff`, `Mp3`, `Wav` | `Id3v2` | + /// | `Ape` , `Mpc`, `WavPack` | `Ape` | + /// | `Flac`, `Opus`, `Vorbis`, `Speex` | `VorbisComments` | + /// | `Mp4` | `Mp4Ilst` | + /// + /// # Panics + /// + /// If an unregistered `FileType` ([`FileType::Custom`]) is encountered. See [`register_custom_resolver`](crate::resolve::register_custom_resolver). + /// + /// # Examples + /// + /// ```rust + /// use lofty::file::FileType; + /// use lofty::tag::TagType; + /// + /// let file_type = FileType::Mpeg; + /// assert_eq!(file_type.primary_tag_type(), TagType::Id3v2); + /// ``` + pub fn primary_tag_type(&self) -> TagType { + match self { + FileType::Aac | FileType::Aiff | FileType::Mpeg | FileType::Wav => TagType::Id3v2, + FileType::Ape | FileType::Mpc | FileType::WavPack => TagType::Ape, + FileType::Flac | FileType::Opus | FileType::Vorbis | FileType::Speex => { + TagType::VorbisComments + }, + FileType::Mp4 => TagType::Mp4Ilst, + FileType::Custom(c) => { + let resolver = crate::resolve::lookup_resolver(c); + resolver.primary_tag_type() + }, + } + } + + /// Returns if the target `FileType` supports a [`TagType`] + /// + /// NOTE: This is feature dependent, meaning if you do not have the + /// `id3v2` feature enabled, [`FileType::Mpeg`] will return `false` for + /// [`TagType::Id3v2`]. + /// + /// # Panics + /// + /// If an unregistered `FileType` ([`FileType::Custom`]) is encountered. See [`register_custom_resolver`](crate::resolve::register_custom_resolver). + /// + /// # Examples + /// + /// ```rust + /// use lofty::file::FileType; + /// use lofty::tag::TagType; + /// + /// let file_type = FileType::Mpeg; + /// assert!(file_type.supports_tag_type(TagType::Id3v2)); + /// ``` + pub fn supports_tag_type(&self, tag_type: TagType) -> bool { + if let FileType::Custom(c) = self { + let resolver = crate::resolve::lookup_resolver(c); + return resolver.supported_tag_types().contains(&tag_type); + } + + match tag_type { + TagType::Ape => crate::ape::ApeTag::SUPPORTED_FORMATS.contains(self), + TagType::Id3v1 => crate::id3::v1::Id3v1Tag::SUPPORTED_FORMATS.contains(self), + TagType::Id3v2 => crate::id3::v2::Id3v2Tag::SUPPORTED_FORMATS.contains(self), + TagType::Mp4Ilst => crate::mp4::Ilst::SUPPORTED_FORMATS.contains(self), + TagType::VorbisComments => crate::ogg::VorbisComments::SUPPORTED_FORMATS.contains(self), + TagType::RiffInfo => crate::iff::wav::RIFFInfoList::SUPPORTED_FORMATS.contains(self), + TagType::AiffText => crate::iff::aiff::AIFFTextChunks::SUPPORTED_FORMATS.contains(self), + } + } + + /// Attempts to extract a [`FileType`] from an extension + /// + /// # Examples + /// + /// ```rust + /// use lofty::file::FileType; + /// + /// let extension = "mp3"; + /// assert_eq!(FileType::from_ext(extension), Some(FileType::Mpeg)); + /// ``` + pub fn from_ext(ext: E) -> Option + where + E: AsRef, + { + let ext = ext.as_ref().to_str()?.to_ascii_lowercase(); + + // Give custom resolvers priority + if unsafe { global_options().use_custom_resolvers } { + if let Some((ty, _)) = custom_resolvers() + .lock() + .ok()? + .iter() + .find(|(_, f)| f.extension() == Some(ext.as_str())) + { + return Some(Self::Custom(ty)); + } + } + + match ext.as_str() { + "aac" => Some(Self::Aac), + "ape" => Some(Self::Ape), + "aiff" | "aif" | "afc" | "aifc" => Some(Self::Aiff), + "mp3" | "mp2" | "mp1" => Some(Self::Mpeg), + "wav" | "wave" => Some(Self::Wav), + "wv" => Some(Self::WavPack), + "opus" => Some(Self::Opus), + "flac" => Some(Self::Flac), + "ogg" => Some(Self::Vorbis), + "mp4" | "m4a" | "m4b" | "m4p" | "m4r" | "m4v" | "3gp" => Some(Self::Mp4), + "mpc" | "mp+" | "mpp" => Some(Self::Mpc), + "spx" => Some(Self::Speex), + _ => None, + } + } + + /// Attempts to determine a [`FileType`] from a path + /// + /// # Examples + /// + /// ```rust + /// use lofty::file::FileType; + /// use std::path::Path; + /// + /// let path = Path::new("path/to/my.mp3"); + /// assert_eq!(FileType::from_path(path), Some(FileType::Mpeg)); + /// ``` + pub fn from_path

(path: P) -> Option + where + P: AsRef, + { + let ext = path.as_ref().extension(); + ext.and_then(Self::from_ext) + } + + /// Attempts to extract a [`FileType`] from a buffer + /// + /// NOTES: + /// + /// * This is for use in [`Probe::guess_file_type`], it + /// is recommended to use it that way + /// * This **will not** search past tags at the start of the buffer. + /// For this behavior, use [`Probe::guess_file_type`]. + /// + /// [`Probe::guess_file_type`]: crate::probe::Probe::guess_file_type + /// + /// # Examples + /// + /// ```rust + /// use lofty::file::FileType; + /// use std::fs::File; + /// use std::io::Read; + /// + /// # fn main() -> lofty::error::Result<()> { + /// # let path_to_opus = "tests/files/assets/minimal/full_test.opus"; + /// let mut file = File::open(path_to_opus)?; + /// + /// let mut buf = [0; 50]; // Search the first 50 bytes of the file + /// file.read_exact(&mut buf)?; + /// + /// assert_eq!(FileType::from_buffer(&buf), Some(FileType::Opus)); + /// # Ok(()) } + /// ``` + pub fn from_buffer(buf: &[u8]) -> Option { + match Self::from_buffer_inner(buf) { + Some(FileTypeGuessResult::Determined(file_ty)) => Some(file_ty), + // We make no attempt to search past an ID3v2 tag or junk here, since + // we only provided a fixed-sized buffer to search from. + // + // That case is handled in `Probe::guess_file_type` + _ => None, + } + } + + // TODO: APE tags in the beginning of the file + pub(crate) fn from_buffer_inner(buf: &[u8]) -> Option { + use crate::id3::v2::util::synchsafe::SynchsafeInteger; + + // Start out with an empty return + let mut ret = None; + + if buf.is_empty() { + return ret; + } + + match Self::quick_type_guess(buf) { + Some(f_ty) => ret = Some(FileTypeGuessResult::Determined(f_ty)), + // Special case for ID3, gets checked in `Probe::guess_file_type` + // The bare minimum size for an ID3v2 header is 10 bytes + None if buf.len() >= 10 && &buf[..3] == b"ID3" => { + // This is infallible, but preferable to an unwrap + if let Ok(arr) = buf[6..10].try_into() { + // Set the ID3v2 size + ret = Some(FileTypeGuessResult::MaybePrecededById3( + u32::from_be_bytes(arr).unsynch(), + )); + } + }, + None => ret = Some(FileTypeGuessResult::MaybePrecededByJunk), + } + + ret + } + + fn quick_type_guess(buf: &[u8]) -> Option { + use crate::mpeg::header::verify_frame_sync; + + // Safe to index, since we return early on an empty buffer + match buf[0] { + 77 if buf.starts_with(b"MAC") => Some(Self::Ape), + 255 if buf.len() >= 2 && verify_frame_sync([buf[0], buf[1]]) => { + // ADTS and MPEG frame headers are way too similar + + // ADTS (https://wiki.multimedia.cx/index.php/ADTS#Header): + // + // AAAAAAAA AAAABCCX + // + // Letter Length (bits) Description + // A 12 Syncword, all bits must be set to 1. + // B 1 MPEG Version, set to 0 for MPEG-4 and 1 for MPEG-2. + // C 2 Layer, always set to 0. + + // MPEG (http://www.mp3-tech.org/programmer/frame_header.html): + // + // AAAAAAAA AAABBCCX + // + // Letter Length (bits) Description + // A 11 Syncword, all bits must be set to 1. + // B 2 MPEG Audio version ID + // C 2 Layer description + + // The subtle overlap in the ADTS header's frame sync and MPEG's version ID + // is the first condition to check. However, since 0b10 and 0b11 are valid versions + // in MPEG, we have to also check the layer. + + // So, if we have a version 1 (0b11) or version 2 (0b10) MPEG frame AND a layer of 0b00, + // we can assume we have an ADTS header. Awesome! + + if buf[1] & 0b10000 > 0 && buf[1] & 0b110 == 0 { + return Some(Self::Aac); + } + + Some(Self::Mpeg) + }, + 70 if buf.len() >= 12 && &buf[..4] == b"FORM" => { + let id = &buf[8..12]; + + if id == b"AIFF" || id == b"AIFC" { + return Some(Self::Aiff); + } + + None + }, + 79 if buf.len() >= 36 && &buf[..4] == b"OggS" => { + if &buf[29..35] == b"vorbis" { + return Some(Self::Vorbis); + } else if &buf[28..36] == b"OpusHead" { + return Some(Self::Opus); + } else if &buf[28..36] == b"Speex " { + return Some(Self::Speex); + } + + None + }, + 102 if buf.starts_with(b"fLaC") => Some(Self::Flac), + 82 if buf.len() >= 12 && &buf[..4] == b"RIFF" => { + if &buf[8..12] == b"WAVE" { + return Some(Self::Wav); + } + + None + }, + 119 if buf.len() >= 4 && &buf[..4] == b"wvpk" => Some(Self::WavPack), + _ if buf.len() >= 8 && &buf[4..8] == b"ftyp" => Some(Self::Mp4), + _ if buf.starts_with(b"MPCK") || buf.starts_with(b"MP+") => Some(Self::Mpc), + _ => None, + } + } +} + +/// The result of a `FileType` guess +/// +/// External callers of `FileType::from_buffer()` will only ever see `Determined` cases. +/// The remaining cases are used internally in `Probe::guess_file_type()`. +pub(crate) enum FileTypeGuessResult { + /// The `FileType` was guessed + Determined(FileType), + /// The stream starts with an ID3v2 tag + MaybePrecededById3(u32), + /// The stream starts with potential junk data + MaybePrecededByJunk, +} diff --git a/src/file/mod.rs b/src/file/mod.rs new file mode 100644 index 000000000..2e6156fe8 --- /dev/null +++ b/src/file/mod.rs @@ -0,0 +1,11 @@ +//! Generic file handling utilities + +mod audio_file; +mod file_type; +mod tagged_file; + +pub use audio_file::AudioFile; +pub use file_type::FileType; +pub use tagged_file::{BoundTaggedFile, TaggedFile, TaggedFileExt}; + +pub(crate) use file_type::FileTypeGuessResult; diff --git a/src/file.rs b/src/file/tagged_file.rs similarity index 52% rename from src/file.rs rename to src/file/tagged_file.rs index 859a0304b..c9185fefd 100644 --- a/src/file.rs +++ b/src/file/tagged_file.rs @@ -1,94 +1,12 @@ +use super::audio_file::AudioFile; +use super::file_type::FileType; +use crate::config::{ParseOptions, WriteOptions}; use crate::error::Result; -use crate::global_options::global_options; -use crate::probe::ParseOptions; use crate::properties::FileProperties; -use crate::resolve::custom_resolvers; -use crate::tag::{Tag, TagType}; -use crate::traits::TagExt; -use crate::write_options::WriteOptions; +use crate::tag::{Tag, TagExt, TagType}; -use std::ffi::OsStr; -use std::fs::{File, OpenOptions}; +use std::fs::File; use std::io::{Read, Seek}; -use std::path::Path; - -/// Provides various methods for interaction with a file -pub trait AudioFile: Into { - /// The struct the file uses for audio properties - /// - /// Not all formats can use [`FileProperties`] since they may contain additional information - type Properties; - - /// Read a file from a reader - /// - /// # Errors - /// - /// Errors depend on the file and tags being read. See [`LoftyError`](crate::LoftyError) - fn read_from(reader: &mut R, parse_options: ParseOptions) -> Result - where - R: Read + Seek, - Self: Sized; - - /// Attempts to write all tags to a path - /// - /// # Errors - /// - /// * `path` does not exist - /// * `path` is not writable - /// * See [`AudioFile::save_to`] - /// - /// # Examples - /// - /// ```rust,no_run - /// use lofty::{AudioFile, TaggedFileExt, WriteOptions}; - /// - /// # fn main() -> lofty::Result<()> { - /// # let path = "tests/files/assets/minimal/full_test.mp3"; - /// let mut tagged_file = lofty::read_from_path(path)?; - /// - /// // Edit the tags - /// - /// tagged_file.save_to_path(path, WriteOptions::default())?; - /// # Ok(()) } - /// ``` - fn save_to_path(&self, path: impl AsRef, write_options: WriteOptions) -> Result<()> { - self.save_to( - &mut OpenOptions::new().read(true).write(true).open(path)?, - write_options, - ) - } - - /// Attempts to write all tags to a file - /// - /// # Errors - /// - /// See [`Tag::save_to`], however this is applicable to every tag in the file. - /// - /// # Examples - /// - /// ```rust,no_run - /// use lofty::{AudioFile, TaggedFileExt, WriteOptions}; - /// use std::fs::OpenOptions; - /// - /// # fn main() -> lofty::Result<()> { - /// # let path = "tests/files/assets/minimal/full_test.mp3"; - /// let mut tagged_file = lofty::read_from_path(path)?; - /// - /// // Edit the tags - /// - /// let mut file = OpenOptions::new().read(true).write(true).open(path)?; - /// tagged_file.save_to(&mut file, WriteOptions::default())?; - /// # Ok(()) } - /// ``` - fn save_to(&self, file: &mut File, write_options: WriteOptions) -> Result<()>; - - /// Returns a reference to the file's properties - fn properties(&self) -> &Self::Properties; - /// Checks if the file contains any tags - fn contains_tag(&self) -> bool; - /// Checks if the file contains the given [`TagType`] - fn contains_tag_type(&self, tag_type: TagType) -> bool; -} /// Provides a common interface between [`TaggedFile`] and [`BoundTaggedFile`] pub trait TaggedFileExt { @@ -97,9 +15,9 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{FileType, TaggedFileExt}; + /// use lofty::file::{FileType, TaggedFileExt}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; /// @@ -113,9 +31,9 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{FileType, TaggedFileExt}; + /// use lofty::file::{FileType, TaggedFileExt}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // An MP3 file with 3 tags /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -134,9 +52,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{TagType, TaggedFileExt}; + /// use lofty::file::TaggedFileExt; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; /// @@ -152,9 +71,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{TagType, TaggedFileExt}; + /// use lofty::file::TaggedFileExt; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; /// @@ -170,9 +90,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{TagType, TaggedFileExt}; + /// use lofty::file::TaggedFileExt; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // Read an MP3 file with an ID3v2 tag /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -191,9 +112,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{TagType, TaggedFileExt}; + /// use lofty::file::TaggedFileExt; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // Read an MP3 file with an ID3v2 tag /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -216,9 +138,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{TagType, TaggedFileExt}; + /// use lofty::file::TaggedFileExt; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // Read an MP3 file with an ID3v2 tag /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -241,9 +164,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{TagType, TaggedFileExt}; + /// use lofty::file::TaggedFileExt; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // Read an MP3 file with an ID3v2 tag /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -269,9 +193,9 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::TaggedFileExt; + /// use lofty::file::TaggedFileExt; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// // A file we know has tags /// let mut tagged_file = lofty::read_from_path(path)?; @@ -293,9 +217,9 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::TaggedFileExt; + /// use lofty::file::TaggedFileExt; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// // A file we know has tags /// let mut tagged_file = lofty::read_from_path(path)?; @@ -319,9 +243,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{AudioFile, Tag, TagType, TaggedFileExt}; + /// use lofty::file::{AudioFile, TaggedFileExt}; + /// use lofty::tag::{Tag, TagType}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // Read an MP3 file without an ID3v2 tag /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -343,9 +268,10 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::{AudioFile, TagType, TaggedFileExt}; + /// use lofty::file::{AudioFile, TaggedFileExt}; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // Read an MP3 file containing an ID3v2 tag /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -365,9 +291,9 @@ pub trait TaggedFileExt { /// # Examples /// /// ```rust - /// use lofty::TaggedFileExt; + /// use lofty::file::TaggedFileExt; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// let mut tagged_file = lofty::read_from_path(path)?; /// @@ -413,9 +339,10 @@ impl TaggedFile { /// # Examples /// /// ```rust - /// use lofty::{AudioFile, FileType, TagType, TaggedFileExt}; + /// use lofty::file::{AudioFile, FileType, TaggedFileExt}; + /// use lofty::tag::TagType; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path_to_mp3 = "tests/files/assets/minimal/full_test.mp3"; /// // Read an MP3 file containing an ID3v2 tag /// let mut tagged_file = lofty::read_from_path(path_to_mp3)?; @@ -532,8 +459,10 @@ impl From for TaggedFile { /// For example: /// /// ```rust,no_run -/// use lofty::{AudioFile, Tag, TagType, TaggedFileExt, WriteOptions}; -/// # fn main() -> lofty::Result<()> { +/// use lofty::config::WriteOptions; +/// use lofty::file::{AudioFile, TaggedFileExt}; +/// use lofty::tag::{Tag, TagType}; +/// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// /// // We create an empty tag @@ -555,11 +484,11 @@ impl From for TaggedFile { /// However, when using `BoundTaggedFile`: /// /// ```rust,no_run -/// use lofty::{ -/// AudioFile, BoundTaggedFile, ParseOptions, Tag, TagType, TaggedFileExt, WriteOptions, -/// }; +/// use lofty::config::{ParseOptions, WriteOptions}; +/// use lofty::file::{AudioFile, BoundTaggedFile, TaggedFileExt}; +/// use lofty::tag::{Tag, TagType}; /// use std::fs::OpenOptions; -/// # fn main() -> lofty::Result<()> { +/// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// /// // We create an empty tag @@ -595,9 +524,11 @@ impl BoundTaggedFile { /// # Examples /// /// ```rust - /// use lofty::{AudioFile, BoundTaggedFile, ParseOptions, Tag, TagType, TaggedFileExt}; + /// use lofty::config::ParseOptions; + /// use lofty::file::{AudioFile, BoundTaggedFile, TaggedFileExt}; + /// use lofty::tag::{Tag, TagType}; /// use std::fs::OpenOptions; - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// /// // We'll need to open our file for reading *and* writing @@ -626,11 +557,11 @@ impl BoundTaggedFile { /// # Examples /// /// ```rust,no_run - /// use lofty::{ - /// AudioFile, BoundTaggedFile, ParseOptions, Tag, TagType, TaggedFileExt, WriteOptions, - /// }; + /// use lofty::config::{ParseOptions, WriteOptions}; + /// use lofty::file::{AudioFile, BoundTaggedFile, TaggedFileExt}; + /// use lofty::tag::{Tag, TagType}; /// use std::fs::OpenOptions; - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// /// // We'll need to open our file for reading *and* writing @@ -716,317 +647,3 @@ impl AudioFile for BoundTaggedFile { self.inner.contains_tag_type(tag_type) } } - -/// The type of file read -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -#[allow(missing_docs)] -#[non_exhaustive] -pub enum FileType { - Aac, - Aiff, - Ape, - Flac, - Mpeg, - Mp4, - Mpc, - Opus, - Vorbis, - Speex, - Wav, - WavPack, - Custom(&'static str), -} - -impl FileType { - /// Returns the file type's "primary" [`TagType`], or the one most likely to be used in the target format - /// - /// | [`FileType`] | [`TagType`] | - /// |-----------------------------------|------------------| - /// | `Aac`, `Aiff`, `Mp3`, `Wav` | `Id3v2` | - /// | `Ape` , `Mpc`, `WavPack` | `Ape` | - /// | `Flac`, `Opus`, `Vorbis`, `Speex` | `VorbisComments` | - /// | `Mp4` | `Mp4Ilst` | - /// - /// # Panics - /// - /// If an unregistered `FileType` ([`FileType::Custom`]) is encountered. See [`register_custom_resolver`](crate::resolve::register_custom_resolver). - /// - /// # Examples - /// - /// ```rust - /// use lofty::{FileType, TagType}; - /// - /// let file_type = FileType::Mpeg; - /// assert_eq!(file_type.primary_tag_type(), TagType::Id3v2); - /// ``` - pub fn primary_tag_type(&self) -> TagType { - match self { - FileType::Aac | FileType::Aiff | FileType::Mpeg | FileType::Wav => TagType::Id3v2, - FileType::Ape | FileType::Mpc | FileType::WavPack => TagType::Ape, - FileType::Flac | FileType::Opus | FileType::Vorbis | FileType::Speex => { - TagType::VorbisComments - }, - FileType::Mp4 => TagType::Mp4Ilst, - FileType::Custom(c) => { - let resolver = crate::resolve::lookup_resolver(c); - resolver.primary_tag_type() - }, - } - } - - /// Returns if the target `FileType` supports a [`TagType`] - /// - /// NOTE: This is feature dependent, meaning if you do not have the - /// `id3v2` feature enabled, [`FileType::Mpeg`] will return `false` for - /// [`TagType::Id3v2`]. - /// - /// # Panics - /// - /// If an unregistered `FileType` ([`FileType::Custom`]) is encountered. See [`register_custom_resolver`](crate::resolve::register_custom_resolver). - /// - /// # Examples - /// - /// ```rust - /// use lofty::{FileType, TagType}; - /// - /// let file_type = FileType::Mpeg; - /// assert!(file_type.supports_tag_type(TagType::Id3v2)); - /// ``` - pub fn supports_tag_type(&self, tag_type: TagType) -> bool { - if let FileType::Custom(c) = self { - let resolver = crate::resolve::lookup_resolver(c); - return resolver.supported_tag_types().contains(&tag_type); - } - - match tag_type { - TagType::Ape => crate::ape::ApeTag::SUPPORTED_FORMATS.contains(self), - TagType::Id3v1 => crate::id3::v1::Id3v1Tag::SUPPORTED_FORMATS.contains(self), - TagType::Id3v2 => crate::id3::v2::Id3v2Tag::SUPPORTED_FORMATS.contains(self), - TagType::Mp4Ilst => crate::mp4::Ilst::SUPPORTED_FORMATS.contains(self), - TagType::VorbisComments => crate::ogg::VorbisComments::SUPPORTED_FORMATS.contains(self), - TagType::RiffInfo => crate::iff::wav::RIFFInfoList::SUPPORTED_FORMATS.contains(self), - TagType::AiffText => crate::iff::aiff::AIFFTextChunks::SUPPORTED_FORMATS.contains(self), - } - } - - /// Attempts to extract a [`FileType`] from an extension - /// - /// # Examples - /// - /// ```rust - /// use lofty::FileType; - /// - /// let extension = "mp3"; - /// assert_eq!(FileType::from_ext(extension), Some(FileType::Mpeg)); - /// ``` - pub fn from_ext(ext: E) -> Option - where - E: AsRef, - { - let ext = ext.as_ref().to_str()?.to_ascii_lowercase(); - - // Give custom resolvers priority - if unsafe { global_options().use_custom_resolvers } { - if let Some((ty, _)) = custom_resolvers() - .lock() - .ok()? - .iter() - .find(|(_, f)| f.extension() == Some(ext.as_str())) - { - return Some(Self::Custom(ty)); - } - } - - match ext.as_str() { - "aac" => Some(Self::Aac), - "ape" => Some(Self::Ape), - "aiff" | "aif" | "afc" | "aifc" => Some(Self::Aiff), - "mp3" | "mp2" | "mp1" => Some(Self::Mpeg), - "wav" | "wave" => Some(Self::Wav), - "wv" => Some(Self::WavPack), - "opus" => Some(Self::Opus), - "flac" => Some(Self::Flac), - "ogg" => Some(Self::Vorbis), - "mp4" | "m4a" | "m4b" | "m4p" | "m4r" | "m4v" | "3gp" => Some(Self::Mp4), - "mpc" | "mp+" | "mpp" => Some(Self::Mpc), - "spx" => Some(Self::Speex), - _ => None, - } - } - - /// Attempts to determine a [`FileType`] from a path - /// - /// # Examples - /// - /// ```rust - /// use lofty::FileType; - /// use std::path::Path; - /// - /// let path = Path::new("path/to/my.mp3"); - /// assert_eq!(FileType::from_path(path), Some(FileType::Mpeg)); - /// ``` - pub fn from_path

(path: P) -> Option - where - P: AsRef, - { - let ext = path.as_ref().extension(); - ext.and_then(Self::from_ext) - } - - /// Attempts to extract a [`FileType`] from a buffer - /// - /// NOTES: - /// - /// * This is for use in [`Probe::guess_file_type`], it - /// is recommended to use it that way - /// * This **will not** search past tags at the start of the buffer. - /// For this behavior, use [`Probe::guess_file_type`]. - /// - /// [`Probe::guess_file_type`]: crate::Probe::guess_file_type - /// - /// # Examples - /// - /// ```rust - /// use lofty::FileType; - /// use std::fs::File; - /// use std::io::Read; - /// - /// # fn main() -> lofty::Result<()> { - /// # let path_to_opus = "tests/files/assets/minimal/full_test.opus"; - /// let mut file = File::open(path_to_opus)?; - /// - /// let mut buf = [0; 50]; // Search the first 50 bytes of the file - /// file.read_exact(&mut buf)?; - /// - /// assert_eq!(FileType::from_buffer(&buf), Some(FileType::Opus)); - /// # Ok(()) } - /// ``` - pub fn from_buffer(buf: &[u8]) -> Option { - match Self::from_buffer_inner(buf) { - Some(FileTypeGuessResult::Determined(file_ty)) => Some(file_ty), - // We make no attempt to search past an ID3v2 tag or junk here, since - // we only provided a fixed-sized buffer to search from. - // - // That case is handled in `Probe::guess_file_type` - _ => None, - } - } - - // TODO: APE tags in the beginning of the file - pub(crate) fn from_buffer_inner(buf: &[u8]) -> Option { - use crate::id3::v2::util::synchsafe::SynchsafeInteger; - - // Start out with an empty return - let mut ret = None; - - if buf.is_empty() { - return ret; - } - - match Self::quick_type_guess(buf) { - Some(f_ty) => ret = Some(FileTypeGuessResult::Determined(f_ty)), - // Special case for ID3, gets checked in `Probe::guess_file_type` - // The bare minimum size for an ID3v2 header is 10 bytes - None if buf.len() >= 10 && &buf[..3] == b"ID3" => { - // This is infallible, but preferable to an unwrap - if let Ok(arr) = buf[6..10].try_into() { - // Set the ID3v2 size - ret = Some(FileTypeGuessResult::MaybePrecededById3( - u32::from_be_bytes(arr).unsynch(), - )); - } - }, - None => ret = Some(FileTypeGuessResult::MaybePrecededByJunk), - } - - ret - } - - fn quick_type_guess(buf: &[u8]) -> Option { - use crate::mpeg::header::verify_frame_sync; - - // Safe to index, since we return early on an empty buffer - match buf[0] { - 77 if buf.starts_with(b"MAC") => Some(Self::Ape), - 255 if buf.len() >= 2 && verify_frame_sync([buf[0], buf[1]]) => { - // ADTS and MPEG frame headers are way too similar - - // ADTS (https://wiki.multimedia.cx/index.php/ADTS#Header): - // - // AAAAAAAA AAAABCCX - // - // Letter Length (bits) Description - // A 12 Syncword, all bits must be set to 1. - // B 1 MPEG Version, set to 0 for MPEG-4 and 1 for MPEG-2. - // C 2 Layer, always set to 0. - - // MPEG (http://www.mp3-tech.org/programmer/frame_header.html): - // - // AAAAAAAA AAABBCCX - // - // Letter Length (bits) Description - // A 11 Syncword, all bits must be set to 1. - // B 2 MPEG Audio version ID - // C 2 Layer description - - // The subtle overlap in the ADTS header's frame sync and MPEG's version ID - // is the first condition to check. However, since 0b10 and 0b11 are valid versions - // in MPEG, we have to also check the layer. - - // So, if we have a version 1 (0b11) or version 2 (0b10) MPEG frame AND a layer of 0b00, - // we can assume we have an ADTS header. Awesome! - - if buf[1] & 0b10000 > 0 && buf[1] & 0b110 == 0 { - return Some(Self::Aac); - } - - Some(Self::Mpeg) - }, - 70 if buf.len() >= 12 && &buf[..4] == b"FORM" => { - let id = &buf[8..12]; - - if id == b"AIFF" || id == b"AIFC" { - return Some(Self::Aiff); - } - - None - }, - 79 if buf.len() >= 36 && &buf[..4] == b"OggS" => { - if &buf[29..35] == b"vorbis" { - return Some(Self::Vorbis); - } else if &buf[28..36] == b"OpusHead" { - return Some(Self::Opus); - } else if &buf[28..36] == b"Speex " { - return Some(Self::Speex); - } - - None - }, - 102 if buf.starts_with(b"fLaC") => Some(Self::Flac), - 82 if buf.len() >= 12 && &buf[..4] == b"RIFF" => { - if &buf[8..12] == b"WAVE" { - return Some(Self::Wav); - } - - None - }, - 119 if buf.len() >= 4 && &buf[..4] == b"wvpk" => Some(Self::WavPack), - _ if buf.len() >= 8 && &buf[4..8] == b"ftyp" => Some(Self::Mp4), - _ if buf.starts_with(b"MPCK") || buf.starts_with(b"MP+") => Some(Self::Mpc), - _ => None, - } - } -} - -/// The result of a `FileType` guess -/// -/// External callers of `FileType::from_buffer()` will only ever see `Determined` cases. -/// The remaining cases are used internally in `Probe::guess_file_type()`. -pub(crate) enum FileTypeGuessResult { - /// The `FileType` was guessed - Determined(FileType), - /// The stream starts with an ID3v2 tag - MaybePrecededById3(u32), - /// The stream starts with potential junk data - MaybePrecededByJunk, -} diff --git a/src/flac/mod.rs b/src/flac/mod.rs index 46f21b988..01102dc00 100644 --- a/src/flac/mod.rs +++ b/src/flac/mod.rs @@ -9,14 +9,14 @@ pub(crate) mod properties; mod read; pub(crate) mod write; +use crate::config::WriteOptions; use crate::error::Result; use crate::file::{FileType, TaggedFile}; use crate::id3::v2::tag::Id3v2Tag; use crate::ogg::tag::VorbisCommentsRef; use crate::ogg::{OggPictureStorage, VorbisComments}; use crate::picture::{Picture, PictureInformation}; -use crate::traits::TagExt; -use crate::write_options::WriteOptions; +use crate::tag::TagExt; use std::fs::File; use std::io::Seek; diff --git a/src/flac/read.rs b/src/flac/read.rs index 97282d851..db961d3a9 100644 --- a/src/flac/read.rs +++ b/src/flac/read.rs @@ -1,6 +1,7 @@ use super::block::Block; use super::properties::FlacProperties; use super::FlacFile; +use crate::config::{ParseOptions, ParsingMode}; use crate::error::Result; use crate::flac::block::{ BLOCK_ID_PADDING, BLOCK_ID_PICTURE, BLOCK_ID_SEEKTABLE, BLOCK_ID_STREAMINFO, @@ -11,7 +12,6 @@ use crate::id3::{find_id3v2, FindId3v2Config, ID3FindResults}; use crate::macros::decode_err; use crate::ogg::read::read_comments; use crate::picture::Picture; -use crate::probe::{ParseOptions, ParsingMode}; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/flac/write.rs b/src/flac/write.rs index 321e2d750..d043c53be 100644 --- a/src/flac/write.rs +++ b/src/flac/write.rs @@ -1,12 +1,12 @@ use super::block::Block; use super::read::verify_flac; +use crate::config::WriteOptions; use crate::error::Result; use crate::macros::{err, try_vec}; use crate::ogg::tag::VorbisCommentsRef; use crate::ogg::write::create_comments; use crate::picture::{Picture, PictureInformation}; use crate::tag::{Tag, TagType}; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; diff --git a/src/id3/v1/constants.rs b/src/id3/v1/constants.rs index e52fd111e..15352612a 100644 --- a/src/id3/v1/constants.rs +++ b/src/id3/v1/constants.rs @@ -194,7 +194,7 @@ pub const GENRES: [&str; 192] = [ "Psybient", ]; -use crate::tag::item::ItemKey; +use crate::tag::ItemKey; pub(crate) const VALID_ITEMKEYS: [ItemKey; 7] = [ ItemKey::TrackTitle, ItemKey::TrackArtist, diff --git a/src/id3/v1/tag.rs b/src/id3/v1/tag.rs index 8a7298f7b..f49219da3 100644 --- a/src/id3/v1/tag.rs +++ b/src/id3/v1/tag.rs @@ -1,9 +1,7 @@ +use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::id3::v1::constants::GENRES; -use crate::tag::item::{ItemKey, ItemValue, TagItem}; -use crate::tag::{Tag, TagType}; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; -use crate::write_options::WriteOptions; +use crate::tag::{Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType}; use std::borrow::Cow; use std::fs::File; @@ -115,7 +113,7 @@ impl Id3v1Tag { /// /// ```rust /// use lofty::id3::v1::Id3v1Tag; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let id3v1_tag = Id3v1Tag::new(); /// assert!(id3v1_tag.is_empty()); @@ -440,8 +438,10 @@ impl<'a> Id3v1TagRef<'a> { #[cfg(test)] mod tests { + use crate::config::WriteOptions; use crate::id3::v1::Id3v1Tag; - use crate::{Tag, TagExt, TagType, WriteOptions}; + use crate::prelude::*; + use crate::tag::{Tag, TagType}; #[test] fn parse_id3v1() { diff --git a/src/id3/v1/write.rs b/src/id3/v1/write.rs index 0efd21d2d..188ea3b24 100644 --- a/src/id3/v1/write.rs +++ b/src/id3/v1/write.rs @@ -1,9 +1,9 @@ use super::tag::Id3v1TagRef; +use crate::config::WriteOptions; use crate::error::Result; use crate::id3::{find_id3v1, ID3FindResults}; use crate::macros::err; use crate::probe::Probe; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::{Cursor, Seek, Write}; diff --git a/src/id3/v2/frame/content.rs b/src/id3/v2/frame/content.rs index e7607356f..6c8d84ead 100644 --- a/src/id3/v2/frame/content.rs +++ b/src/id3/v2/frame/content.rs @@ -1,3 +1,4 @@ +use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::frame::FrameValue; use crate::id3::v2::header::Id3v2Version; @@ -7,7 +8,6 @@ use crate::id3::v2::items::{ TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame, }; use crate::macros::err; -use crate::probe::ParsingMode; use crate::util::text::TextEncoding; use std::io::Read; diff --git a/src/id3/v2/frame/id.rs b/src/id3/v2/frame/id.rs index a43139085..6fe90faf3 100644 --- a/src/id3/v2/frame/id.rs +++ b/src/id3/v2/frame/id.rs @@ -2,8 +2,7 @@ use std::borrow::Cow; use std::fmt::{Display, Formatter}; use crate::error::{Id3v2Error, Id3v2ErrorKind, LoftyError, Result}; -use crate::tag::item::ItemKey; -use crate::tag::TagType; +use crate::tag::{ItemKey, TagType}; /// An `ID3v2` frame ID /// @@ -16,7 +15,7 @@ pub enum FrameId<'a> { /// /// This **will not** be written. It is up to the user to upgrade and store the key as [`Id3v2Frame::Valid`](Self::Valid). /// - /// The entire frame is stored as [`ItemValue::Binary`](crate::ItemValue::Binary). + /// The entire frame is stored as [`ItemValue::Binary`](crate::tag::ItemValue::Binary). Outdated(Cow<'a, str>), } diff --git a/src/id3/v2/frame/mod.rs b/src/id3/v2/frame/mod.rs index d2b8ea9cd..5b4516751 100644 --- a/src/id3/v2/frame/mod.rs +++ b/src/id3/v2/frame/mod.rs @@ -11,8 +11,7 @@ use super::items::{ }; use super::util::upgrade::{upgrade_v2, upgrade_v3}; use crate::error::{ErrorKind, Id3v2Error, Id3v2ErrorKind, LoftyError, Result}; -use crate::tag::item::{ItemKey, ItemValue, TagItem}; -use crate::tag::TagType; +use crate::tag::{ItemKey, ItemValue, TagItem, TagType}; use crate::util::text::TextEncoding; use id::FrameId; diff --git a/src/id3/v2/frame/read.rs b/src/id3/v2/frame/read.rs index b0b570958..8ba2aa128 100644 --- a/src/id3/v2/frame/read.rs +++ b/src/id3/v2/frame/read.rs @@ -1,12 +1,12 @@ use super::header::{parse_header, parse_v2_header}; use super::Frame; +use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::frame::content::parse_content; use crate::id3::v2::header::Id3v2Version; use crate::id3::v2::util::synchsafe::{SynchsafeInteger, UnsynchronizedStream}; use crate::id3::v2::{FrameFlags, FrameId, FrameValue}; use crate::macros::try_vec; -use crate::probe::ParsingMode; use std::io::Read; diff --git a/src/id3/v2/items/relative_volume_adjustment_frame.rs b/src/id3/v2/items/relative_volume_adjustment_frame.rs index 9aa8c10dc..effbd17db 100644 --- a/src/id3/v2/items/relative_volume_adjustment_frame.rs +++ b/src/id3/v2/items/relative_volume_adjustment_frame.rs @@ -1,6 +1,6 @@ +use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::macros::try_vec; -use crate::probe::ParsingMode; use crate::util::text::{decode_text, encode_text, TextDecodeOptions, TextEncoding}; use std::collections::HashMap; @@ -202,8 +202,8 @@ impl RelativeVolumeAdjustmentFrame { #[cfg(test)] mod tests { + use crate::config::ParsingMode; use crate::id3::v2::{ChannelInformation, ChannelType, RelativeVolumeAdjustmentFrame}; - use crate::ParsingMode; use std::collections::HashMap; use std::io::Read; diff --git a/src/id3/v2/items/unique_file_identifier.rs b/src/id3/v2/items/unique_file_identifier.rs index ca3a75d0c..cb373d43d 100644 --- a/src/id3/v2/items/unique_file_identifier.rs +++ b/src/id3/v2/items/unique_file_identifier.rs @@ -1,6 +1,6 @@ +use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::macros::parse_mode_choice; -use crate::probe::ParsingMode; use crate::util::text::{decode_text, encode_text, TextDecodeOptions, TextEncoding}; use std::hash::{Hash, Hasher}; @@ -78,8 +78,8 @@ impl Hash for UniqueFileIdentifierFrame { mod tests { #[test] fn issue_204_invalid_ufid_parsing_mode_best_attempt() { + use crate::config::ParsingMode; use crate::id3::v2::UniqueFileIdentifierFrame; - use crate::ParsingMode; let ufid_no_owner = UniqueFileIdentifierFrame { owner: String::new(), diff --git a/src/id3/v2/read.rs b/src/id3/v2/read.rs index 3bb369bc8..d0775301a 100644 --- a/src/id3/v2/read.rs +++ b/src/id3/v2/read.rs @@ -1,9 +1,9 @@ use super::frame::read::ParsedFrame; use super::header::Id3v2Header; use super::tag::Id3v2Tag; +use crate::config::ParsingMode; use crate::error::{Id3v2Error, Id3v2ErrorKind, Result}; use crate::id3::v2::util::synchsafe::UnsynchronizedStream; -use crate::probe::ParsingMode; use std::io::Read; @@ -104,8 +104,9 @@ where #[test] fn zero_size_id3v2() { + use crate::config::ParsingMode; use crate::id3::v2::header::Id3v2Header; - use crate::ParsingMode; + use std::io::Cursor; let mut f = Cursor::new(std::fs::read("tests/tags/assets/id3v2/zero.id3v2").unwrap()); @@ -115,8 +116,10 @@ fn zero_size_id3v2() { #[test] fn bad_frame_id_relaxed_id3v2() { + use crate::config::ParsingMode; use crate::id3::v2::header::Id3v2Header; - use crate::{Accessor, ParsingMode, TagExt}; + use crate::prelude::*; + use std::io::Cursor; // Contains a frame with a "+" in the ID, which is invalid. diff --git a/src/id3/v2/tag.rs b/src/id3/v2/tag.rs index 144e3c022..a62962a27 100644 --- a/src/id3/v2/tag.rs +++ b/src/id3/v2/tag.rs @@ -4,6 +4,7 @@ mod tests; use super::frame::id::FrameId; use super::frame::{Frame, FrameFlags, FrameValue, EMPTY_CONTENT_DESCRIPTOR, UNKNOWN_LANGUAGE}; use super::header::{Id3v2TagFlags, Id3v2Version}; +use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::id3::v1::GENRES; use crate::id3::v2::frame::{FrameRef, MUSICBRAINZ_UFID_OWNER}; @@ -17,11 +18,10 @@ use crate::id3::v2::util::pairs::{ }; use crate::id3::v2::KeyValueFrame; use crate::picture::{Picture, PictureType, TOMBSTONE_PICTURE}; -use crate::tag::item::{ItemKey, ItemValue, TagItem}; -use crate::tag::{try_parse_year, Tag, TagType}; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; +use crate::tag::{ + try_parse_year, Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, +}; use crate::util::text::{decode_text, TextDecodeOptions, TextEncoding}; -use crate::write_options::WriteOptions; use std::borrow::Cow; use std::fs::File; @@ -155,7 +155,7 @@ impl Id3v2Tag { /// /// ```rust /// use lofty::id3::v2::Id3v2Tag; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let id3v2_tag = Id3v2Tag::new(); /// assert!(id3v2_tag.is_empty()); @@ -200,7 +200,7 @@ impl Id3v2Tag { /// /// ```rust /// use lofty::id3::v2::{FrameId, Id3v2Tag}; - /// use lofty::Accessor; + /// use lofty::tag::Accessor; /// use std::borrow::Cow; /// /// const TITLE_ID: FrameId<'_> = FrameId::Valid(Cow::Borrowed("TIT2")); @@ -241,7 +241,7 @@ impl Id3v2Tag { /// /// ```rust /// use lofty::id3::v2::{FrameId, Id3v2Tag}; - /// use lofty::Accessor; + /// use lofty::tag::Accessor; /// use std::borrow::Cow; /// /// const TITLE_ID: FrameId<'_> = FrameId::Valid(Cow::Borrowed("TIT2")); @@ -314,7 +314,7 @@ impl Id3v2Tag { /// /// ```rust /// use lofty::id3::v2::Id3v2Tag; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let mut tag = Id3v2Tag::new(); /// @@ -376,7 +376,7 @@ impl Id3v2Tag { /// /// ```rust /// use lofty::id3::v2::Id3v2Tag; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let mut tag = Id3v2Tag::new(); /// assert!(tag.is_empty()); @@ -413,12 +413,13 @@ impl Id3v2Tag { /// /// ```rust /// use lofty::id3::v2::{Frame, FrameFlags, FrameId, Id3v2Tag, TextInformationFrame}; - /// use lofty::{TagExt, TextEncoding}; + /// use lofty::tag::TagExt; + /// use lofty::TextEncoding; /// use std::borrow::Cow; /// /// const MOOD_FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("TMOO")); /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// let mut tag = Id3v2Tag::new(); /// assert!(tag.is_empty()); /// diff --git a/src/id3/v2/tag/tests.rs b/src/id3/v2/tag/tests.rs index 12183e8f4..1cc12422a 100644 --- a/src/id3/v2/tag/tests.rs +++ b/src/id3/v2/tag/tests.rs @@ -1,8 +1,8 @@ +use crate::config::ParsingMode; use crate::id3::v2::header::Id3v2Header; use crate::id3::v2::items::Popularimeter; use crate::id3::v2::util::pairs::DEFAULT_NUMBER_IN_PAIR; use crate::picture::MimeType; -use crate::probe::ParsingMode; use crate::tag::utils::test_utils::read_path; use super::*; @@ -438,7 +438,6 @@ fn popm_frame() { #[test] fn multi_value_frame_to_tag() { - use crate::traits::Accessor; let mut tag = Id3v2Tag::default(); tag.set_artist(String::from("foo\0bar\0baz")); @@ -450,7 +449,6 @@ fn multi_value_frame_to_tag() { #[test] fn multi_item_tag_to_id3v2() { - use crate::traits::Accessor; let mut tag = Tag::new(TagType::Id3v2); tag.push_unchecked(TagItem::new( @@ -823,7 +821,6 @@ fn set_disk_total_and_disk() { #[test] fn track_number_tag_to_id3v2() { - use crate::traits::Accessor; let track_number = 1; let mut tag = Tag::new(TagType::Id3v2); @@ -841,7 +838,6 @@ fn track_number_tag_to_id3v2() { #[test] fn track_total_tag_to_id3v2() { - use crate::traits::Accessor; let track_total = 2; let mut tag = Tag::new(TagType::Id3v2); @@ -859,7 +855,6 @@ fn track_total_tag_to_id3v2() { #[test] fn track_number_and_track_total_tag_to_id3v2() { - use crate::traits::Accessor; let track_number = 1; let track_total = 2; @@ -883,7 +878,6 @@ fn track_number_and_track_total_tag_to_id3v2() { #[test] fn disk_number_tag_to_id3v2() { - use crate::traits::Accessor; let disk_number = 1; let mut tag = Tag::new(TagType::Id3v2); @@ -901,7 +895,6 @@ fn disk_number_tag_to_id3v2() { #[test] fn disk_total_tag_to_id3v2() { - use crate::traits::Accessor; let disk_total = 2; let mut tag = Tag::new(TagType::Id3v2); @@ -919,7 +912,6 @@ fn disk_total_tag_to_id3v2() { #[test] fn disk_number_and_disk_total_tag_to_id3v2() { - use crate::traits::Accessor; let disk_number = 1; let disk_total = 2; diff --git a/src/id3/v2/util/mappings.rs b/src/id3/v2/util/mappings.rs index 269ec87df..7e62af06c 100644 --- a/src/id3/v2/util/mappings.rs +++ b/src/id3/v2/util/mappings.rs @@ -1,4 +1,4 @@ -use crate::tag::item::ItemKey; +use crate::tag::ItemKey; pub(crate) const TIPL_MAPPINGS: &[(ItemKey, &str)] = &[ (ItemKey::Producer, "producer"), diff --git a/src/id3/v2/util/pairs.rs b/src/id3/v2/util/pairs.rs index cfe1d2673..586c8b848 100644 --- a/src/id3/v2/util/pairs.rs +++ b/src/id3/v2/util/pairs.rs @@ -1,6 +1,6 @@ //! Contains utilities for ID3v2 style number pairs -use crate::tag::item::{ItemKey, TagItem}; +use crate::tag::{ItemKey, TagItem}; use std::fmt::Display; @@ -58,7 +58,7 @@ pub(crate) fn set_number(item: &TagItem, mut setter: F) { #[cfg(test)] mod tests { use crate::id3::v2::util::pairs::set_number; - use crate::{ItemKey, ItemValue, TagItem}; + use crate::tag::{ItemKey, ItemValue, TagItem}; #[test] fn whitespace_in_number() { diff --git a/src/id3/v2/util/synchsafe.rs b/src/id3/v2/util/synchsafe.rs index fedfe7489..a33ac434b 100644 --- a/src/id3/v2/util/synchsafe.rs +++ b/src/id3/v2/util/synchsafe.rs @@ -16,7 +16,7 @@ use std::io::Read; /// use std::io::{Cursor, Read}; /// use lofty::id3::v2::util::synchsafe::UnsynchronizedStream; /// -/// fn main() -> lofty::Result<()> { +/// fn main() -> lofty::error::Result<()> { /// // The content has two `0xFF 0x00` pairs, which will be removed /// let content = [0xFF, 0x00, 0x1A, 0xFF, 0x00, 0x15]; /// @@ -68,7 +68,7 @@ impl UnsynchronizedStream { /// use lofty::id3::v2::util::synchsafe::UnsynchronizedStream; /// use std::io::Cursor; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// let reader = Cursor::new([0xFF, 0x00, 0x1A]); /// let unsynchronized_reader = UnsynchronizedStream::new(reader); /// diff --git a/src/id3/v2/write/chunk_file.rs b/src/id3/v2/write/chunk_file.rs index 1ee318edd..f0d383d66 100644 --- a/src/id3/v2/write/chunk_file.rs +++ b/src/id3/v2/write/chunk_file.rs @@ -1,6 +1,6 @@ +use crate::config::WriteOptions; use crate::error::Result; use crate::iff::chunk::Chunks; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; diff --git a/src/id3/v2/write/mod.rs b/src/id3/v2/write/mod.rs index 008648f40..ef6a21b5a 100644 --- a/src/id3/v2/write/mod.rs +++ b/src/id3/v2/write/mod.rs @@ -2,6 +2,7 @@ mod chunk_file; mod frame; use super::Id3v2TagFlags; +use crate::config::WriteOptions; use crate::error::Result; use crate::file::FileType; use crate::id3::v2::frame::FrameRef; @@ -11,7 +12,6 @@ use crate::id3::v2::Id3v2Tag; use crate::id3::{find_id3v2, FindId3v2Config}; use crate::macros::{err, try_vec}; use crate::probe::Probe; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; @@ -283,8 +283,9 @@ fn calculate_crc(content: &[u8]) -> [u8; 5] { #[cfg(test)] mod tests { + use crate::config::WriteOptions; use crate::id3::v2::{Id3v2Tag, Id3v2TagFlags}; - use crate::{Accessor, TagExt, WriteOptions}; + use crate::prelude::*; #[test] fn id3v2_write_crc32() { diff --git a/src/iff/aiff/read.rs b/src/iff/aiff/read.rs index 450ec73e3..c8ffbe033 100644 --- a/src/iff/aiff/read.rs +++ b/src/iff/aiff/read.rs @@ -1,11 +1,11 @@ use super::properties::AiffProperties; use super::tag::{AIFFTextChunks, Comment}; use super::AiffFile; +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::probe::ParseOptions; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/iff/aiff/tag.rs b/src/iff/aiff/tag.rs index 0b607e882..186f03fa4 100644 --- a/src/iff/aiff/tag.rs +++ b/src/iff/aiff/tag.rs @@ -1,10 +1,8 @@ +use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::iff::chunk::Chunks; use crate::macros::err; -use crate::tag::item::{ItemKey, ItemValue, TagItem}; -use crate::tag::{Tag, TagType}; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; -use crate::write_options::WriteOptions; +use crate::tag::{Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType}; use std::borrow::Cow; use std::fs::File; @@ -129,7 +127,7 @@ impl AIFFTextChunks { /// /// ```rust /// use lofty::iff::aiff::AIFFTextChunks; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let aiff_tag = AIFFTextChunks::new(); /// assert!(aiff_tag.is_empty()); @@ -483,10 +481,11 @@ where #[cfg(test)] mod tests { + use crate::config::{ParseOptions, WriteOptions}; use crate::iff::aiff::{AIFFTextChunks, Comment}; - use crate::{ItemKey, ItemValue, Tag, TagExt, TagItem, TagType, WriteOptions}; + use crate::prelude::*; + use crate::tag::{ItemValue, Tag, TagItem, TagType}; - use crate::probe::ParseOptions; use std::io::Cursor; #[test] diff --git a/src/iff/chunk.rs b/src/iff/chunk.rs index 01719284b..9c59a3c2c 100644 --- a/src/iff/chunk.rs +++ b/src/iff/chunk.rs @@ -1,7 +1,7 @@ +use crate::config::ParsingMode; use crate::error::Result; use crate::id3::v2::tag::Id3v2Tag; use crate::macros::{err, try_vec}; -use crate::probe::ParsingMode; use crate::util::text::utf8_decode; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/iff/wav/properties.rs b/src/iff/wav/properties.rs index 2fb9e347c..79a2ad234 100644 --- a/src/iff/wav/properties.rs +++ b/src/iff/wav/properties.rs @@ -1,7 +1,7 @@ use crate::error::Result; use crate::macros::decode_err; -use crate::math::RoundedDivision; use crate::properties::{ChannelMask, FileProperties}; +use crate::util::math::RoundedDivision; use std::time::Duration; diff --git a/src/iff/wav/read.rs b/src/iff/wav/read.rs index 59e6b4d8e..ceaa033ea 100644 --- a/src/iff/wav/read.rs +++ b/src/iff/wav/read.rs @@ -1,11 +1,11 @@ use super::properties::WavProperties; use super::tag::RIFFInfoList; use super::WavFile; +use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v2::tag::Id3v2Tag; use crate::iff::chunk::Chunks; use crate::macros::decode_err; -use crate::probe::ParseOptions; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/iff/wav/tag/mod.rs b/src/iff/wav/tag/mod.rs index 5906239f3..fd5c0f9e3 100644 --- a/src/iff/wav/tag/mod.rs +++ b/src/iff/wav/tag/mod.rs @@ -1,11 +1,11 @@ pub(super) mod read; mod write; +use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; -use crate::tag::item::{ItemKey, ItemValue, TagItem}; -use crate::tag::{try_parse_year, Tag, TagType}; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; -use crate::write_options::WriteOptions; +use crate::tag::{ + try_parse_year, Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, +}; use std::borrow::Cow; use std::fs::File; @@ -60,7 +60,7 @@ impl RIFFInfoList { /// /// ```rust /// use lofty::iff::wav::RIFFInfoList; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let riff_info_tag = RIFFInfoList::new(); /// assert!(riff_info_tag.is_empty()); @@ -348,11 +348,14 @@ pub(crate) fn tagitems_into_riff<'a>( #[cfg(test)] mod tests { + use crate::config::WriteOptions; + use crate::iff::chunk::Chunks; use crate::iff::wav::RIFFInfoList; - use crate::{Tag, TagExt, TagType, WriteOptions}; + use crate::prelude::*; + use crate::tag::{Tag, TagType}; - use crate::iff::chunk::Chunks; use byteorder::LittleEndian; + use std::io::Cursor; #[test] diff --git a/src/iff/wav/tag/write.rs b/src/iff/wav/tag/write.rs index 58f727f3a..5fb38a63f 100644 --- a/src/iff/wav/tag/write.rs +++ b/src/iff/wav/tag/write.rs @@ -1,9 +1,9 @@ use super::RIFFInfoListRef; +use crate::config::WriteOptions; use crate::error::Result; use crate::iff::chunk::Chunks; use crate::iff::wav::read::verify_wav; use crate::macros::err; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; diff --git a/src/lib.rs b/src/lib.rs index 904643f40..c08c238d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,15 +10,15 @@ //! //! ## Reading a generic file //! -//! It isn't always convenient to [use concrete file types](#using-concrete-file-types), which is where [`TaggedFile`] +//! It isn't always convenient to [use concrete file types](#using-concrete-file-types), which is where [`TaggedFile`](file::TaggedFile) //! comes in. //! //! ### Using a path //! //! ```rust,no_run -//! # use lofty::LoftyError; -//! # fn main() -> Result<(), LoftyError> { -//! use lofty::{read_from_path, Probe}; +//! # fn main() -> lofty::error::Result<()> { +//! use lofty::probe::Probe; +//! use lofty::read_from_path; //! //! // This will guess the format from the extension //! // ("mp3" in this case), but we can guess from the content if we want to. @@ -35,9 +35,9 @@ //! ### Using an existing reader //! //! ```rust,no_run -//! # use lofty::LoftyError; -//! # fn main() -> Result<(), LoftyError> { -//! use lofty::{read_from, ParseOptions}; +//! # fn main() -> lofty::error::Result<()> { +//! use lofty::config::ParseOptions; +//! use lofty::read_from; //! use std::fs::File; //! //! // Let's read from an open file @@ -53,9 +53,9 @@ //! ### Accessing tags //! //! ```rust,no_run -//! # use lofty::LoftyError; -//! # fn main() -> Result<(), LoftyError> { -//! use lofty::{read_from_path, ParseOptions, TaggedFileExt}; +//! # fn main() -> lofty::error::Result<()> { +//! use lofty::file::TaggedFileExt; +//! use lofty::read_from_path; //! //! let path = "test.mp3"; //! let tagged_file = read_from_path(path)?; @@ -73,10 +73,11 @@ //! ## Using concrete file types //! //! ```rust -//! # use lofty::LoftyError; -//! # fn main() -> Result<(), LoftyError> { +//! # fn main() -> lofty::error::Result<()> { +//! use lofty::config::ParseOptions; +//! use lofty::file::AudioFile; //! use lofty::mpeg::MpegFile; -//! use lofty::{AudioFile, ParseOptions, TagType}; +//! use lofty::tag::TagType; //! use std::fs::File; //! //! # let path = "tests/files/assets/minimal/full_test.mp3"; @@ -150,45 +151,43 @@ extern crate self as lofty; pub(crate) mod _this_is_internal {} +pub mod config; +pub mod error; +pub mod file; +pub(crate) mod macros; +pub mod picture; +pub mod probe; +pub mod properties; +pub mod resolve; +pub mod tag; +mod util; + pub mod aac; pub mod ape; -pub mod error; -pub(crate) mod file; pub mod flac; -pub(crate) mod global_options; pub mod id3; pub mod iff; -pub(crate) mod macros; -mod math; pub mod mp4; pub mod mpeg; pub mod musepack; pub mod ogg; -pub(crate) mod picture; -mod probe; -pub mod properties; -pub mod resolve; -pub(crate) mod tag; -mod traits; -mod util; pub mod wavpack; -mod write_options; - -pub use crate::error::{LoftyError, Result}; -pub use crate::probe::{read_from, read_from_path, ParseOptions, ParsingMode, Probe}; -pub use crate::write_options::WriteOptions; +pub use crate::probe::{read_from, read_from_path}; -pub use crate::file::{AudioFile, BoundTaggedFile, FileType, TaggedFile, TaggedFileExt}; -pub use crate::picture::{MimeType, Picture, PictureType}; -pub use crate::tag::{Tag, TagType}; -pub use tag::item::{ItemKey, ItemValue, TagItem}; pub use util::text::TextEncoding; -pub use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; - -pub use picture::PictureInformation; +pub use lofty_attr::LoftyFile; -pub use global_options::{apply_global_options, GlobalOptions}; +pub mod prelude { + //! A prelude for commonly used items in the library. + //! + //! This module is intended to be wildcard imported. + //! + //! ```rust + //! use lofty::prelude::*; + //! ``` -pub use lofty_attr::LoftyFile; + pub use crate::file::{AudioFile, TaggedFileExt}; + pub use crate::tag::{Accessor, ItemKey, MergeTag, SplitTag, TagExt}; +} diff --git a/src/macros.rs b/src/macros.rs index 1a2ededd0..2ec5ff582 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -73,9 +73,9 @@ macro_rules! parse_mode_choice { DEFAULT: $default:expr ) => { match $parse_mode { - $(crate::probe::ParsingMode::Strict => { $strict_handler },)? - $(crate::probe::ParsingMode::BestAttempt => { $best_attempt_handler },)? - $(crate::probe::ParsingMode::Relaxed => { $relaxed_handler },)? + $(crate::config::ParsingMode::Strict => { $strict_handler },)? + $(crate::config::ParsingMode::BestAttempt => { $best_attempt_handler },)? + $(crate::config::ParsingMode::Relaxed => { $relaxed_handler },)? _ => { $default } } }; @@ -86,9 +86,9 @@ macro_rules! parse_mode_choice { $(RELAXED: $relaxed_handler:expr $(,)?)? ) => { match $parse_mode { - $(crate::probe::ParsingMode::Strict => { $strict_handler },)? - $(crate::probe::ParsingMode::BestAttempt => { $best_attempt_handler },)? - $(crate::probe::ParsingMode::Relaxed => { $relaxed_handler },)? + $(crate::config::ParsingMode::Strict => { $strict_handler },)? + $(crate::config::ParsingMode::BestAttempt => { $best_attempt_handler },)? + $(crate::config::ParsingMode::Relaxed => { $relaxed_handler },)? #[allow(unreachable_patterns)] _ => {} } diff --git a/src/mp4/atom_info.rs b/src/mp4/atom_info.rs index 980d0d75a..7a21de525 100644 --- a/src/mp4/atom_info.rs +++ b/src/mp4/atom_info.rs @@ -1,8 +1,7 @@ +use crate::config::ParsingMode; use crate::error::{ErrorKind, LoftyError, Result}; use crate::macros::{err, try_vec}; -use crate::probe::ParsingMode; -use crate::tag::item::ItemKey; -use crate::tag::TagType; +use crate::tag::{ItemKey, TagType}; use crate::util::text::utf8_decode; use std::borrow::Cow; diff --git a/src/mp4/ilst/atom.rs b/src/mp4/ilst/atom.rs index fe51e56e8..45aba0e71 100644 --- a/src/mp4/ilst/atom.rs +++ b/src/mp4/ilst/atom.rs @@ -179,7 +179,7 @@ impl<'a> Atom<'a> { /// ```rust /// use lofty::mp4::{Atom, AtomData, AtomIdent}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// // Create an artist atom /// let mut atom = Atom::new( /// AtomIdent::Fourcc(*b"\x49ART"), diff --git a/src/mp4/ilst/mod.rs b/src/mp4/ilst/mod.rs index 7fd00d022..1a901924d 100644 --- a/src/mp4/ilst/mod.rs +++ b/src/mp4/ilst/mod.rs @@ -5,13 +5,13 @@ mod r#ref; pub(crate) mod write; use super::AtomIdent; +use crate::config::WriteOptions; use crate::error::LoftyError; use crate::mp4::ilst::atom::AtomDataStorage; use crate::picture::{Picture, PictureType, TOMBSTONE_PICTURE}; -use crate::tag::item::{ItemKey, ItemValue, TagItem}; -use crate::tag::{try_parse_year, Tag, TagType}; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; -use crate::write_options::WriteOptions; +use crate::tag::{ + try_parse_year, Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, +}; use atom::{AdvisoryRating, Atom, AtomData}; use std::borrow::Cow; @@ -92,7 +92,7 @@ impl Ilst { /// /// ```rust /// use lofty::mp4::Ilst; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let ilst_tag = Ilst::new(); /// assert!(ilst_tag.is_empty()); @@ -107,7 +107,7 @@ impl Ilst { /// /// ```rust /// use lofty::mp4::{AtomIdent, Ilst}; - /// use lofty::Accessor; + /// use lofty::tag::Accessor; /// /// let mut ilst = Ilst::new(); /// ilst.set_title(String::from("Foo title")); @@ -175,7 +175,7 @@ impl Ilst { /// /// ```rust /// use lofty::mp4::{Atom, AtomData, AtomIdent, Ilst}; - /// use lofty::Accessor; + /// use lofty::tag::Accessor; /// /// const TITLE_IDENTIFIER: AtomIdent = AtomIdent::Fourcc(*b"\xa9nam"); /// @@ -202,7 +202,7 @@ impl Ilst { /// /// ```rust /// use lofty::mp4::{Atom, AtomData, AtomIdent, Ilst}; - /// use lofty::Accessor; + /// use lofty::tag::Accessor; /// /// const TITLE_IDENTIFIER: AtomIdent = AtomIdent::Fourcc(*b"\xa9nam"); /// @@ -250,7 +250,8 @@ impl Ilst { /// /// ```rust /// use lofty::mp4::Ilst; - /// use lofty::{MimeType, Picture, PictureType, TagExt}; + /// use lofty::picture::{MimeType, Picture, PictureType}; + /// use lofty::tag::TagExt; /// /// let mut ilst = Ilst::new(); /// @@ -293,7 +294,8 @@ impl Ilst { /// /// ```rust /// use lofty::mp4::Ilst; - /// use lofty::{MimeType, Picture, PictureType, TagExt}; + /// use lofty::picture::{MimeType, Picture, PictureType}; + /// use lofty::tag::TagExt; /// /// let mut ilst = Ilst::new(); /// @@ -759,16 +761,16 @@ impl From for Ilst { #[cfg(test)] mod tests { + use crate::config::{ParseOptions, ParsingMode, WriteOptions}; use crate::mp4::ilst::atom::AtomDataStorage; use crate::mp4::ilst::TITLE; use crate::mp4::read::AtomReader; use crate::mp4::{AdvisoryRating, Atom, AtomData, AtomIdent, Ilst, Mp4File}; + use crate::prelude::*; use crate::tag::utils::test_utils; use crate::tag::utils::test_utils::read_path; - use crate::{ - Accessor as _, AudioFile, ItemKey, ItemValue, ParseOptions, ParsingMode, SplitTag as _, - Tag, TagExt as _, TagItem, TagType, WriteOptions, - }; + use crate::tag::{ItemValue, Tag, TagItem, TagType}; + use std::io::{Cursor, Read as _, Seek as _, Write as _}; fn read_ilst(path: &str, parse_mode: ParsingMode) -> Ilst { @@ -852,10 +854,10 @@ mod tests { let len = tag.len(); let cursor = Cursor::new(tag); - let mut reader = AtomReader::new(cursor, crate::ParsingMode::Strict).unwrap(); + let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap(); let parsed_tag = - super::read::parse_ilst(&mut reader, crate::ParsingMode::Strict, len as u64).unwrap(); + super::read::parse_ilst(&mut reader, ParsingMode::Strict, len as u64).unwrap(); assert_eq!(expected_tag, parsed_tag); } @@ -870,15 +872,12 @@ mod tests { .unwrap(); let cursor = Cursor::new(&writer[8..]); - let mut reader = AtomReader::new(cursor, crate::ParsingMode::Strict).unwrap(); + let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap(); // Remove the ilst identifier and size - let temp_parsed_tag = super::read::parse_ilst( - &mut reader, - crate::ParsingMode::Strict, - (writer.len() - 8) as u64, - ) - .unwrap(); + let temp_parsed_tag = + super::read::parse_ilst(&mut reader, ParsingMode::Strict, (writer.len() - 8) as u64) + .unwrap(); assert_eq!(parsed_tag, temp_parsed_tag); } @@ -889,10 +888,9 @@ mod tests { let len = tag.len(); let cursor = Cursor::new(tag); - let mut reader = AtomReader::new(cursor, crate::ParsingMode::Strict).unwrap(); + let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap(); - let ilst = - super::read::parse_ilst(&mut reader, crate::ParsingMode::Strict, len as u64).unwrap(); + let ilst = super::read::parse_ilst(&mut reader, ParsingMode::Strict, len as u64).unwrap(); let tag: Tag = ilst.into(); @@ -1009,14 +1007,11 @@ mod tests { assert_eq!(old_free_size, PADDING_SIZE as u32); let cursor = Cursor::new(ilst_bytes); - let mut reader = AtomReader::new(cursor, crate::ParsingMode::Strict).unwrap(); + let mut reader = AtomReader::new(cursor, ParsingMode::Strict).unwrap(); - ilst = super::read::parse_ilst( - &mut reader, - crate::ParsingMode::Strict, - ilst_bytes.len() as u64, - ) - .unwrap(); + ilst = + super::read::parse_ilst(&mut reader, ParsingMode::Strict, ilst_bytes.len() as u64) + .unwrap(); } let mut file = tempfile::tempfile().unwrap(); diff --git a/src/mp4/ilst/read.rs b/src/mp4/ilst/read.rs index eea7faa69..f07cecabd 100644 --- a/src/mp4/ilst/read.rs +++ b/src/mp4/ilst/read.rs @@ -2,6 +2,7 @@ use super::constants::{ BE_SIGNED_INTEGER, BE_UNSIGNED_INTEGER, BMP, JPEG, PNG, RESERVED, UTF16, UTF8, }; use super::{Atom, AtomData, AtomIdent, Ilst}; +use crate::config::ParsingMode; use crate::error::{LoftyError, Result}; use crate::id3::v1::constants::GENRES; use crate::macros::{err, try_vec}; @@ -10,7 +11,6 @@ use crate::mp4::ilst::atom::AtomDataStorage; use crate::mp4::read::{skip_unneeded, AtomReader}; use crate::picture::{MimeType, Picture, PictureType}; use crate::util::text::{utf16_decode_bytes, utf8_decode}; -use crate::ParsingMode; use std::borrow::Cow; use std::io::{Cursor, Read, Seek, SeekFrom}; diff --git a/src/mp4/ilst/ref.rs b/src/mp4/ilst/ref.rs index 0bd44a999..0e0c5505c 100644 --- a/src/mp4/ilst/ref.rs +++ b/src/mp4/ilst/ref.rs @@ -2,9 +2,9 @@ // Reference Conversions // ********************* +use crate::config::WriteOptions; use crate::error::Result; use crate::mp4::{Atom, AtomData, AtomIdent, Ilst}; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::Write; diff --git a/src/mp4/ilst/write.rs b/src/mp4/ilst/write.rs index f129da726..a49215263 100644 --- a/src/mp4/ilst/write.rs +++ b/src/mp4/ilst/write.rs @@ -1,4 +1,5 @@ use super::r#ref::IlstRef; +use crate::config::{ParseOptions, WriteOptions}; use crate::error::{FileEncodingError, Result}; use crate::file::FileType; use crate::macros::{decode_err, err, try_vec}; @@ -8,8 +9,6 @@ use crate::mp4::read::{atom_tree, meta_is_full, nested_atom, verify_mp4, AtomRea use crate::mp4::write::{AtomWriter, AtomWriterCompanion, ContextualAtom}; use crate::mp4::AtomData; use crate::picture::{MimeType, Picture}; -use crate::probe::ParseOptions; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::{Cursor, Seek, SeekFrom, Write}; diff --git a/src/mp4/mod.rs b/src/mp4/mod.rs index 330c11735..582ce1ce9 100644 --- a/src/mp4/mod.rs +++ b/src/mp4/mod.rs @@ -47,10 +47,11 @@ impl Mp4File { /// # Examples /// /// ```rust,no_run + /// use lofty::config::ParseOptions; + /// use lofty::file::AudioFile; /// use lofty::mp4::Mp4File; - /// use lofty::{AudioFile, ParseOptions}; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let mut m4a_reader = std::io::Cursor::new(&[]); /// let m4a_file = Mp4File::read_from(&mut m4a_reader, ParseOptions::new())?; /// diff --git a/src/mp4/moov.rs b/src/mp4/moov.rs index 885d42cfe..6d2536fa7 100644 --- a/src/mp4/moov.rs +++ b/src/mp4/moov.rs @@ -2,9 +2,9 @@ use super::atom_info::{AtomIdent, AtomInfo}; use super::ilst::read::parse_ilst; use super::ilst::Ilst; use super::read::{meta_is_full, nested_atom, skip_unneeded, AtomReader}; +use crate::config::ParsingMode; use crate::error::Result; use crate::macros::decode_err; -use crate::ParsingMode; use std::io::{Read, Seek}; diff --git a/src/mp4/properties.rs b/src/mp4/properties.rs index 4c27bd79a..6ebac0ea9 100644 --- a/src/mp4/properties.rs +++ b/src/mp4/properties.rs @@ -1,10 +1,10 @@ use super::atom_info::{AtomIdent, AtomInfo}; use super::read::{nested_atom, skip_unneeded, AtomReader}; +use crate::config::ParsingMode; use crate::error::{LoftyError, Result}; use crate::macros::{decode_err, err, try_vec}; -use crate::math::RoundedDivision; -use crate::probe::ParsingMode; use crate::properties::FileProperties; +use crate::util::math::RoundedDivision; use std::io::{Cursor, Read, Seek, SeekFrom}; use std::time::Duration; diff --git a/src/mp4/read.rs b/src/mp4/read.rs index ffe7aa563..c5de5411a 100644 --- a/src/mp4/read.rs +++ b/src/mp4/read.rs @@ -2,10 +2,10 @@ use super::atom_info::{AtomIdent, AtomInfo}; use super::moov::Moov; use super::properties::Mp4Properties; use super::Mp4File; +use crate::config::{ParseOptions, ParsingMode}; use crate::error::{ErrorKind, LoftyError, Result}; use crate::macros::{decode_err, err}; -use crate::probe::{ParseOptions, ParsingMode}; -use crate::traits::SeekStreamLen; +use crate::util::io::SeekStreamLen; use crate::util::text::utf8_decode_str; use std::io::{Read, Seek, SeekFrom}; @@ -28,8 +28,7 @@ where R: Read + Seek, { pub(super) fn new(mut reader: R, parse_mode: ParsingMode) -> Result { - #[allow(unstable_name_collisions)] - let len = reader.stream_len()?; + let len = reader.stream_len_hack()?; Ok(Self { reader, start: 0, @@ -185,7 +184,7 @@ where R: Read + Seek, { let mut reader = AtomReader::new(data, parse_options.parsing_mode)?; - let file_length = reader.stream_len()?; + let file_length = reader.stream_len_hack()?; let ftyp = verify_mp4(&mut reader)?; diff --git a/src/mp4/write.rs b/src/mp4/write.rs index 6ca52c12c..e5bd7de43 100644 --- a/src/mp4/write.rs +++ b/src/mp4/write.rs @@ -1,7 +1,7 @@ +use crate::config::ParsingMode; use crate::error::Result; use crate::mp4::atom_info::{AtomIdent, AtomInfo, IDENTIFIER_LEN}; use crate::mp4::read::skip_unneeded; -use crate::probe::ParsingMode; use std::cell::{RefCell, RefMut}; use std::fs::File; diff --git a/src/mpeg/properties.rs b/src/mpeg/properties.rs index d3b73e1d7..80a50cc08 100644 --- a/src/mpeg/properties.rs +++ b/src/mpeg/properties.rs @@ -1,8 +1,8 @@ use super::header::{ChannelMode, Emphasis, Header, Layer, MpegVersion, XingHeader}; use crate::error::Result; -use crate::math::RoundedDivision; use crate::mpeg::header::{cmp_header, rev_search_for_frame_sync, HeaderCmpResult, HEADER_MASK}; use crate::properties::{ChannelMask, FileProperties}; +use crate::util::math::RoundedDivision; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; diff --git a/src/mpeg/read.rs b/src/mpeg/read.rs index 54d36ebba..628e23684 100644 --- a/src/mpeg/read.rs +++ b/src/mpeg/read.rs @@ -1,13 +1,13 @@ use super::header::{cmp_header, search_for_frame_sync, Header, HeaderCmpResult, XingHeader}; use super::{MpegFile, MpegProperties}; use crate::ape::header::read_ape_header; +use crate::config::{ParseOptions, ParsingMode}; use crate::error::Result; use crate::id3::v2::header::Id3v2Header; use crate::id3::v2::read::parse_id3v2; use crate::id3::{find_id3v1, find_lyrics3v2, FindId3v2Config, ID3FindResults}; use crate::macros::{decode_err, err}; use crate::mpeg::header::HEADER_MASK; -use crate::probe::{ParseOptions, ParsingMode}; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/musepack/read.rs b/src/musepack/read.rs index a9365460e..38fed80a0 100644 --- a/src/musepack/read.rs +++ b/src/musepack/read.rs @@ -2,11 +2,11 @@ use super::sv4to6::MpcSv4to6Properties; use super::sv7::MpcSv7Properties; use super::sv8::MpcSv8Properties; use super::{MpcFile, MpcProperties, MpcStreamVersion}; +use crate::config::ParseOptions; use crate::error::Result; use crate::id3::v2::read::parse_id3v2; use crate::id3::{find_id3v1, find_id3v2, find_lyrics3v2, FindId3v2Config, ID3FindResults}; -use crate::probe::ParseOptions; -use crate::traits::SeekStreamLen; +use crate::util::io::SeekStreamLen; use std::io::{Read, Seek, SeekFrom}; @@ -20,8 +20,7 @@ where let mut version = MpcStreamVersion::Sv4to6; let mut file = MpcFile::default(); - #[allow(unstable_name_collisions)] - let mut stream_length = reader.stream_len()?; + let mut stream_length = reader.stream_len_hack()?; // ID3v2 tags are unsupported in MPC files, but still possible #[allow(unused_variables)] diff --git a/src/musepack/sv4to6/properties.rs b/src/musepack/sv4to6/properties.rs index 18f60f66c..9f5b1c786 100644 --- a/src/musepack/sv4to6/properties.rs +++ b/src/musepack/sv4to6/properties.rs @@ -1,7 +1,7 @@ +use crate::config::ParsingMode; use crate::error::Result; use crate::macros::{decode_err, parse_mode_choice}; use crate::musepack::constants::{MPC_DECODER_SYNTH_DELAY, MPC_FRAME_LENGTH}; -use crate::probe::ParsingMode; use crate::properties::FileProperties; use std::io::Read; diff --git a/src/musepack/sv8/properties.rs b/src/musepack/sv8/properties.rs index bc26fcb59..4ad1fa184 100644 --- a/src/musepack/sv8/properties.rs +++ b/src/musepack/sv8/properties.rs @@ -1,7 +1,7 @@ use super::read::PacketReader; +use crate::config::ParsingMode; use crate::error::Result; use crate::musepack::constants::FREQUENCY_TABLE; -use crate::probe::ParsingMode; use crate::properties::FileProperties; use std::io::Read; diff --git a/src/musepack/sv8/read.rs b/src/musepack/sv8/read.rs index 913b89580..93c745b04 100644 --- a/src/musepack/sv8/read.rs +++ b/src/musepack/sv8/read.rs @@ -1,7 +1,7 @@ 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::probe::ParsingMode; use std::io::Read; use std::time::Duration; diff --git a/src/ogg/opus/mod.rs b/src/ogg/opus/mod.rs index a30c197b2..4aa883346 100644 --- a/src/ogg/opus/mod.rs +++ b/src/ogg/opus/mod.rs @@ -2,9 +2,9 @@ pub(super) mod properties; use super::find_last_page; use super::tag::VorbisComments; +use crate::config::ParseOptions; use crate::error::Result; use crate::ogg::constants::{OPUSHEAD, OPUSTAGS}; -use crate::probe::ParseOptions; use properties::OpusProperties; use std::io::{Read, Seek}; diff --git a/src/ogg/opus/properties.rs b/src/ogg/opus/properties.rs index 472e9fdc8..63ad36dc1 100644 --- a/src/ogg/opus/properties.rs +++ b/src/ogg/opus/properties.rs @@ -1,8 +1,8 @@ use super::find_last_page; use crate::error::Result; use crate::macros::decode_err; -use crate::math::RoundedDivision; use crate::properties::{ChannelMask, FileProperties}; +use crate::util::math::RoundedDivision; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; diff --git a/src/ogg/picture_storage.rs b/src/ogg/picture_storage.rs index 0f25a8dca..fd3051794 100644 --- a/src/ogg/picture_storage.rs +++ b/src/ogg/picture_storage.rs @@ -70,30 +70,41 @@ pub trait OggPictureStorage: private::Sealed { /// # Examples /// /// ```rust - /// use lofty::ogg::{VorbisComments, OggPictureStorage}; - /// # use lofty::{Picture, PictureInformation, PictureType, MimeType}; - /// - /// # fn main() -> lofty::Result<()> { - /// # let front_cover = Picture::new_unchecked(PictureType::CoverFront, Some(MimeType::Png), None, Vec::new()); - /// # let front_cover_info = PictureInformation::default(); - /// # let back_cover = Picture::new_unchecked(PictureType::CoverBack, Some(MimeType::Png), None, Vec::new()); - /// # let back_cover_info = PictureInformation::default(); - /// # let another_picture = Picture::new_unchecked(PictureType::Band, Some(MimeType::Png), None, Vec::new()); + /// use lofty::ogg::{OggPictureStorage, VorbisComments}; + /// use lofty::picture::{MimeType, Picture, PictureInformation, PictureType}; + /// + /// # fn main() -> lofty::error::Result<()> { /// let mut tag = VorbisComments::default(); /// /// // Add a front cover + /// let front_cover = Picture::new_unchecked( + /// PictureType::CoverFront, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); + /// let front_cover_info = PictureInformation::default(); /// tag.insert_picture(front_cover, Some(front_cover_info))?; /// /// assert_eq!(tag.pictures().len(), 1); /// assert_eq!(tag.pictures()[0].0.pic_type(), PictureType::CoverFront); /// /// // Replace the front cover with a back cover + /// let back_cover = Picture::new_unchecked( + /// PictureType::CoverBack, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); + /// let back_cover_info = PictureInformation::default(); /// tag.set_picture(0, back_cover, back_cover_info); /// /// assert_eq!(tag.pictures().len(), 1); /// assert_eq!(tag.pictures()[0].0.pic_type(), PictureType::CoverBack); /// /// // Use an out of bounds index + /// let another_picture = + /// Picture::new_unchecked(PictureType::Band, Some(MimeType::Png), None, Vec::new()); /// tag.set_picture(100, another_picture, PictureInformation::default()); /// /// assert_eq!(tag.pictures().len(), 2); @@ -118,12 +129,18 @@ pub trait OggPictureStorage: private::Sealed { /// # Examples /// /// ```rust - /// use lofty::ogg::{VorbisComments, OggPictureStorage}; - /// # use lofty::{Picture, PictureType, MimeType, PictureInformation}; + /// use lofty::ogg::{OggPictureStorage, VorbisComments}; + /// use lofty::picture::{MimeType, Picture, PictureInformation, PictureType}; + /// + /// # fn main() -> lofty::error::Result<()> { + /// let front_cover = Picture::new_unchecked( + /// PictureType::CoverFront, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); + /// let front_cover_info = PictureInformation::default(); /// - /// # fn main() -> lofty::Result<()> { - /// # let front_cover = Picture::new_unchecked(PictureType::CoverFront, Some(MimeType::Png), None, Vec::new()); - /// # let front_cover_info = PictureInformation::default(); /// let mut tag = VorbisComments::default(); /// /// // Add a front cover @@ -145,18 +162,29 @@ pub trait OggPictureStorage: private::Sealed { /// # Examples /// /// ```rust - /// use lofty::ogg::{VorbisComments, OggPictureStorage}; - /// # use lofty::{Picture, PictureType, MimeType, PictureInformation}; - /// - /// # fn main() -> lofty::Result<()> { - /// # let front_cover = Picture::new_unchecked(PictureType::CoverFront, Some(MimeType::Png), None, Vec::new()); - /// # let front_cover_info = PictureInformation::default(); - /// # let back_cover = Picture::new_unchecked(PictureType::CoverBack, Some(MimeType::Png), None, Vec::new()); - /// # let back_cover_info = PictureInformation::default(); + /// use lofty::ogg::{OggPictureStorage, VorbisComments}; + /// use lofty::picture::{MimeType, Picture, PictureInformation, PictureType}; + /// + /// # fn main() -> lofty::error::Result<()> { /// let mut tag = VorbisComments::default(); /// /// // Add front and back covers + /// let front_cover = Picture::new_unchecked( + /// PictureType::CoverFront, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); + /// let front_cover_info = PictureInformation::default(); /// tag.insert_picture(front_cover, Some(front_cover_info))?; + /// + /// let back_cover = Picture::new_unchecked( + /// PictureType::CoverBack, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); + /// let back_cover_info = PictureInformation::default(); /// tag.insert_picture(back_cover, Some(front_cover_info))?; /// /// assert_eq!(tag.pictures().len(), 2); @@ -174,7 +202,7 @@ pub trait OggPictureStorage: private::Sealed { } mod private { - use crate::{Picture, PictureInformation}; + use crate::picture::{Picture, PictureInformation}; pub trait Sealed { fn pictures_mut(&mut self) -> &mut Vec<(Picture, PictureInformation)>; diff --git a/src/ogg/read.rs b/src/ogg/read.rs index 0e9ba680f..17c26364b 100644 --- a/src/ogg/read.rs +++ b/src/ogg/read.rs @@ -1,9 +1,9 @@ use super::tag::VorbisComments; use super::verify_signature; +use crate::config::ParsingMode; use crate::error::{ErrorKind, LoftyError, Result}; use crate::macros::{decode_err, err, parse_mode_choice}; use crate::picture::{MimeType, Picture, PictureInformation, PictureType}; -use crate::probe::ParsingMode; use crate::util::text::{utf16_decode, utf8_decode, utf8_decode_str}; use std::borrow::Cow; diff --git a/src/ogg/speex/mod.rs b/src/ogg/speex/mod.rs index 9a79398e9..4af2b333d 100644 --- a/src/ogg/speex/mod.rs +++ b/src/ogg/speex/mod.rs @@ -1,9 +1,9 @@ pub(super) mod properties; use super::tag::VorbisComments; +use crate::config::ParseOptions; use crate::error::Result; use crate::ogg::constants::SPEEXHEADER; -use crate::probe::ParseOptions; use properties::SpeexProperties; use std::io::{Read, Seek}; diff --git a/src/ogg/speex/properties.rs b/src/ogg/speex/properties.rs index 660e20f11..9f7189691 100644 --- a/src/ogg/speex/properties.rs +++ b/src/ogg/speex/properties.rs @@ -1,8 +1,8 @@ use crate::error::Result; use crate::macros::decode_err; -use crate::math::RoundedDivision; 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; diff --git a/src/ogg/tag.rs b/src/ogg/tag.rs index a4567e906..f4e2ce069 100644 --- a/src/ogg/tag.rs +++ b/src/ogg/tag.rs @@ -1,3 +1,4 @@ +use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; use crate::file::FileType; use crate::macros::err; @@ -5,10 +6,9 @@ use crate::ogg::picture_storage::OggPictureStorage; use crate::ogg::write::OGGFormat; use crate::picture::{Picture, PictureInformation}; use crate::probe::Probe; -use crate::tag::item::{ItemKey, ItemValue, TagItem}; -use crate::tag::{try_parse_year, Tag, TagType}; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; -use crate::write_options::WriteOptions; +use crate::tag::{ + try_parse_year, Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, +}; use std::borrow::Cow; use std::fs::File; @@ -79,7 +79,7 @@ impl VorbisComments { /// /// ```rust /// use lofty::ogg::VorbisComments; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let vorbis_comments_tag = VorbisComments::new(); /// assert!(vorbis_comments_tag.is_empty()); @@ -144,7 +144,7 @@ impl VorbisComments { /// /// ```rust /// use lofty::ogg::VorbisComments; - /// use lofty::TagExt; + /// use lofty::tag::TagExt; /// /// let mut vorbis_comments = VorbisComments::default(); /// @@ -693,11 +693,10 @@ pub(crate) fn create_vorbis_comments_ref( #[cfg(test)] mod tests { + use crate::config::{ParsingMode, WriteOptions}; use crate::ogg::{OggPictureStorage, VorbisComments}; - use crate::{ - ItemKey, ItemValue, MergeTag as _, ParsingMode, SplitTag as _, Tag, TagExt as _, TagItem, - TagType, WriteOptions, - }; + use crate::prelude::*; + use crate::tag::{ItemValue, Tag, TagItem, TagType}; fn read_tag(tag: &[u8]) -> VorbisComments { let mut reader = std::io::Cursor::new(tag); diff --git a/src/ogg/vorbis/mod.rs b/src/ogg/vorbis/mod.rs index b01adeac4..6f401be8e 100644 --- a/src/ogg/vorbis/mod.rs +++ b/src/ogg/vorbis/mod.rs @@ -2,9 +2,9 @@ pub(super) mod properties; use super::find_last_page; use super::tag::VorbisComments; +use crate::config::ParseOptions; use crate::error::Result; use crate::ogg::constants::{VORBIS_COMMENT_HEAD, VORBIS_IDENT_HEAD}; -use crate::probe::ParseOptions; use properties::VorbisProperties; use std::io::{Read, Seek}; diff --git a/src/ogg/vorbis/properties.rs b/src/ogg/vorbis/properties.rs index c76894cb2..af07bd545 100644 --- a/src/ogg/vorbis/properties.rs +++ b/src/ogg/vorbis/properties.rs @@ -1,7 +1,7 @@ use super::find_last_page; use crate::error::Result; -use crate::math::RoundedDivision; use crate::properties::FileProperties; +use crate::util::math::RoundedDivision; use std::io::{Read, Seek, SeekFrom}; use std::time::Duration; diff --git a/src/ogg/write.rs b/src/ogg/write.rs index f3a945e21..194c7886e 100644 --- a/src/ogg/write.rs +++ b/src/ogg/write.rs @@ -1,4 +1,5 @@ use super::verify_signature; +use crate::config::WriteOptions; use crate::error::Result; use crate::file::FileType; use crate::macros::{decode_err, err, try_vec}; @@ -6,7 +7,6 @@ use crate::ogg::constants::{OPUSTAGS, VORBIS_COMMENT_HEAD}; use crate::ogg::tag::{create_vorbis_comments_ref, VorbisCommentsRef}; use crate::picture::{Picture, PictureInformation}; use crate::tag::{Tag, TagType}; -use crate::write_options::WriteOptions; use std::fs::File; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; diff --git a/src/picture.rs b/src/picture.rs index e9d5b6851..dda994e9e 100644 --- a/src/picture.rs +++ b/src/picture.rs @@ -1,6 +1,8 @@ +//! Format-agnostic picture handling + +use crate::config::ParsingMode; use crate::error::{ErrorKind, LoftyError, Result}; use crate::macros::err; -use crate::probe::ParsingMode; use crate::util::text::utf8_decode_str; use std::borrow::Cow; @@ -59,7 +61,7 @@ impl MimeType { /// # Examples /// /// ```rust - /// use lofty::MimeType; + /// use lofty::picture::MimeType; /// /// let jpeg_mimetype_str = "image/jpeg"; /// assert_eq!(MimeType::from_str(jpeg_mimetype_str), MimeType::Jpeg); @@ -82,7 +84,7 @@ impl MimeType { /// # Examples /// /// ```rust - /// use lofty::MimeType; + /// use lofty::picture::MimeType; /// /// let jpeg_mimetype = MimeType::Jpeg; /// assert_eq!(jpeg_mimetype.as_str(), "image/jpeg") @@ -138,7 +140,7 @@ pub enum PictureType { impl PictureType { // ID3/OGG specific methods - /// Get a u8 from a `PictureType` according to ID3v2 APIC + /// Get a `u8` from a `PictureType` according to ID3v2 APIC pub fn as_u8(&self) -> u8 { match self { Self::Other => 0, diff --git a/src/probe.rs b/src/probe.rs index 6e691dfd7..0d66d5c83 100644 --- a/src/probe.rs +++ b/src/probe.rs @@ -1,9 +1,11 @@ +//! Format-agonostic file parsing tools + use crate::aac::AacFile; use crate::ape::ApeFile; +use crate::config::{global_options, ParseOptions}; use crate::error::Result; use crate::file::{AudioFile, FileType, FileTypeGuessResult, TaggedFile}; use crate::flac::FlacFile; -use crate::global_options::global_options; use crate::iff::aiff::AiffFile; use crate::iff::wav::WavFile; use crate::macros::err; @@ -21,160 +23,6 @@ use std::fs::File; use std::io::{BufReader, Cursor, Read, Seek, SeekFrom}; use std::path::Path; -/// Options to control how Lofty parses a file -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub struct ParseOptions { - pub(crate) read_properties: bool, - pub(crate) parsing_mode: ParsingMode, - pub(crate) max_junk_bytes: usize, -} - -impl Default for ParseOptions { - /// The default implementation for `ParseOptions` - /// - /// The defaults are as follows: - /// - /// ```rust,ignore - /// ParseOptions { - /// read_properties: true, - /// parsing_mode: ParsingMode::BestAttempt, - /// max_junk_bytes: 1024 - /// } - /// ``` - fn default() -> Self { - Self::new() - } -} - -impl ParseOptions { - /// Default parsing mode - pub const DEFAULT_PARSING_MODE: ParsingMode = ParsingMode::BestAttempt; - - /// Default number of junk bytes to read - pub const DEFAULT_MAX_JUNK_BYTES: usize = 1024; - - /// Creates a new `ParseOptions`, alias for `Default` implementation - /// - /// See also: [`ParseOptions::default`] - /// - /// # Examples - /// - /// ```rust - /// use lofty::ParseOptions; - /// - /// let parsing_options = ParseOptions::new(); - /// ``` - #[must_use] - pub const fn new() -> Self { - Self { - read_properties: true, - parsing_mode: Self::DEFAULT_PARSING_MODE, - max_junk_bytes: Self::DEFAULT_MAX_JUNK_BYTES, - } - } - - /// Whether or not to read the audio properties - /// - /// # Examples - /// - /// ```rust - /// use lofty::ParseOptions; - /// - /// // By default, `read_properties` is enabled. Here, we don't want to read them. - /// let parsing_options = ParseOptions::new().read_properties(false); - /// ``` - pub fn read_properties(&mut self, read_properties: bool) -> Self { - self.read_properties = read_properties; - *self - } - - /// The parsing mode to use, see [`ParsingMode`] for details - /// - /// # Examples - /// - /// ```rust - /// use lofty::{ParseOptions, ParsingMode}; - /// - /// // By default, `parsing_mode` is ParsingMode::BestAttempt. Here, we need absolute correctness. - /// let parsing_options = ParseOptions::new().parsing_mode(ParsingMode::Strict); - /// ``` - pub fn parsing_mode(&mut self, parsing_mode: ParsingMode) -> Self { - self.parsing_mode = parsing_mode; - *self - } - - /// The maximum number of allowed junk bytes to search - /// - /// Some information may be surrounded by junk bytes, such as tag padding remnants. This sets the maximum - /// number of junk/unrecognized bytes Lofty will search for required information before giving up. - /// - /// # Examples - /// - /// ```rust - /// use lofty::ParseOptions; - /// - /// // I have files full of junk, I'll double the search window! - /// let parsing_options = ParseOptions::new().max_junk_bytes(2048); - /// ``` - pub fn max_junk_bytes(&mut self, max_junk_bytes: usize) -> Self { - self.max_junk_bytes = max_junk_bytes; - *self - } -} - -/// The parsing strictness mode -/// -/// This can be set with [`Probe::options`]. -/// -/// # Examples -/// -/// ```rust,no_run -/// use lofty::{ParseOptions, ParsingMode, Probe}; -/// -/// # fn main() -> lofty::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, -} - /// A format agnostic reader /// /// This provides a way to determine the [`FileType`] of a reader, for when a concrete @@ -186,9 +34,9 @@ pub enum ParsingMode { /// open file. /// /// ```rust,no_run -/// # use lofty::{LoftyError, Probe}; -/// # fn main() -> Result<(), LoftyError> { -/// use lofty::FileType; +/// # fn main() -> lofty::error::Result<()> { +/// use lofty::file::FileType; +/// use lofty::probe::Probe; /// /// let probe = Probe::open("path/to/my.mp3")?; /// @@ -201,9 +49,9 @@ pub enum ParsingMode { /// When a path isn't available, or is unreliable, content-based detection is also possible. /// /// ```rust,no_run -/// # use lofty::{LoftyError, Probe}; -/// # fn main() -> Result<(), LoftyError> { -/// use lofty::FileType; +/// # fn main() -> lofty::error::Result<()> { +/// use lofty::file::FileType; +/// use lofty::probe::Probe; /// /// // Our same path probe with a guessed file type /// let probe = Probe::open("path/to/my.mp3")?.guess_file_type()?; @@ -217,9 +65,9 @@ pub enum ParsingMode { /// Or with another reader /// /// ```rust -/// # use lofty::{LoftyError, Probe}; -/// # fn main() -> Result<(), LoftyError> { -/// use lofty::FileType; +/// # fn main() -> lofty::error::Result<()> { +/// use lofty::file::FileType; +/// use lofty::probe::Probe; /// use std::io::Cursor; /// /// static MAC_HEADER: &[u8; 3] = b"MAC"; @@ -240,17 +88,17 @@ pub struct Probe { impl Probe { /// Create a new `Probe` /// - /// Before creating a `Probe`, consider wrapping it in a [`BufReader`](std::io::BufReader) for better + /// Before creating a `Probe`, consider wrapping it in a [`BufReader`] for better /// performance. /// /// # Examples /// /// ```rust - /// use lofty::Probe; + /// use lofty::probe::Probe; /// use std::fs::File; /// use std::io::BufReader; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// let file = File::open(path)?; /// let reader = BufReader::new(file); @@ -269,17 +117,18 @@ impl Probe { /// Create a new `Probe` with a specified [`FileType`] /// - /// Before creating a `Probe`, consider wrapping it in a [`BufReader`](std::io::BufReader) for better + /// Before creating a `Probe`, consider wrapping it in a [`BufReader`] for better /// performance. /// /// # Examples /// /// ```rust - /// use lofty::{FileType, Probe}; + /// use lofty::file::FileType; + /// use lofty::probe::Probe; /// use std::fs::File; /// use std::io::BufReader; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let my_mp3_path = "tests/files/assets/minimal/full_test.mp3"; /// // We know the file is going to be an MP3, /// // so we can skip the format detection @@ -302,9 +151,10 @@ impl Probe { /// # Examples /// /// ```rust - /// use lofty::{FileType, Probe}; + /// use lofty::file::FileType; + /// use lofty::probe::Probe; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let reader = std::io::Cursor::new(&[]); /// let probe = Probe::new(reader); /// @@ -320,9 +170,10 @@ impl Probe { /// # Examples /// /// ```rust - /// use lofty::{FileType, Probe}; + /// use lofty::file::FileType; + /// use lofty::probe::Probe; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let reader = std::io::Cursor::new(&[]); /// let mut probe = Probe::new(reader); /// assert_eq!(probe.file_type(), None); @@ -342,9 +193,10 @@ impl Probe { /// # Examples /// /// ```rust - /// use lofty::{ParseOptions, Probe}; + /// use lofty::config::ParseOptions; + /// use lofty::probe::Probe; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let reader = std::io::Cursor::new(&[]); /// // By default, properties will be read. /// // In this example, we want to turn this off. @@ -364,9 +216,10 @@ impl Probe { /// # Examples /// /// ```rust - /// use lofty::{FileType, Probe}; + /// use lofty::file::FileType; + /// use lofty::probe::Probe; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let reader = std::io::Cursor::new(&[]); /// let probe = Probe::new(reader); /// @@ -391,9 +244,10 @@ impl Probe> { /// # Examples /// /// ```rust,no_run - /// use lofty::{FileType, Probe}; + /// use lofty::file::FileType; + /// use lofty::probe::Probe; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// let probe = Probe::open("path/to/my.mp3")?; /// /// // Guessed from the "mp3" extension, see `FileType::from_ext` @@ -436,9 +290,10 @@ impl Probe { /// # Examples /// /// ```rust - /// use lofty::{FileType, Probe}; + /// use lofty::file::FileType; + /// use lofty::probe::Probe; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// # let file = std::fs::File::open(path)?; /// # let reader = std::io::BufReader::new(file); @@ -588,9 +443,10 @@ impl Probe { /// # Examples /// /// ```rust - /// use lofty::{FileType, Probe}; + /// use lofty::file::FileType; + /// use lofty::probe::Probe; /// - /// # fn main() -> lofty::Result<()> { + /// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// # let file = std::fs::File::open(path)?; /// # let reader = std::io::BufReader::new(file); @@ -646,7 +502,7 @@ impl Probe { /// use lofty::read_from; /// use std::fs::File; /// -/// # fn main() -> lofty::Result<()> { +/// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// let mut file = File::open(path)?; /// @@ -673,7 +529,7 @@ pub fn read_from(file: &mut File) -> Result { /// ```rust /// use lofty::read_from_path; /// -/// # fn main() -> lofty::Result<()> { +/// # fn main() -> lofty::error::Result<()> { /// # let path = "tests/files/assets/minimal/full_test.mp3"; /// let parsed_file = read_from_path(path)?; /// # Ok(()) } @@ -687,9 +543,10 @@ where #[cfg(test)] mod tests { - use crate::{FileType, GlobalOptions, Probe}; + use crate::config::{GlobalOptions, ParseOptions}; + use crate::file::FileType; + use crate::probe::Probe; - use lofty::ParseOptions; use std::fs::File; #[test] @@ -784,7 +641,7 @@ mod tests { let parse_options = ParseOptions::new().read_properties(false); let mut global_options = GlobalOptions::new().allocation_limit(50); - crate::global_options::apply_global_options(global_options); + crate::config::apply_global_options(global_options); // An allocation with a size of 40 bytes should be ok let within_limits = create_fake_mp3(40); @@ -802,7 +659,7 @@ mod tests { // Now test the default allocation limit (16MB), which should of course be ok with 60 bytes global_options.allocation_limit = GlobalOptions::DEFAULT_ALLOCATION_LIMIT; - crate::global_options::apply_global_options(global_options); + crate::config::apply_global_options(global_options); let probe = Probe::new(std::io::Cursor::new(&too_big)) .set_file_type(FileType::Mpeg) diff --git a/src/properties/mod.rs b/src/properties/mod.rs index a6d5d12a7..84cb263a3 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -1,7 +1,7 @@ //! Generic audio properties //! //! Many audio formats have their own custom properties, but there are some properties that are -//! common to all audio formats. When using [`TaggedFile`](crate::TaggedFile), any custom properties +//! common to all audio formats. When using [`TaggedFile`](crate::file::TaggedFile), any custom properties //! will simply be converted to [`FileProperties`]. mod channel_mask; diff --git a/src/properties/tests.rs b/src/properties/tests.rs index 27489472c..1a06c2bd7 100644 --- a/src/properties/tests.rs +++ b/src/properties/tests.rs @@ -1,5 +1,7 @@ use crate::aac::{AACProperties, AacFile}; use crate::ape::{ApeFile, ApeProperties}; +use crate::config::ParseOptions; +use crate::file::AudioFile; use crate::flac::{FlacFile, FlacProperties}; use crate::iff::aiff::{AiffFile, AiffProperties}; use crate::iff::wav::{WavFile, WavFormat, WavProperties}; @@ -12,10 +14,8 @@ use crate::musepack::{MpcFile, MpcProperties}; use crate::ogg::{ OpusFile, OpusProperties, SpeexFile, SpeexProperties, VorbisFile, VorbisProperties, }; -use crate::probe::ParseOptions; use crate::properties::ChannelMask; use crate::wavpack::{WavPackFile, WavPackProperties}; -use crate::AudioFile; use std::fs::File; use std::time::Duration; diff --git a/src/resolve.rs b/src/resolve.rs index fb759e95f..2262c0e8a 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -1,9 +1,9 @@ //! Tools to create custom file resolvers //! //! For a full example of a custom resolver, see [this](https://github.com/Serial-ATA/lofty-rs/tree/main/examples/custom_resolver). +use crate::config::ParseOptions; use crate::error::Result; use crate::file::{AudioFile, FileType, TaggedFile}; -use crate::probe::ParseOptions; use crate::tag::TagType; use std::collections::HashMap; @@ -15,7 +15,7 @@ use std::sync::{Arc, Mutex, OnceLock}; /// /// This trait allows for the creation of custom [`FileType`]s, that can make use of /// lofty's API. Registering a `FileResolver` ([`register_custom_resolver`]) makes it possible -/// to detect and read files using [`Probe`](crate::Probe). +/// to detect and read files using [`Probe`](crate::probe::Probe). pub trait FileResolver: Send + Sync + AudioFile { /// The extension associated with the [`FileType`] without the '.' fn extension() -> Option<&'static str>; @@ -129,14 +129,12 @@ pub fn register_custom_resolver(name: &'static str) { #[cfg(test)] mod tests { + use crate::config::{GlobalOptions, ParseOptions}; use crate::file::{FileType, TaggedFileExt}; - use crate::global_options::GlobalOptions; use crate::id3::v2::Id3v2Tag; - use crate::probe::ParseOptions; use crate::properties::FileProperties; use crate::resolve::{register_custom_resolver, FileResolver}; - use crate::tag::TagType; - use crate::traits::Accessor; + use crate::tag::{Accessor, TagType}; use std::fs::File; use std::io::{Read, Seek}; @@ -196,7 +194,7 @@ mod tests { register_custom_resolver::("MyFile"); let global_options = GlobalOptions::new().use_custom_resolvers(true); - crate::apply_global_options(global_options); + crate::config::apply_global_options(global_options); let path = "examples/custom_resolver/test_asset.myfile"; let read = crate::read_from_path(path).unwrap(); diff --git a/src/tag/accessor.rs b/src/tag/accessor.rs new file mode 100644 index 000000000..fc32657ba --- /dev/null +++ b/src/tag/accessor.rs @@ -0,0 +1,119 @@ +use std::borrow::Cow; + +// This defines the `Accessor` trait, used to define unified getters/setters for commonly +// accessed tag values. +// +// Usage: +// +// accessor_trait! { +// [field_name] +// } +// +// * `field_name` is the name of the method to access the field. If a name consists of multiple segments, +// such as `track_number`, they should be separated by spaces like so: [track number]. +// +// * `type` is the return type for `Accessor::field_name`. By default, this type will also be used +// in the setter. +// +// An owned type can also be specified for the setter: +// +// accessor_trait! { +// field_name +// } +macro_rules! accessor_trait { + ($([$($name:tt)+] < $($ty:ty),+ >),+ $(,)?) => { + /// Provides accessors for common items + /// + /// This attempts to only provide methods for items that all tags have in common, + /// but there may be exceptions. + pub trait Accessor { + $( + accessor_trait! { @GETTER [$($name)+] $($ty),+ } + + accessor_trait! { @SETTER [$($name)+] $($ty),+ } + + accessor_trait! { @REMOVE [$($name)+] $($ty),+ } + )+ + } + }; + (@GETTER [$($name:tt)+] $ty:ty $(, $_ty:tt)?) => { + accessor_trait! { @GET_METHOD [$($name)+] Option<$ty> } + }; + (@SETTER [$($name:tt)+] $_ty:ty, $owned_ty:tt) => { + accessor_trait! { @SETTER [$($name)+] $owned_ty } + }; + (@SETTER [$($name:tt)+] $ty:ty) => { + accessor_trait! { @SET_METHOD [$($name)+] $ty } + }; + (@REMOVE [$($name:tt)+] $_ty:ty, $owned_ty:tt) => { + accessor_trait! { @REMOVE [$($name)+] $owned_ty } + }; + (@REMOVE [$($name:tt)+] $ty:ty) => { + accessor_trait! { @REMOVE_METHOD [$($name)+], $ty } + }; + (@GET_METHOD [$name:tt $($other:tt)*] Option<$ret_ty:ty>) => { + paste::paste! { + #[doc = "Returns the " $name $(" " $other)*] + /// # Example + /// + /// ```rust + /// use lofty::tag::{Tag, Accessor}; + /// + /// # let tag_type = lofty::tag::TagType::Id3v2; + /// let mut tag = Tag::new(tag_type); + #[doc = "assert_eq!(tag." $name $(_ $other)* "(), None);"] + /// ``` + fn [< + $name $(_ $other)* + >] (&self) -> Option<$ret_ty> { None } + } + }; + (@SET_METHOD [$name:tt $($other:tt)*] $owned_ty:ty) => { + paste::paste! { + #[doc = "Sets the " $name $(" " $other)*] + /// # Example + /// + /// ```rust,ignore + /// use lofty::tag::{Tag, Accessor}; + /// + /// let mut tag = Tag::new(tag_type); + #[doc = "tag.set_" $name $(_ $other)* "(value);"] + /// + #[doc = "assert_eq!(tag." $name $(_ $other)* "(), Some(value));"] + /// ``` + fn [< + set_ $name $(_ $other)* + >] (&mut self , _value: $owned_ty) {} + } + }; + (@REMOVE_METHOD [$name:tt $($other:tt)*], $ty:ty) => { + paste::paste! { + #[doc = "Removes the " $name $(" " $other)*] + /// # Example + /// + /// ```rust,ignore + /// use lofty::tag::{Tag, Accessor}; + /// + /// let mut tag = Tag::new(tag_type); + #[doc = "tag.set_" $name $(_ $other)* "(value);"] + /// + #[doc = "assert_eq!(tag." $name $(_ $other)* "(), Some(value));"] + /// + #[doc = "tag.remove_" $name $(_ $other)* "();"] + /// + #[doc = "assert_eq!(tag." $name $(_ $other)* "(), None);"] + /// ``` + fn [< + remove_ $name $(_ $other)* + >] (&mut self) {} + } + }; +} + +accessor_trait! { + [artist], String>, [title ], String>, + [album ], String>, [genre ], String>, + [track ], [track total], + [disk ], [disk total ], + [year ], [comment ], String>, +} diff --git a/src/tag/item.rs b/src/tag/item.rs index 7653a692e..2f6beec02 100644 --- a/src/tag/item.rs +++ b/src/tag/item.rs @@ -799,7 +799,7 @@ impl TagItem { /// /// * This will check for validity based on the [`TagType`]. /// * If the [`ItemKey`] does not map to a key in the target format, `None` will be returned. - /// * This is unnecessary if you plan on using [`Tag::insert_item`](crate::Tag::insert), as it does validity checks itself. + /// * This is unnecessary if you plan on using [`Tag::insert`](crate::tag::Tag::insert), as it does validity checks itself. pub fn new_checked( tag_type: TagType, item_key: ItemKey, diff --git a/src/tag/mod.rs b/src/tag/mod.rs index 7a6b05136..9caaddb6f 100644 --- a/src/tag/mod.rs +++ b/src/tag/mod.rs @@ -1,20 +1,30 @@ +//! Utilities for generic tag handling + +mod accessor; pub(crate) mod item; +mod split_merge_tag; +mod tag_ext; +mod tag_type; pub(crate) mod utils; +use crate::config::WriteOptions; use crate::error::{LoftyError, Result}; -use crate::file::FileType; use crate::macros::err; use crate::picture::{Picture, PictureType}; use crate::probe::Probe; -use crate::traits::{Accessor, MergeTag, SplitTag, TagExt}; -use item::{ItemKey, ItemValue, TagItem}; -use crate::WriteOptions; use std::borrow::Cow; -use std::fs::{File, OpenOptions}; +use std::fs::File; use std::io::Write; use std::path::Path; +// Exports +pub use accessor::Accessor; +pub use item::{ItemKey, ItemValue, TagItem}; +pub use split_merge_tag::{MergeTag, SplitTag}; +pub use tag_ext::TagExt; +pub use tag_type::TagType; + macro_rules! impl_accessor { ($($item_key:ident => $name:tt),+) => { paste::paste! { @@ -57,7 +67,7 @@ macro_rules! impl_accessor { /// Accessing common items /// /// ```rust -/// use lofty::{Accessor, Tag, TagType}; +/// use lofty::tag::{Accessor, Tag, TagType}; /// /// let tag = Tag::new(TagType::Id3v2); /// @@ -72,7 +82,7 @@ macro_rules! impl_accessor { /// Getting an item of a known type /// /// ```rust -/// use lofty::{ItemKey, Tag, TagType}; +/// use lofty::tag::{ItemKey, Tag, TagType}; /// /// let tag = Tag::new(TagType::Id3v2); /// @@ -87,7 +97,7 @@ macro_rules! impl_accessor { /// /// ```rust /// use lofty::id3::v2::Id3v2Tag; -/// use lofty::{Tag, TagType}; +/// use lofty::tag::{Tag, TagType}; /// /// // Converting between formats is as simple as an `into` call. /// // However, such conversions can potentially be *very* lossy. @@ -451,27 +461,38 @@ impl Tag { /// # Examples /// /// ```rust - /// use lofty::{Picture, Tag, TagType}; - /// # use lofty::{PictureType, MimeType}; + /// use lofty::picture::{MimeType, Picture, PictureType}; + /// use lofty::tag::{Tag, TagType}; /// - /// # let front_cover = Picture::new_unchecked(PictureType::CoverFront, Some(MimeType::Png), None, Vec::new()); - /// # let back_cover = Picture::new_unchecked(PictureType::CoverBack, Some(MimeType::Png), None, Vec::new()); - /// # let another_picture = Picture::new_unchecked(PictureType::Band, Some(MimeType::Png), None, Vec::new()); /// let mut tag = Tag::new(TagType::Id3v2); /// /// // Add a front cover + /// let front_cover = Picture::new_unchecked( + /// PictureType::CoverFront, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); /// tag.push_picture(front_cover); /// /// assert_eq!(tag.pictures().len(), 1); /// assert_eq!(tag.pictures()[0].pic_type(), PictureType::CoverFront); /// /// // Replace the front cover with a back cover + /// let back_cover = Picture::new_unchecked( + /// PictureType::CoverBack, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); /// tag.set_picture(0, back_cover); /// /// assert_eq!(tag.pictures().len(), 1); /// assert_eq!(tag.pictures()[0].pic_type(), PictureType::CoverBack); /// /// // Use an out of bounds index + /// let another_picture = + /// Picture::new_unchecked(PictureType::Band, Some(MimeType::Png), None, Vec::new()); /// tag.set_picture(100, another_picture); /// /// assert_eq!(tag.pictures().len(), 2); @@ -493,11 +514,18 @@ impl Tag { /// # Examples /// /// ```rust - /// use lofty::{Picture, Tag, TagType}; - /// # use lofty::{PictureType, MimeType}; + /// use lofty::picture::{MimeType, Picture, PictureType}; + /// use lofty::tag::{Tag, TagType}; /// - /// # let picture = Picture::new_unchecked(PictureType::CoverFront, Some(MimeType::Png), None, Vec::new()); /// let mut tag = Tag::new(TagType::Id3v2); + /// + /// let picture = Picture::new_unchecked( + /// PictureType::CoverFront, + /// Some(MimeType::Png), + /// None, + /// Vec::new(), + /// ); + /// /// tag.push_picture(picture); /// /// assert_eq!(tag.pictures().len(), 1); @@ -531,8 +559,8 @@ impl TagExt for Tag { /// /// # Errors /// - /// * A [`FileType`](crate::FileType) couldn't be determined from the File - /// * Attempting to write a tag to a format that does not support it. See [`FileType::supports_tag_type`](crate::FileType::supports_tag_type) + /// * A [`FileType`](crate::file::FileType) couldn't be determined from the File + /// * Attempting to write a tag to a format that does not support it. See [`FileType::supports_tag_type`](crate::file::FileType::supports_tag_type) fn save_to( &self, file: &mut File, @@ -581,6 +609,7 @@ impl TagExt for Tag { } #[derive(Debug, Clone, Default)] +#[allow(missing_docs)] pub struct SplitTagRemainder; impl SplitTag for Tag { @@ -599,72 +628,15 @@ impl MergeTag for SplitTagRemainder { } } -/// The tag's format -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum TagType { - /// This covers both APEv1 and APEv2 as it doesn't matter much - Ape, - /// Represents an ID3v1 tag - Id3v1, - /// This covers all ID3v2 versions since they all get upgraded to ID3v2.4 - Id3v2, - /// Represents an MP4 ilst atom - Mp4Ilst, - /// Represents vorbis comments - VorbisComments, - /// Represents a RIFF INFO LIST - RiffInfo, - /// Represents AIFF text chunks - AiffText, -} - -impl TagType { - /// Remove a tag from a [`Path`] - /// - /// # Errors - /// - /// See [`TagType::remove_from`] - pub fn remove_from_path(&self, path: impl AsRef) -> Result<()> { - let mut file = OpenOptions::new().read(true).write(true).open(path)?; - self.remove_from(&mut file) - } - - #[allow(clippy::shadow_unrelated)] - /// Remove a tag from a [`File`] - /// - /// # Errors - /// - /// * It is unable to guess the file format - /// * The format doesn't support the tag - /// * It is unable to write to the file - pub fn remove_from(&self, file: &mut File) -> Result<()> { - let probe = Probe::new(file).guess_file_type()?; - let Some(file_type) = probe.file_type() else { - err!(UnknownFormat); - }; - - // TODO: This should not have to be manually updated - let special_exceptions = ((file_type == FileType::Ape - || file_type == FileType::Mpc - || file_type == FileType::Flac) - && *self == TagType::Id3v2) - || file_type == FileType::Mpc && *self == TagType::Id3v1; - - if !special_exceptions && !file_type.supports_tag_type(*self) { - err!(UnsupportedTag); - } - - let file = probe.into_inner(); - utils::write_tag(&Tag::new(*self), file, file_type, WriteOptions::default()) // TODO - } -} - #[cfg(test)] mod tests { use super::try_parse_year; + use crate::config::WriteOptions; + use crate::picture::{Picture, PictureType}; + use crate::prelude::*; use crate::tag::utils::test_utils::read_path; - use crate::{Accessor, Picture, PictureType, Tag, TagExt, TagType, WriteOptions}; + use crate::tag::{Tag, TagType}; + use std::io::{Seek, Write}; use std::process::Command; diff --git a/src/tag/split_merge_tag.rs b/src/tag/split_merge_tag.rs new file mode 100644 index 000000000..80804a66d --- /dev/null +++ b/src/tag/split_merge_tag.rs @@ -0,0 +1,68 @@ +use super::Tag; + +/// Split (and merge) tags. +/// +/// Useful and required for implementing lossless read/modify/write round trips. +/// Its counterpart `MergeTag` is used for recombining the results later. +/// +/// # Example +/// +/// ```rust,no_run +/// use lofty::config::{ParseOptions, WriteOptions}; +/// use lofty::mpeg::MpegFile; +/// use lofty::prelude::*; +/// +/// // Read the tag from a file +/// # fn main() -> lofty::error::Result<()> { +/// # let mut file = std::fs::OpenOptions::new().write(true).open("/path/to/file.mp3")?; +/// # let parse_options = ParseOptions::default(); +/// let mut mpeg_file = ::read_from(&mut file, parse_options)?; +/// let mut id3v2 = mpeg_file +/// .id3v2_mut() +/// .map(std::mem::take) +/// .unwrap_or_default(); +/// +/// // Split: ID3v2 -> [`lofty::Tag`] +/// let (mut remainder, mut tag) = id3v2.split_tag(); +/// +/// // Modify the metadata in the generic [`lofty::Tag`], independent +/// // of the underlying tag and file format. +/// tag.insert_text(ItemKey::TrackTitle, "Track Title".to_owned()); +/// tag.remove_key(&ItemKey::Composer); +/// +/// // ID3v2 <- [`lofty::Tag`] +/// let id3v2 = remainder.merge_tag(tag); +/// +/// // Write the changes back into the file +/// mpeg_file.set_id3v2(id3v2); +/// mpeg_file.save_to(&mut file, WriteOptions::default())?; +/// +/// # Ok(()) } +/// ``` +pub trait SplitTag { + /// The remainder of the split operation that is not represented + /// in the resulting `Tag`. + type Remainder: MergeTag; + + /// Extract and split generic contents into a [`Tag`]. + /// + /// Returns the remaining content that cannot be represented in the + /// resulting `Tag` in `Self::Remainder`. This is useful if the + /// modified [`Tag`] is merged later using [`MergeTag::merge_tag`]. + fn split_tag(self) -> (Self::Remainder, Tag); +} + +/// The counterpart of [`SplitTag`]. +pub trait MergeTag { + /// The resulting tag. + type Merged: SplitTag; + + /// Merge a generic [`Tag`] back into the remainder of [`SplitTag::split_tag`]. + /// + /// Restores the original representation merged with the contents of + /// `tag` for further processing, e.g. writing back into a file. + /// + /// Multi-valued items in `tag` with identical keys might get lost + /// depending on the support for multi-valued fields in `self`. + fn merge_tag(self, tag: Tag) -> Self::Merged; +} diff --git a/src/tag/tag_ext.rs b/src/tag/tag_ext.rs new file mode 100644 index 000000000..0ebb55c04 --- /dev/null +++ b/src/tag/tag_ext.rs @@ -0,0 +1,134 @@ +use crate::config::WriteOptions; +use crate::tag::{Accessor, Tag}; + +use std::fs::File; +use std::path::Path; + +/// A set of common methods between tags +/// +/// This provides a set of methods to make interaction with all tags a similar +/// experience. +/// +/// This can be implemented downstream to provide a familiar interface for custom tags. +pub trait TagExt: Accessor + Into + Sized { + /// The associated error which can be returned from IO operations + type Err: From; + /// The type of key used in the tag for non-mutating functions + type RefKey<'a> + where + Self: 'a; + + /// Returns the number of items in the tag + /// + /// This will also include any extras, such as pictures. + /// + /// # Example + /// + /// ```rust + /// use lofty::tag::{Accessor, ItemKey, Tag, TagExt}; + /// # let tag_type = lofty::tag::TagType::Id3v2; + /// + /// let mut tag = Tag::new(tag_type); + /// assert_eq!(tag.len(), 0); + /// + /// tag.set_artist(String::from("Foo artist")); + /// assert_eq!(tag.len(), 1); + /// ``` + fn len(&self) -> usize; + + /// Whether the tag contains an item with the key + /// + /// # Example + /// + /// ```rust + /// use lofty::tag::{Accessor, ItemKey, Tag, TagExt}; + /// # let tag_type = lofty::tag::TagType::Id3v2; + /// + /// let mut tag = Tag::new(tag_type); + /// assert!(tag.is_empty()); + /// + /// tag.set_artist(String::from("Foo artist")); + /// assert!(tag.contains(&ItemKey::TrackArtist)); + /// ``` + fn contains<'a>(&'a self, key: Self::RefKey<'a>) -> bool; + + /// Whether the tag has any items + /// + /// # Example + /// + /// ```rust + /// use lofty::tag::{Accessor, Tag, TagExt}; + /// # let tag_type = lofty::tag::TagType::Id3v2; + /// + /// let mut tag = Tag::new(tag_type); + /// assert!(tag.is_empty()); + /// + /// tag.set_artist(String::from("Foo artist")); + /// assert!(!tag.is_empty()); + /// ``` + fn is_empty(&self) -> bool; + + /// Save the tag to a path + /// + /// # Errors + /// + /// * Path doesn't exist + /// * Path is not writable + /// * See [`TagExt::save_to`] + fn save_to_path>( + &self, + path: P, + write_options: WriteOptions, + ) -> std::result::Result<(), Self::Err> { + self.save_to( + &mut std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(path)?, + write_options, + ) + } + + /// Save the tag to a [`File`] + /// + /// # Errors + /// + /// * The file format could not be determined + /// * Attempting to write a tag to a format that does not support it. + fn save_to( + &self, + file: &mut File, + write_options: WriteOptions, + ) -> std::result::Result<(), Self::Err>; + + #[allow(clippy::missing_errors_doc)] + /// Dump the tag to a writer + /// + /// This will only write the tag, it will not produce a usable file. + fn dump_to( + &self, + writer: &mut W, + write_options: WriteOptions, + ) -> std::result::Result<(), Self::Err>; + + /// Remove a tag from a [`Path`] + /// + /// # Errors + /// + /// See [`TagExt::remove_from`] + fn remove_from_path>(&self, path: P) -> std::result::Result<(), Self::Err>; + + /// Remove a tag from a [`File`] + /// + /// # Errors + /// + /// * It is unable to guess the file format + /// * The format doesn't support the tag + /// * It is unable to write to the file + fn remove_from(&self, file: &mut File) -> std::result::Result<(), Self::Err>; + + /// Clear the tag, removing all items + /// + /// NOTE: This will **not** remove any format-specific extras, such as flags + fn clear(&mut self); +} diff --git a/src/tag/tag_type.rs b/src/tag/tag_type.rs new file mode 100644 index 000000000..628779a82 --- /dev/null +++ b/src/tag/tag_type.rs @@ -0,0 +1,69 @@ +use super::{utils, Tag}; +use crate::config::WriteOptions; +use crate::file::FileType; +use crate::macros::err; +use crate::probe::Probe; + +use std::fs::{File, OpenOptions}; +use std::path::Path; + +/// The tag's format +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum TagType { + /// This covers both APEv1 and APEv2 as it doesn't matter much + Ape, + /// Represents an ID3v1 tag + Id3v1, + /// This covers all ID3v2 versions since they all get upgraded to ID3v2.4 + Id3v2, + /// Represents an MP4 ilst atom + Mp4Ilst, + /// Represents vorbis comments + VorbisComments, + /// Represents a RIFF INFO LIST + RiffInfo, + /// Represents AIFF text chunks + AiffText, +} + +impl TagType { + /// Remove a tag from a [`Path`] + /// + /// # Errors + /// + /// See [`TagType::remove_from`] + pub fn remove_from_path(&self, path: impl AsRef) -> crate::error::Result<()> { + let mut file = OpenOptions::new().read(true).write(true).open(path)?; + self.remove_from(&mut file) + } + + #[allow(clippy::shadow_unrelated)] + /// Remove a tag from a [`File`] + /// + /// # Errors + /// + /// * It is unable to guess the file format + /// * The format doesn't support the tag + /// * It is unable to write to the file + pub fn remove_from(&self, file: &mut File) -> crate::error::Result<()> { + let probe = Probe::new(file).guess_file_type()?; + let Some(file_type) = probe.file_type() else { + err!(UnknownFormat); + }; + + // TODO: This should not have to be manually updated + let special_exceptions = ((file_type == FileType::Ape + || file_type == FileType::Mpc + || file_type == FileType::Flac) + && *self == TagType::Id3v2) + || file_type == FileType::Mpc && *self == TagType::Id3v1; + + if !special_exceptions && !file_type.supports_tag_type(*self) { + err!(UnsupportedTag); + } + + let file = probe.into_inner(); + utils::write_tag(&Tag::new(*self), file, file_type, WriteOptions::default()) // TODO + } +} diff --git a/src/tag/utils.rs b/src/tag/utils.rs index 9ecc7c279..2683764e0 100644 --- a/src/tag/utils.rs +++ b/src/tag/utils.rs @@ -1,8 +1,8 @@ +use crate::config::WriteOptions; use crate::error::Result; use crate::file::FileType; use crate::macros::err; use crate::tag::{Tag, TagType}; -use crate::write_options::WriteOptions; use crate::{aac, ape, flac, iff, mpeg, musepack, wavpack}; use crate::id3::v1::tag::Id3v1TagRef; @@ -99,7 +99,7 @@ pub(crate) fn dump_tag( #[cfg(test)] // Used for tag conversion tests pub(crate) mod test_utils { - use crate::{ItemKey, Tag, TagType}; + use crate::tag::{ItemKey, Tag, TagType}; use std::fs::File; use std::io::Read; diff --git a/src/traits.rs b/src/traits.rs deleted file mode 100644 index ec9e66641..000000000 --- a/src/traits.rs +++ /dev/null @@ -1,336 +0,0 @@ -use std::borrow::Cow; - -// This defines the `Accessor` trait, used to define unified getters/setters for commonly -// accessed tag values. -// -// Usage: -// -// accessor_trait! { -// [field_name] -// } -// -// * `field_name` is the name of the method to access the field. If a name consists of multiple segments, -// such as `track_number`, they should be separated by spaces like so: [track number]. -// -// * `type` is the return type for `Accessor::field_name`. By default, this type will also be used -// in the setter. -// -// An owned type can also be specified for the setter: -// -// accessor_trait! { -// field_name -// } -macro_rules! accessor_trait { - ($([$($name:tt)+] < $($ty:ty),+ >),+ $(,)?) => { - /// Provides accessors for common items - /// - /// This attempts to only provide methods for items that all tags have in common, - /// but there may be exceptions. - pub trait Accessor { - $( - accessor_trait! { @GETTER [$($name)+] $($ty),+ } - - accessor_trait! { @SETTER [$($name)+] $($ty),+ } - - accessor_trait! { @REMOVE [$($name)+] $($ty),+ } - )+ - } - }; - (@GETTER [$($name:tt)+] $ty:ty $(, $_ty:tt)?) => { - accessor_trait! { @GET_METHOD [$($name)+] Option<$ty> } - }; - (@SETTER [$($name:tt)+] $_ty:ty, $owned_ty:tt) => { - accessor_trait! { @SETTER [$($name)+] $owned_ty } - }; - (@SETTER [$($name:tt)+] $ty:ty) => { - accessor_trait! { @SET_METHOD [$($name)+] $ty } - }; - (@REMOVE [$($name:tt)+] $_ty:ty, $owned_ty:tt) => { - accessor_trait! { @REMOVE [$($name)+] $owned_ty } - }; - (@REMOVE [$($name:tt)+] $ty:ty) => { - accessor_trait! { @REMOVE_METHOD [$($name)+], $ty } - }; - (@GET_METHOD [$name:tt $($other:tt)*] Option<$ret_ty:ty>) => { - paste::paste! { - #[doc = "Returns the " $name $(" " $other)*] - /// # Example - /// - /// ```rust - /// use lofty::{Tag, Accessor}; - /// - /// # let tag_type = lofty::TagType::Id3v2; - /// let mut tag = Tag::new(tag_type); - #[doc = "assert_eq!(tag." $name $(_ $other)* "(), None);"] - /// ``` - fn [< - $name $(_ $other)* - >] (&self) -> Option<$ret_ty> { None } - } - }; - (@SET_METHOD [$name:tt $($other:tt)*] $owned_ty:ty) => { - paste::paste! { - #[doc = "Sets the " $name $(" " $other)*] - /// # Example - /// - /// ```rust,ignore - /// use lofty::{Tag, Accessor}; - /// - /// let mut tag = Tag::new(tag_type); - #[doc = "tag.set_" $name $(_ $other)* "(value);"] - /// - #[doc = "assert_eq!(tag." $name $(_ $other)* "(), Some(value));"] - /// ``` - fn [< - set_ $name $(_ $other)* - >] (&mut self , _value: $owned_ty) {} - } - }; - (@REMOVE_METHOD [$name:tt $($other:tt)*], $ty:ty) => { - paste::paste! { - #[doc = "Removes the " $name $(" " $other)*] - /// # Example - /// - /// ```rust,ignore - /// use lofty::{Tag, Accessor}; - /// - /// let mut tag = Tag::new(tag_type); - #[doc = "tag.set_" $name $(_ $other)* "(value);"] - /// - #[doc = "assert_eq!(tag." $name $(_ $other)* "(), Some(value));"] - /// - #[doc = "tag.remove_" $name $(_ $other)* "();"] - /// - #[doc = "assert_eq!(tag." $name $(_ $other)* "(), None);"] - /// ``` - fn [< - remove_ $name $(_ $other)* - >] (&mut self) {} - } - }; -} - -accessor_trait! { - [artist], String>, [title ], String>, - [album ], String>, [genre ], String>, - [track ], [track total], - [disk ], [disk total ], - [year ], [comment ], String>, -} - -use crate::tag::Tag; -use crate::write_options::WriteOptions; - -use std::fs::File; -use std::path::Path; - -/// A set of common methods between tags -/// -/// This provides a set of methods to make interaction with all tags a similar -/// experience. -/// -/// This can be implemented downstream to provide a familiar interface for custom tags. -pub trait TagExt: Accessor + Into + Sized { - /// The associated error which can be returned from IO operations - type Err: From; - /// The type of key used in the tag for non-mutating functions - type RefKey<'a> - where - Self: 'a; - - /// Returns the number of items in the tag - /// - /// This will also include any extras, such as pictures. - /// - /// # Example - /// - /// ```rust - /// use lofty::{Accessor, ItemKey, Tag, TagExt}; - /// # let tag_type = lofty::TagType::Id3v2; - /// - /// let mut tag = Tag::new(tag_type); - /// assert_eq!(tag.len(), 0); - /// - /// tag.set_artist(String::from("Foo artist")); - /// assert_eq!(tag.len(), 1); - /// ``` - fn len(&self) -> usize; - - /// Whether the tag contains an item with the key - /// - /// # Example - /// - /// ```rust - /// use lofty::{Accessor, ItemKey, Tag, TagExt}; - /// # let tag_type = lofty::TagType::Id3v2; - /// - /// let mut tag = Tag::new(tag_type); - /// assert!(tag.is_empty()); - /// - /// tag.set_artist(String::from("Foo artist")); - /// assert!(tag.contains(&ItemKey::TrackArtist)); - /// ``` - fn contains<'a>(&'a self, key: Self::RefKey<'a>) -> bool; - - /// Whether the tag has any items - /// - /// # Example - /// - /// ```rust - /// use lofty::{Accessor, Tag, TagExt}; - /// # let tag_type = lofty::TagType::Id3v2; - /// - /// let mut tag = Tag::new(tag_type); - /// assert!(tag.is_empty()); - /// - /// tag.set_artist(String::from("Foo artist")); - /// assert!(!tag.is_empty()); - /// ``` - fn is_empty(&self) -> bool; - - /// Save the tag to a path - /// - /// # Errors - /// - /// * Path doesn't exist - /// * Path is not writable - /// * See [`TagExt::save_to`] - fn save_to_path>( - &self, - path: P, - write_options: WriteOptions, - ) -> std::result::Result<(), Self::Err> { - self.save_to( - &mut std::fs::OpenOptions::new() - .read(true) - .write(true) - .open(path)?, - write_options, - ) - } - - /// Save the tag to a [`File`] - /// - /// # Errors - /// - /// * The file format could not be determined - /// * Attempting to write a tag to a format that does not support it. - fn save_to( - &self, - file: &mut File, - write_options: WriteOptions, - ) -> std::result::Result<(), Self::Err>; - - #[allow(clippy::missing_errors_doc)] - /// Dump the tag to a writer - /// - /// This will only write the tag, it will not produce a usable file. - fn dump_to( - &self, - writer: &mut W, - write_options: WriteOptions, - ) -> std::result::Result<(), Self::Err>; - - /// Remove a tag from a [`Path`] - /// - /// # Errors - /// - /// See [`TagExt::remove_from`] - fn remove_from_path>(&self, path: P) -> std::result::Result<(), Self::Err>; - - /// Remove a tag from a [`File`] - /// - /// # Errors - /// - /// * It is unable to guess the file format - /// * The format doesn't support the tag - /// * It is unable to write to the file - fn remove_from(&self, file: &mut File) -> std::result::Result<(), Self::Err>; - - /// Clear the tag, removing all items - /// - /// NOTE: This will **not** remove any format-specific extras, such as flags - fn clear(&mut self); -} - -/// Split (and merge) tags. -/// -/// Useful and required for implementing lossless read/modify/write round trips. -/// Its counterpart `MergeTag` is used for recombining the results later. -/// -/// # Example -/// -/// ```rust,no_run -/// use lofty::mpeg::MpegFile; -/// use lofty::{AudioFile, ItemKey, MergeTag as _, SplitTag as _, WriteOptions}; -/// -/// // Read the tag from a file -/// # fn main() -> lofty::Result<()> { -/// # let mut file = std::fs::OpenOptions::new().write(true).open("/path/to/file.mp3")?; -/// # let parse_options = lofty::ParseOptions::default(); -/// let mut mpeg_file = ::read_from(&mut file, parse_options)?; -/// let mut id3v2 = mpeg_file -/// .id3v2_mut() -/// .map(std::mem::take) -/// .unwrap_or_default(); -/// -/// // Split: ID3v2 -> [`lofty::Tag`] -/// let (mut remainder, mut tag) = id3v2.split_tag(); -/// -/// // Modify the metadata in the generic [`lofty::Tag`], independent -/// // of the underlying tag and file format. -/// tag.insert_text(ItemKey::TrackTitle, "Track Title".to_owned()); -/// tag.remove_key(&ItemKey::Composer); -/// -/// // ID3v2 <- [`lofty::Tag`] -/// let id3v2 = remainder.merge_tag(tag); -/// -/// // Write the changes back into the file -/// mpeg_file.set_id3v2(id3v2); -/// mpeg_file.save_to(&mut file, WriteOptions::default())?; -/// -/// # Ok::<(), lofty::LoftyError>(()) } -/// ``` -pub trait SplitTag { - /// The remainder of the split operation that is not represented - /// in the resulting `Tag`. - type Remainder: MergeTag; - - /// Extract and split generic contents into a [`Tag`]. - /// - /// Returns the remaining content that cannot be represented in the - /// resulting `Tag` in `Self::Remainder`. This is useful if the - /// modified [`Tag`] is merged later using [`MergeTag::merge_tag`]. - fn split_tag(self) -> (Self::Remainder, Tag); -} - -/// The counterpart of [`SplitTag`]. -pub trait MergeTag { - /// The resulting tag. - type Merged: SplitTag; - - /// Merge a generic [`Tag`] back into the remainder of [`SplitTag::split_tag`]. - /// - /// Restores the original representation merged with the contents of - /// `tag` for further processing, e.g. writing back into a file. - /// - /// Multi-valued items in `tag` with identical keys might get lost - /// depending on the support for multi-valued fields in `self`. - fn merge_tag(self, tag: Tag) -> Self::Merged; -} - -// TODO: https://github.com/rust-lang/rust/issues/59359 -pub(crate) trait SeekStreamLen: std::io::Seek { - fn stream_len(&mut self) -> crate::error::Result { - use std::io::SeekFrom; - - let current_pos = self.stream_position()?; - let len = self.seek(SeekFrom::End(0))?; - - self.seek(SeekFrom::Start(current_pos))?; - - Ok(len) - } -} - -impl SeekStreamLen for T where T: std::io::Seek {} diff --git a/src/util/alloc.rs b/src/util/alloc.rs index 0fe53c912..42fd6b1db 100644 --- a/src/util/alloc.rs +++ b/src/util/alloc.rs @@ -1,7 +1,7 @@ use crate::error::Result; use crate::macros::err; -use crate::global_options::global_options; +use crate::config::global_options; /// Provides the `fallible_repeat` method on `Vec` /// diff --git a/src/util/io.rs b/src/util/io.rs new file mode 100644 index 000000000..3a2946c6d --- /dev/null +++ b/src/util/io.rs @@ -0,0 +1,15 @@ +// TODO: https://github.com/rust-lang/rust/issues/59359 +pub(crate) trait SeekStreamLen: std::io::Seek { + fn stream_len_hack(&mut self) -> crate::error::Result { + use std::io::SeekFrom; + + let current_pos = self.stream_position()?; + let len = self.seek(SeekFrom::End(0))?; + + self.seek(SeekFrom::Start(current_pos))?; + + Ok(len) + } +} + +impl SeekStreamLen for T where T: std::io::Seek {} diff --git a/src/math.rs b/src/util/math.rs similarity index 100% rename from src/math.rs rename to src/util/math.rs diff --git a/src/util/mod.rs b/src/util/mod.rs index b29ec85ca..009c84209 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,2 +1,4 @@ pub(crate) mod alloc; +pub(crate) mod io; +pub(crate) mod math; pub(crate) mod text; diff --git a/src/wavpack/properties.rs b/src/wavpack/properties.rs index 1f01799b4..5eb5792cd 100644 --- a/src/wavpack/properties.rs +++ b/src/wavpack/properties.rs @@ -1,6 +1,6 @@ +use crate::config::ParsingMode; use crate::error::Result; use crate::macros::{decode_err, err, parse_mode_choice, try_vec}; -use crate::probe::ParsingMode; use crate::properties::{ChannelMask, FileProperties}; use std::io::{Read, Seek, SeekFrom}; diff --git a/src/wavpack/read.rs b/src/wavpack/read.rs index 2d402fe20..b2ab8817f 100644 --- a/src/wavpack/read.rs +++ b/src/wavpack/read.rs @@ -1,8 +1,8 @@ use super::properties::WavPackProperties; use super::WavPackFile; +use crate::config::ParseOptions; use crate::error::Result; use crate::id3::{find_id3v1, find_lyrics3v2, ID3FindResults}; -use crate::probe::ParseOptions; use std::io::{Read, Seek, SeekFrom}; diff --git a/tests/files/aac.rs b/tests/files/aac.rs index 5af2445a3..7d42b0bc3 100644 --- a/tests/files/aac.rs +++ b/tests/files/aac.rs @@ -1,8 +1,10 @@ use crate::{set_artist, temp_file, verify_artist}; -use lofty::{ - Accessor, FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, - TaggedFileExt, -}; +use lofty::config::ParseOptions; +use lofty::file::FileType; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; #[test] diff --git a/tests/files/aiff.rs b/tests/files/aiff.rs index df459f375..de7e9686b 100644 --- a/tests/files/aiff.rs +++ b/tests/files/aiff.rs @@ -1,7 +1,10 @@ use crate::{set_artist, temp_file, verify_artist}; -use lofty::{ - FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, TaggedFileExt, -}; +use lofty::config::ParseOptions; +use lofty::file::FileType; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; #[test] diff --git a/tests/files/ape.rs b/tests/files/ape.rs index 502189f8d..3af288013 100644 --- a/tests/files/ape.rs +++ b/tests/files/ape.rs @@ -1,7 +1,10 @@ use crate::{set_artist, temp_file, verify_artist}; -use lofty::{ - FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, TaggedFileExt, -}; +use lofty::config::ParseOptions; +use lofty::file::FileType; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; #[test] diff --git a/tests/files/flac.rs b/tests/files/flac.rs index 6d17b6286..a9401280d 100644 --- a/tests/files/flac.rs +++ b/tests/files/flac.rs @@ -1,5 +1,6 @@ +use lofty::config::{ParseOptions, ParsingMode}; use lofty::flac::FlacFile; -use lofty::{Accessor, AudioFile, ParseOptions, ParsingMode}; +use lofty::prelude::*; use std::fs::File; use std::io::Seek; diff --git a/tests/files/mp4.rs b/tests/files/mp4.rs index 946a6af6e..0020f675c 100644 --- a/tests/files/mp4.rs +++ b/tests/files/mp4.rs @@ -1,7 +1,10 @@ use crate::{set_artist, temp_file, verify_artist}; -use lofty::{ - FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, TaggedFileExt, -}; +use lofty::config::ParseOptions; +use lofty::file::FileType; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; #[test] diff --git a/tests/files/mpc.rs b/tests/files/mpc.rs index 36484fa16..c9ab4466e 100644 --- a/tests/files/mpc.rs +++ b/tests/files/mpc.rs @@ -1,9 +1,11 @@ use crate::{set_artist, temp_file, verify_artist}; +use lofty::config::ParseOptions; +use lofty::file::{FileType, TaggedFile}; use lofty::musepack::MpcFile; -use lofty::{ - AudioFile, FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, - TaggedFile, TaggedFileExt, -}; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; // Marker test so IntelliJ Rust recognizes this as a test module diff --git a/tests/files/mpeg.rs b/tests/files/mpeg.rs index 9bc9aa267..a72fc3b72 100644 --- a/tests/files/mpeg.rs +++ b/tests/files/mpeg.rs @@ -1,12 +1,13 @@ use crate::{set_artist, temp_file, verify_artist}; -use std::borrow::Cow; - +use lofty::config::{ParseOptions, WriteOptions}; +use lofty::file::FileType; use lofty::id3::v2::{Frame, FrameFlags, FrameId, FrameValue, Id3v2Tag, KeyValueFrame}; use lofty::mpeg::MpegFile; -use lofty::{ - Accessor, AudioFile, FileType, ItemKey, ItemValue, ParseOptions, Probe, Tag, TagExt, TagItem, - TagType, TaggedFileExt, WriteOptions, -}; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::{Tag, TagType}; + +use std::borrow::Cow; use std::io::{Seek, Write}; #[test] diff --git a/tests/files/ogg.rs b/tests/files/ogg.rs index b6c808f9a..1f002f09c 100644 --- a/tests/files/ogg.rs +++ b/tests/files/ogg.rs @@ -1,8 +1,10 @@ use crate::{set_artist, temp_file, verify_artist}; -use lofty::{ - FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, TaggedFileExt, - WriteOptions, -}; +use lofty::config::{ParseOptions, WriteOptions}; +use lofty::file::FileType; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; // The tests for OGG Opus/Vorbis are nearly identical @@ -151,7 +153,6 @@ fn remove(path: &str, tag_type: TagType) { #[test] fn flac_with_id3v2() { use lofty::flac::FlacFile; - use lofty::{Accessor, AudioFile}; let file = std::fs::read("tests/files/assets/flac_with_id3v2.flac").unwrap(); let flac_file = @@ -174,7 +175,6 @@ fn flac_remove_id3v2() { #[test] fn flac_try_write_non_empty_id3v2() { use lofty::id3::v2::Id3v2Tag; - use lofty::Accessor; let mut tag = Id3v2Tag::default(); tag.set_artist(String::from("Foo artist")); diff --git a/tests/files/util/mod.rs b/tests/files/util/mod.rs index 802612b66..72afe7694 100644 --- a/tests/files/util/mod.rs +++ b/tests/files/util/mod.rs @@ -30,10 +30,10 @@ macro_rules! verify_artist { assert_eq!(tag.item_count(), $item_count); assert_eq!( - tag.get(&ItemKey::TrackArtist), - Some(&TagItem::new( - ItemKey::TrackArtist, - ItemValue::Text(String::from($expected_value)) + tag.get(&lofty::prelude::ItemKey::TrackArtist), + Some(&lofty::tag::TagItem::new( + lofty::prelude::ItemKey::TrackArtist, + lofty::tag::ItemValue::Text(String::from($expected_value)) )) ); @@ -62,14 +62,14 @@ macro_rules! set_artist { set_artist!($file_write, $new_value, tag) }; ($file_write:ident, $new_value:literal, $tag:ident) => { - $tag.insert_unchecked(TagItem::new( - ItemKey::TrackArtist, - ItemValue::Text(String::from($new_value)), + $tag.insert_unchecked(lofty::tag::TagItem::new( + lofty::prelude::ItemKey::TrackArtist, + lofty::tag::ItemValue::Text(String::from($new_value)), )); $file_write.rewind().unwrap(); - $tag.save_to(&mut $file_write, lofty::WriteOptions::default()) + $tag.save_to(&mut $file_write, lofty::config::WriteOptions::default()) .unwrap(); }; } @@ -79,8 +79,8 @@ macro_rules! remove_tag { ($path:tt, $tag_type:path) => { let mut file = temp_file!($path); - let tagged_file = lofty::Probe::new(&mut file) - .options(lofty::ParseOptions::new().read_properties(false)) + let tagged_file = lofty::probe::Probe::new(&mut file) + .options(lofty::config::ParseOptions::new().read_properties(false)) .guess_file_type() .unwrap() .read() @@ -93,8 +93,8 @@ macro_rules! remove_tag { file.rewind().unwrap(); - let tagged_file = lofty::Probe::new(&mut file) - .options(lofty::ParseOptions::new().read_properties(false)) + let tagged_file = lofty::probe::Probe::new(&mut file) + .options(lofty::config::ParseOptions::new().read_properties(false)) .guess_file_type() .unwrap() .read() diff --git a/tests/files/wav.rs b/tests/files/wav.rs index 0bbf2422e..2f7b1eebd 100644 --- a/tests/files/wav.rs +++ b/tests/files/wav.rs @@ -1,7 +1,10 @@ use crate::{set_artist, temp_file, verify_artist}; -use lofty::{ - FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, TaggedFileExt, -}; +use lofty::config::ParseOptions; +use lofty::file::FileType; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; #[test] diff --git a/tests/files/wavpack.rs b/tests/files/wavpack.rs index ae4c3b504..8c6d81e72 100644 --- a/tests/files/wavpack.rs +++ b/tests/files/wavpack.rs @@ -1,7 +1,10 @@ use crate::{set_artist, temp_file, verify_artist}; -use lofty::{ - FileType, ItemKey, ItemValue, ParseOptions, Probe, TagExt, TagItem, TagType, TaggedFileExt, -}; +use lofty::config::ParseOptions; +use lofty::file::FileType; +use lofty::prelude::*; +use lofty::probe::Probe; +use lofty::tag::TagType; + use std::io::{Seek, Write}; #[test] diff --git a/tests/files/zero_sized.rs b/tests/files/zero_sized.rs index 876dd2e77..518945d12 100644 --- a/tests/files/zero_sized.rs +++ b/tests/files/zero_sized.rs @@ -1,10 +1,11 @@ use lofty::ape::ApeFile; +use lofty::config::{ParseOptions, ParsingMode}; use lofty::flac::FlacFile; use lofty::iff::aiff::AiffFile; use lofty::iff::wav::WavFile; use lofty::mp4::Mp4File; use lofty::mpeg::MpegFile; -use lofty::{AudioFile, ParseOptions, ParsingMode}; +use lofty::prelude::*; fn read_file_with_properties(path: &str) -> bool { let res = ::read_from( diff --git a/tests/fuzz/id3v2.rs b/tests/fuzz/id3v2.rs index f66b5c2e8..33cc7b1ca 100644 --- a/tests/fuzz/id3v2.rs +++ b/tests/fuzz/id3v2.rs @@ -18,7 +18,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/tests/fuzz/main.rs b/tests/fuzz/main.rs index 1404e48b4..4d0383b4c 100644 --- a/tests/fuzz/main.rs +++ b/tests/fuzz/main.rs @@ -1,4 +1,6 @@ -use lofty::{AudioFile, ParseOptions}; +use lofty::config::ParseOptions; +use lofty::prelude::*; + use std::io::Cursor; use std::path::Path; use std::thread; diff --git a/tests/fuzz/mpegfile_read_from.rs b/tests/fuzz/mpegfile_read_from.rs index 197f6e47e..ff9f89c71 100644 --- a/tests/fuzz/mpegfile_read_from.rs +++ b/tests/fuzz/mpegfile_read_from.rs @@ -1,6 +1,7 @@ use crate::{get_reader, oom_test}; +use lofty::config::ParseOptions; use lofty::mpeg::MpegFile; -use lofty::{AudioFile, ParseOptions}; +use lofty::prelude::*; #[test] fn crash1() { diff --git a/tests/fuzz/pictureinformation_from_jpeg.rs b/tests/fuzz/pictureinformation_from_jpeg.rs index 33a984675..90c6e3a21 100644 --- a/tests/fuzz/pictureinformation_from_jpeg.rs +++ b/tests/fuzz/pictureinformation_from_jpeg.rs @@ -1,6 +1,6 @@ use crate::get_reader; use lofty::error::ErrorKind; -use lofty::PictureInformation; +use lofty::picture::PictureInformation; #[test] fn crash1() { diff --git a/tests/fuzz/pictureinformation_from_png.rs b/tests/fuzz/pictureinformation_from_png.rs index 5d36086c9..7006765cb 100644 --- a/tests/fuzz/pictureinformation_from_png.rs +++ b/tests/fuzz/pictureinformation_from_png.rs @@ -1,5 +1,5 @@ use crate::get_reader; -use lofty::PictureInformation; +use lofty::picture::PictureInformation; #[test] fn crash1() { diff --git a/tests/hound.rs b/tests/hound.rs index 2f8dd5d26..83846c961 100644 --- a/tests/hound.rs +++ b/tests/hound.rs @@ -1,11 +1,14 @@ +use lofty::config::ParseOptions; +use lofty::error::Result; +use lofty::iff::wav::WavFile; +use lofty::prelude::*; + +use hound::WavReader; + use std::fs; use std::fs::File; use std::path::Path; -use hound::WavReader; -use lofty::iff::wav::WavFile; -use lofty::{AudioFile, ParseOptions, Result}; - fn get_properties(path: &Path) -> Result<::Properties> { let mut f = File::open(path).unwrap(); let wav_file = WavFile::read_from(&mut f, ParseOptions::new())?; diff --git a/tests/picture/format_parsers.rs b/tests/picture/format_parsers.rs index b963c028c..b028fe31f 100644 --- a/tests/picture/format_parsers.rs +++ b/tests/picture/format_parsers.rs @@ -1,5 +1,7 @@ +use lofty::config::ParsingMode; use lofty::id3::v2::{AttachedPictureFrame, Id3v2Version}; -use lofty::{ParsingMode, Picture, PictureInformation, PictureType, TextEncoding}; +use lofty::picture::{Picture, PictureInformation, PictureType}; +use lofty::TextEncoding; use std::fs::File; use std::io::Read; diff --git a/tests/picture/from_reader.rs b/tests/picture/from_reader.rs index 56eaca2ae..9c7d5be70 100644 --- a/tests/picture/from_reader.rs +++ b/tests/picture/from_reader.rs @@ -1,4 +1,4 @@ -use lofty::{MimeType, Picture}; +use lofty::picture::{MimeType, Picture}; use std::fs::File; use std::io::Read; diff --git a/tests/picture/information.rs b/tests/picture/information.rs index 6750612da..f2619fcfe 100644 --- a/tests/picture/information.rs +++ b/tests/picture/information.rs @@ -1,4 +1,4 @@ -use lofty::PictureInformation; +use lofty::picture::PictureInformation; use std::fs::File; use std::io::Read; diff --git a/tests/tags/conversions.rs b/tests/tags/conversions.rs index b861b4548..c7e0f7b3f 100644 --- a/tests/tags/conversions.rs +++ b/tests/tags/conversions.rs @@ -1,7 +1,8 @@ // Tests for special case conversions use lofty::id3::v2::{CommentFrame, Frame, FrameFlags, FrameId, Id3v2Tag, UnsynchronizedTextFrame}; -use lofty::{ItemKey, Tag, TagType, TextEncoding}; +use lofty::tag::{ItemKey, Tag, TagType}; +use lofty::TextEncoding; use std::borrow::Cow; #[test]