Skip to content

Commit 16a2419

Browse files
committed
ID3v2: Support duplicate tags
Previously, if we were reading a file and encountered an ID3v2 tag after having already read one, we would overwrite the last one, losing all of its information. Now we preserve all of the information, overwriting frames as necessary.
1 parent 939f3f2 commit 16a2419

File tree

7 files changed

+64
-3
lines changed

7 files changed

+64
-3
lines changed

src/aac/read.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ where
5151
#[cfg(feature = "id3v2")]
5252
{
5353
let id3v2 = parse_id3v2(reader, header)?;
54+
if let Some(existing_tag) = &mut file.id3v2_tag {
55+
// https://github.com/Serial-ATA/lofty-rs/issues/87
56+
// Duplicate tags should have their frames appended to the previous
57+
for frame in id3v2.frames {
58+
existing_tag.insert(frame);
59+
}
60+
continue;
61+
}
5462
file.id3v2_tag = Some(id3v2);
5563
}
5664

src/id3/v2/tag.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ macro_rules! impl_accessor {
8989
pub struct ID3v2Tag {
9090
flags: ID3v2TagFlags,
9191
pub(super) original_version: ID3v2Version,
92-
frames: Vec<Frame>,
92+
pub(crate) frames: Vec<Frame>,
9393
}
9494

9595
impl IntoIterator for ID3v2Tag {

src/iff/aiff/read.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,18 @@ where
6060
while chunks.next(data).is_ok() {
6161
match &chunks.fourcc {
6262
#[cfg(feature = "id3v2")]
63-
b"ID3 " | b"id3 " => id3v2_tag = Some(chunks.id3_chunk(data)?),
63+
b"ID3 " | b"id3 " => {
64+
let tag = chunks.id3_chunk(data)?;
65+
if let Some(existing_tag) = id3v2_tag.as_mut() {
66+
// https://github.com/Serial-ATA/lofty-rs/issues/87
67+
// Duplicate tags should have their frames appended to the previous
68+
for frame in tag.frames {
69+
existing_tag.insert(frame);
70+
}
71+
continue;
72+
}
73+
id3v2_tag = Some(tag);
74+
},
6475
b"COMM" if parse_options.read_properties && comm.is_none() => {
6576
if chunks.size < 18 {
6677
decode_err!(@BAIL AIFF, "File has an invalid \"COMM\" chunk size (< 18)");

src/iff/wav/read.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,18 @@ where
9393
}
9494
},
9595
#[cfg(feature = "id3v2")]
96-
b"ID3 " | b"id3 " => id3v2_tag = Some(chunks.id3_chunk(data)?),
96+
b"ID3 " | b"id3 " => {
97+
let tag = chunks.id3_chunk(data)?;
98+
if let Some(existing_tag) = id3v2_tag.as_mut() {
99+
// https://github.com/Serial-ATA/lofty-rs/issues/87
100+
// Duplicate tags should have their frames appended to the previous
101+
for frame in tag.frames {
102+
existing_tag.insert(frame);
103+
}
104+
continue;
105+
}
106+
id3v2_tag = Some(tag);
107+
},
97108
_ => chunks.skip(data)?,
98109
}
99110
}

src/mpeg/read.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ where
4646
#[cfg(feature = "id3v2")]
4747
{
4848
let id3v2 = parse_id3v2(reader, header)?;
49+
if let Some(existing_tag) = &mut file.id3v2_tag {
50+
// https://github.com/Serial-ATA/lofty-rs/issues/87
51+
// Duplicate tags should have their frames appended to the previous
52+
for frame in id3v2.frames {
53+
existing_tag.insert(frame);
54+
}
55+
continue;
56+
}
4957
file.id3v2_tag = Some(id3v2);
5058
}
5159

11.5 KB
Binary file not shown.

tests/files/mpeg.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,29 @@ fn issue_82_solidus_in_tag() {
6565
assert_eq!(id3v2_tag.title().as_deref(), Some("Foo / title"));
6666
}
6767

68+
#[test]
69+
fn issue_87_duplicate_id3v2() {
70+
// The first tag has a bunch of information: An album, artist, encoder, and a title.
71+
// This tag is immediately followed by another the contains an artist.
72+
// We expect that the title from the first tag has been replaced by this second tag, while
73+
// retaining the rest of the information from the first tag.
74+
let file = Probe::open("tests/files/assets/issue_87_duplicate_id3v2.mp3")
75+
.unwrap()
76+
.read()
77+
.unwrap();
78+
79+
assert_eq!(file.file_type(), FileType::MPEG);
80+
81+
let id3v2_tag = &file.tags()[0];
82+
assert_eq!(id3v2_tag.album().as_deref(), Some("album test"));
83+
assert_eq!(id3v2_tag.artist().as_deref(), Some("Foo artist")); // Original tag has "artist test"
84+
assert_eq!(
85+
id3v2_tag.get_string(&ItemKey::EncoderSettings),
86+
Some("Lavf58.62.100")
87+
);
88+
assert_eq!(id3v2_tag.title().as_deref(), Some("title test"));
89+
}
90+
6891
#[test]
6992
fn write() {
7093
let mut file = temp_file!("tests/files/assets/minimal/full_test.mp3");

0 commit comments

Comments
 (0)