Skip to content

Commit be6eb07

Browse files
committed
ID3v2: Stop using &str for frame IDs
1 parent 4767f5d commit be6eb07

File tree

3 files changed

+46
-24
lines changed

3 files changed

+46
-24
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88

99
## Added
10-
- **ID3v2**: Support for "RVA2", "OWNE", "ETCO", and "PRIV" frames through
11-
`id3::v2::{RelativeVolumeAdjustmentFrame, OwnershipFrame, EventTimingCodesFrame, PrivateFrame}`
10+
- **ID3v2**:
11+
- Support for "RVA2", "OWNE", "ETCO", and "PRIV" frames through
12+
`id3::v2::{RelativeVolumeAdjustmentFrame, OwnershipFrame, EventTimingCodesFrame, PrivateFrame}`
13+
- `FrameId` now implements `Display`
1214
- **MP4**:
1315
- `Atom::into_data`
1416
- `Atom::merge`
@@ -20,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2022
in a tag once and remove them. Those frames are: "MCDI", "ETCO", "MLLT", "SYTC", "RVRB", "PCNT", "RBUF", "POSS", "OWNE", "SEEK", and "ASPI".
2123
- `Id3v2Tag::remove` will now take a `FrameId` rather than `&str`
2224
- `FrameId` now implements `Into<Cow<'_, str>>`, making it possible to use it in `Frame::new`
25+
- `ID3v2Tag` getters will now use `&FrameId` instead of `&str` for IDs
2326
- **MP4**:
2427
- `Ilst::remove` will now return all of the removed atoms
2528
- `Ilst::insert_picture` will now combine all pictures into a single `covr` atom

src/id3/v2/frame/id.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::fmt::{Display, Formatter};
23

34
use crate::error::{Id3v2Error, Id3v2ErrorKind, LoftyError, Result};
45
use crate::tag::item::ItemKey;
@@ -92,6 +93,12 @@ impl<'a> FrameId<'a> {
9293
}
9394
}
9495

96+
impl Display for FrameId<'_> {
97+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
98+
f.write_str(self.as_str())
99+
}
100+
}
101+
95102
impl<'a> Into<Cow<'a, str>> for FrameId<'a> {
96103
fn into(self) -> Cow<'a, str> {
97104
self.into_inner()

