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