Accessor: Add {set_, remove_}track

This commit is contained in:
Serial 2022-06-10 10:24:00 -04:00
parent 829b3e0e95
commit 28fe204058
No known key found for this signature in database
GPG key ID: DA95198DC17C4568
9 changed files with 341 additions and 187 deletions

View file

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- **WavPack** support
- `Accessor::{set_, remove_}track`
### Changed
- Bitrates in properties will be rounded up, similar to FFmpeg and TagLib
@ -21,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `RiffInfo` -> `RIFFInfo`
- `AiffText` -> `AIFFText`
- All types implementing `PartialEq` now implement `Eq`
- `Ilst::track_number` has been moved to the `Accessor::track` implementation
## [0.6.3] - 2022-05-18

View file

@ -16,7 +16,6 @@ use std::path::Path;
macro_rules! impl_accessor {
($($name:ident => $($key:literal)|+;)+) => {
paste::paste! {
impl Accessor for ApeTag {
$(
fn $name(&self) -> Option<&str> {
$(
@ -46,7 +45,6 @@ macro_rules! impl_accessor {
)+
}
}
}
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
@ -79,13 +77,6 @@ pub struct ApeTag {
pub(super) items: Vec<ApeItem>,
}
impl_accessor!(
artist => "Artist";
title => "Title";
album => "Album";
genre => "GENRE";
);
impl ApeTag {
/// Get an [`ApeItem`] by key
///
@ -121,6 +112,41 @@ impl ApeTag {
}
}
impl Accessor for ApeTag {
impl_accessor!(
artist => "Artist";
title => "Title";
album => "Album";
genre => "GENRE";
);
fn track(&self) -> Option<u32> {
if let Some(ApeItem {
value: ItemValue::Text(ref text),
..
}) = self.get_key("Track")
{
if let Ok(ret) = text.parse::<u32>() {
return Some(ret);
}
}
None
}
fn set_track(&mut self, value: u32) {
self.insert(ApeItem {
read_only: false,
key: String::from("Track"),
value: ItemValue::Text(value.to_string()),
})
}
fn remove_track(&mut self) {
self.remove_key("Track");
}
}
impl TagExt for ApeTag {
type Err = LoftyError;

View file

@ -115,6 +115,18 @@ impl Accessor for Id3v1Tag {
fn remove_genre(&mut self) {
self.genre = None
}
fn track(&self) -> Option<u32> {
self.track_number.map(u32::from)
}
fn set_track(&mut self, value: u32) {
self.track_number = Some(value as u8);
}
fn remove_track(&mut self) {
self.track_number = None;
}
}
impl TagExt for Id3v1Tag {

View file

@ -1,7 +1,7 @@
#[cfg(feature = "id3v2_restrictions")]
use super::restrictions::TagRestrictions;
#[derive(Default, Copy, Clone, Debug, PartialEq)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[allow(clippy::struct_excessive_bools)]
/// Flags that apply to the entire tag
pub struct Id3v2TagFlags {

View file

@ -19,9 +19,8 @@ use std::io::Write;
use std::path::Path;
macro_rules! impl_accessor {
($($name:ident, $id:literal;)+) => {
($($name:ident => $id:literal;)+) => {
paste::paste! {
impl Accessor for Id3v2Tag {
$(
fn $name(&self) -> Option<&str> {
if let Some(f) = self.get($id) {
@ -53,7 +52,6 @@ macro_rules! impl_accessor {
)+
}
}
}
}
#[derive(PartialEq, Debug, Clone)]
@ -99,13 +97,6 @@ pub struct Id3v2Tag {
frames: Vec<Frame>,
}
impl_accessor!(
title, "TIT2";
artist, "TPE1";
album, "TALB";
genre, "TCON";
);
impl IntoIterator for Id3v2Tag {
type Item = Frame;
type IntoIter = std::vec::IntoIter<Frame>;
@ -272,6 +263,46 @@ impl Id3v2Tag {
}
}
impl Accessor for Id3v2Tag {
impl_accessor!(
title => "TIT2";
artist => "TPE1";
album => "TALB";
genre => "TCON";
);
fn track(&self) -> Option<u32> {
if let Some(Frame {
value: FrameValue::Text { ref value, .. },
..
}) = self.get("TRCK")
{
if let Some(track_num) = value.split(&['\0', '/'][..]).next() {
if let Ok(ret) = track_num.parse::<u32>() {
return Some(ret);
}
}
}
None
}
fn set_track(&mut self, value: u32) {
self.insert(Frame {
id: FrameID::Valid(String::from("TRCK")),
value: FrameValue::Text {
value: value.to_string(),
encoding: TextEncoding::UTF8,
},
flags: FrameFlags::default(),
});
}
fn remove_track(&mut self) {
self.remove("TRCK");
}
}
impl TagExt for Id3v2Tag {
type Err = LoftyError;

View file

@ -11,9 +11,8 @@ use std::io::Write;
use std::path::Path;
macro_rules! impl_accessor {
($($name:ident, $key:literal;)+) => {
($($name:ident => $key:literal;)+) => {
paste::paste! {
impl Accessor for RiffInfoList {
$(
fn $name(&self) -> Option<&str> {
self.get($key)
@ -29,7 +28,6 @@ macro_rules! impl_accessor {
)+
}
}
}
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
@ -52,13 +50,6 @@ pub struct RiffInfoList {
pub(crate) items: Vec<(String, String)>,
}
impl_accessor!(
artist, "IART";
title, "INAM";
album, "IPRD";
genre, "IGNR";
);
impl RiffInfoList {
/// Get an item by key
pub fn get(&self, key: &str) -> Option<&str> {
@ -99,6 +90,31 @@ impl RiffInfoList {
}
}
impl Accessor for RiffInfoList {
impl_accessor!(
artist => "IART";
title => "INAM";
album => "IPRD";
genre => "IGNR";
);
fn track(&self) -> Option<u32> {
if let Some(item) = self.get("IPRT") {
return item.parse::<u32>().ok();
}
None
}
fn set_track(&mut self, value: u32) {
self.insert(String::from("IPRT"), value.to_string());
}
fn remove_track(&mut self) {
self.remove("IPRT");
}
}
impl TagExt for RiffInfoList {
type Err = LoftyError;

View file

@ -24,9 +24,8 @@ const ALBUM: AtomIdent = AtomIdent::Fourcc(*b"\xa9alb");
const GENRE: AtomIdent = AtomIdent::Fourcc(*b"\xa9gen");
macro_rules! impl_accessor {
($($name:ident, $const:ident;)+) => {
($($name:ident => $const:ident;)+) => {
paste::paste! {
impl Accessor for Ilst {
$(
fn $name(&self) -> Option<&str> {
if let Some(atom) = self.atom(&$const) {
@ -51,7 +50,6 @@ macro_rules! impl_accessor {
)+
}
}
}
}
#[derive(Default, PartialEq, Debug, Clone)]
@ -85,13 +83,6 @@ pub struct Ilst {
pub(crate) atoms: Vec<Atom>,
}
impl_accessor!(
artist, ARTIST;
title, TITLE;
album, ALBUM;
genre, GENRE;
);
impl Ilst {
/// Returns all of the tag's atoms
pub fn atoms(&self) -> &[Atom] {
@ -190,11 +181,6 @@ impl Ilst {
})
}
/// Returns the track number
pub fn track_number(&self) -> Option<u16> {
self.extract_number(*b"trkn", 4)
}
/// Returns the total number of tracks
pub fn track_total(&self) -> Option<u16> {
self.extract_number(*b"trkn", 6)
@ -228,6 +214,36 @@ impl Ilst {
}
}
impl Accessor for Ilst {
impl_accessor!(
artist => ARTIST;
title => TITLE;
album => ALBUM;
genre => GENRE;
);
fn track(&self) -> Option<u32> {
self.extract_number(*b"trkn", 4).map(u32::from)
}
fn set_track(&mut self, value: u32) {
let value = (value as u16).to_be_bytes();
let track_total = self.track_total().unwrap_or(0).to_be_bytes();
self.replace_atom(Atom {
ident: AtomIdent::Fourcc(*b"trkn"),
data: AtomDataStorage::Single(AtomData::Unknown {
code: 0,
data: vec![0, 0, value[0], value[1], track_total[0], track_total[1]],
}),
});
}
fn remove_track(&mut self) {
self.remove_atom(&AtomIdent::Fourcc(*b"trkn"));
}
}
impl TagExt for Ilst {
type Err = LoftyError;

View file

@ -13,9 +13,8 @@ use std::io::{Cursor, Write};
use std::path::Path;
macro_rules! impl_accessor {
($($name:ident, $key:literal;)+) => {
($($name:ident => $key:literal;)+) => {
paste::paste! {
impl Accessor for VorbisComments {
$(
fn $name(&self) -> Option<&str> {
self.get($key)
@ -31,7 +30,6 @@ macro_rules! impl_accessor {
)+
}
}
}
}
/// Vorbis comments
@ -52,13 +50,6 @@ pub struct VorbisComments {
pub(crate) pictures: Vec<(Picture, PictureInformation)>,
}
impl_accessor!(
artist, "ARTIST";
title, "TITLE";
album, "ALBUM";
genre, "GENRE";
);
impl VorbisComments {
/// Returns the vendor string
pub fn vendor(&self) -> &str {
@ -157,6 +148,31 @@ impl VorbisComments {
}
}
impl Accessor for VorbisComments {
impl_accessor!(
artist => "ARTIST";
title => "TITLE";
album => "ALBUM";
genre => "GENRE";
);
fn track(&self) -> Option<u32> {
if let Some(item) = self.get("TRACKNUMBER") {
return item.parse::<u32>().ok();
}
None
}
fn set_track(&mut self, value: u32) {
self.insert(String::from("TRACKNUMBER"), value.to_string(), true);
}
fn remove_track(&mut self) {
let _ = self.remove("TRACKNUMBER");
}
}
impl TagExt for VorbisComments {
type Err = LoftyError;

View file

@ -1,5 +1,22 @@
// This defines the `Accessor` trait, used to define unified getters/setters for commonly
// accessed tag values.
//
// Usage:
//
// accessor_trait! {
// field_name<type>
// }
//
// Where `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<type, owned_type>
// }
macro_rules! accessor_trait {
($($name:ident),+) => {
($($name:ident < $($ty:ty),+ >),+ $(,)?) => {
/// Provides accessors for common items
///
/// This attempts to only provide methods for items that all tags have in common,
@ -7,56 +24,74 @@ macro_rules! accessor_trait {
pub trait Accessor {
paste::paste! {
$(
accessor_trait! { @GETTER $name $($ty),+ }
accessor_trait! { @SETTER $name $($ty),+ }
accessor_trait! { @REMOVE $name $($ty),+ }
)+
}
}
};
(@GETTER $name:ident $ty:ty $(, $_ty:tt)?) => {
paste::paste! {
#[doc = "Returns the " $name]
/// # Example
///
/// ```rust
/// ```rust,ignore
/// use lofty::{Tag, Accessor};
/// # let tag_type = lofty::TagType::ID3v2;
///
/// let mut tag = Tag::new(tag_type);
///
#[doc = "assert_eq!(tag." $name "(), None);"]
/// ```
fn $name(&self) -> Option<&str> { None }
fn $name(&self) -> Option<$ty> { None }
}
};
(@SETTER $name:ident $_ty:ty, $owned_ty:tt) => {
accessor_trait! { @SETTER $name $owned_ty }
};
(@SETTER $name:ident $ty:ty $(, $_ty:tt)?) => {
paste::paste! {
#[doc = "Sets the " $name]
/// # Example
///
/// ```rust
/// ```rust,ignore
/// use lofty::{Tag, Accessor};
/// # let tag_type = lofty::TagType::ID3v2;
///
#[doc = "let mut tag = Tag::new(tag_type);\ntag.set_" $name "(String::from(\"Foo " $name "\"));"]
#[doc = "let mut tag = Tag::new(tag_type);\ntag.set_" $name "(value);"]
///
#[doc = "assert_eq!(tag." $name "(), Some(\"Foo " $name "\"));"]
#[doc = "assert_eq!(tag." $name "(), Some(value));"]
/// ```
fn [<set_ $name>](&mut self, _value: String) {}
fn [<set_ $name>](&mut self, _value: $ty) {}
}
};
(@REMOVE $name:ident $ty:ty $(, $_ty:tt)?) => {
paste::paste! {
#[doc = "Removes the " $name]
///
/// # Example
///
/// ```rust
/// ```rust,ignore
/// use lofty::{Tag, Accessor};
/// # let tag_type = lofty::TagType::ID3v2;
///
#[doc = "let mut tag = Tag::new(tag_type);\ntag.set_" $name "(String::from(\"Foo " $name "\"));"]
#[doc = "let mut tag = Tag::new(tag_type);\ntag.set_" $name "(value);"]
///
#[doc = "assert_eq!(tag." $name "(), Some(\"Foo " $name "\"));"]
#[doc = "assert_eq!(tag." $name "(), Some(value));"]
///
#[doc = "tag.remove_" $name "();"]
///
#[doc = "assert_eq!(tag." $name "(), None);"]
/// ```
fn [<remove_ $name>](&mut self) {}
)+
}
}
};
}
accessor_trait! {
artist, title,
album, genre
artist<&str, String>, title<&str, String>,
album<&str, String>, genre<&str, String>,
track<u32>
}
use crate::tag::Tag;