From e4ee5c95abec8ef6588858e5e2a069461b4fafc8 Mon Sep 17 00:00:00 2001 From: Braden Steffaniak Date: Tue, 21 Nov 2023 20:30:31 -0500 Subject: [PATCH] Feat/add full date capabilities (#34) * Remove unused import * Add date_released, original_date_released, and date_recorded * Remove DATE as well as YEAR * Generify 'date' concept to singular generic `date` property * Generify to `date` on AudioTagEdit * Remove `date_recorded` as well as `year` --- src/anytag.rs | 8 +++++++ src/components/flac_tag.rs | 27 +++++++++++++++++++---- src/components/id3_tag.rs | 44 +++++++++++++++----------------------- src/components/mp4_tag.rs | 18 ++++++++++++++++ src/traits.rs | 5 +++++ tests/io.rs | 11 ++++++++++ 6 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/anytag.rs b/src/anytag.rs index 26059ee..3397fe5 100644 --- a/src/anytag.rs +++ b/src/anytag.rs @@ -1,10 +1,12 @@ use crate::*; +use id3::Timestamp; #[derive(Default)] pub struct AnyTag<'a> { pub config: Config, pub title: Option<&'a str>, pub artists: Option>, + pub date: Option, pub year: Option, pub duration: Option, pub album_title: Option<&'a str>, @@ -39,6 +41,12 @@ impl<'a> AnyTag<'a> { self.artists.as_deref() } // set_artists; add_artist + pub fn date(&self) -> Option { + self.date + } + pub fn set_date(&mut self, date: Timestamp) { + self.date = Some(date); + } pub fn year(&self) -> Option { self.year } diff --git a/src/components/flac_tag.rs b/src/components/flac_tag.rs index b1dcca1..857a22f 100644 --- a/src/components/flac_tag.rs +++ b/src/components/flac_tag.rs @@ -1,5 +1,7 @@ use crate::*; +use id3::Timestamp; use metaflac; +use std::str::FromStr; pub use metaflac::Tag as FlacInnerTag; @@ -14,6 +16,9 @@ impl<'a> From> for FlacTag { if let Some(v) = inp.artists_as_string() { t.set_artist(&v) } + if let Some(v) = inp.date { + t.set_date(v) + } if let Some(v) = inp.year { t.set_year(v) } @@ -44,6 +49,7 @@ impl<'a> From<&'a FlacTag> for AnyTag<'a> { let tag = Self { title: inp.title(), artists: inp.artists(), + date: inp.date(), year: inp.year(), duration: inp.duration(), album_title: inp.album_title(), @@ -104,20 +110,33 @@ impl AudioTagEdit for FlacTag { self.remove("ARTIST"); } + fn date(&self) -> Option { + if let Some(Ok(timestamp)) = self.get_first("DATE").map(Timestamp::from_str) { + Some(timestamp) + } else { + None + } + } + fn set_date(&mut self, date: Timestamp) { + self.set_first("DATE", &date.to_string()); + } + fn remove_date(&mut self) { + self.remove("DATE"); + } + fn year(&self) -> Option { - if let Some(Ok(y)) = self + if let Some(Ok(y)) = self.get_first("YEAR").map(|s| s.parse::()) { + Some(y) + } else if let Some(Ok(y)) = self .get_first("DATE") .map(|s| s.chars().take(4).collect::().parse::()) { Some(y) - } else if let Some(Ok(y)) = self.get_first("YEAR").map(|s| s.parse::()) { - Some(y) } else { None } } fn set_year(&mut self, year: i32) { - self.set_first("DATE", &year.to_string()); self.set_first("YEAR", &year.to_string()); } fn remove_year(&mut self) { diff --git a/src/components/id3_tag.rs b/src/components/id3_tag.rs index 776ea25..72134c7 100644 --- a/src/components/id3_tag.rs +++ b/src/components/id3_tag.rs @@ -1,5 +1,5 @@ use crate::*; -use id3::{self, Content, Frame, TagLike, Timestamp, Version}; +use id3::{self, Content, Frame, TagLike, Timestamp}; pub use id3::Tag as Id3v2InnerTag; @@ -12,6 +12,7 @@ impl<'a> From<&'a Id3v2Tag> for AnyTag<'a> { title: inp.title(), artists: inp.artists(), + date: inp.date(), year: inp.year(), duration: inp.inner.duration().map(f64::from), album_title: inp.album_title(), @@ -40,6 +41,9 @@ impl<'a> From> for Id3v2Tag { if let Some(v) = inp.artists_as_string() { t.set_artist(&v) } + if let Some(v) = inp.date() { + t.set_date_recorded(v) + } if let Some(v) = inp.year { t.set_year(v) } @@ -102,35 +106,21 @@ impl AudioTagEdit for Id3v2Tag { self.inner.remove_artist(); } - fn year(&self) -> Option { - if self.inner.version() == Version::Id3v23 { - if let ret @ Some(_) = self.inner.year() { - return ret; - } - } + fn date(&self) -> Option { + self.inner.date_recorded() + } + fn set_date(&mut self, timestamp: Timestamp) { + self.inner.set_date_recorded(timestamp) + } + fn remove_date(&mut self) { + self.inner.remove_date_recorded() + } - self.inner.date_recorded().map(|timestamp| timestamp.year) + fn year(&self) -> Option { + self.inner.year() } fn set_year(&mut self, year: i32) { - if self.inner.version() == Version::Id3v23 { - self.inner.set_year(year); - return; - } - - if let Some(mut timestamp) = self.inner.date_recorded() { - timestamp.year = year; - self.inner.set_date_recorded(timestamp); - return; - } - - self.inner.set_date_recorded(Timestamp { - year, - month: None, - day: None, - hour: None, - minute: None, - second: None, - }); + self.inner.set_year(year); } fn remove_year(&mut self) { self.inner.remove_date_recorded(); diff --git a/src/components/mp4_tag.rs b/src/components/mp4_tag.rs index 44f76cf..0e4281a 100644 --- a/src/components/mp4_tag.rs +++ b/src/components/mp4_tag.rs @@ -1,5 +1,7 @@ use crate::*; +use id3::Timestamp; use mp4ameta::{self, ImgFmt}; +use std::str::FromStr; pub use mp4ameta::Tag as Mp4InnerTag; @@ -9,6 +11,7 @@ impl<'a> From<&'a Mp4Tag> for AnyTag<'a> { fn from(inp: &'a Mp4Tag) -> Self { let title = inp.title(); let artists = inp.artists().map(|i| i.into_iter().collect::>()); + let date = inp.date(); let year = inp.year(); let duration = inp.duration(); let album_title = inp.album_title(); @@ -29,6 +32,7 @@ impl<'a> From<&'a Mp4Tag> for AnyTag<'a> { config: inp.config, title, artists, + date, year, duration, album_title, @@ -137,6 +141,20 @@ impl AudioTagEdit for Mp4Tag { self.inner.add_artist(v); } + fn date(&self) -> Option { + if let Some(Ok(date)) = self.inner.year().map(Timestamp::from_str) { + Some(date) + } else { + None + } + } + fn set_date(&mut self, date: Timestamp) { + self.inner.set_year(date.to_string()) + } + fn remove_date(&mut self) { + self.inner.remove_year() + } + fn year(&self) -> Option { self.inner.year().and_then(|x| str::parse(x).ok()) } diff --git a/src/traits.rs b/src/traits.rs index 874c4f3..7cd43fa 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,5 @@ use super::*; +use id3::Timestamp; pub trait AudioTag: AudioTagEdit + AudioTagWrite + ToAnyTag {} @@ -31,6 +32,10 @@ pub trait AudioTagEdit: AudioTagConfig { self.set_artist(artist); } + fn date(&self) -> Option; + fn set_date(&mut self, date: Timestamp); + fn remove_date(&mut self); + fn year(&self) -> Option; fn set_year(&mut self, year: i32); fn remove_year(&mut self); diff --git a/tests/io.rs b/tests/io.rs index 4451e2e..c4ed80c 100644 --- a/tests/io.rs +++ b/tests/io.rs @@ -1,7 +1,9 @@ use audiotags::{MimeType, Picture, Tag}; +use id3::Timestamp; use std::ffi::OsString; use std::fs; use std::path::Path; +use std::str::FromStr; use tempfile::Builder; macro_rules! test_file { @@ -28,6 +30,15 @@ macro_rules! test_file { assert!(tags.artist().is_none()); tags.remove_artist(); + tags.set_date(Timestamp::from_str("2020-05-22").unwrap()); + assert_eq!( + tags.date(), + Some(Timestamp::from_str("2020-05-22").unwrap()) + ); + tags.remove_date(); + assert!(tags.date().is_none()); + tags.remove_date(); + tags.set_year(2020); assert_eq!(tags.year(), Some(2020)); tags.remove_year();