Skip to content

Commit 304feb9

Browse files
committed
MP4: Properly handle offset updates for shrinking tags
1 parent 0933898 commit 304feb9

File tree

2 files changed

+71
-16
lines changed

2 files changed

+71
-16
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Fixed
10+
- **MP4**: Atom offset updates will now be properly handled for shrinking tags ([PR](https://github.com/Serial-ATA/lofty-rs/pull/344))
11+
912
## [0.18.0] - 2024-01-12
1013

1114
### Added

src/mp4/ilst/write.rs

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
1818
// A "full" atom is a traditional length + identifier, followed by a version (1) and flags (3)
1919
const FULL_ATOM_SIZE: u64 = ATOM_HEADER_LEN + 4;
2020
const HDLR_SIZE: u64 = ATOM_HEADER_LEN + 25;
21+
const DEFAULT_PADDING_SIZE: u32 = 1024;
2122

2223
// TODO: We are forcing the use of ParseOptions::DEFAULT_PARSING_MODE. This is not good. It should be caller-specified.
2324
pub(crate) fn write_to<'a, I: 'a>(data: &mut File, tag: &mut IlstRef<'a, I>) -> Result<()>
@@ -282,10 +283,7 @@ fn save_to_existing(
282283
write_handle.write_all(&ilst)?;
283284

284285
// Write the remaining padding
285-
write_handle.write_u32::<BigEndian>(remaining_space)?;
286-
write_handle.write_all(b"free")?;
287-
write_handle
288-
.write_all(&try_vec![1; (remaining_space - ATOM_HEADER_LEN as u32) as usize])?;
286+
write_free_atom(&mut write_handle, remaining_space)?;
289287

290288
return Ok(());
291289
}
@@ -311,10 +309,38 @@ fn save_to_existing(
311309
write_handle.write_atom_size(udta.start, *new_udta_size, udta.extended)?;
312310

313311
// Update offset atoms
314-
drop(write_handle);
315312

316-
let difference = (new_meta_size as i64) - (meta.len as i64);
317-
update_offsets(writer, moov, difference)?;
313+
let mut difference = (new_meta_size as i64) - (meta.len as i64);
314+
if difference.is_negative() {
315+
let diff_abs = difference.abs();
316+
if diff_abs >= 8 {
317+
log::trace!(
318+
"Avoiding offset update, padding tag with {} bytes",
319+
diff_abs
320+
);
321+
322+
// If our difference is >= 8, we can make up the difference with
323+
// a `free` atom and skip updating the offsets.
324+
write_free_atom(&mut write_handle, diff_abs as u32)?;
325+
difference = 0;
326+
} else {
327+
log::trace!(
328+
"Cannot avoid offset update, padding tag with {} bytes",
329+
DEFAULT_PADDING_SIZE
330+
);
331+
332+
// Otherwise, we'll have to just pad the default amount,
333+
// and update the offsets.
334+
write_free_atom(&mut write_handle, DEFAULT_PADDING_SIZE)?;
335+
difference += i64::from(DEFAULT_PADDING_SIZE);
336+
}
337+
}
338+
339+
drop(write_handle);
340+
if difference != 0 {
341+
let offset = range.start as u64;
342+
update_offsets(writer, moov, difference, offset)?;
343+
}
318344
}
319345

320346
// Replace the `ilst` atom
@@ -325,7 +351,22 @@ fn save_to_existing(
325351
Ok(())
326352
}
327353

328-
fn update_offsets(writer: &AtomWriter, moov: &ContextualAtom, difference: i64) -> Result<()> {
354+
fn write_free_atom<W>(writer: &mut W, size: u32) -> Result<()>
355+
where
356+
W: Write,
357+
{
358+
writer.write_u32::<BigEndian>(size)?;
359+
writer.write_all(b"free")?;
360+
writer.write_all(&try_vec![1; (size - ATOM_HEADER_LEN as u32) as usize])?;
361+
Ok(())
362+
}
363+
364+
fn update_offsets(
365+
writer: &AtomWriter,
366+
moov: &ContextualAtom,
367+
difference: i64,
368+
offset: u64,
369+
) -> Result<()> {
329370
log::debug!("Checking for offset atoms to update");
330371

331372
let mut write_handle = writer.start_write();
@@ -343,14 +384,17 @@ fn update_offsets(writer: &AtomWriter, moov: &ContextualAtom, difference: i64) -
343384

344385
let count = write_handle.read_u32::<BigEndian>()?;
345386
for _ in 0..count {
346-
let offset = write_handle.read_u32::<BigEndian>()?;
387+
let read_offset = write_handle.read_u32::<BigEndian>()?;
388+
if u64::from(read_offset) < offset {
389+
continue;
390+
}
347391
write_handle.seek(SeekFrom::Current(-4))?;
348-
write_handle.write_u32::<BigEndian>((i64::from(offset) + difference) as u32)?;
392+
write_handle.write_u32::<BigEndian>((i64::from(read_offset) + difference) as u32)?;
349393

350394
log::trace!(
351395
"Updated offset from {} to {}",
352396
offset,
353-
(i64::from(offset) + difference) as u32
397+
(i64::from(read_offset) + difference) as u32
354398
);
355399
}
356400
}
@@ -368,14 +412,18 @@ fn update_offsets(writer: &AtomWriter, moov: &ContextualAtom, difference: i64) -
368412

369413
let count = write_handle.read_u32::<BigEndian>()?;
370414
for _ in 0..count {
371-
let offset = write_handle.read_u64::<BigEndian>()?;
415+
let read_offset = write_handle.read_u64::<BigEndian>()?;
416+
if read_offset < offset {
417+
continue;
418+
}
419+
372420
write_handle.seek(SeekFrom::Current(-8))?;
373-
write_handle.write_u64::<BigEndian>((offset as i64 + difference) as u64)?;
421+
write_handle.write_u64::<BigEndian>((read_offset as i64 + difference) as u64)?;
374422

375423
log::trace!(
376424
"Updated offset from {} to {}",
377425
offset,
378-
((offset as i64) + difference) as u64
426+
((read_offset as i64) + difference) as u64
379427
);
380428
}
381429
}
@@ -402,9 +450,13 @@ fn update_offsets(writer: &AtomWriter, moov: &ContextualAtom, difference: i64) -
402450
let base_data_offset = (flags & 0b1) != 0;
403451

404452
if base_data_offset {
405-
let offset = write_handle.read_u64::<BigEndian>()?;
453+
let read_offset = write_handle.read_u64::<BigEndian>()?;
454+
if read_offset < offset {
455+
continue;
456+
}
457+
406458
write_handle.seek(SeekFrom::Current(-8))?;
407-
write_handle.write_u64::<BigEndian>((offset as i64 + difference) as u64)?;
459+
write_handle.write_u64::<BigEndian>((read_offset as i64 + difference) as u64)?;
408460

409461
log::trace!(
410462
"Updated offset from {} to {}",

0 commit comments

Comments
 (0)