From ac6f75cfaf1906a58c6650fcdff8718b8685f6da Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Mon, 9 Sep 2024 07:40:59 -0400 Subject: [PATCH 1/3] ItemKey: Add `ItemKey::TrackArtists` This is a multi-valued item where each entry contains one artist name. See . --- CHANGELOG.md | 6 ++++++ lofty/src/id3/v2/tag.rs | 14 ++++++++++++++ lofty/src/id3/v2/tag/tests.rs | 20 ++++++++++++++++++++ lofty/src/tag/item.rs | 9 +++++++++ 4 files changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dbc86b8f..0cc0454ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **ItemKey**: `ItemKey::TrackArtists`, available for ID3v2, Vorbis Comments, APE, and MP4 Ilst ([PR](https://github.com/Serial-ATA/lofty-rs/issues/454)) + - This is a multi-value item that stores each artist for a track. It should be retrieved with `Tag::get_strings` or `Tag::take_strings`. + - For example, a track has `ItemKey::TrackArtist` = "Foo & Bar", then `ItemKey::TrackArtists` = ["Foo", "Bar"]. + - See + ### Fixed - **MusePack**: Fix potential panic when the beginning silence makes up the entire sample count ([PR](https://github.com/Serial-ATA/lofty-rs/pull/449)) - **Timestamp**: Support timestamps without separators (ex. "20240906" vs "2024-09-06") ([issue](https://github.com/Serial-ATA/lofty-rs/issues/452)) ([PR](https://github.com/Serial-ATA/lofty-rs/issues/453)) diff --git a/lofty/src/id3/v2/tag.rs b/lofty/src/id3/v2/tag.rs index 2e624811f..cb370ab3b 100644 --- a/lofty/src/id3/v2/tag.rs +++ b/lofty/src/id3/v2/tag.rs @@ -1402,6 +1402,20 @@ impl MergeTag for SplitTagRemainder { } } + // Multi-valued TXXX key-to-frame mappings + #[allow(clippy::single_element_loop)] + for item_key in [&ItemKey::TrackArtists] { + let frame_id = item_key + .map_key(TagType::Id3v2, false) + .expect("valid frame id"); + if let Some(text) = join_text_items(&mut tag, [item_key]) { + let frame = new_user_text_frame(String::from(frame_id), text); + // Optimization: No duplicate checking according to the preconditions + debug_assert!(!merged.frames.contains(&frame)); + merged.frames.push(frame); + } + } + // Multi-valued Label/Publisher key-to-frame mapping { let frame_id = ItemKey::Label diff --git a/lofty/src/id3/v2/tag/tests.rs b/lofty/src/id3/v2/tag/tests.rs index c8f61c5ff..10fa44233 100644 --- a/lofty/src/id3/v2/tag/tests.rs +++ b/lofty/src/id3/v2/tag/tests.rs @@ -1534,3 +1534,23 @@ fn split_tdrc_on_id3v23_save() { .expect("Expected TIME frame"); assert_eq!(time, "1408"); } + +#[test_log::test] +fn artists_tag_conversion() { + const ARTISTS: &[&str] = &["Foo", "Bar", "Baz"]; + + let mut tag = Tag::new(TagType::Id3v2); + + for artist in ARTISTS { + tag.push(TagItem::new( + ItemKey::TrackArtists, + ItemValue::Text((*artist).to_string()), + )); + } + + let tag: Id3v2Tag = tag.into(); + let txxx_artists = tag.get_user_text("ARTISTS").unwrap(); + let id3v2_artists = txxx_artists.split('\0').collect::>(); + + assert_eq!(id3v2_artists, ARTISTS); +} diff --git a/lofty/src/tag/item.rs b/lofty/src/tag/item.rs index 56e2df882..b415654d6 100644 --- a/lofty/src/tag/item.rs +++ b/lofty/src/tag/item.rs @@ -88,6 +88,7 @@ gen_map!( "ARTISTSORT" => TrackArtistSortOrder, "Album Artist" | "ALBUMARTIST" => AlbumArtist, "Artist" => TrackArtist, + "Artists" => TrackArtists, "Arranger" => Arranger, "Writer" => Writer, "Composer" => Composer, @@ -154,6 +155,7 @@ gen_map!( "TSOC" => ComposerSortOrder, "TPE2" => AlbumArtist, "TPE1" => TrackArtist, + "ARTISTS" => TrackArtists, "TEXT" => Writer, "TCOM" => Composer, "TPE3" => Conductor, @@ -249,6 +251,7 @@ gen_map!( "soco" => ComposerSortOrder, "aART" => AlbumArtist, "\u{a9}ART" => TrackArtist, + "----:com.apple.iTunes:ARTISTS" => TrackArtists, "\u{a9}wrt" => Composer, "\u{a9}dir" => Director, "----:com.apple.iTunes:CONDUCTOR" => Conductor, @@ -349,6 +352,7 @@ gen_map!( "ARTISTSORT" => TrackArtistSortOrder, "ALBUMARTIST" => AlbumArtist, "ARTIST" => TrackArtist, + "ARTISTS" => TrackArtists, "ARRANGER" => Arranger, "AUTHOR" | "WRITER" => Writer, "COMPOSER" => Composer, @@ -520,6 +524,11 @@ gen_item_keys!( // People & Organizations AlbumArtist, TrackArtist, + /// The name of each credited artist + /// + /// This tag is meant to appear multiple times in a tag, so it should be retrieved with + /// [`Tag::get_strings`] or [`Tag::take_strings`]. + TrackArtists, Arranger, Writer, Composer, From 9a2557c48c8409c664287641f004264e7befbd9b Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Mon, 9 Sep 2024 07:52:53 -0400 Subject: [PATCH 2/3] ID3v2: Handle TXXX `ItemKey` conversions correctly --- CHANGELOG.md | 1 + lofty/src/id3/v2/tag.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc0454ec..ca8b051ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **MusePack**: Fix potential panic when the beginning silence makes up the entire sample count ([PR](https://github.com/Serial-ATA/lofty-rs/pull/449)) - **Timestamp**: Support timestamps without separators (ex. "20240906" vs "2024-09-06") ([issue](https://github.com/Serial-ATA/lofty-rs/issues/452)) ([PR](https://github.com/Serial-ATA/lofty-rs/issues/453)) +- **ID3v2**: `ItemKey::Director` will now be written correctly as a TXXX frame ([PR](https://github.com/Serial-ATA/lofty-rs/issues/454)) ## [0.21.1] - 2024-08-28 diff --git a/lofty/src/id3/v2/tag.rs b/lofty/src/id3/v2/tag.rs index cb370ab3b..efd87629f 100644 --- a/lofty/src/id3/v2/tag.rs +++ b/lofty/src/id3/v2/tag.rs @@ -1379,7 +1379,6 @@ impl MergeTag for SplitTagRemainder { &ItemKey::Composer, &ItemKey::Conductor, &ItemKey::Writer, - &ItemKey::Director, &ItemKey::Lyricist, &ItemKey::MusicianCredits, &ItemKey::InternetRadioStationName, @@ -1403,8 +1402,11 @@ impl MergeTag for SplitTagRemainder { } // Multi-valued TXXX key-to-frame mappings - #[allow(clippy::single_element_loop)] - for item_key in [&ItemKey::TrackArtists] { + for item_key in [ + &ItemKey::TrackArtists, + &ItemKey::Director, + &ItemKey::CatalogNumber, + ] { let frame_id = item_key .map_key(TagType::Id3v2, false) .expect("valid frame id"); @@ -1521,6 +1523,7 @@ impl MergeTag for SplitTagRemainder { )); } + // iTunes advisory rating 'rate: { if let Some(advisory_rating) = tag.take_strings(&ItemKey::ParentalAdvisory).next() { let Ok(rating) = advisory_rating.parse::() else { From d6c637a3c28c2dc63f8c5ced099326af747ef7b7 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Mon, 9 Sep 2024 07:54:00 -0400 Subject: [PATCH 3/3] ItemKey: Fix links --- lofty/src/tag/item.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lofty/src/tag/item.rs b/lofty/src/tag/item.rs index b415654d6..7dbcc309e 100644 --- a/lofty/src/tag/item.rs +++ b/lofty/src/tag/item.rs @@ -528,6 +528,9 @@ gen_item_keys!( /// /// This tag is meant to appear multiple times in a tag, so it should be retrieved with /// [`Tag::get_strings`] or [`Tag::take_strings`]. + /// + /// [`Tag::get_strings`]: crate::tag::Tag::get_strings + /// [`Tag::take_strings`]: crate::tag::Tag::take_strings TrackArtists, Arranger, Writer,