#![allow(dead_code)]
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SapType {
Type1,
Type2,
Type3,
None,
}
impl SapType {
#[must_use]
pub const fn as_u8(self) -> u8 {
match self {
Self::Type1 => 1,
Self::Type2 => 2,
Self::Type3 => 3,
Self::None => 0,
}
}
#[must_use]
pub const fn from_u8(v: u8) -> Self {
match v {
1 => Self::Type1,
2 => Self::Type2,
3 => Self::Type3,
_ => Self::None,
}
}
}
impl fmt::Display for SapType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Type1 => f.write_str("SAP-1 (IDR)"),
Self::Type2 => f.write_str("SAP-2 (Open GOP)"),
Self::Type3 => f.write_str("SAP-3 (GDR)"),
Self::None => f.write_str("No SAP"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SegmentIndexEntry {
pub referenced_size: u32,
pub subsegment_duration: u32,
pub reference_type: bool,
pub sap_type: SapType,
pub sap_delta_time: u32,
}
impl SegmentIndexEntry {
#[must_use]
pub const fn new(referenced_size: u32, subsegment_duration: u32, sap_type: SapType) -> Self {
Self {
referenced_size,
subsegment_duration,
reference_type: false,
sap_type,
sap_delta_time: 0,
}
}
#[must_use]
pub const fn with_reference_type(mut self, is_sidx: bool) -> Self {
self.reference_type = is_sidx;
self
}
#[must_use]
pub const fn with_sap_delta(mut self, delta: u32) -> Self {
self.sap_delta_time = delta;
self
}
#[must_use]
pub fn starts_with_sap(&self) -> bool {
self.sap_type != SapType::None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SegmentIndex {
pub reference_id: u32,
pub timescale: u32,
pub earliest_presentation_time: u64,
pub first_offset: u64,
pub entries: Vec<SegmentIndexEntry>,
}
impl SegmentIndex {
#[must_use]
pub fn new(reference_id: u32, timescale: u32) -> Self {
Self {
reference_id,
timescale,
earliest_presentation_time: 0,
first_offset: 0,
entries: Vec::new(),
}
}
pub fn push(&mut self, entry: SegmentIndexEntry) {
self.entries.push(entry);
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn total_size(&self) -> u64 {
self.entries
.iter()
.map(|e| u64::from(e.referenced_size))
.sum()
}
#[must_use]
pub fn total_duration_ticks(&self) -> u64 {
self.entries
.iter()
.map(|e| u64::from(e.subsegment_duration))
.sum()
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn total_duration_secs(&self) -> f64 {
if self.timescale == 0 {
return 0.0;
}
self.total_duration_ticks() as f64 / f64::from(self.timescale)
}
#[must_use]
pub fn find_entry_at(&self, time_ticks: u64) -> Option<usize> {
let mut accumulated = 0u64;
for (i, entry) in self.entries.iter().enumerate() {
accumulated += u64::from(entry.subsegment_duration);
if time_ticks < accumulated {
return Some(i);
}
}
None
}
#[must_use]
pub fn byte_offset_of(&self, index: usize) -> u64 {
self.entries
.iter()
.take(index)
.map(|e| u64::from(e.referenced_size))
.sum()
}
#[must_use]
pub fn sap_indices(&self) -> Vec<usize> {
self.entries
.iter()
.enumerate()
.filter(|(_, e)| e.starts_with_sap())
.map(|(i, _)| i)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_entry(size: u32, dur: u32, sap: SapType) -> SegmentIndexEntry {
SegmentIndexEntry::new(size, dur, sap)
}
#[test]
fn test_sap_type_roundtrip() {
for v in 0..=3 {
assert_eq!(SapType::from_u8(v).as_u8(), v);
}
}
#[test]
fn test_sap_type_display() {
assert_eq!(format!("{}", SapType::Type1), "SAP-1 (IDR)");
assert_eq!(format!("{}", SapType::None), "No SAP");
}
#[test]
fn test_starts_with_sap() {
assert!(sample_entry(100, 48000, SapType::Type1).starts_with_sap());
assert!(!sample_entry(100, 48000, SapType::None).starts_with_sap());
}
#[test]
fn test_entry_builders() {
let e = SegmentIndexEntry::new(200, 90000, SapType::Type2)
.with_reference_type(true)
.with_sap_delta(1000);
assert!(e.reference_type);
assert_eq!(e.sap_delta_time, 1000);
}
#[test]
fn test_new_index_empty() {
let idx = SegmentIndex::new(1, 90000);
assert!(idx.is_empty());
assert_eq!(idx.len(), 0);
}
#[test]
fn test_push() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
assert_eq!(idx.len(), 1);
}
#[test]
fn test_total_size() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
idx.push(sample_entry(2000, 90000, SapType::None));
assert_eq!(idx.total_size(), 3000);
}
#[test]
fn test_total_duration_ticks() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
idx.push(sample_entry(2000, 45000, SapType::None));
assert_eq!(idx.total_duration_ticks(), 135000);
}
#[test]
fn test_total_duration_secs() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
let dur = idx.total_duration_secs();
assert!((dur - 1.0).abs() < 1e-9);
}
#[test]
fn test_total_duration_secs_zero_timescale() {
let mut idx = SegmentIndex::new(1, 0);
idx.push(sample_entry(100, 500, SapType::None));
assert_eq!(idx.total_duration_secs(), 0.0);
}
#[test]
fn test_find_entry_at() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
idx.push(sample_entry(2000, 90000, SapType::None));
assert_eq!(idx.find_entry_at(0), Some(0));
assert_eq!(idx.find_entry_at(89999), Some(0));
assert_eq!(idx.find_entry_at(90000), Some(1));
}
#[test]
fn test_find_entry_at_out_of_range() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
assert!(idx.find_entry_at(90000).is_none());
}
#[test]
fn test_byte_offset_of() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
idx.push(sample_entry(2000, 90000, SapType::None));
idx.push(sample_entry(3000, 90000, SapType::Type1));
assert_eq!(idx.byte_offset_of(0), 0);
assert_eq!(idx.byte_offset_of(1), 1000);
assert_eq!(idx.byte_offset_of(2), 3000);
}
#[test]
fn test_sap_indices() {
let mut idx = SegmentIndex::new(1, 90000);
idx.push(sample_entry(1000, 90000, SapType::Type1));
idx.push(sample_entry(2000, 90000, SapType::None));
idx.push(sample_entry(3000, 90000, SapType::Type2));
assert_eq!(idx.sap_indices(), vec![0, 2]);
}
#[test]
fn test_index_fields() {
let idx = SegmentIndex::new(42, 48000);
assert_eq!(idx.reference_id, 42);
assert_eq!(idx.timescale, 48000);
}
#[test]
fn test_sap_type_unknown() {
assert_eq!(SapType::from_u8(255), SapType::None);
}
}