1use super::constants::WELL_KNOWN_TYPE_SET;
2use super::data_type::DataType;
3use super::{Atom, AtomData, AtomIdent, Ilst};
4use crate::config::{ParseOptions, ParsingMode};
5use crate::error::{LoftyError, Result};
6use crate::id3::v1::constants::GENRES;
7use crate::macros::{err, try_vec};
8use crate::mp4::atom_info::{ATOM_HEADER_LEN, AtomInfo};
9use crate::mp4::ilst::atom::AtomDataStorage;
10use crate::mp4::read::{AtomReader, skip_atom};
11use crate::picture::{MimeType, Picture, PictureType};
12use crate::tag::TagExt;
13use crate::util::text::{utf8_decode, utf16_decode_bytes};
14
15use std::borrow::Cow;
16use std::io::{Cursor, Read, Seek, SeekFrom};
17
18pub(in crate::mp4) fn parse_ilst<R>(
19 reader: &mut AtomReader<R>,
20 parse_options: ParseOptions,
21 len: u64,
22) -> Result<Ilst>
23where
24 R: Read + Seek,
25{
26 let parsing_mode = parse_options.parsing_mode;
27
28 let mut contents = try_vec![0; len as usize];
29 reader.read_exact(&mut contents)?;
30
31 let mut cursor = Cursor::new(contents);
32
33 let mut ilst_reader = AtomReader::new(&mut cursor, parsing_mode)?;
34
35 let mut tag = Ilst::default();
36
37 let mut upgraded_gnres = Vec::new();
38 while let Ok(Some(atom)) = ilst_reader.next() {
39 if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
40 match fourcc {
41 b"free" | b"skip" => {
42 skip_atom(&mut ilst_reader, atom.extended, atom.len)?;
43 continue;
44 },
45 b"covr" => {
46 if parse_options.read_cover_art {
47 handle_covr(&mut ilst_reader, parsing_mode, &mut tag, &atom)?;
48 } else {
49 skip_atom(&mut ilst_reader, atom.extended, atom.len)?;
50 }
51
52 continue;
53 },
54 b"gnre" if parse_options.implicit_conversions => {
56 log::warn!("Encountered outdated 'gnre' atom, attempting to upgrade to '©gen'");
57
58 if let Some(atom_data) =
59 parse_data_inner(&mut ilst_reader, parsing_mode, &atom)?
60 {
61 for (_, content) in atom_data {
62 if content.len() >= 2 {
63 let index = content[1] as usize;
64 if index > 0 && index <= GENRES.len() {
65 upgraded_gnres
66 .push(AtomData::UTF8(String::from(GENRES[index - 1])));
67 }
68 }
69 }
70 }
71
72 continue;
73 },
74 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 }
89
90 if let Some(storage) = AtomDataStorage::from_vec(data) {
91 tag.atoms.push(Atom {
92 ident: AtomIdent::Fourcc(*b"gnre"),
93 data: storage,
94 })
95 }
96 }
97
98 continue;
99 },
100 b"plID" => {
103 if let Some(atom_data) =
104 parse_data_inner(&mut ilst_reader, parsing_mode, &atom)?
105 {
106 let mut data = Vec::new();
107
108 for (code, content) in atom_data {
109 if content.len() == 8 {
110 data.push(AtomData::Unknown {
111 code,
112 data: content,
113 })
114 }
115 }
116
117 if !data.is_empty() {
118 let storage = match data.len() {
119 1 => AtomDataStorage::Single(data.remove(0)),
120 _ => AtomDataStorage::Multiple(data),
121 };
122
123 tag.atoms.push(Atom {
124 ident: AtomIdent::Fourcc(*b"plID"),
125 data: storage,
126 })
127 }
128 }
129
130 continue;
131 },
132 b"cpil" | b"hdvd" | b"pcst" | b"pgap" | b"shwm" => {
133 if let Some(atom_data) =
134 parse_data_inner(&mut ilst_reader, parsing_mode, &atom)?
135 {
136 if let Some((_, content)) = atom_data.first() {
137 let is_true = content.iter().any(|&b| b != 0);
139 let data = AtomData::Bool(is_true);
140
141 tag.atoms.push(Atom {
142 ident: AtomIdent::Fourcc(*fourcc),
143 data: AtomDataStorage::Single(data),
144 })
145 }
146 }
147
148 continue;
149 },
150 _ => {},
151 }
152 }
153
154 parse_data(&mut ilst_reader, parsing_mode, &mut tag, atom)?;
155 }
156
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
171 Ok(tag)
172}
173
174fn parse_data<R>(
175 reader: &mut AtomReader<R>,
176 parsing_mode: ParsingMode,
177 tag: &mut Ilst,
178 atom_info: AtomInfo,
179) -> Result<()>
180where
181 R: Read + Seek,
182{
183 let handle_error = |err: LoftyError, parsing_mode: ParsingMode| -> Result<()> {
184 match parsing_mode {
185 ParsingMode::Strict => Err(err),
186 ParsingMode::BestAttempt | ParsingMode::Relaxed => {
187 log::warn!("Skipping atom with invalid content: {}", err);
188 Ok(())
189 },
190 }
191 };
192
193 if let Some(mut atom_data) = parse_data_inner(reader, parsing_mode, &atom_info)? {
194 if atom_data.len() == 1 {
196 let (flags, content) = atom_data.remove(0);
197 let data = match interpret_atom_content(flags, content) {
198 Ok(data) => data,
199 Err(err) => return handle_error(err, parsing_mode),
200 };
201
202 tag.atoms.push(Atom {
203 ident: atom_info.ident,
204 data: AtomDataStorage::Single(data),
205 });
206
207 return Ok(());
208 }
209
210 let mut data = Vec::new();
211 for (flags, content) in atom_data {
212 let value = match interpret_atom_content(flags, content) {
213 Ok(data) => data,
214 Err(err) => return handle_error(err, parsing_mode),
215 };
216
217 data.push(value);
218 }
219
220 tag.atoms.push(Atom {
221 ident: atom_info.ident,
222 data: AtomDataStorage::Multiple(data),
223 });
224 }
225
226 Ok(())
227}
228
229const DATA_ATOM_IDENT: AtomIdent<'static> = AtomIdent::Fourcc(*b"data");
230
231fn parse_data_inner<R>(
232 reader: &mut AtomReader<R>,
233 parsing_mode: ParsingMode,
234 atom_info: &AtomInfo,
235) -> Result<Option<Vec<(DataType, Vec<u8>)>>>
236where
237 R: Read + Seek,
238{
239 let mut ret = Vec::new();
241
242 let atom_end = atom_info.start + atom_info.len;
243 let position = reader.stream_position()?;
244 assert!(
245 atom_end >= position,
246 "uncaught size mismatch, reader position: {position} (expected <= {atom_end})",
247 );
248
249 let to_read = atom_end - position;
250 let mut pos = 0;
251 while pos < to_read {
252 let Some(next_atom) = reader.next()? else {
253 break;
254 };
255
256 if next_atom.len < 16 {
257 log::warn!(
258 "Expected data atom to be at least 16 bytes, got {}. Stopping",
259 next_atom.len
260 );
261 if parsing_mode == ParsingMode::Strict {
262 err!(BadAtom("Data atom is too small"))
263 }
264
265 break;
266 }
267
268 if next_atom.ident != DATA_ATOM_IDENT {
269 if parsing_mode == ParsingMode::Strict {
270 err!(BadAtom("Expected atom \"data\" to follow name"))
271 }
272
273 log::warn!(
274 "Skipping unexpected atom {actual_ident:?}, expected {expected_ident:?}",
275 actual_ident = next_atom.ident,
276 expected_ident = DATA_ATOM_IDENT
277 );
278
279 pos += next_atom.len;
280 skip_atom(reader, next_atom.extended, next_atom.len)?;
281 continue;
282 }
283
284 let Some(data_type) = parse_type_indicator(reader, parsing_mode)? else {
285 log::warn!("Skipping atom with unknown type set");
286 let remaining_atom_len = next_atom.len - (ATOM_HEADER_LEN + 1);
287
288 reader.seek(SeekFrom::Current(remaining_atom_len as i64))?;
289 pos += remaining_atom_len;
290 continue;
291 };
292
293 reader.seek(SeekFrom::Current(4))?;
295
296 let content_len = (next_atom.len - 16) as usize;
297 if content_len > 0 {
298 let mut content = try_vec![0; content_len];
299 reader.read_exact(&mut content)?;
300 ret.push((data_type, content));
301 } else {
302 log::warn!("Skipping empty \"data\" atom");
303 }
304
305 pos += next_atom.len;
306 }
307
308 let ret = if ret.is_empty() { None } else { Some(ret) };
309 Ok(ret)
310}
311
312fn parse_type_indicator<R>(
313 reader: &mut AtomReader<R>,
314 parsing_mode: ParsingMode,
315) -> Result<Option<DataType>>
316where
317 R: Read + Seek,
318{
319 let type_set = reader.read_u8()?;
324 if type_set != WELL_KNOWN_TYPE_SET {
325 if parsing_mode == ParsingMode::Strict {
326 err!(BadAtom("Unknown type set in data atom"))
327 }
328
329 return Ok(None);
330 }
331
332 Ok(Some(DataType::from(reader.read_u24()?)))
333}
334
335fn parse_uint(bytes: &[u8]) -> Result<u32> {
336 Ok(match bytes.len() {
337 1 => u32::from(bytes[0]),
338 2 => u32::from(u16::from_be_bytes([bytes[0], bytes[1]])),
339 3 => u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]),
340 4 => u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
341 _ => err!(BadAtom(
342 "Unexpected atom size for type \"BE unsigned integer\""
343 )),
344 })
345}
346
347fn parse_int(bytes: &[u8]) -> Result<i32> {
348 Ok(match bytes.len() {
349 1 => i32::from(bytes[0]),
350 2 => i32::from(i16::from_be_bytes([bytes[0], bytes[1]])),
351 3 => i32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]),
352 4 => i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
353 _ => err!(BadAtom(
354 "Unexpected atom size for type \"BE signed integer\""
355 )),
356 })
357}
358
359fn handle_covr<R>(
360 reader: &mut AtomReader<R>,
361 parsing_mode: ParsingMode,
362 tag: &mut Ilst,
363 atom_info: &AtomInfo,
364) -> Result<()>
365where
366 R: Read + Seek,
367{
368 if let Some(atom_data) = parse_data_inner(reader, parsing_mode, atom_info)? {
369 let mut data = Vec::new();
370
371 let len = atom_data.len();
372 for (data_type, value) in atom_data {
373 let mime_type = match data_type {
374 DataType::Reserved => None,
376 DataType::Gif => Some(MimeType::Gif),
378 DataType::Jpeg => Some(MimeType::Jpeg),
379 DataType::Png => Some(MimeType::Png),
380 DataType::Bmp => Some(MimeType::Bmp),
381 _ => {
382 if parsing_mode == ParsingMode::Strict {
383 err!(BadAtom("\"covr\" atom has an unknown type"))
384 }
385
386 log::warn!(
387 "Encountered \"covr\" atom with an unknown type of `{}`, discarding",
388 Into::<u32>::into(data_type)
389 );
390 return Ok(());
391 },
392 };
393
394 let picture_data = AtomData::Picture(Picture {
395 pic_type: PictureType::Other,
396 mime_type,
397 description: None,
398 data: Cow::from(value),
399 });
400
401 if len == 1 {
402 tag.atoms.push(Atom {
403 ident: AtomIdent::Fourcc(*b"covr"),
404 data: AtomDataStorage::Single(picture_data),
405 });
406
407 return Ok(());
408 }
409
410 data.push(picture_data);
411 }
412
413 tag.atoms.push(Atom {
414 ident: AtomIdent::Fourcc(*b"covr"),
415 data: AtomDataStorage::Multiple(data),
416 });
417 }
418
419 Ok(())
420}
421
422fn interpret_atom_content(flags: DataType, content: Vec<u8>) -> Result<AtomData> {
423 Ok(match flags {
425 DataType::Utf8 => AtomData::UTF8(utf8_decode(content)?),
426 DataType::Utf16 => AtomData::UTF16(utf16_decode_bytes(&content, u16::from_be_bytes)?),
427 DataType::BeSignedInteger => AtomData::SignedInteger(parse_int(&content)?),
428 DataType::BeUnsignedInteger => AtomData::UnsignedInteger(parse_uint(&content)?),
429 code => AtomData::Unknown {
430 code,
431 data: content,
432 },
433 })
434}