#![cfg(unix)]
use exiftool_rs::ExifTool;
use std::io::{Seek, SeekFrom, Write};
use std::time::{Duration, Instant};
fn find_atom(data: &[u8], want: &[u8; 4]) -> Option<(usize, u64)> {
let mut pos = 0usize;
while pos + 8 <= data.len() {
let size32 = u32::from_be_bytes(data[pos..pos + 4].try_into().unwrap()) as u64;
let typ = &data[pos + 4..pos + 8];
let size = match size32 {
1 => u64::from_be_bytes(data[pos + 8..pos + 16].try_into().unwrap()),
0 => (data.len() - pos) as u64,
n => n,
};
if typ == want {
return Some((pos, size));
}
if size == 0 {
break;
}
pos += size as usize;
}
None
}
#[test]
fn parses_metadata_from_a_multi_gb_file_without_reading_it_whole() {
let src = std::path::Path::new("tests/images/Android.mp4");
if !src.exists() {
return;
}
let data = std::fs::read(src).unwrap();
let (mdat_off, _) = find_atom(&data, b"mdat").expect("mdat atom");
let (moov_off, _) = find_atom(&data, b"moov").expect("moov atom");
const HOLE: u64 = 4 * 1024 * 1024 * 1024;
let path = std::env::temp_dir().join("exiftool_rs_large_sparse.mp4");
{
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(&data[..mdat_off]).unwrap(); f.write_all(&1u32.to_be_bytes()).unwrap(); f.write_all(b"mdat").unwrap();
f.write_all(&(16u64 + HOLE).to_be_bytes()).unwrap();
f.seek(SeekFrom::Current(HOLE as i64)).unwrap(); f.write_all(&data[moov_off..]).unwrap(); f.flush().unwrap();
}
assert!(
std::fs::metadata(&path).unwrap().len() > HOLE,
"file should be multi-GB"
);
let start = Instant::now();
let tags = ExifTool::new()
.extract_info(&path)
.expect("parse large file");
let elapsed = start.elapsed();
let make = tags
.iter()
.find(|t| t.name == "AndroidMake")
.map(|t| t.print_value.as_str());
assert_eq!(
make,
Some("Xiaomi"),
"device metadata must survive at 4 GB+"
);
assert!(
elapsed < Duration::from_secs(10),
"parsing a sparse 4 GB file took {elapsed:?} — reader is not skipping mdat by offset"
);
std::fs::remove_file(&path).ok();
}