use crate::error::Result;
use alloc::{string::String, vec::Vec};
use scte35_splice::{commands::AnyCommand, SpliceInfoSection};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MediaTime(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MediaDuration(pub u64);
impl MediaDuration {
pub fn as_seconds_f64(self) -> f64 {
self.0 as f64 / crate::PTS_HZ as f64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum EventKind {
BreakStart,
BreakEnd,
Chapter,
Unspecified,
}
impl EventKind {
pub fn name(&self) -> &'static str {
match self {
EventKind::BreakStart => "break_start",
EventKind::BreakEnd => "break_end",
EventKind::Chapter => "chapter",
EventKind::Unspecified => "unspecified",
}
}
}
dvb_common::impl_spec_display!(EventKind);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum SourcePayload {
Scte35 { raw: Vec<u8> },
Emsg {
scheme_id_uri: String,
value: String,
raw: Vec<u8>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TimedEvent {
pub id: Option<u32>,
pub kind: EventKind,
pub at: Option<MediaTime>,
pub duration: Option<MediaDuration>,
pub source: SourcePayload,
}
impl TimedEvent {
pub fn from_scte35(section: &SpliceInfoSection, raw: &[u8]) -> Result<Self> {
let mut id = None;
let mut kind = EventKind::Unspecified;
let mut at = None;
let mut duration = None;
if let Some(clear) = §ion.clear {
if let AnyCommand::SpliceInsert(si) = &clear.command {
id = Some(si.splice_event_id);
kind = if si.out_of_network_indicator {
EventKind::BreakStart
} else {
EventKind::BreakEnd
};
if let Some(st) = &si.splice_time {
at = st.pts_time.map(MediaTime);
}
if let Some(bd) = &si.break_duration {
duration = Some(MediaDuration(bd.duration));
}
}
}
Ok(TimedEvent {
id,
kind,
at,
duration,
source: SourcePayload::Scte35 { raw: raw.to_vec() },
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
use dvb_common::traits::Parse;
use scte35_splice::SpliceInfoSection;
fn splice_2002() -> Vec<u8> {
let hex = "FC302100000000000000FFF01005000007D27FEF7F7E0020F580C0000000000088B9661D";
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn from_scte35_extracts_break_start_and_duration() {
let raw = splice_2002();
let section = SpliceInfoSection::parse(&raw).unwrap();
let ev = TimedEvent::from_scte35(§ion, &raw).unwrap();
assert_eq!(ev.id, Some(2002));
assert_eq!(ev.kind, EventKind::BreakStart); assert_eq!(ev.at, None); assert_eq!(ev.duration, Some(MediaDuration(2_160_000)));
assert!((ev.duration.unwrap().as_seconds_f64() - 24.0).abs() < 1e-9);
match &ev.source {
SourcePayload::Scte35 { raw: r } => assert_eq!(r, &raw), _ => panic!("expected Scte35 payload"),
}
}
#[test]
fn event_kind_labels() {
assert_eq!(EventKind::BreakStart.name(), "break_start");
assert_eq!(alloc::format!("{}", EventKind::BreakEnd), "break_end");
}
}