src/id3/v2/tag.rs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ macro_rules! impl_accessor {
3535
paste::paste! {
3636
$(
3737
fn $name(&self) -> Option<Cow<'_, str>> {
38-
self.get_text($id)
38+
self.get_text(&[<$name:upper _ID>])
3939
}
4040

4141
fn [<set_ $name>](&mut self, value: String) {
@@ -161,10 +161,8 @@ impl Id3v2Tag {
161161
/// Gets a [`Frame`] from an id
162162
///
163163
/// NOTE: This is *not* case-sensitive
164-
pub fn get(&self, id: &str) -> Option<&Frame<'static>> {
165-
self.frames
166-
.iter()
167-
.find(|f| f.id_str().eq_ignore_ascii_case(id))
164+
pub fn get(&self, id: &FrameId<'_>) -> Option<&Frame<'static>> {
165+
self.frames.iter().find(|f| &f.id == id)
168166
}
169167

170168
/// Gets the text for a frame
@@ -173,7 +171,7 @@ impl Id3v2Tag {
173171
///
174172
/// If the tag is [`Id3v2Version::V4`], this will allocate if the text contains any
175173
/// null (`'\0'`) text separators to replace them with a slash (`'/'`).
176-
pub fn get_text(&self, id: &str) -> Option<Cow<'_, str>> {
174+
pub fn get_text(&self, id: &FrameId<'_>) -> Option<Cow<'_, str>> {
177175
let frame = self.get(id);
178176
if let Some(Frame {
179177
value: FrameValue::Text(TextInformationFrame { value, .. }),
@@ -468,7 +466,7 @@ impl Id3v2Tag {
468466
})
469467
}
470468

471-
fn split_num_pair(&self, id: &str) -> (Option<u32>, Option<u32>) {
469+
fn split_num_pair(&self, id: &FrameId<'_>) -> (Option<u32>, Option<u32>) {
472470
if let Some(Frame {
473471
value: FrameValue::Text(TextInformationFrame { ref value, .. }),
474472
..
@@ -499,9 +497,14 @@ impl Id3v2Tag {
499497
};
500498
}
501499

502-
fn insert_number_pair(&mut self, id: &'static str, number: Option<u32>, total: Option<u32>) {
500+
fn insert_number_pair(
501+
&mut self,
502+
id: FrameId<'static>,
503+
number: Option<u32>,
504+
total: Option<u32>,
505+
) {
503506
if let Some(content) = format_number_pair(number, total) {
504-
self.insert(Frame::text(Cow::Borrowed(id), content));
507+
self.insert(Frame::text(id.into_inner(), content));
505508
} else {
506509
log::warn!("{id} is not set. number: {number:?}, total: {total:?}");
507510
}
@@ -587,23 +590,23 @@ impl Accessor for Id3v2Tag {
587590
);
588591

589592
fn track(&self) -> Option<u32> {
590-
self.split_num_pair("TRCK").0
593+
self.split_num_pair(&TRACK_ID).0
591594
}
592595

593596
fn set_track(&mut self, value: u32) {
594-
self.insert_number_pair("TRCK", Some(value), self.track_total());
597+
self.insert_number_pair(TRACK_ID, Some(value), self.track_total());
595598
}
596599

597600
fn remove_track(&mut self) {
598601
let _ = self.remove(&TRACK_ID);
599602
}
600603

601604
fn track_total(&self) -> Option<u32> {
602-
self.split_num_pair("TRCK").1
605+
self.split_num_pair(&TRACK_ID).1
603606
}
604607

605608
fn set_track_total(&mut self, value: u32) {
606-
self.insert_number_pair("TRCK", self.track(), Some(value));
609+
self.insert_number_pair(TRACK_ID, self.track(), Some(value));
607610
}
608611

609612
fn remove_track_total(&mut self) {
@@ -616,23 +619,23 @@ impl Accessor for Id3v2Tag {
616619
}
617620

618621
fn disk(&self) -> Option<u32> {
619-
self.split_num_pair("TPOS").0
622+
self.split_num_pair(&DISC_ID).0
620623
}
621624

622625
fn set_disk(&mut self, value: u32) {
623-
self.insert_number_pair("TPOS", Some(value), self.disk_total());
626+
self.insert_number_pair(DISC_ID, Some(value), self.disk_total());
624627
}
625628

626629
fn remove_disk(&mut self) {
627630
let _ = self.remove(&DISC_ID);
628631
}
629632

630633
fn disk_total(&self) -> Option<u32> {
631-
self.split_num_pair("TPOS").1
634+
self.split_num_pair(&DISC_ID).1
632635
}
633636

634637
fn set_disk_total(&mut self, value: u32) {
635-
self.insert_number_pair("TPOS", self.disk(), Some(value));
638+
self.insert_number_pair(DISC_ID, self.disk(), Some(value));
636639
}
637640

638641
fn remove_disk_total(&mut self) {
@@ -648,7 +651,7 @@ impl Accessor for Id3v2Tag {
648651
if let Some(Frame {
649652
value: FrameValue::Text(TextInformationFrame { value, .. }),
650653
..
651-
}) = self.get("TDRC")
654+
}) = self.get(&RECORDING_TIME_ID)
652655
{
653656
return try_parse_year(value);
654657
}
@@ -1447,7 +1450,7 @@ mod tests {
14471450
#[test]
14481451
fn tag_to_id3v2() {
14491452
fn verify_frame(tag: &Id3v2Tag, id: &str, value: &str) {
1450-
let frame = tag.get(id);
1453+
let frame = tag.get(&FrameId::Valid(Cow::Borrowed(id)));
14511454

14521455
assert!(frame.is_some());
14531456

@@ -1470,7 +1473,9 @@ mod tests {
14701473
verify_frame(&id3v2_tag, "TPE1", "Bar artist");
14711474
verify_frame(&id3v2_tag, "TALB", "Baz album");
14721475

1473-
let frame = id3v2_tag.get(COMMENT_FRAME_ID).unwrap();
1476+
let frame = id3v2_tag
1477+
.get(&FrameId::Valid(Cow::Borrowed(COMMENT_FRAME_ID)))
1478+
.unwrap();
14741479
assert_eq!(
14751480
frame.content(),
14761481
&FrameValue::Comment(CommentFrame {
@@ -2340,7 +2345,9 @@ mod tests {
23402345
id3v2.insert(txxx_frame2);
23412346

23422347
// We cannot get user defined texts through `get_text`
2343-
assert!(id3v2.get_text("TXXX").is_none());
2348+
assert!(id3v2
2349+
.get_text(&FrameId::Valid(Cow::Borrowed("TXXX")))
2350+
.is_none());
23442351

23452352
assert_eq!(id3v2.get_user_text(description.as_str()), Some(&*content));
23462353

@@ -2369,6 +2376,11 @@ mod tests {
23692376
fn read_multiple_composers_should_not_fail_with_bad_frame_length() {
23702377
// Issue #255
23712378
let tag = read_tag("tests/tags/assets/id3v2/multiple_composers.id3v24");
2372-
assert_eq!(tag.get_text("TCOM").as_deref().unwrap(), "A/B");
2379+
assert_eq!(
2380+
tag.get_text(&FrameId::Valid(Cow::Borrowed("TCOM")))
2381+
.as_deref()
2382+
.unwrap(),
2383+
"A/B"
2384+
);
23732385
}
23742386
}

0 commit comments

Comments
 (0)