Skip to content

Commit bd96b4f

Browse files
committed
MP4: Properly handle covrs
Previously, `Ilst::insert_picture` would insert a new `covr` atom, and `Ilst::pictures` would check for multiple `covr` atoms. Now, when inserting pictures they will all be merged into a single atom. Additionally, `Ilst::insert` will check for `covr` atom inserts, and perform `Ilst::insert_picture` correctly.
1 parent c078126 commit bd96b4f

File tree

2 files changed

+107
-18
lines changed

2 files changed

+107
-18
lines changed

src/mp4/ilst/atom.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ impl AtomDataStorage {
2020
AtomDataStorage::Multiple(data) => data.first_mut().expect("not empty"),
2121
}
2222
}
23+
24+
pub(super) fn is_pictures(&self) -> bool {
25+
match self {
26+
AtomDataStorage::Single(v) => matches!(v, AtomData::Picture(_)),
27+
AtomDataStorage::Multiple(v) => v.iter().all(|p| matches!(p, AtomData::Picture(_))),
28+
}
29+
}
2330
}
2431

2532
impl Debug for AtomDataStorage {

src/mp4/ilst/mod.rs

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const ALBUM: AtomIdent<'_> = AtomIdent::Fourcc(*b"\xa9alb");
2727
const GENRE: AtomIdent<'_> = AtomIdent::Fourcc(*b"\xa9gen");
2828
const COMMENT: AtomIdent<'_> = AtomIdent::Fourcc(*b"\xa9cmt");
2929
const ADVISORY_RATING: AtomIdent<'_> = AtomIdent::Fourcc(*b"rtng");
30+
const COVR: AtomIdent<'_> = AtomIdent::Fourcc(*b"covr");
3031

3132
macro_rules! impl_accessor {
3233
($($name:ident => $const:ident;)+) => {
@@ -118,6 +119,10 @@ impl Ilst {
118119
self.atoms.iter().find(|a| &a.ident == ident)
119120
}
120121

122+
fn get_mut(&mut self, ident: &AtomIdent<'_>) -> Option<&mut Atom<'static>> {
123+
self.atoms.iter_mut().find(|a| &a.ident == ident)
124+
}
125+
121126
/// Inserts an [`Atom`]
122127
///
123128
/// # Examples
@@ -138,6 +143,16 @@ impl Ilst {
138143
/// assert!(title.is_some());
139144
/// ```
140145
pub fn insert(&mut self, atom: Atom<'_>) {
146+
if atom.ident == COVR && atom.data.is_pictures() {
147+
for data in atom.data {
148+
match data {
149+
AtomData::Picture(p) => self.insert_picture(p),
150+
_ => unreachable!(),
151+
}
152+
}
153+
return;
154+
}
155+
141156
self.atoms.push(atom.into_owned());
142157
}
143158

@@ -216,31 +231,98 @@ impl Ilst {
216231
self.atoms.retain(f)
217232
}
218233

219-
/// Returns all pictures
220-
pub fn pictures(&self) -> impl Iterator<Item = &Picture> + Clone {
221-
const COVR: AtomIdent<'_> = AtomIdent::Fourcc(*b"covr");
222-
223-
self.atoms.iter().filter_map(|a| match a.ident {
224-
COVR => {
225-
if let Some(AtomData::Picture(pic)) = a.data().next() {
226-
Some(pic)
227-
} else {
228-
None
229-
}
230-
},
231-
_ => None,
232-
})
234+
/// Returns all pictures, if there are any
235+
///
236+
/// # Examples
237+
///
238+
/// ```rust
239+
/// use lofty::mp4::Ilst;
240+
/// use lofty::{MimeType, Picture, PictureType, TagExt};
241+
///
242+
/// let mut ilst = Ilst::new();
243+
///
244+
/// # let png_data = b"foo".to_vec();
245+
/// // Insert pictures
246+
/// ilst.insert_picture(Picture::new_unchecked(
247+
/// PictureType::Other,
248+
/// MimeType::Png,
249+
/// None,
250+
/// png_data,
251+
/// ));
252+
///
253+
/// # let jpeg_data = b"bar".to_vec();
254+
/// ilst.insert_picture(Picture::new_unchecked(
255+
/// PictureType::Other,
256+
/// MimeType::Jpeg,
257+
/// None,
258+
/// jpeg_data,
259+
/// ));
260+
///
261+
/// assert_eq!(ilst.pictures().unwrap().count(), 2);
262+
/// ```
263+
pub fn pictures(&self) -> Option<impl Iterator<Item = &Picture>> {
264+
let Some(covr) = self.get(&COVR) else {
265+
return None;
266+
};
267+
268+
Some(covr.data().filter_map(|d| {
269+
if let AtomData::Picture(pic) = d {
270+
Some(pic)
271+
} else {
272+
None
273+
}
274+
}))
233275
}
234276

235277
/// Inserts a picture
278+
///
279+
/// NOTE: If a `covr` atom exists in the tag, the picture will be appended to it.
280+
///
281+
/// # Examples
282+
///
283+
/// ```rust
284+
/// use lofty::mp4::Ilst;
285+
/// use lofty::{MimeType, Picture, PictureType, TagExt};
286+
///
287+
/// let mut ilst = Ilst::new();
288+
///
289+
/// # let png_data = b"foo".to_vec();
290+
/// // Insert a single picture
291+
/// ilst.insert_picture(Picture::new_unchecked(
292+
/// PictureType::Other,
293+
/// MimeType::Png,
294+
/// None,
295+
/// png_data,
296+
/// ));
297+
/// assert_eq!(ilst.len(), 1);
298+
///
299+
/// # let jpeg_data = b"bar".to_vec();
300+
/// // Insert another picture
301+
/// ilst.insert_picture(Picture::new_unchecked(
302+
/// PictureType::Other,
303+
/// MimeType::Jpeg,
304+
/// None,
305+
/// jpeg_data,
306+
/// ));
307+
///
308+
/// // The existing `covr` atom is reused
309+
/// assert_eq!(ilst.len(), 1);
310+
/// assert_eq!(ilst.pictures().unwrap().count(), 2);
311+
/// ```
236312
pub fn insert_picture(&mut self, mut picture: Picture) {
237313
// This is just for correctness, it doesn't really matter.
238314
picture.pic_type = PictureType::Other;
239315

240-
self.atoms.push(Atom {
241-
ident: AtomIdent::Fourcc(*b"covr"),
242-
data: AtomDataStorage::Single(AtomData::Picture(picture)),
243-
})
316+
let data = AtomData::Picture(picture);
317+
let Some(existing_covr) = self.get_mut(&COVR) else {
318+
self.atoms.push(Atom {
319+
ident: COVR,
320+
data: AtomDataStorage::Single(data),
321+
});
322+
return;
323+
};
324+
325+
existing_covr.push_data(data);
244326
}
245327

246328
/// Removes all pictures

0 commit comments

Comments
 (0)