use crate::config::MediaConfig;
use crate::element::ElementTree;
use crate::stream::{StreamCollection, StreamKind};
use revelo_util::Ztring;
pub struct FileAnalyze<'a> {
buffer: &'a [u8],
element_offset: usize,
truncated: bool,
tree: ElementTree,
streams: StreamCollection,
bs_active: bool,
bs_bits_consumed: usize,
pub trace_activated: bool,
pub config: MediaConfig,
pub multi_file_data: Option<Vec<u8>>,
pub reference_count: usize,
pub duplicate_indices: Vec<(StreamKind, usize)>,
}
impl<'a> FileAnalyze<'a> {
pub fn new(buffer: &'a [u8]) -> Self {
FileAnalyze {
buffer,
element_offset: 0,
truncated: false,
tree: ElementTree::new(),
streams: StreamCollection::new(),
bs_active: false,
bs_bits_consumed: 0,
trace_activated: true,
config: MediaConfig::default(),
multi_file_data: None,
reference_count: 0,
duplicate_indices: Vec::new(),
}
}
pub fn tree(&self) -> &ElementTree {
&self.tree
}
pub fn tree_mut(&mut self) -> &mut ElementTree {
&mut self.tree
}
pub fn streams(&self) -> &StreamCollection {
&self.streams
}
pub fn streams_mut(&mut self) -> &mut StreamCollection {
&mut self.streams
}
pub fn stream_prepare(&mut self, kind: StreamKind) -> usize {
self.streams.stream_prepare(kind)
}
pub fn set_field(
&mut self,
kind: StreamKind,
pos: usize,
parameter: &str,
value: impl Into<Ztring>,
) {
self.streams.set_field(kind, pos, parameter, value);
}
pub fn force_field(
&mut self,
kind: StreamKind,
pos: usize,
parameter: &str,
value: impl Into<Ztring>,
) {
self.streams.force_field(kind, pos, parameter, value);
}
pub fn set_extra_field(
&mut self,
kind: StreamKind,
pos: usize,
parameter: &str,
value: impl Into<Ztring>,
) {
self.streams.set_extra_field(kind, pos, parameter, value);
}
pub fn force_extra_field(
&mut self,
kind: StreamKind,
pos: usize,
parameter: &str,
value: impl Into<Ztring>,
) {
self.streams.force_extra_field(kind, pos, parameter, value);
}
pub fn retrieve(&self, kind: StreamKind, pos: usize, parameter: &str) -> Option<&Ztring> {
self.streams.retrieve(kind, pos, parameter)
}
pub fn stream_count(&self, kind: StreamKind) -> usize {
self.streams.stream_count(kind)
}
pub fn element_begin(&mut self, name: &str) {
self.tree.element_begin(name);
}
pub fn element_end(&mut self) {
self.tree.element_end();
}
pub fn element_info(&mut self, value: impl Into<String>, measure: Option<&str>) {
self.tree.element_info(value, measure);
}
pub fn element_name(&mut self, name: &str) {
self.tree.element_name(name);
}
pub fn element_level(&self) -> usize {
self.tree.element_level()
}
fn param<V: ToString>(&mut self, name: &str, value: V) {
if self.trace_activated && !name.is_empty() {
self.tree.param(name, value.to_string());
}
}
pub fn element_offset(&self) -> usize {
self.element_offset
}
pub fn element_size(&self) -> usize {
self.buffer.len()
}
pub fn set_config(&mut self, config: MediaConfig) {
self.trace_activated = config.trace_activated;
self.config = config;
}
pub fn set_option(&mut self, key: &str, value: &str) -> bool {
self.config.set_option(key, value)
}
pub fn remain(&self) -> usize {
self.buffer.len().saturating_sub(self.element_offset)
}
pub fn truncated(&self) -> bool {
self.truncated
}
fn read_be_u64(&mut self, n: usize) -> Option<u64> {
if self.remain() < n {
self.truncated = true;
self.element_offset = self.buffer.len();
return None;
}
let mut v: u64 = 0;
for i in 0..n {
v = (v << 8) | self.buffer[self.element_offset + i] as u64;
}
self.element_offset += n;
Some(v)
}
fn peek_be_u64(&self, n: usize) -> Option<u64> {
if self.remain() < n {
return None;
}
let mut v: u64 = 0;
for i in 0..n {
v = (v << 8) | self.buffer[self.element_offset + i] as u64;
}
Some(v)
}
pub fn get_b1(&mut self, name: &str) -> u8 {
let v = self.read_be_u64(1).unwrap_or(0) as u8;
self.param(name, v);
v
}
pub fn get_b2(&mut self, name: &str) -> u16 {
let v = self.read_be_u64(2).unwrap_or(0) as u16;
self.param(name, v);
v
}
pub fn get_b3(&mut self, name: &str) -> u32 {
let v = self.read_be_u64(3).unwrap_or(0) as u32;
self.param(name, v);
v
}
pub fn get_b4(&mut self, name: &str) -> u32 {
let v = self.read_be_u64(4).unwrap_or(0) as u32;
self.param(name, v);
v
}
pub fn get_b5(&mut self, name: &str) -> u64 {
let v = self.read_be_u64(5).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_b6(&mut self, name: &str) -> u64 {
let v = self.read_be_u64(6).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_b7(&mut self, name: &str) -> u64 {
let v = self.read_be_u64(7).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_b8(&mut self, name: &str) -> u64 {
let v = self.read_be_u64(8).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_b16(&mut self, name: &str) -> u128 {
if self.remain() < 16 {
self.truncated = true;
self.element_offset = self.buffer.len();
self.param(name, 0u128);
return 0;
}
let mut v: u128 = 0;
for i in 0..16 {
v = (v << 8) | self.buffer[self.element_offset + i] as u128;
}
self.element_offset += 16;
self.param(name, v);
v
}
pub fn peek_b1(&self) -> u8 {
self.peek_be_u64(1).unwrap_or(0) as u8
}
pub fn peek_b2(&self) -> u16 {
self.peek_be_u64(2).unwrap_or(0) as u16
}
pub fn peek_b3(&self) -> u32 {
self.peek_be_u64(3).unwrap_or(0) as u32
}
pub fn peek_b4(&self) -> u32 {
self.peek_be_u64(4).unwrap_or(0) as u32
}
pub fn peek_b5(&self) -> u64 {
self.peek_be_u64(5).unwrap_or(0)
}
pub fn peek_b6(&self) -> u64 {
self.peek_be_u64(6).unwrap_or(0)
}
pub fn peek_b7(&self) -> u64 {
self.peek_be_u64(7).unwrap_or(0)
}
pub fn peek_b8(&self) -> u64 {
self.peek_be_u64(8).unwrap_or(0)
}
pub fn peek_b16(&self) -> u128 {
if self.remain() < 16 {
return 0;
}
let mut v: u128 = 0;
for i in 0..16 {
v = (v << 8) | self.buffer[self.element_offset + i] as u128;
}
v
}
pub fn skip_b1(&mut self, _name: &str) {
self.skip(1)
}
pub fn skip_b2(&mut self, _name: &str) {
self.skip(2)
}
pub fn skip_b3(&mut self, _name: &str) {
self.skip(3)
}
pub fn skip_b4(&mut self, _name: &str) {
self.skip(4)
}
pub fn skip_b5(&mut self, _name: &str) {
self.skip(5)
}
pub fn skip_b6(&mut self, _name: &str) {
self.skip(6)
}
pub fn skip_b7(&mut self, _name: &str) {
self.skip(7)
}
pub fn skip_b8(&mut self, _name: &str) {
self.skip(8)
}
pub fn skip_b16(&mut self, _name: &str) {
self.skip(16)
}
pub fn skip_hexa(&mut self, bytes: usize, _name: &str) {
self.skip(bytes);
}
pub fn read_raw(&mut self, n: usize) -> &[u8] {
if self.remain() < n {
self.truncated = true;
self.element_offset = self.buffer.len();
return &[];
}
let start = self.element_offset;
self.element_offset += n;
&self.buffer[start..start + n]
}
pub fn peek_magic<const N: usize>(&self, expected: &[u8; N]) -> bool {
if self.remain() < N {
return false;
}
&self.buffer[self.element_offset..self.element_offset + N] == expected.as_slice()
}
pub fn peek_raw(&self, n: usize) -> Option<&[u8]> {
if self.remain() < n {
return None;
}
Some(&self.buffer[self.element_offset..self.element_offset + n])
}
pub fn peek_raw_at(&self, at: usize, n: usize) -> Option<&[u8]> {
if at >= self.buffer.len() {
return None;
}
let end = (at + n).min(self.buffer.len());
Some(&self.buffer[at..end])
}
fn skip(&mut self, n: usize) {
if self.remain() < n {
self.truncated = true;
self.element_offset = self.buffer.len();
} else {
self.element_offset += n;
}
}
fn read_le_u64(&mut self, n: usize) -> Option<u64> {
if self.remain() < n {
self.truncated = true;
self.element_offset = self.buffer.len();
return None;
}
let mut v: u64 = 0;
for i in 0..n {
v |= (self.buffer[self.element_offset + i] as u64) << (8 * i);
}
self.element_offset += n;
Some(v)
}
fn peek_le_u64(&self, n: usize) -> Option<u64> {
if self.remain() < n {
return None;
}
let mut v: u64 = 0;
for i in 0..n {
v |= (self.buffer[self.element_offset + i] as u64) << (8 * i);
}
Some(v)
}
pub fn get_l1(&mut self, name: &str) -> u8 {
let v = self.read_le_u64(1).unwrap_or(0) as u8;
self.param(name, v);
v
}
pub fn get_l2(&mut self, name: &str) -> u16 {
let v = self.read_le_u64(2).unwrap_or(0) as u16;
self.param(name, v);
v
}
pub fn get_l3(&mut self, name: &str) -> u32 {
let v = self.read_le_u64(3).unwrap_or(0) as u32;
self.param(name, v);
v
}
pub fn get_l4(&mut self, name: &str) -> u32 {
let v = self.read_le_u64(4).unwrap_or(0) as u32;
self.param(name, v);
v
}
pub fn get_l5(&mut self, name: &str) -> u64 {
let v = self.read_le_u64(5).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_l6(&mut self, name: &str) -> u64 {
let v = self.read_le_u64(6).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_l7(&mut self, name: &str) -> u64 {
let v = self.read_le_u64(7).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_l8(&mut self, name: &str) -> u64 {
let v = self.read_le_u64(8).unwrap_or(0);
self.param(name, v);
v
}
pub fn get_l16(&mut self, name: &str) -> u128 {
if self.remain() < 16 {
self.truncated = true;
self.element_offset = self.buffer.len();
self.param(name, 0u128);
return 0;
}
let mut v: u128 = 0;
for i in 0..16 {
v |= (self.buffer[self.element_offset + i] as u128) << (8 * i);
}
self.element_offset += 16;
self.param(name, v);
v
}
pub fn peek_l1(&self) -> u8 {
self.peek_le_u64(1).unwrap_or(0) as u8
}
pub fn peek_l2(&self) -> u16 {
self.peek_le_u64(2).unwrap_or(0) as u16
}
pub fn peek_l3(&self) -> u32 {
self.peek_le_u64(3).unwrap_or(0) as u32
}
pub fn peek_l4(&self) -> u32 {
self.peek_le_u64(4).unwrap_or(0) as u32
}
pub fn peek_l5(&self) -> u64 {
self.peek_le_u64(5).unwrap_or(0)
}
pub fn peek_l6(&self) -> u64 {
self.peek_le_u64(6).unwrap_or(0)
}
pub fn peek_l7(&self) -> u64 {
self.peek_le_u64(7).unwrap_or(0)
}
pub fn peek_l8(&self) -> u64 {
self.peek_le_u64(8).unwrap_or(0)
}
pub fn peek_l16(&self) -> u128 {
if self.remain() < 16 {
return 0;
}
let mut v: u128 = 0;
for i in 0..16 {
v |= (self.buffer[self.element_offset + i] as u128) << (8 * i);
}
v
}
pub fn skip_l1(&mut self, _name: &str) {
self.skip(1)
}
pub fn skip_l2(&mut self, _name: &str) {
self.skip(2)
}
pub fn skip_l3(&mut self, _name: &str) {
self.skip(3)
}
pub fn skip_l4(&mut self, _name: &str) {
self.skip(4)
}
pub fn skip_l5(&mut self, _name: &str) {
self.skip(5)
}
pub fn skip_l6(&mut self, _name: &str) {
self.skip(6)
}
pub fn skip_l7(&mut self, _name: &str) {
self.skip(7)
}
pub fn skip_l8(&mut self, _name: &str) {
self.skip(8)
}
pub fn skip_l16(&mut self, _name: &str) {
self.skip(16)
}
pub fn get_bf4(&mut self, name: &str) -> f32 {
let v = self.read_be_u64(4).map(|bits| f32::from_bits(bits as u32)).unwrap_or(0.0);
self.param(name, v);
v
}
pub fn get_bf8(&mut self, name: &str) -> f64 {
let v = self.read_be_u64(8).map(f64::from_bits).unwrap_or(0.0);
self.param(name, v);
v
}
pub fn get_bf10(&mut self, name: &str) -> f64 {
if self.remain() < 10 {
self.truncated = true;
self.element_offset = self.buffer.len();
self.param(name, 0.0);
return 0.0;
}
let bytes = &self.buffer[self.element_offset..self.element_offset + 10];
self.element_offset += 10;
let v = decode_f80_be(bytes);
self.param(name, v);
v
}
pub fn get_lf4(&mut self, name: &str) -> f32 {
let v = self.read_le_u64(4).map(|bits| f32::from_bits(bits as u32)).unwrap_or(0.0);
self.param(name, v);
v
}
pub fn get_lf8(&mut self, name: &str) -> f64 {
let v = self.read_le_u64(8).map(f64::from_bits).unwrap_or(0.0);
self.param(name, v);
v
}
pub fn peek_bf4(&self) -> f32 {
self.peek_be_u64(4).map(|bits| f32::from_bits(bits as u32)).unwrap_or(0.0)
}
pub fn peek_bf8(&self) -> f64 {
self.peek_be_u64(8).map(f64::from_bits).unwrap_or(0.0)
}
pub fn peek_lf4(&self) -> f32 {
self.peek_le_u64(4).map(|bits| f32::from_bits(bits as u32)).unwrap_or(0.0)
}
pub fn peek_lf8(&self) -> f64 {
self.peek_le_u64(8).map(f64::from_bits).unwrap_or(0.0)
}
pub fn skip_bf4(&mut self, _name: &str) {
self.skip(4)
}
pub fn skip_bf8(&mut self, _name: &str) {
self.skip(8)
}
pub fn skip_bf10(&mut self, _name: &str) {
self.skip(10)
}
pub fn skip_lf4(&mut self, _name: &str) {
self.skip(4)
}
pub fn skip_lf8(&mut self, _name: &str) {
self.skip(8)
}
pub fn bs_begin(&mut self) {
self.bs_active = true;
self.bs_bits_consumed = 0;
}
pub fn bs_end(&mut self) {
if self.bs_bits_consumed > 0 {
self.element_offset += 1;
self.bs_bits_consumed = 0;
}
self.bs_active = false;
}
fn read_bits_be(&mut self, n: usize) -> u64 {
debug_assert!(self.bs_active, "Get_S* called outside BS_Begin/BS_End");
if n == 0 {
return 0;
}
debug_assert!(n <= 64);
let bits_in_current_byte = 8 - self.bs_bits_consumed;
let bytes_after_current =
if n <= bits_in_current_byte { 0 } else { (n - bits_in_current_byte).div_ceil(8) };
let bytes_needed = 1 + bytes_after_current;
if self.element_offset + bytes_needed > self.buffer.len() {
self.truncated = true;
self.element_offset = self.buffer.len();
self.bs_bits_consumed = 0;
return 0;
}
let mut value: u64 = 0;
let mut bits_left = n;
let mut cursor_byte = self.element_offset;
let mut bit_in_byte = self.bs_bits_consumed;
while bits_left > 0 {
let avail = 8 - bit_in_byte;
let take = bits_left.min(avail);
let shift_in_byte = avail - take;
let chunk = (self.buffer[cursor_byte] >> shift_in_byte) as u64 & ((1u64 << take) - 1);
value = (value << take) | chunk;
bits_left -= take;
bit_in_byte += take;
if bit_in_byte == 8 {
cursor_byte += 1;
bit_in_byte = 0;
}
}
self.element_offset = cursor_byte;
self.bs_bits_consumed = bit_in_byte;
value
}
pub fn get_s1(&mut self, n: usize, name: &str) -> u8 {
let v = self.read_bits_be(n) as u8;
self.param(name, v);
v
}
pub fn get_s2(&mut self, n: usize, name: &str) -> u16 {
let v = self.read_bits_be(n) as u16;
self.param(name, v);
v
}
pub fn get_s3(&mut self, n: usize, name: &str) -> u32 {
let v = self.read_bits_be(n) as u32;
self.param(name, v);
v
}
pub fn get_s4(&mut self, n: usize, name: &str) -> u32 {
let v = self.read_bits_be(n) as u32;
self.param(name, v);
v
}
pub fn get_s5(&mut self, n: usize, name: &str) -> u64 {
let v = self.read_bits_be(n);
self.param(name, v);
v
}
pub fn get_s8(&mut self, n: usize, name: &str) -> u64 {
let v = self.read_bits_be(n);
self.param(name, v);
v
}
pub fn skip_s1(&mut self, n: usize, _name: &str) {
self.read_bits_be(n);
}
pub fn skip_s2(&mut self, n: usize, _name: &str) {
self.read_bits_be(n);
}
pub fn skip_s3(&mut self, n: usize, _name: &str) {
self.read_bits_be(n);
}
pub fn skip_s4(&mut self, n: usize, _name: &str) {
self.read_bits_be(n);
}
pub fn skip_s5(&mut self, n: usize, _name: &str) {
self.read_bits_be(n);
}
pub fn skip_s8(&mut self, n: usize, _name: &str) {
self.read_bits_be(n);
}
pub fn get_c4(&mut self, name: &str) -> u32 {
let v = self.read_be_u64(4).unwrap_or(0) as u32;
if self.trace_activated && !name.is_empty() {
let bytes = v.to_be_bytes();
let printable = bytes.iter().all(|b| b.is_ascii_graphic() || *b == b' ');
let value = if printable {
String::from_utf8_lossy(&bytes).into_owned()
} else {
v.to_string()
};
self.tree.param(name, value);
}
v
}
pub fn peek_c4(&self) -> u32 {
self.peek_b4()
}
pub fn skip_c4(&mut self, _name: &str) {
self.skip(4)
}
}
fn decode_f80_be(bytes: &[u8]) -> f64 {
debug_assert_eq!(bytes.len(), 10);
let sign = (bytes[0] >> 7) & 1;
let exp = (((bytes[0] & 0x7F) as u16) << 8) | bytes[1] as u16;
let mut mant: u64 = 0;
for i in 0..8 {
mant = (mant << 8) | bytes[2 + i] as u64;
}
if exp == 0 && mant == 0 {
return if sign == 1 { -0.0 } else { 0.0 };
}
if exp == 0x7FFF {
return if mant == 0 {
if sign == 1 { f64::NEG_INFINITY } else { f64::INFINITY }
} else {
f64::NAN
};
}
let scaled = (mant as f64) / (1u64 << 63) as f64;
let result = scaled * 2f64.powi(exp as i32 - 16383);
if sign == 1 { -result } else { result }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_b1_through_b8_read_big_endian() {
let buf = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let mut fa = FileAnalyze::new(&buf);
let a = fa.get_b1("a");
let b = fa.get_b2("b");
let c = fa.get_b4("c");
assert_eq!(a, 0x12);
assert_eq!(b, 0x3456);
assert_eq!(c, 0x789A_BCDE);
assert_eq!(fa.element_offset(), 7);
assert_eq!(fa.remain(), 1);
}
#[test]
fn get_b3_5_6_7_widths() {
let buf = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A];
let mut fa = FileAnalyze::new(&buf);
let a = fa.get_b3("a");
let b = fa.get_b5("b");
assert_eq!(a, 0x00_0102_03);
assert_eq!(b, 0x00_0405_0607_08);
}
#[test]
fn get_b16_reads_128_bits() {
let buf: Vec<u8> = (0..16u8).collect();
let mut fa = FileAnalyze::new(&buf);
let v = fa.get_b16("guid");
assert_eq!(v, 0x0001_0203_0405_0607_0809_0A0B_0C0D_0E0F);
}
#[test]
fn truncation_pins_position_and_sets_flag() {
let buf = [0xAA, 0xBB];
let mut fa = FileAnalyze::new(&buf);
let v = fa.get_b4("v");
assert_eq!(v, 0);
assert!(fa.truncated());
assert_eq!(fa.element_offset(), 2);
}
#[test]
fn peek_does_not_advance() {
let buf = [0x11, 0x22, 0x33, 0x44];
let mut fa = FileAnalyze::new(&buf);
assert_eq!(fa.peek_b4(), 0x1122_3344);
assert_eq!(fa.element_offset(), 0);
let w = fa.get_b4("w");
assert_eq!(w, 0x1122_3344);
assert_eq!(fa.element_offset(), 4);
}
#[test]
fn skip_advances() {
let buf = [0; 16];
let mut fa = FileAnalyze::new(&buf);
fa.skip_b4("padding");
fa.skip_hexa(8, "header");
assert_eq!(fa.element_offset(), 12);
assert_eq!(fa.remain(), 4);
}
#[test]
fn cc4_reads_mp4_atom_name_as_u32() {
let buf = [b'f', b't', b'y', b'p'];
let mut fa = FileAnalyze::new(&buf);
let code = fa.get_c4("Type");
assert_eq!(code, 0x6674_7970);
}
#[test]
fn get_l1_through_l8_read_little_endian() {
let buf = [0x12, 0x34, 0x56, 0x78];
let mut fa = FileAnalyze::new(&buf);
let a = fa.get_l1("a");
let b = fa.get_l2("b");
assert_eq!(a, 0x12);
assert_eq!(b, 0x5634);
let buf2 = [0x12, 0x34, 0x56, 0x78];
let mut fa2 = FileAnalyze::new(&buf2);
let c = fa2.get_l4("c");
assert_eq!(c, 0x7856_3412);
}
#[test]
fn get_l16_reads_uuid_little_endian() {
let buf: [u8; 16] = [
0xEF, 0xCD, 0xAB, 0x90, 0x78, 0x56, 0x34, 0x12, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33,
0x22, 0x11,
];
let mut fa = FileAnalyze::new(&buf);
let v = fa.get_l16("uuid");
assert_eq!(v, 0x1122_3344_5566_7788_1234_5678_90AB_CDEF);
}
#[test]
fn get_bf4_reads_be_float32() {
let v = std::f32::consts::PI;
let buf = v.to_be_bytes();
let mut fa = FileAnalyze::new(&buf);
let out = fa.get_bf4("pi");
assert_eq!(out, v);
}
#[test]
fn get_bf8_reads_be_float64() {
let v = std::f64::consts::E;
let buf = v.to_be_bytes();
let mut fa = FileAnalyze::new(&buf);
let out = fa.get_bf8("e");
assert_eq!(out, v);
}
#[test]
fn get_lf4_lf8_read_le_floats() {
let f4 = 1.5_f32;
let f8 = std::f64::consts::PI;
let mut buf = Vec::new();
buf.extend_from_slice(&f4.to_le_bytes());
buf.extend_from_slice(&f8.to_le_bytes());
let mut fa = FileAnalyze::new(&buf);
let a = fa.get_lf4("a");
let b = fa.get_lf8("b");
assert_eq!(a, f4);
assert_eq!(b, f8);
}
#[test]
fn get_bf10_aiff_sample_rate_44100() {
let buf: [u8; 10] = [0x40, 0x0E, 0xAC, 0x44, 0, 0, 0, 0, 0, 0];
let mut fa = FileAnalyze::new(&buf);
let hz = fa.get_bf10("SampleRate");
assert!((hz - 44100.0).abs() < 1e-9, "got {hz}");
}
#[test]
fn get_bf10_zero() {
let buf = [0u8; 10];
let mut fa = FileAnalyze::new(&buf);
let v = fa.get_bf10("zero");
assert_eq!(v, 0.0);
}
#[test]
fn get_b4_records_field_on_current_element() {
let buf = [0xDE, 0xAD, 0xBE, 0xEF];
let mut fa = FileAnalyze::new(&buf);
fa.element_begin("header");
let v = fa.get_b4("Magic");
assert_eq!(v, 0xDEAD_BEEF);
fa.element_end();
let header = &fa.tree().root().children[0];
assert_eq!(header.name, "header");
assert_eq!(header.infos.len(), 1);
assert_eq!(header.infos[0].name.as_deref(), Some("Magic"));
assert_eq!(header.infos[0].value, "3735928559");
}
#[test]
fn get_c4_records_atom_name_as_printable_string() {
let buf = [b'f', b't', b'y', b'p'];
let mut fa = FileAnalyze::new(&buf);
fa.element_begin("atom");
let code = fa.get_c4("Type");
assert_eq!(code, 0x6674_7970);
fa.element_end();
let atom = &fa.tree().root().children[0];
assert_eq!(atom.infos.len(), 1);
assert_eq!(atom.infos[0].name.as_deref(), Some("Type"));
assert_eq!(atom.infos[0].value, "ftyp");
}
#[test]
fn nested_atoms_build_correct_trace_tree() {
let buf = [
0x00, 0x00, 0x00, 0x01,
0, 0, 0, 0,
];
let mut fa = FileAnalyze::new(&buf);
fa.element_begin("moov");
fa.element_begin("mvhd");
let ver = fa.get_b1("Version");
assert_eq!(ver, 0);
let flags = fa.get_b3("Flags");
assert_eq!(flags, 1);
fa.element_end();
fa.element_begin("trak");
fa.element_end();
fa.element_end();
let moov = &fa.tree().root().children[0];
assert_eq!(moov.name, "moov");
assert_eq!(moov.children.len(), 2);
let mvhd = &moov.children[0];
assert_eq!(mvhd.name, "mvhd");
assert_eq!(mvhd.infos.len(), 2);
assert_eq!(mvhd.infos[0].name.as_deref(), Some("Version"));
assert_eq!(mvhd.infos[0].value, "0");
assert_eq!(mvhd.infos[1].name.as_deref(), Some("Flags"));
assert_eq!(mvhd.infos[1].value, "1");
let trak = &moov.children[1];
assert_eq!(trak.name, "trak");
assert!(trak.infos.is_empty());
}
#[test]
fn trace_activated_false_suppresses_param_recording() {
let buf = [0x12, 0x34, 0x56, 0x78];
let mut fa = FileAnalyze::new(&buf);
fa.trace_activated = false;
fa.element_begin("silent");
let v = fa.get_b4("Value");
assert_eq!(v, 0x1234_5678);
fa.element_end();
assert!(fa.tree().root().children[0].infos.is_empty());
}
#[test]
fn file_analyze_fills_streams_directly() {
let buf = [0; 4];
let mut fa = FileAnalyze::new(&buf);
let pos = fa.stream_prepare(StreamKind::Audio);
fa.set_field(StreamKind::Audio, pos, "Format", "FLAC");
fa.set_field(StreamKind::Audio, pos, "BitDepth", "24");
assert_eq!(fa.retrieve(StreamKind::Audio, pos, "Format").map(|z| z.as_str()), Some("FLAC"));
assert_eq!(fa.stream_count(StreamKind::Audio), 1);
}
#[test]
fn bs_get_s_reads_bit_aligned_fields() {
let buf = [0xAB, 0xCD];
let mut fa = FileAnalyze::new(&buf);
fa.bs_begin();
let a = fa.get_s1(4, "a");
let b = fa.get_s1(4, "b");
let c = fa.get_s1(8, "c");
fa.bs_end();
assert_eq!(a, 0xA);
assert_eq!(b, 0xB);
assert_eq!(c, 0xCD);
assert_eq!(fa.element_offset(), 2);
}
#[test]
fn bs_streaminfo_layout_decoded_correctly() {
let mut packed: u64 = 0;
let sample_rate: u64 = 48000;
let channels_m1: u64 = 1; let bps_m1: u64 = 15; let samples: u64 = 71638;
packed |= sample_rate << (3 + 5 + 36);
packed |= channels_m1 << (5 + 36);
packed |= bps_m1 << 36;
packed |= samples;
let buf = packed.to_be_bytes();
let mut fa = FileAnalyze::new(&buf);
fa.bs_begin();
let sr = fa.get_s3(20, "SampleRate");
let ch = fa.get_s1(3, "Channels");
let bps = fa.get_s1(5, "BitsPerSample");
let samp = fa.get_s5(36, "Samples");
fa.bs_end();
assert_eq!(sr, 48000);
assert_eq!(ch + 1, 2);
assert_eq!(bps + 1, 16);
assert_eq!(samp, 71638);
assert_eq!(fa.element_offset(), 8);
}
#[test]
fn bs_end_byte_aligns_when_partially_consumed() {
let buf = [0xFF, 0x12];
let mut fa = FileAnalyze::new(&buf);
fa.bs_begin();
let a = fa.get_s1(3, "a");
assert_eq!(a, 0b111);
fa.bs_end();
assert_eq!(fa.element_offset(), 1);
let b = fa.get_b1("b");
assert_eq!(b, 0x12);
}
#[test]
fn bs_end_no_op_when_already_aligned() {
let buf = [0xAA, 0xBB];
let mut fa = FileAnalyze::new(&buf);
fa.bs_begin();
let a = fa.get_s1(8, "a");
fa.bs_end();
assert_eq!(a, 0xAA);
assert_eq!(fa.element_offset(), 1);
}
#[test]
fn empty_name_does_not_record_param() {
let buf = [0xAA, 0xBB];
let mut fa = FileAnalyze::new(&buf);
fa.element_begin("e");
let v = fa.get_b2("");
assert_eq!(v, 0xAABB);
fa.element_end();
assert!(fa.tree().root().children[0].infos.is_empty());
}
}