use crate::error::{Error, Result};
use mp4_emsg::{EmsgBox, PresentationTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SegmentTiming {
pub earliest_presentation_time: u64,
pub presentation_time_offset: u64,
pub timescale: u32,
}
pub fn emsg_to_v1<'a>(emsg: &EmsgBox<'a>, timing: &SegmentTiming) -> Result<EmsgBox<'a>> {
validate_timescale(emsg.timescale, timing.timescale)?;
let t = movie_timeline_t(emsg, timing);
Ok(EmsgBox {
scheme_id_uri: emsg.scheme_id_uri,
value: emsg.value,
timescale: emsg.timescale,
presentation_time: PresentationTime::Absolute(t),
event_duration: emsg.event_duration,
id: emsg.id,
message_data: emsg.message_data,
})
}
pub fn emsg_to_v0<'a>(emsg: &EmsgBox<'a>, timing: &SegmentTiming) -> Result<EmsgBox<'a>> {
validate_timescale(emsg.timescale, timing.timescale)?;
let t = movie_timeline_t(emsg, timing);
let delta = t
.checked_sub(timing.earliest_presentation_time)
.ok_or(Error::EmsgPresentationTimeBeforeEpt)?;
if delta > u64::from(u32::MAX) {
return Err(Error::EmsgDeltaOverflow(delta));
}
Ok(EmsgBox {
scheme_id_uri: emsg.scheme_id_uri,
value: emsg.value,
timescale: emsg.timescale,
presentation_time: PresentationTime::Delta(delta as u32),
event_duration: emsg.event_duration,
id: emsg.id,
message_data: emsg.message_data,
})
}
fn movie_timeline_t(emsg: &EmsgBox<'_>, timing: &SegmentTiming) -> u64 {
match emsg.presentation_time {
PresentationTime::Absolute(pt) => pt,
PresentationTime::Delta(d) => timing.earliest_presentation_time + u64::from(d),
#[allow(unreachable_patterns)]
_ => unreachable!("non_exhaustive PresentationTime"),
}
}
fn validate_timescale(emsg: u32, timing: u32) -> Result<()> {
if emsg != timing {
return Err(Error::EmsgTimescaleMismatch { emsg, timing });
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v0_to_v1_to_v0_round_trips() {
let emsg = EmsgBox {
scheme_id_uri: "urn:scte:scte35:2013:bin",
value: "1",
timescale: 90_000,
presentation_time: PresentationTime::Delta(0),
event_duration: 2_160_000,
id: 1,
message_data: &[0xFC, 0x30, 0x21],
};
let timing = SegmentTiming {
earliest_presentation_time: 500_000,
presentation_time_offset: 0,
timescale: 90_000,
};
let v1 = emsg_to_v1(&emsg, &timing).unwrap();
assert_eq!(v1.presentation_time, PresentationTime::Absolute(500_000));
let v0_back = emsg_to_v0(&v1, &timing).unwrap();
assert_eq!(v0_back, emsg);
}
#[test]
fn v1_to_v0_to_v1_round_trips() {
let emsg = EmsgBox {
scheme_id_uri: "urn:scte:scte35:2013:bin",
value: "2",
timescale: 90_000,
presentation_time: PresentationTime::Absolute(1_000_000),
event_duration: 0,
id: 42,
message_data: &[],
};
let timing = SegmentTiming {
earliest_presentation_time: 900_000,
presentation_time_offset: 0,
timescale: 90_000,
};
let v0 = emsg_to_v0(&emsg, &timing).unwrap();
assert_eq!(v0.presentation_time, PresentationTime::Delta(100_000));
let v1_back = emsg_to_v1(&v0, &timing).unwrap();
assert_eq!(v1_back, emsg);
}
#[test]
fn timescale_mismatch_returns_error() {
let emsg = EmsgBox {
scheme_id_uri: "urn:scte:scte35:2013:bin",
value: "",
timescale: 90_000,
presentation_time: PresentationTime::Delta(0),
event_duration: 0,
id: 0,
message_data: &[],
};
let timing = SegmentTiming {
earliest_presentation_time: 0,
presentation_time_offset: 0,
timescale: 48_000,
};
assert!(matches!(
emsg_to_v1(&emsg, &timing),
Err(Error::EmsgTimescaleMismatch { .. })
));
assert!(matches!(
emsg_to_v0(&emsg, &timing),
Err(Error::EmsgTimescaleMismatch { .. })
));
}
#[test]
fn v0_event_before_ept_errors() {
let emsg = EmsgBox {
scheme_id_uri: "urn:scte:scte35:2013:bin",
value: "",
timescale: 90_000,
presentation_time: PresentationTime::Delta(10),
event_duration: 0,
id: 0,
message_data: &[],
};
let timing = SegmentTiming {
earliest_presentation_time: 1_000,
presentation_time_offset: 0,
timescale: 90_000,
};
let v1_before = EmsgBox {
presentation_time: PresentationTime::Absolute(500),
..emsg
};
assert!(matches!(
emsg_to_v0(&v1_before, &timing),
Err(Error::EmsgPresentationTimeBeforeEpt)
));
}
}