mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 06:34:18 +00:00
Make things less confusing to use
This commit is contained in:
parent
bd48066b64
commit
754bf03413
7 changed files with 79 additions and 77 deletions
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -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};
|
||||
|
|
96
src/tag.rs
96
src/tag.rs
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
14
tests/io.rs
14
tests/io.rs
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue