use crate::convert::usize_from;
use crate::error::{FormatError, Result};
use crate::input::{
ArtInput, BinaryTagInput, EmbeddedBinaryTag, EmbeddedPicture, PictureType, TagInput,
};
use crate::layout::{RegionLayout, Segment};
use crate::size;
use std::io::{self, Read, Seek, SeekFrom};
const MAX_MP4_METADATA_BYTES: u64 = 256 * 1024 * 1024;
fn be_u32(b: &[u8], pos: usize) -> Result<u32> {
let s = b.get(pos..pos + 4).ok_or(FormatError::Malformed)?;
Ok(u32::from_be_bytes(s.try_into().unwrap()))
}
fn be_u64(b: &[u8], pos: usize) -> Result<u64> {
let s = b.get(pos..pos + 8).ok_or(FormatError::Malformed)?;
Ok(u64::from_be_bytes(s.try_into().unwrap()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct BoxRef {
kind: [u8; 4],
start: usize,
header_len: usize, total_len: usize, }
impl BoxRef {
fn payload_start(&self) -> usize {
self.start + self.header_len
}
fn end(&self) -> usize {
self.start + self.total_len
}
fn payload<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
debug_assert!(
self.end() <= buf.len(),
"BoxRef::payload called with a buffer it was not parsed from"
);
&buf[self.payload_start()..self.end()]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BoxHeader {
pub kind: [u8; 4],
pub header_len: u64,
pub total_len: u64,
}
pub fn box_header(hdr: &[u8], remaining: u64) -> Result<BoxHeader> {
let size32 = u64::from(be_u32(hdr, 0)?);
let kind: [u8; 4] = hdr
.get(4..8)
.ok_or(FormatError::Malformed)?
.try_into()
.unwrap();
let (header_len, total_len) = match size32 {
1 => (16u64, be_u64(hdr, 8)?),
0 => (8u64, remaining),
n => (8u64, n),
};
if total_len < header_len || total_len > remaining {
return Err(FormatError::Malformed);
}
Ok(BoxHeader {
kind,
header_len,
total_len,
})
}
#[derive(Debug, thiserror::Error)]
pub enum Mp4ScanError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Format(#[from] FormatError),
#[error("MP4 {box_kind} box is {size} bytes, exceeds the {cap}-byte metadata cap")]
MetadataTooLarge {
box_kind: &'static str,
size: u64,
cap: u64,
},
}
fn read_box(buf: &[u8], pos: usize) -> Result<BoxRef> {
let size32 = u64::from(be_u32(buf, pos)?);
let kind: [u8; 4] = buf
.get(pos + 4..pos + 8)
.ok_or(FormatError::Malformed)?
.try_into()
.unwrap();
let (header_len, total) = match size32 {
1 => (16usize, be_u64(buf, pos + 8)?),
0 => (8usize, (buf.len() - pos) as u64),
n => (8usize, n),
};
let total = usize_from(total);
let Some(end) = pos.checked_add(total) else {
return Err(FormatError::Malformed);
};
if total < header_len || end > buf.len() {
return Err(FormatError::Malformed);
}
Ok(BoxRef {
kind,
start: pos,
header_len,
total_len: total,
})
}
fn child_boxes(buf: &[u8]) -> Result<Vec<BoxRef>> {
let mut out = Vec::new();
let mut pos = 0;
while pos + 8 <= buf.len() {
let b = read_box(buf, pos)?;
pos = b.end();
out.push(b);
}
Ok(out)
}
fn find_box(buf: &[u8], kind: &[u8; 4]) -> Result<Option<BoxRef>> {
Ok(child_boxes(buf)?.into_iter().find(|b| &b.kind == kind))
}
fn find_path(buf: &[u8], path: &[&[u8; 4]]) -> Result<Option<(usize, usize)>> {
let mut base = 0usize;
let mut last = None;
for kind in path {
let region = &buf[base..];
let Some(b) = find_box(region, kind)? else {
return Ok(None);
};
let ps = base + b.payload_start();
last = Some((ps, b.total_len - b.header_len));
base = ps;
}
Ok(last)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mp4Bounds {
pub audio_offset: u64,
pub audio_length: u64,
}
fn validate_moov(moov_payload: &[u8]) -> Result<()> {
if find_box(moov_payload, b"mvex")?.is_some() {
return Err(FormatError::NotMp4);
}
let traks: Vec<_> = child_boxes(moov_payload)?
.into_iter()
.filter(|b| &b.kind == b"trak")
.collect();
if traks.len() != 1 {
return Err(FormatError::NotMp4);
}
let trak = traks[0].payload(moov_payload);
let (hp, hl) = find_path(trak, &[b"mdia", b"hdlr"])?.ok_or(FormatError::NotMp4)?;
if trak[hp..hp + hl].get(8..12) != Some(b"soun") {
return Err(FormatError::NotMp4);
}
Ok(())
}
fn locate(buf: &[u8]) -> Result<(BoxRef, BoxRef, BoxRef)> {
let top = child_boxes(buf).map_err(|_| FormatError::NotMp4)?;
if top.iter().any(|b| &b.kind == b"moof") {
return Err(FormatError::NotMp4);
}
let one = |kind: &[u8; 4]| -> Result<BoxRef> {
let mut it = top.iter().filter(|b| &b.kind == kind);
let first = it.next().copied().ok_or(FormatError::NotMp4)?;
if it.next().is_some() {
return Err(FormatError::NotMp4);
}
Ok(first)
};
let ftyp = one(b"ftyp")?;
let moov = one(b"moov")?;
let mdat = one(b"mdat")?;
validate_moov(moov.payload(buf))?;
Ok((ftyp, moov, mdat))
}
pub fn locate_audio(buf: &[u8]) -> Result<Mp4Bounds> {
let (_ftyp, _moov, mdat) = locate(buf)?;
Ok(Mp4Bounds {
audio_offset: mdat.payload_start() as u64,
audio_length: (mdat.total_len - mdat.header_len) as u64,
})
}
#[derive(Debug, Clone, PartialEq)]
pub struct Mp4Scan {
pub ftyp: Vec<u8>,
pub moov: Vec<u8>,
pub mdat_header: Vec<u8>,
pub mdat_payload_offset: u64,
pub mdat_payload_len: u64,
}
pub fn read_structure(buf: &[u8]) -> Result<Mp4Scan> {
let (ftyp, moov, mdat) = locate(buf)?;
Ok(Mp4Scan {
ftyp: buf[ftyp.start..ftyp.end()].to_vec(),
moov: buf[moov.start..moov.end()].to_vec(),
mdat_header: buf[mdat.start..mdat.payload_start()].to_vec(),
mdat_payload_offset: mdat.payload_start() as u64,
mdat_payload_len: (mdat.total_len - mdat.header_len) as u64,
})
}
pub fn read_structure_from<R: Read + Seek>(
r: &mut R,
file_len: u64,
) -> std::result::Result<Mp4Scan, Mp4ScanError> {
fn region<R: Read + Seek>(r: &mut R, off: u64, len: usize) -> io::Result<Vec<u8>> {
r.seek(SeekFrom::Start(off))?;
let mut buf = vec![0u8; len];
r.read_exact(&mut buf)?;
Ok(buf)
}
let mut ftyp: Option<(u64, BoxHeader)> = None;
let mut moov: Option<(u64, BoxHeader)> = None;
let mut mdat: Option<(u64, BoxHeader)> = None;
let mut dup = false;
let mut pos = 0u64;
while pos + 8 <= file_len {
let first8 = region(r, pos, 8)?;
let size32 = u32::from_be_bytes(first8[0..4].try_into().unwrap());
let hdr = if size32 == 1 {
let mut h = first8;
h.extend_from_slice(®ion(r, pos + 8, 8)?);
h
} else {
first8
};
let bh = box_header(&hdr, file_len - pos)?;
let total = bh.total_len;
match &bh.kind {
b"moof" => return Err(FormatError::NotMp4.into()),
b"ftyp" => dup |= ftyp.replace((pos, bh)).is_some(),
b"moov" => dup |= moov.replace((pos, bh)).is_some(),
b"mdat" => dup |= mdat.replace((pos, bh)).is_some(),
_ => {}
}
pos += total;
}
if dup {
return Err(FormatError::NotMp4.into());
}
let (ftyp_s, ftyp_h) = ftyp.ok_or(FormatError::NotMp4)?;
let (moov_s, moov_h) = moov.ok_or(FormatError::NotMp4)?;
let (mdat_s, mdat_h) = mdat.ok_or(FormatError::NotMp4)?;
for (box_kind, total_len) in [("ftyp", ftyp_h.total_len), ("moov", moov_h.total_len)] {
if total_len > MAX_MP4_METADATA_BYTES {
return Err(Mp4ScanError::MetadataTooLarge {
box_kind,
size: total_len,
cap: MAX_MP4_METADATA_BYTES,
});
}
}
let ftyp_len = usize::try_from(ftyp_h.total_len).map_err(|_| FormatError::Malformed)?;
let moov_len = usize::try_from(moov_h.total_len).map_err(|_| FormatError::Malformed)?;
let ftyp_bytes = region(r, ftyp_s, ftyp_len)?;
let moov_bytes = region(r, moov_s, moov_len)?;
let mdat_header = region(r, mdat_s, usize_from(mdat_h.header_len))?;
validate_moov(&moov_bytes[usize_from(moov_h.header_len)..])?;
Ok(Mp4Scan {
ftyp: ftyp_bytes,
moov: moov_bytes,
mdat_header,
mdat_payload_offset: mdat_s + mdat_h.header_len,
mdat_payload_len: mdat_h.total_len - mdat_h.header_len,
})
}
fn ilst_region(buf: &[u8]) -> Option<(usize, usize)> {
let moov = find_box(buf, b"moov").ok()??;
let mp = moov.payload(buf);
let base = moov.payload_start();
let (up, ul) = find_path(mp, &[b"udta"]).ok()??;
let udta = &mp[up..up + ul];
let meta = find_box(udta, b"meta").ok()??;
let meta_children = udta.get(meta.payload_start() + 4..meta.end())?;
let il = find_box(meta_children, b"ilst").ok()??;
let start = base + up + meta.payload_start() + 4 + il.payload_start();
Some((start, il.total_len - il.header_len))
}
fn read_freeform(inner: &[u8]) -> Option<(String, String)> {
let name_box = find_box(inner, b"name").ok()??;
let data_box = find_box(inner, b"data").ok()??;
let np = name_box.payload(inner);
let dp = data_box.payload(inner);
if np.len() < 4 || dp.len() < 8 {
return None;
}
let type_code = u32::from_be_bytes([dp[0], dp[1], dp[2], dp[3]]);
if type_code != 1 {
return None;
}
let name = std::str::from_utf8(&np[4..]).ok()?;
let value = std::str::from_utf8(&dp[8..]).ok()?;
let mean = find_box(inner, b"mean")
.ok()
.flatten()
.map_or("com.apple.iTunes", |m| {
let p = m.payload(inner);
if p.len() >= 4 {
std::str::from_utf8(&p[4..]).unwrap_or("com.apple.iTunes")
} else {
"com.apple.iTunes"
}
});
let key = crate::tagmap::mp4_freeform_to_key(mean, name)
.map_or_else(|| name.to_string(), str::to_string);
Some((key, value.to_string()))
}
pub fn read_tags(buf: &[u8]) -> Vec<(String, String)> {
let Some((start, len)) = ilst_region(buf) else {
return Vec::new();
};
let ilst = &buf[start..start + len];
let mut out = Vec::new();
for atom in child_boxes(ilst).unwrap_or_default() {
let inner = atom.payload(ilst);
if &atom.kind == b"----" {
if let Some(pair) = read_freeform(inner) {
out.push(pair);
}
continue;
}
let Ok(Some(data)) = find_box(inner, b"data") else {
continue;
};
let dp = data.payload(inner);
if dp.len() < 8 {
continue;
}
let value = &dp[8..]; if let Some(key) = crate::tagmap::mp4_atom_to_key(&atom.kind) {
if let Ok(s) = std::str::from_utf8(value) {
out.push((key.to_string(), s.to_string()));
}
} else if &atom.kind == b"trkn" && value.len() >= 4 {
out.push((
"tracknumber".into(),
u16::from_be_bytes([value[2], value[3]]).to_string(),
));
} else if &atom.kind == b"disk" && value.len() >= 4 {
out.push((
"discnumber".into(),
u16::from_be_bytes([value[2], value[3]]).to_string(),
));
}
}
out
}
pub fn read_pictures(buf: &[u8], max_art_bytes: usize) -> Vec<EmbeddedPicture> {
let Some((start, len)) = ilst_region(buf) else {
return Vec::new();
};
let ilst = &buf[start..start + len];
let mut out = Vec::new();
for atom in child_boxes(ilst).unwrap_or_default() {
if &atom.kind != b"covr" {
continue;
}
let inner = atom.payload(ilst);
for data in child_boxes(inner).unwrap_or_default() {
if &data.kind != b"data" {
continue;
}
let dp = data.payload(inner);
if dp.len() < 8 {
continue;
}
if dp.len() - 8 > max_art_bytes {
continue;
}
let mime = match u32::from_be_bytes([dp[0], dp[1], dp[2], dp[3]]) {
13 => "image/jpeg",
14 => "image/png",
_ => continue,
};
out.push(EmbeddedPicture {
mime: mime.to_string(),
picture_type: PictureType::new(3).expect("3 is in range"),
description: String::new(),
width: 0,
height: 0,
data: dp[8..].to_vec(),
});
}
}
out
}
pub fn read_binary_tags(buf: &[u8], max_binary_tag_bytes: usize) -> Vec<EmbeddedBinaryTag> {
let Some((start, len)) = ilst_region(buf) else {
return Vec::new();
};
let ilst = &buf[start..start + len];
let mut out = Vec::new();
for atom in child_boxes(ilst).unwrap_or_default() {
if &atom.kind != b"----" {
continue;
}
let inner = atom.payload(ilst);
let Ok(Some(data)) = find_box(inner, b"data") else {
continue;
};
let dp = data.payload(inner);
if dp.len() < 8 {
continue;
}
if dp.len() - 8 > max_binary_tag_bytes {
continue;
}
let type_code = u32::from_be_bytes([dp[0], dp[1], dp[2], dp[3]]);
if type_code == 1 {
continue;
}
let Some(name) = find_box(inner, b"name").ok().flatten().and_then(|n| {
let p = n.payload(inner);
(p.len() >= 4)
.then(|| std::str::from_utf8(&p[4..]).ok())
.flatten()
}) else {
continue;
};
let mean = find_box(inner, b"mean")
.ok()
.flatten()
.map_or("com.apple.iTunes", |m| {
let p = m.payload(inner);
if p.len() >= 4 {
std::str::from_utf8(&p[4..]).unwrap_or("com.apple.iTunes")
} else {
"com.apple.iTunes"
}
});
out.push(EmbeddedBinaryTag {
key: format!("----:{mean}:{name}"),
payload: dp[8..].to_vec(),
});
}
out
}
fn boxed(kind: &[u8; 4], payload: &[u8]) -> Result<Vec<u8>> {
let size = u32::try_from(8 + payload.len()).map_err(|_| FormatError::TooLarge)?;
let mut v = size.to_be_bytes().to_vec();
v.extend_from_slice(kind);
v.extend_from_slice(payload);
Ok(v)
}
fn text_atom(kind: &[u8; 4], values: &[&str]) -> Result<Vec<u8>> {
let mut inner = Vec::new();
for v in values {
let mut data = 1u32.to_be_bytes().to_vec(); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(v.as_bytes());
inner.extend(boxed(b"data", &data)?);
}
boxed(kind, &inner)
}
fn number_atom(kind: &[u8; 4], n: u16, width: usize) -> Result<Vec<u8>> {
debug_assert!(
width >= 4,
"number_atom width must hold the 4-byte reserved+value prefix"
);
let mut data = 0u32.to_be_bytes().to_vec(); data.extend_from_slice(&0u32.to_be_bytes()); let mut body = vec![0u8, 0];
body.extend_from_slice(&n.to_be_bytes());
body.resize(width, 0);
data.extend_from_slice(&body);
boxed(kind, &boxed(b"data", &data)?)
}
fn freeform_atom(mean: &str, name: &str, values: &[&str]) -> Result<Vec<u8>> {
let mut inner = Vec::new();
let mut mean_body = 0u32.to_be_bytes().to_vec(); mean_body.extend_from_slice(mean.as_bytes());
inner.extend(boxed(b"mean", &mean_body)?);
let mut name_body = 0u32.to_be_bytes().to_vec();
name_body.extend_from_slice(name.as_bytes());
inner.extend(boxed(b"name", &name_body)?);
for v in values {
let mut data = 1u32.to_be_bytes().to_vec(); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(v.as_bytes());
inner.extend(boxed(b"data", &data)?);
}
boxed(b"----", &inner)
}
fn parse_freeform_key(key: &str) -> Option<(&str, &str)> {
key.strip_prefix("----:")?.split_once(':')
}
fn freeform_binary_prefix(mean: &str, name: &str, payload_len: u64) -> Result<Vec<u8>> {
let mut mean_body = 0u32.to_be_bytes().to_vec(); mean_body.extend_from_slice(mean.as_bytes());
let mean_box = boxed(b"mean", &mean_body)?;
let mut name_body = 0u32.to_be_bytes().to_vec();
name_body.extend_from_slice(name.as_bytes());
let name_box = boxed(b"name", &name_body)?;
let data_size = size::checked_add(16, payload_len)?; let inner_len = size::checked_sum([mean_box.len() as u64, name_box.len() as u64, data_size])?;
let outer_len = size::checked_add(8, inner_len)?;
let mut out = u32::try_from(outer_len)
.map_err(|_| FormatError::TooLarge)?
.to_be_bytes()
.to_vec();
out.extend_from_slice(b"----");
out.extend_from_slice(&mean_box);
out.extend_from_slice(&name_box);
out.extend_from_slice(
&u32::try_from(data_size)
.map_err(|_| FormatError::TooLarge)?
.to_be_bytes(),
);
out.extend_from_slice(b"data");
out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); Ok(out)
}
fn build_udta(
tags: &[TagInput],
binary_tags: &[BinaryTagInput],
arts: &[ArtInput],
) -> Result<(Vec<Segment>, u64)> {
let mut groups: Vec<(&str, Vec<&str>)> = Vec::new();
for t in tags {
match groups.last_mut() {
Some(g) if g.0 == t.key => g.1.push(&t.value),
_ => groups.push((&t.key, vec![&t.value])),
}
}
let mut ilst_inline: Vec<u8> = Vec::new();
for (key, values) in &groups {
match crate::tagmap::key_to_mp4(key) {
Some(crate::tagmap::Mp4Slot::Text(atom)) => {
ilst_inline.extend(text_atom(atom, values)?);
}
Some(crate::tagmap::Mp4Slot::Number(atom, width)) => {
if let Ok(n) = values.first().copied().unwrap_or("").parse::<u16>() {
ilst_inline.extend(number_atom(atom, n, width)?);
}
}
Some(crate::tagmap::Mp4Slot::Freeform(mean, name)) => {
ilst_inline.extend(freeform_atom(mean, name, values)?);
}
None => ilst_inline.extend(freeform_atom("com.apple.iTunes", key, values)?),
}
}
let mut ilst_segments: Vec<Segment> = Vec::new();
let mut streamed_total: u64 = 0;
for bt in binary_tags {
let Some((mean, name)) = parse_freeform_key(&bt.key) else {
continue;
};
ilst_inline.extend_from_slice(&freeform_binary_prefix(mean, name, bt.len.get())?);
ilst_segments.push(Segment::Inline(std::mem::take(&mut ilst_inline)));
ilst_segments.push(Segment::BinaryTag {
payload_id: bt.payload_id,
len: bt.len,
});
streamed_total = size::checked_add(streamed_total, bt.len.get())?;
}
if !arts.is_empty() {
let covr_size: u64 = arts.iter().try_fold(8u64, |acc, a| {
size::checked_add(acc, size::checked_add(16, a.data_len.get())?)
})?;
ilst_inline.extend_from_slice(
&u32::try_from(covr_size)
.map_err(|_| FormatError::TooLarge)?
.to_be_bytes(),
);
ilst_inline.extend_from_slice(b"covr");
for a in arts {
let type_code: u32 = if a.mime == "image/png" { 14 } else { 13 };
let data_size = size::checked_add(16, a.data_len.get())?; ilst_inline.extend_from_slice(
&u32::try_from(data_size)
.map_err(|_| FormatError::TooLarge)?
.to_be_bytes(),
);
ilst_inline.extend_from_slice(b"data");
ilst_inline.extend_from_slice(&type_code.to_be_bytes());
ilst_inline.extend_from_slice(&0u32.to_be_bytes()); ilst_segments.push(Segment::Inline(std::mem::take(&mut ilst_inline)));
ilst_segments.push(Segment::ArtImage {
art_id: a.art_id,
len: a.data_len,
});
streamed_total = size::checked_add(streamed_total, a.data_len.get())?;
}
} else if !ilst_inline.is_empty() {
ilst_segments.push(Segment::Inline(std::mem::take(&mut ilst_inline)));
}
let ilst_inline_len: u64 = ilst_segments
.iter()
.map(|s| match s {
Segment::Inline(b) => b.len() as u64,
_ => 0,
})
.sum();
let mut hdlr_body = vec![0u8; 8];
hdlr_body.extend_from_slice(b"mdir");
hdlr_body.extend_from_slice(b"appl");
hdlr_body.extend_from_slice(&[0u8; 9]);
let hdlr = boxed(b"hdlr", &hdlr_body)?;
let ilst_size = size::checked_sum([8, ilst_inline_len, streamed_total])?;
let meta_inline_len = 4 + hdlr.len() as u64 + 8 + ilst_inline_len; let meta_size = size::checked_sum([8, meta_inline_len, streamed_total])?;
let udta_inline_len = 8 + meta_inline_len; let udta_size = size::checked_sum([8, udta_inline_len, streamed_total])?;
let udta_size = u32::try_from(udta_size).map_err(|_| FormatError::TooLarge)?;
let meta_size = u32::try_from(meta_size).map_err(|_| FormatError::TooLarge)?;
let ilst_size = u32::try_from(ilst_size).map_err(|_| FormatError::TooLarge)?;
let mut header = udta_size.to_be_bytes().to_vec();
header.extend_from_slice(b"udta");
header.extend_from_slice(&meta_size.to_be_bytes());
header.extend_from_slice(b"meta");
header.extend_from_slice(&0u32.to_be_bytes()); header.extend_from_slice(&hdlr);
header.extend_from_slice(&ilst_size.to_be_bytes());
header.extend_from_slice(b"ilst");
let mut segments: Vec<Segment> = Vec::new();
let mut lead = header;
for seg in ilst_segments {
match seg {
Segment::Inline(b) => lead.extend_from_slice(&b),
other => {
segments.push(Segment::Inline(std::mem::take(&mut lead)));
segments.push(other);
}
}
}
if !lead.is_empty() {
segments.push(Segment::Inline(lead));
}
Ok((segments, streamed_total))
}
fn patch_chunk_offsets(kept: &mut [u8], delta: i64) -> Result<()> {
let (range, entry) = match find_path(kept, &[b"trak", b"mdia", b"minf", b"stbl", b"stco"])? {
Some(r) => (r, 4usize),
None => match find_path(kept, &[b"trak", b"mdia", b"minf", b"stbl", b"co64"])? {
Some(r) => (r, 8usize),
None => return Err(FormatError::Malformed),
},
};
let (start, len) = range;
let count = be_u32(kept, start + 4)? as usize;
for i in 0..count {
let pos = start + 8 + i * entry;
if pos + entry > start + len {
return Err(FormatError::Malformed);
}
if entry == 4 {
let v = i64::from(be_u32(kept, pos)?) + delta;
let new_val = u32::try_from(v).map_err(|_| FormatError::TooLarge)?;
kept[pos..pos + 4].copy_from_slice(&new_val.to_be_bytes());
} else {
let v = be_u64(kept, pos)?.cast_signed() + delta;
if v < 0 {
return Err(FormatError::Malformed);
}
kept[pos..pos + 8].copy_from_slice(&v.cast_unsigned().to_be_bytes());
}
}
Ok(())
}
pub fn synthesize_layout(
scan: &Mp4Scan,
tags: &[TagInput],
binary_tags: &[BinaryTagInput],
arts: &[ArtInput],
) -> Result<RegionLayout> {
let moov_payload_start = read_box(&scan.moov, 0)?.payload_start();
let moov_payload = &scan.moov[moov_payload_start..];
let mut kept = Vec::new();
for b in child_boxes(moov_payload)? {
if &b.kind != b"udta" {
kept.extend_from_slice(&moov_payload[b.start..b.end()]);
}
}
let arts: Vec<ArtInput> = arts.to_vec();
let (udta_segments, _streamed_total) = build_udta(tags, binary_tags, &arts)?;
let udta_total: u64 = udta_segments.iter().map(Segment::len).sum();
let new_moov_size = size::checked_sum([8, kept.len() as u64, udta_total])?;
let new_moov_size_u32 = u32::try_from(new_moov_size).map_err(|_| FormatError::TooLarge)?;
let new_mdat_payload_pos = size::checked_sum([
scan.ftyp.len() as u64,
new_moov_size,
scan.mdat_header.len() as u64,
])?;
let delta = new_mdat_payload_pos.cast_signed() - scan.mdat_payload_offset.cast_signed();
patch_chunk_offsets(&mut kept, delta)?;
let mut head = Vec::new();
head.extend_from_slice(&scan.ftyp);
head.extend_from_slice(&new_moov_size_u32.to_be_bytes());
head.extend_from_slice(b"moov");
head.extend_from_slice(&kept);
let mut udta_iter = udta_segments.into_iter();
let Some(Segment::Inline(first)) = udta_iter.next() else {
return Err(FormatError::ProducerBug(
"build_udta did not yield a leading Inline framing segment",
));
};
head.extend_from_slice(&first);
let mut segments: Vec<Segment> = vec![Segment::Inline(head)];
segments.extend(udta_iter);
match segments.last_mut() {
Some(Segment::Inline(b)) => b.extend_from_slice(&scan.mdat_header),
_ => segments.push(Segment::Inline(scan.mdat_header.clone())),
}
segments.push(Segment::BackingAudio {
offset: scan.mdat_payload_offset,
len: scan.mdat_payload_len,
});
Ok(RegionLayout::validated(segments)?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input::{BlobLen, PictureType};
fn bx(kind: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let mut v = u32::try_from(8 + payload.len())
.unwrap()
.to_be_bytes()
.to_vec();
v.extend_from_slice(kind);
v.extend_from_slice(payload);
v
}
#[test]
fn walks_top_level_boxes() {
let mut buf = bx(b"ftyp", b"M4A ");
buf.extend(bx(b"free", b"\x00\x00"));
let boxes = child_boxes(&buf).unwrap();
assert_eq!(boxes.len(), 2);
assert_eq!(&boxes[0].kind, b"ftyp");
assert_eq!(boxes[0].payload(&buf), b"M4A ");
assert_eq!(&boxes[1].kind, b"free");
}
#[test]
fn find_box_and_nested_path() {
let mut hdlr_payload = vec![0u8; 8];
hdlr_payload.extend_from_slice(b"soun");
hdlr_payload.extend_from_slice(&[0u8; 12]);
let moov = bx(
b"moov",
&bx(b"trak", &bx(b"mdia", &bx(b"hdlr", &hdlr_payload))),
);
let m = find_box(&moov, b"moov").unwrap().unwrap();
let (start, len) = find_path(m.payload(&moov), &[b"trak", b"mdia", b"hdlr"])
.unwrap()
.unwrap();
assert_eq!(&m.payload(&moov)[start..start + len][8..12], b"soun");
}
#[test]
fn rejects_truncated_box() {
let buf = [0u8, 0, 0, 99, b'm', b'o', b'o', b'v']; assert!(child_boxes(&buf).is_err());
}
fn mk_mp4(moov_first: bool, mdat_payload: &[u8], stco_entries: &[u32]) -> Vec<u8> {
let mut stco = vec![0u8; 4];
stco.extend_from_slice(&u32::try_from(stco_entries.len()).unwrap().to_be_bytes());
for e in stco_entries {
stco.extend_from_slice(&e.to_be_bytes());
}
let mut hdlr_p = vec![0u8; 8];
hdlr_p.extend_from_slice(b"soun");
hdlr_p.extend_from_slice(&[0u8; 12]);
let minf = bx(b"minf", &bx(b"stbl", &bx(b"stco", &stco)));
let mdia = bx(b"mdia", &[bx(b"hdlr", &hdlr_p), minf].concat());
let trak = bx(b"trak", &mdia);
let moov = bx(b"moov", &[bx(b"mvhd", &[0u8; 8]), trak].concat());
let mdat = bx(b"mdat", mdat_payload);
let ftyp = bx(b"ftyp", b"M4A isom");
if moov_first {
[ftyp, moov, mdat].concat()
} else {
[ftyp, mdat, moov].concat()
}
}
#[test]
fn locates_audio_moov_first_and_last() {
for moov_first in [true, false] {
let buf = mk_mp4(moov_first, b"AUDIODATA", &[0]);
let b = locate_audio(&buf).unwrap();
assert_eq!(b.audio_length, 9);
assert_eq!(&buf[usize_from(b.audio_offset)..][..9], b"AUDIODATA");
}
}
#[test]
fn rejects_fragmented_video_and_multi_mdat() {
let base = mk_mp4(true, b"X", &[0]);
let mut frag = base.clone();
frag.extend(bx(b"moof", b"\x00"));
assert!(locate_audio(&frag).is_err());
let mut two = base.clone();
two.extend(bx(b"mdat", b"Y"));
assert!(locate_audio(&two).is_err());
let mut hdlr_p = vec![0u8; 8];
hdlr_p.extend_from_slice(b"vide");
hdlr_p.extend_from_slice(&[0u8; 12]);
let video_moov = bx(b"moov", &bx(b"trak", &bx(b"mdia", &bx(b"hdlr", &hdlr_p))));
let vbuf = [bx(b"ftyp", b"M4A "), video_moov, bx(b"mdat", b"Z")].concat();
assert!(locate_audio(&vbuf).is_err());
}
fn soun_trak() -> Vec<u8> {
let mut stco = vec![0u8; 4];
stco.extend_from_slice(&1u32.to_be_bytes());
stco.extend_from_slice(&0u32.to_be_bytes());
let mut hdlr_p = vec![0u8; 8];
hdlr_p.extend_from_slice(b"soun");
hdlr_p.extend_from_slice(&[0u8; 12]);
let minf = bx(b"minf", &bx(b"stbl", &bx(b"stco", &stco)));
let mdia = bx(b"mdia", &[bx(b"hdlr", &hdlr_p), minf].concat());
bx(b"trak", &mdia)
}
#[test]
fn rejects_mvex_in_moov() {
let moov = bx(
b"moov",
&[bx(b"mvhd", &[0u8; 8]), bx(b"mvex", b"\x00"), soun_trak()].concat(),
);
let buf = [bx(b"ftyp", b"M4A isom"), moov, bx(b"mdat", b"X")].concat();
assert!(locate_audio(&buf).is_err());
}
#[test]
fn rejects_multi_trak() {
let moov = bx(
b"moov",
&[bx(b"mvhd", &[0u8; 8]), soun_trak(), soun_trak()].concat(),
);
let buf = [bx(b"ftyp", b"M4A isom"), moov, bx(b"mdat", b"X")].concat();
assert!(locate_audio(&buf).is_err());
}
#[test]
fn reads_structure_parts() {
let buf = mk_mp4(false, b"AUDIODATA", &[0]); let s = read_structure(&buf).unwrap();
assert_eq!(&s.ftyp[4..8], b"ftyp");
assert_eq!(&s.moov[4..8], b"moov");
assert_eq!(&s.mdat_header[4..8], b"mdat");
assert_eq!(s.mdat_payload_len, 9);
assert_eq!(&buf[usize_from(s.mdat_payload_offset)..][..9], b"AUDIODATA");
}
fn data_atom(type_code: u32, value: &[u8]) -> Vec<u8> {
let mut p = type_code.to_be_bytes().to_vec();
p.extend_from_slice(&0u32.to_be_bytes()); p.extend_from_slice(value);
bx(b"data", &p)
}
fn mp4_with_ilst(ilst_atoms: &[u8], moov_first: bool) -> Vec<u8> {
let ilst = bx(b"ilst", ilst_atoms);
let mut hdlr = vec![0u8; 8];
hdlr.extend_from_slice(b"mdir");
hdlr.extend_from_slice(b"appl");
hdlr.extend_from_slice(&[0u8; 9]);
let mut meta = vec![0u8; 4]; meta.extend(bx(b"hdlr", &hdlr));
meta.extend(ilst);
let udta = bx(b"udta", &bx(b"meta", &meta));
let mut hdlr_p = vec![0u8; 8];
hdlr_p.extend_from_slice(b"soun");
hdlr_p.extend_from_slice(&[0u8; 12]);
let mut stco = vec![0u8; 4];
stco.extend_from_slice(&1u32.to_be_bytes());
stco.extend_from_slice(&0u32.to_be_bytes());
let minf = bx(b"minf", &bx(b"stbl", &bx(b"stco", &stco)));
let trak = bx(
b"trak",
&bx(b"mdia", &[bx(b"hdlr", &hdlr_p), minf].concat()),
);
let moov = bx(b"moov", &[bx(b"mvhd", &[0u8; 8]), trak, udta].concat());
let ftyp = bx(b"ftyp", b"M4A ");
let mdat = bx(b"mdat", b"AUDIO");
if moov_first {
[ftyp, moov, mdat].concat()
} else {
[ftyp, mdat, moov].concat()
}
}
#[test]
fn reads_text_and_track_tags() {
let atoms = [
bx(b"\xa9nam", &data_atom(1, b"Song")),
bx(b"aART", &data_atom(1, b"Band")),
bx(b"trkn", &data_atom(0, &[0, 0, 0, 3, 0, 0, 0, 0])),
]
.concat();
let buf = mp4_with_ilst(&atoms, true);
let tags = read_tags(&buf);
assert!(tags.contains(&("title".into(), "Song".into())));
assert!(tags.contains(&("albumartist".into(), "Band".into())));
assert!(tags.contains(&("tracknumber".into(), "3".into())));
}
#[test]
fn reads_cover_art() {
let jpeg = [0xff, 0xd8, 0xff, 0xe0, 1, 2, 3];
let buf = mp4_with_ilst(&bx(b"covr", &data_atom(13, &jpeg)), false);
let pics = read_pictures(&buf, usize::MAX);
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].mime, "image/jpeg");
assert_eq!(pics[0].data, jpeg);
}
#[test]
fn read_side_never_panics_on_garbage() {
assert!(read_tags(&[]).is_empty());
assert!(read_pictures(&[], usize::MAX).is_empty());
let garbage = b"not an mp4 file at all............";
assert!(read_tags(garbage).is_empty());
assert!(read_pictures(garbage, usize::MAX).is_empty());
let no_ilst = mk_mp4(true, b"AUDIO", &[0]);
assert!(read_tags(&no_ilst).is_empty());
assert!(read_pictures(&no_ilst, usize::MAX).is_empty());
let truncated_meta = bx(b"udta", &bx(b"meta", &[0u8, 0]));
let moov = bx(b"moov", &[bx(b"mvhd", &[0u8; 8]), truncated_meta].concat());
let ftyp = bx(b"ftyp", b"M4A ");
let mdat = bx(b"mdat", b"AUDIO");
let lying = [ftyp, moov, mdat].concat();
assert!(read_tags(&lying).is_empty());
assert!(read_pictures(&lying, usize::MAX).is_empty());
}
#[test]
fn build_udta_no_art_round_trips() {
let tags = vec![
TagInput::new("title", "Song"),
TagInput::new("tracknumber", "5"),
];
let (segs, streamed) = build_udta(&tags, &[], &[]).unwrap();
assert_eq!(streamed, 0);
let prefix = materialize_udta(&segs);
let b = read_box(&prefix, 0).unwrap();
assert_eq!(&b.kind, b"udta");
assert_eq!(b.total_len, prefix.len());
let buf = [
bx(b"ftyp", b"M4A "),
bx(b"moov", &prefix),
bx(b"mdat", b"A"),
]
.concat();
let tags = read_tags(&buf);
assert!(tags.contains(&("title".into(), "Song".into())));
assert!(tags.contains(&("tracknumber".into(), "5".into())));
}
#[test]
fn build_udta_with_art_reserves_size_without_image() {
let art = ArtInput {
art_id: 1,
mime: "image/png".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(100).unwrap(),
};
let (segs, streamed) = build_udta(&[TagInput::new("title", "T")], &[], &[art]).unwrap();
assert_eq!(streamed, 100);
assert!(matches!(
segs.last(),
Some(Segment::ArtImage { len, .. }) if len.get() == 100
));
let inline_total: usize = segs
.iter()
.filter_map(|s| match s {
Segment::Inline(b) => Some(b.len()),
_ => None,
})
.sum();
let Segment::Inline(head) = &segs[0] else {
panic!("first udta segment is inline framing");
};
let declared = u32::from_be_bytes(head[0..4].try_into().unwrap()) as usize;
assert_eq!(declared, inline_total + 100);
assert!(head.windows(4).any(|w| w == b"covr"));
}
#[test]
fn build_udta_rejects_oversize_art() {
let art = ArtInput {
art_id: 1,
mime: "image/jpeg".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(u64::from(u32::MAX) + 1).unwrap(),
};
assert!(matches!(
build_udta(&[TagInput::new("title", "T")], &[], &[art]),
Err(FormatError::TooLarge)
));
}
#[test]
fn build_udta_groups_multi_value_text() {
let tags = vec![
TagInput::new("genre", "Rock"),
TagInput::new("genre", "Metal"),
];
let (segs, streamed) = build_udta(&tags, &[], &[]).unwrap();
assert_eq!(streamed, 0);
let prefix = materialize_udta(&segs);
let gen_count = prefix.windows(4).filter(|w| *w == b"\xa9gen").count();
assert_eq!(
gen_count, 1,
"expected exactly one genre atom, got {gen_count}"
);
let kind_at = prefix
.windows(4)
.position(|w| w == b"\xa9gen")
.expect("genre atom present");
let atom = read_box(&prefix, kind_at - 4).unwrap();
assert_eq!(&atom.kind, b"\xa9gen");
let children = child_boxes(atom.payload(&prefix)).unwrap();
let data_count = children.iter().filter(|c| &c.kind == b"data").count();
assert_eq!(
data_count, 2,
"expected two data sub-boxes, got {data_count}"
);
assert!(prefix.windows(4).any(|w| w == b"Rock"));
assert!(prefix.windows(5).any(|w| w == b"Metal"));
}
#[test]
fn build_udta_empty_tags_is_valid() {
let (segs, streamed) = build_udta(&[], &[], &[]).unwrap();
assert_eq!(streamed, 0);
let prefix = materialize_udta(&segs);
let b = read_box(&prefix, 0).unwrap();
assert_eq!(&b.kind, b"udta");
assert_eq!(b.total_len, prefix.len());
let buf = [
bx(b"ftyp", b"M4A "),
bx(b"moov", &prefix),
bx(b"mdat", b"A"),
]
.concat();
assert!(read_tags(&buf).is_empty());
}
fn inline_head(layout: &RegionLayout) -> Vec<u8> {
match &layout.segments()[0] {
Segment::Inline(b) => b.clone(),
_ => panic!("expected Inline head"),
}
}
fn materialize_udta(segments: &[Segment]) -> Vec<u8> {
let mut out = Vec::new();
for seg in segments {
match seg {
Segment::Inline(b) => out.extend_from_slice(b),
Segment::BinaryTag { len, .. } | Segment::ArtImage { len, .. } => {
out.resize(out.len() + usize_from(len.get()), 0);
}
other => panic!("unexpected segment in udta: {other:?}"),
}
}
out
}
fn find_moov_in_head(head: &[u8]) -> BoxRef {
let mut pos = 0;
loop {
let b = read_box(head, pos).unwrap();
if &b.kind == b"moov" {
return b;
}
pos = b.end();
}
}
fn first_stco(head: &[u8]) -> Vec<u32> {
let moov = find_moov_in_head(head);
let mp = moov.payload(head);
let (sp, sl) = find_path(mp, &[b"trak", b"mdia", b"minf", b"stbl", b"stco"])
.unwrap()
.unwrap();
let stco = &mp[sp..sp + sl];
let count = u32::from_be_bytes(stco[4..8].try_into().unwrap()) as usize;
(0..count)
.map(|i| u32::from_be_bytes(stco[8 + i * 4..12 + i * 4].try_into().unwrap()))
.collect()
}
#[test]
fn synthesize_no_art_patches_stco() {
let buf = mk_mp4(true, b"AUDIODATA", &[42, 100]);
let scan = read_structure(&buf).unwrap();
let layout = synthesize_layout(&scan, &[TagInput::new("title", "New")], &[], &[]).unwrap();
match layout.segments().last().unwrap() {
Segment::BackingAudio { offset, len } => {
assert_eq!(*offset, scan.mdat_payload_offset);
assert_eq!(*len, scan.mdat_payload_len);
}
_ => panic!("expected BackingAudio tail"),
}
let head = inline_head(&layout);
let new_mdat = head.len() as u64;
let delta = new_mdat - scan.mdat_payload_offset;
assert_eq!(
first_stco(&head),
vec![
42 + u32::try_from(delta).unwrap(),
100 + u32::try_from(delta).unwrap()
]
);
let moov = find_moov_in_head(&head);
assert_eq!(moov.end(), head.len() - scan.mdat_header.len());
}
fn mk_mp4_co64(mdat_payload: &[u8], co64_entries: &[u64]) -> Vec<u8> {
let mut co64 = vec![0u8; 4];
co64.extend_from_slice(&u32::try_from(co64_entries.len()).unwrap().to_be_bytes());
for e in co64_entries {
co64.extend_from_slice(&e.to_be_bytes());
}
let mut hdlr_p = vec![0u8; 8];
hdlr_p.extend_from_slice(b"soun");
hdlr_p.extend_from_slice(&[0u8; 12]);
let minf = bx(b"minf", &bx(b"stbl", &bx(b"co64", &co64)));
let mdia = bx(b"mdia", &[bx(b"hdlr", &hdlr_p), minf].concat());
let trak = bx(b"trak", &mdia);
let moov = bx(b"moov", &[bx(b"mvhd", &[0u8; 8]), trak].concat());
let mdat = bx(b"mdat", mdat_payload);
let ftyp = bx(b"ftyp", b"M4A isom");
[ftyp, moov, mdat].concat()
}
fn first_co64(head: &[u8]) -> Vec<u64> {
let moov = find_moov_in_head(head);
let mp = moov.payload(head);
let (sp, sl) = find_path(mp, &[b"trak", b"mdia", b"minf", b"stbl", b"co64"])
.unwrap()
.unwrap();
let co64 = &mp[sp..sp + sl];
let count = u32::from_be_bytes(co64[4..8].try_into().unwrap()) as usize;
(0..count)
.map(|i| u64::from_be_bytes(co64[8 + i * 8..16 + i * 8].try_into().unwrap()))
.collect()
}
#[test]
fn synthesize_patches_co64() {
let buf = mk_mp4_co64(b"AUDIODATA", &[42, 100]);
let scan = read_structure(&buf).unwrap();
let layout = synthesize_layout(&scan, &[TagInput::new("title", "New")], &[], &[]).unwrap();
match layout.segments().last().unwrap() {
Segment::BackingAudio { offset, len } => {
assert_eq!(*offset, scan.mdat_payload_offset);
assert_eq!(*len, scan.mdat_payload_len);
}
_ => panic!("expected BackingAudio tail"),
}
let head = inline_head(&layout);
let new_mdat = head.len() as u64;
let delta = new_mdat - scan.mdat_payload_offset;
assert_eq!(first_co64(&head), vec![42 + delta, 100 + delta]);
let moov = find_moov_in_head(&head);
assert_eq!(moov.end(), head.len() - scan.mdat_header.len());
}
#[test]
fn synthesize_with_art_splits_for_streaming() {
let buf = mk_mp4(false, b"AUDIODATA", &[0]);
let scan = read_structure(&buf).unwrap();
let art = ArtInput {
art_id: 7,
mime: "image/jpeg".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(50).unwrap(),
};
let layout = synthesize_layout(&scan, &[TagInput::new("title", "T")], &[], &[art]).unwrap();
let segs = layout.segments();
assert!(matches!(segs[1], Segment::ArtImage { art_id: 7, len, .. } if len.get() == 50));
assert!(matches!(segs[2], Segment::Inline(_))); assert!(matches!(segs.last().unwrap(), Segment::BackingAudio { .. }));
}
#[test]
fn synthesize_picks_first_nonempty_art() {
let buf = mk_mp4(false, b"AUDIODATA", &[0]);
let scan = read_structure(&buf).unwrap();
let real = ArtInput {
art_id: 9,
mime: "image/png".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(40).unwrap(),
};
let layout =
synthesize_layout(&scan, &[TagInput::new("title", "T")], &[], &[real]).unwrap();
let segs = layout.segments();
assert!(
segs.iter()
.any(|s| matches!(s, Segment::ArtImage { art_id: 9, len, .. } if len.get() == 40)),
"the first nonempty art must be served"
);
}
#[test]
fn synthesize_handles_zero_length_mdat() {
let buf = mk_mp4(true, b"", &[0]); let scan = read_structure(&buf).unwrap();
assert_eq!(scan.mdat_payload_len, 0);
let layout = synthesize_layout(&scan, &[TagInput::new("title", "Z")], &[], &[]).unwrap();
match layout.segments().last().unwrap() {
Segment::BackingAudio { offset, len } => {
assert_eq!(*offset, scan.mdat_payload_offset);
assert_eq!(*len, 0);
}
_ => panic!("expected BackingAudio tail"),
}
}
#[test]
fn box_header_parses_8_byte_16_byte_and_size0() {
let mut h = 16u32.to_be_bytes().to_vec();
h.extend_from_slice(b"moov");
let bh = box_header(&h, 1000).unwrap();
assert_eq!(&bh.kind, b"moov");
assert_eq!(bh.header_len, 8);
assert_eq!(bh.total_len, 16);
let mut h = 1u32.to_be_bytes().to_vec();
h.extend_from_slice(b"mdat");
h.extend_from_slice(&40u64.to_be_bytes());
let bh = box_header(&h, 1000).unwrap();
assert_eq!(bh.header_len, 16);
assert_eq!(bh.total_len, 40);
let mut h = 0u32.to_be_bytes().to_vec();
h.extend_from_slice(b"mdat");
let bh = box_header(&h, 500).unwrap();
assert_eq!(bh.header_len, 8);
assert_eq!(bh.total_len, 500);
}
#[test]
fn box_header_rejects_impossible_sizes() {
let mut h = 4u32.to_be_bytes().to_vec();
h.extend_from_slice(b"moov");
assert_eq!(box_header(&h, 1000), Err(FormatError::Malformed));
let mut h = 2000u32.to_be_bytes().to_vec();
h.extend_from_slice(b"moov");
assert_eq!(box_header(&h, 100), Err(FormatError::Malformed));
assert_eq!(box_header(&[0u8; 4], 1000), Err(FormatError::Malformed));
}
#[test]
fn read_structure_from_matches_buffer_path() {
for moov_first in [true, false] {
let buf = mk_mp4(moov_first, &vec![0xABu8; 4096], &[0]);
let from_buf = read_structure(&buf).unwrap();
let mut cur = std::io::Cursor::new(buf.clone());
let from_stream = read_structure_from(&mut cur, buf.len() as u64).unwrap();
assert_eq!(from_stream, from_buf);
}
}
#[test]
fn read_structure_from_never_reads_mdat_payload() {
let buf = mk_mp4(false, &vec![0xCDu8; 100_000], &[0]);
let scan = read_structure(&buf).unwrap();
let pay_start = scan.mdat_payload_offset;
let pay_end = pay_start + scan.mdat_payload_len;
struct Tracking {
inner: std::io::Cursor<Vec<u8>>,
touched: Vec<(u64, u64)>,
}
impl std::io::Read for Tracking {
fn read(&mut self, b: &mut [u8]) -> std::io::Result<usize> {
let off = self.inner.position();
let n = std::io::Read::read(&mut self.inner, b)?;
self.touched.push((off, off + n as u64));
Ok(n)
}
}
impl std::io::Seek for Tracking {
fn seek(&mut self, p: std::io::SeekFrom) -> std::io::Result<u64> {
self.inner.seek(p)
}
}
let mut tr = Tracking {
inner: std::io::Cursor::new(buf.clone()),
touched: Vec::new(),
};
let from_stream = read_structure_from(&mut tr, buf.len() as u64).unwrap();
assert_eq!(from_stream, scan);
for (s, e) in &tr.touched {
assert!(
*e <= pay_start || *s >= pay_end,
"read [{s},{e}) overlaps mdat payload [{pay_start},{pay_end})"
);
}
}
#[test]
fn read_freeform_extracts_name_and_value() {
let mut mean_body = 0u32.to_be_bytes().to_vec();
mean_body.extend_from_slice(b"com.apple.iTunes");
let mut name_body = 0u32.to_be_bytes().to_vec();
name_body.extend_from_slice(b"MusicBrainz Album Id");
let mut data = 1u32.to_be_bytes().to_vec(); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(b"abc-123");
let mut inner = boxed(b"mean", &mean_body).unwrap();
inner.extend(boxed(b"name", &name_body).unwrap());
inner.extend(boxed(b"data", &data).unwrap());
let (key, value) = read_freeform(&inner).unwrap();
assert_eq!(key, "musicbrainz_albumid"); assert_eq!(value, "abc-123");
}
#[test]
fn read_freeform_unknown_name_passes_through_verbatim() {
let mut mean_body = 0u32.to_be_bytes().to_vec();
mean_body.extend_from_slice(b"com.apple.iTunes");
let mut name_body = 0u32.to_be_bytes().to_vec();
name_body.extend_from_slice(b"My Custom Field");
let mut data = 1u32.to_be_bytes().to_vec(); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(b"hello");
let mut inner = boxed(b"mean", &mean_body).unwrap();
inner.extend(boxed(b"name", &name_body).unwrap());
inner.extend(boxed(b"data", &data).unwrap());
let (key, value) = read_freeform(&inner).unwrap();
assert_eq!(key, "My Custom Field"); assert_eq!(value, "hello");
}
#[test]
fn read_freeform_skips_binary_typed_data() {
let mut name_body = 0u32.to_be_bytes().to_vec();
name_body.extend_from_slice(b"My Custom Field");
let mut data = 0u32.to_be_bytes().to_vec(); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(&[0xff, 0x00, 0x01]);
let mut inner = boxed(b"name", &name_body).unwrap();
inner.extend(boxed(b"data", &data).unwrap());
assert!(read_freeform(&inner).is_none()); }
#[test]
fn build_udta_round_trips_freeform_and_vocabulary() {
let tags = vec![
TagInput::new("title", "Song"),
TagInput::new("tracknumber", "3"),
TagInput::new("MyRating", "5"), TagInput::new("musicbrainz_albumid", "abc-123"), ];
let (segs, _streamed) = build_udta(&tags, &[], &[]).unwrap();
let udta = materialize_udta(&segs);
let moov = boxed(b"moov", &udta).unwrap();
let tags = read_tags(&moov);
for expected in [
("title", "Song"),
("tracknumber", "3"),
("MyRating", "5"),
("musicbrainz_albumid", "abc-123"),
] {
assert!(
tags.contains(&(expected.0.to_string(), expected.1.to_string())),
"missing {expected:?} in {tags:?}"
);
}
}
#[test]
fn read_box_rejects_overflowing_extended_size() {
let mut bytes = 1u32.to_be_bytes().to_vec(); bytes.extend_from_slice(b"moov");
bytes.extend_from_slice(&u64::MAX.to_be_bytes()); assert!(
read_structure(&bytes).is_err(),
"must return an error, not panic"
);
}
#[test]
fn read_structure_from_handles_largesize_mdat() {
fn largesize_mdat(payload: &[u8]) -> Vec<u8> {
let total = 16 + payload.len() as u64;
let mut v = 1u32.to_be_bytes().to_vec(); v.extend_from_slice(b"mdat");
v.extend_from_slice(&total.to_be_bytes()); v.extend_from_slice(payload);
v
}
let normal = mk_mp4(true, &[0xABu8; 64], &[0]); let scan = read_structure(&normal).unwrap();
let payload_start = usize_from(scan.mdat_payload_offset);
let mdat_box_start = payload_start - scan.mdat_header.len(); let payload = normal[payload_start..].to_vec();
let mut buf = normal[..mdat_box_start].to_vec(); buf.extend(largesize_mdat(&payload));
let from_buf = read_structure(&buf).unwrap();
let mut cur = std::io::Cursor::new(buf.clone());
let from_stream = read_structure_from(&mut cur, buf.len() as u64).unwrap();
assert_eq!(from_stream, from_buf);
assert_eq!(from_stream.mdat_header.len(), 16); assert_eq!(from_stream.mdat_payload_len, payload.len() as u64);
}
#[test]
fn box_header_accepts_empty_payload_box() {
let mut h = 8u32.to_be_bytes().to_vec();
h.extend_from_slice(b"free");
let bh = box_header(&h, 1000).unwrap();
assert_eq!(bh.header_len, 8);
assert_eq!(bh.total_len, 8);
}
#[test]
fn read_box_size0_extends_to_end_from_offset() {
let mut buf = bx(b"free", b""); buf.extend_from_slice(&0u32.to_be_bytes()); buf.extend_from_slice(b"mdat"); buf.extend_from_slice(b"AUDIOPAYLOAD"); assert_eq!(buf.len(), 28);
let b = read_box(&buf, 8).unwrap();
assert_eq!(&b.kind, b"mdat");
assert_eq!(b.total_len, buf.len() - 8); }
#[test]
fn read_structure_from_rejects_box_overrunning_eof() {
let mut buf = mk_mp4(true, b"AUDIO", &[0]); let scan = read_structure(&buf).unwrap();
let mdat_start = usize_from(scan.mdat_payload_offset - scan.mdat_header.len() as u64);
let real = u32::from_be_bytes(buf[mdat_start..mdat_start + 4].try_into().unwrap());
buf[mdat_start..mdat_start + 4].copy_from_slice(&(real + 100).to_be_bytes());
let mut cur = std::io::Cursor::new(buf.clone());
assert!(read_structure_from(&mut cur, buf.len() as u64).is_err());
}
#[test]
fn read_structure_from_rejects_moof() {
let mut buf = mk_mp4(true, b"AUDIO", &[0]);
buf.extend(bx(b"moof", b"\x00\x00\x00\x00"));
let mut cur = std::io::Cursor::new(buf.clone());
assert!(read_structure_from(&mut cur, buf.len() as u64).is_err());
}
#[test]
fn read_structure_from_rejects_duplicate_top_level_boxes() {
let dup = |extra: Vec<u8>| {
let mut buf = mk_mp4(true, b"AUDIO", &[0]);
buf.extend(extra);
let mut cur = std::io::Cursor::new(buf.clone());
read_structure_from(&mut cur, buf.len() as u64).is_err()
};
assert!(dup(bx(b"ftyp", b"M4A isom")), "duplicate ftyp must reject"); let extra_moov = {
let other = mk_mp4(true, b"AUDIO", &[0]);
let s = read_structure(&other).unwrap();
s.moov
};
assert!(dup(extra_moov), "duplicate moov must reject"); assert!(dup(bx(b"mdat", b"Y")), "duplicate mdat must reject"); }
#[test]
fn read_freeform_accepts_minimal_name_and_data() {
let name_body = 0u32.to_be_bytes().to_vec(); let mut data = 1u32.to_be_bytes().to_vec(); data.extend_from_slice(&0u32.to_be_bytes()); let mut inner = boxed(b"name", &name_body).unwrap();
inner.extend(boxed(b"data", &data).unwrap());
let (key, value) = read_freeform(&inner).unwrap();
assert_eq!(key, ""); assert_eq!(value, "");
}
#[test]
fn read_freeform_short_name_returns_none() {
let name_body = vec![0u8, 0, 0]; let mut data = 1u32.to_be_bytes().to_vec();
data.extend_from_slice(&0u32.to_be_bytes());
let mut inner = boxed(b"name", &name_body).unwrap();
inner.extend(boxed(b"data", &data).unwrap());
assert!(read_freeform(&inner).is_none());
}
#[test]
fn read_freeform_mean_payload_exactly_4_uses_empty_mean() {
let mean_body = vec![0u8, 0, 0, 0]; let mut name_body = 0u32.to_be_bytes().to_vec();
name_body.extend_from_slice(b"MusicBrainz Album Id");
let mut data = 1u32.to_be_bytes().to_vec();
data.extend_from_slice(&0u32.to_be_bytes());
data.extend_from_slice(b"abc-123");
let mut inner = boxed(b"mean", &mean_body).unwrap();
inner.extend(boxed(b"name", &name_body).unwrap());
inner.extend(boxed(b"data", &data).unwrap());
let (key, value) = read_freeform(&inner).unwrap();
assert_eq!(key, "MusicBrainz Album Id"); assert_eq!(value, "abc-123");
}
#[test]
fn read_tags_data_payload_exactly_8_is_read() {
let atoms = bx(b"\xa9nam", &data_atom(1, b"")); let buf = mp4_with_ilst(&atoms, true);
assert!(read_tags(&buf).contains(&("title".into(), String::new())));
}
#[test]
fn read_tags_disk_exact_4_byte_value_yields_discnumber() {
let atoms = bx(b"disk", &data_atom(0, &[0, 0, 0, 2])); let buf = mp4_with_ilst(&atoms, true);
assert!(read_tags(&buf).contains(&("discnumber".into(), "2".into())));
}
#[test]
fn read_tags_disk_short_value_is_skipped() {
let atoms = bx(b"disk", &data_atom(0, &[0, 0])); let buf = mp4_with_ilst(&atoms, true);
assert!(!read_tags(&buf).iter().any(|(k, _)| k == "discnumber"));
}
#[test]
fn read_tags_trkn_short_value_is_skipped() {
let atoms = bx(b"trkn", &data_atom(0, &[0, 0])); let buf = mp4_with_ilst(&atoms, true);
assert!(!read_tags(&buf).iter().any(|(k, _)| k == "tracknumber"));
}
#[test]
fn read_pictures_data_payload_exactly_8_is_read() {
let buf = mp4_with_ilst(&bx(b"covr", &data_atom(13, b"")), true);
let pics = read_pictures(&buf, usize::MAX);
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].mime, "image/jpeg");
assert!(pics[0].data.is_empty());
}
#[test]
fn read_pictures_recognizes_png() {
let png = [0x89, b'P', b'N', b'G', 1, 2, 3];
let buf = mp4_with_ilst(&bx(b"covr", &data_atom(14, &png)), false);
let pics = read_pictures(&buf, usize::MAX);
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].mime, "image/png");
assert_eq!(pics[0].data, png);
}
#[test]
fn read_pictures_reads_all_data_atoms_in_one_covr() {
let jpeg = [0xFF, 0xD8, 0xFF, 1];
let png = [0x89, b'P', b'N', b'G', 2];
let covr = bx(
b"covr",
&[
data_atom(13, &jpeg),
data_atom(99, b"skipped"), data_atom(14, &png),
]
.concat(),
);
let buf = mp4_with_ilst(&covr, true);
let pics = read_pictures(&buf, usize::MAX);
assert_eq!(pics.len(), 2);
assert_eq!(pics[0].mime, "image/jpeg");
assert_eq!(pics[0].data, jpeg);
assert_eq!(pics[1].mime, "image/png");
assert_eq!(pics[1].data, png);
}
#[test]
fn read_pictures_skips_art_over_budget() {
let over = vec![0xFFu8; 5];
let buf = mp4_with_ilst(&bx(b"covr", &data_atom(13, &over)), true);
assert!(read_pictures(&buf, 4).is_empty());
}
#[test]
fn read_pictures_accepts_art_exactly_at_budget() {
let exact = vec![0xFFu8; 4];
let buf = mp4_with_ilst(&bx(b"covr", &data_atom(13, &exact)), true);
let pics = read_pictures(&buf, 4);
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].data, exact);
}
#[test]
fn read_pictures_skips_non_data_children_of_covr() {
let png = [0x89, b'P', b'N', b'G'];
let covr = bx(
b"covr",
&[bx(b"free", b"pad"), data_atom(14, &png)].concat(),
);
let buf = mp4_with_ilst(&covr, false);
let pics = read_pictures(&buf, usize::MAX);
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].mime, "image/png");
assert_eq!(pics[0].data, png);
}
#[test]
fn build_udta_png_art_uses_type_code_14() {
for (mime, expected) in [("image/png", 14u32), ("image/jpeg", 13u32)] {
let art = ArtInput {
art_id: 1,
mime: mime.into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(10).unwrap(),
};
let (segs, _) = build_udta(&[TagInput::new("title", "T")], &[], &[art]).unwrap();
let prefix = materialize_udta(&segs);
let cpos = prefix.windows(4).position(|w| w == b"covr").expect("covr");
assert_eq!(&prefix[cpos + 8..cpos + 12], b"data");
let type_code = u32::from_be_bytes(prefix[cpos + 12..cpos + 16].try_into().unwrap());
assert_eq!(type_code, expected, "mime {mime}");
}
}
#[test]
fn build_udta_art_box_sizes_are_exact() {
let art = ArtInput {
art_id: 1,
mime: "image/jpeg".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(10).unwrap(),
};
let (segs, _) = build_udta(&[TagInput::new("title", "T")], &[], &[art]).unwrap();
let prefix = materialize_udta(&segs);
let cpos = prefix.windows(4).position(|w| w == b"covr").expect("covr");
let covr_size = u32::from_be_bytes(prefix[cpos - 4..cpos].try_into().unwrap());
let data_size = u32::from_be_bytes(prefix[cpos + 4..cpos + 8].try_into().unwrap());
assert_eq!(data_size, 8 + 8 + 10); assert_eq!(covr_size, 8 + data_size); }
#[test]
fn build_udta_multiple_arts_one_covr_n_data_atoms() {
let art = |id: i64, mime: &str, len: u64| ArtInput {
art_id: id,
mime: mime.into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(len).unwrap(),
};
let arts = [art(1, "image/jpeg", 10), art(2, "image/png", 20)];
let (segs, streamed) = build_udta(&[TagInput::new("title", "T")], &[], &arts).unwrap();
assert_eq!(streamed, 30);
let prefix = materialize_udta(&segs);
let covr_positions: Vec<usize> = prefix
.windows(4)
.enumerate()
.filter_map(|(i, w)| (w == b"covr").then_some(i))
.collect();
assert_eq!(covr_positions.len(), 1);
let cpos = covr_positions[0];
let covr_size = u32::from_be_bytes(prefix[cpos - 4..cpos].try_into().unwrap());
assert_eq!(covr_size, 8 + (16 + 10) + (16 + 20));
let d1 = cpos + 4;
assert_eq!(&prefix[d1 + 4..d1 + 8], b"data");
assert_eq!(
u32::from_be_bytes(prefix[d1..d1 + 4].try_into().unwrap()),
26
);
assert_eq!(
u32::from_be_bytes(prefix[d1 + 8..d1 + 12].try_into().unwrap()),
13
);
let d2 = d1 + 26;
assert_eq!(&prefix[d2 + 4..d2 + 8], b"data");
assert_eq!(
u32::from_be_bytes(prefix[d2..d2 + 4].try_into().unwrap()),
36
);
assert_eq!(
u32::from_be_bytes(prefix[d2 + 8..d2 + 12].try_into().unwrap()),
14
);
let art_segs: Vec<(i64, u64)> = segs
.iter()
.filter_map(|s| match s {
Segment::ArtImage { art_id, len } => Some((*art_id, len.get())),
_ => None,
})
.collect();
assert_eq!(art_segs, vec![(1, 10), (2, 20)]);
}
#[test]
fn build_udta_two_arts_round_trips_through_read_pictures() {
let art = |id: i64, mime: &str, len: u64| ArtInput {
art_id: id,
mime: mime.into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(len).unwrap(),
};
let arts = [art(1, "image/jpeg", 5), art(2, "image/png", 9)];
let (segs, _) = build_udta(&[TagInput::new("title", "Song")], &[], &arts).unwrap();
let prefix = materialize_udta(&segs);
let buf = [
bx(b"ftyp", b"M4A "),
bx(b"moov", &prefix),
bx(b"mdat", b"A"),
]
.concat();
let pics = read_pictures(&buf, usize::MAX);
assert_eq!(pics.len(), 2);
assert_eq!(pics[0].mime, "image/jpeg");
assert_eq!(pics[0].data.len(), 5);
assert_eq!(pics[1].mime, "image/png");
assert_eq!(pics[1].data.len(), 9);
}
#[test]
fn build_udta_udta_size_exactly_u32_max_is_ok() {
fn art(data_len: u64) -> ArtInput {
ArtInput {
art_id: 1,
mime: "image/jpeg".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(data_len).unwrap(),
}
}
let (segs0, _) = build_udta(&[TagInput::new("title", "T")], &[], &[art(1)]).unwrap();
let Segment::Inline(h0) = &segs0[0] else {
panic!("inline head")
};
let overhead = u64::from(u32::from_be_bytes(h0[0..4].try_into().unwrap())) - 1;
let max_len = u64::from(u32::MAX) - overhead;
let (segs_max, streamed) =
build_udta(&[TagInput::new("title", "T")], &[], &[art(max_len)]).unwrap();
assert_eq!(streamed, max_len);
let Segment::Inline(h_max) = &segs_max[0] else {
panic!("inline head")
};
assert_eq!(
u32::from_be_bytes(h_max[0..4].try_into().unwrap()),
u32::MAX
);
assert!(matches!(
build_udta(&[TagInput::new("title", "T")], &[], &[art(max_len + 1)]),
Err(FormatError::TooLarge)
));
}
#[test]
fn patch_chunk_offsets_stco_overflow_and_underflow_boundaries() {
let mut k = soun_trak();
assert!(patch_chunk_offsets(&mut k, 0).is_ok());
let mut k = soun_trak();
assert!(patch_chunk_offsets(&mut k, i64::from(u32::MAX)).is_ok());
let mut k = soun_trak();
assert!(matches!(
patch_chunk_offsets(&mut k, i64::from(u32::MAX) + 1), Err(FormatError::TooLarge)
));
let mut k = soun_trak();
assert!(matches!(
patch_chunk_offsets(&mut k, -1), Err(FormatError::TooLarge)
));
}
#[test]
fn patch_chunk_offsets_rejects_count_past_table() {
let mut stco = vec![0u8; 4]; stco.extend_from_slice(&2u32.to_be_bytes()); stco.extend_from_slice(&0u32.to_be_bytes()); let stbl = bx(
b"stbl",
&[bx(b"stco", &stco), bx(b"free", &[0u8; 8])].concat(),
);
let mut kept = bx(b"trak", &bx(b"mdia", &bx(b"minf", &stbl)));
assert!(matches!(
patch_chunk_offsets(&mut kept, 0),
Err(FormatError::Malformed)
));
}
#[test]
fn patch_chunk_offsets_co64_zero_offset_is_ok() {
let mut co64 = vec![0u8; 4]; co64.extend_from_slice(&1u32.to_be_bytes()); co64.extend_from_slice(&0u64.to_be_bytes()); let stbl = bx(b"stbl", &bx(b"co64", &co64));
let mut kept = bx(b"trak", &bx(b"mdia", &bx(b"minf", &stbl)));
assert!(patch_chunk_offsets(&mut kept, 0).is_ok());
}
fn freeform_atom_typed(mean: &str, name: &str, type_code: u32, value: &[u8]) -> Vec<u8> {
let mut mean_body = 0u32.to_be_bytes().to_vec();
mean_body.extend_from_slice(mean.as_bytes());
let mut name_body = 0u32.to_be_bytes().to_vec();
name_body.extend_from_slice(name.as_bytes());
let mut data_body = type_code.to_be_bytes().to_vec();
data_body.extend_from_slice(&0u32.to_be_bytes()); data_body.extend_from_slice(value);
let mut inner = boxed(b"mean", &mean_body).unwrap();
inner.extend(boxed(b"name", &name_body).unwrap());
inner.extend(boxed(b"data", &data_body).unwrap());
boxed(b"----", &inner).unwrap()
}
fn moov_with_ilst(ilst_body: &[u8]) -> Vec<u8> {
let ilst = boxed(b"ilst", ilst_body).unwrap();
let mut meta = 0u32.to_be_bytes().to_vec(); meta.extend(boxed(b"hdlr", &[0u8; 25]).unwrap());
meta.extend_from_slice(&ilst);
let udta = boxed(b"udta", &boxed(b"meta", &meta).unwrap()).unwrap();
boxed(b"moov", &udta).unwrap()
}
#[test]
fn read_binary_tags_extracts_opaque_freeform_skips_text() {
let serato = vec![0x00, 0xff, 0x10, 0x42, 0x99];
let binary = freeform_atom_typed("com.serato.dj", "analysis", 0, &serato);
let text = freeform_atom_typed("com.apple.iTunes", "MOOD", 1, b"calm");
let moov = moov_with_ilst(&[binary, text].concat());
let tags = read_binary_tags(&moov, usize::MAX);
assert_eq!(tags.len(), 1, "only the binary `----` is opaque");
assert_eq!(tags[0].key, "----:com.serato.dj:analysis");
assert_eq!(tags[0].payload, serato);
assert!(
read_binary_tags(&moov, usize::MAX)
.iter()
.all(|t| t.key != "----:com.apple.iTunes:MOOD")
);
}
#[test]
fn read_binary_tags_handles_data_box_length_boundary() {
let mut short_inner = boxed(b"mean", &{
let mut b = 0u32.to_be_bytes().to_vec();
b.extend_from_slice(b"com.serato.dj");
b
})
.unwrap();
short_inner.extend(
boxed(b"name", &{
let mut b = 0u32.to_be_bytes().to_vec();
b.extend_from_slice(b"short");
b
})
.unwrap(),
);
short_inner.extend(boxed(b"data", &[0u8; 5]).unwrap()); let short = boxed(b"----", &short_inner).unwrap();
let empty = freeform_atom_typed("com.serato.dj", "empty", 0, b"");
let moov = moov_with_ilst(&[short, empty].concat());
let tags = read_binary_tags(&moov, usize::MAX);
assert_eq!(tags.len(), 1, "short data skipped, 8-byte data emitted");
assert_eq!(tags[0].key, "----:com.serato.dj:empty");
assert!(tags[0].payload.is_empty());
}
#[test]
fn read_binary_tags_skips_payload_over_budget() {
let over = vec![0xABu8; 5];
let atom = freeform_atom_typed("com.serato.dj", "analysis", 0, &over);
let moov = moov_with_ilst(&atom);
assert!(read_binary_tags(&moov, 4).is_empty());
}
#[test]
fn read_binary_tags_accepts_payload_exactly_at_budget() {
let exact = vec![0xABu8; 4];
let atom = freeform_atom_typed("com.serato.dj", "analysis", 0, &exact);
let moov = moov_with_ilst(&atom);
let tags = read_binary_tags(&moov, 4);
assert_eq!(tags.len(), 1);
assert_eq!(tags[0].payload, exact);
}
#[test]
fn synthesize_interleaves_binary_freeform_segment() {
let buf = mk_mp4(true, b"AUDIODATA", &[42, 100]);
let scan = read_structure(&buf).unwrap();
let payload = vec![0xde, 0xad, 0xbe, 0xef, 0x00, 0x01];
let bins = vec![BinaryTagInput {
key: "----:com.serato.dj:analysis".into(),
payload_id: 7,
len: BlobLen::new(payload.len() as u64).unwrap(),
}];
let layout = synthesize_layout(&scan, &[TagInput::new("title", "T")], &bins, &[]).unwrap();
let bt: Vec<_> = layout
.segments()
.iter()
.filter_map(|s| match s {
Segment::BinaryTag { payload_id, len } => Some((*payload_id, len.get())),
_ => None,
})
.collect();
assert_eq!(bt, vec![(7, payload.len() as u64)]);
match layout.segments().last().unwrap() {
Segment::BackingAudio { offset, len } => {
assert_eq!(*offset, scan.mdat_payload_offset);
assert_eq!(*len, scan.mdat_payload_len);
}
_ => panic!("expected BackingAudio tail"),
}
let mut served = Vec::new();
for seg in layout.segments() {
match seg {
Segment::Inline(b) => served.extend_from_slice(b),
Segment::BinaryTag { .. } => served.extend_from_slice(&payload),
Segment::BackingAudio { offset, len } => {
let s = usize_from(*offset);
served.extend_from_slice(&buf[s..s + usize_from(*len)]);
}
other => panic!("unexpected segment: {other:?}"),
}
}
read_structure(&served).expect("synthesized file re-parses to a valid moov/mdat");
let reparsed = read_binary_tags(&served, usize::MAX);
assert_eq!(reparsed.len(), 1);
assert_eq!(reparsed[0].key, "----:com.serato.dj:analysis");
assert_eq!(reparsed[0].payload, payload);
}
#[test]
fn synthesize_new_moov_size_exactly_u32_max_is_ok() {
fn art(data_len: u64) -> ArtInput {
ArtInput {
art_id: 1,
mime: "image/jpeg".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(data_len).unwrap(),
}
}
let buf = mk_mp4(true, b"AUDIO", &[0]);
let scan = read_structure(&buf).unwrap();
let tags = [TagInput::new("title", "T")];
let layout1 = synthesize_layout(&scan, &tags, &[], &[art(1)]).unwrap();
let head_len = inline_head(&layout1).len();
let overhead = (head_len as u64) - (scan.ftyp.len() as u64);
let max_len = u64::from(u32::MAX) - overhead;
assert!(max_len > 0, "overhead {overhead} must be < u32::MAX");
assert!(synthesize_layout(&scan, &tags, &[], &[art(max_len)]).is_ok());
assert!(matches!(
synthesize_layout(&scan, &tags, &[], &[art(max_len + 1)]),
Err(FormatError::TooLarge)
));
}
#[test]
fn synthesize_layout_emits_all_nonzero_arts() {
let art = |id: i64, len: u64| ArtInput {
art_id: id,
mime: "image/jpeg".into(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(len).unwrap(),
};
let buf = mk_mp4(true, b"AUDIO", &[0]);
let scan = read_structure(&buf).unwrap();
let layout = synthesize_layout(
&scan,
&[TagInput::new("title", "T")],
&[],
&[art(1, 5), art(3, 7)],
)
.unwrap();
let art_segs: Vec<(i64, u64)> = layout
.segments()
.iter()
.filter_map(|s| match s {
Segment::ArtImage { art_id, len } => Some((*art_id, len.get())),
_ => None,
})
.collect();
assert_eq!(art_segs, vec![(1, 5), (3, 7)]);
}
#[test]
fn read_structure_from_rejects_oversized_moov() {
use std::io::Cursor;
let moov_size: u32 = 600 * 1024 * 1024;
let mut buf = Vec::new();
buf.extend_from_slice(&16u32.to_be_bytes());
buf.extend_from_slice(b"ftyp");
buf.extend_from_slice(&[0u8; 8]);
buf.extend_from_slice(&16u32.to_be_bytes());
buf.extend_from_slice(b"mdat");
buf.extend_from_slice(&[0u8; 8]);
buf.extend_from_slice(&moov_size.to_be_bytes());
buf.extend_from_slice(b"moov");
assert_eq!(buf.len(), 40);
let file_len = 32 + u64::from(moov_size);
let mut cur = Cursor::new(buf);
match read_structure_from(&mut cur, file_len).unwrap_err() {
Mp4ScanError::MetadataTooLarge {
box_kind,
size,
cap,
} => {
assert_eq!(box_kind, "moov");
assert_eq!(size, u64::from(moov_size));
assert_eq!(cap, 256 * 1024 * 1024);
}
other => panic!("expected MetadataTooLarge, got {other:?}"),
}
}
#[test]
fn read_structure_from_admits_box_at_exactly_the_cap() {
use std::io::Cursor;
let cap: u32 = 256 * 1024 * 1024;
let mut buf = Vec::new();
buf.extend_from_slice(&16u32.to_be_bytes());
buf.extend_from_slice(b"ftyp");
buf.extend_from_slice(&[0u8; 8]);
buf.extend_from_slice(&16u32.to_be_bytes());
buf.extend_from_slice(b"mdat");
buf.extend_from_slice(&[0u8; 8]);
buf.extend_from_slice(&cap.to_be_bytes());
buf.extend_from_slice(b"moov");
let file_len = 32 + u64::from(cap);
let mut cur = Cursor::new(buf);
let err = read_structure_from(&mut cur, file_len).unwrap_err();
assert!(
matches!(err, Mp4ScanError::Io(_)),
"exact-cap box must pass the strict `>` guard (got {err:?})"
);
}
#[test]
fn build_udta_checked_art_len_rejects_overflow() {
let mk = |data_len: u64| crate::input::ArtInput {
art_id: 1,
mime: "image/png".to_string(),
description: String::new(),
picture_type: PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: BlobLen::new(data_len).unwrap(),
};
assert_eq!(
build_udta(&[], &[], &[mk(u64::MAX)]).err(),
Some(FormatError::TooLarge)
);
}
#[test]
fn build_udta_checked_binary_tag_len_rejects_overflow() {
let bins = vec![crate::input::BinaryTagInput {
key: "----:com.example:x".to_string(),
payload_id: 1,
len: BlobLen::new(u64::MAX).unwrap(),
}];
assert_eq!(
build_udta(&[], &bins, &[]).err(),
Some(FormatError::TooLarge)
);
}
#[test]
fn freeform_binary_prefix_checked_outer_box_size_rejects_overflow() {
assert_eq!(
freeform_binary_prefix("m", "n", u64::MAX - 42).err(),
Some(FormatError::TooLarge)
);
}
}