1use std::collections::BTreeSet;
4use std::time::Duration;
5
6use transmux::parse_iso8601_duration;
7
8use super::descriptors::XLINK_RESOLVE_TO_ZERO;
9use super::model::*;
10use super::xml::{Elem, Ns, read_tree};
11use crate::error::{Limit, MpdError, Unsupported};
12use crate::limits::Limits;
13use crate::timing::datetime::parse_xs_datetime;
14use crate::timing::utc_timing::UtcScheme;
15
16const PROFILE_SEPARATOR: char = ',';
17const INFINITY_TOKEN: &str = "INF";
18const NEGATIVE_INFINITY_TOKEN: &str = "-INF";
19const MAX_START_WITH_SAP: u8 = 6;
20
21fn invalid(what: &'static str, detail: impl Into<String>) -> MpdError {
22 MpdError::Invalid {
23 what,
24 detail: detail.into(),
25 }
26}
27
28fn opt_str(e: &Elem, name: &str) -> Option<String> {
29 e.attr(name).map(str::to_owned)
30}
31
32fn opt_parse<T: core::str::FromStr>(e: &Elem, name: &'static str) -> Result<Option<T>, MpdError> {
33 match e.attr(name) {
34 None => Ok(None),
35 Some(v) => v
36 .trim()
37 .parse::<T>()
38 .map(Some)
39 .map_err(|_| invalid(name, v)),
40 }
41}
42
43fn opt_u64(e: &Elem, name: &'static str) -> Result<Option<u64>, MpdError> {
44 opt_parse(e, name)
45}
46
47fn opt_u32(e: &Elem, name: &'static str) -> Result<Option<u32>, MpdError> {
48 opt_parse(e, name)
49}
50
51fn opt_i64(e: &Elem, name: &'static str) -> Result<Option<i64>, MpdError> {
52 opt_parse(e, name)
53}
54
55fn positive_u64(e: &Elem, name: &'static str) -> Result<Option<u64>, MpdError> {
56 match opt_u64(e, name)? {
57 Some(0) => Err(invalid(name, "must be greater than zero")),
58 other => Ok(other),
59 }
60}
61
62fn parse_bool(name: &'static str, v: &str) -> Result<bool, MpdError> {
63 match v.trim() {
64 "true" | "1" => Ok(true),
65 "false" | "0" => Ok(false),
66 other => Err(invalid(name, other)),
67 }
68}
69
70fn opt_bool(e: &Elem, name: &'static str) -> Result<Option<bool>, MpdError> {
71 e.attr(name).map(|v| parse_bool(name, v)).transpose()
72}
73
74fn opt_f64(e: &Elem, name: &'static str) -> Result<Option<f64>, MpdError> {
75 let Some(v) = e.attr(name) else {
76 return Ok(None);
77 };
78 let v = v.trim();
79 if v == INFINITY_TOKEN {
80 return Ok(Some(f64::INFINITY));
81 }
82 if v == NEGATIVE_INFINITY_TOKEN {
83 return Ok(Some(f64::NEG_INFINITY));
84 }
85 match v.parse::<f64>() {
86 Ok(x) if x.is_finite() => Ok(Some(x)),
87 _ => Err(invalid(name, v)),
88 }
89}
90
91fn opt_duration(e: &Elem, name: &'static str) -> Result<Option<Duration>, MpdError> {
92 e.attr(name)
93 .map(|v| parse_iso8601_duration(v).map_err(|_| invalid(name, v)))
94 .transpose()
95}
96
97fn opt_time(e: &Elem, name: &'static str) -> Result<Option<i64>, MpdError> {
98 e.attr(name)
99 .map(|v| {
100 parse_xs_datetime(v)
101 .map(|t| t.unix_us)
102 .map_err(|_| invalid(name, v))
103 })
104 .transpose()
105}
106
107fn parse_range(name: &'static str, v: &str) -> Result<ByteRange, MpdError> {
108 let v = v.trim();
109 let (first, last) = v.split_once('-').ok_or_else(|| invalid(name, v))?;
110 let first = first.parse::<u64>().map_err(|_| invalid(name, v))?;
111 let last = if last.is_empty() {
112 None
113 } else {
114 Some(last.parse::<u64>().map_err(|_| invalid(name, v))?)
115 };
116 if let Some(l) = last
117 && l < first
118 {
119 return Err(invalid(name, v));
120 }
121 Ok(ByteRange { first, last })
122}
123
124fn opt_range(e: &Elem, name: &'static str) -> Result<Option<ByteRange>, MpdError> {
125 e.attr(name).map(|v| parse_range(name, v)).transpose()
126}
127
128fn xlink(e: &Elem) -> Result<Option<Xlink>, MpdError> {
129 let Some(href) = e.attr_in(Ns::Xlink, "href") else {
130 return Ok(None);
131 };
132 let actuate = match e.attr_in(Ns::Xlink, "actuate") {
133 None | Some("onRequest") => Actuate::OnRequest,
134 Some("onLoad") => Actuate::OnLoad,
135 Some(other) => return Err(invalid("xlink:actuate", other)),
136 };
137 Ok(Some(Xlink {
138 href: href.to_owned(),
139 actuate,
140 }))
141}
142
143fn is_resolve_to_zero(x: &Option<Xlink>) -> bool {
144 x.as_ref().is_some_and(|x| x.href == XLINK_RESOLVE_TO_ZERO)
145}
146
147struct Ctx<'a> {
148 limits: &'a Limits,
149 features: MpdFeatures,
150}
151
152fn check_count(count: usize, max: usize, limit: Limit) -> Result<(), MpdError> {
153 if count > max {
154 Err(MpdError::TooLarge { limit, max })
155 } else {
156 Ok(())
157 }
158}
159
160fn descriptor(e: &Elem) -> Result<Descriptor, MpdError> {
161 let scheme_id_uri = e
162 .attr("schemeIdUri")
163 .ok_or_else(|| invalid("schemeIdUri", format!("missing on {}", e.name)))?
164 .to_owned();
165 let url_query = e
166 .children
167 .iter()
168 .find(|c| c.ns == Ns::UrlParam && c.name == "UrlQueryInfo")
169 .map(|q| -> Result<UrlQueryInfo, MpdError> {
170 Ok(UrlQueryInfo {
171 use_mpd_url_query: opt_bool(q, "useMPDUrlQuery")?.unwrap_or(false),
172 query_string: opt_str(q, "queryString"),
173 query_template: opt_str(q, "queryTemplate"),
174 })
175 })
176 .transpose()?;
177 Ok(Descriptor {
178 scheme_id_uri,
179 value: opt_str(e, "value"),
180 id: opt_str(e, "id"),
181 default_kid: e.attr_in(Ns::Cenc, "default_KID").map(str::to_owned),
182 url_query,
183 })
184}
185
186fn descriptors(e: &Elem, name: &str) -> Result<Vec<Descriptor>, MpdError> {
187 e.children_named(name).map(descriptor).collect()
188}
189
190fn base_urls(e: &Elem, ctx: &mut Ctx<'_>) -> Result<Vec<BaseUrl>, MpdError> {
191 let mut out = Vec::new();
192 for b in e.children_named("BaseURL") {
193 check_count(out.len() + 1, ctx.limits.max_base_urls, Limit::BaseUrls)?;
194 let ato = opt_f64(b, "availabilityTimeOffset")?;
195 let complete = opt_bool(b, "availabilityTimeComplete")?;
196 if ato.is_some() || complete == Some(false) {
197 ctx.features.low_latency = true;
198 }
199 out.push(BaseUrl {
200 url: b.text.trim().to_owned(),
201 service_location: opt_str(b, "serviceLocation"),
202 priority: dvb_u32(b, "priority")?,
203 weight: dvb_u32(b, "weight")?,
204 availability_time_offset: ato,
205 availability_time_complete: complete,
206 time_shift_buffer_depth: opt_duration(b, "timeShiftBufferDepth")?,
207 });
208 }
209 Ok(out)
210}
211
212fn dvb_u32(e: &Elem, name: &'static str) -> Result<Option<u32>, MpdError> {
213 e.attr_in(Ns::Dvb, name)
214 .map(|v| v.trim().parse::<u32>().map_err(|_| invalid(name, v)))
215 .transpose()
216}
217
218fn url_type(e: &Elem) -> Result<UrlType, MpdError> {
219 Ok(UrlType {
220 source_url: opt_str(e, "sourceURL"),
221 range: opt_range(e, "range")?,
222 })
223}
224
225fn base_info(e: &Elem, ctx: &mut Ctx<'_>) -> Result<BaseInfo, MpdError> {
226 let ato = opt_f64(e, "availabilityTimeOffset")?;
227 let complete = opt_bool(e, "availabilityTimeComplete")?;
228 if ato.is_some() || complete == Some(false) {
229 ctx.features.low_latency = true;
230 }
231 Ok(BaseInfo {
232 timescale: positive_u64(e, "timescale")?,
233 presentation_time_offset: opt_u64(e, "presentationTimeOffset")?,
234 ept_delta: opt_i64(e, "eptDelta")?,
235 pd_delta: opt_i64(e, "pdDelta")?,
236 presentation_duration: opt_u64(e, "presentationDuration")?,
237 time_shift_buffer_depth: opt_duration(e, "timeShiftBufferDepth")?,
238 index_range: opt_range(e, "indexRange")?,
239 index_range_exact: opt_bool(e, "indexRangeExact")?,
240 availability_time_offset: ato,
241 availability_time_complete: complete,
242 initialization: e.child("Initialization").map(url_type).transpose()?,
243 representation_index: e.child("RepresentationIndex").map(url_type).transpose()?,
244 })
245}
246
247fn timeline(e: &Elem, ctx: &Ctx<'_>) -> Result<SegmentTimeline, MpdError> {
248 let mut out = SegmentTimeline::default();
249 if e.has_child("Pattern") {
250 out.unsupported = Some(Unsupported::SegmentSequence);
251 }
252 for s in e.children_named("S") {
253 check_count(
254 out.entries.len() + 1,
255 ctx.limits.max_timeline_entries,
256 Limit::TimelineEntries,
257 )?;
258 let d = opt_u64(s, "d")?.ok_or_else(|| invalid("S@d", "missing"))?;
259 if d == 0 {
260 return Err(invalid("S@d", "must be greater than zero"));
261 }
262 let k = opt_u64(s, "k")?;
263 if k.is_some_and(|k| k > 1) {
264 out.unsupported = Some(Unsupported::SegmentSequence);
265 }
266 out.entries.push(TimelineEntry {
267 t: opt_u64(s, "t")?,
268 n: opt_u64(s, "n")?,
269 d,
270 r: opt_i64(s, "r")?.unwrap_or(0),
271 k,
272 });
273 }
274 Ok(out)
275}
276
277fn multi_info(e: &Elem, ctx: &mut Ctx<'_>) -> Result<MultiInfo, MpdError> {
278 Ok(MultiInfo {
279 base: base_info(e, ctx)?,
280 duration: positive_u64(e, "duration")?,
281 start_number: opt_u64(e, "startNumber")?,
282 end_number: opt_u64(e, "endNumber")?,
283 timeline: e
284 .child("SegmentTimeline")
285 .map(|t| timeline(t, ctx))
286 .transpose()?,
287 bitstream_switching: e.child("BitstreamSwitching").map(url_type).transpose()?,
288 })
289}
290
291fn segment_layer(e: &Elem, ctx: &mut Ctx<'_>) -> Result<SegmentLayer, MpdError> {
292 let mut layer = SegmentLayer::default();
293 let count = ["SegmentBase", "SegmentList", "SegmentTemplate"]
294 .iter()
295 .map(|n| e.children_named(n).count())
296 .sum::<usize>();
297 if count > 1 {
298 return Err(invalid(
299 "Segment information",
300 format!(
301 "more than one of SegmentBase/SegmentList/SegmentTemplate in {}",
302 e.name
303 ),
304 ));
305 }
306 if let Some(b) = e.child("SegmentBase") {
307 layer.base = Some(SegmentBase {
308 info: base_info(b, ctx)?,
309 });
310 }
311 if let Some(l) = e.child("SegmentList") {
312 let mut urls = Vec::new();
313 for u in l.children_named("SegmentURL") {
314 check_count(
315 urls.len() + 1,
316 ctx.limits.max_segment_urls,
317 Limit::SegmentUrls,
318 )?;
319 urls.push(SegmentUrl {
320 media: opt_str(u, "media"),
321 media_range: opt_range(u, "mediaRange")?,
322 index: opt_str(u, "index"),
323 index_range: opt_range(u, "indexRange")?,
324 });
325 }
326 layer.list = Some(SegmentList {
327 multi: multi_info(l, ctx)?,
328 xlink: xlink(l)?,
329 urls,
330 });
331 }
332 if let Some(t) = e.child("SegmentTemplate") {
333 layer.template = Some(SegmentTemplate {
334 multi: multi_info(t, ctx)?,
335 media: opt_str(t, "media"),
336 index: opt_str(t, "index"),
337 initialization: opt_str(t, "initialization"),
338 bitstream_switching: opt_str(t, "bitstreamSwitching"),
339 });
340 }
341 Ok(layer)
342}
343
344fn common(e: &Elem) -> Result<CommonAttributes, MpdError> {
345 Ok(CommonAttributes {
346 profiles: opt_str(e, "profiles"),
347 width: opt_u32(e, "width")?,
348 height: opt_u32(e, "height")?,
349 sar: opt_str(e, "sar"),
350 frame_rate: opt_str(e, "frameRate"),
351 audio_sampling_rate: opt_str(e, "audioSamplingRate"),
352 mime_type: opt_str(e, "mimeType"),
353 codecs: opt_str(e, "codecs"),
354 max_playout_rate: opt_f64(e, "maxPlayoutRate")?,
355 })
356}
357
358fn representation(e: &Elem, ctx: &mut Ctx<'_>) -> Result<Representation, MpdError> {
359 let id = e
360 .attr("id")
361 .ok_or_else(|| invalid("Representation@id", "missing"))?
362 .to_owned();
363 let bandwidth = opt_u64(e, "bandwidth")?
364 .ok_or_else(|| invalid("Representation@bandwidth", format!("missing on {id}")))?;
365 Ok(Representation {
366 id,
367 bandwidth,
368 quality_ranking: opt_u32(e, "qualityRanking")?,
369 dependency_ids: e
370 .attr("dependencyId")
371 .map(|v| v.split_ascii_whitespace().map(str::to_owned).collect())
372 .unwrap_or_default(),
373 common: common(e)?,
374 base_urls: base_urls(e, ctx)?,
375 segment: segment_layer(e, ctx)?,
376 content_protections: descriptors(e, "ContentProtection")?,
377 essential_properties: descriptors(e, "EssentialProperty")?,
378 supplemental_properties: descriptors(e, "SupplementalProperty")?,
379 audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
380 })
381}
382
383fn conditional(name: &'static str, e: &Elem) -> Result<bool, MpdError> {
384 match e.attr(name) {
385 None => Ok(false),
386 Some(v) => match v.trim() {
387 "true" => Ok(true),
388 "false" => Ok(false),
389 digits => digits
390 .parse::<u64>()
391 .map(|n| n != 0)
392 .map_err(|_| invalid(name, digits)),
393 },
394 }
395}
396
397fn adaptation_set(e: &Elem, ctx: &mut Ctx<'_>) -> Result<Option<AdaptationSet>, MpdError> {
398 let link = xlink(e)?;
399 if is_resolve_to_zero(&link) {
400 ctx.features.xlink_resolve_to_zero = true;
401 return Ok(None);
402 }
403 let start_with_sap = opt_u32(e, "startWithSAP")?
404 .map(|v| u8::try_from(v).ok().filter(|v| *v <= MAX_START_WITH_SAP))
405 .map(|v| v.ok_or_else(|| invalid("startWithSAP", "out of range")))
406 .transpose()?;
407 let mut representations = Vec::new();
408 for r in e.children_named("Representation") {
409 check_count(
410 representations.len() + 1,
411 ctx.limits.max_representations,
412 Limit::Representations,
413 )?;
414 representations.push(representation(r, ctx)?);
415 }
416 Ok(Some(AdaptationSet {
417 id: opt_u64(e, "id")?,
418 group: opt_u64(e, "group")?,
419 lang: opt_str(e, "lang"),
420 content_type: opt_str(e, "contentType"),
421 par: opt_str(e, "par"),
422 segment_alignment: conditional("segmentAlignment", e)?,
423 start_with_sap,
424 bitstream_switching: opt_bool(e, "bitstreamSwitching")?,
425 common: common(e)?,
426 xlink: link,
427 roles: descriptors(e, "Role")?,
428 accessibilities: descriptors(e, "Accessibility")?,
429 labels: e
430 .children_named("Label")
431 .map(|l| l.text.trim().to_owned())
432 .collect(),
433 base_urls: base_urls(e, ctx)?,
434 segment: segment_layer(e, ctx)?,
435 content_protections: descriptors(e, "ContentProtection")?,
436 essential_properties: descriptors(e, "EssentialProperty")?,
437 supplemental_properties: descriptors(e, "SupplementalProperty")?,
438 audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
439 representations,
440 }))
441}
442
443fn period(e: &Elem, ctx: &mut Ctx<'_>) -> Result<Option<Period>, MpdError> {
444 let link = xlink(e)?;
445 if is_resolve_to_zero(&link) {
446 ctx.features.xlink_resolve_to_zero = true;
447 return Ok(None);
448 }
449 let mut sets = Vec::new();
450 for a in e.children_named("AdaptationSet") {
451 check_count(
452 sets.len() + 1,
453 ctx.limits.max_adaptation_sets,
454 Limit::AdaptationSets,
455 )?;
456 if let Some(set) = adaptation_set(a, ctx)? {
457 sets.push(set);
458 }
459 }
460 Ok(Some(Period {
461 id: opt_str(e, "id"),
462 start: opt_duration(e, "start")?,
463 duration: opt_duration(e, "duration")?,
464 bitstream_switching: opt_bool(e, "bitstreamSwitching")?.unwrap_or(false),
465 xlink: link,
466 base_urls: base_urls(e, ctx)?,
467 segment: segment_layer(e, ctx)?,
468 asset_identifier: e.child("AssetIdentifier").map(descriptor).transpose()?,
469 essential_properties: descriptors(e, "EssentialProperty")?,
470 supplemental_properties: descriptors(e, "SupplementalProperty")?,
471 adaptation_sets: sets,
472 }))
473}
474
475fn scan_features(e: &Elem, f: &mut MpdFeatures) {
476 if e.ns == Ns::Mpd {
477 match e.name.as_str() {
478 "PatchLocation" => f.patch_location = true,
479 "EventStream" => f.event_stream = true,
480 "InbandEventStream" => f.inband_event_stream = true,
481 "Preselection" => f.preselection = true,
482 "ServiceDescription" => f.service_description = true,
483 "ProducerReferenceTime" => f.producer_reference_time = true,
484 "ContentSteering" => f.content_steering = true,
485 "Resync" => f.resync = true,
486 _ => {}
487 }
488 }
489 for c in &e.children {
490 scan_features(c, f);
491 }
492}
493
494impl Mpd {
495 pub fn parse(bytes: &[u8]) -> Result<Self, MpdError> {
501 Self::parse_with(bytes, &Limits::default())
502 }
503
504 pub fn parse_with(bytes: &[u8], limits: &Limits) -> Result<Self, MpdError> {
509 let root = read_tree(bytes, limits)?;
510 let mut ctx = Ctx {
511 limits,
512 features: MpdFeatures::default(),
513 };
514 let kind = match root.attr("type") {
515 None | Some("static") => PresentationType::Static,
516 Some("dynamic") => PresentationType::Dynamic,
517 Some("list") => return Err(MpdError::Unsupported(Unsupported::PresentationTypeList)),
518 Some(other) => return Err(invalid("MPD@type", other)),
519 };
520 let mut periods = Vec::new();
521 for p in root.children_named("Period") {
522 check_count(periods.len() + 1, limits.max_periods, Limit::Periods)?;
523 if let Some(period) = period(p, &mut ctx)? {
524 periods.push(period);
525 }
526 }
527 let mut period_ids = BTreeSet::new();
528 for id in periods.iter().filter_map(|p| p.id.as_deref()) {
529 if !period_ids.insert(id) {
530 return Err(invalid(
531 "Period@id",
532 format!("`{id}` names more than one Period"),
533 ));
534 }
535 }
536 let mut utc_timings = Vec::new();
537 for u in root.children_named("UTCTiming") {
538 let scheme_id_uri = u
539 .attr("schemeIdUri")
540 .ok_or_else(|| invalid("schemeIdUri", "missing on UTCTiming"))?
541 .to_owned();
542 utc_timings.push(UtcTiming {
543 scheme: UtcScheme::from_scheme_id_uri(&scheme_id_uri),
544 scheme_id_uri,
545 value: u.attr("value").unwrap_or_default().to_owned(),
546 id: opt_str(u, "id"),
547 });
548 }
549 let base = base_urls(&root, &mut ctx)?;
550 let mut features = ctx.features;
551 scan_features(&root, &mut features);
552 Ok(Self {
553 id: opt_str(&root, "id"),
554 profiles: root
555 .attr("profiles")
556 .map(|p| {
557 p.split(PROFILE_SEPARATOR)
558 .map(|s| s.trim().to_owned())
559 .filter(|s| !s.is_empty())
560 .collect()
561 })
562 .unwrap_or_default(),
563 kind,
564 availability_start_time_us: opt_time(&root, "availabilityStartTime")?,
565 availability_end_time_us: opt_time(&root, "availabilityEndTime")?,
566 publish_time_us: opt_time(&root, "publishTime")?,
567 media_presentation_duration: opt_duration(&root, "mediaPresentationDuration")?,
568 minimum_update_period: opt_duration(&root, "minimumUpdatePeriod")?,
569 min_buffer_time: opt_duration(&root, "minBufferTime")?,
570 time_shift_buffer_depth: opt_duration(&root, "timeShiftBufferDepth")?,
571 suggested_presentation_delay: opt_duration(&root, "suggestedPresentationDelay")?,
572 max_segment_duration: opt_duration(&root, "maxSegmentDuration")?,
573 max_subsegment_duration: opt_duration(&root, "maxSubsegmentDuration")?,
574 base_urls: base,
575 locations: root
576 .children_named("Location")
577 .map(|l| l.text.trim().to_owned())
578 .collect(),
579 utc_timings,
580 essential_properties: descriptors(&root, "EssentialProperty")?,
581 supplemental_properties: descriptors(&root, "SupplementalProperty")?,
582 periods,
583 features,
584 })
585 }
586}