dash_mpd/
lib.rs

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