1pub(super) mod content;
2mod conversion;
3pub(super) mod header;
4pub(super) mod read;
5
6use super::header::Id3v2Version;
7use super::items::{
8 AttachedPictureFrame, BinaryFrame, CommentFrame, EventTimingCodesFrame, ExtendedTextFrame,
9 ExtendedUrlFrame, KeyValueFrame, OwnershipFrame, PopularimeterFrame, PrivateFrame,
10 RelativeVolumeAdjustmentFrame, TextInformationFrame, TimestampFrame, UniqueFileIdentifierFrame,
11 UnsynchronizedTextFrame, UrlLinkFrame,
12};
13use crate::error::Result;
14use crate::id3::v2::FrameHeader;
15use crate::util::text::TextEncoding;
16use header::FrameId;
17
18use std::borrow::Cow;
19use std::hash::Hash;
20use std::ops::Deref;
21
22pub(super) const MUSICBRAINZ_UFID_OWNER: &str = "http://musicbrainz.org";
23
24pub(super) const EMPTY_CONTENT_DESCRIPTOR: String = String::new();
31
32macro_rules! define_frames {
35 (
36 $(#[$meta:meta])*
37 pub enum Frame<'a> {
38 $(
39 $(#[$field_meta:meta])+
40 $variant:ident($type:ty),
41 )*
42 }
43 ) => {
44 $(#[$meta])*
45 pub enum Frame<'a> {
46 $(
47 $(#[$field_meta])+
48 $variant($type),
49 )*
50 }
51
52 impl Frame<'_> {
53 pub fn id(&self) -> &FrameId<'_> {
55 match self {
56 $(
57 Frame::$variant(frame) => &frame.header.id,
58 )*
59 }
60 }
61
62 pub fn flags(&self) -> FrameFlags {
64 match self {
65 $(
66 Frame::$variant(frame) => frame.flags(),
67 )*
68 }
69 }
70
71 pub fn set_flags(&mut self, flags: FrameFlags) {
73 match self {
74 $(
75 Frame::$variant(frame) => frame.set_flags(flags),
76 )*
77 }
78 }
79 }
80
81 $(
82 impl<'a> From<$type> for Frame<'a> {
83 fn from(value: $type) -> Self {
84 Frame::$variant(value)
85 }
86 }
87 )*
88 }
89}
90
91define_frames! {
92 #[non_exhaustive]
107 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
108 pub enum Frame<'a> {
109 Comment(CommentFrame<'a>),
111 UnsynchronizedText(UnsynchronizedTextFrame<'a>),
113 Text(TextInformationFrame<'a>),
115 UserText(ExtendedTextFrame<'a>),
117 Url(UrlLinkFrame<'a>),
119 UserUrl(ExtendedUrlFrame<'a>),
121 Picture(AttachedPictureFrame<'a>),
123 Popularimeter(PopularimeterFrame<'a>),
125 KeyValue(KeyValueFrame<'a>),
127 RelativeVolumeAdjustment(RelativeVolumeAdjustmentFrame<'a>),
129 UniqueFileIdentifier(UniqueFileIdentifierFrame<'a>),
131 Ownership(OwnershipFrame<'a>),
133 EventTimingCodes(EventTimingCodesFrame<'a>),
135 Private(PrivateFrame<'a>),
137 Timestamp(TimestampFrame<'a>),
139 Binary(BinaryFrame<'a>),
148 }
149}
150
151impl<'a> Frame<'a> {
152 pub fn id_str(&self) -> &str {
154 self.id().as_str()
155 }
156
157 pub(crate) fn text(id: Cow<'a, str>, content: String) -> Self {
159 Frame::Text(TextInformationFrame {
160 header: FrameHeader::new(FrameId::Valid(id), FrameFlags::default()),
161 encoding: TextEncoding::UTF8,
162 value: content,
163 })
164 }
165}
166
167impl Frame<'_> {
168 pub(super) fn is_empty(&self) -> Option<bool> {
172 let is_empty = match self {
173 Frame::Text(text) => text.value.is_empty(),
174 Frame::UserText(extended_text) => extended_text.content.is_empty(),
175 Frame::Url(link) => link.content.is_empty(),
176 Frame::UserUrl(extended_url) => extended_url.content.is_empty(),
177 Frame::Comment(comment) => comment.content.is_empty(),
178 Frame::UnsynchronizedText(unsync_text) => unsync_text.content.is_empty(),
179 Frame::Picture(picture) => picture.picture.data.is_empty(),
180 Frame::KeyValue(key_value) => key_value.key_value_pairs.is_empty(),
181 Frame::UniqueFileIdentifier(ufid) => ufid.identifier.is_empty(),
182 Frame::EventTimingCodes(event_timing) => event_timing.events.is_empty(),
183 Frame::Private(private) => private.private_data.is_empty(),
184 Frame::Binary(binary) => binary.data.is_empty(),
185 Frame::Popularimeter(_)
186 | Frame::RelativeVolumeAdjustment(_)
187 | Frame::Ownership(_)
188 | Frame::Timestamp(_) => {
189 return None;
191 },
192 };
193 Some(is_empty)
194 }
195}
196
197impl Frame<'_> {
198 pub(super) fn as_bytes(&self, is_id3v23: bool) -> Result<Vec<u8>> {
199 Ok(match self {
200 Frame::Comment(comment) => comment.as_bytes(is_id3v23)?,
201 Frame::UnsynchronizedText(lf) => lf.as_bytes(is_id3v23)?,
202 Frame::Text(tif) => tif.as_bytes(is_id3v23),
203 Frame::UserText(content) => content.as_bytes(is_id3v23),
204 Frame::UserUrl(content) => content.as_bytes(is_id3v23),
205 Frame::Url(link) => link.as_bytes(),
206 Frame::Picture(attached_picture) => {
207 let version = if is_id3v23 {
208 Id3v2Version::V3
209 } else {
210 Id3v2Version::V4
211 };
212
213 attached_picture.as_bytes(version)?
214 },
215 Frame::Popularimeter(popularimeter) => popularimeter.as_bytes()?,
216 Frame::KeyValue(content) => content.as_bytes(is_id3v23),
217 Frame::RelativeVolumeAdjustment(frame) => frame.as_bytes(),
218 Frame::UniqueFileIdentifier(frame) => frame.as_bytes(),
219 Frame::Ownership(frame) => frame.as_bytes(is_id3v23)?,
220 Frame::EventTimingCodes(frame) => frame.as_bytes(),
221 Frame::Private(frame) => frame.as_bytes()?,
222 Frame::Timestamp(frame) => frame.as_bytes(is_id3v23)?,
223 Frame::Binary(frame) => frame.as_bytes(),
224 })
225 }
226
227 pub(super) fn name(&self) -> &'static str {
229 match self {
230 Frame::Comment(_) => "Comment",
231 Frame::UnsynchronizedText(_) => "UnsynchronizedText",
232 Frame::Text { .. } => "Text",
233 Frame::UserText(_) => "UserText",
234 Frame::Url(_) => "Url",
235 Frame::UserUrl(_) => "UserUrl",
236 Frame::Picture { .. } => "Picture",
237 Frame::Popularimeter(_) => "Popularimeter",
238 Frame::KeyValue(_) => "KeyValue",
239 Frame::UniqueFileIdentifier(_) => "UniqueFileIdentifier",
240 Frame::RelativeVolumeAdjustment(_) => "RelativeVolumeAdjustment",
241 Frame::Ownership(_) => "Ownership",
242 Frame::EventTimingCodes(_) => "EventTimingCodes",
243 Frame::Private(_) => "Private",
244 Frame::Timestamp(_) => "Timestamp",
245 Frame::Binary(_) => "Binary",
246 }
247 }
248}
249
250#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
252#[allow(clippy::struct_excessive_bools)]
253pub struct FrameFlags {
254 pub tag_alter_preservation: bool,
256 pub file_alter_preservation: bool,
258 pub read_only: bool,
260 pub grouping_identity: Option<u8>,
264 pub compression: bool,
268 pub encryption: Option<u8>,
274 pub unsynchronisation: bool, pub data_length_indicator: Option<u32>,
290}
291
292impl FrameFlags {
293 pub fn parse_id3v24(flags: u16) -> Self {
300 FrameFlags {
301 tag_alter_preservation: flags & 0x4000 == 0x4000,
302 file_alter_preservation: flags & 0x2000 == 0x2000,
303 read_only: flags & 0x1000 == 0x1000,
304 grouping_identity: (flags & 0x0040 == 0x0040).then_some(0),
305 compression: flags & 0x0008 == 0x0008,
306 encryption: (flags & 0x0004 == 0x0004).then_some(0),
307 unsynchronisation: flags & 0x0002 == 0x0002,
308 data_length_indicator: (flags & 0x0001 == 0x0001).then_some(0),
309 }
310 }
311
312 pub fn parse_id3v23(flags: u16) -> Self {
318 FrameFlags {
319 tag_alter_preservation: flags & 0x8000 == 0x8000,
320 file_alter_preservation: flags & 0x4000 == 0x4000,
321 read_only: flags & 0x2000 == 0x2000,
322 grouping_identity: (flags & 0x0020 == 0x0020).then_some(0),
323 compression: flags & 0x0080 == 0x0080,
324 encryption: (flags & 0x0040 == 0x0040).then_some(0),
325 unsynchronisation: false,
326 data_length_indicator: None,
327 }
328 }
329
330 pub fn as_id3v24_bytes(&self) -> u16 {
332 let mut flags = 0;
333
334 if *self == FrameFlags::default() {
335 return flags;
336 }
337
338 if self.tag_alter_preservation {
339 flags |= 0x4000
340 }
341
342 if self.file_alter_preservation {
343 flags |= 0x2000
344 }
345
346 if self.read_only {
347 flags |= 0x1000
348 }
349
350 if self.grouping_identity.is_some() {
351 flags |= 0x0040
352 }
353
354 if self.compression {
355 flags |= 0x0008
356 }
357
358 if self.encryption.is_some() {
359 flags |= 0x0004
360 }
361
362 if self.unsynchronisation {
363 flags |= 0x0002
364 }
365
366 if self.data_length_indicator.is_some() {
367 flags |= 0x0001
368 }
369
370 flags
371 }
372
373 pub fn as_id3v23_bytes(&self) -> u16 {
375 let mut flags = 0;
376
377 if *self == FrameFlags::default() {
378 return flags;
379 }
380
381 if self.tag_alter_preservation {
382 flags |= 0x8000
383 }
384
385 if self.file_alter_preservation {
386 flags |= 0x4000
387 }
388
389 if self.read_only {
390 flags |= 0x2000
391 }
392
393 if self.grouping_identity.is_some() {
394 flags |= 0x0020
395 }
396
397 if self.compression {
398 flags |= 0x0080
399 }
400
401 if self.encryption.is_some() {
402 flags |= 0x0040
403 }
404
405 flags
406 }
407}
408
409#[derive(Clone)]
410pub(crate) struct FrameRef<'a>(pub(crate) Cow<'a, Frame<'a>>);
411
412impl<'a> Deref for FrameRef<'a> {
413 type Target = Frame<'a>;
414
415 fn deref(&self) -> &Self::Target {
416 self.0.as_ref()
417 }
418}
419
420impl<'a> Frame<'a> {
421 pub(crate) fn as_opt_ref(&'a self) -> Option<FrameRef<'a>> {
422 if let FrameId::Valid(_) = self.id() {
423 Some(FrameRef(Cow::Borrowed(self)))
424 } else {
425 None
426 }
427 }
428}