Add support for TIPL frames

This commit is contained in:
Frieder Hannenheim 2023-07-02 15:09:31 +02:00 committed by Alex
parent 307ef146d1
commit fe8883ccd5
6 changed files with 87 additions and 0 deletions

View file

@ -3,6 +3,7 @@ use crate::id3::v2::frame::FrameValue;
use crate::id3::v2::items::{ use crate::id3::v2::items::{
AttachedPictureFrame, CommentFrame, ExtendedTextFrame, ExtendedUrlFrame, Popularimeter, AttachedPictureFrame, CommentFrame, ExtendedTextFrame, ExtendedUrlFrame, Popularimeter,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame, TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
KeyValueFrame
}; };
use crate::id3::v2::Id3v2Version; use crate::id3::v2::Id3v2Version;
use crate::macros::err; use crate::macros::err;
@ -28,6 +29,7 @@ pub(super) fn parse_content<R: Read>(
"WXXX" => ExtendedUrlFrame::parse(reader, version)?.map(FrameValue::UserUrl), "WXXX" => ExtendedUrlFrame::parse(reader, version)?.map(FrameValue::UserUrl),
"COMM" => CommentFrame::parse(reader, version)?.map(FrameValue::Comment), "COMM" => CommentFrame::parse(reader, version)?.map(FrameValue::Comment),
"USLT" => UnsynchronizedTextFrame::parse(reader, version)?.map(FrameValue::UnsynchronizedText), "USLT" => UnsynchronizedTextFrame::parse(reader, version)?.map(FrameValue::UnsynchronizedText),
"TIPL" => KeyValueFrame::parse(reader, version)?.map(FrameValue::KeyValueFrame),
"UFID" => UniqueFileIdentifierFrame::parse(reader, parse_mode)?.map(FrameValue::UniqueFileIdentifier), "UFID" => UniqueFileIdentifierFrame::parse(reader, parse_mode)?.map(FrameValue::UniqueFileIdentifier),
_ if id.starts_with('T') => TextInformationFrame::parse(reader, version)?.map(FrameValue::Text), _ if id.starts_with('T') => TextInformationFrame::parse(reader, version)?.map(FrameValue::Text),
// Apple proprietary frames // Apple proprietary frames

View file

@ -6,6 +6,7 @@ pub(super) mod read;
use super::items::{ use super::items::{
AttachedPictureFrame, CommentFrame, ExtendedTextFrame, ExtendedUrlFrame, Popularimeter, AttachedPictureFrame, CommentFrame, ExtendedTextFrame, ExtendedUrlFrame, Popularimeter,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame, TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
KeyValueFrame
}; };
use super::util::upgrade::{upgrade_v2, upgrade_v3}; use super::util::upgrade::{upgrade_v2, upgrade_v3};
use super::Id3v2Version; use super::Id3v2Version;
@ -178,6 +179,8 @@ pub enum FrameValue {
Picture(AttachedPictureFrame), Picture(AttachedPictureFrame),
/// Represents a "POPM" frame /// Represents a "POPM" frame
Popularimeter(Popularimeter), Popularimeter(Popularimeter),
/// Represents an "IPLS" or "TPIL" frame
KeyValueFrame(KeyValueFrame),
/// Binary data /// Binary data
/// ///
/// NOTES: /// NOTES:
@ -262,6 +265,12 @@ impl From<Popularimeter> for FrameValue {
} }
} }
impl From<KeyValueFrame> for FrameValue {
fn from(value: KeyValueFrame) -> Self {
Self::KeyValueFrame(value)
}
}
impl From<UniqueFileIdentifierFrame> for FrameValue { impl From<UniqueFileIdentifierFrame> for FrameValue {
fn from(value: UniqueFileIdentifierFrame) -> Self { fn from(value: UniqueFileIdentifierFrame) -> Self {
Self::UniqueFileIdentifier(value) Self::UniqueFileIdentifier(value)
@ -279,6 +288,7 @@ impl FrameValue {
FrameValue::Url(link) => link.as_bytes(), FrameValue::Url(link) => link.as_bytes(),
FrameValue::Picture(attached_picture) => attached_picture.as_bytes(Id3v2Version::V4)?, FrameValue::Picture(attached_picture) => attached_picture.as_bytes(Id3v2Version::V4)?,
FrameValue::Popularimeter(popularimeter) => popularimeter.as_bytes(), FrameValue::Popularimeter(popularimeter) => popularimeter.as_bytes(),
FrameValue::KeyValueFrame(content) => content.as_bytes(),
FrameValue::Binary(binary) => binary.clone(), FrameValue::Binary(binary) => binary.clone(),
FrameValue::UniqueFileIdentifier(frame) => frame.as_bytes(), FrameValue::UniqueFileIdentifier(frame) => frame.as_bytes(),
}) })

View file

@ -0,0 +1,69 @@
use crate::error::Result;
use crate::id3::v2::frame::content::verify_encoding;
use crate::id3::v2::Id3v2Version;
use crate::util::text::{decode_text, encode_text, TextEncoding};
use byteorder::ReadBytesExt;
use std::io::Read;
/// An `ID3v2` text frame
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct KeyValueFrame {
/// The encoding of the text
pub encoding: TextEncoding,
/// The text itself
pub values: Vec<(String, String)>,
}
impl KeyValueFrame {
/// Read an [`TextInformationFrame`] from a slice
///
/// NOTE: This expects the frame header to have already been skipped
///
/// # Errors
///
/// * Unable to decode the text
///
/// ID3v2.2:
///
/// * The encoding is not [`TextEncoding::Latin1`] or [`TextEncoding::UTF16`]
pub fn parse<R>(reader: &mut R, version: Id3v2Version) -> Result<Option<Self>>
where
R: Read,
{
let Ok(encoding_byte) = reader.read_u8() else {
return Ok(None);
};
let encoding = verify_encoding(encoding_byte, version)?;
let mut values = vec![];
loop {
let key = decode_text(reader, encoding, true)?;
let value = decode_text(reader, encoding, true)?;
if key.bytes_read == 0 || value.bytes_read == 0 {
break;
}
values.push((key.content, value.content));
}
Ok(Some(Self{ encoding, values }))
}
/// Convert an [`TextInformationFrame`] to a byte vec
pub fn as_bytes(&self) -> Vec<u8> {
let mut content = vec![];
for (key, value) in &self.values {
content.append(&mut encode_text(key, self.encoding, true));
content.append(&mut encode_text(value, self.encoding, true));
}
content.insert(0, self.encoding as u8);
content
}
}

View file

@ -9,6 +9,7 @@ mod popularimeter;
mod sync_text; mod sync_text;
mod text_information_frame; mod text_information_frame;
mod url_link_frame; mod url_link_frame;
mod key_value_frame;
pub use attached_picture_frame::AttachedPictureFrame; pub use attached_picture_frame::AttachedPictureFrame;
pub use audio_text_frame::{scramble, AudioTextFrame, AudioTextFrameFlags}; pub use audio_text_frame::{scramble, AudioTextFrame, AudioTextFrameFlags};
@ -21,3 +22,4 @@ pub use popularimeter::Popularimeter;
pub use sync_text::{SyncTextContentType, SynchronizedText, TimestampFormat}; pub use sync_text::{SyncTextContentType, SynchronizedText, TimestampFormat};
pub use text_information_frame::TextInformationFrame; pub use text_information_frame::TextInformationFrame;
pub use url_link_frame::UrlLinkFrame; pub use url_link_frame::UrlLinkFrame;
pub use key_value_frame::KeyValueFrame;

View file

@ -788,6 +788,9 @@ impl SplitTag for Id3v2Tag {
FrameValue::Popularimeter(popularimeter) => { FrameValue::Popularimeter(popularimeter) => {
ItemValue::Binary(popularimeter.as_bytes()) ItemValue::Binary(popularimeter.as_bytes())
}, },
FrameValue::KeyValueFrame(_) => {
return true; // Keep frame
},
FrameValue::Binary(binary) => ItemValue::Binary(std::mem::take(binary)), FrameValue::Binary(binary) => ItemValue::Binary(std::mem::take(binary)),
FrameValue::UniqueFileIdentifier(_) => { FrameValue::UniqueFileIdentifier(_) => {
return true; // Keep unsupported frame return true; // Keep unsupported frame

View file

@ -47,6 +47,7 @@ fn verify_frame(frame: &FrameRef<'_>) -> Result<()> {
FrameValue::UserUrl(_) => "UserUrl", FrameValue::UserUrl(_) => "UserUrl",
FrameValue::Picture { .. } => "Picture", FrameValue::Picture { .. } => "Picture",
FrameValue::Popularimeter(_) => "Popularimeter", FrameValue::Popularimeter(_) => "Popularimeter",
FrameValue::KeyValueFrame(_) => "KeyValueData",
FrameValue::Binary(_) => "Binary", FrameValue::Binary(_) => "Binary",
FrameValue::UniqueFileIdentifier(_) => "UniqueFileIdentifier", FrameValue::UniqueFileIdentifier(_) => "UniqueFileIdentifier",
}, },