mod atom_reader;
use super::Mp4File;
use super::atom_info::{ATOM_HEADER_LEN, AtomIdent, AtomInfo, EXTENDED_ATOM_HEADER_LEN};
use super::moov::Moov;
use super::properties::Mp4Properties;
use crate::config::{ParseOptions, ParsingMode};
use crate::error::{NotEnoughDataError, TooMuchDataError, UnknownFormatError};
use crate::mp4::error::{AtomParseError, Mp4ParseError};
use crate::util::io::SeekStreamLen;
use crate::util::text::utf8_decode_str;
use std::io::{Read, Seek, SeekFrom};
use byteorder::{BigEndian, ReadBytesExt};
pub(super) use atom_reader::AtomReader;
pub(in crate::mp4) fn verify_mp4<R>(reader: &mut AtomReader<R>) -> Result<String, Mp4ParseError>
where
R: Read + Seek,
{
let Some(atom) = reader.next()? else {
return Err(UnknownFormatError.into());
};
if atom.ident != AtomIdent::Fourcc(*b"ftyp") {
return Err(UnknownFormatError.into());
}
if atom.len < 12 {
return Err(NotEnoughDataError::new(Some(12)).into());
}
let mut major_brand = [0u8; 4];
reader.read_exact(&mut major_brand)?;
reader.seek(SeekFrom::Current((atom.len - 12) as i64))?;
let major_brand = utf8_decode_str(&major_brand)
.map(ToOwned::to_owned)
.map_err(|_| Mp4ParseError::message("malformed ftyp major brand"))?;
log::debug!("Verified to be an MP4 file. Major brand: {}", major_brand);
Ok(major_brand)
}
pub(crate) fn read_from<R>(
data: &mut R,
parse_options: ParseOptions,
) -> Result<Mp4File, Mp4ParseError>
where
R: Read + Seek,
{
let mut reader = AtomReader::new(data, parse_options.parsing_mode)?;
let file_length = reader.stream_len_hack()?;
let ftyp = verify_mp4(&mut reader)?;
let moov_info = Moov::find(&mut reader)?;
reader.reset_bounds(moov_info.data_start(), moov_info.data_len());
let moov = Moov::parse(&mut reader, parse_options)?;
let mut properties = if parse_options.read_properties {
reader.reset_bounds(0, file_length);
super::properties::read_properties(
&mut reader,
&moov.traks,
file_length,
parse_options.parsing_mode,
)?
} else {
Mp4Properties::default()
};
properties.ftyp = ftyp;
Ok(Mp4File {
ilst_tag: moov.ilst,
properties,
})
}
pub(super) fn skip_atom<R>(reader: &mut R, extended: bool, len: u64) -> Result<(), AtomParseError>
where
R: Read + Seek,
{
if !extended {
log::trace!("Attempting to skip {} bytes", len - ATOM_HEADER_LEN);
reader.seek(SeekFrom::Current(
i64::from(len as u32) - ATOM_HEADER_LEN as i64,
))?;
return Ok(());
}
log::trace!(
"Attempting to skip {} bytes",
len - EXTENDED_ATOM_HEADER_LEN
);
let pos = reader.stream_position()?;
let Some(pos) = pos.checked_add(len - EXTENDED_ATOM_HEADER_LEN) else {
return Err(TooMuchDataError.into());
};
reader.seek(SeekFrom::Start(pos))?;
Ok(())
}
pub(super) fn find_child_atom<R>(
reader: &mut R,
mut len: u64,
expected: [u8; 4],
parse_mode: ParsingMode,
) -> Result<Option<AtomInfo>, AtomParseError>
where
R: Read + Seek,
{
let mut ret = None;
while len >= ATOM_HEADER_LEN {
let Some(atom) = AtomInfo::read(reader, len, parse_mode)? else {
break;
};
match atom.ident {
AtomIdent::Fourcc(fourcc) if fourcc == expected => {
ret = Some(atom);
break;
},
_ => {
skip_atom(reader, atom.extended, atom.len)?;
len = len.saturating_sub(atom.len);
},
}
}
Ok(ret)
}
pub(super) fn atom_tree<R>(
reader: &mut R,
mut len: u64,
up_to: &[u8],
parse_mode: ParsingMode,
) -> Result<(Option<usize>, Vec<AtomInfo>), Mp4ParseError>
where
R: Read + Seek,
{
let mut found_idx = None;
let mut buf = Vec::new();
let mut i = 0;
while len >= ATOM_HEADER_LEN {
let Some(atom) = AtomInfo::read(reader, len, parse_mode)? else {
break;
};
skip_atom(reader, atom.extended, atom.len)?;
len = len.saturating_sub(atom.len);
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
if fourcc == up_to && found_idx.is_none() {
found_idx = Some(i);
}
buf.push(atom);
i += 1;
}
}
Ok((found_idx, buf))
}
pub(super) fn meta_is_full<R>(reader: &mut R) -> Result<bool, AtomParseError>
where
R: Read + Seek,
{
let _version_flags = reader.read_u32::<BigEndian>()?;
let mut possible_ident = [0; 4];
reader.read_exact(&mut possible_ident)?;
match &possible_ident {
b"hdlr" | b"ilst" | b"mhdr" | b"ctry" | b"lang" => {
log::warn!("File contains a non-full 'meta' atom");
reader.seek(SeekFrom::Current(-8))?;
Ok(false)
},
_ => {
reader.seek(SeekFrom::Current(-4))?;
Ok(true)
},
}
}