ItemKey: Add ItemKey::TrackArtists

This is a multi-valued item where each entry contains one artist name.

See <https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#artists>.
This commit is contained in:
Serial 2024-09-09 07:40:59 -04:00 committed by Alex
parent e787f81544
commit da4dea1f8b
4 changed files with 49 additions and 0 deletions

View file

@ -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 <https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#artists>
### 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))

View file

@ -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

View file

@ -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::<Vec<_>>();
assert_eq!(id3v2_artists, ARTISTS);
}

View file

@ -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,