use crate::box_parser::{b, iter_boxes, parse_full_box, read_u16, read_u32, BoxType};
use crate::error::{AvifError as Error, Result};
const SBGP: BoxType = b(b"sbgp");
const CSGP: BoxType = b(b"csgp");
const SGPD: BoxType = b(b"sgpd");
const MAX_RUNS: usize = 1 << 20;
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SampleToGroupRun {
pub sample_count: u32,
pub group_description_index: u32,
}
impl SampleToGroupRun {
#[allow(clippy::too_many_arguments)]
pub fn new(sample_count: u32, group_description_index: u32) -> Self {
Self {
sample_count,
group_description_index,
}
}
}
impl SampleToGroupRun {
pub fn is_fragment_local(&self, bits: u32) -> bool {
if bits == 0 || bits > 32 {
return false;
}
let msb = 1u32 << (bits - 1);
self.group_description_index & msb != 0
}
pub fn description_index(&self, bits: u32) -> u32 {
if bits == 0 || bits > 32 {
return self.group_description_index;
}
let msb = 1u32 << (bits - 1);
self.group_description_index & !msb
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SampleToGroupKind {
Sbgp,
Csgp,
}
#[doc(hidden)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SampleToGroup {
pub kind: SampleToGroupKind,
pub grouping_type: BoxType,
pub grouping_type_parameter: Option<u32>,
pub index_bits: u32,
pub runs: Vec<SampleToGroupRun>,
}
impl SampleToGroup {
#[allow(clippy::too_many_arguments)]
pub fn new(
kind: SampleToGroupKind,
grouping_type: BoxType,
grouping_type_parameter: Option<u32>,
index_bits: u32,
runs: Vec<SampleToGroupRun>,
) -> Self {
Self {
kind,
grouping_type,
grouping_type_parameter,
index_bits,
runs,
}
}
}
impl SampleToGroup {
pub fn covered_sample_count(&self) -> u64 {
self.runs
.iter()
.map(|r| u64::from(r.sample_count))
.fold(0u64, u64::saturating_add)
}
pub fn group_index_for_sample(&self, sample: u32) -> Option<u32> {
let mut base: u64 = 0;
for run in &self.runs {
let next = base.saturating_add(u64::from(run.sample_count));
if u64::from(sample) < next {
return Some(run.group_description_index);
}
base = next;
}
None
}
}
#[doc(hidden)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SampleGroupDescription {
pub version: u8,
pub grouping_type: BoxType,
pub default_length: Option<u32>,
pub default_group_description_index: Option<u32>,
pub entry_count: u32,
pub entries: Vec<Vec<u8>>,
}
impl SampleGroupDescription {
#[allow(clippy::too_many_arguments)]
pub fn new(
version: u8,
grouping_type: BoxType,
default_length: Option<u32>,
default_group_description_index: Option<u32>,
entry_count: u32,
entries: Vec<Vec<u8>>,
) -> Self {
Self {
version,
grouping_type,
default_length,
default_group_description_index,
entry_count,
entries,
}
}
}
pub fn parse_sbgp(payload: &[u8]) -> Result<SampleToGroup> {
let (version, _flags, body) = parse_full_box(payload)?;
let mut cursor = 0usize;
let grouping_type = read_fourcc(body, cursor)
.ok_or_else(|| Error::InvalidData("sbgp: truncated grouping_type".to_string()))?;
cursor += 4;
let grouping_type_parameter = if version == 1 {
let p = read_u32(body, cursor).map_err(|_| {
Error::InvalidData("sbgp: truncated grouping_type_parameter".to_string())
})?;
cursor += 4;
Some(p)
} else {
None
};
let entry_count = read_u32(body, cursor)
.map_err(|_| Error::InvalidData("sbgp: truncated entry_count".to_string()))?
as usize;
cursor += 4;
let mut runs = Vec::with_capacity(entry_count.min(64));
for _ in 0..entry_count.min(MAX_RUNS) {
if cursor + 8 > body.len() {
break;
}
let sample_count = read_u32(body, cursor)?;
let group_description_index = read_u32(body, cursor + 4)?;
cursor += 8;
runs.push(SampleToGroupRun {
sample_count,
group_description_index,
});
}
Ok(SampleToGroup {
kind: SampleToGroupKind::Sbgp,
grouping_type,
grouping_type_parameter,
index_bits: 32,
runs,
})
}
pub fn parse_csgp(payload: &[u8]) -> Result<SampleToGroup> {
let (version, flags, body) = parse_full_box(payload)?;
let _ = version; let index_size_code = flags & 0b11;
let count_size_code = (flags >> 2) & 0b11;
let pattern_size_code = (flags >> 4) & 0b11;
let grouping_type_parameter_present = (flags >> 6) & 0b1 == 1;
let index_bits = 4u32 << index_size_code;
let count_bits = 4u32 << count_size_code;
let pattern_bits = 4u32 << pattern_size_code;
let mut reader = BitReader::new(body);
let grouping_type = {
let v = reader
.read(32)
.ok_or_else(|| Error::InvalidData("csgp: truncated grouping_type".to_string()))?;
(v as u32).to_be_bytes()
};
let grouping_type_parameter = if grouping_type_parameter_present {
Some(reader.read(32).ok_or_else(|| {
Error::InvalidData("csgp: truncated grouping_type_parameter".to_string())
})? as u32)
} else {
None
};
let pattern_count = reader
.read(32)
.ok_or_else(|| Error::InvalidData("csgp: truncated pattern_count".to_string()))?
as usize;
let pattern_count = pattern_count.min(MAX_RUNS);
let mut patterns: Vec<(u32, u32)> = Vec::with_capacity(pattern_count.min(64));
for _ in 0..pattern_count {
let Some(pattern_length) = reader.read(pattern_bits) else {
break;
};
let Some(sample_count) = reader.read(count_bits) else {
break;
};
patterns.push((pattern_length as u32, sample_count as u32));
}
let mut runs: Vec<SampleToGroupRun> = Vec::new();
'patterns: for (pattern_length, sample_count) in patterns {
let plen = pattern_length;
if plen == 0 {
continue;
}
let mut indices = Vec::with_capacity((plen as usize).min(256));
for _ in 0..plen {
let Some(idx) = reader.read(index_bits) else {
break 'patterns;
};
indices.push(idx as u32);
}
if indices.len() < plen as usize {
break;
}
let base = sample_count / plen;
let rem = sample_count % plen;
if runs.len() + plen as usize > MAX_RUNS {
break;
}
for (k, &idx) in indices.iter().enumerate() {
let extra = if (k as u32) < rem { 1 } else { 0 };
let run_samples = base + extra;
runs.push(SampleToGroupRun {
sample_count: run_samples,
group_description_index: idx,
});
}
}
Ok(SampleToGroup {
kind: SampleToGroupKind::Csgp,
grouping_type,
grouping_type_parameter,
index_bits,
runs,
})
}
pub fn parse_sgpd(payload: &[u8]) -> Result<SampleGroupDescription> {
let (version, _flags, body) = parse_full_box(payload)?;
let mut cursor = 0usize;
let grouping_type = read_fourcc(body, cursor)
.ok_or_else(|| Error::InvalidData("sgpd: truncated grouping_type".to_string()))?;
cursor += 4;
let default_length = if version == 1 {
let v = read_u32(body, cursor)
.map_err(|_| Error::InvalidData("sgpd: truncated default_length".to_string()))?;
cursor += 4;
Some(v)
} else {
None
};
let default_group_description_index = if version >= 2 {
let v = read_u32(body, cursor).map_err(|_| {
Error::InvalidData("sgpd: truncated default_group_description_index".to_string())
})?;
cursor += 4;
Some(v)
} else {
None
};
let entry_count = read_u32(body, cursor)
.map_err(|_| Error::InvalidData("sgpd: truncated entry_count".to_string()))?;
cursor += 4;
let mut entries = Vec::new();
if version >= 1 {
let fixed = default_length.unwrap_or(0);
for _ in 0..entry_count {
let len = if fixed != 0 {
fixed as usize
} else {
let Ok(dl) = read_u32(body, cursor) else {
break;
};
cursor += 4;
dl as usize
};
if cursor + len > body.len() {
break;
}
entries.push(body[cursor..cursor + len].to_vec());
cursor += len;
}
}
Ok(SampleGroupDescription {
version,
grouping_type,
default_length,
default_group_description_index,
entry_count,
entries,
})
}
pub fn parse_sample_to_groups(stbl: &[u8]) -> Vec<SampleToGroup> {
let mut out = Vec::new();
for hdr in iter_boxes(stbl).flatten() {
let p = &stbl[hdr.payload_start..hdr.end()];
let parsed = if hdr.box_type == SBGP {
parse_sbgp(p)
} else if hdr.box_type == CSGP {
parse_csgp(p)
} else {
continue;
};
if let Ok(stg) = parsed {
out.push(stg);
}
}
out
}
pub fn parse_sample_group_descriptions(stbl: &[u8]) -> Vec<SampleGroupDescription> {
let mut out = Vec::new();
for hdr in iter_boxes(stbl).flatten() {
if hdr.box_type != SGPD {
continue;
}
let p = &stbl[hdr.payload_start..hdr.end()];
if let Ok(sgpd) = parse_sgpd(p) {
out.push(sgpd);
}
}
out
}
fn read_fourcc(buf: &[u8], at: usize) -> Option<BoxType> {
if at + 4 > buf.len() {
return None;
}
Some([buf[at], buf[at + 1], buf[at + 2], buf[at + 3]])
}
struct BitReader<'a> {
buf: &'a [u8],
bit_pos: usize,
}
impl<'a> BitReader<'a> {
fn new(buf: &'a [u8]) -> Self {
BitReader { buf, bit_pos: 0 }
}
fn read(&mut self, n: u32) -> Option<u64> {
if n == 0 {
return Some(0);
}
let total_bits = self.buf.len().checked_mul(8)?;
if self.bit_pos.checked_add(n as usize)? > total_bits {
return None;
}
let mut value: u64 = 0;
for _ in 0..n {
let byte = self.buf[self.bit_pos / 8];
let bit = (byte >> (7 - (self.bit_pos % 8))) & 1;
value = (value << 1) | u64::from(bit);
self.bit_pos += 1;
}
Some(value)
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BracketingEntry {
AutoExposure {
exposure_step: i8,
exposure_numerator: i8,
},
WhiteBalance { blue_amber: u16, green_magenta: i8 },
Focus {
focus_distance_numerator: u16,
focus_distance_denominator: u16,
},
FlashExposure {
flash_exposure_numerator: i8,
flash_exposure_denominator: i8,
},
DepthOfField {
f_stop_numerator: i8,
f_stop_denominator: i8,
},
}
impl BracketingEntry {
pub fn parse(kind: crate::derived::BracketingKind, entry: &[u8]) -> Result<BracketingEntry> {
use crate::derived::BracketingKind as K;
let need = |n: usize| -> Result<()> {
if entry.len() < n {
Err(Error::InvalidData(format!(
"sgpd: bracketing entry too short ({} < {n})",
entry.len()
)))
} else {
Ok(())
}
};
Ok(match kind {
K::AutoExposure => {
need(2)?;
BracketingEntry::AutoExposure {
exposure_step: entry[0] as i8,
exposure_numerator: entry[1] as i8,
}
}
K::WhiteBalance => {
need(3)?;
BracketingEntry::WhiteBalance {
blue_amber: read_u16(entry, 0)?,
green_magenta: entry[2] as i8,
}
}
K::Focus => {
need(4)?;
BracketingEntry::Focus {
focus_distance_numerator: read_u16(entry, 0)?,
focus_distance_denominator: read_u16(entry, 2)?,
}
}
K::FlashExposure => {
need(2)?;
BracketingEntry::FlashExposure {
flash_exposure_numerator: entry[0] as i8,
flash_exposure_denominator: entry[1] as i8,
}
}
K::DepthOfField => {
need(2)?;
BracketingEntry::DepthOfField {
f_stop_numerator: entry[0] as i8,
f_stop_denominator: entry[1] as i8,
}
}
})
}
}
impl SampleGroupDescription {
pub fn bracketing_entries(&self) -> Option<Result<Vec<BracketingEntry>>> {
let kind = crate::derived::BracketingKind::from_four_cc(&self.grouping_type)?;
Some(
self.entries
.iter()
.map(|e| BracketingEntry::parse(kind, e))
.collect(),
)
}
pub fn equivalence_entries(&self) -> Option<Result<Vec<VisualEquivalenceEntry>>> {
if &self.grouping_type != b"eqiv" {
return None;
}
Some(
self.entries
.iter()
.map(|e| VisualEquivalenceEntry::parse(e))
.collect(),
)
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VisualEquivalenceEntry {
pub time_offset: i16,
pub timescale_multiplier: u16,
}
impl VisualEquivalenceEntry {
#[allow(clippy::too_many_arguments)]
pub fn new(time_offset: i16, timescale_multiplier: u16) -> Self {
Self {
time_offset,
timescale_multiplier,
}
}
}
impl VisualEquivalenceEntry {
pub fn parse(entry: &[u8]) -> Result<VisualEquivalenceEntry> {
if entry.len() < 4 {
return Err(Error::InvalidData(format!(
"sgpd: eqiv entry too short ({} < 4)",
entry.len()
)));
}
Ok(VisualEquivalenceEntry {
time_offset: read_u16(entry, 0)? as i16,
timescale_multiplier: read_u16(entry, 2)?,
})
}
pub fn timescale_multiplier_f64(&self) -> Option<f64> {
if self.timescale_multiplier == 0 {
None
} else {
Some(f64::from(self.timescale_multiplier) / 256.0)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn full_box(version: u8, flags: u32, body: &[u8]) -> Vec<u8> {
let mut out = vec![
version,
(flags >> 16) as u8,
(flags >> 8) as u8,
flags as u8,
];
out.extend_from_slice(body);
out
}
fn wrap(btype: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let size = (8 + payload.len()) as u32;
let mut out = size.to_be_bytes().to_vec();
out.extend_from_slice(btype);
out.extend_from_slice(payload);
out
}
#[test]
fn sbgp_v0_runs_and_per_sample_lookup() {
let mut body = Vec::new();
body.extend_from_slice(b"roll");
body.extend_from_slice(&2u32.to_be_bytes()); body.extend_from_slice(&3u32.to_be_bytes()); body.extend_from_slice(&1u32.to_be_bytes()); body.extend_from_slice(&2u32.to_be_bytes()); body.extend_from_slice(&2u32.to_be_bytes()); let payload = full_box(0, 0, &body);
let stg = parse_sbgp(&payload).unwrap();
assert_eq!(stg.kind, SampleToGroupKind::Sbgp);
assert_eq!(&stg.grouping_type, b"roll");
assert_eq!(stg.grouping_type_parameter, None);
assert_eq!(stg.index_bits, 32);
assert_eq!(stg.runs.len(), 2);
assert_eq!(stg.covered_sample_count(), 5);
assert_eq!(stg.group_index_for_sample(0), Some(1));
assert_eq!(stg.group_index_for_sample(2), Some(1));
assert_eq!(stg.group_index_for_sample(3), Some(2));
assert_eq!(stg.group_index_for_sample(4), Some(2));
assert_eq!(stg.group_index_for_sample(5), None);
}
#[test]
fn sbgp_v1_carries_grouping_type_parameter() {
let mut body = Vec::new();
body.extend_from_slice(b"sync");
body.extend_from_slice(&0xDEAD_BEEFu32.to_be_bytes()); body.extend_from_slice(&1u32.to_be_bytes()); body.extend_from_slice(&4u32.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); let payload = full_box(1, 0, &body);
let stg = parse_sbgp(&payload).unwrap();
assert_eq!(stg.grouping_type_parameter, Some(0xDEAD_BEEF));
assert_eq!(stg.runs.len(), 1);
assert_eq!(stg.group_index_for_sample(0), Some(0));
}
#[test]
fn sbgp_truncated_entries_bounded() {
let mut body = Vec::new();
body.extend_from_slice(b"roll");
body.extend_from_slice(&4u32.to_be_bytes());
body.extend_from_slice(&2u32.to_be_bytes());
body.extend_from_slice(&1u32.to_be_bytes());
body.extend_from_slice(&7u32.to_be_bytes()); let payload = full_box(0, 0, &body);
let stg = parse_sbgp(&payload).unwrap();
assert_eq!(stg.runs.len(), 1);
}
#[test]
fn sbgp_too_short_header_errors() {
let payload = full_box(0, 0, b"ro"); assert!(parse_sbgp(&payload).is_err());
}
#[test]
fn csgp_all_8bit_single_pattern() {
let flags = 0b01 | (0b01 << 2) | (0b01 << 4);
let mut body = Vec::new();
body.extend_from_slice(b"roll"); body.extend_from_slice(&1u32.to_be_bytes()); body.push(2);
body.push(5);
body.push(3);
body.push(4);
let payload = full_box(0, flags, &body);
let stg = parse_csgp(&payload).unwrap();
assert_eq!(stg.kind, SampleToGroupKind::Csgp);
assert_eq!(&stg.grouping_type, b"roll");
assert_eq!(stg.index_bits, 8);
assert_eq!(stg.runs.len(), 2);
assert_eq!(stg.runs[0].sample_count, 3);
assert_eq!(stg.runs[0].group_description_index, 3);
assert_eq!(stg.runs[1].sample_count, 2);
assert_eq!(stg.runs[1].group_description_index, 4);
assert_eq!(stg.covered_sample_count(), 5);
assert_eq!(stg.group_index_for_sample(0), Some(3));
assert_eq!(stg.group_index_for_sample(2), Some(3));
assert_eq!(stg.group_index_for_sample(3), Some(4));
assert_eq!(stg.group_index_for_sample(4), Some(4));
assert_eq!(stg.group_index_for_sample(5), None);
}
#[test]
fn csgp_4bit_indices_packed() {
let flags = (0b01 << 2) | (0b01 << 4);
let mut body = Vec::new();
body.extend_from_slice(b"abcd");
body.extend_from_slice(&1u32.to_be_bytes()); body.push(3); body.push(3); body.push(0x12);
body.push(0xF0);
let payload = full_box(0, flags, &body);
let stg = parse_csgp(&payload).unwrap();
assert_eq!(stg.index_bits, 4);
assert_eq!(stg.runs.len(), 3);
assert_eq!(stg.runs[0].group_description_index, 1);
assert_eq!(stg.runs[1].group_description_index, 2);
assert_eq!(stg.runs[2].group_description_index, 15);
assert_eq!(stg.runs.iter().map(|r| r.sample_count).sum::<u32>(), 3);
assert_eq!(stg.group_index_for_sample(0), Some(1));
assert_eq!(stg.group_index_for_sample(1), Some(2));
assert_eq!(stg.group_index_for_sample(2), Some(15));
}
#[test]
fn csgp_grouping_type_parameter_present() {
let flags = (0b01) | (0b01 << 2) | (0b01 << 4) | (1 << 6);
let mut body = Vec::new();
body.extend_from_slice(b"roll");
body.extend_from_slice(&0x1234_5678u32.to_be_bytes()); body.extend_from_slice(&1u32.to_be_bytes()); body.push(1); body.push(2); body.push(7); let payload = full_box(0, flags, &body);
let stg = parse_csgp(&payload).unwrap();
assert_eq!(stg.grouping_type_parameter, Some(0x1234_5678));
assert_eq!(stg.runs.len(), 1);
assert_eq!(stg.runs[0].sample_count, 2);
assert_eq!(stg.runs[0].group_description_index, 7);
}
#[test]
fn csgp_fragment_local_msb() {
let run = SampleToGroupRun {
sample_count: 1,
group_description_index: 0x83, };
assert!(run.is_fragment_local(8));
assert_eq!(run.description_index(8), 3);
assert!(!run.is_fragment_local(32));
assert_eq!(run.description_index(32), 0x83);
}
#[test]
fn sgpd_v2_default_index() {
let mut body = Vec::new();
body.extend_from_slice(b"roll");
body.extend_from_slice(&2u32.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); let payload = full_box(2, 0, &body);
let sgpd = parse_sgpd(&payload).unwrap();
assert_eq!(sgpd.version, 2);
assert_eq!(&sgpd.grouping_type, b"roll");
assert_eq!(sgpd.default_group_description_index, Some(2));
assert_eq!(sgpd.default_length, None);
assert_eq!(sgpd.entry_count, 0);
}
#[test]
fn sgpd_v1_default_length() {
let mut body = Vec::new();
body.extend_from_slice(b"sync");
body.extend_from_slice(&8u32.to_be_bytes()); body.extend_from_slice(&1u32.to_be_bytes()); let payload = full_box(1, 0, &body);
let sgpd = parse_sgpd(&payload).unwrap();
assert_eq!(sgpd.default_length, Some(8));
assert_eq!(sgpd.default_group_description_index, None);
assert_eq!(sgpd.entry_count, 1);
}
#[test]
fn sgpd_v1_fixed_length_slices_entries() {
let mut body = Vec::new();
body.extend_from_slice(b"aebr");
body.extend_from_slice(&2u32.to_be_bytes()); body.extend_from_slice(&2u32.to_be_bytes()); body.extend_from_slice(&[1u8, 3u8]); body.extend_from_slice(&[0xFEu8, 0x02u8]); let sgpd = parse_sgpd(&full_box(1, 0, &body)).unwrap();
assert_eq!(sgpd.entries.len(), 2);
let decoded = sgpd.bracketing_entries().unwrap().unwrap();
assert_eq!(
decoded[0],
BracketingEntry::AutoExposure {
exposure_step: 1,
exposure_numerator: 3,
}
);
assert_eq!(
decoded[1],
BracketingEntry::AutoExposure {
exposure_step: -2,
exposure_numerator: 2,
}
);
}
#[test]
fn sgpd_v1_self_describing_entries() {
let mut body = Vec::new();
body.extend_from_slice(b"wbbr");
body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&1u32.to_be_bytes()); body.extend_from_slice(&3u32.to_be_bytes()); body.extend_from_slice(&5500u16.to_be_bytes()); body.push(0xF6u8); let sgpd = parse_sgpd(&full_box(1, 0, &body)).unwrap();
assert_eq!(sgpd.entries.len(), 1);
let decoded = sgpd.bracketing_entries().unwrap().unwrap();
assert_eq!(
decoded[0],
BracketingEntry::WhiteBalance {
blue_amber: 5500,
green_magenta: -10,
}
);
}
#[test]
fn bracketing_entry_focus_infinity_and_flash_and_dof() {
let focus = BracketingEntry::parse(
crate::derived::BracketingKind::Focus,
&[0x00, 0x00, 0x00, 0x00],
)
.unwrap();
assert_eq!(
focus,
BracketingEntry::Focus {
focus_distance_numerator: 0,
focus_distance_denominator: 0,
}
);
let flash =
BracketingEntry::parse(crate::derived::BracketingKind::FlashExposure, &[0x01, 0x02])
.unwrap();
assert_eq!(
flash,
BracketingEntry::FlashExposure {
flash_exposure_numerator: 1,
flash_exposure_denominator: 2,
}
);
let dof =
BracketingEntry::parse(crate::derived::BracketingKind::DepthOfField, &[0xFF, 0x02])
.unwrap();
assert_eq!(
dof,
BracketingEntry::DepthOfField {
f_stop_numerator: -1,
f_stop_denominator: 2,
}
);
}
#[test]
fn bracketing_entry_rejects_truncated() {
assert!(
BracketingEntry::parse(crate::derived::BracketingKind::AutoExposure, &[0x01]).is_err()
);
assert!(BracketingEntry::parse(
crate::derived::BracketingKind::WhiteBalance,
&[0x01, 0x02]
)
.is_err());
}
#[test]
fn sgpd_eqiv_entries_decode() {
let mut body = Vec::new();
body.extend_from_slice(b"eqiv");
body.extend_from_slice(&4u32.to_be_bytes()); body.extend_from_slice(&2u32.to_be_bytes()); body.extend_from_slice(&5i16.to_be_bytes()); body.extend_from_slice(&(1u16 << 8).to_be_bytes()); body.extend_from_slice(&(-3i16).to_be_bytes()); body.extend_from_slice(&512u16.to_be_bytes()); let sgpd = parse_sgpd(&full_box(1, 0, &body)).unwrap();
let entries = sgpd.equivalence_entries().unwrap().unwrap();
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].time_offset, 5);
assert_eq!(entries[0].timescale_multiplier, 256);
assert_eq!(entries[0].timescale_multiplier_f64(), Some(1.0));
assert_eq!(entries[1].time_offset, -3);
assert_eq!(entries[1].timescale_multiplier_f64(), Some(2.0));
assert!(sgpd.bracketing_entries().is_none());
}
#[test]
fn eqiv_entry_reserved_multiplier_and_truncation() {
let e = VisualEquivalenceEntry::parse(&[0x00, 0x00, 0x00, 0x00]).unwrap();
assert_eq!(e.timescale_multiplier, 0);
assert_eq!(e.timescale_multiplier_f64(), None);
assert!(VisualEquivalenceEntry::parse(&[0x00, 0x00, 0x00]).is_err());
}
#[test]
fn bracketing_entries_none_for_non_bracket_grouping() {
let mut body = Vec::new();
body.extend_from_slice(b"roll");
body.extend_from_slice(&0u32.to_be_bytes()); let sgpd = parse_sgpd(&full_box(0, 0, &body)).unwrap();
assert!(sgpd.bracketing_entries().is_none());
assert!(sgpd.entries.is_empty());
}
#[test]
fn walk_stbl_collects_sbgp_and_sgpd() {
let mut sbgp_body = Vec::new();
sbgp_body.extend_from_slice(b"roll");
sbgp_body.extend_from_slice(&1u32.to_be_bytes());
sbgp_body.extend_from_slice(&3u32.to_be_bytes());
sbgp_body.extend_from_slice(&1u32.to_be_bytes());
let mut sgpd_body = Vec::new();
sgpd_body.extend_from_slice(b"roll");
sgpd_body.extend_from_slice(&0u32.to_be_bytes());
let mut stbl = Vec::new();
stbl.extend_from_slice(&wrap(b"sbgp", &full_box(0, 0, &sbgp_body)));
stbl.extend_from_slice(&wrap(b"sgpd", &full_box(0, 0, &sgpd_body)));
let stgs = parse_sample_to_groups(&stbl);
assert_eq!(stgs.len(), 1);
assert_eq!(&stgs[0].grouping_type, b"roll");
let sgpds = parse_sample_group_descriptions(&stbl);
assert_eq!(sgpds.len(), 1);
assert_eq!(&sgpds[0].grouping_type, b"roll");
}
#[test]
fn walk_stbl_collects_csgp() {
let flags = 0b01 | (0b01 << 2) | (0b01 << 4);
let mut csgp_body = Vec::new();
csgp_body.extend_from_slice(b"roll");
csgp_body.extend_from_slice(&1u32.to_be_bytes());
csgp_body.push(1);
csgp_body.push(2);
csgp_body.push(5);
let mut stbl = Vec::new();
stbl.extend_from_slice(&wrap(b"csgp", &full_box(0, flags, &csgp_body)));
let stgs = parse_sample_to_groups(&stbl);
assert_eq!(stgs.len(), 1);
assert_eq!(stgs[0].kind, SampleToGroupKind::Csgp);
assert_eq!(stgs[0].runs[0].group_description_index, 5);
}
#[test]
fn bit_reader_crosses_byte_boundary() {
let buf = [0b1010_0110u8, 0b1100_0000u8];
let mut r = BitReader::new(&buf);
assert_eq!(r.read(4), Some(0b1010));
assert_eq!(r.read(4), Some(0b0110));
assert_eq!(r.read(2), Some(0b11));
assert_eq!(r.read(8), None); }
}