Skip to content

Commit 1370be7

Browse files
committed
MP4: Stop overwriting file with padding on tag shrink
1 parent 20b6c60 commit 1370be7

File tree

2 files changed

+81
-46
lines changed

2 files changed

+81
-46
lines changed

CHANGELOG.md

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

77
## [Unreleased]
88

9-
## [0.18.1] - 2024-01-20
9+
## [0.18.2] - 2024-01-23
10+
11+
### Fixed
12+
- **MP4**: Padding for shrinking tags will no longer overwrite unrelated data ([PR](https://github.com/Serial-ATA/lofty-rs/pull/346))
13+
14+
## [0.18.1] - 2024-01-20 (YANKED)
1015

1116
### Fixed
1217
- **ID3v2**: Fix panic in UTF-16 parsing when BOM is missing ([issue](https://github.com/Serial-ATA/lofty-rs/issues/295)) ([PR](https://github.com/Serial-ATA/lofty-rs/pull/343))

src/mp4/ilst/write.rs

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::picture::{MimeType, Picture};
1111
use crate::probe::ParseOptions;
1212

1313
use std::fs::File;
14-
use std::io::{Seek, SeekFrom, Write};
14+
use std::io::{Cursor, Seek, SeekFrom, Write};
1515

1616
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
1717

@@ -208,7 +208,7 @@ fn save_to_existing(
208208
ilst: Vec<u8>,
209209
remove_tag: bool,
210210
) -> Result<()> {
211-
let replacement;
211+
let mut replacement;
212212
let range;
213213

214214
let mut write_handle = writer.start_write();
@@ -293,9 +293,24 @@ fn save_to_existing(
293293
}
294294
}
295295

296-
let new_meta_size = (meta.len - range.len() as u64) + replacement.len() as u64;
297-
298296
drop(write_handle);
297+
298+
let mut new_meta_size = (meta.len - range.len() as u64) + replacement.len() as u64;
299+
300+
// Pad the `ilst` in the event of a shrink
301+
let mut difference = (new_meta_size as i64) - (meta.len as i64);
302+
if !replacement.is_empty() && difference != 0 {
303+
log::trace!("Tag size changed, attempting to avoid offset update");
304+
305+
let mut ilst_writer = Cursor::new(replacement);
306+
let (atom_size_difference, padding_size) = pad_atom(&mut ilst_writer, difference)?;
307+
308+
replacement = ilst_writer.into_inner();
309+
new_meta_size += padding_size;
310+
difference = atom_size_difference;
311+
}
312+
313+
// Update the parent atom sizes
299314
if new_meta_size != meta.len {
300315
// We need to change the `meta` and `udta` atom sizes
301316
let mut write_handle = writer.start_write();
@@ -307,40 +322,13 @@ fn save_to_existing(
307322

308323
write_handle.seek(SeekFrom::Start(udta.start))?;
309324
write_handle.write_atom_size(udta.start, *new_udta_size, udta.extended)?;
310-
311-
// Update offset atoms
312-
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-
339325
drop(write_handle);
340-
if difference != 0 {
341-
let offset = range.start as u64;
342-
update_offsets(writer, moov, difference, offset)?;
343-
}
326+
}
327+
328+
// Update offset atoms
329+
if difference != 0 {
330+
let offset = range.start as u64;
331+
update_offsets(writer, moov, difference, offset)?;
344332
}
345333

346334
// Replace the `ilst` atom
@@ -351,6 +339,48 @@ fn save_to_existing(
351339
Ok(())
352340
}
353341

342+
fn pad_atom<W>(writer: &mut W, mut atom_size_difference: i64) -> Result<(i64, u64)>
343+
where
344+
W: Write + Seek,
345+
{
346+
if atom_size_difference.is_positive() {
347+
log::trace!("Atom has grown, cannot avoid offset update");
348+
return Ok((atom_size_difference, 0));
349+
}
350+
351+
// When the tag shrinks, we need to try and pad it out to avoid updating
352+
// the offsets.
353+
writer.seek(SeekFrom::End(0))?;
354+
355+
let padding_size: u64;
356+
let diff_abs = atom_size_difference.abs();
357+
if diff_abs >= 8 {
358+
log::trace!(
359+
"Avoiding offset update, padding atom with {} bytes",
360+
diff_abs
361+
);
362+
363+
// If our difference is >= 8, we can make up the difference with
364+
// a `free` atom and skip updating the offsets.
365+
write_free_atom(writer, diff_abs as u32)?;
366+
atom_size_difference = 0;
367+
padding_size = diff_abs as u64;
368+
} else {
369+
log::trace!(
370+
"Cannot avoid offset update, padding atom with {} bytes",
371+
DEFAULT_PADDING_SIZE
372+
);
373+
374+
// Otherwise, we'll have to just pad the default amount,
375+
// and update the offsets.
376+
write_free_atom(writer, DEFAULT_PADDING_SIZE)?;
377+
atom_size_difference += i64::from(DEFAULT_PADDING_SIZE);
378+
padding_size = u64::from(DEFAULT_PADDING_SIZE);
379+
}
380+
381+
Ok((atom_size_difference, padding_size))
382+
}
383+
354384
fn write_free_atom<W>(writer: &mut W, size: u32) -> Result<()>
355385
where
356386
W: Write,
@@ -365,7 +395,7 @@ fn update_offsets(
365395
writer: &AtomWriter,
366396
moov: &ContextualAtom,
367397
difference: i64,
368-
offset: u64,
398+
ilst_offset: u64,
369399
) -> Result<()> {
370400
log::debug!("Checking for offset atoms to update");
371401

@@ -385,15 +415,15 @@ fn update_offsets(
385415
let count = write_handle.read_u32::<BigEndian>()?;
386416
for _ in 0..count {
387417
let read_offset = write_handle.read_u32::<BigEndian>()?;
388-
if u64::from(read_offset) < offset {
418+
if u64::from(read_offset) < ilst_offset {
389419
continue;
390420
}
391421
write_handle.seek(SeekFrom::Current(-4))?;
392422
write_handle.write_u32::<BigEndian>((i64::from(read_offset) + difference) as u32)?;
393423

394424
log::trace!(
395425
"Updated offset from {} to {}",
396-
offset,
426+
read_offset,
397427
(i64::from(read_offset) + difference) as u32
398428
);
399429
}
@@ -413,7 +443,7 @@ fn update_offsets(
413443
let count = write_handle.read_u32::<BigEndian>()?;
414444
for _ in 0..count {
415445
let read_offset = write_handle.read_u64::<BigEndian>()?;
416-
if read_offset < offset {
446+
if read_offset < ilst_offset {
417447
continue;
418448
}
419449

@@ -422,7 +452,7 @@ fn update_offsets(
422452

423453
log::trace!(
424454
"Updated offset from {} to {}",
425-
offset,
455+
read_offset,
426456
((read_offset as i64) + difference) as u64
427457
);
428458
}
@@ -451,7 +481,7 @@ fn update_offsets(
451481

452482
if base_data_offset {
453483
let read_offset = write_handle.read_u64::<BigEndian>()?;
454-
if read_offset < offset {
484+
if read_offset < ilst_offset {
455485
continue;
456486
}
457487

@@ -460,8 +490,8 @@ fn update_offsets(
460490

461491
log::trace!(
462492
"Updated offset from {} to {}",
463-
offset,
464-
((offset as i64) + difference) as u64
493+
read_offset,
494+
((read_offset as i64) + difference) as u64
465495
);
466496
}
467497
}

0 commit comments

Comments
 (0)