use crate::boxes::{SBGP, SGPD};
#[derive(Clone, Debug, Default)]
pub struct SampleToGroup {
pub grouping_type: [u8; 4],
pub grouping_type_parameter: Option<u32>,
pub entries: Vec<(u32, u32)>,
}
#[derive(Clone, Debug, Default)]
pub struct SampleGroupDescription {
pub grouping_type: [u8; 4],
pub default_sample_description_index: Option<u32>,
pub entries: Vec<Vec<u8>>,
}
pub fn build_sbgp(s: &SampleToGroup) -> Vec<u8> {
let version: u8 = if s.grouping_type_parameter.is_some() {
1
} else {
0
};
let mut body =
Vec::with_capacity(4 + 4 + if version == 1 { 4 } else { 0 } + 4 + s.entries.len() * 8);
body.push(version);
body.extend_from_slice(&[0, 0, 0]); body.extend_from_slice(&s.grouping_type);
if let Some(p) = s.grouping_type_parameter {
body.extend_from_slice(&p.to_be_bytes());
}
body.extend_from_slice(&(s.entries.len() as u32).to_be_bytes());
for (count, idx) in &s.entries {
body.extend_from_slice(&count.to_be_bytes());
body.extend_from_slice(&idx.to_be_bytes());
}
wrap(&SBGP, &body)
}
pub fn build_sgpd(s: &SampleGroupDescription) -> Vec<u8> {
let common_len = entries_common_length(&s.entries);
let want_v2 = s.default_sample_description_index.is_some() && common_len.is_some();
let (version, default_length): (u8, u32) = if want_v2 {
(2, 0)
} else if let Some(cl) = common_len {
(1, cl as u32)
} else {
(1, 0)
};
let entries_payload_len = match (version, default_length) {
(1, 0) => s.entries.iter().map(|e| 4 + e.len()).sum::<usize>(),
(1, dl) => s.entries.len() * dl as usize,
(2, _) => s.entries.iter().map(|e| e.len()).sum::<usize>(),
_ => unreachable!(),
};
let extra = match version {
1 => 4,
2 => 4,
_ => unreachable!(),
};
let mut body = Vec::with_capacity(4 + 4 + extra + 4 + entries_payload_len);
body.push(version);
body.extend_from_slice(&[0, 0, 0]); body.extend_from_slice(&s.grouping_type);
match version {
1 => body.extend_from_slice(&default_length.to_be_bytes()),
2 => body.extend_from_slice(
&s.default_sample_description_index
.unwrap_or(0)
.to_be_bytes(),
),
_ => unreachable!(),
}
body.extend_from_slice(&(s.entries.len() as u32).to_be_bytes());
match (version, default_length) {
(1, 0) => {
for e in &s.entries {
body.extend_from_slice(&(e.len() as u32).to_be_bytes());
body.extend_from_slice(e);
}
}
(1, _) => {
for e in &s.entries {
body.extend_from_slice(e);
}
}
(2, _) => {
for e in &s.entries {
body.extend_from_slice(e);
}
}
_ => unreachable!(),
}
wrap(&SGPD, &body)
}
fn entries_common_length(entries: &[Vec<u8>]) -> Option<usize> {
let first = entries.first()?;
if first.is_empty() {
return None;
}
let len = first.len();
if entries.iter().all(|e| e.len() == len) {
Some(len)
} else {
None
}
}
fn wrap(kind: &[u8; 4], body: &[u8]) -> Vec<u8> {
let total = (8 + body.len()) as u32;
let mut out = Vec::with_capacity(total as usize);
out.extend_from_slice(&total.to_be_bytes());
out.extend_from_slice(kind);
out.extend_from_slice(body);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sbgp_v0_two_runs_byte_exact() {
let s = SampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
entries: vec![(10, 1), (5, 0)],
};
let b = build_sbgp(&s);
assert_eq!(b.len(), 36);
assert_eq!(&b[0..4], &36u32.to_be_bytes());
assert_eq!(&b[4..8], b"sbgp");
assert_eq!(&b[8..12], &[0, 0, 0, 0]); assert_eq!(&b[12..16], b"roll");
assert_eq!(&b[16..20], &2u32.to_be_bytes()); assert_eq!(&b[20..24], &10u32.to_be_bytes());
assert_eq!(&b[24..28], &1u32.to_be_bytes());
assert_eq!(&b[28..32], &5u32.to_be_bytes());
assert_eq!(&b[32..36], &0u32.to_be_bytes());
}
#[test]
fn sbgp_v1_with_parameter_byte_exact() {
let s = SampleToGroup {
grouping_type: *b"rap ",
grouping_type_parameter: Some(7),
entries: vec![(3, 2)],
};
let b = build_sbgp(&s);
assert_eq!(b.len(), 32);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[12..16], b"rap ");
assert_eq!(&b[16..20], &7u32.to_be_bytes()); assert_eq!(&b[20..24], &1u32.to_be_bytes()); assert_eq!(&b[24..28], &3u32.to_be_bytes());
assert_eq!(&b[28..32], &2u32.to_be_bytes());
}
#[test]
fn sbgp_zero_entries_legal() {
let s = SampleToGroup {
grouping_type: *b"roll",
grouping_type_parameter: None,
entries: vec![],
};
let b = build_sbgp(&s);
assert_eq!(b.len(), 20);
assert_eq!(&b[16..20], &0u32.to_be_bytes()); }
#[test]
fn sbgp_fragment_local_index_preserved() {
let s = SampleToGroup {
grouping_type: *b"sync",
grouping_type_parameter: None,
entries: vec![(1, 0x1_0001)],
};
let b = build_sbgp(&s);
assert_eq!(&b[24..28], &0x1_0001u32.to_be_bytes());
}
#[test]
fn sgpd_v1_fixed_length_when_entries_share_size() {
let s = SampleGroupDescription {
grouping_type: *b"roll",
default_sample_description_index: None,
entries: vec![vec![0xFF, 0xFB], vec![0x00, 0x05]],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 28);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[12..16], b"roll");
assert_eq!(&b[16..20], &2u32.to_be_bytes()); assert_eq!(&b[20..24], &2u32.to_be_bytes()); assert_eq!(&b[24..26], &[0xFF, 0xFB]);
assert_eq!(&b[26..28], &[0x00, 0x05]);
}
#[test]
fn sgpd_v1_variable_length_when_entries_differ() {
let s = SampleGroupDescription {
grouping_type: *b"prol",
default_sample_description_index: None,
entries: vec![vec![0xAA, 0xBB, 0xCC], vec![0xDD]],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 36);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[16..20], &0u32.to_be_bytes()); assert_eq!(&b[20..24], &2u32.to_be_bytes()); assert_eq!(&b[24..28], &3u32.to_be_bytes()); assert_eq!(&b[28..31], &[0xAA, 0xBB, 0xCC]);
assert_eq!(&b[31..35], &1u32.to_be_bytes()); assert_eq!(&b[35..36], &[0xDD]);
}
#[test]
fn sgpd_v2_when_default_sample_description_index_set() {
let s = SampleGroupDescription {
grouping_type: *b"alst",
default_sample_description_index: Some(3),
entries: vec![vec![0x01, 0x02], vec![0x03, 0x04]],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 28);
assert_eq!(&b[8..12], &[2, 0, 0, 0]); assert_eq!(&b[12..16], b"alst");
assert_eq!(&b[16..20], &3u32.to_be_bytes()); assert_eq!(&b[20..24], &2u32.to_be_bytes()); assert_eq!(&b[24..26], &[0x01, 0x02]);
assert_eq!(&b[26..28], &[0x03, 0x04]);
}
#[test]
fn sgpd_v2_falls_back_to_v1_when_entries_differ() {
let s = SampleGroupDescription {
grouping_type: *b"alst",
default_sample_description_index: Some(3),
entries: vec![vec![0x01], vec![0x02, 0x03]],
};
let b = build_sgpd(&s);
assert_eq!(b[8], 1); assert_eq!(&b[16..20], &0u32.to_be_bytes()); }
#[test]
fn sgpd_empty_entries_legal() {
let s = SampleGroupDescription {
grouping_type: *b"roll",
default_sample_description_index: None,
entries: vec![],
};
let b = build_sgpd(&s);
assert_eq!(b.len(), 24);
assert_eq!(&b[8..12], &[1, 0, 0, 0]); assert_eq!(&b[20..24], &0u32.to_be_bytes()); }
#[test]
fn entries_common_length_helper() {
assert_eq!(entries_common_length(&[]), None);
assert_eq!(entries_common_length(&[vec![]]), None);
assert_eq!(entries_common_length(&[vec![1, 2]]), Some(2));
assert_eq!(entries_common_length(&[vec![1, 2], vec![3, 4]]), Some(2));
assert_eq!(entries_common_length(&[vec![1], vec![2, 3]]), None);
}
}