ID3v2: Introduce ExtendedUrlFrame

This commit is contained in:
Serial 2023-04-10 15:59:56 -04:00 committed by Alex
parent a126dfa196
commit d0b9a50d03
7 changed files with 74 additions and 26 deletions

View file

@ -1,7 +1,7 @@
use crate::error::{ID3v2Error, ID3v2ErrorKind, LoftyError, Result};
use crate::id3::v2::frame::FrameValue;
use crate::id3::v2::items::{
ExtendedTextFrame, LanguageFrame, Popularimeter, UniqueFileIdentifierFrame,
ExtendedTextFrame, LanguageFrame, Popularimeter, UniqueFileIdentifierFrame, ExtendedUrlFrame
};
use crate::id3::v2::ID3v2Version;
use crate::macros::err;
@ -73,7 +73,7 @@ fn parse_user_defined(
Ok(Some(if link {
let content = decode_text(content, TextEncoding::Latin1, false)?.unwrap_or_default();
FrameValue::UserURL(ExtendedTextFrame {
FrameValue::UserURL(ExtendedUrlFrame {
encoding,
description,
content,

View file

@ -3,19 +3,19 @@ mod header;
pub(super) mod id;
pub(super) mod read;
use super::items::{ExtendedTextFrame, LanguageFrame, Popularimeter, UniqueFileIdentifierFrame};
use super::items::{
ExtendedTextFrame, ExtendedUrlFrame, LanguageFrame, Popularimeter, UniqueFileIdentifierFrame,
};
use super::util::upgrade::{upgrade_v2, upgrade_v3};
use super::ID3v2Version;
use crate::error::{ID3v2Error, ID3v2ErrorKind, LoftyError, Result};
use crate::id3::v2::util::upgrade::{upgrade_v2, upgrade_v3};
use crate::id3::v2::ID3v2Version;
use crate::picture::Picture;
use crate::tag::item::{ItemValue, TagItem};
use crate::tag::item::{ItemKey, ItemValue, TagItem};
use crate::tag::TagType;
use crate::util::text::{encode_text, TextEncoding};
use crate::ItemKey;
use id::FrameID;
use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
use std::hash::{Hash, Hasher};
@ -185,8 +185,8 @@ pub enum FrameValue {
URL(String),
/// Represents a "WXXX" frame
///
/// Due to the amount of information needed, it is contained in a separate struct, [`ExtendedTextFrame`]
UserURL(ExtendedTextFrame),
/// Due to the amount of information needed, it is contained in a separate struct, [`ExtendedUrlFrame`]
UserURL(ExtendedUrlFrame),
/// Represents an "APIC" or "PIC" frame
Picture {
/// The encoding of the description
@ -232,7 +232,8 @@ impl FrameValue {
content.insert(0, *encoding as u8);
content
},
FrameValue::UserText(content) | FrameValue::UserURL(content) => content.as_bytes(),
FrameValue::UserText(content) => content.as_bytes(),
FrameValue::UserURL(content) => content.as_bytes(),
FrameValue::URL(link) => link.as_bytes().to_vec(),
FrameValue::Picture { encoding, picture } => {
picture.as_apic_bytes(ID3v2Version::V4, *encoding)?
@ -312,7 +313,7 @@ impl From<TagItem> for Option<Frame<'static>> {
(FrameID::Valid(ref s), ItemValue::Locator(text) | ItemValue::Text(text))
if s == "WXXX" =>
{
FrameValue::UserURL(ExtendedTextFrame {
FrameValue::UserURL(ExtendedUrlFrame {
encoding: TextEncoding::UTF8,
description: EMPTY_CONTENT_DESCRIPTOR,
content: text,
@ -345,7 +346,7 @@ impl From<TagItem> for Option<Frame<'static>> {
},
ItemValue::Locator(locator) => {
frame_id = FrameID::Valid(Cow::Borrowed("WXXX"));
value = FrameValue::UserURL(ExtendedTextFrame {
value = FrameValue::UserURL(ExtendedUrlFrame {
encoding: TextEncoding::UTF8,
description: String::from(desc),
content: locator,
@ -426,14 +427,14 @@ impl<'a> TryFrom<&'a TagItem> for FrameRef<'a> {
content: text.clone(),
}),
("WXXX", ItemValue::Locator(text) | ItemValue::Text(text)) => {
FrameValue::UserURL(ExtendedTextFrame {
FrameValue::UserURL(ExtendedUrlFrame {
encoding: TextEncoding::UTF8,
description: EMPTY_CONTENT_DESCRIPTOR,
content: text.clone(),
})
},
(locator_id, ItemValue::Locator(text)) if locator_id.len() > 4 => {
FrameValue::UserURL(ExtendedTextFrame {
FrameValue::UserURL(ExtendedUrlFrame {
encoding: TextEncoding::UTF8,
description: EMPTY_CONTENT_DESCRIPTOR,
content: text.clone(),
@ -475,7 +476,7 @@ impl<'a> TryFrom<&'a TagItem> for FrameRef<'a> {
},
ItemValue::Locator(locator) => {
frame_id = FrameID::Valid(Cow::Borrowed("WXXX"));
value = FrameValue::UserURL(ExtendedTextFrame {
value = FrameValue::UserURL(ExtendedUrlFrame {
encoding: TextEncoding::UTF8,
description: String::from(desc),
content: locator.clone(),

View file

@ -2,11 +2,11 @@ use crate::util::text::{encode_text, TextEncoding};
use std::hash::{Hash, Hasher};
/// An `ID3v2` text frame
/// An extended `ID3v2` text frame
///
/// This is used in the frames `TXXX` and `WXXX`, where the frames
/// This is used in the `TXXX` frame, where the frames
/// are told apart by descriptions, rather than their [`FrameID`](crate::id3::v2::FrameID)s.
/// This means for each `EncodedTextFrame` in the tag, the description
/// This means for each `ExtendedTextFrame` in the tag, the description
/// must be unique.
#[derive(Clone, Debug, Eq)]
pub struct ExtendedTextFrame {

View file

@ -0,0 +1,43 @@
use crate::util::text::{encode_text, TextEncoding};
use std::hash::{Hash, Hasher};
/// An extended `ID3v2` URL frame
///
/// This is used in the `WXXX` frame, where the frames
/// are told apart by descriptions, rather than their [`FrameID`](crate::id3::v2::FrameID)s.
/// This means for each `ExtendedUrlFrame` in the tag, the description
/// must be unique.
#[derive(Clone, Debug, Eq)]
pub struct ExtendedUrlFrame {
/// The encoding of the description and comment text
pub encoding: TextEncoding,
/// Unique content description
pub description: String,
/// The actual frame content
pub content: String,
}
impl PartialEq for ExtendedUrlFrame {
fn eq(&self, other: &Self) -> bool {
self.description == other.description
}
}
impl Hash for ExtendedUrlFrame {
fn hash<H: Hasher>(&self, state: &mut H) {
self.description.hash(state);
}
}
impl ExtendedUrlFrame {
/// Convert an [`ExtendedUrlFrame`] to a byte vec
pub fn as_bytes(&self) -> Vec<u8> {
let mut bytes = vec![self.encoding as u8];
bytes.extend(encode_text(&self.description, self.encoding, true).iter());
bytes.extend(encode_text(&self.content, self.encoding, false));
bytes
}
}

View file

@ -1,13 +1,15 @@
mod encapsulated_object;
mod encoded_text_frame;
mod extended_text_frame;
mod extended_url_frame;
mod identifier;
mod language_frame;
mod popularimeter;
mod sync_text;
pub use encapsulated_object::{GEOBInformation, GeneralEncapsulatedObject};
pub use encoded_text_frame::ExtendedTextFrame;
pub use extended_text_frame::ExtendedTextFrame;
pub use identifier::UniqueFileIdentifierFrame;
pub use extended_url_frame::ExtendedUrlFrame;
pub use language_frame::LanguageFrame;
pub use popularimeter::Popularimeter;
pub use sync_text::{SyncTextContentType, SyncTextInformation, SynchronizedText, TimestampFormat};

View file

View file

@ -4,7 +4,9 @@ use super::frame::{Frame, FrameFlags, FrameValue, EMPTY_CONTENT_DESCRIPTOR, UNKN
use super::ID3v2Version;
use crate::error::{LoftyError, Result};
use crate::id3::v2::frame::{FrameRef, MUSICBRAINZ_UFID_OWNER};
use crate::id3::v2::items::{ExtendedTextFrame, LanguageFrame, UniqueFileIdentifierFrame};
use crate::id3::v2::items::{
ExtendedTextFrame, ExtendedUrlFrame, LanguageFrame, UniqueFileIdentifierFrame,
};
use crate::picture::{Picture, PictureType, TOMBSTONE_PICTURE};
use crate::tag::item::{ItemKey, ItemValue, TagItem};
use crate::tag::{try_parse_year, Tag, TagType};
@ -735,7 +737,7 @@ impl SplitTag for ID3v2Tag {
},
(
"WXXX",
FrameValue::UserURL(ExtendedTextFrame {
FrameValue::UserURL(ExtendedUrlFrame {
ref description,
ref content,
..
@ -819,7 +821,7 @@ impl SplitTag for ID3v2Tag {
return false; // Frame consumed
},
FrameValue::URL(content)
| FrameValue::UserURL(ExtendedTextFrame { content, .. }) => {
| FrameValue::UserURL(ExtendedUrlFrame { content, .. }) => {
ItemValue::Locator(std::mem::take(content))
},
FrameValue::Picture { picture, .. } => {
@ -1072,7 +1074,7 @@ mod tests {
use std::borrow::Cow;
use crate::id3::v2::frame::MUSICBRAINZ_UFID_OWNER;
use crate::id3::v2::items::{Popularimeter, UniqueFileIdentifierFrame};
use crate::id3::v2::items::{ExtendedUrlFrame, Popularimeter, UniqueFileIdentifierFrame};
use crate::id3::v2::tag::{
filter_comment_frame_by_description, new_text_frame, DEFAULT_NUMBER_IN_PAIR,
};
@ -1711,7 +1713,7 @@ mod tests {
let wxxx_frame = Frame::new(
"WXXX",
FrameValue::UserURL(ExtendedTextFrame {
FrameValue::UserURL(ExtendedUrlFrame {
encoding: TextEncoding::UTF8,
description: String::from("BAR_URL_FRAME"),
content: String::from("bar url"),