2022-12-12 18:40:39 +00:00
|
|
|
use std::borrow::Cow;
|
|
|
|
|
2022-06-10 14:24:00 +00:00
|
|
|
// This defines the `Accessor` trait, used to define unified getters/setters for commonly
|
|
|
|
// accessed tag values.
|
|
|
|
//
|
|
|
|
// Usage:
|
|
|
|
//
|
|
|
|
// accessor_trait! {
|
2022-06-20 15:13:58 +00:00
|
|
|
// [field_name]<type>
|
2022-06-10 14:24:00 +00:00
|
|
|
// }
|
|
|
|
//
|
2022-06-20 15:13:58 +00:00
|
|
|
// * `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>.
|
|
|
|
//
|
|
|
|
// * `type` is the return type for `Accessor::field_name`. By default, this type will also be used
|
2022-06-10 14:24:00 +00:00
|
|
|
// in the setter.
|
|
|
|
//
|
|
|
|
// An owned type can also be specified for the setter:
|
|
|
|
//
|
|
|
|
// accessor_trait! {
|
|
|
|
// field_name<type, owned_type>
|
|
|
|
// }
|
2022-02-19 14:39:09 +00:00
|
|
|
macro_rules! accessor_trait {
|
2022-06-20 15:13:58 +00:00
|
|
|
($([$($name:tt)+] < $($ty:ty),+ >),+ $(,)?) => {
|
2022-02-19 14:39:09 +00:00
|
|
|
/// 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 {
|
2022-06-20 15:13:58 +00:00
|
|
|
$(
|
|
|
|
accessor_trait! { @GETTER [$($name)+] $($ty),+ }
|
2022-06-10 14:24:00 +00:00
|
|
|
|
2022-06-20 15:13:58 +00:00
|
|
|
accessor_trait! { @SETTER [$($name)+] $($ty),+ }
|
2022-06-10 14:24:00 +00:00
|
|
|
|
2022-06-20 15:13:58 +00:00
|
|
|
accessor_trait! { @REMOVE [$($name)+] $($ty),+ }
|
|
|
|
)+
|
2022-02-19 14:39:09 +00:00
|
|
|
}
|
|
|
|
};
|
2022-06-20 15:13:58 +00:00
|
|
|
(@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>) => {
|
2022-06-10 14:24:00 +00:00
|
|
|
paste::paste! {
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "Returns the " $name $(" " $other)*]
|
2022-06-10 14:24:00 +00:00
|
|
|
/// # Example
|
|
|
|
///
|
2022-06-20 15:13:58 +00:00
|
|
|
/// ```rust
|
2022-06-10 14:24:00 +00:00
|
|
|
/// use lofty::{Tag, Accessor};
|
|
|
|
///
|
2023-04-21 02:29:11 +00:00
|
|
|
/// # let tag_type = lofty::TagType::Id3v2;
|
|
|
|
/// let mut tag = Tag::new(tag_type); ///
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "assert_eq!(tag." $name $(_ $other)* "(), None);"]
|
2022-06-10 14:24:00 +00:00
|
|
|
/// ```
|
2022-06-20 15:13:58 +00:00
|
|
|
fn [<
|
|
|
|
$name $(_ $other)*
|
|
|
|
>] (&self) -> Option<$ret_ty> { None }
|
2022-06-10 14:24:00 +00:00
|
|
|
}
|
|
|
|
};
|
2022-06-20 15:13:58 +00:00
|
|
|
(@SET_METHOD [$name:tt $($other:tt)*] $owned_ty:ty) => {
|
2022-06-10 14:24:00 +00:00
|
|
|
paste::paste! {
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "Sets the " $name $(" " $other)*]
|
2022-06-10 14:24:00 +00:00
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust,ignore
|
|
|
|
/// use lofty::{Tag, Accessor};
|
|
|
|
///
|
2022-06-20 15:13:58 +00:00
|
|
|
/// let mut tag = Tag::new(tag_type);
|
|
|
|
#[doc = "tag.set_" $name $(_ $other)* "(value);"]
|
2022-06-10 14:24:00 +00:00
|
|
|
///
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "assert_eq!(tag." $name $(_ $other)* "(), Some(value));"]
|
2022-06-10 14:24:00 +00:00
|
|
|
/// ```
|
2022-06-20 15:13:58 +00:00
|
|
|
fn [<
|
|
|
|
set_ $name $(_ $other)*
|
|
|
|
>] (&mut self , _value: $owned_ty) {}
|
2022-06-10 14:24:00 +00:00
|
|
|
}
|
|
|
|
};
|
2022-06-20 15:13:58 +00:00
|
|
|
(@REMOVE_METHOD [$name:tt $($other:tt)*], $ty:ty) => {
|
2022-06-10 14:24:00 +00:00
|
|
|
paste::paste! {
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "Removes the " $name $(" " $other)*]
|
2022-06-10 14:24:00 +00:00
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust,ignore
|
|
|
|
/// use lofty::{Tag, Accessor};
|
|
|
|
///
|
2022-06-20 15:13:58 +00:00
|
|
|
/// let mut tag = Tag::new(tag_type);
|
|
|
|
#[doc = "tag.set_" $name $(_ $other)* "(value);"]
|
2022-06-10 14:24:00 +00:00
|
|
|
///
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "assert_eq!(tag." $name $(_ $other)* "(), Some(value));"]
|
2022-06-10 14:24:00 +00:00
|
|
|
///
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "tag.remove_" $name $(_ $other)* "();"]
|
2022-06-10 14:24:00 +00:00
|
|
|
///
|
2022-06-20 15:13:58 +00:00
|
|
|
#[doc = "assert_eq!(tag." $name $(_ $other)* "(), None);"]
|
2022-06-10 14:24:00 +00:00
|
|
|
/// ```
|
2022-06-20 15:13:58 +00:00
|
|
|
fn [<
|
|
|
|
remove_ $name $(_ $other)*
|
|
|
|
>] (&mut self) {}
|
2022-06-10 14:24:00 +00:00
|
|
|
}
|
|
|
|
};
|
2022-02-19 14:39:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
accessor_trait! {
|
2022-12-12 18:40:39 +00:00
|
|
|
[artist]<Cow<'_, str>, String>, [title ]<Cow<'_, str>, String>,
|
|
|
|
[album ]<Cow<'_, str>, String>, [genre ]<Cow<'_, str>, String>,
|
|
|
|
[track ]<u32>, [track total]<u32>,
|
|
|
|
[disk ]<u32>, [disk total ]<u32>,
|
|
|
|
[year ]<u32>, [comment ]<Cow<'_, str>, String>,
|
2022-02-19 14:39:09 +00:00
|
|
|
}
|
|
|
|
|
2022-03-04 14:07:17 +00:00
|
|
|
use crate::tag::Tag;
|
2022-02-19 14:39:09 +00:00
|
|
|
|
|
|
|
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<Tag> + Sized {
|
|
|
|
/// The associated error which can be returned from IO operations
|
2023-04-16 00:27:59 +00:00
|
|
|
type Err: From<std::io::Error>;
|
2022-11-05 13:54:37 +00:00
|
|
|
/// The type of key used in the tag for non-mutating functions
|
|
|
|
type RefKey<'a>
|
|
|
|
where
|
|
|
|
Self: 'a;
|
2022-02-19 14:39:09 +00:00
|
|
|
|
2023-01-12 02:07:52 +00:00
|
|
|
/// 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};
|
2023-04-21 02:29:11 +00:00
|
|
|
/// # let tag_type = lofty::TagType::Id3v2;
|
2023-01-12 02:07:52 +00:00
|
|
|
///
|
|
|
|
/// 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;
|
|
|
|
|
2022-11-05 13:54:37 +00:00
|
|
|
/// Whether the tag contains an item with the key
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// use lofty::{Accessor, ItemKey, Tag, TagExt};
|
2023-04-21 02:29:11 +00:00
|
|
|
/// # let tag_type = lofty::TagType::Id3v2;
|
2022-11-05 13:54:37 +00:00
|
|
|
///
|
|
|
|
/// 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;
|
2022-07-04 19:05:25 +00:00
|
|
|
|
2022-02-19 14:39:09 +00:00
|
|
|
/// Whether the tag has any items
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2022-02-19 14:41:36 +00:00
|
|
|
/// use lofty::{Accessor, Tag, TagExt};
|
2023-04-21 02:29:11 +00:00
|
|
|
/// # let tag_type = lofty::TagType::Id3v2;
|
2022-02-19 14:39:09 +00:00
|
|
|
///
|
|
|
|
/// 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`]
|
2023-04-16 00:27:59 +00:00
|
|
|
fn save_to_path<P: AsRef<Path>>(&self, path: P) -> std::result::Result<(), Self::Err> {
|
|
|
|
self.save_to(
|
|
|
|
&mut std::fs::OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.write(true)
|
|
|
|
.open(path)?,
|
|
|
|
)
|
|
|
|
}
|
2022-02-19 14:39:09 +00:00
|
|
|
|
|
|
|
/// 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) -> 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<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), Self::Err>;
|
|
|
|
|
|
|
|
/// Remove a tag from a [`Path`]
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// See [`TagExt::remove_from`]
|
|
|
|
fn remove_from_path<P: AsRef<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>;
|
2022-02-26 01:38:58 +00:00
|
|
|
|
2022-03-18 19:22:40 +00:00
|
|
|
/// Clear the tag, removing all items
|
|
|
|
///
|
|
|
|
/// NOTE: This will **not** remove any format-specific extras, such as flags
|
|
|
|
fn clear(&mut self);
|
2022-02-19 14:39:09 +00:00
|
|
|
}
|
2022-07-12 03:01:17 +00:00
|
|
|
|
2023-02-28 14:28:32 +00:00
|
|
|
/// Split (and merge) tags.
|
2023-01-18 22:01:39 +00:00
|
|
|
///
|
2023-01-19 10:21:47 +00:00
|
|
|
/// Useful and required for implementing lossless read/modify/write round trips.
|
2023-02-28 14:28:32 +00:00
|
|
|
/// Its counterpart `MergeTag` is used for recombining the results later.
|
2023-01-19 10:21:47 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```no_run
|
2023-04-21 02:32:47 +00:00
|
|
|
/// use lofty::mpeg::MpegFile;
|
2023-02-28 14:28:32 +00:00
|
|
|
/// use lofty::{AudioFile, ItemKey, MergeTag as _, SplitTag as _};
|
2023-01-19 10:21:47 +00:00
|
|
|
///
|
|
|
|
/// // Read the tag from a file
|
|
|
|
/// # let mut file = std::fs::OpenOptions::new().write(true).open("/path/to/file.mp3")?;
|
|
|
|
/// # let parse_options = lofty::ParseOptions::default();
|
2023-04-21 02:32:47 +00:00
|
|
|
/// let mut mpeg_file = <MpegFile as AudioFile>::read_from(&mut file, parse_options)?;
|
2023-02-28 14:28:32 +00:00
|
|
|
/// let mut id3v2 = mpeg_file
|
|
|
|
/// .id3v2_mut()
|
|
|
|
/// .map(std::mem::take)
|
|
|
|
/// .unwrap_or_default();
|
2023-01-19 10:21:47 +00:00
|
|
|
///
|
|
|
|
/// // Split: ID3v2 -> [`lofty::Tag`]
|
2023-02-28 14:28:32 +00:00
|
|
|
/// let (mut remainder, mut tag) = id3v2.split_tag();
|
2023-01-19 10:21:47 +00:00
|
|
|
///
|
|
|
|
/// // 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`]
|
2023-02-28 14:28:32 +00:00
|
|
|
/// let id3v2 = remainder.merge_tag(tag);
|
2023-01-19 10:21:47 +00:00
|
|
|
///
|
|
|
|
/// // Write the changes back into the file
|
2023-02-28 14:28:32 +00:00
|
|
|
/// mpeg_file.set_id3v2(id3v2);
|
2023-01-19 10:21:47 +00:00
|
|
|
/// mpeg_file.save_to(&mut file)?;
|
|
|
|
///
|
|
|
|
/// # Ok::<(), lofty::LoftyError>(())
|
|
|
|
/// ```
|
2023-02-28 14:28:32 +00:00
|
|
|
pub trait SplitTag {
|
|
|
|
/// The remainder of the split operation that is not represented
|
|
|
|
/// in the resulting `Tag`.
|
|
|
|
type Remainder: MergeTag;
|
|
|
|
|
2023-01-18 22:01:39 +00:00
|
|
|
/// Extract and split generic contents into a [`Tag`].
|
|
|
|
///
|
2023-02-28 14:28:32 +00:00
|
|
|
/// 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);
|
|
|
|
}
|
2023-01-18 22:01:39 +00:00
|
|
|
|
2023-02-28 14:28:32 +00:00
|
|
|
/// 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`].
|
2023-01-18 22:01:39 +00:00
|
|
|
///
|
|
|
|
/// Restores the original representation merged with the contents of
|
|
|
|
/// `tag` for further processing, e.g. writing back into a file.
|
2023-01-20 08:43:56 +00:00
|
|
|
///
|
2023-02-05 11:12:13 +00:00
|
|
|
/// Multi-valued items in `tag` with identical keys might get lost
|
|
|
|
/// depending on the support for multi-valued fields in `self`.
|
2023-02-28 14:28:32 +00:00
|
|
|
fn merge_tag(self, tag: Tag) -> Self::Merged;
|
2023-01-18 22:01:39 +00:00
|
|
|
}
|
|
|
|
|
2022-07-12 03:01:17 +00:00
|
|
|
// TODO: https://github.com/rust-lang/rust/issues/59359
|
|
|
|
pub(crate) trait SeekStreamLen: std::io::Seek {
|
|
|
|
fn stream_len(&mut self) -> crate::error::Result<u64> {
|
|
|
|
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<T> SeekStreamLen for T where T: std::io::Seek {}
|