use crate::blocks::common::{read_link, BlockHeader, BLOCK_HEADER_SIZE, ID_BLOCK_SIZE};
use crate::error::{Mf4Error, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
const MF4_SIGNATURE: &[u8; 8] = b"MDF ";
const UNFINISHED_SIGNATURE: &[u8; 8] = b"UnFinMF ";
#[derive(Debug, Clone)]
pub struct IdBlock {
pub file_id: [u8; 8],
pub format_id: [u8; 8],
pub program_id: [u8; 8],
pub reserved1: [u8; 4],
pub version_number: u16,
pub reserved2: [u8; 30],
pub unfinalized_flags: u16,
pub custom_unfinalized_flags: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct UnfinalizedFlags {
pub update_cg_counters: bool,
pub update_sr_counters: bool,
pub update_last_dt_length: bool,
pub update_last_rd_length: bool,
pub update_last_dl: bool,
pub update_vlsd_bytes: bool,
pub update_vlsd_offsets: bool,
pub custom: u16,
}
impl UnfinalizedFlags {
fn from_parts(flags: u16, custom: u16) -> Self {
UnfinalizedFlags {
update_cg_counters: (flags & 0x01) != 0,
update_sr_counters: (flags & 0x02) != 0,
update_last_dt_length: (flags & 0x04) != 0,
update_last_rd_length: (flags & 0x08) != 0,
update_last_dl: (flags & 0x10) != 0,
update_vlsd_bytes: (flags & 0x20) != 0,
update_vlsd_offsets: (flags & 0x40) != 0,
custom,
}
}
}
impl IdBlock {
pub fn unfinalized(&self) -> Option<UnfinalizedFlags> {
if !self.is_unfinished() {
return None;
}
Some(UnfinalizedFlags::from_parts(
self.unfinalized_flags,
self.custom_unfinalized_flags,
))
}
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < ID_BLOCK_SIZE {
return Err(Mf4Error::truncated(0, ID_BLOCK_SIZE, data.len()));
}
let mut file_id = [0u8; 8];
file_id.copy_from_slice(&data[0..8]);
if &file_id != MF4_SIGNATURE && &file_id != UNFINISHED_SIGNATURE {
return Err(Mf4Error::InvalidSignature(
String::from_utf8_lossy(&file_id).to_string(),
));
}
let mut format_id = [0u8; 8];
format_id.copy_from_slice(&data[8..16]);
let mut program_id = [0u8; 8];
program_id.copy_from_slice(&data[16..24]);
let mut reserved1 = [0u8; 4];
reserved1.copy_from_slice(&data[24..28]);
let mut cursor = Cursor::new(&data[28..30]);
let version_number = cursor.read_u16::<LittleEndian>()?;
let mut reserved2 = [0u8; 30];
reserved2.copy_from_slice(&data[30..60]);
let mut cursor = Cursor::new(&data[60..62]);
let unfinalized_flags = cursor.read_u16::<LittleEndian>()?;
let mut cursor = Cursor::new(&data[62..64]);
let custom_unfinalized_flags = cursor.read_u16::<LittleEndian>()?;
Ok(IdBlock {
file_id,
format_id,
program_id,
reserved1,
version_number,
reserved2,
unfinalized_flags,
custom_unfinalized_flags,
})
}
pub fn version_major(&self) -> u16 {
self.version_number / 100
}
pub fn version_minor(&self) -> u16 {
self.version_number % 100
}
pub fn format_str(&self) -> &str {
std::str::from_utf8(&self.format_id)
.unwrap_or("????????")
.trim_end()
}
pub fn program_str(&self) -> &str {
std::str::from_utf8(&self.program_id)
.unwrap_or("????????")
.trim_end()
}
pub fn is_finalized(&self) -> bool {
&self.file_id != UNFINISHED_SIGNATURE && self.unfinalized_flags == 0
}
pub fn is_unfinished(&self) -> bool {
&self.file_id == UNFINISHED_SIGNATURE || self.unfinalized_flags != 0
}
pub fn version_raw(&self) -> u16 {
self.version_number
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HdFlags {
pub start_angle_valid: bool,
pub start_distance_valid: bool,
}
impl HdFlags {
fn from_u16(value: u16) -> Self {
HdFlags {
start_angle_valid: (value & 0x01) != 0,
start_distance_valid: (value & 0x02) != 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeClass {
LocalPcTime,
ExternalTime,
ExternalAbsolute,
}
impl TimeClass {
fn from_u8(value: u8) -> Self {
match value {
0 => TimeClass::LocalPcTime,
10 => TimeClass::ExternalTime,
16 => TimeClass::ExternalAbsolute,
_ => TimeClass::LocalPcTime,
}
}
}
#[derive(Debug, Clone)]
pub struct HdBlock {
pub header: BlockHeader,
pub dg_first: u64,
pub fh_first: u64,
pub ch_first: u64,
pub at_first: u64,
pub ev_first: u64,
pub md_comment: u64,
pub start_time_ns: i64,
pub tz_offset_min: i16,
pub dst_offset_min: i16,
pub time_class: TimeClass,
pub flags: HdFlags,
pub reserved: u8,
pub start_angle_rad: f64,
pub start_distance_m: f64,
}
impl HdBlock {
pub const MIN_SIZE: u64 = BLOCK_HEADER_SIZE as u64 + 6 * 8 + 32;
pub fn parse(data: &[u8], offset: u64) -> Result<Self> {
let header = BlockHeader::parse(data, offset)?;
header.validate_type(b"##HD", offset)?;
if header.length < Self::MIN_SIZE {
return Err(Mf4Error::invalid_block_size(
"HD",
header.length,
Self::MIN_SIZE,
));
}
let links_start = BLOCK_HEADER_SIZE;
let dg_first = read_link(data, links_start)?;
let fh_first = read_link(data, links_start + 8)?;
let ch_first = read_link(data, links_start + 16)?;
let at_first = read_link(data, links_start + 24)?;
let ev_first = read_link(data, links_start + 32)?;
let md_comment = read_link(data, links_start + 40)?;
let data_start = header.data_offset();
let data_section = data
.get(data_start..)
.ok_or_else(|| Mf4Error::truncated(offset, data_start, data.len()))?;
let mut cursor = Cursor::new(data_section);
let start_time_ns = cursor.read_i64::<LittleEndian>()?;
let tz_offset_min = cursor.read_i16::<LittleEndian>()?;
let dst_offset_min = cursor.read_i16::<LittleEndian>()?;
let time_flags = cursor.read_u8()?;
let time_class = TimeClass::from_u8(time_flags);
let flags_value = cursor.read_u8()?;
let flags = HdFlags::from_u16(flags_value as u16);
let reserved = cursor.read_u8()?;
let _reserved2 = cursor.read_u8()?;
let start_angle_rad = cursor.read_f64::<LittleEndian>()?;
let start_distance_m = cursor.read_f64::<LittleEndian>()?;
Ok(HdBlock {
header,
dg_first,
fh_first,
ch_first,
at_first,
ev_first,
md_comment,
start_time_ns,
tz_offset_min,
dst_offset_min,
time_class,
flags,
reserved,
start_angle_rad,
start_distance_m,
})
}
pub fn start_time_unix(&self) -> f64 {
self.start_time_ns as f64 / 1_000_000_000.0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_id_block() -> Vec<u8> {
let mut data = vec![0u8; 64];
data[0..8].copy_from_slice(b"MDF ");
data[8..16].copy_from_slice(b"4.20 ");
data[16..24].copy_from_slice(b"TestProg");
data[28..30].copy_from_slice(&420u16.to_le_bytes()); data
}
#[test]
fn test_id_block_parse() {
let data = create_test_id_block();
let id = IdBlock::parse(&data).unwrap();
assert_eq!(id.version_major(), 4);
assert_eq!(id.version_minor(), 20);
assert_eq!(id.format_str(), "4.20");
assert!(id.is_finalized());
}
#[test]
fn test_id_block_invalid_signature() {
let mut data = create_test_id_block();
data[0..8].copy_from_slice(b"INVALID ");
let result = IdBlock::parse(&data);
assert!(matches!(result, Err(Mf4Error::InvalidSignature(_))));
}
#[test]
fn test_id_block_unfinished() {
let mut data = create_test_id_block();
data[0..8].copy_from_slice(b"UnFinMF ");
data[60..62].copy_from_slice(&1u16.to_le_bytes());
let id = IdBlock::parse(&data).unwrap();
assert!(!id.is_finalized());
}
fn create_test_hd_block() -> Vec<u8> {
let mut data = vec![0u8; 104];
data[0..4].copy_from_slice(b"##HD");
data[8..16].copy_from_slice(&104u64.to_le_bytes()); data[16..24].copy_from_slice(&6u64.to_le_bytes());
data[24..32].copy_from_slice(&200u64.to_le_bytes()); data[32..40].copy_from_slice(&300u64.to_le_bytes()); data[40..48].copy_from_slice(&0u64.to_le_bytes()); data[48..56].copy_from_slice(&0u64.to_le_bytes()); data[56..64].copy_from_slice(&0u64.to_le_bytes()); data[64..72].copy_from_slice(&400u64.to_le_bytes());
let start_time_ns: i64 = 1_640_000_000_000_000_000; data[72..80].copy_from_slice(&start_time_ns.to_le_bytes());
data
}
#[test]
fn test_hd_block_parse() {
let data = create_test_hd_block();
let hd = HdBlock::parse(&data, 64).unwrap();
assert_eq!(hd.header.block_type, *b"##HD");
assert_eq!(hd.dg_first, 200);
assert_eq!(hd.fh_first, 300);
assert_eq!(hd.md_comment, 400);
}
}