Make things less confusing to use

This commit is contained in:
Serial 2021-04-22 18:01:09 -04:00
parent bd48066b64
commit 754bf03413
7 changed files with 79 additions and 77 deletions

View file

@ -1,12 +1,10 @@
use criterion::{criterion_group, criterion_main, Criterion};
use lofty::{DetermineFrom, Tag};
use lofty::Tag;
macro_rules! test_read {
($function:ident, $path:expr) => {
fn $function() {
let _ = Tag::new()
.read_from_path($path, DetermineFrom::Extension)
.unwrap();
let _ = Tag::new().read_from_path($path).unwrap();
}
};
}

View file

@ -1,12 +1,10 @@
use criterion::{criterion_group, criterion_main, Criterion};
use lofty::{DetermineFrom, Tag};
use lofty::Tag;
macro_rules! test_read {
($function:ident, $path:expr) => {
fn $function() {
let _ = Tag::new()
.read_from_path($path, DetermineFrom::Signature)
.unwrap();
let _ = Tag::new().read_from_path_signature($path).unwrap();
}
};
}

View file

@ -1,6 +1,6 @@
#![cfg(feature = "id3")]
use crate::tag::ID3Underlying;
use crate::tag::ID3Format;
use crate::{
impl_tag, Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, Error, MimeType, Picture,
Result, TagType, ToAny, ToAnyTag,
@ -16,30 +16,26 @@ use std::path::Path;
#[cfg(feature = "duration")]
use std::time::Duration;
impl_tag!(
Id3v2Tag,
Id3v2InnerTag,
TagType::Id3v2(ID3Underlying::Default)
);
impl_tag!(Id3v2Tag, Id3v2InnerTag, TagType::Id3v2(ID3Format::Default));
impl Id3v2Tag {
#[allow(clippy::missing_errors_doc)]
pub fn read_from_path<P>(path: P, format: ID3Underlying) -> Result<Self>
pub fn read_from_path<P>(path: P, format: ID3Format) -> Result<Self>
where
P: AsRef<Path>,
{
return match format {
ID3Underlying::Default => Ok(Self {
ID3Format::Default => Ok(Self {
inner: Id3v2InnerTag::read_from_path(&path)?,
#[cfg(feature = "duration")]
duration: Some(mp3_duration::from_path(&path)?),
}),
ID3Underlying::RIFF => Ok(Self {
ID3Format::RIFF => Ok(Self {
inner: Id3v2InnerTag::read_from_wav(&path)?,
#[cfg(feature = "duration")]
duration: None, // TODO
}),
ID3Underlying::Form => Ok(Self {
ID3Format::Form => Ok(Self {
inner: Id3v2InnerTag::read_from_aiff(&path)?,
#[cfg(feature = "duration")]
duration: None, // TODO

View file

@ -27,18 +27,22 @@
//! # Examples
//!
//! ```
//! use lofty::{Tag, TagType, DetermineFrom};
//! use lofty::{Tag, TagType};
//!
//! // Guess the format from the extension, in this case `mp3`
//! let mut tag = Tag::new().read_from_path("tests/assets/a.mp3", DetermineFrom::Extension).unwrap();
//! let mut tag = Tag::new().read_from_path("tests/assets/a.mp3").unwrap();
//! tag.set_title("Foo");
//!
//! // You can also guess the format from the file signature
//! let mut tag_sig = Tag::new().read_from_path_signature("tests/assets/a.wav").unwrap();
//! tag_sig.set_artist("Foo artist");
//!
//! // You can convert the tag type and save the metadata to another file.
//! tag.to_dyn_tag(TagType::Mp4).write_to_path("tests/assets/a.m4a");
//!
//! // You can specify the tag type, but when you want to do this
//! // also consider directly using the concrete type
//! let tag = Tag::new().with_tag_type(TagType::Mp4).read_from_path("tests/assets/a.m4a", DetermineFrom::Extension).unwrap();
//! let tag = Tag::new().with_tag_type(TagType::Mp4).read_from_path("tests/assets/a.m4a").unwrap();
//! assert_eq!(tag.title(), Some("Foo"));
//! ```
//!
@ -89,7 +93,7 @@ pub use crate::types::{
};
mod tag;
pub use crate::tag::{DetermineFrom, ID3Underlying, Tag, TagType, VorbisFormat};
pub use crate::tag::{ID3Format, Tag, TagType, VorbisFormat};
mod error;
pub use crate::error::{Error, Result};

View file

@ -27,14 +27,6 @@ const RIFF: [u8; 4] = [82, 73, 70, 70];
#[derive(Default)]
pub struct Tag(Option<TagType>);
/// Used in Tag::read_from_path to choose the method to determine the tag type
pub enum DetermineFrom {
/// Determine the format from the file extension
Extension,
/// Determine the format by reading the file, and matching the signature
Signature,
}
impl Tag {
/// Initiate a new Tag
pub fn new() -> Self {
@ -47,36 +39,56 @@ impl Tag {
Self(Some(tag_type))
}
/// Path of the file to read, and the method to determine the tag type
/// Attempts to get the tag format based on the file extension
///
/// NOTE: Since this only looks at the extension, the result could be incorrect.
///
///
/// # Errors
///
/// * `path` either has no extension, or the extension is not valid unicode (DetermineFrom::Extension)
/// * `path` has an unsupported/unknown extension (DetermineFrom::Extension)
/// * `path` does not exist (DetermineFrom::Signature)
/// * `path` either has no extension, or the extension is not valid unicode
/// * `path` has an unsupported/unknown extension
///
/// # Warning
/// Using DetermineFrom::Extension on a `wav`/`wave` file will **always** assume there's an ID3 tag.
/// DetermineFrom::Signature is recommended instead, in the event that a RIFF INFO list is present instead.
/// However, if both are present, only the ID3 tag is read.
pub fn read_from_path(
&self,
path: impl AsRef<Path>,
method: DetermineFrom,
) -> Result<Box<dyn AudioTag>> {
let tag_type = match method {
DetermineFrom::Extension => {
let extension = path
.as_ref()
.extension()
.ok_or(Error::UnknownFileExtension)?;
let extension_str = extension.to_str().ok_or(Error::UnknownFileExtension)?;
/// Using this on a `wav`/`wave`/`riff` file will **always** assume there's an ID3 tag.
/// [`read_from_path_signature`](Tag::read_from_path_signature) is recommended, in the event that a RIFF INFO list is present instead.
pub fn read_from_path(&self, path: impl AsRef<Path>) -> Result<Box<dyn AudioTag>> {
let tag_type = self.0.clone().unwrap_or({
let extension = path
.as_ref()
.extension()
.ok_or(Error::UnknownFileExtension)?;
let extension_str = extension.to_str().ok_or(Error::UnknownFileExtension)?;
TagType::try_from_ext(extension_str)?
},
DetermineFrom::Signature => TagType::try_from_sig(&std::fs::read(path.as_ref())?)?,
};
TagType::try_from_ext(extension_str)?
});
Self::match_tag(path, tag_type)
}
/// Attempts to get the tag format based on the file signature
///
/// NOTE: This is *slightly* slower than reading from extension, but more accurate.
/// The only times were this would really be necessary is if the file format being read
/// supports more than one metadata format (ex. RIFF), or there is no file extension.
///
/// # Errors
///
/// * `path` does not exist
/// * The tag is non-existent/invalid/unknown
///
/// # Warning
/// In the event that a riff file contains both an ID3 tag *and* a RIFF INFO chunk, the ID3 tag will **always** be chosen.
pub fn read_from_path_signature(&self, path: impl AsRef<Path>) -> Result<Box<dyn AudioTag>> {
let tag_type = self
.0
.clone()
.unwrap_or(TagType::try_from_sig(&std::fs::read(path.as_ref())?)?);
Self::match_tag(path, tag_type)
}
fn match_tag(path: impl AsRef<Path>, tag_type: TagType) -> Result<Box<dyn AudioTag>> {
match tag_type {
#[cfg(feature = "ape")]
TagType::Ape => Ok(Box::new(ApeTag::read_from_path(path)?)),
@ -99,13 +111,13 @@ pub enum TagType {
/// Common file extensions: `.ape`
Ape,
#[cfg(feature = "id3")]
/// Represents multiple formats, see [`ID3Format`] for extensions.
Id3v2(ID3Underlying),
/// Represents multiple formats, see [`ID3Format`](ID3Format) for extensions.
Id3v2(ID3Format),
#[cfg(feature = "mp4")]
/// Common file extensions: `.mp4, .m4a, .m4p, .m4b, .m4r, .m4v`
Mp4,
#[cfg(any(feature = "vorbis", feature = "opus", feature = "flac"))]
/// Represents multiple formats, see [`VorbisFormat`] for extensions.
/// Represents multiple formats, see [`VorbisFormat`](VorbisFormat) for extensions.
Vorbis(VorbisFormat),
#[cfg(feature = "riff")]
/// Metadata stored in a RIFF INFO chunk
@ -131,12 +143,12 @@ pub enum VorbisFormat {
#[derive(Clone, Debug, PartialEq)]
#[cfg(feature = "id3")]
/// ID3 tag's underlying format
pub enum ID3Underlying {
pub enum ID3Format {
/// MP3
Default,
/// AIFF
Form,
/// WAV/WAVE
/// RIFF/WAV/WAVE
RIFF,
}
@ -146,11 +158,11 @@ impl TagType {
#[cfg(feature = "ape")]
"ape" => Ok(Self::Ape),
#[cfg(feature = "id3")]
"aiff" | "aif" => Ok(Self::Id3v2(ID3Underlying::Form)),
"aiff" | "aif" => Ok(Self::Id3v2(ID3Format::Form)),
#[cfg(feature = "id3")]
"mp3" => Ok(Self::Id3v2(ID3Underlying::Default)),
"mp3" => Ok(Self::Id3v2(ID3Format::Default)),
#[cfg(all(feature = "riff", feature = "id3"))]
"wav" | "wave" | "riff" => Ok(Self::Id3v2(ID3Underlying::RIFF)),
"wav" | "wave" | "riff" => Ok(Self::Id3v2(ID3Format::RIFF)),
#[cfg(feature = "opus")]
"opus" => Ok(Self::Vorbis(VorbisFormat::Opus)),
#[cfg(feature = "flac")]
@ -171,7 +183,7 @@ impl TagType {
#[cfg(feature = "ape")]
77 if data.starts_with(&MAC) => Ok(Self::Ape),
#[cfg(feature = "id3")]
73 if data.starts_with(&ID3) => Ok(Self::Id3v2(ID3Underlying::Default)),
73 if data.starts_with(&ID3) => Ok(Self::Id3v2(ID3Format::Default)),
#[cfg(feature = "id3")]
70 if data.starts_with(&FORM) => {
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
@ -204,7 +216,7 @@ impl TagType {
}
if found_id3 {
return Ok(Self::Id3v2(ID3Underlying::Form));
return Ok(Self::Id3v2(ID3Format::Form));
}
// TODO: support AIFF chunks?
@ -252,7 +264,7 @@ impl TagType {
}
if found_id3 {
return Ok(Self::Id3v2(ID3Underlying::RIFF));
return Ok(Self::Id3v2(ID3Format::RIFF));
}
}

View file

@ -1,4 +1,4 @@
use lofty::{DetermineFrom, ID3Underlying, Tag, TagType, ToAnyTag, VorbisTag};
use lofty::{ID3Format, Tag, TagType, ToAnyTag, VorbisTag};
#[test]
#[cfg(all(feature = "id3", feature = "flac"))]
@ -15,7 +15,7 @@ fn test_inner() {
let tag: VorbisTag = innertag.into();
// Turn the VorbisTag into a Box<dyn AudioTag>
let id3tag = tag.to_dyn_tag(TagType::Id3v2(ID3Underlying::Default));
let id3tag = tag.to_dyn_tag(TagType::Id3v2(ID3Format::Default));
// Write Box<dyn AudioTag> to `a.mp3`
id3tag
@ -24,7 +24,7 @@ fn test_inner() {
// Read from `a.mp3`
let id3tag_reload = Tag::default()
.read_from_path("tests/assets/a.mp3", DetermineFrom::Extension)
.read_from_path("tests/assets/a.mp3")
.expect("Fail to read!");
// Confirm title still matches

View file

@ -1,5 +1,5 @@
#![cfg(feature = "default")]
use lofty::{DetermineFrom, MimeType, Picture, Tag};
use lofty::{MimeType, Picture, Tag};
macro_rules! full_test {
($function:ident, $file:expr) => {
@ -18,9 +18,7 @@ macro_rules! full_test {
macro_rules! add_tags {
($file:expr) => {
println!("Reading file");
let mut tag = Tag::default()
.read_from_path($file, DetermineFrom::Signature)
.unwrap();
let mut tag = Tag::default().read_from_path_signature($file).unwrap();
println!("Setting title");
tag.set_title("foo title");
@ -54,9 +52,7 @@ macro_rules! add_tags {
macro_rules! verify_write {
($file:expr) => {
println!("Reading file");
let tag = Tag::default()
.read_from_path($file, DetermineFrom::Signature)
.unwrap();
let tag = Tag::default().read_from_path_signature($file).unwrap();
let file_name = stringify!($file);
@ -86,9 +82,7 @@ macro_rules! verify_write {
macro_rules! remove_tags {
($file:expr) => {
println!("Reading file");
let mut tag = Tag::default()
.read_from_path($file, DetermineFrom::Signature)
.unwrap();
let mut tag = Tag::default().read_from_path_signature($file).unwrap();
println!("Removing title");
tag.remove_title();