use byteorder::{BigEndian as BE, ReadBytesExt, WriteBytesExt};
use std::io::{Cursor, Read, Write};
use std::time::Duration;
use std::ops::Range;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("sidx box mismatch")] SidxBoxMismatch,
#[error("byte read/write error: {0}")] ByteReadWriteError(#[from] std::io::Error),
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Sidx {
pub version: u8,
pub flags: u32,
pub reference_id: u32,
pub timescale: u32,
pub earliest_presentation: u64,
pub first_offset: u64,
pub references: Vec<SidxEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReferenceType {
Moof,
Sidx,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SidxEntry {
pub reference_type: ReferenceType,
pub referenced_size: u32,
pub subsegment_duration: u32,
pub starts_with_sap: bool,
pub sap_type: u8,
pub sap_delta_time: u32,
}
impl Sidx {
#[fehler::throws]
pub fn parse_box(media_box: &[u8]) -> Self {
let mut reader = Cursor::new(media_box);
let mut sidx = [0; 4];
reader.read_u32::<BE>()?; reader.read_exact(&mut sidx)?; if &sidx != b"sidx" { fehler::throw!(Error::SidxBoxMismatch); }
let version = reader.read_u8()?;
let flags = reader.read_u24::<BE>()?;
let reference_id = reader.read_u32::<BE>()?;
let timescale = reader.read_u32::<BE>()?;
let (earliest_presentation, first_offset) = if version == 0 {
(u64::from(reader.read_u32::<BE>()?), u64::from(reader.read_u32::<BE>()?))
} else {
(reader.read_u64::<BE>()?, reader.read_u64::<BE>()?)
};
reader.read_u16::<BE>()?;
let reference_count = reader.read_u16::<BE>()?;
let mut references: Vec<SidxEntry> = Vec::with_capacity(usize::from(reference_count));
for _ in 0..reference_count {
let reference_type_and_size = reader.read_u32::<BE>()?;
let reference_type = if reference_type_and_size >> 31 == 0 { ReferenceType::Moof } else { ReferenceType::Sidx };
let referenced_size = reference_type_and_size & 0x7fffffff;
let subsegment_duration = reader.read_u32::<BE>()?;
let starts_with_sap_and_sap_type_and_sap_delta_time = reader.read_u32::<BE>()?;
let starts_with_sap = starts_with_sap_and_sap_type_and_sap_delta_time >> 31 == 1; let sap_type = ((starts_with_sap_and_sap_type_and_sap_delta_time >> 28) & 0b0111) as u8;
let sap_delta_time = starts_with_sap_and_sap_type_and_sap_delta_time & !(0b1111 << 28);
references.push(SidxEntry { reference_type, referenced_size, subsegment_duration, starts_with_sap, sap_type, sap_delta_time });
}
Sidx { version, flags, reference_id, timescale, earliest_presentation, first_offset, references }
}
#[fehler::throws]
pub fn build_box(&self) -> Vec<u8> {
const BASE_SIZE: usize = 24;
let size = BASE_SIZE + if self.version == 0 { 8 } else { 16 } + self.references.len() * 12;
let mut writer = Cursor::new(Vec::with_capacity(size));
writer.write_u32::<BE>(u32::try_from(size).unwrap())?;
writer.write(b"sidx")?;
writer.write_u8(self.version)?;
writer.write_u24::<BE>(self.flags)?;
writer.write_u32::<BE>(self.reference_id)?;
writer.write_u32::<BE>(self.timescale)?;
if self.version == 0 {
writer.write_u32::<BE>(u32::try_from(self.earliest_presentation).unwrap())?;
writer.write_u32::<BE>(u32::try_from(self.first_offset).unwrap())?;
} else {
writer.write_u64::<BE>(self.earliest_presentation)?;
writer.write_u64::<BE>(self.first_offset)?;
}
writer.write_u16::<BE>(0)?; writer.write_u16::<BE>(u16::try_from(self.references.len()).unwrap())?;
for reference in &self.references {
let reference_type_and_size = if reference.reference_type == ReferenceType::Moof { 0 } else { 1 << 31 } | reference.referenced_size;
writer.write_u32::<BE>(reference_type_and_size)?;
writer.write_u32::<BE>(reference.subsegment_duration)?;
let starts_with_sap_and_sap_type_and_sap_delta_time = if reference.starts_with_sap { 1 << 31 } else { 0 } | (reference.sap_type as u32) << 28 | reference.sap_delta_time;
writer.write_u32::<BE>(starts_with_sap_and_sap_type_and_sap_delta_time)?;
}
writer.into_inner()
}
pub fn time_to_bytes(&self, time: &Range<Duration>) -> (Range<u64>, Range<Duration>) {
let presentation_offset = self.earliest_presentation;
let mut start_time = presentation_offset;
let mut start_offset = 0;
for entry in &self.references {
if (start_time as f64 / self.timescale as f64) >= time.start.as_secs_f64() { break; }
start_time += u64::from(entry.subsegment_duration);
start_offset += u64::from(entry.referenced_size);
}
let mut end_time = presentation_offset;
let mut end_offset = 0;
for entry in &self.references {
if (end_time as f64 / self.timescale as f64) > time.end.as_secs_f64() { break; }
end_time += u64::from(entry.subsegment_duration);
end_offset += u64::from(entry.referenced_size);
}
(start_offset..end_offset, Duration::from_secs_f64(start_time as f64 / self.timescale as f64)..Duration::from_secs_f64(end_time as f64 / self.timescale as f64))
}
}