use crate::core::error::XmpResult;
use std::io::{Read, Seek, SeekFrom};
#[cfg(feature = "mpeg4")]
pub mod mpeg4;
#[cfg(feature = "mpegh")]
pub mod mpegh;
#[cfg(feature = "mpeg4")]
pub use mpeg4::Mpeg4Handler;
#[cfg(feature = "mpegh")]
pub use mpegh::MpeghHandler;
pub const FTYP_BOX: &[u8; 4] = b"ftyp";
pub const UUID_BOX: &[u8; 4] = b"uuid";
pub const XMP_UUID: &[u8] = &[
0xBE, 0x7A, 0xCF, 0xCB, 0x97, 0xA9, 0x42, 0xE8, 0x9C, 0x71, 0x99, 0x94, 0x91, 0xE3, 0xAF, 0xAC,
];
#[derive(Debug, Clone)]
pub struct BmffBox {
pub size: u64,
pub box_type: [u8; 4],
pub data_offset: u64,
pub header_offset: u64,
}
impl BmffBox {
pub fn header_size(&self) -> u64 {
self.data_offset - self.header_offset
}
pub fn data_size(&self) -> u64 {
self.size - self.header_size()
}
}
pub fn is_bmff<R: Read + Seek>(reader: &mut R) -> XmpResult<bool> {
let pos = reader.stream_position()?;
let file_len = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(pos))?;
if file_len < 8 {
return Ok(false);
}
let mut header = [0u8; 8];
if reader.read_exact(&mut header).is_err() {
reader.seek(SeekFrom::Start(pos))?;
return Ok(false);
}
reader.seek(SeekFrom::Start(pos))?;
let box_size = u32::from_be_bytes([header[0], header[1], header[2], header[3]]);
let box_type = &header[4..8];
if box_size != 0 && box_size != 1 && box_size < 8 {
return Ok(false);
}
if box_type == FTYP_BOX {
return Ok(true);
}
let qt_boxes: &[&[u8; 4]] = &[b"moov", b"mdat", b"wide", b"free", b"skip", b"pnot"];
for qt_box in qt_boxes {
if box_type == *qt_box {
return Ok(true);
}
}
Ok(false)
}
pub fn read_box<R: Read + Seek>(reader: &mut R) -> std::io::Result<BmffBox> {
let header_offset = reader.stream_position()?;
let mut size_bytes = [0u8; 4];
reader.read_exact(&mut size_bytes)?;
let size = u32::from_be_bytes(size_bytes) as u64;
let mut box_type = [0u8; 4];
reader.read_exact(&mut box_type)?;
let (actual_size, data_offset) = if size == 1 {
let mut ext_size_bytes = [0u8; 8];
reader.read_exact(&mut ext_size_bytes)?;
(u64::from_be_bytes(ext_size_bytes), header_offset + 16)
} else if size == 0 {
let current_pos = reader.stream_position()?;
let file_end = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(current_pos))?;
(file_end.saturating_sub(header_offset), header_offset + 8)
} else {
(size, header_offset + 8)
};
let header_size = data_offset - header_offset;
if actual_size < header_size {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid BMFF box size {} for {:?}: smaller than header size {}",
actual_size,
std::str::from_utf8(&box_type).unwrap_or("????"),
header_size
),
));
}
Ok(BmffBox {
size: actual_size,
box_type,
data_offset,
header_offset,
})
}
pub fn skip_box<R: Read + Seek>(reader: &mut R, box_info: &BmffBox) -> std::io::Result<()> {
reader.seek(SeekFrom::Start(box_info.header_offset + box_info.size))?;
Ok(())
}
pub fn read_box_data<R: Read + Seek>(
reader: &mut R,
box_info: &BmffBox,
) -> std::io::Result<Vec<u8>> {
reader.seek(SeekFrom::Start(box_info.data_offset))?;
let mut data = vec![0u8; box_info.data_size() as usize];
reader.read_exact(&mut data)?;
Ok(data)
}
pub fn copy_bytes<R: Read, W: std::io::Write>(
reader: &mut R,
writer: &mut W,
count: u64,
) -> std::io::Result<()> {
let mut buffer = [0u8; 8192];
let mut remaining = count;
while remaining > 0 {
let to_read = (remaining as usize).min(buffer.len());
let n = reader.read(&mut buffer[..to_read])?;
if n == 0 {
break;
}
writer.write_all(&buffer[..n])?;
remaining -= n as u64;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn create_minimal_bmff() -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(&20u32.to_be_bytes()); data.extend_from_slice(FTYP_BOX); data.extend_from_slice(b"isom"); data.extend_from_slice(&0u32.to_be_bytes()); data.extend_from_slice(b"isom"); data
}
#[test]
fn test_is_bmff() {
let data = create_minimal_bmff();
let mut reader = Cursor::new(data);
assert!(is_bmff(&mut reader).unwrap());
}
#[test]
fn test_is_bmff_invalid() {
let data = vec![0x00, 0x01, 0x02, 0x03];
let mut reader = Cursor::new(data);
assert!(!is_bmff(&mut reader).unwrap());
}
#[test]
fn test_read_box() {
let data = create_minimal_bmff();
let mut reader = Cursor::new(data);
let box_info = read_box(&mut reader).unwrap();
assert_eq!(box_info.size, 20);
assert_eq!(&box_info.box_type, FTYP_BOX);
assert_eq!(box_info.header_offset, 0);
assert_eq!(box_info.data_offset, 8);
}
#[test]
fn test_read_box_size_zero_extends_to_eof() {
let mut data = Vec::new();
data.extend_from_slice(&0u32.to_be_bytes());
data.extend_from_slice(b"free");
data.extend_from_slice(&[1, 2, 3, 4]);
let mut reader = Cursor::new(data);
let box_info = read_box(&mut reader).unwrap();
assert_eq!(box_info.size, 12);
assert_eq!(&box_info.box_type, b"free");
assert_eq!(box_info.header_offset, 0);
assert_eq!(box_info.data_offset, 8);
}
#[test]
fn test_read_box_rejects_size_smaller_than_header() {
let mut data = Vec::new();
data.extend_from_slice(&4u32.to_be_bytes());
data.extend_from_slice(b"free");
let mut reader = Cursor::new(data);
let err = read_box(&mut reader).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
}
#[test]
fn test_read_box_rejects_extended_size_smaller_than_header() {
let mut data = Vec::new();
data.extend_from_slice(&1u32.to_be_bytes());
data.extend_from_slice(b"free");
data.extend_from_slice(&8u64.to_be_bytes());
let mut reader = Cursor::new(data);
let err = read_box(&mut reader).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
}
}