Skip to main content

dash_mpd/
lib.rs

1//! A Rust library for parsing, serializing and downloading media content from a DASH MPD manifest,
2//! as used for on-demand replay of TV content and video streaming services. Allows both parsing of
3//! a DASH manifest (XML format) to Rust structs (deserialization) and programmatic generation of an
4//! MPD manifest (serialization). The library also allows you to download media content from a
5//! streaming server.
6
7//! [DASH](https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP) (dynamic adaptive
8//! streaming over HTTP), also called MPEG-DASH, is a technology used for media streaming over the
9//! web, commonly used for video on demand (VOD) services. The Media Presentation Description (MPD)
10//! is a description of the resources (manifest or “playlist”) forming a streaming service, that a
11//! DASH client uses to determine which assets to request in order to perform adaptive streaming of
12//! the content. DASH MPD manifests can be used both with content encoded as MPEG and as WebM.
13//!
14
15//! This library provides a serde-based parser (deserializer) and serializer for the DASH MPD
16//! format, as formally defined in ISO/IEC standard 23009-1:2022. This version of the standard is
17//! [available for free online](https://standards.iso.org/ittf/PubliclyAvailableStandards/c083314_ISO_IEC%2023009-1_2022(en).zip). XML schema files are [available for no cost from
18//! ISO](https://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/). When
19//! MPD files in practical use diverge from the formal standard, this library prefers to
20//! interoperate with existing practice.
21//!
22//! The library does not yet provide full coverage of the fifth edition of the specification. All
23//! elements and attributes in common use are supported, however.
24//!
25//! The library also provides experimental support for downloading content (audio or video)
26//! described by an MPD manifest. This involves selecting the alternative with the most appropriate
27//! encoding (in terms of bitrate, codec, etc.), fetching segments of the content using HTTP or
28//! HTTPS requests (this functionality depends on the `reqwest` crate) and muxing audio and video
29//! segments together (using ffmpeg via the `ac_ffmpeg` crate).
30//!
31//!
32//! ## DASH features supported
33//!
34//! - VOD (static) stream manifests
35//! - Multi-period content
36//! - XLink elements (only with actuate=onLoad semantics, resolve-to-zero supported)
37//! - All forms of segment index info: SegmentBase@indexRange, SegmentTimeline,
38//!   SegmentTemplate@duration, SegmentTemplate@index, SegmentList
39//! - Media containers of types supported by mkvmerge, ffmpeg, VLC and MP4Box (this includes
40//!   Matroska, ISO-BMFF / CMAF / MP4, WebM, MPEG-2 TS)
41//! - Subtitles: preliminary support for WebVTT and TTML streams
42//!
43//!
44//! ## Limitations / unsupported features
45//!
46//! - Dynamic MPD manifests, that are used for live streaming/OTT TV
47//! - XLink with actuate=onRequest semantics
48//! - Application of MPD patches
49//
50//
51//
52// Reference libdash library: https://github.com/bitmovin/libdash
53//   https://github.com/bitmovin/libdash/blob/master/libdash/libdash/source/xml/Node.cpp
54// Reference dash.js library: https://github.com/Dash-Industry-Forum/dash.js
55// Google Shaka player: https://github.com/google/shaka-player
56// The DASH code in VLC: https://code.videolan.org/videolan/vlc/-/tree/master/modules/demux/dash
57// Streamlink source code: https://github.com/streamlink/streamlink/blob/master/src/streamlink/stream/dash_manifest.py
58
59// TODO: handle dynamic MPD as per https://livesim.dashif.org/livesim/mup_30/testpic_2s/Manifest.mpd
60// TODO: handle indexRange attribute, as per https://dash.akamaized.net/dash264/TestCasesMCA/dolby/2/1/ChID_voices_71_768_ddp.mpd
61// TODO: implement MPD Patch support when downloading, with test cases from https://github.com/ab2022/mpddiffs/tree/main
62
63
64#![allow(non_snake_case)]
65
66/// If library feature `libav` is enabled, muxing support (combining audio and video streams, which
67/// are often separated out in DASH streams) is provided by ffmpeg's libav library, via the
68/// `ac_ffmpeg` crate. Otherwise, muxing is implemented by calling `mkvmerge`, `ffmpeg` or `vlc` as
69/// a subprocess. The muxing support is only compiled when the fetch feature is enabled.
70#[cfg(feature = "fetch")]
71pub mod media;
72#[cfg(all(feature = "fetch", feature = "libav"))]
73mod libav;
74#[cfg(all(feature = "fetch", not(feature = "libav")))]
75pub mod ffmpeg;
76#[cfg(feature = "fetch")]
77pub mod sidx;
78#[cfg(feature = "fetch")]
79pub mod fetch;
80#[cfg(feature = "fetch")]
81pub mod decryption;
82// Support for the SCTE-35 standard for insertion of alternate content
83#[cfg(feature = "scte35")]
84pub mod scte35;
85#[cfg(feature = "scte35")]
86use crate::scte35::{Signal, SpliceInfoSection};
87
88#[cfg(all(feature = "fetch", feature = "libav"))]
89use crate::libav::{mux_audio_video, copy_video_to_container, copy_audio_to_container};
90#[cfg(all(feature = "fetch", not(feature = "libav")))]
91use crate::ffmpeg::{mux_audio_video, copy_video_to_container, copy_audio_to_container};
92
93#[cfg(all(feature = "sandbox", feature = "fetch", target_os = "linux"))]
94pub mod sandbox;
95
96use serde::{Serialize, Serializer, Deserialize};
97use serde::de;
98use serde_with::skip_serializing_none;
99use regex::Regex;
100use std::sync::LazyLock;
101use std::time::Duration;
102use chrono::DateTime;
103use url::Url;
104#[allow(unused_imports)]
105use tracing::warn;
106
107// used to parse duration when de-serializing to MPD
108static XS_DURATION_REGEX: LazyLock<Regex> = LazyLock::new(||
109    Regex::new(concat!(r"^(?P<sign>[+-])?P",
110        r"(?:(?P<years>\d+)Y)?",
111        r"(?:(?P<months>\d+)M)?",
112        r"(?:(?P<weeks>\d+)W)?",
113        r"(?:(?P<days>\d+)D)?",
114        r"(?:(?P<hastime>T)", // time part must begin with a T
115        r"(?:(?P<hours>\d+)H)?",
116        r"(?:(?P<minutes>\d+)M)?",
117        r"(?:(?P<seconds>\d+)(?:(?P<nanoseconds>[.,]\d+)?)S)?",
118        r")?")).unwrap()
119);
120
121/// Type representing an xs:dateTime, as per <https://www.w3.org/TR/xmlschema-2/#dateTime>
122// Something like 2021-06-03T13:00:00Z or 2022-12-06T22:27:53
123pub type XsDatetime = DateTime<chrono::offset::Utc>;
124
125#[derive(thiserror::Error, Debug)]
126#[non_exhaustive]
127pub enum DashMpdError {
128    #[error("parse error {0:?}")]
129    Parsing(String),
130    #[error("invalid Duration: {0:?}")]
131    InvalidDuration(String),
132    #[error("invalid DateTime: {0:?}")]
133    InvalidDateTime(String),
134    #[error("invalid media stream: {0:?}")]
135    UnhandledMediaStream(String),
136    #[error("I/O error {1} ({0:?})")]
137    Io(#[source] std::io::Error, String),
138    #[error("network error {0:?}")]
139    Network(String),
140    #[error("network timeout: {0:?}")]
141    NetworkTimeout(String),
142    #[error("network connection: {0:?}")]
143    NetworkConnect(String),
144    #[error("muxing error {0:?}")]
145    Muxing(String),
146    #[error("decryption error {0:?}")]
147    Decrypting(String),
148    #[error("{0:?}")]
149    Other(String),
150}
151
152
153// Serialize an xsd:double parameter. We can't use the default serde serialization for f64 due to
154// the difference in handling INF, -INF and NaN values.
155//
156// Reference: http://www.datypic.com/sc/xsd/t-xsd_double.html
157fn serialize_xsd_double<S>(xsd: &f64, serializer: S) -> Result<S::Ok, S::Error>
158where
159    S: Serializer,
160{
161    let formatted = if xsd.is_nan() {
162        String::from("NaN")
163    } else if xsd.is_infinite() {
164        if xsd.is_sign_positive() {
165            // Here serde returns "inf", which doesn't match the XML Schema definition.
166            String::from("INF")
167        } else {
168            String::from("-INF")
169        }
170    } else {
171        xsd.to_string()
172    };
173    serializer.serialize_str(&formatted)
174}
175
176// Serialize an Option<f64> as an xsd:double.
177fn serialize_opt_xsd_double<S>(oxsd: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
178where
179    S: Serializer,
180{
181    if let Some(xsd) = oxsd {
182        serialize_xsd_double(xsd, serializer)
183    } else {
184        // in fact this won't be called because of the #[skip_serializing_none] annotation
185        serializer.serialize_none()
186    }
187}
188
189
190/// Parse an XML duration string, as per https://www.w3.org/TR/xmlschema-2/#duration
191///
192/// The lexical representation for duration is the ISO 8601 extended format PnYn MnDTnH nMnS, where
193/// nY represents the number of years, nM the number of months, nD the number of days, 'T' is the
194/// date/time separator, nH the number of hours, nM the number of minutes and nS the number of
195/// seconds. The number of seconds can include decimal digits to arbitrary precision.
196///
197/// Examples: "PT0H0M30.030S", "PT1.2S", PT1004199059S, PT130S
198/// P2Y6M5DT12H35M30S  => 2 years, 6 months, 5 days, 12 hours, 35 minutes, 30 seconds
199/// P1DT2H => 1 day, 2 hours
200/// P0Y20M0D => 20 months (0 is permitted as a number, but is not required)
201/// PT1M30.5S => 1 minute, 30.5 seconds
202///
203/// Limitations:
204///   - this function can't represent negative durations (leading "-" character) due to the choice of a
205///     std::time::Duration.
206///
207///   - this function only accepts fractional parts of seconds, and rejects for example "P0.5Y" and "PT2.3H"
208///
209///   - months are approximated as 30 days and years as 365 days, as std::time::Duration cannot
210///     represent calendar-relative durations. This means that values involving months or years are
211///     not perfectly round-trippable.
212fn parse_xs_duration(s: &str) -> Result<Duration, DashMpdError> {
213    use std::cmp::min;
214
215    match XS_DURATION_REGEX.captures(s) {
216        Some(m) => {
217            if m.name("hastime").is_none() &&
218               m.name("years").is_none() &&
219               m.name("months").is_none() &&
220               m.name("weeks").is_none() &&
221               m.name("days").is_none() {
222                  return Err(DashMpdError::InvalidDuration("empty".to_string()));
223            }
224            let mut secs: u64 = 0;
225            let mut nsecs: u32 = 0;
226            if let Some(nano) = m.name("nanoseconds") {
227                // We drop the initial "." and limit precision in the fractional seconds to 9 digits
228                // (nanosecond precision)
229                let lim = min(nano.as_str().len(), 9 + ".".len());
230                if let Some(ss) = &nano.as_str().get(1..lim) {
231                    let padded = format!("{ss:0<9}");
232                    nsecs = padded.parse::<u32>()
233                        .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
234                }
235            }
236            if let Some(mseconds) = m.name("seconds") {
237                let seconds = mseconds.as_str().parse::<u64>()
238                    .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
239                secs += seconds;
240            }
241            if let Some(mminutes) = m.name("minutes") {
242                let minutes = mminutes.as_str().parse::<u64>()
243                    .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
244                secs += minutes * 60;
245            }
246            if let Some(mhours) = m.name("hours") {
247                let hours = mhours.as_str().parse::<u64>()
248                    .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
249                secs += hours * 60 * 60;
250            }
251            if let Some(mdays) = m.name("days") {
252                let days = mdays.as_str().parse::<u64>()
253                    .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
254                secs += days * 60 * 60 * 24;
255            }
256            if let Some(mweeks) = m.name("weeks") {
257                let weeks = mweeks.as_str().parse::<u64>()
258                    .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
259                secs += weeks * 60 * 60 * 24 * 7;
260            }
261            if let Some(mmonths) = m.name("months") {
262                let months = mmonths.as_str().parse::<u64>()
263                    .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
264                secs += months * 60 * 60 * 24 * 30;
265            }
266            if let Some(myears) = m.name("years") {
267                let years = myears.as_str().parse::<u64>()
268                    .map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
269                secs += years * 60 * 60 * 24 * 365;
270            }
271            if let Some(msign) = m.name("sign") {
272                if msign.as_str() == "-" {
273                    return Err(DashMpdError::InvalidDuration("can't represent negative durations".to_string()));
274                }
275            }
276            Ok(Duration::new(secs, nsecs))
277        },
278        None => Err(DashMpdError::InvalidDuration(String::from("couldn't parse XS duration"))),
279    }
280}
281
282
283// Note bug in current version of the iso8601 crate which incorrectly parses
284// strings like "PT344S" (seen in a real MPD) as a zero duration. However, ISO 8601 standard as
285// adopted by Indian Bureau of Standards includes p29 an example "PT72H", as do various MPD
286// manifests in the wild. https://archive.org/details/gov.in.is.7900.2007/
287// fn parse_xs_duration_buggy(s: &str) -> Result<Duration> {
288//     match iso8601::duration(s) {
289//         Ok(iso_duration) => {
290//             match iso_duration {
291//                 iso8601::Duration::Weeks(w) => Ok(Duration::new(w as u64*60 * 60 * 24 * 7, 0)),
292//                 iso8601::Duration::YMDHMS {year, month, day, hour, minute, second, millisecond } => {
293//                     // note that if year and month are specified, we are not going to do a very
294//                     // good conversion here
295//                     let mut secs: u64 = second.into();
296//                     secs += minute as u64 * 60;
297//                     secs += hour   as u64 * 60 * 60;
298//                     secs += day    as u64 * 60 * 60 * 24;
299//                     secs += month  as u64 * 60 * 60 * 24 * 31;
300//                     secs += year   as u64 * 60 * 60 * 24 * 31 * 365;
301//                     Ok(Duration::new(secs, millisecond * 1000_000))
302//                 },
303//             }
304//         },
305//         Err(e) => Err(anyhow!("Couldn't parse XS duration {}: {:?}", s, e)),
306//     }
307// }
308
309// The iso8601_duration crate can't handle durations with fractional seconds
310// fn parse_xs_duration_buggy(s: &str) -> Result<Duration> {
311//     match iso8601_duration::Duration::parse(s) {
312//         Ok(d) => {
313//             let nanos: u32 = 1000_000 * d.second.fract() as u32;
314//             let mut secs: u64 = d.second.trunc() as u64;
315//             secs += d.minute as u64 * 60;
316//             secs += d.hour   as u64 * 60 * 60;
317//             secs += d.day    as u64 * 60 * 60 * 24;
318//             secs += d.month  as u64 * 60 * 60 * 24 * 31;
319//             secs += d.year   as u64 * 60 * 60 * 24 * 31 * 365;
320//             Ok(Duration::new(secs, nanos))
321//         },
322//         Err(e) => Err(anyhow!("Couldn't parse XS duration {}: {:?}", s, e)),
323//     }
324// }
325
326
327
328// Deserialize an optional XML duration string to an Option<Duration>. This is a little trickier
329// than deserializing a required field with serde.
330fn deserialize_xs_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
331where
332    D: de::Deserializer<'de>,
333{
334    match <Option<String>>::deserialize(deserializer) {
335        Ok(optstring) => match optstring {
336            Some(xs) => match parse_xs_duration(&xs) {
337                Ok(d) => Ok(Some(d)),
338                Err(e) => Err(de::Error::custom(e)),
339            },
340            None => Ok(None),
341        },
342        // the field isn't present, return an Ok(None)
343        Err(_) => Ok(None),
344    }
345}
346
347// There are many possible correct ways of serializing a Duration in xs:duration (ISO 8601) format.
348// We choose to serialize to a perhaps-canonical xs:duration format including hours and minutes
349// (instead of representing them as a large number of seconds). Hour and minute count are not
350// included when the duration is less than a minute. Trailing zeros are omitted. Fractional seconds
351// are included to a nanosecond precision.
352//
353// Example: Duration::new(3600, 40_000_000) => "PT1H0M0.04S"
354fn serialize_xs_duration<S>(oxs: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
355where
356    S: Serializer,
357{
358    if let Some(xs) = oxs {
359        let total_secs = xs.as_secs();
360        let nanos = xs.subsec_nanos();
361        let hours = total_secs / 3600;
362        let mins = (total_secs % 3600) / 60;
363        let secs = total_secs % 60;
364        let frac = if nanos > 0 {
365            format!(".{nanos:09}").trim_end_matches('0').to_string()
366        } else {
367            String::new()
368        };
369        let s = match (hours, mins, secs, nanos) {
370            (h, 0, 0, 0) if h > 0 => format!("PT{h}H"),
371            (h, m, 0, 0) if h > 0 => format!("PT{h}H{m}M"),
372            (0, m, 0, 0) if m > 0 => format!("PT{m}M"),
373            (h, m, s, _) if h > 0 => format!("PT{h}H{m}M{s}{frac}S"),
374            (0, m, s, _) if m > 0 => format!("PT{m}M{s}{frac}S"),
375            _ => format!("PT{secs}{frac}S"),
376        };
377        serializer.serialize_str(&s)
378    } else {
379        // in fact this won't be called because of the #[skip_serializing_none] annotation
380        serializer.serialize_none()
381    }
382}
383
384
385// We can't use the parsing functionality from the chrono crate, because that assumes RFC 3339
386// format (including a timezone), whereas the xs:dateTime type (as per
387// <https://www.w3.org/TR/xmlschema-2/#dateTime>) allows the timezone to be omitted. For more on the
388// complicated relationship between ISO 8601 and RFC 3339, see
389// <https://ijmacd.github.io/rfc3339-iso8601/>.
390fn parse_xs_datetime(s: &str) -> Result<XsDatetime, DashMpdError> {
391    use iso8601::Date;
392    use chrono::{LocalResult, NaiveDate, TimeZone};
393    use num_traits::cast::FromPrimitive;
394    match DateTime::<chrono::offset::FixedOffset>::parse_from_rfc3339(s) {
395        Ok(dt) => Ok(dt.into()),
396        Err(_) => match iso8601::datetime(s) {
397            Ok(dt) => {
398                let nd = match dt.date {
399                    Date::YMD { year, month, day } =>
400                        NaiveDate::from_ymd_opt(year, month, day)
401                        .ok_or(DashMpdError::InvalidDateTime(s.to_string()))?,
402                    Date::Week { year, ww, d } => {
403                        let d = chrono::Weekday::from_u32(d)
404                            .ok_or(DashMpdError::InvalidDateTime(s.to_string()))?;
405                        NaiveDate::from_isoywd_opt(year, ww, d)
406                            .ok_or(DashMpdError::InvalidDateTime(s.to_string()))?
407                    },
408                    Date::Ordinal { year, ddd } =>
409                        NaiveDate::from_yo_opt(year, ddd)
410                        .ok_or(DashMpdError::InvalidDateTime(s.to_string()))?,
411                };
412                let nd = nd.and_hms_nano_opt(dt.time.hour, dt.time.minute, dt.time.second, dt.time.millisecond*1000*1000)
413                    .ok_or(DashMpdError::InvalidDateTime(s.to_string()))?;
414                let tz_secs = dt.time.tz_offset_hours * 3600 + dt.time.tz_offset_minutes * 60;
415                match chrono::FixedOffset::east_opt(tz_secs)
416                    .ok_or(DashMpdError::InvalidDateTime(s.to_string()))?
417                    .from_local_datetime(&nd)
418                {
419                    LocalResult::Single(local) => Ok(local.with_timezone(&chrono::Utc)),
420                    _ => Err(DashMpdError::InvalidDateTime(s.to_string())),
421                }
422            },
423            Err(_) => Err(DashMpdError::InvalidDateTime(s.to_string())),
424        }
425    }
426}
427
428// Deserialize an optional XML datetime string (type xs:datetime) to an Option<XsDatetime>.
429fn deserialize_xs_datetime<'de, D>(deserializer: D) -> Result<Option<XsDatetime>, D::Error>
430where
431    D: de::Deserializer<'de>,
432{
433    match <Option<String>>::deserialize(deserializer) {
434        Ok(optstring) => match optstring {
435            Some(xs) => match parse_xs_datetime(&xs) {
436                Ok(d) => Ok(Some(d)),
437                Err(e) => Err(de::Error::custom(e)),
438            },
439            None => Ok(None),
440        },
441        // the field isn't present; return an Ok(None)
442        Err(_) => Ok(None),
443    }
444}
445
446// XSD type is "UIntVectorType", or whitespace-separated list of unsigned integers.
447// It's a <xs:list itemType="xs:unsignedInt"/>.
448fn serialize_xsd_uintvector<S>(v: &Vec<u64>, serializer: S) -> Result<S::Ok, S::Error>
449where
450    S: Serializer,
451{
452    let mut formatted = String::new();
453    for u in v {
454        formatted += &format!("{u} ");
455    }
456    serializer.serialize_str(&formatted)
457}
458
459fn deserialize_xsd_uintvector<'de, D>(deserializer: D) -> Result<Vec<u64>, D::Error>
460where
461    D: de::Deserializer<'de>,
462{
463    let s = String::deserialize(deserializer)?;
464    let mut out = Vec::<u64>::new();
465    for uint64_str in s.split_whitespace() {
466        match uint64_str.parse::<u64>() {
467            Ok(val) => out.push(val),
468            Err(e) => return Err(de::Error::custom(e)),
469        }
470    }
471    Ok(out)
472}
473
474// These serialization functions are need to serialize correct default values for various optional
475// namespaces specified as attributes of the root MPD struct (e.g. xmlns:xsi, xmlns:xlink). If a
476// value is present in the struct field (specified in the parsed XML or provided explicitly when
477// building the MPD struct) then we use that, and otherwise default to the well-known URLs for these
478// namespaces.
479//
480// The quick-xml support for #[serde(default = "fn")] (which would allow a less heavyweight solution
481// to this) does not seem to work.
482
483fn serialize_xmlns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
484where S: serde::Serializer {
485    if let Some(s) = os {
486        serializer.serialize_str(s)
487    } else {
488        serializer.serialize_str("urn:mpeg:dash:schema:mpd:2011")
489    }
490}
491
492fn serialize_xsi_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
493where S: serde::Serializer {
494    if let Some(s) = os {
495        serializer.serialize_str(s)
496    } else {
497        serializer.serialize_str("http://www.w3.org/2001/XMLSchema-instance")
498    }
499}
500
501fn serialize_cenc_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
502where S: serde::Serializer {
503    if let Some(s) = os {
504        serializer.serialize_str(s)
505    } else {
506        serializer.serialize_str("urn:mpeg:cenc:2013")
507    }
508}
509
510fn serialize_mspr_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
511where S: serde::Serializer {
512    if let Some(s) = os {
513        serializer.serialize_str(s)
514    } else {
515        serializer.serialize_str("urn:microsoft:playready")
516    }
517}
518
519fn serialize_xlink_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
520where S: serde::Serializer {
521    if let Some(s) = os {
522        serializer.serialize_str(s)
523    } else {
524        serializer.serialize_str("http://www.w3.org/1999/xlink")
525    }
526}
527
528fn serialize_dvb_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
529where S: serde::Serializer {
530    if let Some(s) = os {
531        serializer.serialize_str(s)
532    } else {
533        serializer.serialize_str("urn:dvb:dash-extensions:2014-1")
534    }
535}
536
537
538// These default_* functions are needed to provide defaults for serde deserialization of certain
539// elements, where the Default function for that type doesn't return a value compatible with the
540// default specified in the XSD specification.
541fn default_optstring_on_request() -> Option<String> {
542    Some("onRequest".to_string())
543}
544
545fn default_optstring_one() -> Option<String> {
546    Some(String::from("1"))
547}
548
549fn default_optstring_encoder() -> Option<String> {
550    Some(String::from("encoder"))
551}
552
553fn default_optstring_any() -> Option<String> {
554    Some(String::from("any"))
555}
556
557fn default_optstring_query() -> Option<String> {
558    Some(String::from("query"))
559}
560
561fn default_optstring_segment() -> Option<String> {
562    Some(String::from("segment"))
563}
564
565fn default_optbool_true() -> Option<bool> {
566    Some(true)
567}
568
569fn default_optbool_false() -> Option<bool> {
570    Some(false)
571}
572
573fn default_optu64_zero() -> Option<u64> {
574    Some(0)
575}
576
577fn default_optu64_one() -> Option<u64> {
578    Some(1)
579}
580
581
582// The MPD format is documented by ISO using an XML Schema at
583// https://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD-edition2.xsd
584// Historical spec: https://ptabdata.blob.core.windows.net/files/2020/IPR2020-01688/v67_EXHIBIT%201067%20-%20ISO-IEC%2023009-1%202019(E)%20-%20Info.%20Tech.%20-%20Dynamic%20Adaptive%20Streaming%20Over%20HTTP%20(DASH).pdf
585// We occasionally diverge from the standard when in-the-wild implementations do.
586// Some reference code for DASH is at https://github.com/bitmovin/libdash
587//
588// We are using the quick_xml + serde crates to deserialize the XML content to Rust structs, and the
589// reverse serialization process of programmatically generating XML from Rust structs. Note that
590// serde will ignore unknown fields when deserializing, so we don't need to cover every single
591// possible field.
592
593/// The title of the media stream.
594#[skip_serializing_none]
595#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
596#[serde(default)]
597pub struct Title {
598    #[serde(rename = "$text")]
599    pub content: Option<String>,
600}
601
602/// The original source of the media stream.
603#[skip_serializing_none]
604#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
605#[serde(default)]
606pub struct Source {
607    #[serde(rename = "$text")]
608    pub content: Option<String>,
609}
610
611/// Copyright information concerning the media stream.
612#[skip_serializing_none]
613#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
614#[serde(default)]
615pub struct Copyright {
616    #[serde(rename = "$text")]
617    pub content: Option<String>,
618}
619
620/// Metainformation concerning the media stream (title, language, etc.)
621#[skip_serializing_none]
622#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
623#[serde(default)]
624pub struct ProgramInformation {
625    /// Language in RFC 5646 format
626    #[serde(rename = "@lang")]
627    pub lang: Option<String>,
628    #[serde(rename = "@moreInformationURL")]
629    pub moreInformationURL: Option<String>,
630    pub Title: Option<Title>,
631    pub Source: Option<Source>,
632    pub Copyright: Option<Copyright>,
633    #[serde(rename(serialize = "scte214:ContentIdentifier", deserialize = "ContentIdentifier"))]
634    pub scte214ContentIdentifier: Option<Scte214ContentIdentifier>,
635}
636
637/// DASH specification MPEG extension (SCTE 214) program identification type.
638///
639/// Indicates how the program content is identified.
640#[skip_serializing_none]
641#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
642#[serde(default)]
643pub struct Scte214ContentIdentifier {
644    #[serde(rename = "@type")]
645    pub idType: Option<String>,
646    #[serde(rename = "@value")]
647    pub idValue: Option<String>,
648}
649
650/// Describes a sequence of contiguous Segments with identical duration.
651#[skip_serializing_none]
652#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
653#[serde(default)]
654pub struct S {
655    /// Time
656    #[serde(rename = "@t")]
657    pub t: Option<u64>,
658    #[serde(rename = "@n")]
659    pub n: Option<u64>,
660    /// The duration (shall not exceed the value of MPD@maxSegmentDuration).
661    #[serde(rename = "@d")]
662    pub d: u64,
663    /// The repeat count (number of contiguous Segments with identical MPD duration minus one),
664    /// defaulting to zero if not present.
665    #[serde(rename = "@r")]
666    pub r: Option<i64>,
667    #[serde(rename = "@k")]
668    pub k: Option<u64>,
669}
670
671/// Contains a sequence of `S` elements, each of which describes a sequence of contiguous segments of
672/// identical duration.
673#[skip_serializing_none]
674#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
675#[serde(default)]
676pub struct SegmentTimeline {
677    /// There must be at least one S element.
678    #[serde(rename = "S")]
679    pub segments: Vec<S>,
680}
681
682/// Information on the bitstream switching capabilities for Representations.
683///
684/// When bitstream switching is enabled, the player can seamlessly switch between Representations in
685/// the manifest without reinitializing the media decoder. This means fewer perturbations for the
686/// viewer when the network conditions change. It requires the media segments to have been encoded
687/// respecting a certain number of constraints.
688#[skip_serializing_none]
689#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
690#[serde(default)]
691pub struct BitstreamSwitching {
692    #[serde(rename = "@sourceURL")]
693    pub source_url: Option<String>,
694    #[serde(rename = "@range")]
695    pub range: Option<String>,
696}
697
698/// The first media segment in a sequence of Segments.
699///
700/// Subsequent segments can be concatenated to this segment to produce a media stream.
701#[skip_serializing_none]
702#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
703#[serde(default)]
704pub struct Initialization {
705    #[serde(rename = "@sourceURL")]
706    pub sourceURL: Option<String>,
707    #[serde(rename = "@range")]
708    pub range: Option<String>,
709}
710
711#[skip_serializing_none]
712#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
713#[serde(default)]
714pub struct RepresentationIndex {
715    #[serde(rename = "@range")]
716    pub range: Option<String>,
717    #[serde(rename = "@sourceURL")]
718    pub sourceURL: Option<String>,
719}
720
721/// Allows template-based `SegmentURL` construction. Specifies various substitution rules using
722/// dynamic values such as `$Time$` and `$Number$` that map to a sequence of Segments.
723#[skip_serializing_none]
724#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
725#[serde(default)]
726pub struct SegmentTemplate {
727    #[serde(rename = "@media")]
728    pub media: Option<String>,
729    #[serde(rename = "@index")]
730    pub index: Option<String>,
731    #[serde(rename = "@initialization")]
732    pub initialization: Option<String>,
733    #[serde(rename = "@bitstreamSwitching")]
734    pub bitstreamSwitching: Option<String>,
735    #[serde(rename = "@indexRange")]
736    pub indexRange: Option<String>,
737    #[serde(rename = "@indexRangeExact")]
738    pub indexRangeExact: Option<bool>,
739    #[serde(rename = "@startNumber")]
740    pub startNumber: Option<u64>,
741    // note: the spec says this is an unsigned int, not an xs:duration. In practice, some manifests
742    // use a floating point value (eg.
743    // https://dash.akamaized.net/akamai/bbb_30fps/bbb_with_multiple_tiled_thumbnails.mpd)
744    #[serde(rename = "@duration")]
745    pub duration: Option<f64>,
746    #[serde(rename = "@timescale")]
747    pub timescale: Option<u64>,
748    /// Indicates a possible offset between media segment start/end points and period start/end points.
749    #[serde(rename = "@eptDelta")]
750    pub eptDelta: Option<i64>,
751    /// Specifies the difference between the presentation duration of this Representation and the
752    /// Period duration. Expressed in units of @timescale.
753    #[serde(rename = "@pdDelta")]
754    pub pbDelta: Option<i64>,
755    #[serde(rename = "@presentationTimeOffset")]
756    pub presentationTimeOffset: Option<u64>,
757    #[serde(rename = "@availabilityTimeOffset", serialize_with="serialize_opt_xsd_double")]
758    pub availabilityTimeOffset: Option<f64>,
759    #[serde(rename = "@availabilityTimeComplete")]
760    pub availabilityTimeComplete: Option<bool>,
761    pub Initialization: Option<Initialization>,
762    #[serde(rename = "RepresentationIndex")]
763    pub representation_index: Option<RepresentationIndex>,
764    // The XSD included in the DASH specification only includes a FailoverContent element on the
765    // SegmentBase element, but also includes it on a SegmentTemplate element in one of the
766    // examples. Even if examples are not normative, we choose to be tolerant in parsing.
767    #[serde(rename = "FailoverContent")]
768    pub failover_content: Option<FailoverContent>,
769    pub SegmentTimeline: Option<SegmentTimeline>,
770    pub BitstreamSwitching: Option<BitstreamSwitching>,
771}
772
773/// A URI string to which a new request for an updated manifest should be made.
774///
775/// This feature is intended for servers and clients that can't use sticky HTTP redirects.
776#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
777#[serde(default)]
778pub struct Location {
779    #[serde(rename = "$text")]
780    pub url: String,
781}
782
783/// A URI string that specifies one or more common locations for Segments and other resources.
784///
785/// Used as a prefix for SegmentURLs. Can be specified at the level of the MPD node, or Period,
786/// AdaptationSet, Representation, and can be nested (the client should combine the prefix on MPD
787/// and on Representation, for example).
788#[skip_serializing_none]
789#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
790#[serde(default)]
791pub struct BaseURL {
792    #[serde(rename = "@serviceLocation")]
793    pub serviceLocation: Option<String>,
794    #[serde(rename = "@byteRange")]
795    pub byte_range: Option<String>,
796    /// Elements with the same `@serviceLocation` value are likely to have their URLs resolve to
797    /// services at a common network location, for example the same CDN.
798    #[serde(rename = "@availabilityTimeOffset", serialize_with="serialize_opt_xsd_double")]
799    pub availability_time_offset: Option<f64>,
800    #[serde(rename = "@availabilityTimeComplete")]
801    pub availability_time_complete: Option<bool>,
802    #[serde(rename = "@timeShiftBufferDepth",
803            serialize_with = "serialize_xs_duration",
804            deserialize_with = "deserialize_xs_duration",
805            default)]
806    pub timeShiftBufferDepth: Option<Duration>,
807    /// Lowest value indicates the highest priority.
808    #[serde(rename = "@dvb:priority", alias = "@priority")]
809    pub priority: Option<u64>,
810    /// For load balancing between different base urls with the same @priority. The BaseURL to use
811    /// is chosen at random by the player, with the weight of any given BaseURL being its @weight
812    /// value divided by the sum of all @weight values.
813    #[serde(rename = "@dvb:weight", alias = "@weight")]
814    pub weight: Option<i64>,
815    #[serde(rename = "$text")]
816    pub base: String,
817}
818
819/// Failover Content Segment (FCS).
820///
821/// The time and optional duration for which a representation does not represent the main content
822/// but a failover version. It can and is also used to represent gaps where no segments are present
823/// at all - used within the `FailoverContent` element.
824#[skip_serializing_none]
825#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
826#[serde(default)]
827pub struct Fcs {
828    /// The time at which no/failover segments for this representation starts (if the valid
829    /// flag is set to `true` in `FailoverContent`).
830    #[serde(rename = "@t")]
831    pub t: u64,
832
833    /// The optional duration for which there is failover or no content.  If `None` then
834    /// the duration is for the remainder of the `Period` the parent `Representation` is in.
835    #[serde(rename = "@d")]
836    pub d: Option<u64>,
837}
838
839/// Period of time for which either failover content or no content/segments exist for the
840/// parent `Representation`.
841#[skip_serializing_none]
842#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
843#[serde(default)]
844pub struct FailoverContent {
845    // If true, the FCS represents failover content; if false, it represents a gap
846    // where there are no segments at all.
847    #[serde(rename = "@valid")]
848    pub valid: Option<bool>,
849    #[serde(rename = "FCS")]
850    pub fcs_list: Vec<Fcs>,
851}
852
853/// Specifies some common information concerning media segments.
854#[skip_serializing_none]
855#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
856#[serde(default)]
857pub struct SegmentBase {
858    #[serde(rename = "@timescale")]
859    pub timescale: Option<u64>,
860    #[serde(rename = "@presentationTimeOffset")]
861    pub presentationTimeOffset: Option<u64>,
862    #[serde(rename = "@indexRange")]
863    pub indexRange: Option<String>,
864    #[serde(rename = "@indexRangeExact")]
865    pub indexRangeExact: Option<bool>,
866    #[serde(rename = "@availabilityTimeOffset", serialize_with="serialize_opt_xsd_double")]
867    pub availabilityTimeOffset: Option<f64>,
868    #[serde(rename = "@availabilityTimeComplete")]
869    pub availabilityTimeComplete: Option<bool>,
870    #[serde(rename = "@presentationDuration")]
871    pub presentationDuration: Option<u64>,
872    /// Indicates a possible offset between media segment start/end points and period start/end points.
873    #[serde(rename = "@eptDelta")]
874    pub eptDelta: Option<i64>,
875    /// Specifies the difference between the presentation duration of this Representation and the
876    /// Period duration. Expressed in units of @timescale.
877    #[serde(rename = "@pdDelta")]
878    pub pbDelta: Option<i64>,
879    pub Initialization: Option<Initialization>,
880    #[serde(rename = "RepresentationIndex")]
881    pub representation_index: Option<RepresentationIndex>,
882    #[serde(rename = "FailoverContent")]
883    pub failover_content: Option<FailoverContent>,
884}
885
886/// The URL of a media segment.
887#[skip_serializing_none]
888#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
889#[serde(default)]
890pub struct SegmentURL {
891    #[serde(rename = "@media")]
892    pub media: Option<String>, // actually an URI
893    #[serde(rename = "@mediaRange")]
894    pub mediaRange: Option<String>,
895    #[serde(rename = "@index")]
896    pub index: Option<String>, // actually an URI
897    #[serde(rename = "@indexRange")]
898    pub indexRange: Option<String>,
899}
900
901/// Contains a sequence of SegmentURL elements.
902#[skip_serializing_none]
903#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
904#[serde(default)]
905pub struct SegmentList {
906    // note: the spec says this is an unsigned int, not an xs:duration
907    #[serde(rename = "@duration")]
908    pub duration: Option<u64>,
909    #[serde(rename = "@timescale")]
910    pub timescale: Option<u64>,
911    #[serde(rename = "@indexRange")]
912    pub indexRange: Option<String>,
913    #[serde(rename = "@indexRangeExact")]
914    pub indexRangeExact: Option<bool>,
915    /// A "remote resource", following the XML Linking Language (XLink) specification.
916    #[serde(rename = "@xlink:href", alias = "@href")]
917    pub href: Option<String>,
918    #[serde(rename = "@xlink:actuate", alias = "@actuate", default="default_optstring_on_request")]
919    pub actuate: Option<String>,
920    #[serde(rename = "@xlink:type", alias = "@type")]
921    pub sltype: Option<String>,
922    #[serde(rename = "@xlink:show", alias = "@show")]
923    pub show: Option<String>,
924    pub Initialization: Option<Initialization>,
925    pub SegmentTimeline: Option<SegmentTimeline>,
926    pub BitstreamSwitching: Option<BitstreamSwitching>,
927    #[serde(rename = "SegmentURL")]
928    pub segment_urls: Vec<SegmentURL>,
929}
930
931#[skip_serializing_none]
932#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
933#[serde(default)]
934pub struct Resync {
935    #[serde(rename = "@type")]
936    pub rtype: Option<String>,
937    #[serde(rename = "@dT")]
938    pub dT: Option<u64>,
939    #[serde(rename = "@dImax")]
940    pub dImax: Option<f64>,
941    #[serde(rename = "@dImin")]
942    pub dImin: Option<f64>,
943    #[serde(rename = "@marker")]
944    pub marker: Option<bool>,
945}
946
947/// Specifies information concerning the audio channel (e.g. stereo, multichannel).
948#[skip_serializing_none]
949#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
950#[serde(default)]
951pub struct AudioChannelConfiguration {
952    #[serde(rename = "@schemeIdUri")]
953    pub schemeIdUri: String,
954    #[serde(rename = "@value")]
955    pub value: Option<String>,
956    #[serde(rename = "@id")]
957    pub id: Option<String>,
958}
959
960// This element is not specified in ISO/IEC 23009-1:2022; exact format is unclear.
961#[skip_serializing_none]
962#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
963#[serde(default)]
964pub struct Language {
965    #[serde(rename = "$text")]
966    pub content: Option<String>,
967}
968
969/// A Preselection is a personalization option to produce a “complete audio experience”.
970///
971/// Used for audio signaling in the context of the ATSC 3.0 standard for advanced IP-based
972/// television broadcasting. Details are specified by the “DASH-IF Interoperability Point for ATSC
973/// 3.0” document.
974#[skip_serializing_none]
975#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
976#[serde(default)]
977pub struct Preselection {
978    #[serde(rename = "@id", default = "default_optstring_one")]
979    pub id: Option<String>,
980    /// Specifies the ids of the contained elements/content components of this Preselection list as
981    /// white space separated list in processing order. The first id defines the main element.
982    #[serde(rename = "@preselectionComponents")]
983    pub preselectionComponents: String,
984    #[serde(rename = "@lang")]
985    pub lang: Option<String>,
986    #[serde(rename = "@audioSamplingRate")]
987    pub audioSamplingRate: Option<String>,
988    /// An RFC6381 string, <https://tools.ietf.org/html/rfc6381>
989    #[serde(rename = "@codecs")]
990    pub codecs: String,
991    #[serde(rename = "@selectionPriority")]
992    pub selectionPriority: Option<u64>,
993    #[serde(rename = "@tag")]
994    pub tag: String,
995    pub FramePacking: Vec<FramePacking>,
996    pub AudioChannelConfiguration: Vec<AudioChannelConfiguration>,
997    pub ContentProtection: Vec<ContentProtection>,
998    pub OutputProtection: Option<OutputProtection>,
999    #[serde(rename = "EssentialProperty")]
1000    pub essential_property: Vec<EssentialProperty>,
1001    #[serde(rename = "SupplementalProperty")]
1002    pub supplemental_property: Vec<SupplementalProperty>,
1003    pub InbandEventStream: Vec<InbandEventStream>,
1004    pub Switching: Vec<Switching>,
1005    // TODO: missing RandomAccess element
1006    #[serde(rename = "GroupLabel")]
1007    pub group_label: Vec<Label>,
1008    pub Label: Vec<Label>,
1009    pub ProducerReferenceTime: Option<ProducerReferenceTime>,
1010    // TODO: missing ContentPopularityRate element
1011    pub Resync: Option<Resync>,
1012    #[serde(rename = "Accessibility")]
1013    pub accessibilities: Vec<Accessibility>,
1014    #[serde(rename = "Role")]
1015    pub roles: Vec<Role>,
1016    #[serde(rename = "Rating")]
1017    pub ratings: Vec<Rating>,
1018    #[serde(rename = "Viewpoint")]
1019    pub viewpoints: Vec<Viewpoint>,
1020    // end PreselectionType specific elements
1021    #[serde(rename = "Language")]
1022    pub languages: Vec<Language>,
1023}
1024
1025/// Specifies that content is suitable for presentation to audiences for which that rating is known to be
1026/// appropriate, or for unrestricted audiences.
1027#[skip_serializing_none]
1028#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1029#[serde(default)]
1030pub struct Rating {
1031    #[serde(rename = "@id")]
1032    pub id: Option<String>,
1033    #[serde(rename = "@schemeIdUri")]
1034    pub schemeIdUri: String,
1035    #[serde(rename = "@value")]
1036    pub value: Option<String>,
1037}
1038
1039/// Specifies frame-packing arrangement information of the video media component type.
1040#[skip_serializing_none]
1041#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1042#[serde(default)]
1043pub struct FramePacking {
1044    #[serde(rename = "@id")]
1045    pub id: Option<String>,
1046    #[serde(rename = "@schemeIdUri")]
1047    pub schemeIdUri: String,
1048    #[serde(rename = "@value")]
1049    pub value: Option<String>,
1050}
1051
1052/// Information used to allow Adaptation Set Switching (for instance, allowing the player to switch
1053/// between camera angles).
1054///
1055/// This is different from "bitstream switching".
1056#[skip_serializing_none]
1057#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1058#[serde(default)]
1059pub struct Switching {
1060    #[serde(rename = "@interval")]
1061    pub interval: Option<u64>,
1062    /// Valid values are "media" and "bitstream".
1063    #[serde(rename = "@type")]
1064    pub stype: Option<String>,
1065}
1066
1067/// Specifies the accessibility scheme used by the media content.
1068#[skip_serializing_none]
1069#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1070#[serde(default)]
1071pub struct Accessibility {
1072    #[serde(rename = "@schemeIdUri")]
1073    pub schemeIdUri: String,
1074    #[serde(rename = "@value")]
1075    pub value: Option<String>,
1076    #[serde(rename = "@id")]
1077    pub id: Option<String>,
1078}
1079
1080/// Scope of a namespace.
1081#[skip_serializing_none]
1082#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1083#[serde(default)]
1084pub struct Scope {
1085    #[serde(rename = "@schemeIdUri")]
1086    pub schemeIdUri: String,
1087    #[serde(rename = "@value")]
1088    pub value: Option<String>,
1089    #[serde(rename = "@id")]
1090    pub id: Option<String>,
1091}
1092
1093/// A SubRepresentation contains information that only applies to one media stream in a Representation.
1094#[skip_serializing_none]
1095#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1096#[serde(default)]
1097pub struct SubRepresentation {
1098    #[serde(rename = "@level")]
1099    pub level: Option<u32>,
1100    #[serde(rename = "@dependencyLevel")]
1101    pub dependencyLevel: Option<String>,
1102    /// If present, a whitespace-separated list of values of ContentComponent@id values.
1103    #[serde(rename = "@contentComponent")]
1104    pub contentComponent: Option<String>,
1105    #[serde(rename = "@mimeType")]
1106    pub mimeType: Option<String>,
1107    /// An RFC6381 string, <https://tools.ietf.org/html/rfc6381>
1108    #[serde(rename = "@codecs")]
1109    pub codecs: Option<String>,
1110    #[serde(rename = "@contentType")]
1111    pub contentType: Option<String>,
1112    #[serde(rename = "@profiles")]
1113    pub profiles: Option<String>,
1114    #[serde(rename = "@segmentProfiles")]
1115    /// Specifies the profiles of Segments that are essential to process the Representation. The
1116    /// semantics depend on the value of the @mimeType attribute.
1117    pub segmentProfiles: Option<String>,
1118    /// If present, this attribute is expected to be set to "progressive".
1119    #[serde(rename = "@scanType")]
1120    pub scanType: Option<String>,
1121    #[serde(rename = "@frameRate")]
1122    pub frameRate: Option<String>, // can be something like "15/2"
1123    /// The Sample Aspect Ratio, eg. "1:1"
1124    #[serde(rename = "@sar")]
1125    pub sar: Option<String>,
1126    /// The average bandwidth of the Representation.
1127    #[serde(rename = "@bandwidth")]
1128    pub bandwidth: Option<u64>,
1129    #[serde(rename = "@audioSamplingRate")]
1130    pub audioSamplingRate: Option<String>,
1131    /// Indicates the possibility for accelerated playout allowed by this codec profile and level.
1132    #[serde(rename = "@maxPlayoutRate", serialize_with="serialize_opt_xsd_double")]
1133    pub maxPlayoutRate: Option<f64>,
1134    #[serde(rename = "@codingDependency")]
1135    pub codingDependency: Option<bool>,
1136    #[serde(rename = "@width")]
1137    pub width: Option<u64>,
1138    #[serde(rename = "@height")]
1139    pub height: Option<u64>,
1140    #[serde(rename = "@startWithSAP")]
1141    pub startWithSAP: Option<u64>,
1142    #[serde(rename = "@maximumSAPPeriod", serialize_with="serialize_opt_xsd_double")]
1143    pub maximumSAPPeriod: Option<f64>,
1144    pub FramePacking: Vec<FramePacking>,
1145    pub AudioChannelConfiguration: Vec<AudioChannelConfiguration>,
1146    pub ContentProtection: Vec<ContentProtection>,
1147    pub OutputProtection: Option<OutputProtection>,
1148    #[serde(rename = "EssentialProperty")]
1149    pub essential_property: Vec<EssentialProperty>,
1150    #[serde(rename = "SupplementalProperty")]
1151    pub supplemental_property: Vec<SupplementalProperty>,
1152    pub InbandEventStream: Vec<InbandEventStream>,
1153    pub Switching: Vec<Switching>,
1154    // TODO: missing RandomAccess element
1155    #[serde(rename = "GroupLabel")]
1156    pub group_label: Vec<Label>,
1157    pub Label: Vec<Label>,
1158    pub ProducerReferenceTime: Option<ProducerReferenceTime>,
1159    // TODO: missing ContentPopularityRate element
1160    pub Resync: Option<Resync>,
1161}
1162
1163/// A Representation describes a version of the content, using a specific encoding and bitrate.
1164///
1165/// Streams often have multiple representations with different bitrates, to allow the client to
1166/// select that most suitable to its network conditions (adaptive bitrate or ABR streaming).
1167#[skip_serializing_none]
1168#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1169#[serde(default)]
1170pub struct Representation {
1171    // no id for a linked Representation (with xlink:href), so this attribute is optional
1172    #[serde(rename = "@id")]
1173    pub id: Option<String>,
1174    /// The average bandwidth of the Representation.
1175    #[serde(rename = "@bandwidth")]
1176    pub bandwidth: Option<u64>,
1177    /// Specifies a quality ranking of this Representation relative to others in the same
1178    /// AdaptationSet. Lower values represent higher quality content. If not present, then no
1179    /// ranking is defined.
1180    #[serde(rename = "@qualityRanking")]
1181    pub qualityRanking: Option<u8>,
1182    /// Identifies the base layer representation of this enhancement layer representation.
1183    /// Separation between a base layer and a number of enhancement layers is used by certain
1184    /// content encoding mechanisms, such as HEVC Scalable and Dolby Vision.
1185    #[serde(rename = "@dependencyId")]
1186    pub dependencyId: Option<String>,
1187    #[serde(rename = "@associationId")]
1188    pub associationId: Option<String>,
1189    #[serde(rename = "@associationType")]
1190    pub associationType: Option<String>,
1191    #[serde(rename = "@mediaStreamStructureId")]
1192    pub mediaStreamStructureId: Option<String>,
1193    #[serde(rename = "@profiles")]
1194    pub profiles: Option<String>,
1195    #[serde(rename = "@width")]
1196    pub width: Option<u64>,
1197    #[serde(rename = "@height")]
1198    pub height: Option<u64>,
1199    /// The Sample Aspect Ratio, eg. "1:1".
1200    #[serde(rename = "@sar")]
1201    pub sar: Option<String>,
1202    #[serde(rename = "@frameRate")]
1203    pub frameRate: Option<String>, // can be something like "15/2"
1204    #[serde(rename = "@audioSamplingRate")]
1205    pub audioSamplingRate: Option<String>,
1206    // The specification says that @mimeType is mandatory, but it's not always present on
1207    // akamaized.net MPDs
1208    #[serde(rename = "@mimeType")]
1209    pub mimeType: Option<String>,
1210    /// Specifies the profiles of Segments that are essential to process the Representation. The
1211    /// semantics depend on the value of the @mimeType attribute.
1212    #[serde(rename = "@segmentProfiles")]
1213    pub segmentProfiles: Option<String>,
1214    /// A "remote resource", following the XML Linking Language (XLink) specification.
1215    /// An RFC6381 string, <https://tools.ietf.org/html/rfc6381>
1216    #[serde(rename = "@codecs")]
1217    pub codecs: Option<String>,
1218    #[serde(rename = "@containerProfiles")]
1219    pub containerProfiles: Option<String>,
1220    #[serde(rename = "@maximumSAPPeriod")]
1221    pub maximumSAPPeriod: Option<f64>,
1222    #[serde(rename = "@startWithSAP")]
1223    pub startWithSAP: Option<u64>,
1224    /// Indicates the possibility for accelerated playout allowed by this codec profile and level.
1225    #[serde(rename = "@maxPlayoutRate", serialize_with="serialize_opt_xsd_double")]
1226    pub maxPlayoutRate: Option<f64>,
1227    #[serde(rename = "@codingDependency")]
1228    pub codingDependency: Option<bool>,
1229    /// If present, this attribute is expected to be set to "progressive".
1230    #[serde(rename = "@scanType")]
1231    pub scanType: Option<String>,
1232    #[serde(rename = "@selectionPriority")]
1233    pub selectionPriority: Option<u64>,
1234    #[serde(rename = "@tag")]
1235    pub tag: Option<String>,
1236    #[serde(rename = "@contentType")]
1237    pub contentType: Option<String>,
1238    /// Language in RFC 5646 format.
1239    #[serde(rename = "@lang")]
1240    pub lang: Option<String>,
1241    #[serde(rename = "@sampleRate")]
1242    pub sampleRate: Option<u64>,
1243    #[serde(rename = "@numChannels")]
1244    pub numChannels: Option<u32>,
1245    #[serde(rename = "@xlink:href", alias = "@href")]
1246    pub href: Option<String>,
1247    #[serde(rename = "@xlink:actuate", alias = "@actuate", default = "default_optstring_on_request")]
1248    pub actuate: Option<String>,
1249    #[serde(rename = "@scte214:supplementalProfiles", alias = "@supplementalProfiles")]
1250    pub scte214_supplemental_profiles: Option<String>,
1251    #[serde(rename = "@scte214:supplementalCodecs", alias = "@supplementalCodecs")]
1252    pub scte214_supplemental_codecs: Option<String>,
1253    pub FramePacking: Vec<FramePacking>,
1254    pub AudioChannelConfiguration: Vec<AudioChannelConfiguration>,
1255    pub ContentProtection: Vec<ContentProtection>,
1256    pub OutputProtection: Option<OutputProtection>,
1257    #[serde(rename = "EssentialProperty")]
1258    pub essential_property: Vec<EssentialProperty>,
1259    #[serde(rename = "SupplementalProperty")]
1260    pub supplemental_property: Vec<SupplementalProperty>,
1261    pub InbandEventStream: Vec<InbandEventStream>,
1262    pub Switching: Vec<Switching>,
1263    // TODO: missing RandomAccess element
1264    #[serde(rename = "GroupLabel")]
1265    pub group_label: Vec<Label>,
1266    pub Label: Vec<Label>,
1267    pub ProducerReferenceTime: Vec<ProducerReferenceTime>,
1268    // TODO: missing ContentPopularityRate element
1269    pub Resync: Vec<Resync>,
1270    pub BaseURL: Vec<BaseURL>,
1271    // TODO: missing ExtendedBandwidth element
1272    pub SubRepresentation: Vec<SubRepresentation>,
1273    pub SegmentBase: Option<SegmentBase>,
1274    pub SegmentList: Option<SegmentList>,
1275    pub SegmentTemplate: Option<SegmentTemplate>,
1276    #[serde(rename = "RepresentationIndex")]
1277    pub representation_index: Option<RepresentationIndex>,
1278}
1279
1280/// Describes a media content component.
1281#[skip_serializing_none]
1282#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1283#[serde(default)]
1284pub struct ContentComponent {
1285    #[serde(rename = "@id")]
1286    pub id: Option<String>,
1287    /// Language in RFC 5646 format (eg. "fr-FR", "en-AU").
1288    #[serde(rename = "@lang")]
1289    pub lang: Option<String>,
1290    #[serde(rename = "@contentType")]
1291    pub contentType: Option<String>,
1292    #[serde(rename = "@par")]
1293    pub par: Option<String>,
1294    #[serde(rename = "@tag")]
1295    pub tag: Option<String>,
1296    pub Accessibility: Vec<Accessibility>,
1297    pub Role: Vec<Role>,
1298    pub Rating: Vec<Rating>,
1299    pub Viewpoint: Vec<Viewpoint>,
1300}
1301
1302/// A Common Encryption "Protection System Specific Header" box. Content is typically base64 encoded.
1303#[skip_serializing_none]
1304#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1305#[serde(default)]
1306pub struct CencPssh {
1307    #[serde(rename = "$text")]
1308    pub content: Option<String>,
1309}
1310
1311/// Licence acquisition URL for content using Microsoft PlayReady DRM.
1312#[skip_serializing_none]
1313#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1314#[serde(default)]
1315pub struct Laurl {
1316    #[serde(rename = "@Lic_type")]
1317    pub lic_type: Option<String>,
1318    #[serde(rename = "$text")]
1319    pub content: Option<String>,
1320}
1321
1322/// Initialization data that is specific to the Microsoft PlayReady DRM.
1323#[skip_serializing_none]
1324#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1325#[serde(default)]
1326pub struct MsprPro {
1327    #[serde(rename = "@xmlns", serialize_with="serialize_xmlns")]
1328    pub xmlns: Option<String>,
1329    #[serde(rename = "$text")]
1330    pub content: Option<String>,
1331}
1332
1333#[skip_serializing_none]
1334#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1335#[serde(default)]
1336pub struct MsprIsEncrypted {
1337    #[serde(rename = "$text")]
1338    pub content: Option<String>,
1339}
1340
1341#[skip_serializing_none]
1342#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1343#[serde(default)]
1344pub struct MsprIVSize {
1345    #[serde(rename = "$text")]
1346    pub content: Option<String>,
1347}
1348
1349#[skip_serializing_none]
1350#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1351#[serde(default)]
1352pub struct MsprKid {
1353    #[serde(rename = "$text")]
1354    pub content: Option<String>,
1355}
1356
1357#[skip_serializing_none]
1358#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1359#[serde(default)]
1360pub struct OutputProtection {
1361    #[serde(rename = "@schemeIdUri")]
1362    pub schemeIdUri: String,
1363    #[serde(rename = "@value")]
1364    pub value: Option<String>,
1365    #[serde(rename = "@id")]
1366    pub id: Option<String>,
1367}
1368
1369/// Contains information on DRM (rights management / encryption) mechanisms used in the stream.
1370///
1371/// If this node is not present, no content protection (such as Widevine and Playready) is applied
1372/// by the source.
1373#[skip_serializing_none]
1374#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1375#[serde(default)]
1376pub struct ContentProtection {
1377    /// The robustness level required for this content protection scheme.
1378    #[serde(rename = "@robustness")]
1379    pub robustness: Option<String>,
1380    #[serde(rename = "@refId")]
1381    pub refId: Option<String>,
1382    /// An xs:IDREF that references an identifier in this MPD.
1383    #[serde(rename = "@ref")]
1384    pub r#ref: Option<String>,
1385    /// References an identifier in this MPD.
1386    #[serde(rename = "@schemeIdUri")]
1387    pub schemeIdUri: String,
1388    #[serde(rename = "@value")]
1389    pub value: Option<String>,
1390    #[serde(rename = "@id")]
1391    pub id: Option<String>,
1392    /// The DRM initialization data (Protection System Specific Header).
1393    #[serde(rename="cenc:pssh", alias="pssh")]
1394    pub cenc_pssh: Vec<CencPssh>,
1395    /// The DRM key identifier.
1396    #[serde(rename = "@cenc:default_KID", alias = "@default_KID")]
1397    pub default_KID: Option<String>,
1398    /// License acquisition URL.
1399    #[serde(rename = "dashif:laurl", alias = "laurl")]
1400    pub laurl: Option<Laurl>,
1401    /// License acquisition URL. The name clearkey:Laurl is obsolete and replaced by dashif:laurl.
1402    /// Some manifests in the wild include both, and the parser does not allow for duplicate fields,
1403    /// so we need to allow for this field using a distinct name.
1404    #[serde(rename = "clearkey:Laurl", alias = "Laurl")]
1405    pub clearkey_laurl: Option<Laurl>,
1406    /// Content specific to initialization data using Microsoft PlayReady DRM.
1407    #[serde(rename = "mspr:pro", alias = "pro")]
1408    pub msprpro: Option<MsprPro>,
1409    #[serde(rename = "mspr:IsEncrypted", alias = "IsEncrypted")]
1410    pub mspr_is_encrypted: Option<MsprIsEncrypted>,
1411    #[serde(rename = "mspr:IV_Size", alias = "IV_Size")]
1412    pub mspr_iv_size: Option<MsprIVSize>,
1413    #[serde(rename = "mspr:kid", alias = "kid")]
1414    pub mspr_kid: Option<MsprKid>,
1415}
1416
1417/// The Role specifies the purpose of this media stream (caption, subtitle, main content, etc.).
1418///
1419/// Possible values include "caption", "subtitle", "main", "alternate", "supplementary",
1420/// "commentary", and "dub" (this is the attribute scheme for @value when the schemeIdUri is
1421/// "urn:mpeg:dash:role:2011").
1422#[skip_serializing_none]
1423#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1424#[serde(default)]
1425pub struct Role {
1426    #[serde(rename = "@id")]
1427    pub id: Option<String>,
1428    #[serde(rename = "@schemeIdUri")]
1429    pub schemeIdUri: String,
1430    #[serde(rename = "@value")]
1431    pub value: Option<String>,
1432}
1433
1434#[skip_serializing_none]
1435#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1436#[serde(default)]
1437pub struct Viewpoint {
1438    #[serde(rename = "@id")]
1439    pub id: Option<String>,
1440    #[serde(rename = "@schemeIdUri")]
1441    pub schemeIdUri: String,
1442    #[serde(rename = "@value")]
1443    pub value: Option<String>,
1444}
1445
1446#[skip_serializing_none]
1447#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1448#[serde(default)]
1449pub struct Selection {
1450    #[serde(rename = "@dataEncoding")]
1451    pub dataEncoding: Option<String>,
1452    #[serde(rename = "@parameter")]
1453    pub parameter: Option<String>,
1454    #[serde(rename = "@data")]
1455    pub data: Option<String>,
1456}
1457
1458#[skip_serializing_none]
1459#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1460#[serde(default)]
1461pub struct SelectionInfo {
1462    #[serde(rename = "@selectionInfo")]
1463    pub selectionInfo: Option<String>,
1464    #[serde(rename = "@contactURL")]
1465    pub contactURL: Option<String>,
1466    pub Selection: Vec<Selection>,
1467}
1468
1469/// A mechanism allowing the server to send additional information to the DASH client which is
1470/// synchronized with the media stream.
1471///
1472/// DASH Events are Used for various purposes such as dynamic ad insertion, providing additional
1473/// metainformation concerning the actors or location at a point in the media stream, providing
1474/// parental guidance information, or sending custom data to the DASH player application.
1475#[skip_serializing_none]
1476#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1477#[serde(default)]
1478pub struct Event {
1479    #[serde(rename = "@id")]
1480    pub id: Option<String>,
1481    #[serde(rename = "@presentationTime", default = "default_optu64_zero")]
1482    pub presentationTime: Option<u64>,
1483    #[serde(rename = "@presentationTimeOffset")]
1484    pub presentationTimeOffset: Option<u64>,
1485    #[serde(rename = "@duration")]
1486    pub duration: Option<u64>,
1487    #[serde(rename = "@timescale")]
1488    pub timescale: Option<u64>,
1489    /// Possible encoding (e.g. "base64") for the Event content or the value of the @messageData
1490    /// attribute.
1491    #[serde(rename = "@contentEncoding")]
1492    pub contentEncoding: Option<String>,
1493    /// The value for this event stream element. This attribute is present for backward
1494    /// compatibility; message content should be included in the Event element instead.
1495    #[serde(rename = "@messageData")]
1496    pub messageData: Option<String>,
1497    pub SelectionInfo: Option<SelectionInfo>,
1498    #[cfg(feature = "scte35")]
1499    #[serde(rename = "scte35:Signal", alias="Signal")]
1500    #[cfg(feature = "scte35")]
1501    pub signal: Vec<Signal>,
1502    #[cfg(feature = "scte35")]
1503    #[serde(rename = "scte35:SpliceInfoSection", alias="SpliceInfoSection")]
1504    #[cfg(feature = "scte35")]
1505    pub splice_info_section: Vec<SpliceInfoSection>,
1506    // #[serde(rename = "@schemeIdUri")]
1507    // pub schemeIdUri: String,
1508    #[serde(rename = "@value")]
1509    pub value: Option<String>,
1510    // The content may be base64 encoded, but may also be text. See for example
1511    // https://refapp.hbbtv.org/videos/00_llama_multiperiod_v1/manifest.mpd
1512    #[serde(rename = "$text")]
1513    pub content: Option<String>,
1514}
1515
1516#[skip_serializing_none]
1517#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1518#[serde(default)]
1519pub struct EventStream {
1520    #[serde(rename = "@xlink:href")]
1521    #[serde(alias = "@href")]
1522    pub href: Option<String>,
1523    #[serde(rename = "@xlink:actuate", alias = "@actuate", default = "default_optstring_on_request")]
1524    pub actuate: Option<String>,
1525    #[serde(rename = "@messageData")]
1526    // actually an xs:anyURI
1527    pub messageData: Option<String>,
1528    #[serde(rename = "@schemeIdUri")]
1529    pub schemeIdUri: String,
1530    #[serde(rename = "@value")]
1531    pub value: Option<String>,
1532    #[serde(rename = "@timescale")]
1533    pub timescale: Option<u64>,
1534    #[serde(rename = "@presentationTimeOffset")]
1535    pub presentationTimeOffset: Option<u64>,
1536    #[serde(rename = "Event")]
1537    pub event: Vec<Event>,
1538}
1539
1540/// "Inband" events are materialized by the presence of DASHEventMessageBoxes (emsg) in the media
1541/// segments.
1542///
1543/// The client is informed of their presence by the inclusion of an InbandEventStream element in the
1544/// AdaptationSet or Representation element.
1545#[skip_serializing_none]
1546#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1547#[serde(default)]
1548pub struct InbandEventStream {
1549    #[serde(rename = "@timescale")]
1550    pub timescale: Option<u64>,
1551    #[serde(rename = "@schemeIdUri")]
1552    pub schemeIdUri: String,
1553    #[serde(rename = "Event")]
1554    pub event: Vec<Event>,
1555    #[serde(rename = "@value")]
1556    pub value: Option<String>,
1557    /// A "remote resource", following the XML Linking Language (XLink) specification.
1558    #[serde(rename = "@xlink:href")]
1559    #[serde(alias = "@href")]
1560    pub href: Option<String>,
1561    #[serde(rename = "@xlink:actuate", alias = "@actuate")]
1562    pub actuate: Option<String>,
1563}
1564
1565#[skip_serializing_none]
1566#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1567#[serde(default)]
1568pub struct EssentialProperty {
1569    #[serde(rename = "@id")]
1570    pub id: Option<String>,
1571    #[serde(rename = "@schemeIdUri")]
1572    pub schemeIdUri: String,
1573    #[serde(rename = "@value")]
1574    pub value: Option<String>,
1575}
1576
1577#[skip_serializing_none]
1578#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1579#[serde(default)]
1580pub struct SupplementalProperty {
1581    #[serde(rename = "@id")]
1582    pub id: Option<String>,
1583    #[serde(rename = "@schemeIdUri")]
1584    pub schemeIdUri: String,
1585    #[serde(rename = "@value")]
1586    pub value: Option<String>,
1587    #[serde(rename(serialize = "scte214:ContentIdentifier"))]
1588    #[serde(rename(deserialize = "ContentIdentifier"))]
1589    pub scte214ContentIdentifiers: Vec<Scte214ContentIdentifier>,
1590}
1591
1592/// Provides a textual description of the content, which can be used by the client to allow
1593/// selection of the desired media stream.
1594#[skip_serializing_none]
1595#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1596#[serde(default)]
1597pub struct Label {
1598    #[serde(rename = "@id")]
1599    pub id: Option<String>,
1600    #[serde(rename = "@lang")]
1601    pub lang: Option<String>,
1602    #[serde(rename = "$text")]
1603    pub content: String,
1604}
1605
1606/// Contains a set of Representations.
1607///
1608/// For example, if multiple language streams are available for the audio content, each one can be
1609/// in its own AdaptationSet. DASH implementation guidelines indicate that "representations in the
1610/// same video adaptation set should be alternative encodings of the same source content, encoded
1611/// such that switching between them does not produce visual glitches due to picture size or aspect
1612/// ratio differences".
1613#[skip_serializing_none]
1614#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1615#[serde(default)]
1616pub struct AdaptationSet {
1617    #[serde(rename = "@id")]
1618    pub id: Option<String>,
1619    /// A "remote resource", following the XML Linking Language (XLink) specification.
1620    #[serde(rename = "@xlink:href", alias = "@href")]
1621    pub href: Option<String>,
1622    #[serde(rename = "@xlink:actuate", alias = "@actuate", default = "default_optstring_on_request")]
1623    pub actuate: Option<String>,
1624    #[serde(rename = "@group")]
1625    pub group: Option<i64>,
1626    #[serde(rename = "@selectionPriority")]
1627    pub selectionPriority: Option<u64>,
1628    // e.g. "audio", "video", "text"
1629    #[serde(rename = "@contentType")]
1630    pub contentType: Option<String>,
1631    #[serde(rename = "@profiles")]
1632    pub profiles: Option<String>,
1633    /// Content language, in RFC 5646 format.
1634    #[serde(rename = "@lang")]
1635    pub lang: Option<String>,
1636    /// The Sample Aspect Ratio, eg. "1:1".
1637    #[serde(rename = "@sar")]
1638    pub sar: Option<String>,
1639    /// The Pixel Aspect Ratio, eg. "16:9".
1640    #[serde(rename = "@par")]
1641    pub par: Option<String>,
1642    /// If present, this attribute is expected to be set to "progressive".
1643    #[serde(rename = "@scanType")]
1644    pub scanType: Option<String>,
1645    #[serde(rename = "@segmentAlignment")]
1646    pub segmentAlignment: Option<bool>,
1647    #[serde(rename = "@segmentProfiles")]
1648    /// Specifies the profiles of Segments that are essential to process the Representation. The
1649    /// semantics depend on the value of the @mimeType attribute.
1650    pub segmentProfiles: Option<String>,
1651    #[serde(rename = "@subsegmentAlignment")]
1652    pub subsegmentAlignment: Option<bool>,
1653    #[serde(rename = "@subsegmentStartsWithSAP")]
1654    pub subsegmentStartsWithSAP: Option<u64>,
1655    #[serde(rename = "@bitstreamSwitching")]
1656    pub bitstreamSwitching: Option<bool>,
1657    #[serde(rename = "@audioSamplingRate")]
1658    pub audioSamplingRate: Option<String>,
1659    #[serde(rename = "@width")]
1660    pub width: Option<u64>,
1661    #[serde(rename = "@height")]
1662    pub height: Option<u64>,
1663    // eg "video/mp4"
1664    #[serde(rename = "@mimeType")]
1665    pub mimeType: Option<String>,
1666    /// An RFC6381 string, <https://tools.ietf.org/html/rfc6381> (eg. "avc1.4D400C").
1667    #[serde(rename = "@codecs")]
1668    pub codecs: Option<String>,
1669    #[serde(rename = "@minBandwidth")]
1670    pub minBandwidth: Option<u64>,
1671    #[serde(rename = "@maxBandwidth")]
1672    pub maxBandwidth: Option<u64>,
1673    #[serde(rename = "@minWidth")]
1674    pub minWidth: Option<u64>,
1675    #[serde(rename = "@maxWidth")]
1676    pub maxWidth: Option<u64>,
1677    #[serde(rename = "@minHeight")]
1678    pub minHeight: Option<u64>,
1679    #[serde(rename = "@maxHeight")]
1680    pub maxHeight: Option<u64>,
1681    #[serde(rename = "@frameRate")]
1682    pub frameRate: Option<String>, // it can be something like "15/2"
1683    #[serde(rename = "@minFrameRate")]
1684    pub minFrameRate: Option<String>, // it can be something like "15/2"
1685    #[serde(rename = "@maxFrameRate")]
1686    pub maxFrameRate: Option<String>, // it can be something like "15/2"
1687    /// Indicates the possibility for accelerated playout allowed by this codec profile and level.
1688    #[serde(rename = "@maxPlayoutRate", serialize_with="serialize_opt_xsd_double")]
1689    pub maxPlayoutRate: Option<f64>,
1690    #[serde(rename = "@maximumSAPPeriod", serialize_with="serialize_opt_xsd_double")]
1691    pub maximumSAPPeriod: Option<f64>,
1692    #[serde(rename = "@startWithSAP")]
1693    pub startWithSAP: Option<u64>,
1694    #[serde(rename = "@codingDependency")]
1695    pub codingDependency: Option<bool>,
1696    pub FramePacking: Vec<FramePacking>,
1697    pub AudioChannelConfiguration: Vec<AudioChannelConfiguration>,
1698    pub ContentProtection: Vec<ContentProtection>,
1699    // TODO OutputProtection element
1700    #[serde(rename = "EssentialProperty")]
1701    pub essential_property: Vec<EssentialProperty>,
1702    #[serde(rename = "SupplementalProperty")]
1703    pub supplemental_property: Vec<SupplementalProperty>,
1704    pub InbandEventStream: Vec<InbandEventStream>,
1705    pub Switching: Vec<Switching>,
1706    // TODO RandomAccess element
1707    pub GroupLabel: Vec<Label>,
1708    pub Label: Vec<Label>,
1709    pub ProducerReferenceTime: Vec<ProducerReferenceTime>,
1710    // TODO ContentPopularityRate element
1711    pub Resync: Vec<Resync>,
1712    pub Accessibility: Vec<Accessibility>,
1713    pub Role: Vec<Role>,
1714    pub Rating: Vec<Rating>,
1715    pub Viewpoint: Vec<Viewpoint>,
1716    pub ContentComponent: Vec<ContentComponent>,
1717    pub BaseURL: Vec<BaseURL>,
1718    pub SegmentBase: Option<SegmentBase>,
1719    pub SegmentList: Option<SegmentList>,
1720    pub SegmentTemplate: Option<SegmentTemplate>,
1721    #[serde(rename = "Representation")]
1722    pub representations: Vec<Representation>,
1723    #[serde(rename = "@scte214:supplementalProfiles", alias = "@supplementalProfiles")]
1724    pub scte214_supplemental_profiles: Option<String>,
1725    #[serde(rename = "@scte214:supplementalCodecs", alias = "@supplementalCodecs")]
1726    pub scte214_supplemental_codecs: Option<String>,
1727}
1728
1729/// Identifies the asset to which a given Period belongs.
1730///
1731/// Can be used to implement client functionality that depends on distinguishing between ads and
1732/// main content.
1733#[skip_serializing_none]
1734#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1735#[serde(default)]
1736pub struct AssetIdentifier {
1737    #[serde(rename = "@schemeIdUri")]
1738    pub schemeIdUri: String,
1739    #[serde(rename = "@value")]
1740    pub value: Option<String>,
1741    #[serde(rename(serialize = "scte214:ContentIdentifier"))]
1742    #[serde(rename(deserialize = "ContentIdentifier"))]
1743    pub scte214ContentIdentifiers: Vec<Scte214ContentIdentifier>,
1744}
1745
1746/// Subsets provide a mechanism to restrict the combination of active Adaptation Sets.
1747///
1748/// An active Adaptation Set is one for which the DASH Client is presenting at least one of the
1749/// contained Representations.
1750#[skip_serializing_none]
1751#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1752#[serde(default)]
1753pub struct Subset {
1754    #[serde(rename = "@id")]
1755    pub id: Option<String>,
1756    /// Specifies the AdaptationSets contained in a Subset by providing a whitespace separated
1757    /// list of the @id values of the contained AdaptationSets.
1758    #[serde(rename = "@contains",
1759            deserialize_with = "deserialize_xsd_uintvector",
1760            serialize_with = "serialize_xsd_uintvector",
1761            default)]
1762    pub contains: Vec<u64>,
1763}
1764
1765/// Describes a chunk of the content with a start time and a duration. Content can be split up into
1766/// multiple periods (such as chapters, advertising segments).
1767#[skip_serializing_none]
1768#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1769#[serde(default)]
1770pub struct Period {
1771    /// A "remote resource", following the XML Linking Language (XLink) specification.
1772    #[serde(rename = "@xlink:href", alias = "@href")]
1773    pub href: Option<String>,
1774
1775    #[serde(rename = "@xlink:actuate", alias = "@actuate", default="default_optstring_on_request")]
1776    pub actuate: Option<String>,
1777
1778    #[serde(rename = "@id")]
1779    pub id: Option<String>,
1780
1781    /// The start time of the Period relative to the MPD availability start time.
1782    #[serde(rename = "@start",
1783            serialize_with = "serialize_xs_duration",
1784            deserialize_with = "deserialize_xs_duration",
1785            default)]
1786    pub start: Option<Duration>,
1787
1788    // note: the spec says that this is an xs:duration, not an unsigned int as for other "duration" fields
1789    #[serde(rename = "@duration",
1790            serialize_with = "serialize_xs_duration",
1791            deserialize_with = "deserialize_xs_duration",
1792            default)]
1793    pub duration: Option<Duration>,
1794
1795    // The default for the bitstreamSwitching attribute is specified to be "false".
1796    #[serde(rename = "@bitstreamSwitching", default)]
1797    pub bitstreamSwitching: Option<bool>,
1798
1799    pub BaseURL: Vec<BaseURL>,
1800
1801    pub SegmentBase: Option<SegmentBase>,
1802
1803    pub SegmentList: Option<SegmentList>,
1804
1805    pub SegmentTemplate: Option<SegmentTemplate>,
1806
1807    #[serde(rename = "AssetIdentifier")]
1808    pub asset_identifier: Option<AssetIdentifier>,
1809
1810    #[serde(rename = "EventStream")]
1811    pub event_streams: Vec<EventStream>,
1812
1813    #[serde(rename = "ServiceDescription")]
1814    pub service_description: Vec<ServiceDescription>,
1815
1816    pub ContentProtection: Vec<ContentProtection>,
1817
1818    #[serde(rename = "AdaptationSet")]
1819    pub adaptations: Vec<AdaptationSet>,
1820
1821    #[serde(rename = "Subset")]
1822    pub subsets: Vec<Subset>,
1823
1824    #[serde(rename = "SupplementalProperty")]
1825    pub supplemental_property: Vec<SupplementalProperty>,
1826
1827    #[serde(rename = "EmptyAdaptationSet")]
1828    pub empty_adaptations: Vec<AdaptationSet>,
1829
1830    #[serde(rename = "GroupLabel")]
1831    pub group_label: Vec<Label>,
1832
1833    #[serde(rename = "Preselection")]
1834    pub pre_selections: Vec<Preselection>,
1835
1836    #[serde(rename = "EssentialProperty")]
1837    pub essential_property: Vec<EssentialProperty>,
1838}
1839
1840#[skip_serializing_none]
1841#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1842#[serde(default)]
1843pub struct Reporting {
1844    #[serde(rename = "@id")]
1845    pub id: Option<String>,
1846    #[serde(rename = "@schemeIdUri")]
1847    pub schemeIdUri: String,
1848    #[serde(rename = "@value")]
1849    pub value: Option<String>,
1850    #[serde(rename = "@dvb:reportingUrl", alias = "@reportingUrl")]
1851    pub reportingUrl: Option<String>,
1852    #[serde(rename = "@dvb:probability", alias = "@probability")]
1853    pub probability: Option<u64>,
1854}
1855
1856#[skip_serializing_none]
1857#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1858#[serde(default)]
1859pub struct Range {
1860    #[serde(rename = "@starttime",
1861            serialize_with = "serialize_xs_duration",
1862            deserialize_with = "deserialize_xs_duration",
1863            default)]
1864    pub starttime: Option<Duration>,
1865    #[serde(rename = "@duration",
1866            serialize_with = "serialize_xs_duration",
1867            deserialize_with = "deserialize_xs_duration",
1868            default)]
1869    pub duration: Option<Duration>,
1870}
1871
1872#[skip_serializing_none]
1873#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
1874#[serde(default)]
1875pub struct Metrics {
1876    #[serde(rename = "@metrics")]
1877    pub metrics: String,
1878    pub Reporting: Vec<Reporting>,
1879    pub Range: Vec<Range>,
1880}
1881
1882/// Service Description Latency.
1883#[skip_serializing_none]
1884#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1885#[serde(default)]
1886pub struct Latency {
1887    #[serde(rename = "@min", serialize_with="serialize_opt_xsd_double")]
1888    pub min: Option<f64>,
1889    #[serde(rename = "@max", serialize_with="serialize_opt_xsd_double")]
1890    pub max: Option<f64>,
1891    #[serde(rename = "@target", serialize_with="serialize_opt_xsd_double")]
1892    pub target: Option<f64>,
1893    #[serde(rename = "@referenceId")]
1894    pub referenceId: Option<String>,
1895}
1896
1897/// Service Description Playback Rate.
1898#[skip_serializing_none]
1899#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1900#[serde(default)]
1901pub struct PlaybackRate {
1902    #[serde(rename = "@min", serialize_with="serialize_opt_xsd_double")]
1903    pub min: Option<f64>,
1904    #[serde(rename = "@max", serialize_with="serialize_opt_xsd_double")]
1905    pub max: Option<f64>,
1906}
1907
1908/// Service Description Operating Quality.
1909#[skip_serializing_none]
1910#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1911#[serde(default)]
1912pub struct OperatingQuality {
1913    #[serde(default = "default_optstring_any")]
1914    pub mediaType: Option<String>,
1915    #[serde(rename = "@min")]
1916    pub min: Option<u64>,
1917    #[serde(rename = "@max")]
1918    pub max: Option<u64>,
1919    #[serde(rename = "@target")]
1920    pub target: Option<u64>,
1921    #[serde(rename = "@type")]
1922    pub _type: Option<String>,
1923    #[serde(rename = "@maxDifference")]
1924    pub maxDifference: Option<u64>,
1925}
1926
1927///Service Description Operating Bandwidth.
1928#[skip_serializing_none]
1929#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1930#[serde(default)]
1931pub struct OperatingBandwidth {
1932    #[serde(rename = "@mediaType", default = "default_optstring_any")]
1933    pub mediaType: Option<String>,
1934    #[serde(rename = "@min")]
1935    pub min: Option<u64>,
1936    #[serde(rename = "@max")]
1937    pub max: Option<u64>,
1938    #[serde(rename = "@target")]
1939    pub target: Option<u64>,
1940}
1941
1942#[skip_serializing_none]
1943#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1944#[serde(default)]
1945pub struct ContentSteering {
1946    #[serde(rename = "@defaultServiceLocation")]
1947    pub defaultServiceLocation: Option<String>,
1948    #[serde(rename = "@queryBeforeStart", default = "default_optbool_false")]
1949    pub queryBeforeStart: Option<bool>,
1950    #[serde(rename = "@clientRequirement", default = "default_optbool_true")]
1951    pub clientRequirement: Option<bool>,
1952}
1953
1954#[skip_serializing_none]
1955#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1956#[serde(default)]
1957pub struct CMCDParameters {
1958    #[serde(rename = "@version", default = "default_optu64_one")]
1959    pub version: Option<u64>,
1960    #[serde(rename = "@mode", default = "default_optstring_query")]
1961    pub mode: Option<String>,
1962    #[serde(rename = "@includeInRequests", default = "default_optstring_segment")]
1963    pub includeInRequests: Option<String>,
1964    #[serde(rename = "@keys")]
1965    pub keys: String,
1966    #[serde(rename = "@contentID")]
1967    pub contentID: Option<String>,
1968    #[serde(rename = "@sessionID")]
1969    pub sessionID: Option<String>,
1970}
1971
1972/// Generic Recording System descriptor.
1973#[skip_serializing_none]
1974#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1975#[serde(default)]
1976pub struct ClientDataReporting {
1977    pub CMCDParameters: Vec<CMCDParameters>,
1978    #[serde(rename = "@serviceLocations")]
1979    pub serviceLocations: Option<String>,
1980    #[serde(rename = "@adaptationSets")]
1981    pub adaptationSets: Option<String>,
1982}
1983
1984#[skip_serializing_none]
1985#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1986#[serde(default)]
1987pub struct PlaybackRestrictions {
1988    #[serde(rename = "@skipAfter",
1989            serialize_with = "serialize_xs_duration",
1990            deserialize_with = "deserialize_xs_duration",
1991            default)]
1992    pub skipAfter: Option<Duration>,
1993}
1994
1995#[skip_serializing_none]
1996#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
1997#[serde(default)]
1998pub struct ServiceDescription {
1999    #[serde(rename = "Scope")]
2000    pub scopes: Vec<Scope>,
2001    pub Latency: Vec<Latency>,
2002    pub PlaybackRate: Vec<PlaybackRate>,
2003    pub OperatingQuality: Vec<OperatingQuality>,
2004    pub OperatingBandwidth: Vec<OperatingBandwidth>,
2005    pub ContentSteering: Vec<ContentSteering>,
2006    pub ClientDataReporting: Vec<ClientDataReporting>,
2007    pub PlaybackRestrictions: Vec<PlaybackRestrictions>,
2008    #[serde(rename = "@id")]
2009    pub id: Option<String>,
2010}
2011
2012/// Used to synchronize the clocks of the DASH client and server, to allow low-latency streaming.
2013#[skip_serializing_none]
2014#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
2015#[serde(default)]
2016pub struct UTCTiming {
2017    #[serde(rename = "@id")]
2018    pub id: Option<String>,
2019    // prefixed with urn:mpeg:dash:utc, one of http-xsdate:2014, http-iso:2014,
2020    // http-ntp:2014, ntp:2014, http-head:2014, direct:2014
2021    #[serde(rename = "@schemeIdUri")]
2022    pub schemeIdUri: String,
2023    #[serde(rename = "@value")]
2024    pub value: Option<String>,
2025}
2026
2027/// Specifies wall‐clock times at which media fragments were produced.
2028///
2029/// This information helps clients consume the fragments at the same rate at which they were
2030/// produced. Used by the low-latency streaming extensions to DASH.
2031#[skip_serializing_none]
2032#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
2033#[serde(default)]
2034pub struct ProducerReferenceTime {
2035    // This attribute is required according to the specification XSD.
2036    #[serde(rename = "@id")]
2037    pub id: Option<String>,
2038    #[serde(rename = "@inband", default = "default_optbool_false")]
2039    pub inband: Option<bool>,
2040    // This attribute is required according to the specification XSD.
2041    #[serde(rename = "@presentationTime")]
2042    pub presentationTime: Option<u64>,
2043    #[serde(rename = "@type", default = "default_optstring_encoder")]
2044    pub prtType: Option<String>,
2045    // There are two capitalizations for this attribute in the specification at
2046    // https://dashif.org/docs/CR-Low-Latency-Live-r8.pdf. The attribute is required according to
2047    // the specification XSD.
2048    #[serde(rename = "@wallClockTime",
2049            alias="@wallclockTime",
2050            deserialize_with = "deserialize_xs_datetime",
2051            default)]
2052    pub wallClockTime: Option<XsDatetime>,
2053    pub UTCTiming: Option<UTCTiming>,
2054}
2055
2056#[skip_serializing_none]
2057#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
2058#[serde(default)]
2059pub struct LeapSecondInformation {
2060    #[serde(rename = "@availabilityStartLeapOffset")]
2061    pub availabilityStartLeapOffset: Option<i64>,
2062    #[serde(rename = "@nextAvailabilityStartLeapOffset")]
2063    pub nextAvailabilityStartLeapOffset: Option<i64>,
2064    #[serde(rename = "@nextLeapChangeTime",
2065            deserialize_with = "deserialize_xs_datetime",
2066            default)]
2067    pub nextLeapChangeTime: Option<XsDatetime>,
2068}
2069
2070/// The Patch mechanism allows the DASH client to retrieve a set of instructions for replacing
2071/// certain parts of the MPD manifest with updated information.
2072///
2073/// It is a bandwidth-friendly alternative to retrieving a new version of the full MPD manifest. The
2074/// MPD patch document is guaranteed to be available between MPD@publishTime and MPD@publishTime +
2075/// PatchLocation@ttl.
2076#[skip_serializing_none]
2077#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
2078#[serde(default)]
2079pub struct PatchLocation {
2080    #[serde(rename = "@ttl", serialize_with="serialize_opt_xsd_double")]
2081    pub ttl: Option<f64>,
2082    #[serde(rename = "$text")]
2083    pub content: String,
2084}
2085
2086/// The root node of a parsed DASH MPD manifest.
2087#[skip_serializing_none]
2088#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
2089#[serde(default)]
2090pub struct MPD {
2091    #[serde(rename = "@xmlns", serialize_with="serialize_xmlns")]
2092    pub xmlns: Option<String>,
2093    #[serde(rename = "@id")]
2094    pub id: Option<String>,
2095    #[serde(rename = "@profiles")]
2096    pub profiles: Option<String>,
2097    /// The Presentation Type, either "static" or "dynamic" (a live stream for which segments become
2098    /// available over time).
2099    #[serde(rename = "@type")]
2100    pub mpdtype: Option<String>,
2101    #[serde(rename = "@availabilityStartTime",
2102            deserialize_with = "deserialize_xs_datetime",
2103            default)]
2104    pub availabilityStartTime: Option<XsDatetime>,
2105    #[serde(rename = "@availabilityEndTime",
2106            deserialize_with = "deserialize_xs_datetime",
2107            default)]
2108    pub availabilityEndTime: Option<XsDatetime>,
2109    #[serde(rename = "@publishTime",
2110            deserialize_with = "deserialize_xs_datetime",
2111            default)]
2112    pub publishTime: Option<XsDatetime>,
2113    #[serde(rename = "@mediaPresentationDuration",
2114            serialize_with = "serialize_xs_duration",
2115            deserialize_with = "deserialize_xs_duration",
2116            default)]
2117    pub mediaPresentationDuration: Option<Duration>,
2118    #[serde(rename = "@minimumUpdatePeriod",
2119            serialize_with = "serialize_xs_duration",
2120            deserialize_with = "deserialize_xs_duration",
2121            default)]
2122    pub minimumUpdatePeriod: Option<Duration>,
2123    // This attribute is actually required by the XSD specification, but we make it optional.
2124    #[serde(rename = "@minBufferTime",
2125            serialize_with = "serialize_xs_duration",
2126            deserialize_with = "deserialize_xs_duration",
2127            default)]
2128    pub minBufferTime: Option<Duration>,
2129    /// Prescribes how many seconds of buffer a client should keep to avoid stalling when streaming
2130    /// under ideal network conditions with bandwidth matching the @bandwidth attribute.
2131    #[serde(rename = "@timeShiftBufferDepth",
2132            serialize_with = "serialize_xs_duration",
2133            deserialize_with = "deserialize_xs_duration",
2134            default)]
2135    pub timeShiftBufferDepth: Option<Duration>,
2136    /// A suggested delay of the presentation compared to the Live edge.
2137    #[serde(rename = "@suggestedPresentationDelay",
2138            serialize_with = "serialize_xs_duration",
2139            deserialize_with = "deserialize_xs_duration",
2140            default)]
2141    pub suggestedPresentationDelay: Option<Duration>,
2142    #[serde(rename = "@maxSegmentDuration",
2143            serialize_with = "serialize_xs_duration",
2144            deserialize_with = "deserialize_xs_duration",
2145            default)]
2146    pub maxSegmentDuration: Option<Duration>,
2147    #[serde(rename = "@maxSubsegmentDuration",
2148            serialize_with = "serialize_xs_duration",
2149            deserialize_with = "deserialize_xs_duration",
2150            default)]
2151    pub maxSubsegmentDuration: Option<Duration>,
2152    /// The XML namespace prefix used by convention for the XML Schema Instance namespace.
2153    #[serialize_always]
2154    #[serde(rename="@xmlns:xsi", alias="@xsi", serialize_with="serialize_xsi_ns")]
2155    pub xsi: Option<String>,
2156    #[serde(alias = "@ext", rename = "@xmlns:ext")]
2157    pub ext: Option<String>,
2158    /// The XML namespace prefix used by convention for the Common Encryption scheme.
2159    #[serialize_always]
2160    #[serde(rename="@xmlns:cenc", alias="@cenc", serialize_with="serialize_cenc_ns")]
2161    pub cenc: Option<String>,
2162    /// The XML namespace prefix used by convention for the Microsoft PlayReady scheme.
2163    #[serialize_always]
2164    #[serde(rename="@xmlns:mspr", alias="@mspr", serialize_with="serialize_mspr_ns")]
2165    pub mspr: Option<String>,
2166    /// The XML namespace prefix used by convention for the XML Linking Language.
2167    #[serialize_always]
2168    #[serde(rename="@xmlns:xlink", alias="@xlink", serialize_with="serialize_xlink_ns")]
2169    pub xlink: Option<String>,
2170    /// The XML namespace prefix used by convention for the “Digital Program Insertion Cueing
2171    /// Message for Cable” (SCTE 35) signaling standard.
2172    #[cfg(feature = "scte35")]
2173    #[serialize_always]
2174    #[serde(rename="@xmlns:scte35", alias="@scte35", serialize_with="scte35::serialize_scte35_ns")]
2175    pub scte35: Option<String>,
2176    /// The XML namespace prefix used by convention for DASH extensions proposed by the Digital
2177    /// Video Broadcasting Project, as per RFC 5328.
2178    #[serialize_always]
2179    #[serde(rename="@xmlns:dvb", alias="@dvb", serialize_with="serialize_dvb_ns")]
2180    pub dvb: Option<String>,
2181    #[serde(rename = "@xsi:schemaLocation", alias = "@schemaLocation")]
2182    pub schemaLocation: Option<String>,
2183    // scte214 namespace
2184    #[serde(alias = "@scte214", rename = "@xmlns:scte214")]
2185    pub scte214: Option<String>,
2186    pub ProgramInformation: Vec<ProgramInformation>,
2187    /// There may be several BaseURLs, for redundancy (for example multiple CDNs)
2188    #[serde(rename = "BaseURL")]
2189    pub base_url: Vec<BaseURL>,
2190    #[serde(rename = "Location", default)]
2191    pub locations: Vec<Location>,
2192    /// Specifies the location of an MPD “patch document”, a set of instructions for replacing
2193    /// certain parts of the MPD manifest with updated information.
2194    pub PatchLocation: Vec<PatchLocation>,
2195    pub ServiceDescription: Vec<ServiceDescription>,
2196    // TODO: elements InitializationSet, InitializationGroup, InitializationPresentation
2197    pub ContentProtection: Vec<ContentProtection>,
2198    #[serde(rename = "Period", default)]
2199    pub periods: Vec<Period>,
2200    pub Metrics: Vec<Metrics>,
2201    #[serde(rename = "EssentialProperty")]
2202    pub essential_property: Vec<EssentialProperty>,
2203    #[serde(rename = "SupplementalProperty")]
2204    pub supplemental_property: Vec<SupplementalProperty>,
2205    pub UTCTiming: Vec<UTCTiming>,
2206    /// Correction for leap seconds, used by the DASH Low Latency specification.
2207    pub LeapSecondInformation: Option<LeapSecondInformation>,
2208}
2209
2210impl std::fmt::Display for MPD {
2211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2212        write!(f, "{}", quick_xml::se::to_string(self).map_err(|_| std::fmt::Error)?)
2213    }
2214}
2215
2216/// Parse an MPD manifest, provided as an XML string, returning an `MPD` node.
2217pub fn parse(xml: &str) -> Result<MPD, DashMpdError> {
2218    #[cfg(feature = "warn_ignored_elements")]
2219    {
2220        let xd = &mut quick_xml::de::Deserializer::from_str(xml);
2221        let _: MPD = serde_ignored::deserialize(xd, |path| {
2222            warn!("Unused XML element in manifest: {path}");
2223        }).map_err(|e| DashMpdError::Parsing(e.to_string()))?;
2224    }
2225    let xd = &mut quick_xml::de::Deserializer::from_str(xml);
2226    let mpd: MPD = serde_path_to_error::deserialize(xd)
2227        .map_err(|e| DashMpdError::Parsing(e.to_string()))?;
2228    Ok(mpd)
2229}
2230
2231
2232// Note that a codec name can be of the form "mp4a" or "mp4a.40.2".
2233fn is_audio_codec(name: &str) -> bool {
2234    name.starts_with("mp4a") ||
2235        name.starts_with("aac") ||
2236        name.starts_with("vorbis") ||
2237        name.starts_with("opus") ||
2238        name.starts_with("ogg") ||
2239        name.starts_with("webm") ||
2240        name.starts_with("flac") ||
2241        name.starts_with("mp3") ||
2242        name.starts_with("mpeg") ||
2243        name.starts_with("3gpp") ||
2244        name.starts_with("wav") ||
2245        name.starts_with("ec-3") ||
2246        name.starts_with("ac-4") ||
2247        name.starts_with("dtsc") ||
2248        name.starts_with("aptx") ||
2249        name.starts_with("aiff") ||
2250        name.starts_with("mha1")       // MPEG-H 3D Audio
2251}
2252
2253
2254/// Returns `true` if this AdaptationSet contains audio content.
2255///
2256/// It contains audio if the codec attribute corresponds to a known audio codec, or the
2257/// `contentType` attribute` is `audio`, or the `mimeType` attribute is `audio/*`, or if one of its
2258/// child `Representation` nodes has an audio `contentType` or `mimeType` attribute.
2259pub fn is_audio_adaptation(a: &&AdaptationSet) -> bool {
2260    if let Some(codec) = &a.codecs {
2261        if is_audio_codec(codec) {
2262            return true;
2263        }
2264    }
2265    if let Some(ct) = &a.contentType {
2266        if ct == "audio" {
2267            return true;
2268        }
2269    }
2270    if let Some(mimetype) = &a.mimeType {
2271        if mimetype.starts_with("audio/") {
2272            return true;
2273        }
2274    }
2275    for r in &a.representations {
2276        if let Some(ct) = &r.contentType {
2277            if ct == "audio" {
2278                return true;
2279            }
2280        }
2281        if let Some(mimetype) = &r.mimeType {
2282            if mimetype.starts_with("audio/") {
2283                return true;
2284            }
2285        }
2286    }
2287    false
2288}
2289
2290/// Returns `true` if this AdaptationSet contains video content.
2291///
2292/// It contains video if the `contentType` attribute` is `video`, or the `mimeType` attribute is
2293/// `video/*` (but without a codec specifying a subtitle format), or if one of its child
2294/// `Representation` nodes has an audio `contentType` or `mimeType` attribute.
2295///
2296/// Note: if it's an audio adaptation then it's not a video adaptation (an audio adaptation means
2297/// audio-only), but a video adaptation may contain audio.
2298pub fn is_video_adaptation(a: &&AdaptationSet) -> bool {
2299    if is_audio_adaptation(a) {
2300        return false;
2301    }
2302    if let Some(ct) = &a.contentType {
2303        if ct == "video" {
2304            return true;
2305        }
2306    }
2307    if let Some(mimetype) = &a.mimeType {
2308        if mimetype.starts_with("video/") {
2309            return true;
2310        }
2311    }
2312    for r in &a.representations {
2313        if let Some(ct) = &r.contentType {
2314            if ct == "video" {
2315                return true;
2316            }
2317        }
2318        // We can have a Representation with mimeType="video/mp4" and codecs="wvtt", which means
2319        // WebVTT in a (possibly fragmented) MP4 container.
2320        if r.codecs.as_deref().is_some_and(is_subtitle_codec) {
2321            return false;
2322        }
2323        if let Some(mimetype) = &r.mimeType {
2324            if mimetype.starts_with("video/") {
2325                return true;
2326            }
2327        }
2328    }
2329    false
2330}
2331
2332
2333fn is_subtitle_mimetype(mt: &str) -> bool {
2334    mt.eq("text/vtt") ||
2335    mt.eq("application/ttml+xml") ||
2336    mt.eq("application/x-sami")
2337
2338    // Some manifests use a @mimeType of "application/mp4" together with @contentType="text"; we'll
2339    // classify these only based on their contentType.
2340}
2341
2342fn is_subtitle_codec(c: &str) -> bool {
2343    c == "wvtt" ||
2344    c == "c608" ||
2345    c == "stpp" ||
2346    c == "tx3g" ||
2347    c.starts_with("stpp.")
2348}
2349
2350/// Returns `true` if this AdaptationSet contains subtitle content.
2351///
2352/// For now, it contains subtitles if the `@mimeType` attribute is "text/vtt" (WebVTT) or
2353/// "application/ttml+xml" or "application/x-sami" (SAMI). Further work needed to handle an
2354/// Adaptation that contains a Representation with @contentType="text" and @codecs="stpp" or a
2355/// subset like @codecs="stpp.ttml.im1t" (fragmented TTML in an MP4 container) or @codecs="wvtt"
2356/// (fragmented VTTcue in an MP4 container).
2357///
2358/// The DVB-DASH specification also allows for closed captions for hearing impaired viewers in an
2359/// AdaptationSet with Accessibility node having @SchemeIdUri =
2360/// "urn:tva:metadata:cs:AudioPurposeCS:2007" and @value=2.
2361pub fn is_subtitle_adaptation(a: &&AdaptationSet) -> bool {
2362    if a.mimeType.as_deref().is_some_and(is_subtitle_mimetype) {
2363        return true;
2364    }
2365    if a.contentType.as_deref().is_some_and(|ct| ct.eq("text")) {
2366        return true;
2367    }
2368    if a.codecs.as_deref().is_some_and(is_subtitle_codec) {
2369        return true;
2370    }
2371    for cc in a.ContentComponent.iter() {
2372        if cc.contentType.as_deref().is_some_and(|ct| ct.eq("text")) {
2373            return true;
2374        }
2375    }
2376    for r in a.representations.iter() {
2377        if r.mimeType.as_deref().is_some_and(is_subtitle_mimetype) {
2378            return true;
2379        }
2380        // Often, but now always, the subtitle codec is also accompanied by a contentType of "text".
2381        if r.codecs.as_deref().is_some_and(is_subtitle_codec) {
2382            return true;
2383        }
2384    }
2385    false
2386}
2387
2388
2389// Incomplete, see https://en.wikipedia.org/wiki/Subtitles#Subtitle_formats
2390#[derive(Debug, PartialEq, Eq, Clone, Copy)]
2391pub enum SubtitleType {
2392    /// W3C WebVTT, as used in particular for HTML5 media
2393    Vtt,
2394    /// SubRip
2395    Srt,
2396    /// MPSub
2397    Sub,
2398    /// Advanced Substation Alpha
2399    Ass,
2400    /// MPEG-4 Timed Text, aka MP4TT aka 3GPP-TT (codec=tx3g)
2401    Ttxt,
2402    /// Timed Text Markup Language
2403    Ttml,
2404    /// Synchronized Accessible Media Interchange
2405    Sami,
2406    /// Binary WebVTT in a wvtt box in fragmented MP4 container, as specified by ISO/IEC
2407    /// 14496-30:2014. Mostly intended for live streams where it's not possible to provide a
2408    /// standalone VTT file.
2409    Wvtt,
2410    /// XML content (generally TTML) in an stpp box in fragmented MP4 container
2411    Stpp,
2412    /// EIA-608 aka CEA-608, a legacy standard for closed captioning for NTSC TV
2413    Eia608,
2414    Unknown,
2415}
2416
2417fn subtitle_type_for_mimetype(mt: &str) -> Option<SubtitleType> {
2418    match mt {
2419        "text/vtt" => Some(SubtitleType::Vtt),
2420        "application/ttml+xml" => Some(SubtitleType::Ttml),
2421        "application/x-sami" => Some(SubtitleType::Sami),
2422        _ => None
2423    }
2424}
2425
2426#[must_use]
2427pub fn subtitle_type(a: &&AdaptationSet) -> SubtitleType {
2428    if let Some(mimetype) = &a.mimeType {
2429        if let Some(st) = subtitle_type_for_mimetype(mimetype) {
2430            return st;
2431        }
2432    }
2433    if let Some(codecs) = &a.codecs {
2434        if codecs == "wvtt" {
2435            // can be extracted with https://github.com/xhlove/dash-subtitle-extractor
2436            return SubtitleType::Wvtt;
2437        }
2438        if codecs == "c608" {
2439            return SubtitleType::Eia608;
2440        }
2441        if codecs == "tx3g" {
2442            return SubtitleType::Ttxt;
2443        }
2444        if codecs == "stpp" {
2445            return SubtitleType::Stpp;
2446        }
2447        if codecs.starts_with("stpp.") {
2448            return SubtitleType::Stpp;
2449        }
2450    }
2451    for r in a.representations.iter() {
2452        if let Some(mimetype) = &r.mimeType {
2453            if let Some(st) = subtitle_type_for_mimetype(mimetype) {
2454                return st;
2455            }
2456        }
2457        if let Some(codecs) = &r.codecs {
2458            if codecs == "wvtt" {
2459                return SubtitleType::Wvtt;
2460            }
2461            if codecs == "c608" {
2462                return SubtitleType::Eia608;
2463            }
2464            if codecs == "tx3g" {
2465                return SubtitleType::Ttxt;
2466            }
2467            if codecs == "stpp" {
2468                return SubtitleType::Stpp;
2469            }
2470            if codecs.starts_with("stpp.") {
2471                return SubtitleType::Stpp;
2472            }
2473        }
2474    }
2475    SubtitleType::Unknown
2476}
2477
2478
2479#[allow(dead_code)]
2480fn content_protection_type(cp: &ContentProtection) -> String {
2481    if let Some(v) = &cp.value {
2482        if v.eq("cenc") {
2483            return String::from("cenc");
2484        }
2485        if v.eq("Widevine") {
2486            return String::from("Widevine");
2487        }
2488        if v.eq("MSPR 2.0") {
2489            return String::from("PlayReady");
2490        }
2491    }
2492    // See list at https://dashif.org/identifiers/content_protection/
2493    let uri = &cp.schemeIdUri;
2494    let uri = uri.to_lowercase();
2495    if uri.eq("urn:mpeg:dash:mp4protection:2011") {
2496        return String::from("cenc");
2497    }
2498    if uri.eq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed") {
2499        return String::from("Widevine");
2500    }
2501    if uri.eq("urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95") {
2502        return String::from("PlayReady");
2503    }
2504    if uri.eq("urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2") {
2505        return String::from("FairPlay");
2506    }
2507    if uri.eq("urn:uuid:3ea8778f-7742-4bf9-b18b-e834b2acbd47") {
2508        return String::from("Clear Key AES-128");
2509    }
2510    if uri.eq("urn:uuid:be58615b-19c4-4684-88b3-c8c57e99e957") {
2511        return String::from("Clear Key SAMPLE-AES");
2512    }
2513    if uri.eq("urn:uuid:adb41c24-2dbf-4a6d-958b-4457c0d27b95") {
2514        return String::from("Nagra");
2515    }
2516    if uri.eq("urn:uuid:5e629af5-38da-4063-8977-97ffbd9902d4") {
2517        return String::from("Marlin");
2518    }
2519    if uri.eq("urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb") {
2520        return String::from("Adobe PrimeTime");
2521    }
2522    if uri.eq("urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b") {
2523        return String::from("W3C Common PSSH box");
2524    }
2525    if uri.eq("urn:uuid:80a6be7e-1448-4c37-9e70-d5aebe04c8d2") {
2526        return String::from("Irdeto Content Protection");
2527    }
2528    if uri.eq("urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c") {
2529        return String::from("WisePlay-ChinaDRM");
2530    }
2531    if uri.eq("urn:uuid:616c7469-6361-7374-2d50-726f74656374") {
2532        return String::from("Alticast");
2533    }
2534    if uri.eq("urn:uuid:6dd8b3c3-45f4-4a68-bf3a-64168d01a4a6") {
2535        return String::from("ABV DRM");
2536    }
2537    // Segment encryption
2538    if uri.eq("urn:mpeg:dash:sea:2012") {
2539        return String::from("SEA");
2540    }
2541    String::from("<unknown>")
2542}
2543
2544
2545fn check_segment_template_duration(
2546    st: &SegmentTemplate,
2547    max_seg_duration: &Duration,
2548    outer_timescale: u64) -> Vec<String>
2549{
2550    let mut errors = Vec::new();
2551    if let Some(timeline) = &st.SegmentTimeline {
2552        for s in &timeline.segments {
2553            let sd = s.d / st.timescale.unwrap_or(outer_timescale);
2554            if sd > max_seg_duration.as_secs() {
2555                errors.push(String::from("SegmentTimeline has segment@d > @maxSegmentDuration"));
2556            }
2557        }
2558    }
2559    errors
2560}
2561
2562fn check_segment_template_conformity(st: &SegmentTemplate) -> Vec<String> {
2563    let mut errors = Vec::new();
2564    if let Some(md) = &st.media {
2565        if !valid_url_p(md) {
2566            errors.push(format!("invalid URL {md}"));
2567        }
2568        if md.contains("$Number$") && md.contains("$Time") {
2569            errors.push(String::from("both $Number$ and $Time$ are used in media template URL"));
2570        }
2571    }
2572    if let Some(init) = &st.initialization {
2573        if !valid_url_p(init) {
2574            errors.push(format!("invalid URL {init}"));
2575        }
2576        if init.contains("$Number") {
2577            errors.push(String::from("$Number$ identifier used in initialization segment URL"));
2578        }
2579        if init.contains("$Time") {
2580            errors.push(String::from("$Time$ identifier used in initialization segment URL"));
2581        }
2582    }
2583    if st.duration.is_some() && st.SegmentTimeline.is_some() {
2584        errors.push(String::from("both SegmentTemplate.duration and SegmentTemplate.SegmentTimeline present"));
2585    }
2586    errors
2587}
2588
2589
2590// Check the URL or URL path u for conformity. This is a very relaxed check because the Url crate is
2591// very tolerant, in particular concerning the syntax accepted for the path component of an URL.
2592fn valid_url_p(u: &str) -> bool {
2593    use url::ParseError;
2594
2595    match Url::parse(u) {
2596        Ok(url) => {
2597            url.scheme() == "https" ||
2598                url.scheme() == "http" ||
2599                url.scheme() == "ftp" ||
2600                url.scheme() == "file" ||
2601                url.scheme() == "data"
2602        },
2603        Err(ParseError::RelativeUrlWithoutBase) => true,
2604        Err(_) => false,
2605    }
2606}
2607
2608/// Returns a list of DASH conformity errors in the DASH manifest mpd.
2609#[must_use]
2610pub fn check_conformity(mpd: &MPD) -> Vec<String> {
2611    let mut errors = Vec::new();
2612
2613    // @maxHeight on the AdaptationSet should give the maximum value of the @height values of its
2614    // Representation elements.
2615    for p in &mpd.periods {
2616        if p.adaptations.is_empty() {
2617            errors.push(format!("Period with @id {} contains no AdaptationSet elements",
2618                                p.id.clone().unwrap_or(String::from("<unspecified>"))));
2619        }
2620        for a in &p.adaptations {
2621            if let Some(mh) = a.maxHeight {
2622                if let Some(mr) = a.representations.iter().max_by_key(|r| r.height.unwrap_or(0)) {
2623                    if mr.height.unwrap_or(0) > mh {
2624                        errors.push(String::from("invalid @maxHeight on AdaptationSet"));
2625                    }
2626                }
2627            }
2628        }
2629    }
2630    // @maxWidth on the AdaptationSet should give the maximum value of the @width values of its
2631    // Representation elements.
2632    for p in &mpd.periods {
2633        for a in &p.adaptations {
2634            if let Some(mw) = a.maxWidth {
2635                if let Some(mr) = a.representations.iter().max_by_key(|r| r.width.unwrap_or(0)) {
2636                    if mr.width.unwrap_or(0) > mw {
2637                        errors.push(String::from("invalid @maxWidth on AdaptationSet"));
2638                    }
2639                }
2640            }
2641        }
2642    }
2643    // @maxBandwidth on the AdaptationSet should give the maximum value of the @bandwidth values of its
2644    // Representation elements.
2645    for p in &mpd.periods {
2646        for a in &p.adaptations {
2647            if let Some(mb) = a.maxBandwidth {
2648                if let Some(mr) = a.representations.iter().max_by_key(|r| r.bandwidth.unwrap_or(0)) {
2649                    if mr.bandwidth.unwrap_or(0) > mb {
2650                        errors.push(String::from("invalid @maxBandwidth on AdaptationSet"));
2651                    }
2652                }
2653            }
2654        }
2655    }
2656    // No @d of a segment should be greater than @maxSegmentDuration.
2657    if let Some(max_seg_duration) = mpd.maxSegmentDuration {
2658        for p in &mpd.periods {
2659            for a in &p.adaptations {
2660                // We need to keep track of outer_timescale for situations with a nested SegmentTemplate.
2661                // For an example see test/fixtures/aws.xml.
2662                // <SegmentTemplate startNumber="1" timescale="90000"/>
2663                //   <Representation bandwidth="3296000" ...>
2664                //     <SegmentTemplate initialization="i.mp4" media="m$Number$.mp4">
2665                //       <SegmentTimeline>
2666                //         <S d="180000" r="6" t="0"/>
2667                //       </SegmentTimeline>
2668                //     </SegmentTemplate>
2669                // ...
2670                let mut outer_timescale = 1;
2671                if let Some(st) = &a.SegmentTemplate {
2672                    check_segment_template_duration(st, &max_seg_duration, outer_timescale)
2673                        .into_iter()
2674                        .for_each(|msg| errors.push(msg));
2675                    if let Some(ots) = st.timescale {
2676                        outer_timescale = ots;
2677                    }
2678                }
2679                for r in &a.representations {
2680                    if let Some(st) = &r.SegmentTemplate {
2681                        check_segment_template_duration(st, &max_seg_duration, outer_timescale)
2682                            .into_iter()
2683                            .for_each(|msg| errors.push(msg));
2684                    }
2685                }
2686            }
2687        }
2688    }
2689
2690    for bu in &mpd.base_url {
2691        if !valid_url_p(&bu.base) {
2692            errors.push(format!("invalid URL {}", &bu.base));
2693        }
2694    }
2695    for p in &mpd.periods {
2696        for bu in &p.BaseURL {
2697            if !valid_url_p(&bu.base) {
2698                errors.push(format!("invalid URL {}", &bu.base));
2699            }
2700        }
2701        for a in &p.adaptations {
2702            for bu in &a.BaseURL {
2703                if !valid_url_p(&bu.base) {
2704                    errors.push(format!("invalid URL {}", &bu.base));
2705                }
2706            }
2707            if let Some(st) = &a.SegmentTemplate {
2708                check_segment_template_conformity(st)
2709                    .into_iter()
2710                    .for_each(|msg| errors.push(msg));
2711            }
2712            for r in &a.representations {
2713                for bu in &r.BaseURL {
2714                    if !valid_url_p(&bu.base) {
2715                        errors.push(format!("invalid URL {}", &bu.base));
2716                    }
2717                }
2718                if let Some(sb) = &r.SegmentBase {
2719                    if let Some(init) = &sb.Initialization {
2720                        if let Some(su) = &init.sourceURL {
2721                            if !valid_url_p(su) {
2722                                errors.push(format!("invalid URL {su}"));
2723                            }
2724                            if su.contains("$Number") {
2725                                errors.push(String::from("$Number$ identifier used in initialization segment URL"));
2726                            }
2727                            if su.contains("$Time") {
2728                                errors.push(String::from("$Time$ identifier used in initialization segment URL"));
2729                            }
2730                        }
2731                    }
2732                    if let Some(ri) = &sb.representation_index {
2733                        if let Some(su) = &ri.sourceURL {
2734                            if !valid_url_p(su) {
2735                                errors.push(format!("invalid URL {su}"));
2736                            }
2737                        }
2738                    }
2739                }
2740                if let Some(sl) = &r.SegmentList {
2741                    if let Some(hr) = &sl.href {
2742                        if !valid_url_p(hr) {
2743                            errors.push(format!("invalid URL {hr}"));
2744                        }
2745                    }
2746                    if let Some(init) = &sl.Initialization {
2747                        if let Some(su) = &init.sourceURL {
2748                            if !valid_url_p(su) {
2749                                errors.push(format!("invalid URL {su}"));
2750                            }
2751                            if su.contains("$Number") {
2752                                errors.push(String::from("$Number$ identifier used in initialization segment URL"));
2753                            }
2754                            if su.contains("$Time") {
2755                                errors.push(String::from("$Time$ identifier used in initialization segment URL"));
2756                            }
2757                        }
2758                    }
2759                    for su in &sl.segment_urls {
2760                        if let Some(md) = &su.media {
2761                            if !valid_url_p(md) {
2762                                errors.push(format!("invalid URL {md}"));
2763                            }
2764                        }
2765                        if let Some(ix) = &su.index {
2766                            if !valid_url_p(ix) {
2767                                errors.push(format!("invalid URL {ix}"));
2768                            }
2769                        }
2770                    }
2771                }
2772                if let Some(st) = &r.SegmentTemplate {
2773                    check_segment_template_conformity(st)
2774                        .into_iter()
2775                        .for_each(|msg| errors.push(msg));
2776                }
2777            }
2778        }
2779    }
2780    for pi in &mpd.ProgramInformation {
2781        if let Some(u) = &pi.moreInformationURL {
2782            if !valid_url_p(u) {
2783                errors.push(format!("invalid URL {u}"));
2784            }
2785        }
2786    }
2787    errors
2788}
2789
2790#[cfg(test)]
2791mod tests {
2792    use proptest::prelude::*;
2793    use std::fs;
2794    use std::path::PathBuf;
2795    use std::time::Duration;
2796
2797    proptest! {
2798        #[test]
2799        fn doesnt_crash(s in "\\PC*") {
2800            let _ = super::parse_xs_duration(&s);
2801            let _ = super::parse_xs_datetime(&s);
2802        }
2803    }
2804
2805    #[test]
2806    fn test_parse_xs_duration() {
2807        use super::parse_xs_duration;
2808
2809        assert!(parse_xs_duration("").is_err());
2810        assert!(parse_xs_duration("foobles").is_err());
2811        assert!(parse_xs_duration("P").is_err());
2812        assert!(parse_xs_duration("PW").is_err());
2813        // assert!(parse_xs_duration("PT-4.5S").is_err());
2814        assert!(parse_xs_duration("-PT4.5S").is_err());
2815        assert!(parse_xs_duration("1Y2M3DT4H5M6S").is_err()); // missing initial P
2816        assert_eq!(parse_xs_duration("PT3H11M53S").ok(), Some(Duration::new(11513, 0)));
2817        assert_eq!(parse_xs_duration("PT42M30S").ok(), Some(Duration::new(2550, 0)));
2818        assert_eq!(parse_xs_duration("PT30M38S").ok(), Some(Duration::new(1838, 0)));
2819        assert_eq!(parse_xs_duration("PT0H10M0.00S").ok(), Some(Duration::new(600, 0)));
2820        assert_eq!(parse_xs_duration("PT1.5S").ok(), Some(Duration::new(1, 500_000_000)));
2821        assert_eq!(parse_xs_duration("PT1.500S").ok(), Some(Duration::new(1, 500_000_000)));
2822        assert_eq!(parse_xs_duration("PT1.500000000S").ok(), Some(Duration::new(1, 500_000_000)));
2823        assert_eq!(parse_xs_duration("PT0S").ok(), Some(Duration::new(0, 0)));
2824        assert_eq!(parse_xs_duration("PT0.001S").ok(), Some(Duration::new(0, 1_000_000)));
2825        assert_eq!(parse_xs_duration("PT0.00100S").ok(), Some(Duration::new(0, 1_000_000)));
2826        assert_eq!(parse_xs_duration("PT344S").ok(), Some(Duration::new(344, 0)));
2827        assert_eq!(parse_xs_duration("PT634.566S").ok(), Some(Duration::new(634, 566_000_000)));
2828        assert_eq!(parse_xs_duration("PT72H").ok(), Some(Duration::new(72*60*60, 0)));
2829        assert_eq!(parse_xs_duration("PT0H0M30.030S").ok(), Some(Duration::new(30, 30_000_000)));
2830        assert_eq!(parse_xs_duration("PT1004199059S").ok(), Some(Duration::new(1004199059, 0)));
2831        assert_eq!(parse_xs_duration("P0Y20M0D").ok(), Some(Duration::new(51840000, 0)));
2832        assert_eq!(parse_xs_duration("PT1M30.5S").ok(), Some(Duration::new(90, 500_000_000)));
2833        assert_eq!(parse_xs_duration("PT10M10S").ok(), Some(Duration::new(610, 0)));
2834        assert_eq!(parse_xs_duration("PT1H0.040S").ok(), Some(Duration::new(3600, 40_000_000)));
2835        assert_eq!(parse_xs_duration("PT00H03M30SZ").ok(), Some(Duration::new(210, 0)));
2836        assert_eq!(parse_xs_duration("PT3.14159S").ok(), Some(Duration::new(3, 141_590_000)));
2837        assert_eq!(parse_xs_duration("PT3.14159265S").ok(), Some(Duration::new(3, 141_592_650)));
2838        assert_eq!(parse_xs_duration("PT3.141592653S").ok(), Some(Duration::new(3, 141_592_653)));
2839        // We are truncating rather than rounding the number of nanoseconds
2840        assert_eq!(parse_xs_duration("PT3.141592653897S").ok(), Some(Duration::new(3, 141_592_653)));
2841        assert_eq!(parse_xs_duration("P0W").ok(), Some(Duration::new(0, 0)));
2842        assert_eq!(parse_xs_duration("P26W").ok(), Some(Duration::new(15724800, 0)));
2843        assert_eq!(parse_xs_duration("P52W").ok(), Some(Duration::new(31449600, 0)));
2844        assert_eq!(parse_xs_duration("P10D").ok(), Some(Duration::new(864000, 0)));
2845        assert_eq!(parse_xs_duration("P0Y").ok(), Some(Duration::new(0, 0)));
2846        assert_eq!(parse_xs_duration("P1Y").ok(), Some(Duration::new(31536000, 0)));
2847        assert_eq!(parse_xs_duration("P1Y0W0S").ok(), Some(Duration::new(31536000, 0)));
2848        assert_eq!(parse_xs_duration("PT4H").ok(), Some(Duration::new(14400, 0)));
2849        assert_eq!(parse_xs_duration("+PT4H").ok(), Some(Duration::new(14400, 0)));
2850        assert_eq!(parse_xs_duration("PT0004H").ok(), Some(Duration::new(14400, 0)));
2851        assert_eq!(parse_xs_duration("PT4H0M").ok(), Some(Duration::new(14400, 0)));
2852        assert_eq!(parse_xs_duration("PT4H0S").ok(), Some(Duration::new(14400, 0)));
2853        assert_eq!(parse_xs_duration("P23DT23H").ok(), Some(Duration::new(2070000, 0)));
2854        assert_eq!(parse_xs_duration("P0Y0M0DT0H4M20.880S").ok(), Some(Duration::new(260, 880_000_000)));
2855        assert_eq!(parse_xs_duration("P1Y2M3DT4H5M6.7S").ok(), Some(Duration::new(36993906, 700_000_000)));
2856        assert_eq!(parse_xs_duration("P1Y2M3DT4H5M6,7S").ok(), Some(Duration::new(36993906, 700_000_000)));
2857
2858        // we are not currently handling fractional parts except in the seconds
2859        // assert_eq!(parse_xs_duration("PT0.5H1S").ok(), Some(Duration::new(30*60+1, 0)));
2860        // assert_eq!(parse_xs_duration("P0001-02-03T04:05:06").ok(), Some(Duration::new(36993906, 0)));
2861    }
2862
2863    #[test]
2864    fn test_serialize_xs_duration() {
2865        use super::MPD;
2866
2867        fn serialized_xs_duration(d: Duration) -> String {
2868            let mpd = MPD {
2869                minBufferTime: Some(d),
2870                ..Default::default()
2871            };
2872            let xml = mpd.to_string();
2873            let doc = roxmltree::Document::parse(&xml).unwrap();
2874            String::from(doc.root_element().attribute("minBufferTime").unwrap())
2875        }
2876
2877        assert_eq!("PT0S", serialized_xs_duration(Duration::new(0, 0)));
2878        assert_eq!("PT0.001S", serialized_xs_duration(Duration::new(0, 1_000_000)));
2879        assert_eq!("PT42S", serialized_xs_duration(Duration::new(42, 0)));
2880        assert_eq!("PT1.5S", serialized_xs_duration(Duration::new(1, 500_000_000)));
2881        assert_eq!("PT30.03S", serialized_xs_duration(Duration::new(30, 30_000_000)));
2882        assert_eq!("PT1M30.5S", serialized_xs_duration(Duration::new(90, 500_000_000)));
2883        assert_eq!("PT5M44S", serialized_xs_duration(Duration::new(344, 0)));
2884        assert_eq!("PT42M30S", serialized_xs_duration(Duration::new(2550, 0)));
2885        assert_eq!("PT30M38S", serialized_xs_duration(Duration::new(1838, 0)));
2886        assert_eq!("PT10M10S", serialized_xs_duration(Duration::new(610, 0)));
2887        assert_eq!("PT1H0M0.04S", serialized_xs_duration(Duration::new(3600, 40_000_000)));
2888        assert_eq!("PT3H11M53S", serialized_xs_duration(Duration::new(11513, 0)));
2889        assert_eq!("PT4H", serialized_xs_duration(Duration::new(14400, 0)));
2890    }
2891
2892    #[test]
2893    fn test_parse_xs_datetime() {
2894        use chrono::{DateTime, NaiveDate};
2895        use chrono::offset::Utc;
2896        use super::parse_xs_datetime;
2897
2898        let date = NaiveDate::from_ymd_opt(2023, 4, 19)
2899            .unwrap()
2900            .and_hms_opt(1, 3, 2)
2901            .unwrap();
2902        assert_eq!(parse_xs_datetime("2023-04-19T01:03:02Z").ok(),
2903                   Some(DateTime::<Utc>::from_naive_utc_and_offset(date, Utc)));
2904        let date = NaiveDate::from_ymd_opt(2023, 4, 19)
2905            .unwrap()
2906            .and_hms_nano_opt(1, 3, 2, 958*1000*1000)
2907            .unwrap();
2908        assert_eq!(parse_xs_datetime("2023-04-19T01:03:02.958Z").ok(),
2909                   Some(DateTime::<Utc>::from_naive_utc_and_offset(date, Utc)));
2910    }
2911
2912    #[test]
2913    fn test_parse_failure() {
2914        use super::parse;
2915
2916        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2917        path.push("tests");
2918        path.push("fixtures");
2919        path.push("incomplete.mpd");
2920        let xml = fs::read_to_string(path).unwrap();
2921        assert!(matches!(parse(&xml), Err(crate::DashMpdError::Parsing(_))));
2922    }
2923
2924    #[test]
2925    fn test_conformity_checking() {
2926        use super::{parse, check_conformity};
2927
2928        // These test fixtures have no currently detected non-conformities.
2929        for fixture in [
2930            "a2d-tv.mpd",
2931            "ad-insertion-testcase1.mpd",
2932            "ad-insertion-testcase6-av1.mpd",
2933            "ad-insertion-testcase6-av2.mpd",
2934            "ad-insertion-testcase6-av5.mpd",
2935            "aws.xml",
2936            "dashif-live-atoinf.mpd",
2937            "dashif-low-latency.mpd",
2938            "dash-testcases-5b-1-thomson.mpd",
2939            "dolby-ac4.xml",
2940            "example_G22.mpd",
2941            "f64-inf.mpd",
2942            "jurassic-compact-5975.mpd",
2943            "mediapackage.xml",
2944            "multiple_supplementals.mpd",
2945            "orange.xml",
2946            "patch-location.mpd",
2947            "st-sl.mpd",
2948            "telenet-mid-ad-rolls.mpd",
2949            "manifest_wvcenc_1080p.mpd"] {
2950            let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2951            path.push("tests");
2952            path.push("fixtures");
2953            path.push(fixture);
2954            let xml = fs::read_to_string(path)
2955                .unwrap_or_else(|_| panic!("failed to read fixture {fixture}"));
2956            let mpd = parse(&xml)
2957                .unwrap_or_else(|_| panic!("failed to parse fixture {fixture}"));
2958            let anomalies = check_conformity(&mpd);
2959            assert!(anomalies.is_empty());
2960        }
2961        // Now some manifests that have known non-conformities
2962        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2963        path.push("tests");
2964        path.push("fixtures");
2965        path.push("admanager.xml");
2966        let xml = fs::read_to_string(path).unwrap();
2967        let mpd = parse(&xml).unwrap();
2968        let anomalies = check_conformity(&mpd);
2969        assert!(!anomalies.is_empty());
2970        for anomaly in anomalies {
2971            assert!(anomaly.starts_with("SegmentTimeline has segment@d"));
2972        }
2973        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2974        path.push("tests");
2975        path.push("fixtures");
2976        path.push( "avod-mediatailor.mpd");
2977        let xml = fs::read_to_string(path).unwrap();
2978        let mpd = parse(&xml).unwrap();
2979        let anomalies = check_conformity(&mpd);
2980        assert!(!anomalies.is_empty());
2981        for anomaly in anomalies {
2982            assert!(anomaly.starts_with("SegmentTimeline has segment@d"));
2983        }
2984        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2985        path.push("tests");
2986        path.push("fixtures");
2987        path.push("telestream-binary.xml");
2988        let xml = fs::read_to_string(path).unwrap();
2989        let mpd = parse(&xml).unwrap();
2990        let anomalies = check_conformity(&mpd);
2991        assert!(!anomalies.is_empty());
2992        for anomaly in anomalies {
2993            assert!(anomaly.starts_with("Period with @id <unspecified> contains no AdaptationSet elements"));
2994        }
2995        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2996        path.push("tests");
2997        path.push("fixtures");
2998        path.push("telestream-elements.xml");
2999        let xml = fs::read_to_string(path).unwrap();
3000        let mpd = parse(&xml).unwrap();
3001        let anomalies = check_conformity(&mpd);
3002        assert!(!anomalies.is_empty());
3003        for anomaly in anomalies {
3004            assert!(anomaly.starts_with("Period with @id <unspecified> contains no AdaptationSet elements"));
3005        }
3006        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
3007        path.push("tests");
3008        path.push("fixtures");
3009        path.push("vod-aip-unif-streaming.mpd");
3010        let xml = fs::read_to_string(path).unwrap();
3011        let mpd = parse(&xml).unwrap();
3012        let anomalies = check_conformity(&mpd);
3013        assert!(!anomalies.is_empty());
3014        for anomaly in anomalies {
3015            assert!(anomaly.starts_with("SegmentTimeline has segment@d > @maxSegmentDuration"));
3016        }
3017    }
3018}