ID3v2: Support PRIV frames

This commit is contained in:
Serial 2023-07-18 00:25:45 -04:00 committed by Alex
parent cd3a795956
commit 7b98078317
8 changed files with 106 additions and 6 deletions

View file

@ -1,9 +1,9 @@
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::id3::v2::frame::FrameValue;
use crate::id3::v2::items::{
AttachedPictureFrame, CommentFrame, ExtendedTextFrame, ExtendedUrlFrame, KeyValueFrame,
OwnershipFrame, Popularimeter, RelativeVolumeAdjustmentFrame, TextInformationFrame,
UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame, EventTimingCodesFrame
AttachedPictureFrame, CommentFrame, EventTimingCodesFrame, ExtendedTextFrame, ExtendedUrlFrame,
KeyValueFrame, OwnershipFrame, Popularimeter, PrivateFrame, RelativeVolumeAdjustmentFrame,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
};
use crate::id3::v2::Id3v2Version;
use crate::macros::err;
@ -34,6 +34,7 @@ pub(super) fn parse_content<R: Read>(
"RVA2" => RelativeVolumeAdjustmentFrame::parse(reader, parse_mode)?.map(FrameValue::RelativeVolumeAdjustment),
"OWNE" => OwnershipFrame::parse(reader)?.map(FrameValue::Ownership),
"ETCO" => EventTimingCodesFrame::parse(reader)?.map(FrameValue::EventTimingCodes),
"PRIV" => PrivateFrame::parse(reader)?.map(FrameValue::Private),
_ if id.starts_with('T') => TextInformationFrame::parse(reader, version)?.map(FrameValue::Text),
// Apple proprietary frames
// WFED (Podcast URL), GRP1 (Grouping), MVNM (Movement Name), MVIN (Movement Number)

View file

@ -5,7 +5,7 @@ pub(super) mod read;
use super::items::{
AttachedPictureFrame, CommentFrame, EventTimingCodesFrame, ExtendedTextFrame, ExtendedUrlFrame,
KeyValueFrame, OwnershipFrame, Popularimeter, RelativeVolumeAdjustmentFrame,
KeyValueFrame, OwnershipFrame, Popularimeter, PrivateFrame, RelativeVolumeAdjustmentFrame,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
};
use super::util::upgrade::{upgrade_v2, upgrade_v3};
@ -189,6 +189,8 @@ pub enum FrameValue {
Ownership(OwnershipFrame),
/// Represents an "ETCO" frame
EventTimingCodes(EventTimingCodesFrame),
/// Represents a "PRIV" frame
Private(PrivateFrame),
/// Binary data
///
/// NOTES:
@ -301,6 +303,12 @@ impl From<EventTimingCodesFrame> for FrameValue {
}
}
impl From<PrivateFrame> for FrameValue {
fn from(value: PrivateFrame) -> Self {
Self::Private(value)
}
}
impl FrameValue {
pub(super) fn as_bytes(&self) -> Result<Vec<u8>> {
Ok(match self {
@ -317,6 +325,7 @@ impl FrameValue {
FrameValue::UniqueFileIdentifier(frame) => frame.as_bytes(),
FrameValue::Ownership(frame) => frame.as_bytes()?,
FrameValue::EventTimingCodes(frame) => frame.as_bytes()?,
FrameValue::Private(frame) => frame.as_bytes(),
FrameValue::Binary(binary) => binary.clone(),
})
}

View file

@ -177,7 +177,7 @@ pub struct EventTimingCodesFrame {
/// The events
///
/// Events are guaranteed to be sorted by their timestamps when read. They can be inserted in
/// arbitrary order after the fact, and will be sorted against prior to writing.
/// arbitrary order after the fact, and will be sorted again prior to writing.
pub events: Vec<Event>,
}

View file

@ -9,6 +9,7 @@ mod key_value_frame;
pub(in crate::id3::v2) mod language_frame;
mod ownership_frame;
mod popularimeter;
mod private_frame;
mod relative_volume_adjustment_frame;
mod sync_text;
mod text_information_frame;
@ -25,6 +26,7 @@ pub use key_value_frame::KeyValueFrame;
pub use language_frame::{CommentFrame, UnsynchronizedTextFrame};
pub use ownership_frame::OwnershipFrame;
pub use popularimeter::Popularimeter;
pub use private_frame::PrivateFrame;
pub use relative_volume_adjustment_frame::{
ChannelInformation, ChannelType, RelativeVolumeAdjustmentFrame,
};

View file

@ -0,0 +1,86 @@
use crate::error::Result;
use crate::util::text::{decode_text, encode_text, TextEncoding};
use std::io::Read;
/// An `ID3v2` private frame
///
/// This frame is used to contain information from a software producer that
/// its program uses and does not fit into the other frames.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct PrivateFrame {
/// A URL containing an email address, or a link to a location where an email can be found,
/// that belongs to the organisation responsible for the frame
pub owner: String,
/// Binary data
pub private_data: Vec<u8>,
}
impl PrivateFrame {
/// Read an [`PrivateFrame`]
///
/// NOTE: This expects the frame header to have already been skipped
pub fn parse<R>(reader: &mut R) -> Result<Option<Self>>
where
R: Read,
{
let Ok(owner) = decode_text(reader, TextEncoding::Latin1, true) else {
return Ok(None);
};
let owner = owner.content;
let mut private_data = Vec::new();
reader.read_to_end(&mut private_data)?;
Ok(Some(PrivateFrame {
owner,
private_data,
}))
}
/// Convert an [`PrivateFrame`] to a byte vec
pub fn as_bytes(&self) -> Vec<u8> {
let Self {
owner,
private_data,
} = self;
let mut content = Vec::with_capacity(owner.len() + private_data.len());
content.extend(encode_text(owner.as_str(), TextEncoding::Latin1, true));
content.extend_from_slice(private_data);
content
}
}
#[cfg(test)]
mod tests {
use crate::id3::v2::PrivateFrame;
fn expected() -> PrivateFrame {
PrivateFrame {
owner: String::from("foo@bar.com"),
private_data: String::from("some data").into_bytes(),
}
}
#[test]
fn priv_decode() {
let cont = crate::tag::utils::test_utils::read_path("tests/tags/assets/id3v2/test.priv");
let parsed_priv = PrivateFrame::parse(&mut &cont[..]).unwrap().unwrap();
assert_eq!(parsed_priv, expected());
}
#[test]
fn priv_encode() {
let encoded = expected().as_bytes();
let expected_bytes =
crate::tag::utils::test_utils::read_path("tests/tags/assets/id3v2/test.priv");
assert_eq!(encoded, expected_bytes);
}
}

View file

@ -929,7 +929,8 @@ impl SplitTag for Id3v2Tag {
| FrameValue::UniqueFileIdentifier(_)
| FrameValue::RelativeVolumeAdjustment(_)
| FrameValue::Ownership(_)
| FrameValue::EventTimingCodes(_) => {
| FrameValue::EventTimingCodes(_)
| FrameValue::Private(_) => {
return true; // Keep unsupported frame
},
};

View file

@ -53,6 +53,7 @@ fn verify_frame(frame: &FrameRef<'_>) -> Result<()> {
FrameValue::RelativeVolumeAdjustment(_) => "RelativeVolumeAdjustment",
FrameValue::Ownership(_) => "Ownership",
FrameValue::EventTimingCodes(_) => "EventTimingCodes",
FrameValue::Private(_) => "Private",
FrameValue::Binary(_) => "Binary",
},
))

Binary file not shown.