Skip to content

Commit

Permalink
feat: Add support for IPLS frame
Browse files Browse the repository at this point in the history
  • Loading branch information
Holzhaus committed Nov 11, 2024
1 parent fd9f62e commit 1697036
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 8 deletions.
63 changes: 63 additions & 0 deletions src/frame/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub enum Content {
TableOfContents(TableOfContents),
/// A value containing the parsed contents of a unique file identifier frame (UFID).
UniqueFileIdentifier(UniqueFileIdentifier),
/// A value containing the parsed contents of an involved people list IPLS
InvolvedPeopleList(InvolvedPeopleList),
/// A value containing the bytes of a currently unknown frame type.
///
/// Users that wish to write custom decoders must use [`Content::to_unknown`] instead of
Expand Down Expand Up @@ -114,6 +116,19 @@ impl Content {
Self::UniqueFileIdentifier(unique_file_identifier) => Comparable(vec![Cow::Borrowed(
unique_file_identifier.owner_identifier.as_bytes(),
)]),
Self::InvolvedPeopleList(involved_people_list) => Comparable(
involved_people_list
.items
.iter()
.flat_map(|item| {
[
Cow::Borrowed(item.involvement.as_bytes()),
Cow::Borrowed(item.involvee.as_bytes()),
]
.into_iter()
})
.collect(),
),
Self::Unknown(_) => Incomparable,
}
}
Expand Down Expand Up @@ -262,6 +277,14 @@ impl Content {
}
}

/// Returns the `InvolvedPeopleList` or None if the value is not `IPLS`
pub fn involved_people_list(&self) -> Option<&InvolvedPeopleList> {
match self {
Content::InvolvedPeopleList(involved_people_list) => Some(involved_people_list),
_ => None,
}
}

/// Returns the `Unknown` or None if the value is not `Unknown`.
#[deprecated(note = "Use to_unknown")]
pub fn unknown(&self) -> Option<&[u8]> {
Expand Down Expand Up @@ -308,6 +331,9 @@ impl fmt::Display for Content {
Content::UniqueFileIdentifier(unique_file_identifier) => {
write!(f, "{}", unique_file_identifier)
}
Content::InvolvedPeopleList(involved_people_list) => {
write!(f, "{}", involved_people_list)
}
Content::Unknown(unknown) => write!(f, "{}", unknown),
}
}
Expand Down Expand Up @@ -850,6 +876,43 @@ impl From<UniqueFileIdentifier> for Frame {
}
}

/// The parsed contents of an IPLS frame.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct InvolvedPeopleList {
/// Items in the People List.
pub items: Vec<InvolvedPeopleListItem>,
}

/// The parsed contents of an IPLS frame.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct InvolvedPeopleListItem {
/// Role of the involved person.
pub involvement: String,
/// Name of the involved person.
pub involvee: String,
}

impl fmt::Display for InvolvedPeopleList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let item_count = self.items.len();
for (i, item) in self.items.iter().enumerate() {
if i == 0 && item_count > 1 {
write!(f, "{}: {} / ", item.involvement, item.involvee)?;
} else {
write!(f, "{}: {}", item.involvement, item.involvee)?;
}
}

Ok(())
}
}

