use sheathe_core::{MediaKind, StreamInfo};
use std::fmt::Write as _;
#[derive(Debug, Clone)]
pub struct Representation {
pub id: String,
pub stream: StreamInfo,
pub init: String,
pub media: String,
pub timescale: u32,
pub segment_durations: Vec<u64>,
}
#[derive(Debug, Clone)]
pub struct Protection {
pub scheme: String,
pub default_kid: [u8; 16],
}
#[derive(Debug, Clone, Default)]
pub struct Manifest {
pub duration_seconds: f64,
pub representations: Vec<Representation>,
pub protection: Option<Protection>,
}
impl Manifest {
pub fn to_xml(&self) -> String {
let mut s = String::new();
s.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
let cenc_ns =
if self.protection.is_some() { " xmlns:cenc=\"urn:mpeg:cenc:2013\"" } else { "" };
let _ = writeln!(
s,
concat!(
"<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\"{} ",
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" ",
"type=\"static\" mediaPresentationDuration=\"{}\" ",
"minBufferTime=\"PT2S\">"
),
cenc_ns,
iso8601_duration(self.duration_seconds),
);
s.push_str(" <Period>\n");
for (kind, content_type) in
[(MediaKind::Video, "video"), (MediaKind::Audio, "audio"), (MediaKind::Text, "text")]
{
let reps: Vec<&Representation> =
self.representations.iter().filter(|r| r.stream.kind == kind).collect();
if reps.is_empty() {
continue;
}
let _ = writeln!(
s,
" <AdaptationSet contentType=\"{}\" segmentAlignment=\"true\">",
content_type
);
if let Some(p) = &self.protection {
render_content_protection(&mut s, p);
}
for r in reps {
render_representation(&mut s, r);
}
s.push_str(" </AdaptationSet>\n");
}
s.push_str(" </Period>\n</MPD>\n");
s
}
}
fn render_content_protection(s: &mut String, p: &Protection) {
let _ = writeln!(
s,
concat!(
" <ContentProtection ",
"schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\" ",
"value=\"{}\" cenc:default_KID=\"{}\"/>"
),
p.scheme,
kid_uuid(&p.default_kid),
);
}
fn kid_uuid(kid: &[u8; 16]) -> String {
let h: String = kid.iter().map(|b| format!("{b:02x}")).collect();
format!("{}-{}-{}-{}-{}", &h[0..8], &h[8..12], &h[12..16], &h[16..20], &h[20..32])
}
fn render_representation(s: &mut String, r: &Representation) {
let codec = r.stream.rfc6381();
let bandwidth = r.stream.bitrate.unwrap_or(0);
let _ = write!(s, " <Representation id=\"{}\" codecs=\"{}\"", r.id, codec);
if let Some((w, h)) = r.stream.resolution {
let _ = write!(s, " width=\"{}\" height=\"{}\"", w, h);
}
if let Some(rate) = r.stream.sample_rate {
let _ = write!(s, " audioSamplingRate=\"{}\"", rate);
}
let _ = writeln!(s, " bandwidth=\"{}\">", bandwidth);
let _ = writeln!(
s,
" <SegmentTemplate timescale=\"{}\" initialization=\"{}\" media=\"{}\" startNumber=\"1\">",
r.timescale, r.init, r.media
);
s.push_str(" <SegmentTimeline>\n");
render_timeline(s, &r.segment_durations);
s.push_str(" </SegmentTimeline>\n");
s.push_str(" </SegmentTemplate>\n");
s.push_str(" </Representation>\n");
}
fn render_timeline(s: &mut String, durations: &[u64]) {
let mut t = 0u64;
let mut i = 0;
let mut first = true;
while i < durations.len() {
let d = durations[i];
let mut run = 1;
while i + run < durations.len() && durations[i + run] == d {
run += 1;
}
s.push_str(" <S");
if first {
let _ = write!(s, " t=\"{t}\"");
first = false;
}
let _ = write!(s, " d=\"{d}\"");
if run > 1 {
let _ = write!(s, " r=\"{}\"", run - 1);
}
s.push_str("/>\n");
t += d * run as u64;
i += run;
}
}
fn iso8601_duration(seconds: f64) -> String {
let total_ms = (seconds * 1000.0).round() as u64;
let h = total_ms / 3_600_000;
let m = (total_ms % 3_600_000) / 60_000;
let s = (total_ms % 60_000) as f64 / 1000.0;
let mut out = String::from("PT");
if h > 0 {
let _ = write!(out, "{}H", h);
}
if m > 0 {
let _ = write!(out, "{}M", m);
}
let _ = write!(out, "{}S", s);
out
}