Skip to main content

dash_mpd/
lib.rs

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