mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-13 14:12:31 +00:00
Add support for TIPL frames
This commit is contained in:
parent
307ef146d1
commit
fe8883ccd5
6 changed files with 87 additions and 0 deletions
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
})
|
})
|
||||||
|
|
69
src/id3/v2/items/key_value_frame.rs
Normal file
69
src/id3/v2/items/key_value_frame.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue