mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-11-10 14:44:22 +00:00
ID3v2: Support PRIV frames
This commit is contained in:
parent
cd3a795956
commit
7b98078317
8 changed files with 106 additions and 6 deletions
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
86
src/id3/v2/items/private_frame.rs
Normal file
86
src/id3/v2/items/private_frame.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
))
|
||||
|
|
BIN
tests/tags/assets/id3v2/test.priv
Normal file
BIN
tests/tags/assets/id3v2/test.priv
Normal file
Binary file not shown.
Loading…
Reference in a new issue