Skip to main content

dash_mpd/
lib.rs

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