Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ID3v2: Support more frames #236

Merged
merged 12 commits into from
Jul 20, 2023
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## Added
- **ID3v2**: Support for "RVA2", "OWNE", "ETCO", and "PRIV" frames through
`id3::v2::{RelativeVolumeAdjustmentFrame, OwnershipFrame, EventTimingCodesFrame, PrivateFrame}`

## Changed
- **ID3v2**: For spec compliance, `Id3v2Tag::insert` will now check for frames that are only meant to appear
in a tag once and remove them. Those frames are: "MCDI", "ETCO", "MLLT", "SYTC", "RVRB", "PCNT", "RBUF", "POSS", "OWNE", "SEEK", and "ASPI".

## [0.15.0] - 2023-07-11

## Added
Expand Down
9 changes: 9 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ pub enum Id3v2ErrorKind {
BadSyncText,
/// Arises when decoding a [`UniqueFileIdentifierFrame`](crate::id3::v2::UniqueFileIdentifierFrame) with no owner
MissingUfidOwner,
/// Arises when decoding a [`RelativeVolumeAdjustmentFrame`](crate::id3::v2::RelativeVolumeAdjustmentFrame) with an invalid channel type
BadRva2ChannelType,
/// Arises when decoding a [`TimestampFormat`](crate::id3::v2::TimestampFormat) with an invalid type
BadTimestampFormat,

// Compression
#[cfg(feature = "id3v2_compression_support")]
Expand Down Expand Up @@ -159,6 +163,11 @@ impl Display for Id3v2ErrorKind {
},
Self::BadSyncText => write!(f, "Encountered invalid data in SYLT frame"),
Self::MissingUfidOwner => write!(f, "Missing owner in UFID frame"),
Self::BadRva2ChannelType => write!(f, "Encountered invalid channel type in RVA2 frame"),
Self::BadTimestampFormat => write!(
f,
"Encountered an invalid timestamp format in a synchronized frame"
),

// Compression
#[cfg(feature = "id3v2_compression_support")]
Expand Down
10 changes: 7 additions & 3 deletions src/id3/v2/frame/content.rs
Original file line number Diff line number Diff line change
@@ -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,
Popularimeter, TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame,
UrlLinkFrame,
AttachedPictureFrame, CommentFrame, EventTimingCodesFrame, ExtendedTextFrame, ExtendedUrlFrame,
KeyValueFrame, OwnershipFrame, Popularimeter, PrivateFrame, RelativeVolumeAdjustmentFrame,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
};
use crate::id3::v2::Id3v2Version;
use crate::macros::err;
Expand Down Expand Up @@ -31,6 +31,10 @@ pub(super) fn parse_content<R: Read>(
"USLT" => UnsynchronizedTextFrame::parse(reader, version)?.map(FrameValue::UnsynchronizedText),
"TIPL" | "TMCL" => KeyValueFrame::parse(reader, version)?.map(FrameValue::KeyValue),
"UFID" => UniqueFileIdentifierFrame::parse(reader, parse_mode)?.map(FrameValue::UniqueFileIdentifier),
"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)
Expand Down
69 changes: 63 additions & 6 deletions src/id3/v2/frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pub(super) mod id;
pub(super) mod read;

use super::items::{
AttachedPictureFrame, CommentFrame, ExtendedTextFrame, ExtendedUrlFrame, KeyValueFrame,
Popularimeter, TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame,
UrlLinkFrame,
AttachedPictureFrame, CommentFrame, EventTimingCodesFrame, ExtendedTextFrame, ExtendedUrlFrame,
KeyValueFrame, OwnershipFrame, Popularimeter, PrivateFrame, RelativeVolumeAdjustmentFrame,
TextInformationFrame, UniqueFileIdentifierFrame, UnsynchronizedTextFrame, UrlLinkFrame,
};
use super::util::upgrade::{upgrade_v2, upgrade_v3};
use super::Id3v2Version;
Expand Down Expand Up @@ -181,6 +181,16 @@ pub enum FrameValue {
Popularimeter(Popularimeter),
/// Represents an "IPLS" or "TPIL" frame
KeyValue(KeyValueFrame),
/// Represents an "RVA2" frame
RelativeVolumeAdjustment(RelativeVolumeAdjustmentFrame),
/// Unique file identifier
UniqueFileIdentifier(UniqueFileIdentifierFrame),
Copy link
Contributor

@uklotzde uklotzde Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. This is used by many MusicBrainz tags needed for http://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#id21

/// Represents an "OWNE" frame
Ownership(OwnershipFrame),
/// Represents an "ETCO" frame
EventTimingCodes(EventTimingCodesFrame),
/// Represents a "PRIV" frame
Private(PrivateFrame),
/// Binary data
///
/// NOTES:
Expand All @@ -190,8 +200,6 @@ pub enum FrameValue {
/// * This is used for **all** frames with an ID of [`FrameId::Outdated`]
/// * This is used for unknown frames
Binary(Vec<u8>),
/// Unique file identifier
UniqueFileIdentifier(UniqueFileIdentifierFrame),
}

impl TryFrom<ItemValue> for FrameValue {
Expand Down Expand Up @@ -271,12 +279,36 @@ impl From<KeyValueFrame> for FrameValue {
}
}

impl From<RelativeVolumeAdjustmentFrame> for FrameValue {
fn from(value: RelativeVolumeAdjustmentFrame) -> Self {
Self::RelativeVolumeAdjustment(value)
}
}

impl From<UniqueFileIdentifierFrame> for FrameValue {
fn from(value: UniqueFileIdentifierFrame) -> Self {
Self::UniqueFileIdentifier(value)
}
}

impl From<OwnershipFrame> for FrameValue {
fn from(value: OwnershipFrame) -> Self {
Self::Ownership(value)
}
}

impl From<EventTimingCodesFrame> for FrameValue {
fn from(value: EventTimingCodesFrame) -> Self {
Self::EventTimingCodes(value)
}
}

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 {
Expand All @@ -289,10 +321,35 @@ impl FrameValue {
FrameValue::Picture(attached_picture) => attached_picture.as_bytes(Id3v2Version::V4)?,
FrameValue::Popularimeter(popularimeter) => popularimeter.as_bytes(),
FrameValue::KeyValue(content) => content.as_bytes(),
FrameValue::Binary(binary) => binary.clone(),
FrameValue::RelativeVolumeAdjustment(frame) => frame.as_bytes(),
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(),
})
}

/// Used for errors in write::frame::verify_frame
pub(super) fn name(&self) -> &'static str {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, looks like they have some useful derives. There are probably a few places they could be used. I might look into it eventually.

match self {
FrameValue::Comment(_) => "Comment",
FrameValue::UnsynchronizedText(_) => "UnsynchronizedText",
FrameValue::Text { .. } => "Text",
FrameValue::UserText(_) => "UserText",
FrameValue::Url(_) => "Url",
FrameValue::UserUrl(_) => "UserUrl",
FrameValue::Picture { .. } => "Picture",
FrameValue::Popularimeter(_) => "Popularimeter",
FrameValue::KeyValue(_) => "KeyValue",
FrameValue::UniqueFileIdentifier(_) => "UniqueFileIdentifier",
FrameValue::RelativeVolumeAdjustment(_) => "RelativeVolumeAdjustment",
FrameValue::Ownership(_) => "Ownership",
FrameValue::EventTimingCodes(_) => "EventTimingCodes",
FrameValue::Private(_) => "Private",
FrameValue::Binary(_) => "Binary",
}
}
}

/// Various flags to describe the content of an item
Expand Down
Loading