impl From<InvolvedPeopleList> for Frame {
fn from(c: InvolvedPeopleList) -> Self {
Self::with_content("IPLS", Content::InvolvedPeopleList(c))
}
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(missing_docs)]
pub struct TableOfContents {
Expand Down
10 changes: 6 additions & 4 deletions src/frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use std::fmt;
use std::str;

pub use self::content::{
Chapter, Comment, Content, EncapsulatedObject, ExtendedLink, ExtendedText, Lyrics,
MpegLocationLookupTable, MpegLocationLookupTableReference, Picture, PictureType, Popularimeter,
Private, SynchronisedLyrics, SynchronisedLyricsType, TableOfContents, TimestampFormat,
UniqueFileIdentifier, Unknown,
Chapter, Comment, Content, EncapsulatedObject, ExtendedLink, ExtendedText, InvolvedPeopleList,
InvolvedPeopleListItem, Lyrics, MpegLocationLookupTable, MpegLocationLookupTableReference,
Picture, PictureType, Popularimeter, Private, SynchronisedLyrics, SynchronisedLyricsType,
TableOfContents, TimestampFormat, UniqueFileIdentifier, Unknown,
};
pub use self::timestamp::Timestamp;

Expand Down Expand Up @@ -84,6 +84,7 @@ impl Frame {
("APIC", Content::Picture(_)) => Ok(()),
("CHAP", Content::Chapter(_)) => Ok(()),
("MLLT", Content::MpegLocationLookupTable(_)) => Ok(()),
("IPLS", Content::InvolvedPeopleList(_)) => Ok(()),
("PRIV", Content::Private(_)) => Ok(()),
("CTOC", Content::TableOfContents(_)) => Ok(()),
("UFID", Content::UniqueFileIdentifier(_)) => Ok(()),
Expand All @@ -105,6 +106,7 @@ impl Frame {
Content::Private(_) => "PrivateFrame",
Content::TableOfContents(_) => "TableOfContents",
Content::UniqueFileIdentifier(_) => "UFID",
Content::InvolvedPeopleList(_) => "InvolvedPeopleList",
Content::Unknown(_) => "Unknown",
};
Err(Error::new(
Expand Down
58 changes: 54 additions & 4 deletions src/stream/frame/content.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::frame::{
Chapter, Comment, Content, EncapsulatedObject, ExtendedLink, ExtendedText, Lyrics,
MpegLocationLookupTable, MpegLocationLookupTableReference, Picture, PictureType, Popularimeter,
Private, SynchronisedLyrics, SynchronisedLyricsType, TableOfContents, TimestampFormat,
UniqueFileIdentifier, Unknown,
Chapter, Comment, Content, EncapsulatedObject, ExtendedLink, ExtendedText, InvolvedPeopleList,
InvolvedPeopleListItem, Lyrics, MpegLocationLookupTable, MpegLocationLookupTableReference,
Picture, PictureType, Popularimeter, Private, SynchronisedLyrics, SynchronisedLyricsType,
TableOfContents, TimestampFormat, UniqueFileIdentifier, Unknown,
};
use crate::stream::encoding::Encoding;
use crate::stream::frame;
Expand Down Expand Up @@ -306,6 +306,21 @@ impl<W: io::Write> Encoder<W> {
Ok(())
}

fn involved_people_list(
&mut self,
content: &InvolvedPeopleList,
) -> crate::Result<()> {
for (i, item) in content.items.iter().enumerate() {
if i != 0 {
self.byte(0)?;
}
self.bytes(item.involvement.as_bytes())?;
self.byte(0)?;
self.bytes(item.involvee.as_bytes())?;
}
Ok(())
}

fn table_of_contents_content(&mut self, content: &TableOfContents) -> crate::Result<()> {
self.string_with_other_encoding(Encoding::Latin1, &content.element_id)?;
self.byte(0)?;
Expand Down Expand Up @@ -361,6 +376,7 @@ pub fn encode(
Content::Private(c) => encoder.private_content(c)?,
Content::TableOfContents(c) => encoder.table_of_contents_content(c)?,
Content::UniqueFileIdentifier(c) => encoder.unique_file_identifier_content(c)?,
Content::InvolvedPeopleList(c) => encoder.involved_people_list(c)?,
Content::Unknown(c) => encoder.bytes(&c.data)?,
};

Expand Down Expand Up @@ -411,6 +427,7 @@ pub fn decode(
encoding = Some(enc);
Ok(content)
}
"IPLS" | "IPL" | "TMCL" | "TIPL" => decoder.involved_people_list(),
id if id.starts_with('T') => decoder.text_content(),
id if id.starts_with('W') => decoder.link_content(),
"GRP1" => decoder.text_content(),
Expand Down Expand Up @@ -513,6 +530,39 @@ impl<'a> Decoder<'a> {
Ok(Content::Text(text))
}

fn involved_people_list(self) -> crate::Result<Content> {
self.text_content().and_then(|content| {
if let Content::Text(text) = content {
let string_count = text.split('\0').count();
if (string_count & 1) == 1 {
return Err(Error {
kind: ErrorKind::Parsing,
description: "Involved People List contains an odd number of strings"
.to_string(),
partial_tag: None,
});
}
let items = text
.split('\0')
.enumerate()
.filter_map(|(i, s)| (i & 1 == 0).then_some(s))
.zip(
text.split('\0')
.enumerate()
.filter_map(|(i, s)| (i & 1 == 1).then_some(s)),
)
.map(|(involvement, involvee)| InvolvedPeopleListItem {
involvement: involvement.to_string(),
involvee: involvee.to_string(),
})
.collect::<Vec<_>>();
Ok(Content::InvolvedPeopleList(InvolvedPeopleList { items }))
} else {
Ok(content)
}
})
}

fn link_content(self) -> crate::Result<Content> {
Ok(Content::Link(String::from_utf8(self.r.to_vec())?))
}
Expand Down

0 comments on commit 1697036

Please sign in to comment.