use crate::config::ParsingMode;
use crate::error::{LoftyError, Result};
use crate::io::{FileLike, Length, Truncate};
use crate::macros::err;
use crate::mp4::atom_info::{AtomIdent, AtomInfo, IDENTIFIER_LEN};
use crate::mp4::read::{meta_is_full, skip_atom};
use std::cell::{RefCell, RefMut};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::ops::RangeBounds;
use byteorder::{BigEndian, WriteBytesExt};
#[derive(Debug)]
pub(super) struct ContextualAtom {
pub(crate) info: AtomInfo,
pub(crate) children: Vec<ContextualAtom>,
}
const META_ATOM_IDENT: AtomIdent<'_> = AtomIdent::Fourcc(*b"meta");
#[rustfmt::skip]
const IMPORTANT_CONTAINERS: &[[u8; 4]] = &[
*b"moov",
*b"udta",
*b"moof",
*b"trak",
*b"mdia",
*b"minf",
*b"stbl",
];
impl ContextualAtom {
pub(super) fn read<R>(
reader: &mut R,
reader_len: &mut u64,
parse_mode: ParsingMode,
) -> Result<Option<ContextualAtom>>
where
R: Read + Seek,
{
if *reader_len == 0 {
return Ok(None);
}
let Some(info) = AtomInfo::read(reader, *reader_len, parse_mode)? else {
return Ok(None);
};
match info.ident {
AtomIdent::Fourcc(ident) if IMPORTANT_CONTAINERS.contains(&ident) => {},
_ => {
*reader_len = reader_len.saturating_sub(info.len);
skip_atom(reader, info.extended, info.len)?;
return Ok(Some(ContextualAtom {
info,
children: Vec::new(),
}));
},
}
let mut len = info.len - info.header_size();
let mut children = Vec::new();
if info.ident == META_ATOM_IDENT && meta_is_full(reader)? {
len -= 4;
}
while let Some(child) = Self::read(reader, &mut len, parse_mode)? {
children.push(child);
}
if len != 0 {
err!(BadAtom("Unable to read entire container"));
}
*reader_len = reader_len.saturating_sub(info.len);
Ok(Some(ContextualAtom { info, children }))
}
pub(super) fn find_all_children(
&self,
expected: [u8; 4],
recurse: bool,
) -> AtomFindAll<std::slice::Iter<'_, ContextualAtom>> {
AtomFindAll {
atoms: self.children.iter(),
expected_fourcc: expected,
recurse,
current_container: None,
}
}
}
pub(super) struct AtomWriter {
contents: RefCell<Cursor<Vec<u8>>>,
atoms: Vec<ContextualAtom>,
}
impl AtomWriter {
pub(super) fn new(content: Vec<u8>, _parse_mode: ParsingMode) -> Self {
Self {
contents: RefCell::new(Cursor::new(content)),
atoms: Vec::new(),
}
}
pub(super) fn new_from_file<F>(file: &mut F, parse_mode: ParsingMode) -> Result<Self>
where
F: FileLike,
LoftyError: From<<F as Truncate>::Error>,
LoftyError: From<<F as Length>::Error>,
{
let mut contents = Cursor::new(Vec::new());
file.read_to_end(contents.get_mut())?;
let mut len = contents.get_ref().len() as u64;
let mut atoms = Vec::new();
while let Some(atom) = ContextualAtom::read(&mut contents, &mut len, parse_mode)? {
atoms.push(atom);
}
contents.rewind()?;
Ok(Self {
contents: RefCell::new(contents),
atoms,
})
}
pub(super) fn find_contextual_atom(&self, fourcc: [u8; 4]) -> Option<&ContextualAtom> {
self.atoms
.iter()
.find(|atom| matches!(atom.info.ident, AtomIdent::Fourcc(ident) if ident == fourcc))
}
pub(super) fn into_contents(self) -> Vec<u8> {
self.contents.into_inner().into_inner()
}
pub(super) fn start_write(&self) -> AtomWriterCompanion<'_> {
AtomWriterCompanion {
contents: self.contents.borrow_mut(),
}
}
pub(super) fn save_to<F>(&mut self, file: &mut F) -> Result<()>
where
F: FileLike,
LoftyError: From<<F as Truncate>::Error>,
LoftyError: From<<F as Length>::Error>,
{
file.rewind()?;
file.truncate(0)?;
file.write_all(self.contents.borrow().get_ref())?;
Ok(())
}
}
pub(super) struct AtomWriterCompanion<'a> {
contents: RefMut<'a, Cursor<Vec<u8>>>,
}
impl AtomWriterCompanion<'_> {
pub(super) fn insert(&mut self, index: usize, byte: u8) {
self.contents.get_mut().insert(index, byte);
}
pub(super) fn splice<R, I>(&mut self, range: R, replacement: I)
where
R: RangeBounds<usize>,
I: IntoIterator<Item = u8>,
{
self.contents.get_mut().splice(range, replacement);
}
pub(super) fn write_atom_size(&mut self, start: u64, size: u64, extended: bool) -> Result<()> {
if u32::try_from(size).is_ok() {
self.write_u32::<BigEndian>(size as u32)?;
self.seek(SeekFrom::Current(IDENTIFIER_LEN as i64))?;
return Ok(());
}
self.write_u32::<BigEndian>(1)?;
self.seek(SeekFrom::Current(IDENTIFIER_LEN as i64))?;
let extended_size = size.to_be_bytes();
if extended {
self.write_u64::<BigEndian>(size)?;
} else {
for i in extended_size {
self.insert((start + 8 + u64::from(i)) as usize, i);
}
self.seek(SeekFrom::Current(8))?;
}
Ok(())
}
pub(super) fn len(&self) -> usize {
self.contents.get_ref().len()
}
}
impl Seek for AtomWriterCompanion<'_> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.contents.seek(pos)
}
}
impl Read for AtomWriterCompanion<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.contents.read(buf)
}
}
impl Write for AtomWriterCompanion<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.contents.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.contents.flush()
}
}
pub struct AtomFindAll<I> {
atoms: I,
expected_fourcc: [u8; 4],
recurse: bool,
current_container: Option<Box<AtomFindAll<I>>>,
}
impl<'a> Iterator for AtomFindAll<std::slice::Iter<'a, ContextualAtom>> {
type Item = &'a AtomInfo;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ref mut container) = self.current_container {
match container.next() {
Some(next) => {
return Some(next);
},
None => {
self.current_container = None;
},
}
}
loop {
let atom = self.atoms.next()?;
let AtomIdent::Fourcc(fourcc) = atom.info.ident else {
continue;
};
if fourcc == self.expected_fourcc {
return Some(&atom.info);
}
if self.recurse {
if atom.children.is_empty() {
continue;
}
self.current_container = Some(Box::new(
atom.find_all_children(self.expected_fourcc, self.recurse),
));
return self.next();
}
}
}
}