mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-13 14:12:31 +00:00
Tag: Support ItemKey::ParentalAdvisory
for Ilst and ID3v2
This commit is contained in:
parent
1474efa9a3
commit
c1f117f5a4
5 changed files with 91 additions and 0 deletions
|
@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Tag**: Support `ItemKey::ParentalAdvisory` for `Ilst` and `Id3v2Tag` ([issue](https://github.com/Serial-ATA/lofty-rs/issues/99)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/388))
|
||||||
|
- This will allow for generic edits to the iTunes-style parental advisory tag. Note that this will use the
|
||||||
|
numeric representation. For more information, see: https://docs.mp3tag.de/mapping/#itunesadvisory.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **VorbisComments**/**ApeTag**: Verify contents of `ItemKey::FlagCompilation` during `Tag` merge ([PR](https://github.com/Serial-ATA/lofty-rs/pull/387))
|
- **VorbisComments**/**ApeTag**: Verify contents of `ItemKey::FlagCompilation` during `Tag` merge ([PR](https://github.com/Serial-ATA/lofty-rs/pull/387))
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ use crate::id3::v2::util::pairs::{
|
||||||
format_number_pair, set_number, NUMBER_PAIR_KEYS, NUMBER_PAIR_SEPARATOR,
|
format_number_pair, set_number, NUMBER_PAIR_KEYS, NUMBER_PAIR_SEPARATOR,
|
||||||
};
|
};
|
||||||
use crate::id3::v2::KeyValueFrame;
|
use crate::id3::v2::KeyValueFrame;
|
||||||
|
use crate::mp4::AdvisoryRating;
|
||||||
use crate::picture::{Picture, PictureType, TOMBSTONE_PICTURE};
|
use crate::picture::{Picture, PictureType, TOMBSTONE_PICTURE};
|
||||||
use crate::tag::{
|
use crate::tag::{
|
||||||
try_parse_year, Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType,
|
try_parse_year, Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType,
|
||||||
|
@ -713,6 +714,18 @@ fn new_text_frame(id: FrameId<'_>, value: String, flags: FrameFlags) -> Frame<'_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_user_text_frame(description: String, content: String, flags: FrameFlags) -> Frame<'static> {
|
||||||
|
Frame {
|
||||||
|
id: FrameId::Valid(Cow::Borrowed(USER_DEFINED_TEXT_FRAME_ID)),
|
||||||
|
value: FrameValue::UserText(ExtendedTextFrame {
|
||||||
|
encoding: TextEncoding::UTF8,
|
||||||
|
description,
|
||||||
|
content,
|
||||||
|
}),
|
||||||
|
flags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_comment_frame(content: String, flags: FrameFlags) -> Frame<'static> {
|
fn new_comment_frame(content: String, flags: FrameFlags) -> Frame<'static> {
|
||||||
Frame {
|
Frame {
|
||||||
id: FrameId::Valid(Cow::Borrowed(COMMENT_FRAME_ID)),
|
id: FrameId::Valid(Cow::Borrowed(COMMENT_FRAME_ID)),
|
||||||
|
@ -1407,6 +1420,28 @@ impl MergeTag for SplitTagRemainder {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
'rate: {
|
||||||
|
if let Some(advisory_rating) = tag.take_strings(&ItemKey::ParentalAdvisory).next() {
|
||||||
|
let Ok(rating) = advisory_rating.parse::<u8>() else {
|
||||||
|
log::warn!(
|
||||||
|
"Parental advisory rating is not a number: {advisory_rating}, discarding"
|
||||||
|
);
|
||||||
|
break 'rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(parsed_rating) = AdvisoryRating::try_from(rating) else {
|
||||||
|
log::warn!("Parental advisory rating is out of range: {rating}, discarding");
|
||||||
|
break 'rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
merged.frames.push(new_user_text_frame(
|
||||||
|
"ITUNESADVISORY".to_string(),
|
||||||
|
parsed_rating.as_u8().to_string(),
|
||||||
|
FrameFlags::default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Insert all remaining items as single frames and deduplicate as needed
|
// Insert all remaining items as single frames and deduplicate as needed
|
||||||
for item in tag.items {
|
for item in tag.items {
|
||||||
merged.insert_item(item);
|
merged.insert_item(item);
|
||||||
|
|
|
@ -1332,3 +1332,25 @@ fn flag_item_conversion() {
|
||||||
Some("0")
|
Some("0")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn itunes_advisory_roundtrip() {
|
||||||
|
use crate::mp4::{AdvisoryRating, Ilst};
|
||||||
|
|
||||||
|
let mut tag = Ilst::new();
|
||||||
|
tag.set_advisory_rating(AdvisoryRating::Explicit);
|
||||||
|
|
||||||
|
let tag: Tag = tag.into();
|
||||||
|
let tag: Id3v2Tag = tag.into();
|
||||||
|
|
||||||
|
assert_eq!(tag.frames.len(), 1);
|
||||||
|
|
||||||
|
let frame = tag.get_user_text("ITUNESADVISORY");
|
||||||
|
assert!(frame.is_some());
|
||||||
|
assert_eq!(frame.unwrap(), "1");
|
||||||
|
|
||||||
|
let tag: Tag = tag.into();
|
||||||
|
let tag: Ilst = tag.into();
|
||||||
|
|
||||||
|
assert_eq!(tag.advisory_rating(), Some(AdvisoryRating::Explicit));
|
||||||
|
}
|
||||||
|
|
|
@ -654,6 +654,11 @@ impl SplitTag for Ilst {
|
||||||
false // Atom consumed
|
false // Atom consumed
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(rating) = self.advisory_rating() {
|
||||||
|
tag.insert_text(ItemKey::ParentalAdvisory, rating.as_u8().to_string());
|
||||||
|
let _ = self.remove(&ADVISORY_RATING);
|
||||||
|
}
|
||||||
|
|
||||||
(SplitTagRemainder(self), tag)
|
(SplitTagRemainder(self), tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -715,6 +720,29 @@ impl MergeTag for SplitTagRemainder {
|
||||||
data: AtomDataStorage::Single(AtomData::Bool(data)),
|
data: AtomDataStorage::Single(AtomData::Bool(data)),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
ItemKey::ParentalAdvisory => {
|
||||||
|
let Ok(rating) = text.parse::<u8>() else {
|
||||||
|
log::warn!(
|
||||||
|
"Parental advisory rating is not a number: {}, discarding",
|
||||||
|
text
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(parsed_rating) = AdvisoryRating::try_from(rating) else {
|
||||||
|
log::warn!(
|
||||||
|
"Parental advisory rating is out of range: {rating}, discarding"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
merged.atoms.push(Atom {
|
||||||
|
ident: ident.into_owned(),
|
||||||
|
data: AtomDataStorage::Single(AtomData::SignedInteger(i32::from(
|
||||||
|
parsed_rating.as_u8(),
|
||||||
|
))),
|
||||||
|
})
|
||||||
|
},
|
||||||
_ => merged.atoms.push(Atom {
|
_ => merged.atoms.push(Atom {
|
||||||
ident: ident.into_owned(),
|
ident: ident.into_owned(),
|
||||||
data: AtomDataStorage::Single(AtomData::UTF8(text)),
|
data: AtomDataStorage::Single(AtomData::UTF8(text)),
|
||||||
|
|
|
@ -169,6 +169,7 @@ gen_map!(
|
||||||
"TRCK" => TrackNumber,
|
"TRCK" => TrackNumber,
|
||||||
"TRCK" => TrackTotal,
|
"TRCK" => TrackTotal,
|
||||||
"POPM" => Popularimeter,
|
"POPM" => Popularimeter,
|
||||||
|
"ITUNESADVISORY" => ParentalAdvisory,
|
||||||
"TDRC" => RecordingDate,
|
"TDRC" => RecordingDate,
|
||||||
"TDOR" => OriginalReleaseDate,
|
"TDOR" => OriginalReleaseDate,
|
||||||
"TSRC" => Isrc,
|
"TSRC" => Isrc,
|
||||||
|
|
Loading…
Reference in a new issue