diff --git a/src/mp4/ilst/atom.rs b/src/mp4/ilst/atom.rs index 1131c81d..fdc49682 100644 --- a/src/mp4/ilst/atom.rs +++ b/src/mp4/ilst/atom.rs @@ -63,6 +63,38 @@ pub enum AtomData { }, } +#[derive(Clone, Copy, Debug, PartialEq)] +/// The parental advisory rating +pub enum AdvisoryRating { + /// A rating of 0 + None, + /// A rating of 2 + Clean, + /// A rating of (1 || > 2) + Explicit, +} + +impl AdvisoryRating { + /// Returns the rating as it appears in the `rtng` atom + pub fn as_u8(&self) -> u8 { + match self { + AdvisoryRating::None => 0, + AdvisoryRating::Clean => 2, + AdvisoryRating::Explicit => 4, + } + } +} + +impl From for AdvisoryRating { + fn from(input: u8) -> Self { + match input { + 0 => Self::None, + 2 => Self::Clean, + _ => Self::Explicit, + } + } +} + pub(crate) struct AtomRef<'a> { pub(crate) ident: AtomIdentRef<'a>, pub(crate) data: AtomDataRef<'a>, diff --git a/src/mp4/ilst/mod.rs b/src/mp4/ilst/mod.rs index 923b4c9b..8d85608e 100644 --- a/src/mp4/ilst/mod.rs +++ b/src/mp4/ilst/mod.rs @@ -3,12 +3,13 @@ pub(super) mod constants; pub(super) mod read; pub(crate) mod write; +use super::constants::BE_SIGNED_INTEGER; use super::AtomIdent; use crate::error::{LoftyError, Result}; use crate::types::item::{ItemKey, ItemValue, TagItem}; use crate::types::picture::{Picture, PictureType}; use crate::types::tag::{Accessor, Tag, TagIO, TagType}; -use atom::{Atom, AtomData, AtomDataRef, AtomIdentRef, AtomRef}; +use atom::{AdvisoryRating, Atom, AtomData, AtomDataRef, AtomIdentRef, AtomRef}; use std::convert::TryInto; use std::fs::{File, OpenOptions}; @@ -142,6 +143,34 @@ impl Ilst { .retain(|a| !matches!(a.data(), AtomData::Picture(_))) } + /// Returns the parental advisory rating according to the `rtng` atom + pub fn advisory_rating(&self) -> Option { + if let Some(Atom { data, .. }) = self.atom(&AtomIdent::Fourcc(*b"rtng")) { + let rating = match data { + AtomData::SignedInteger(si) => *si as u8, + AtomData::Unknown { data: c, .. } if !c.is_empty() => c[0], + _ => return None, + }; + + return Some(AdvisoryRating::from(rating)); + } + + None + } + + /// Sets the advisory rating + pub fn set_advisory_rating(&mut self, advisory_rating: AdvisoryRating) { + let byte = advisory_rating.as_u8(); + + self.replace_atom(Atom { + ident: AtomIdent::Fourcc(*b"rtng"), + data: AtomData::Unknown { + code: BE_SIGNED_INTEGER, + data: vec![byte], + }, + }) + } + /// Returns the track number pub fn track_number(&self) -> Option { self.extract_number(*b"trkn", 4) @@ -188,7 +217,8 @@ impl TagIO for Ilst { } fn save_to_path>(&self, path: P) -> std::result::Result<(), Self::Err> { - self.save_to(&mut OpenOptions::new().read(true).write(true).open(path)?) + let mut f = OpenOptions::new().read(true).write(true).open(path)?; + self.save_to(&mut f) } fn save_to(&self, file: &mut File) -> std::result::Result<(), Self::Err> { @@ -410,9 +440,14 @@ fn item_key_to_ident(key: &ItemKey) -> Option> { #[cfg(test)] mod tests { - use crate::mp4::{Atom, AtomData, AtomIdent, Ilst}; + use crate::mp4::{AdvisoryRating, Atom, AtomData, AtomIdent, Ilst}; use crate::{ItemKey, Tag, TagIO, TagType}; + fn read_ilst(path: &str) -> Ilst { + let tag = crate::tag_utils::test_utils::read_path(path); + super::read::parse_ilst(&mut &tag[..], tag.len() as u64).unwrap() + } + fn verify_atom(ilst: &Ilst, ident: [u8; 4], data: &AtomData) { let atom = ilst.atom(&AtomIdent::Fourcc(ident)).unwrap(); assert_eq!(atom.data(), data); @@ -481,8 +516,7 @@ mod tests { #[test] fn ilst_re_read() { - let tag = crate::tag_utils::test_utils::read_path("tests/tags/assets/test.ilst"); - let parsed_tag = super::read::parse_ilst(&mut &tag[..], tag.len() as u64).unwrap(); + let parsed_tag = read_ilst("tests/tags/assets/test.ilst"); let mut writer = Vec::new(); parsed_tag.dump_to(&mut writer).unwrap(); @@ -562,9 +596,7 @@ mod tests { #[test] fn issue_34() { - let tag_bytes = crate::tag_utils::test_utils::read_path("tests/tags/assets/issue_34.ilst"); - - let ilst = super::read::parse_ilst(&mut &tag_bytes[..], tag_bytes.len() as u64).unwrap(); + let ilst = read_ilst("tests/tags/assets/issue_34.ilst"); verify_atom( &ilst, @@ -580,4 +612,17 @@ mod tests { }, ) } + + #[test] + fn advisory_rating() { + let ilst = read_ilst("tests/tags/assets/advisory_rating.ilst"); + + verify_atom( + &ilst, + *b"\xa9ART", + &AtomData::UTF8(String::from("Foo artist")), + ); + + assert_eq!(ilst.advisory_rating(), Some(AdvisoryRating::Explicit)); + } } diff --git a/src/mp4/mod.rs b/src/mp4/mod.rs index b69bb8d6..aa2c1789 100644 --- a/src/mp4/mod.rs +++ b/src/mp4/mod.rs @@ -23,7 +23,7 @@ cfg_if::cfg_if! { pub(crate) mod ilst; pub use atom_info::AtomIdent; - pub use ilst::atom::{Atom, AtomData}; + pub use ilst::atom::{Atom, AtomData, AdvisoryRating}; pub use ilst::Ilst; /// This module contains the codes for all of the [Well-known data types](https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW34) diff --git a/tests/tags/assets/advisory_rating.ilst b/tests/tags/assets/advisory_rating.ilst new file mode 100644 index 00000000..33720092 Binary files /dev/null and b/tests/tags/assets/advisory_rating.ilst differ