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 = if self.protection.is_some() {
56 " xmlns:cenc=\"urn:mpeg:cenc:2013\""
57 } else {
58 ""
59 };
60 let _ = writeln!(
63 s,
64 concat!(
65 "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\"{} ",
66 "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" ",
67 "type=\"static\" mediaPresentationDuration=\"{}\" ",
68 "minBufferTime=\"PT2S\">"
69 ),
70 cenc_ns,
71 iso8601_duration(self.duration_seconds),
72 );
73 s.push_str(" <Period>\n");
74
75 for (kind, content_type) in [
76 (MediaKind::Video, "video"),
77 (MediaKind::Audio, "audio"),
78 (MediaKind::Text, "text"),
79 ] {
80 let reps: Vec<&Representation> = self
81 .representations
82 .iter()
83 .filter(|r| r.stream.kind == kind)
84 .collect();
85 if reps.is_empty() {
86 continue;
87 }
88 let _ = writeln!(
89 s,
90 " <AdaptationSet contentType=\"{}\" segmentAlignment=\"true\">",
91 content_type
92 );
93 if let Some(p) = &self.protection {
94 render_content_protection(&mut s, p);
95 }
96 for r in reps {
97 render_representation(&mut s, r);
98 }
99 s.push_str(" </AdaptationSet>\n");
100 }
101
102 s.push_str(" </Period>\n</MPD>\n");
103 s
104 }
105}
106
107fn render_content_protection(s: &mut String, p: &Protection) {
109 let _ = writeln!(
110 s,
111 concat!(
112 " <ContentProtection ",
113 "schemeIdUri=\"urn:mpeg:dash:mp4protection:2011\" ",
114 "value=\"{}\" cenc:default_KID=\"{}\"/>"
115 ),
116 p.scheme,
117 kid_uuid(&p.default_kid),
118 );
119}
120
121fn kid_uuid(kid: &[u8; 16]) -> String {
123 let h: String = kid.iter().map(|b| format!("{b:02x}")).collect();
124 format!(
125 "{}-{}-{}-{}-{}",
126 &h[0..8],
127 &h[8..12],
128 &h[12..16],
129 &h[16..20],
130 &h[20..32]
131 )
132}
133
134fn render_representation(s: &mut String, r: &Representation) {
135 let codec = r.stream.rfc6381();
136 let bandwidth = r.stream.bitrate.unwrap_or(0);
137 let _ = write!(
138 s,
139 " <Representation id=\"{}\" codecs=\"{}\"",
140 r.id, codec
141 );
142 if let Some((w, h)) = r.stream.resolution {
143 let _ = write!(s, " width=\"{}\" height=\"{}\"", w, h);
144 }
145 if let Some(rate) = r.stream.sample_rate {
146 let _ = write!(s, " audioSamplingRate=\"{}\"", rate);
147 }
148 let _ = writeln!(s, " bandwidth=\"{}\">", bandwidth);
149
150 let _ = writeln!(
151 s,
152 " <SegmentTemplate timescale=\"{}\" initialization=\"{}\" media=\"{}\" startNumber=\"1\">",
153 r.timescale, r.init, r.media
154 );
155 s.push_str(" <SegmentTimeline>\n");
156 render_timeline(s, &r.segment_durations);
157 s.push_str(" </SegmentTimeline>\n");
158 s.push_str(" </SegmentTemplate>\n");
159 s.push_str(" </Representation>\n");
160}
161
162fn render_timeline(s: &mut String, durations: &[u64]) {
164 let mut t = 0u64;
165 let mut i = 0;
166 let mut first = true;
167 while i < durations.len() {
168 let d = durations[i];
169 let mut run = 1;
170 while i + run < durations.len() && durations[i + run] == d {
171 run += 1;
172 }
173 s.push_str(" <S");
174 if first {
175 let _ = write!(s, " t=\"{t}\"");
176 first = false;
177 }
178 let _ = write!(s, " d=\"{d}\"");
179 if run > 1 {
180 let _ = write!(s, " r=\"{}\"", run - 1);
181 }
182 s.push_str("/>\n");
183 t += d * run as u64;
184 i += run;
185 }
186}
187
188fn iso8601_duration(seconds: f64) -> String {
190 let total_ms = (seconds * 1000.0).round() as u64;
191 let h = total_ms / 3_600_000;
192 let m = (total_ms % 3_600_000) / 60_000;
193 let s = (total_ms % 60_000) as f64 / 1000.0;
194 let mut out = String::from("PT");
195 if h > 0 {
196 let _ = write!(out, "{}H", h);
197 }
198 if m > 0 {
199 let _ = write!(out, "{}M", m);
200 }
201 let _ = write!(out, "{}S", s);
202 out
203}