Skip to content

Commit e1a3baf

Browse files
committed
Ilst: Change rules for gnre upgrades
1 parent f38ee26 commit e1a3baf

File tree

6 files changed

+118
-19
lines changed

6 files changed

+118
-19
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
- It can be converted to and from a `u32`
2727
- `AtomData::data_type()` to get the data type code of the atom content.
2828

29+
### Changed
30+
- **Ilst**: Add new rules for `gnre` atom upgrades ([issue](https://github.com/Serial-ATA/lofty-rs/issues/409)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/485))
31+
- In the case that a `©gen` and `gnre` atom are present in a file, there was no way to tell which `©gen` atoms were upgraded.
32+
the new rules are:
33+
- `gnre` present + no `©gen` present, `gnre` gets upgraded as normal
34+
- `gnre` present + `©gen` present, `©gen` takes precedence and `gnre` is discarded
35+
- With [ParsingOptions::implicit_conversions](https://docs.rs/lofty/latest/lofty/config/struct.ParseOptions.html#method.implicit_conversions)
36+
set to `false`, `gnre` will be retained as an atom of type `Unknown`.
37+
2938
### Fixed
3039
- **MusePack**: Fix potential panic when the beginning silence makes up the entire sample count ([PR](https://github.com/Serial-ATA/lofty-rs/pull/449))
3140
- **Timestamp**:

lofty/src/mp4/ilst/atom.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ impl AtomDataStorage {
2828
AtomDataStorage::Multiple(v) => v.iter().all(|p| matches!(p, AtomData::Picture(_))),
2929
}
3030
}
31+
32+
pub(super) fn from_vec(mut v: Vec<AtomData>) -> Option<Self> {
33+
match v.len() {
34+
0 => None,
35+
1 => Some(AtomDataStorage::Single(v.remove(0))),
36+
_ => Some(AtomDataStorage::Multiple(v)),
37+
}
38+
}
3139
}
3240

3341
impl Debug for AtomDataStorage {

lofty/src/mp4/ilst/mod.rs

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -892,22 +892,24 @@ mod tests {
892892

893893
use std::io::{Cursor, Read as _, Seek as _, Write as _};
894894

895-
fn read_ilst(path: &str, parse_mode: ParsingMode) -> Ilst {
895+
fn read_ilst(path: &str, parse_options: ParseOptions) -> Ilst {
896896
let tag = std::fs::read(path).unwrap();
897-
read_ilst_raw(&tag, parse_mode)
897+
read_ilst_raw(&tag, parse_options)
898898
}
899899

900-
fn read_ilst_raw(bytes: &[u8], parse_mode: ParsingMode) -> Ilst {
901-
let options = ParseOptions::new().parsing_mode(parse_mode);
902-
read_ilst_with_options(bytes, options)
900+
fn read_ilst_raw(bytes: &[u8], parse_options: ParseOptions) -> Ilst {
901+
read_ilst_with_options(bytes, parse_options)
903902
}
904903

905904
fn read_ilst_strict(path: &str) -> Ilst {
906-
read_ilst(path, ParsingMode::Strict)
905+
read_ilst(path, ParseOptions::new().parsing_mode(ParsingMode::Strict))
907906
}
908907

909908
fn read_ilst_bestattempt(path: &str) -> Ilst {
910-
read_ilst(path, ParsingMode::BestAttempt)
909+
read_ilst(
910+
path,
911+
ParseOptions::new().parsing_mode(ParsingMode::BestAttempt),
912+
)
911913
}
912914

913915
fn read_ilst_with_options(bytes: &[u8], parse_options: ParseOptions) -> Ilst {
@@ -1427,7 +1429,10 @@ mod tests {
14271429

14281430
tag_bytes.drain(..8); // Remove the ilst identifier and size for `read_ilst`
14291431

1430-
let tag_re_read = read_ilst_raw(&tag_bytes[..], ParsingMode::Strict);
1432+
let tag_re_read = read_ilst_raw(
1433+
&tag_bytes[..],
1434+
ParseOptions::new().parsing_mode(ParsingMode::Strict),
1435+
);
14311436
assert_eq!(tag, tag_re_read);
14321437

14331438
// Now write from `Tag`
@@ -1439,7 +1444,10 @@ mod tests {
14391444

14401445
tag_bytes.drain(..8); // Remove the ilst identifier and size for `read_ilst`
14411446

1442-
let generic_tag_re_read = read_ilst_raw(&tag_bytes[..], ParsingMode::Strict);
1447+
let generic_tag_re_read = read_ilst_raw(
1448+
&tag_bytes[..],
1449+
ParseOptions::new().parsing_mode(ParsingMode::Strict),
1450+
);
14431451
assert_eq!(tag_re_read, generic_tag_re_read);
14441452
}
14451453

@@ -1465,4 +1473,49 @@ mod tests {
14651473
assert_eq!(ilst.len(), 1); // Artist, no picture
14661474
assert!(ilst.artist().is_some());
14671475
}
1476+
1477+
#[test_log::test]
1478+
fn gnre_conversion_case_1() {
1479+
// Case 1: 1 `gnre` atom present, no `©gen` present. `gnre` gets converted without issue.
1480+
let ilst = read_ilst_bestattempt("tests/tags/assets/ilst/gnre_conversion_case_1.ilst");
1481+
1482+
assert_eq!(ilst.len(), 2);
1483+
assert_eq!(ilst.artist().unwrap(), "Foo artist"); // Sanity check
1484+
assert_eq!(ilst.genre().unwrap(), "Funk");
1485+
}
1486+
1487+
#[test_log::test]
1488+
fn gnre_conversion_case_2() {
1489+
// Case 2: 1 `gnre` atom present, 1 `©gen` present. `gnre` gets discarded.
1490+
let ilst = read_ilst_bestattempt("tests/tags/assets/ilst/gnre_conversion_case_2.ilst");
1491+
1492+
assert_eq!(ilst.len(), 2);
1493+
assert_eq!(ilst.artist().unwrap(), "Foo artist"); // Sanity check
1494+
assert_eq!(ilst.genre().unwrap(), "My Custom Genre");
1495+
}
1496+
1497+
#[test_log::test]
1498+
fn gnre_conversion_case_3() {
1499+
// Case 2: 1 `gnre` atom present, 1 `©gen` present. implicit conversion are disabled, `gnre` is retained
1500+
// as an unknown atom.
1501+
let ilst = read_ilst(
1502+
"tests/tags/assets/ilst/gnre_conversion_case_2.ilst",
1503+
ParseOptions::new().implicit_conversions(false),
1504+
);
1505+
1506+
assert_eq!(ilst.len(), 3);
1507+
assert_eq!(ilst.artist().unwrap(), "Foo artist"); // Sanity check
1508+
assert_eq!(ilst.genre().unwrap(), "My Custom Genre");
1509+
assert_eq!(
1510+
ilst.get(&AtomIdent::Fourcc(*b"gnre"))
1511+
.unwrap()
1512+
.data()
1513+
.next()
1514+
.unwrap(),
1515+
&AtomData::Unknown {
1516+
code: DataType::BeSignedInteger,
1517+
data: vec![0, 6]
1518+
}
1519+
);
1520+
}
14681521
}

lofty/src/mp4/ilst/read.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::mp4::atom_info::{AtomInfo, ATOM_HEADER_LEN};
99
use crate::mp4::ilst::atom::AtomDataStorage;
1010
use crate::mp4::read::{skip_atom, AtomReader};
1111
use crate::picture::{MimeType, Picture, PictureType};
12+
use crate::tag::TagExt;
1213
use crate::util::text::{utf16_decode_bytes, utf8_decode};
1314

1415
use std::borrow::Cow;
@@ -33,6 +34,7 @@ where
3334

3435
let mut tag = Ilst::default();
3536

37+
let mut upgraded_gnres = Vec::new();
3638
while let Ok(Some(atom)) = ilst_reader.next() {
3739
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
3840
match fourcc {
@@ -50,31 +52,44 @@ where
5052
continue;
5153
},
5254
// Upgrade this to a \xa9gen atom
53-
b"gnre" => {
55+
b"gnre" if parse_options.implicit_conversions => {
5456
log::warn!("Encountered outdated 'gnre' atom, attempting to upgrade to '©gen'");
5557

5658
if let Some(atom_data) =
5759
parse_data_inner(&mut ilst_reader, parsing_mode, &atom)?
5860
{
59-
let mut data = Vec::new();
60-
6161
for (_, content) in atom_data {
6262
if content.len() >= 2 {
6363
let index = content[1] as usize;
6464
if index > 0 && index <= GENRES.len() {
65-
data.push(AtomData::UTF8(String::from(GENRES[index - 1])));
65+
upgraded_gnres
66+
.push(AtomData::UTF8(String::from(GENRES[index - 1])));
6667
}
6768
}
6869
}
70+
}
6971

70-
if !data.is_empty() {
71-
let storage = match data.len() {
72-
1 => AtomDataStorage::Single(data.remove(0)),
73-
_ => AtomDataStorage::Multiple(data),
74-
};
72+
continue;
73+
},
74+
// Just insert these normally, the caller will deal with them (hopefully)
75+
b"gnre" => {
76+
log::warn!("Encountered outdated 'gnre' atom");
77+
78+
if let Some(atom_data) =
79+
parse_data_inner(&mut ilst_reader, parsing_mode, &atom)?
80+
{
81+
let mut data = Vec::new();
82+
83+
for (code, content) in atom_data {
84+
data.push(AtomData::Unknown {
85+
code,
86+
data: content,
87+
});
88+
}
7589

90+
if let Some(storage) = AtomDataStorage::from_vec(data) {
7691
tag.atoms.push(Atom {
77-
ident: AtomIdent::Fourcc(*b"\xa9gen"),
92+
ident: AtomIdent::Fourcc(*b"gnre"),
7893
data: storage,
7994
})
8095
}
@@ -139,6 +154,20 @@ where
139154
parse_data(&mut ilst_reader, parsing_mode, &mut tag, atom)?;
140155
}
141156

157+
if parse_options.implicit_conversions && !upgraded_gnres.is_empty() {
158+
if tag.contains(&AtomIdent::Fourcc(*b"\xa9gen")) {
159+
log::warn!("Encountered '©gen' atom, discarding upgraded 'gnre' atom(s)");
160+
return Ok(tag);
161+
}
162+
163+
if let Some(storage) = AtomDataStorage::from_vec(upgraded_gnres) {
164+
tag.atoms.push(Atom {
165+
ident: AtomIdent::Fourcc(*b"\xa9gen"),
166+
data: storage,
167+
})
168+
}
169+
}
170+
142171
Ok(tag)
143172
}
144173

Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)