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, Default)]
31pub struct Manifest {
32 pub duration_seconds: f64,
34 pub representations: Vec<Representation>,
36}
37
38impl Manifest {
39 pub fn to_xml(&self) -> String {
41 let mut s = String::new();
42 s.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
43 let _ = writeln!(
46 s,
47 concat!(
48 "<MPD xmlns=\"urn:mpeg:dash:schema:mpd:2011\" ",
49 "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" ",
50 "type=\"static\" mediaPresentationDuration=\"{}\" ",
51 "minBufferTime=\"PT2S\">"
52 ),
53 iso8601_duration(self.duration_seconds),
54 );
55 s.push_str(" <Period>\n");
56
57 for (kind, content_type) in [
58 (MediaKind::Video, "video"),
59 (MediaKind::Audio, "audio"),
60 (MediaKind::Text, "text"),
61 ] {
62 let reps: Vec<&Representation> = self
63 .representations
64 .iter()
65 .filter(|r| r.stream.kind == kind)
66 .collect();
67 if reps.is_empty() {
68 continue;
69 }
70 let _ = writeln!(
71 s,
72 " <AdaptationSet contentType=\"{}\" segmentAlignment=\"true\">",
73 content_type
74 );
75 for r in reps {
76 render_representation(&mut s, r);
77 }
78 s.push_str(" </AdaptationSet>\n");
79 }
80
81 s.push_str(" </Period>\n</MPD>\n");
82 s
83 }
84}
85
86fn render_representation(s: &mut String, r: &Representation) {
87 let codec = r.stream.rfc6381();
88 let bandwidth = r.stream.bitrate.unwrap_or(0);
89 let _ = write!(
90 s,
91 " <Representation id=\"{}\" codecs=\"{}\"",
92 r.id, codec
93 );
94 if let Some((w, h)) = r.stream.resolution {
95 let _ = write!(s, " width=\"{}\" height=\"{}\"", w, h);
96 }
97 if let Some(rate) = r.stream.sample_rate {
98 let _ = write!(s, " audioSamplingRate=\"{}\"", rate);
99 }
100 let _ = writeln!(s, " bandwidth=\"{}\">", bandwidth);
101
102 let _ = writeln!(
103 s,
104 " <SegmentTemplate timescale=\"{}\" initialization=\"{}\" media=\"{}\" startNumber=\"1\">",
105 r.timescale, r.init, r.media
106 );
107 s.push_str(" <SegmentTimeline>\n");
108 render_timeline(s, &r.segment_durations);
109 s.push_str(" </SegmentTimeline>\n");
110 s.push_str(" </SegmentTemplate>\n");
111 s.push_str(" </Representation>\n");
112}
113
114fn render_timeline(s: &mut String, durations: &[u64]) {
116 let mut t = 0u64;
117 let mut i = 0;
118 let mut first = true;
119 while i < durations.len() {
120 let d = durations[i];
121 let mut run = 1;
122 while i + run < durations.len() && durations[i + run] == d {
123 run += 1;
124 }
125 s.push_str(" <S");
126 if first {
127 let _ = write!(s, " t=\"{t}\"");
128 first = false;
129 }
130 let _ = write!(s, " d=\"{d}\"");
131 if run > 1 {
132 let _ = write!(s, " r=\"{}\"", run - 1);
133 }
134 s.push_str("/>\n");
135 t += d * run as u64;
136 i += run;
137 }
138}
139
140fn iso8601_duration(seconds: f64) -> String {
142 let total_ms = (seconds * 1000.0).round() as u64;
143 let h = total_ms / 3_600_000;
144 let m = (total_ms % 3_600_000) / 60_000;
145 let s = (total_ms % 60_000) as f64 / 1000.0;
146 let mut out = String::from("PT");
147 if h > 0 {
148 let _ = write!(out, "{}H", h);
149 }
150 if m > 0 {
151 let _ = write!(out, "{}M", m);
152 }
153 let _ = write!(out, "{}S", s);
154 out
155}