1use sheathe_core::{MediaKind, StreamInfo};
10use std::fmt::Write as _;
11
12#[derive(Debug, Clone)]
14pub struct Representation {
15 pub id: String,
17 pub stream: StreamInfo,
19 pub init: String,
21 pub media: String,
23 pub timescale: u32,
25 pub segment_durations: Vec<u64>,
27}
28
29#[derive(Debug, Clone)]
31pub struct Protection {
32 pub scheme: String,
34 pub default_kid: [u8; 16],
36}
37
38#[derive(Debug, Clone, Default)]
40pub struct Manifest {
41 pub duration_seconds: f64,
43 pub representations: Vec<Representation>,
45 pub protection: Option<Protection>,
47}
48
49impl Manifest {
50 pub fn to_xml(&self) -> String {
52 let mut s = String::new();
53 s.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
54 let cenc_ns =
56 if self.protection.is_some() { " xmlns:cenc=\"urn:mpeg:cenc:2013\"" } else { "" };
57 let _ = writeln!(
60 s,
61 concat!(
62 "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\"{} ",
63 "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" ",
64 "type=\"static\" mediaPresentationDuration=\"{}\" ",
65 "minBufferTime=\"PT2S\">"
66 ),
67 cenc_ns,
68 iso8601_duration(self.duration_seconds),
69 );
70 s.push_str(" <Period>\n");
71
72 for (kind, content_type) in
73 [(MediaKind::Video, "video"), (MediaKind::Audio, "audio"), (MediaKind::Text, "text")]
74 {
75 let reps: Vec<&Representation> =
76 self.representations.iter().filter(|r| r.stream.kind == kind).collect();
77 if reps.is_empty() {
78 continue;
79 }
80 let _ = writeln!(
81 s,
82 " <AdaptationSet contentType=\"{}\" segmentAlignment=\"true\">",
83 content_type
84 );
85 if let Some(p) = &self.protection {
86 render_content_protection(&mut s, p);
87 }
88 for r in reps {
89 render_representation(&mut s, r);
90 }
91 s.push_str(" </AdaptationSet>\n");
92 }
93
94 s.push_str(" </Period>\n</MPD>\n");
95 s
96 }
97}
98
99fn render_content_protection(s: &mut String, p: &Protection) {
101 let _ = writeln!(
102 s,
103 concat!(
104 " <ContentProtection ",
105 "schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\" ",
106 "value=\"{}\" cenc:default_KID=\"{}\"/>"
107 ),
108 p.scheme,
109 kid_uuid(&p.default_kid),
110 );
111}
112
113fn kid_uuid(kid: &[u8; 16]) -> String {
115 let h: String = kid.iter().map(|b| format!("{b:02x}")).collect();
116 format!("{}-{}-{}-{}-{}", &h[0..8], &h[8..12], &h[12..16], &h[16..20], &h[20..32])
117}
118
119fn render_representation(s: &mut String, r: &Representation) {
120 let codec = r.stream.rfc6381();
121 let bandwidth = r.stream.bitrate.unwrap_or(0);
122 let _ = write!(s, " <Representation id=\"{}\" codecs=\"{}\"", r.id, codec);
123 if let Some((w, h)) = r.stream.resolution {
124 let _ = write!(s, " width=\"{}\" height=\"{}\"", w, h);
125 }
126 if let Some(rate) = r.stream.sample_rate {
127 let _ = write!(s, " audioSamplingRate=\"{}\"", rate);
128 }
129 let _ = writeln!(s, " bandwidth=\"{}\">", bandwidth);
130
131 let _ = writeln!(
132 s,
133 " <SegmentTemplate timescale=\"{}\" initialization=\"{}\" media=\"{}\" startNumber=\"1\">",
134 r.timescale, r.init, r.media
135 );
136 s.push_str(" <SegmentTimeline>\n");
137 render_timeline(s, &r.segment_durations);
138 s.push_str(" </SegmentTimeline>\n");
139 s.push_str(" </SegmentTemplate>\n");
140 s.push_str(" </Representation>\n");
141}
142
143fn render_timeline(s: &mut String, durations: &[u64]) {
145 let mut t = 0u64;
146 let mut i = 0;
147 let mut first = true;
148 while i < durations.len() {
149 let d = durations[i];
150 let mut run = 1;
151 while i + run < durations.len() && durations[i + run] == d {
152 run += 1;
153 }
154 s.push_str(" <S");
155 if first {
156 let _ = write!(s, " t=\"{t}\"");
157 first = false;
158 }
159 let _ = write!(s, " d=\"{d}\"");
160 if run > 1 {
161 let _ = write!(s, " r=\"{}\"", run - 1);
162 }
163 s.push_str("/>\n");
164 t += d * run as u64;
165 i += run;
166 }
167}
168
169fn iso8601_duration(seconds: f64) -> String {
171 let total_ms = (seconds * 1000.0).round() as u64;
172 let h = total_ms / 3_600_000;
173 let m = (total_ms % 3_600_000) / 60_000;
174 let s = (total_ms % 60_000) as f64 / 1000.0;
175 let mut out = String::from("PT");
176 if h > 0 {
177 let _ = write!(out, "{}H", h);
178 }
179 if m > 0 {
180 let _ = write!(out, "{}M", m);
181 }
182 let _ = write!(out, "{}S", s);
183 out
184}