#![allow(non_snake_case)]
#[cfg(feature = "fetch")]
mod media;
#[cfg(all(feature = "fetch", feature = "libav"))]
mod libav;
#[cfg(all(feature = "fetch", not(feature = "libav")))]
mod ffmpeg;
#[cfg(feature = "fetch")]
pub mod sidx;
#[cfg(feature = "fetch")]
pub mod fetch;
#[cfg(feature = "scte35")]
pub mod scte35;
#[cfg(feature = "scte35")]
use crate::scte35::{Signal, SpliceInfoSection};
#[cfg(all(feature = "fetch", feature = "libav"))]
use crate::libav::{mux_audio_video, copy_video_to_container, copy_audio_to_container};
#[cfg(all(feature = "fetch", not(feature = "libav")))]
use crate::ffmpeg::{mux_audio_video, copy_video_to_container, copy_audio_to_container};
use std::cell::OnceCell;
use serde::{Serialize, Serializer, Deserialize};
use serde::de;
use serde_with::skip_serializing_none;
use regex::Regex;
use std::time::Duration;
use chrono::DateTime;
use url::Url;
#[allow(unused_imports)]
use tracing::warn;
pub type XsDatetime = DateTime<chrono::offset::Utc>;
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum DashMpdError {
#[error("parse error {0:?}")]
Parsing(String),
#[error("invalid Duration: {0:?}")]
InvalidDuration(String),
#[error("invalid DateTime: {0:?}")]
InvalidDateTime(String),
#[error("invalid media stream: {0:?}")]
UnhandledMediaStream(String),
#[error("I/O error {1} ({0:?})")]
Io(#[source] std::io::Error, String),
#[error("network error {0:?}")]
Network(String),
#[error("network timeout: {0:?}")]
NetworkTimeout(String),
#[error("network connection: {0:?}")]
NetworkConnect(String),
#[error("muxing error {0:?}")]
Muxing(String),
#[error("decryption error {0:?}")]
Decrypting(String),
#[error("{0:?}")]
Other(String),
}
fn serialize_xsd_double<S>(xsd: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let formatted = if xsd.is_nan() {
String::from("NaN")
} else if xsd.is_infinite() {
if xsd.is_sign_positive() {
String::from("INF")
} else {
String::from("-INF")
}
} else {
xsd.to_string()
};
serializer.serialize_str(&formatted)
}
fn serialize_opt_xsd_double<S>(oxsd: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(xsd) = oxsd {
serialize_xsd_double(xsd, serializer)
} else {
serializer.serialize_none()
}
}
fn parse_xs_duration(s: &str) -> Result<Duration, DashMpdError> {
use std::cmp::min;
let xs_duration_regex = OnceCell::new();
match xs_duration_regex.get_or_init(
|| Regex::new(concat!(r"^(?P<sign>[+-])?P",
r"(?:(?P<years>\d+)Y)?",
r"(?:(?P<months>\d+)M)?",
r"(?:(?P<weeks>\d+)W)?",
r"(?:(?P<days>\d+)D)?",
r"(?:(?P<hastime>T)", r"(?:(?P<hours>\d+)H)?",
r"(?:(?P<minutes>\d+)M)?",
r"(?:(?P<seconds>\d+)(?:(?P<nanoseconds>[.,]\d+)?)S)?",
r")?")).unwrap()).captures(s)
{
Some(m) => {
if m.name("hastime").is_none() &&
m.name("years").is_none() &&
m.name("months").is_none() &&
m.name("weeks").is_none() &&
m.name("days").is_none() {
return Err(DashMpdError::InvalidDuration("empty".to_string()));
}
let mut secs: u64 = 0;
let mut nsecs: u32 = 0;
if let Some(nano) = m.name("nanoseconds") {
let lim = min(nano.as_str().len(), 9 + ".".len());
if let Some(ss) = &nano.as_str().get(1..lim) {
let padded = format!("{ss:0<9}");
nsecs = padded.parse::<u32>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
}
}
if let Some(mseconds) = m.name("seconds") {
let seconds = mseconds.as_str().parse::<u64>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
secs += seconds;
}
if let Some(mminutes) = m.name("minutes") {
let minutes = mminutes.as_str().parse::<u64>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
secs += minutes * 60;
}
if let Some(mhours) = m.name("hours") {
let hours = mhours.as_str().parse::<u64>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
secs += hours * 60 * 60;
}
if let Some(mdays) = m.name("days") {
let days = mdays.as_str().parse::<u64>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
secs += days * 60 * 60 * 24;
}
if let Some(mweeks) = m.name("weeks") {
let weeks = mweeks.as_str().parse::<u64>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
secs += weeks * 60 * 60 * 24 * 7;
}
if let Some(mmonths) = m.name("months") {
let months = mmonths.as_str().parse::<u64>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
secs += months * 60 * 60 * 24 * 30;
}
if let Some(myears) = m.name("years") {
let years = myears.as_str().parse::<u64>()
.map_err(|_| DashMpdError::InvalidDuration(String::from(s)))?;
secs += years * 60 * 60 * 24 * 365;
}
if let Some(msign) = m.name("sign") {
if msign.as_str() == "-" {
return Err(DashMpdError::InvalidDuration("can't represent negative durations".to_string()));
}
}
Ok(Duration::new(secs, nsecs))
},
None => Err(DashMpdError::InvalidDuration(String::from("couldn't parse XS duration"))),
}
}
fn deserialize_xs_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: de::Deserializer<'de>,
{
match <Option<String>>::deserialize(deserializer) {
Ok(optstring) => match optstring {
Some(xs) => match parse_xs_duration(&xs) {
Ok(d) => Ok(Some(d)),
Err(e) => Err(de::Error::custom(e)),
},
None => Ok(None),
},
Err(_) => Ok(None),
}
}
fn serialize_xs_duration<S>(oxs: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if let Some(xs) = oxs {
let seconds = xs.as_secs();
let nanos = xs.subsec_nanos();
let minutes = seconds / 60;
let hours = if minutes > 59 { minutes / 60 } else { 0 };
let fractional_maybe = if nanos > 0 {
format!(".{nanos:09}").trim_end_matches('0').to_string()
} else {
"".to_string()
};
let formatted_duration = if hours > 0 {
let mins = minutes % 60;
let secs = seconds % 60;
format!("PT{hours}H{mins}M{secs}{fractional_maybe}S")
} else if minutes > 0 {
let secs = seconds % 60;
format!("PT{minutes}M{secs}{fractional_maybe}S")
} else {
format!("PT{seconds}{fractional_maybe}S")
};
serializer.serialize_str(&formatted_duration)
} else {
serializer.serialize_none()
}
}
fn parse_xs_datetime(s: &str) -> Result<XsDatetime, DashMpdError> {
use iso8601::Date;
use chrono::{LocalResult, NaiveDate, TimeZone};
use num_traits::cast::FromPrimitive;
match DateTime::<chrono::offset::FixedOffset>::parse_from_rfc3339(s) {
Ok(dt) => Ok(dt.into()),
Err(_) => match iso8601::datetime(s) {
Ok(dt) => {
let nd = match dt.date {
Date::YMD { year, month, day } =>
NaiveDate::from_ymd_opt(year, month, day)
.ok_or(DashMpdError::InvalidDateTime(s.to_string()))?,
Date::Week { year, ww, d } => {
let d = chrono::Weekday::from_u32(d)
.ok_or(DashMpdError::InvalidDateTime(s.to_string()))?;
NaiveDate::from_isoywd_opt(year, ww, d)
.ok_or(DashMpdError::InvalidDateTime(s.to_string()))?
},
Date::Ordinal { year, ddd } =>
NaiveDate::from_yo_opt(year, ddd)
.ok_or(DashMpdError::InvalidDateTime(s.to_string()))?,
};
let nd = nd.and_hms_nano_opt(dt.time.hour, dt.time.minute, dt.time.second, dt.time.millisecond*1000*1000)
.ok_or(DashMpdError::InvalidDateTime(s.to_string()))?;
let tz_secs = dt.time.tz_offset_hours * 3600 + dt.time.tz_offset_minutes * 60;
match chrono::FixedOffset::east_opt(tz_secs)
.ok_or(DashMpdError::InvalidDateTime(s.to_string()))?
.from_local_datetime(&nd)
{
LocalResult::Single(local) => Ok(local.with_timezone(&chrono::Utc)),
_ => Err(DashMpdError::InvalidDateTime(s.to_string())),
}
},
Err(_) => Err(DashMpdError::InvalidDateTime(s.to_string())),
}
}
}
fn deserialize_xs_datetime<'de, D>(deserializer: D) -> Result<Option<XsDatetime>, D::Error>
where
D: de::Deserializer<'de>,
{
match <Option<String>>::deserialize(deserializer) {
Ok(optstring) => match optstring {
Some(xs) => match parse_xs_datetime(&xs) {
Ok(d) => Ok(Some(d)),
Err(e) => Err(de::Error::custom(e)),
},
None => Ok(None),
},
Err(_) => Ok(None),
}
}
fn serialize_xsd_uintvector<S>(v: &Vec<u64>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut formatted = String::new();
for u in v {
formatted += &format!("{u} ");
}
serializer.serialize_str(&formatted)
}
fn deserialize_xsd_uintvector<'de, D>(deserializer: D) -> Result<Vec<u64>, D::Error>
where
D: de::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let mut out = Vec::<u64>::new();
for uint64_str in s.split_whitespace() {
match uint64_str.parse::<u64>() {
Ok(val) => out.push(val),
Err(e) => return Err(de::Error::custom(e)),
}
}
Ok(out)
}
fn serialize_xmlns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(s) = os {
serializer.serialize_str(s)
} else {
serializer.serialize_str("urn:mpeg:dash:schema:mpd:2011")
}
}
fn serialize_xsi_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(s) = os {
serializer.serialize_str(s)
} else {
serializer.serialize_str("http://www.w3.org/2001/XMLSchema-instance")
}
}
fn serialize_cenc_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(s) = os {
serializer.serialize_str(s)
} else {
serializer.serialize_str("urn:mpeg:cenc:2013")
}
}
fn serialize_mspr_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(s) = os {
serializer.serialize_str(s)
} else {
serializer.serialize_str("urn:microsoft:playready")
}
}
fn serialize_xlink_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(s) = os {
serializer.serialize_str(s)
} else {
serializer.serialize_str("http://www.w3.org/1999/xlink")
}
}
fn serialize_dvb_ns<S>(os: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(s) = os {
serializer.serialize_str(s)
} else {
serializer.serialize_str("urn:dvb:dash-extensions:2014-1")
}
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Title {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Source {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Copyright {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct ProgramInformation {
pub Title: Option<Title>,
pub Source: Option<Source>,
pub Copyright: Option<Copyright>,
#[serde(rename = "@lang")]
pub lang: Option<String>,
#[serde(rename = "@moreInformationURL")]
pub moreInformationURL: Option<String>,
#[serde(rename(serialize = "scte214:ContentIdentifier"))]
#[serde(rename(deserialize = "ContentIdentifier"))]
pub scte214ContentIdentifier: Option<Scte214ContentIdentifier>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Scte214ContentIdentifier {
#[serde(rename = "@type")]
pub idType: Option<String>,
#[serde(rename = "@value")]
pub idValue: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct S {
#[serde(rename = "@t")]
pub t: Option<u64>,
#[serde(rename = "@n")]
pub n: Option<u64>,
#[serde(rename = "@d")]
pub d: u64,
#[serde(rename = "@r")]
pub r: Option<i64>,
#[serde(rename = "@k")]
pub k: Option<u64>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct SegmentTimeline {
#[serde(rename = "S")]
pub segments: Vec<S>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct BitstreamSwitching {
#[serde(rename = "@sourceURL")]
pub source_url: Option<String>,
#[serde(rename = "@range")]
pub range: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Initialization {
#[serde(rename = "@sourceURL")]
pub sourceURL: Option<String>,
#[serde(rename = "@range")]
pub range: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct RepresentationIndex {
#[serde(rename = "@range")]
pub range: Option<String>,
#[serde(rename = "@sourceURL")]
pub sourceURL: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct SegmentTemplate {
#[serde(rename = "@initialization")]
pub initialization: Option<String>,
#[serde(rename = "@media")]
pub media: Option<String>,
#[serde(rename = "@index")]
pub index: Option<String>,
#[serde(rename = "@indexRange")]
pub indexRange: Option<String>,
#[serde(rename = "@indexRangeExact")]
pub indexRangeExact: Option<bool>,
pub SegmentTimeline: Option<SegmentTimeline>,
pub BitstreamSwitching: Option<BitstreamSwitching>,
pub RepresentationIndex: Option<RepresentationIndex>,
#[serde(rename = "@startNumber")]
pub startNumber: Option<u64>,
#[serde(rename = "@duration")]
pub duration: Option<f64>,
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "@eptDelta")]
pub eptDelta: Option<i64>,
#[serde(rename = "@pdDelta")]
pub pbDelta: Option<i64>,
#[serde(rename = "@presentationTimeOffset")]
pub presentationTimeOffset: Option<u64>,
#[serde(rename = "@bitstreamSwitching")]
pub bitstreamSwitching: Option<String>,
#[serde(rename = "@availabilityTimeOffset", serialize_with="serialize_opt_xsd_double")]
pub availabilityTimeOffset: Option<f64>,
#[serde(rename = "@availabilityTimeComplete")]
pub availabilityTimeComplete: Option<bool>,
#[serde(rename = "FailoverContent")]
pub failover_content: Option<FailoverContent>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Location {
#[serde(rename = "$text")]
pub url: String,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct BaseURL {
#[serde(rename = "$text")]
pub base: String,
#[serde(rename = "@serviceLocation")]
pub serviceLocation: Option<String>,
#[serde(rename = "@byteRange")]
pub byte_range: Option<String>,
#[serde(rename = "@availabilityTimeOffset", serialize_with="serialize_opt_xsd_double")]
pub availability_time_offset: Option<f64>,
#[serde(rename = "@availabilityTimeComplete")]
pub availability_time_complete: Option<bool>,
#[serde(rename = "@dvb:priority", alias = "@priority")]
pub priority: Option<u64>,
#[serde(rename = "@dvb:weight", alias = "@weight")]
pub weight: Option<i64>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Fcs {
#[serde(rename = "@t")]
pub t: u64,
#[serde(rename = "@d")]
pub d: Option<u64>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct FailoverContent {
#[serde(rename = "FCS")]
pub fcs_list: Vec<Fcs>,
#[serde(rename = "@valid")]
pub valid: Option<bool>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct SegmentBase {
#[serde(rename = "Initialization")]
pub initialization: Option<Initialization>,
pub RepresentationIndex: Option<RepresentationIndex>,
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "@presentationDuration")]
pub presentationDuration: Option<u64>,
#[serde(rename = "@presentationTimeOffset")]
pub presentationTimeOffset: Option<u64>,
#[serde(rename = "@eptDelta")]
pub eptDelta: Option<i64>,
#[serde(rename = "@pdDelta")]
pub pbDelta: Option<i64>,
#[serde(rename = "@indexRange")]
pub indexRange: Option<String>,
#[serde(rename = "@indexRangeExact")]
pub indexRangeExact: Option<bool>,
#[serde(rename = "@availabilityTimeOffset", serialize_with="serialize_opt_xsd_double")]
pub availabilityTimeOffset: Option<f64>,
#[serde(rename = "@availabilityTimeComplete")]
pub availabilityTimeComplete: Option<bool>,
#[serde(rename = "FailoverContent")]
pub failover_content: Option<FailoverContent>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct SegmentURL {
#[serde(rename = "@media")]
pub media: Option<String>, #[serde(rename = "@mediaRange")]
pub mediaRange: Option<String>,
#[serde(rename = "@index")]
pub index: Option<String>, #[serde(rename = "@indexRange")]
pub indexRange: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct SegmentList {
#[serde(rename = "@duration")]
pub duration: Option<u64>,
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "@indexRange")]
pub indexRange: Option<String>,
#[serde(rename = "@indexRangeExact")]
pub indexRangeExact: Option<bool>,
#[serde(rename = "@xlink:href")]
#[serde(alias = "@href")]
pub href: Option<String>,
#[serde(rename = "@xlink:actuate")]
#[serde(alias = "@actuate")]
pub actuate: Option<String>,
#[serde(rename = "@xlink:type")]
#[serde(alias = "@type")]
pub sltype: Option<String>,
#[serde(rename = "@xlink:show")]
#[serde(alias = "@show")]
pub show: Option<String>,
pub Initialization: Option<Initialization>,
#[serde(rename = "SegmentURL")]
pub segment_urls: Vec<SegmentURL>,
pub SegmentTimeline: Option<SegmentTimeline>,
pub BitstreamSwitching: Option<BitstreamSwitching>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Resync {
#[serde(rename = "@dT")]
pub dT: Option<u64>,
#[serde(rename = "@dImax")]
pub dImax: Option<u64>,
#[serde(rename = "@dImin")]
pub dImin: Option<u64>,
#[serde(rename = "@type")]
pub rtype: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct AudioChannelConfiguration {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Language {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Preselection {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@audioSamplingRate")]
pub audioSamplingRate: Option<String>,
#[serde(rename = "@codecs")]
pub codecs: String,
#[serde(rename = "@selectionPriority")]
pub selectionPriority: Option<u64>,
#[serde(rename = "@preselectionComponents")]
pub preselectionComponents: String,
#[serde(rename = "@tag")]
pub tag: String,
#[serde(rename = "Language")]
pub languages: Vec<Language>,
#[serde(rename = "Role")]
pub roles: Vec<Role>,
#[serde(rename = "Accessibility")]
pub accessibilities: Vec<Accessibility>,
#[serde(rename = "Viewpoint")]
pub viewpoints: Vec<Viewpoint>,
#[serde(rename = "Rating")]
pub ratings: Vec<Rating>,
#[serde(rename = "Label")]
pub labels: Vec<Label>,
#[serde(rename = "AudioChannelConfiguration")]
pub audio_channel_configurations: Vec<AudioChannelConfiguration>,
#[serde(rename = "EssentialProperty")]
pub essential_properties: Vec<EssentialProperty>,
#[serde(rename = "SupplementalProperty")]
pub supplemental_properties: Vec<SupplementalProperty>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Rating {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct FramePacking {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Switching {
#[serde(rename = "@interval")]
pub interval: Option<u64>,
#[serde(rename = "@type")]
pub stype: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Accessibility {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Scope {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct SubRepresentation {
#[serde(rename = "@level")]
pub level: Option<u32>,
#[serde(rename = "@dependencyLevel")]
pub dependencyLevel: Option<String>,
#[serde(rename = "@contentComponent")]
pub contentComponent: Option<String>,
#[serde(rename = "@mimeType")]
pub mimeType: Option<String>,
#[serde(rename = "@codecs")]
pub codecs: Option<String>,
#[serde(rename = "@contentType")]
pub contentType: Option<String>,
#[serde(rename = "@profiles")]
pub profiles: Option<String>,
#[serde(rename = "@segmentProfiles")]
pub segmentProfiles: Option<String>,
#[serde(rename = "@scanType")]
pub scanType: Option<String>,
#[serde(rename = "@frameRate")]
pub frameRate: Option<String>, #[serde(rename = "@sar")]
pub sar: Option<String>,
#[serde(rename = "@bandwidth")]
pub bandwidth: Option<u64>,
#[serde(rename = "@audioSamplingRate")]
pub audioSamplingRate: Option<String>,
#[serde(rename = "@maxPlayoutRate", serialize_with="serialize_opt_xsd_double")]
pub maxPlayoutRate: Option<f64>,
#[serde(rename = "@codingDependency")]
pub codingDependency: Option<bool>,
#[serde(rename = "@width")]
pub width: Option<u64>,
#[serde(rename = "@height")]
pub height: Option<u64>,
#[serde(rename = "@startWithSAP")]
pub startWithSAP: Option<u64>,
#[serde(rename = "@maximumSAPPeriod", serialize_with="serialize_opt_xsd_double")]
pub maximumSAPPeriod: Option<f64>,
pub AudioChannelConfiguration: Vec<AudioChannelConfiguration>,
pub ContentProtection: Vec<ContentProtection>,
pub FramePacking: Vec<FramePacking>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct Representation {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@dependencyId")]
pub dependencyId: Option<String>,
pub BaseURL: Vec<BaseURL>,
#[serde(rename = "@mimeType")]
pub mimeType: Option<String>,
#[serde(rename = "@codecs")]
pub codecs: Option<String>,
#[serde(rename = "@contentType")]
pub contentType: Option<String>,
#[serde(rename = "@lang")]
pub lang: Option<String>,
#[serde(rename = "@profiles")]
pub profiles: Option<String>,
#[serde(rename = "@segmentProfiles")]
pub segmentProfiles: Option<String>,
#[serde(rename = "@scanType")]
pub scanType: Option<String>,
#[serde(rename = "@frameRate")]
pub frameRate: Option<String>, #[serde(rename = "@sar")]
pub sar: Option<String>,
#[serde(rename = "@qualityRanking")]
pub qualityRanking: Option<u8>,
#[serde(rename = "@bandwidth")]
pub bandwidth: Option<u64>,
#[serde(rename = "@sampleRate")]
pub sampleRate: Option<u64>,
#[serde(rename = "@audioSamplingRate")]
pub audioSamplingRate: Option<String>,
#[serde(rename = "@maxPlayoutRate", serialize_with="serialize_opt_xsd_double")]
pub maxPlayoutRate: Option<f64>,
#[serde(rename = "@numChannels")]
pub numChannels: Option<u32>,
#[serde(rename = "@codingDependency")]
pub codingDependency: Option<bool>,
#[serde(rename = "@width")]
pub width: Option<u64>,
#[serde(rename = "@height")]
pub height: Option<u64>,
#[serde(rename = "@startWithSAP")]
pub startWithSAP: Option<u64>,
pub Label: Vec<Label>,
pub AudioChannelConfiguration: Vec<AudioChannelConfiguration>,
pub ContentProtection: Vec<ContentProtection>,
pub FramePacking: Vec<FramePacking>,
#[serde(rename = "@mediaStreamStructureId")]
pub mediaStreamStructureId: Option<String>,
pub InbandEventStream: Vec<InbandEventStream>,
pub SubRepresentation: Vec<SubRepresentation>,
pub SegmentTemplate: Option<SegmentTemplate>,
pub SegmentBase: Option<SegmentBase>,
pub SegmentList: Option<SegmentList>,
pub RepresentationIndex: Option<RepresentationIndex>,
pub Resync: Option<Resync>,
pub ProducerReferenceTime: Option<ProducerReferenceTime>,
#[serde(rename = "SupplementalProperty")]
pub supplemental_property: Vec<SupplementalProperty>,
#[serde(rename = "EssentialProperty")]
pub essential_property: Vec<EssentialProperty>,
#[serde(rename = "@xlink:href", alias = "@href")]
pub href: Option<String>,
#[serde(rename = "@xlink:actuate", alias = "@actuate")]
pub actuate: Option<String>,
#[serde(rename = "@scte214:supplementalProfiles", alias = "@supplementalProfiles")]
pub scte214_supplemental_profiles: Option<String>,
#[serde(rename = "@scte214:supplementalCodecs", alias = "@supplementalCodecs")]
pub scte214_supplemental_codecs: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct ContentComponent {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@lang")]
pub lang: Option<String>,
#[serde(rename = "@contentType")]
pub contentType: Option<String>,
#[serde(rename = "@par")]
pub par: Option<String>,
#[serde(rename = "@tag")]
pub tag: Option<String>,
pub Accessibility: Vec<Accessibility>,
pub Role: Vec<Role>,
pub Rating: Vec<Rating>,
pub Viewpoint: Vec<Viewpoint>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct CencPssh {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Laurl {
#[serde(rename = "@Lic_type")]
pub lic_type: Option<String>,
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct MsprPro {
#[serde(rename = "@xmlns", serialize_with="serialize_xmlns")]
pub xmlns: Option<String>,
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct MsprIsEncrypted {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct MsprIVSize {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct MsprKid {
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct ContentProtection {
#[serde(rename = "@robustness")]
pub robustness: Option<String>,
#[serde(rename = "@ref")]
pub r#ref: Option<String>,
#[serde(rename = "@refId")]
pub refId: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename="cenc:pssh", alias="pssh")]
pub cenc_pssh: Vec<CencPssh>,
#[serde(rename = "@cenc:default_KID", alias = "@default_KID")]
pub default_KID: Option<String>,
#[serde(rename = "dashif:laurl", alias = "laurl")]
pub laurl: Option<Laurl>,
#[serde(rename = "clearkey:Laurl", alias = "Laurl")]
pub clearkey_laurl: Option<Laurl>,
#[serde(rename = "mspr:pro", alias = "pro")]
pub msprpro: Option<MsprPro>,
#[serde(rename = "mspr:IsEncrypted", alias = "IsEncrypted")]
pub mspr_is_encrypted: Option<MsprIsEncrypted>,
#[serde(rename = "mspr:IV_Size", alias = "IV_Size")]
pub mspr_iv_size: Option<MsprIVSize>,
#[serde(rename = "mspr:kid", alias = "kid")]
pub mspr_kid: Option<MsprKid>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Role {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Viewpoint {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct Event {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@presentationTime")]
pub presentationTime: Option<u64>,
#[serde(rename = "@presentationTimeOffset")]
pub presentationTimeOffset: Option<u64>,
#[serde(rename = "@duration")]
pub duration: Option<u64>,
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "@contentEncoding")]
pub contentEncoding: Option<String>,
#[serde(rename = "@messageData")]
pub messageData: Option<String>,
#[cfg(feature = "scte35")]
#[serde(rename = "scte35:Signal", alias="Signal")]
#[cfg(feature = "scte35")]
pub signal: Vec<Signal>,
#[cfg(feature = "scte35")]
#[serde(rename = "scte35:SpliceInfoSection", alias="SpliceInfoSection")]
#[cfg(feature = "scte35")]
pub splice_info_section: Vec<SpliceInfoSection>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
#[serde(rename = "$text")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct EventStream {
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "Event")]
pub event: Vec<Event>,
#[serde(rename = "@value")]
pub value: Option<String>,
#[serde(rename = "@presentationTimeOffset")]
pub presentationTimeOffset: Option<u64>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct InbandEventStream {
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "Event")]
pub event: Vec<Event>,
#[serde(rename = "@value")]
pub value: Option<String>,
#[serde(rename = "@xlink:href")]
#[serde(alias = "@href")]
pub href: Option<String>,
#[serde(rename = "@xlink:actuate", alias = "@actuate")]
pub actuate: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct EssentialProperty {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: String,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct SupplementalProperty {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: String,
#[serde(rename = "@value")]
pub value: Option<String>,
#[serde(rename(serialize = "scte214:ContentIdentifier"))]
#[serde(rename(deserialize = "ContentIdentifier"))]
pub scte214ContentIdentifiers: Vec<Scte214ContentIdentifier>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Label {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@lang")]
pub lang: Option<String>,
#[serde(rename = "$text")]
pub content: String,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct AdaptationSet {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@xlink:href")]
#[serde(alias = "@href")]
pub href: Option<String>,
#[serde(rename = "@xlink:actuate")]
#[serde(alias = "@actuate")]
pub actuate: Option<String>,
pub BaseURL: Vec<BaseURL>,
#[serde(rename = "@group")]
pub group: Option<i64>,
#[serde(rename = "@selectionPriority")]
pub selectionPriority: Option<u64>,
#[serde(rename = "@contentType")]
pub contentType: Option<String>,
#[serde(rename = "@profiles")]
pub profiles: Option<String>,
#[serde(rename = "@lang")]
pub lang: Option<String>,
#[serde(rename = "@sar")]
pub sar: Option<String>,
#[serde(rename = "@par")]
pub par: Option<String>,
#[serde(rename = "@scanType")]
pub scanType: Option<String>,
#[serde(rename = "@segmentAlignment")]
pub segmentAlignment: Option<bool>,
#[serde(rename = "@segmentProfiles")]
pub segmentProfiles: Option<String>,
#[serde(rename = "@subsegmentAlignment")]
pub subsegmentAlignment: Option<bool>,
#[serde(rename = "@subsegmentStartsWithSAP")]
pub subsegmentStartsWithSAP: Option<u64>,
#[serde(rename = "@bitstreamSwitching")]
pub bitstreamSwitching: Option<bool>,
#[serde(rename = "@audioSamplingRate")]
pub audioSamplingRate: Option<String>,
#[serde(rename = "@width")]
pub width: Option<u64>,
#[serde(rename = "@height")]
pub height: Option<u64>,
#[serde(rename = "@mimeType")]
pub mimeType: Option<String>,
#[serde(rename = "@codecs")]
pub codecs: Option<String>,
#[serde(rename = "@minBandwidth")]
pub minBandwidth: Option<u64>,
#[serde(rename = "@maxBandwidth")]
pub maxBandwidth: Option<u64>,
#[serde(rename = "@minWidth")]
pub minWidth: Option<u64>,
#[serde(rename = "@maxWidth")]
pub maxWidth: Option<u64>,
#[serde(rename = "@minHeight")]
pub minHeight: Option<u64>,
#[serde(rename = "@maxHeight")]
pub maxHeight: Option<u64>,
#[serde(rename = "@frameRate")]
pub frameRate: Option<String>, #[serde(rename = "@maxFrameRate")]
pub maxFrameRate: Option<String>, #[serde(rename = "@maxPlayoutRate", serialize_with="serialize_opt_xsd_double")]
pub maxPlayoutRate: Option<f64>,
#[serde(rename = "@maximumSAPPeriod", serialize_with="serialize_opt_xsd_double")]
pub maximumSAPPeriod: Option<f64>,
#[serde(rename = "@startWithSAP")]
pub startWithSAP: Option<u64>,
#[serde(rename = "@codingDependency")]
pub codingDependency: Option<bool>,
pub Role: Vec<Role>,
pub Rating: Vec<Rating>,
pub Viewpoint: Vec<Viewpoint>,
pub Label: Vec<Label>,
pub SegmentTemplate: Option<SegmentTemplate>,
pub SegmentList: Option<SegmentList>,
pub ContentComponent: Vec<ContentComponent>,
pub ContentProtection: Vec<ContentProtection>,
pub Switching: Vec<Switching>,
pub Resync: Option<Resync>,
pub Accessibility: Vec<Accessibility>,
pub AudioChannelConfiguration: Vec<AudioChannelConfiguration>,
pub InbandEventStream: Vec<InbandEventStream>,
#[serde(rename = "SupplementalProperty")]
pub supplemental_property: Vec<SupplementalProperty>,
#[serde(rename = "EssentialProperty")]
pub essential_property: Vec<EssentialProperty>,
#[serde(rename = "Representation")]
pub representations: Vec<Representation>,
pub ProducerReferenceTime: Option<ProducerReferenceTime>,
#[serde(rename = "@scte214:supplementalProfiles", alias = "@supplementalProfiles")]
pub scte214_supplemental_profiles: Option<String>,
#[serde(rename = "@scte214:supplementalCodecs", alias = "@supplementalCodecs")]
pub scte214_supplemental_codecs: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct AssetIdentifier {
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
#[serde(rename(serialize = "scte214:ContentIdentifier"))]
#[serde(rename(deserialize = "ContentIdentifier"))]
pub scte214ContentIdentifiers: Vec<Scte214ContentIdentifier>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Subset {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@contains",
deserialize_with = "deserialize_xsd_uintvector",
serialize_with = "serialize_xsd_uintvector",
default)]
pub contains: Vec<u64>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct Period {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@start",
serialize_with = "serialize_xs_duration",
deserialize_with = "deserialize_xs_duration",
default)]
pub start: Option<Duration>,
#[serde(rename = "@duration",
serialize_with = "serialize_xs_duration",
deserialize_with = "deserialize_xs_duration",
default)]
pub duration: Option<Duration>,
#[serde(rename = "@bitstreamSwitching")]
pub bitstreamSwitching: Option<bool>,
#[serde(rename = "@xlink:href", alias = "@href")]
pub href: Option<String>,
#[serde(rename = "@xlink:actuate", alias = "@actuate")]
pub actuate: Option<String>,
pub BaseURL: Vec<BaseURL>,
#[serde(rename = "EssentialProperty")]
pub essential_property: Vec<EssentialProperty>,
pub SegmentTemplate: Option<SegmentTemplate>,
#[serde(rename = "AssetIdentifier")]
pub asset_identifier: Option<AssetIdentifier>,
#[serde(rename = "EventStream")]
pub event_streams: Vec<EventStream>,
pub ContentProtection: Vec<ContentProtection>,
#[serde(rename = "AdaptationSet")]
pub adaptations: Vec<AdaptationSet>,
#[serde(rename = "Subset")]
pub subsets: Vec<Subset>,
#[serde(rename = "SupplementalProperty")]
pub supplemental_property: Vec<SupplementalProperty>,
#[serde(rename = "Preselection")]
pub pre_selections: Vec<Preselection>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Reporting {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
#[serde(rename = "@dvb:reportingUrl", alias = "@reportingUrl")]
pub reportingUrl: Option<String>,
#[serde(rename = "@dvb:probability", alias = "@probability")]
pub probability: Option<u64>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Range {
#[serde(rename = "@starttime",
serialize_with = "serialize_xs_duration",
deserialize_with = "deserialize_xs_duration",
default)]
pub starttime: Option<Duration>,
#[serde(rename = "@duration",
serialize_with = "serialize_xs_duration",
deserialize_with = "deserialize_xs_duration",
default)]
pub duration: Option<Duration>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct Metrics {
#[serde(rename = "@metrics")]
pub metrics: String,
pub Reporting: Vec<Reporting>,
pub Range: Vec<Range>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct Latency {
#[serde(rename = "@min", serialize_with="serialize_opt_xsd_double")]
pub min: Option<f64>,
#[serde(rename = "@max", serialize_with="serialize_opt_xsd_double")]
pub max: Option<f64>,
#[serde(rename = "@target", serialize_with="serialize_opt_xsd_double")]
pub target: Option<f64>,
#[serde(rename = "@referenceId")]
pub referenceId: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct PlaybackRate {
#[serde(rename = "@min", serialize_with="serialize_xsd_double")]
pub min: f64,
#[serde(rename = "@max", serialize_with="serialize_xsd_double")]
pub max: f64,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct ServiceDescription {
#[serde(rename = "@id")]
pub id: Option<String>,
pub Latency: Option<Latency>,
pub PlaybackRate: Option<PlaybackRate>,
#[serde(rename = "Scope")]
pub scopes: Vec<Scope>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct UTCTiming {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct ProducerReferenceTime {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@inband")]
pub inband: Option<bool>,
#[serde(rename = "@presentationTime")]
pub presentationTime: Option<u64>,
#[serde(rename = "@type")]
pub prtType: Option<String>,
#[serde(rename = "@wallclockTime",
alias="@wallClockTime",
deserialize_with = "deserialize_xs_datetime",
default)]
pub wallClockTime: Option<XsDatetime>,
pub UTCTiming: Vec<UTCTiming>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Hash)]
#[serde(default)]
pub struct LeapSecondInformation {
#[serde(rename = "@availabilityStartLeapOffset")]
pub availabilityStartLeapOffset: Option<i64>,
#[serde(rename = "@nextAvailabilityStartLeapOffset")]
pub nextAvailabilityStartLeapOffset: Option<i64>,
#[serde(rename = "@nextLeapChangeTime",
deserialize_with = "deserialize_xs_datetime",
default)]
pub nextLeapChangeTime: Option<XsDatetime>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct PatchLocation {
#[serde(rename = "@ttl", serialize_with="serialize_opt_xsd_double")]
pub ttl: Option<f64>,
#[serde(rename = "$text")]
pub content: String,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
#[serde(default)]
pub struct MPD {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@type")]
pub mpdtype: Option<String>,
#[serialize_always]
#[serde(rename="@xmlns:xsi", alias="@xsi", serialize_with="serialize_xsi_ns")]
pub xsi: Option<String>,
#[serde(alias = "@ext", rename = "@xmlns:ext")]
pub ext: Option<String>,
#[serialize_always]
#[serde(rename="@xmlns:cenc", alias="@cenc", serialize_with="serialize_cenc_ns")]
pub cenc: Option<String>,
#[serialize_always]
#[serde(rename="@xmlns:mspr", alias="@mspr", serialize_with="serialize_mspr_ns")]
pub mspr: Option<String>,
#[serialize_always]
#[serde(rename="@xmlns:xlink", alias="@xlink", serialize_with="serialize_xlink_ns")]
pub xlink: Option<String>,
#[cfg(feature = "scte35")]
#[serialize_always]
#[serde(rename="@xmlns:scte35", alias="@scte35", serialize_with="scte35::serialize_scte35_ns")]
pub scte35: Option<String>,
#[serialize_always]
#[serde(rename="@xmlns:dvb", alias="@dvb", serialize_with="serialize_dvb_ns")]
pub dvb: Option<String>,
#[serde(rename = "@xmlns", serialize_with="serialize_xmlns")]
pub xmlns: Option<String>,
#[serde(rename = "@xsi:schemaLocation", alias = "@schemaLocation")]
pub schemaLocation: Option<String>,
#[serde(alias = "@scte214", rename = "@xmlns:scte214")]
pub scte214: Option<String>,
#[serde(rename = "@profiles")]
pub profiles: Option<String>,
#[serde(deserialize_with = "deserialize_xs_duration", default)]
#[serde(serialize_with = "serialize_xs_duration")]
#[serde(rename = "@minBufferTime")]
pub minBufferTime: Option<Duration>,
#[serde(deserialize_with = "deserialize_xs_duration", default)]
#[serde(serialize_with = "serialize_xs_duration")]
#[serde(rename = "@minimumUpdatePeriod")]
pub minimumUpdatePeriod: Option<Duration>,
#[serde(deserialize_with = "deserialize_xs_duration", default)]
#[serde(serialize_with = "serialize_xs_duration")]
#[serde(rename = "@timeShiftBufferDepth")]
pub timeShiftBufferDepth: Option<Duration>,
#[serde(deserialize_with = "deserialize_xs_duration", default)]
#[serde(serialize_with = "serialize_xs_duration")]
#[serde(rename = "@mediaPresentationDuration")]
pub mediaPresentationDuration: Option<Duration>,
#[serde(deserialize_with = "deserialize_xs_duration", default)]
#[serde(serialize_with = "serialize_xs_duration")]
#[serde(rename = "@maxSegmentDuration")]
pub maxSegmentDuration: Option<Duration>,
#[serde(rename = "@maxSubsegmentDuration",
serialize_with = "serialize_xs_duration",
deserialize_with = "deserialize_xs_duration",
default)]
pub maxSubsegmentDuration: Option<Duration>,
#[serde(rename = "@suggestedPresentationDelay",
serialize_with = "serialize_xs_duration",
deserialize_with = "deserialize_xs_duration",
default)]
pub suggestedPresentationDelay: Option<Duration>,
#[serde(rename = "@publishTime",
deserialize_with = "deserialize_xs_datetime",
default)]
pub publishTime: Option<XsDatetime>,
#[serde(rename = "@availabilityStartTime",
deserialize_with = "deserialize_xs_datetime",
default)]
pub availabilityStartTime: Option<XsDatetime>,
#[serde(rename = "@availabilityEndTime",
deserialize_with = "deserialize_xs_datetime",
default)]
pub availabilityEndTime: Option<XsDatetime>,
pub ProgramInformation: Option<ProgramInformation>,
#[serde(rename = "BaseURL")]
pub base_url: Vec<BaseURL>,
#[serde(rename = "Location", default)]
pub locations: Vec<Location>,
pub PatchLocation: Vec<PatchLocation>,
pub ServiceDescription: Option<ServiceDescription>,
pub ContentProtection: Vec<ContentProtection>,
#[serde(rename = "Period", default)]
pub periods: Vec<Period>,
pub Metrics: Vec<Metrics>,
#[serde(rename = "EssentialProperty")]
pub essential_property: Vec<EssentialProperty>,
#[serde(rename = "SupplementalProperty")]
pub supplemental_property: Vec<SupplementalProperty>,
pub UTCTiming: Vec<UTCTiming>,
pub LeapSecondInformation: Option<LeapSecondInformation>,
}
impl std::fmt::Display for MPD {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", quick_xml::se::to_string(self).map_err(|_| std::fmt::Error)?)
}
}
pub fn parse(xml: &str) -> Result<MPD, DashMpdError> {
#[cfg(feature = "warn_ignored_elements")]
{
let xd = &mut quick_xml::de::Deserializer::from_str(xml);
let _: MPD = serde_ignored::deserialize(xd, |path| {
warn!("Unused XML element in manifest: {path}");
}).map_err(|e| DashMpdError::Parsing(e.to_string()))?;
}
let xd = &mut quick_xml::de::Deserializer::from_str(xml);
let mpd: MPD = serde_path_to_error::deserialize(xd)
.map_err(|e| DashMpdError::Parsing(e.to_string()))?;
Ok(mpd)
}
fn is_audio_codec(name: &str) -> bool {
name.starts_with("mp4a") ||
name.starts_with("aac") ||
name.starts_with("vorbis") ||
name.starts_with("opus") ||
name.starts_with("flac") ||
name.starts_with("mp3") ||
name.starts_with("ec-3") ||
name.starts_with("ac-4") ||
name.starts_with("dtsc") ||
name.starts_with("mha1") }
pub fn is_audio_adaptation(a: &&AdaptationSet) -> bool {
if let Some(codec) = &a.codecs {
if is_audio_codec(codec) {
return true;
}
}
if let Some(ct) = &a.contentType {
if ct == "audio" {
return true;
}
}
if let Some(mimetype) = &a.mimeType {
if mimetype.starts_with("audio/") {
return true;
}
}
for r in a.representations.iter() {
if let Some(ct) = &r.contentType {
if ct == "audio" {
return true;
}
}
if let Some(mimetype) = &r.mimeType {
if mimetype.starts_with("audio/") {
return true;
}
}
}
false
}
pub fn is_video_adaptation(a: &&AdaptationSet) -> bool {
if is_audio_adaptation(a) {
return false;
}
if let Some(ct) = &a.contentType {
if ct == "video" {
return true;
}
}
if let Some(mimetype) = &a.mimeType {
if mimetype.starts_with("video/") {
return true;
}
}
for r in a.representations.iter() {
if let Some(ct) = &r.contentType {
if ct == "video" {
return true;
}
}
if r.codecs.as_deref().is_some_and(is_subtitle_codec) {
return false;
}
if let Some(mimetype) = &r.mimeType {
if mimetype.starts_with("video/") {
return true;
}
}
}
false
}
fn is_subtitle_mimetype(mt: &str) -> bool {
mt.eq("text/vtt") ||
mt.eq("application/ttml+xml") ||
mt.eq("application/x-sami")
}
fn is_subtitle_codec(c: &str) -> bool {
c == "wvtt" ||
c == "c608" ||
c == "stpp" ||
c == "tx3g" ||
c.starts_with("stpp.")
}
pub fn is_subtitle_adaptation(a: &&AdaptationSet) -> bool {
if a.mimeType.as_deref().is_some_and(is_subtitle_mimetype) {
return true;
}
if a.contentType.as_deref().is_some_and(|ct| ct.eq("text")) {
return true;
}
if a.codecs.as_deref().is_some_and(is_subtitle_codec) {
return true;
}
for cc in a.ContentComponent.iter() {
if cc.contentType.as_deref().is_some_and(|ct| ct.eq("text")) {
return true;
}
}
for r in a.representations.iter() {
if r.mimeType.as_deref().is_some_and(is_subtitle_mimetype) {
return true;
}
if r.codecs.as_deref().is_some_and(is_subtitle_codec) {
return true;
}
}
false
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SubtitleType {
Vtt,
Srt,
Sub,
Ass,
Ttxt,
Ttml,
Sami,
Wvtt,
Stpp,
Eia608,
Unknown,
}
fn subtitle_type_for_mimetype(mt: &str) -> Option<SubtitleType> {
match mt {
"text/vtt" => Some(SubtitleType::Vtt),
"application/ttml+xml" => Some(SubtitleType::Ttml),
"application/x-sami" => Some(SubtitleType::Sami),
_ => None
}
}
pub fn subtitle_type(a: &&AdaptationSet) -> SubtitleType {
if let Some(mimetype) = &a.mimeType {
if let Some(st) = subtitle_type_for_mimetype(mimetype) {
return st;
}
}
if let Some(codecs) = &a.codecs {
if codecs == "wvtt" {
return SubtitleType::Wvtt;
}
if codecs == "c608" {
return SubtitleType::Eia608;
}
if codecs == "tx3g" {
return SubtitleType::Ttxt;
}
if codecs == "stpp" {
return SubtitleType::Stpp;
}
if codecs.starts_with("stpp.") {
return SubtitleType::Stpp;
}
}
for r in a.representations.iter() {
if let Some(mimetype) = &r.mimeType {
if let Some(st) = subtitle_type_for_mimetype(mimetype) {
return st;
}
}
if let Some(codecs) = &r.codecs {
if codecs == "wvtt" {
return SubtitleType::Wvtt;
}
if codecs == "c608" {
return SubtitleType::Eia608;
}
if codecs == "tx3g" {
return SubtitleType::Ttxt;
}
if codecs == "stpp" {
return SubtitleType::Stpp;
}
if codecs.starts_with("stpp.") {
return SubtitleType::Stpp;
}
}
}
SubtitleType::Unknown
}
#[allow(dead_code)]
fn content_protection_type(cp: &ContentProtection) -> String {
if let Some(v) = &cp.value {
if v.eq("cenc") {
return String::from("cenc");
}
if v.eq("Widevine") {
return String::from("Widevine");
}
if v.eq("MSPR 2.0") {
return String::from("PlayReady");
}
}
if let Some(uri) = &cp.schemeIdUri {
let uri = uri.to_lowercase();
if uri.eq("urn:mpeg:dash:mp4protection:2011") {
return String::from("cenc");
}
if uri.eq("urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed") {
return String::from("Widevine");
}
if uri.eq("urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95") {
return String::from("PlayReady");
}
if uri.eq("urn:uuid:94ce86fb-07ff-4f43-adb8-93d2fa968ca2") {
return String::from("FairPlay");
}
if uri.eq("urn:uuid:3ea8778f-7742-4bf9-b18b-e834b2acbd47") {
return String::from("Clear Key AES-128");
}
if uri.eq("urn:uuid:be58615b-19c4-4684-88b3-c8c57e99e957") {
return String::from("Clear Key SAMPLE-AES");
}
if uri.eq("urn:uuid:adb41c24-2dbf-4a6d-958b-4457c0d27b95") {
return String::from("Nagra");
}
if uri.eq("urn:uuid:5e629af5-38da-4063-8977-97ffbd9902d4") {
return String::from("Marlin");
}
if uri.eq("urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb") {
return String::from("Adobe PrimeTime");
}
if uri.eq("urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b") {
return String::from("W3C Common PSSH box");
}
if uri.eq("urn:uuid:80a6be7e-1448-4c37-9e70-d5aebe04c8d2") {
return String::from("Irdeto Content Protection");
}
if uri.eq("urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c") {
return String::from("WisePlay-ChinaDRM");
}
if uri.eq("urn:uuid:616c7469-6361-7374-2d50-726f74656374") {
return String::from("Alticast");
}
if uri.eq("urn:uuid:6dd8b3c3-45f4-4a68-bf3a-64168d01a4a6") {
return String::from("ABV DRM");
}
if uri.eq("urn:mpeg:dash:sea:2012") {
return String::from("SEA");
}
}
String::from("<unknown>")
}
fn check_segment_template_duration(
st: &SegmentTemplate,
max_seg_duration: &Duration,
outer_timescale: u64) -> Vec<String>
{
let mut errors = Vec::new();
if let Some(timeline) = &st.SegmentTimeline {
for s in &timeline.segments {
let sd = s.d / st.timescale.unwrap_or(outer_timescale);
if sd > max_seg_duration.as_secs() {
errors.push(String::from("SegmentTimeline has segment@d > @maxSegmentDuration"));
}
}
}
errors
}
fn check_segment_template_conformity(st: &SegmentTemplate) -> Vec<String> {
let mut errors = Vec::new();
if let Some(md) = &st.media {
if !valid_url_p(md) {
errors.push(format!("invalid URL {md}"));
}
if md.contains("$Number$") && md.contains("$Time") {
errors.push(String::from("both $Number$ and $Time$ are used in media template URL"));
}
}
if let Some(init) = &st.initialization {
if !valid_url_p(init) {
errors.push(format!("invalid URL {init}"));
}
if init.contains("$Number") {
errors.push(String::from("$Number$ identifier used in initialization segment URL"));
}
if init.contains("$Time") {
errors.push(String::from("$Time$ identifier used in initialization segment URL"));
}
}
if st.duration.is_some() && st.SegmentTimeline.is_some() {
errors.push(String::from("both SegmentTemplate.duration and SegmentTemplate.SegmentTimeline present"));
}
errors
}
fn valid_url_p(u: &str) -> bool {
use url::ParseError;
match Url::parse(u) {
Ok(url) => {
url.scheme() == "https" ||
url.scheme() == "http" ||
url.scheme() == "ftp" ||
url.scheme() == "file" ||
url.scheme() == "data"
},
Err(ParseError::RelativeUrlWithoutBase) => true,
Err(_) => false,
}
}
pub fn check_conformity(mpd: &MPD) -> Vec<String> {
let mut errors = Vec::new();
for p in &mpd.periods {
if p.adaptations.is_empty() {
errors.push(format!("Period with @id {} contains no AdaptationSet elements",
p.id.clone().unwrap_or(String::from("<unspecified>"))));
}
for a in &p.adaptations {
if let Some(mh) = a.maxHeight {
if let Some(mr) = a.representations.iter().max_by_key(|r| r.height.unwrap_or(0)) {
if mr.height.unwrap_or(0) > mh {
errors.push(String::from("invalid @maxHeight on AdaptationSet"));
}
}
}
}
}
for p in &mpd.periods {
for a in &p.adaptations {
if let Some(mw) = a.maxWidth {
if let Some(mr) = a.representations.iter().max_by_key(|r| r.width.unwrap_or(0)) {
if mr.width.unwrap_or(0) > mw {
errors.push(String::from("invalid @maxWidth on AdaptationSet"));
}
}
}
}
}
for p in &mpd.periods {
for a in &p.adaptations {
if let Some(mb) = a.maxBandwidth {
if let Some(mr) = a.representations.iter().max_by_key(|r| r.bandwidth.unwrap_or(0)) {
if mr.bandwidth.unwrap_or(0) > mb {
errors.push(String::from("invalid @maxBandwidth on AdaptationSet"));
}
}
}
}
}
if let Some(max_seg_duration) = mpd.maxSegmentDuration {
for p in &mpd.periods {
for a in &p.adaptations {
let mut outer_timescale = 1;
if let Some(st) = &a.SegmentTemplate {
check_segment_template_duration(st, &max_seg_duration, outer_timescale)
.into_iter()
.for_each(|msg| errors.push(msg));
if let Some(ots) = st.timescale {
outer_timescale = ots;
}
}
for r in &a.representations {
if let Some(st) = &r.SegmentTemplate {
check_segment_template_duration(st, &max_seg_duration, outer_timescale)
.into_iter()
.for_each(|msg| errors.push(msg));
}
}
}
}
}
for bu in &mpd.base_url {
if !valid_url_p(&bu.base) {
errors.push(format!("invalid URL {}", &bu.base));
}
}
for p in &mpd.periods {
for bu in &p.BaseURL {
if !valid_url_p(&bu.base) {
errors.push(format!("invalid URL {}", &bu.base));
}
}
for a in &p.adaptations {
for bu in &a.BaseURL {
if !valid_url_p(&bu.base) {
errors.push(format!("invalid URL {}", &bu.base));
}
}
if let Some(st) = &a.SegmentTemplate {
check_segment_template_conformity(st)
.into_iter()
.for_each(|msg| errors.push(msg));
}
for r in &a.representations {
for bu in &r.BaseURL {
if !valid_url_p(&bu.base) {
errors.push(format!("invalid URL {}", &bu.base));
}
}
if let Some(sb) = &r.SegmentBase {
if let Some(init) = &sb.initialization {
if let Some(su) = &init.sourceURL {
if !valid_url_p(su) {
errors.push(format!("invalid URL {su}"));
}
if su.contains("$Number") {
errors.push(String::from("$Number$ identifier used in initialization segment URL"));
}
if su.contains("$Time") {
errors.push(String::from("$Time$ identifier used in initialization segment URL"));
}
}
}
if let Some(ri) = &sb.RepresentationIndex {
if let Some(su) = &ri.sourceURL {
if !valid_url_p(su) {
errors.push(format!("invalid URL {su}"));
}
}
}
}
if let Some(sl) = &r.SegmentList {
if let Some(hr) = &sl.href {
if !valid_url_p(hr) {
errors.push(format!("invalid URL {hr}"));
}
}
if let Some(init) = &sl.Initialization {
if let Some(su) = &init.sourceURL {
if !valid_url_p(su) {
errors.push(format!("invalid URL {su}"));
}
if su.contains("$Number") {
errors.push(String::from("$Number$ identifier used in initialization segment URL"));
}
if su.contains("$Time") {
errors.push(String::from("$Time$ identifier used in initialization segment URL"));
}
}
}
for su in &sl.segment_urls {
if let Some(md) = &su.media {
if !valid_url_p(md) {
errors.push(format!("invalid URL {md}"));
}
}
if let Some(ix) = &su.index {
if !valid_url_p(ix) {
errors.push(format!("invalid URL {ix}"));
}
}
}
}
if let Some(st) = &r.SegmentTemplate {
check_segment_template_conformity(st)
.into_iter()
.for_each(|msg| errors.push(msg));
}
}
}
}
if let Some(pi) = &mpd.ProgramInformation {
if let Some(u) = &pi.moreInformationURL {
if !valid_url_p(u) {
errors.push(format!("invalid URL {u}"));
}
}
}
errors
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
proptest! {
#[test]
fn doesnt_crash(s in "\\PC*") {
let _ = super::parse_xs_duration(&s);
let _ = super::parse_xs_datetime(&s);
}
}
#[test]
fn test_parse_xs_duration() {
use std::time::Duration;
use super::parse_xs_duration;
assert!(parse_xs_duration("").is_err());
assert!(parse_xs_duration("foobles").is_err());
assert!(parse_xs_duration("P").is_err());
assert!(parse_xs_duration("PW").is_err());
assert!(parse_xs_duration("-PT4.5S").is_err());
assert!(parse_xs_duration("1Y2M3DT4H5M6S").is_err()); assert_eq!(parse_xs_duration("PT3H11M53S").ok(), Some(Duration::new(11513, 0)));
assert_eq!(parse_xs_duration("PT42M30S").ok(), Some(Duration::new(2550, 0)));
assert_eq!(parse_xs_duration("PT30M38S").ok(), Some(Duration::new(1838, 0)));
assert_eq!(parse_xs_duration("PT0H10M0.00S").ok(), Some(Duration::new(600, 0)));
assert_eq!(parse_xs_duration("PT1.5S").ok(), Some(Duration::new(1, 500_000_000)));
assert_eq!(parse_xs_duration("PT1.500S").ok(), Some(Duration::new(1, 500_000_000)));
assert_eq!(parse_xs_duration("PT1.500000000S").ok(), Some(Duration::new(1, 500_000_000)));
assert_eq!(parse_xs_duration("PT0S").ok(), Some(Duration::new(0, 0)));
assert_eq!(parse_xs_duration("PT0.001S").ok(), Some(Duration::new(0, 1_000_000)));
assert_eq!(parse_xs_duration("PT0.00100S").ok(), Some(Duration::new(0, 1_000_000)));
assert_eq!(parse_xs_duration("PT344S").ok(), Some(Duration::new(344, 0)));
assert_eq!(parse_xs_duration("PT634.566S").ok(), Some(Duration::new(634, 566_000_000)));
assert_eq!(parse_xs_duration("PT72H").ok(), Some(Duration::new(72*60*60, 0)));
assert_eq!(parse_xs_duration("PT0H0M30.030S").ok(), Some(Duration::new(30, 30_000_000)));
assert_eq!(parse_xs_duration("PT1004199059S").ok(), Some(Duration::new(1004199059, 0)));
assert_eq!(parse_xs_duration("P0Y20M0D").ok(), Some(Duration::new(51840000, 0)));
assert_eq!(parse_xs_duration("PT1M30.5S").ok(), Some(Duration::new(90, 500_000_000)));
assert_eq!(parse_xs_duration("PT10M10S").ok(), Some(Duration::new(610, 0)));
assert_eq!(parse_xs_duration("PT1H0.040S").ok(), Some(Duration::new(3600, 40_000_000)));
assert_eq!(parse_xs_duration("PT00H03M30SZ").ok(), Some(Duration::new(210, 0)));
assert_eq!(parse_xs_duration("PT3.14159S").ok(), Some(Duration::new(3, 141_590_000)));
assert_eq!(parse_xs_duration("PT3.14159265S").ok(), Some(Duration::new(3, 141_592_650)));
assert_eq!(parse_xs_duration("PT3.141592653S").ok(), Some(Duration::new(3, 141_592_653)));
assert_eq!(parse_xs_duration("PT3.141592653897S").ok(), Some(Duration::new(3, 141_592_653)));
assert_eq!(parse_xs_duration("P0W").ok(), Some(Duration::new(0, 0)));
assert_eq!(parse_xs_duration("P26W").ok(), Some(Duration::new(15724800, 0)));
assert_eq!(parse_xs_duration("P52W").ok(), Some(Duration::new(31449600, 0)));
assert_eq!(parse_xs_duration("P10D").ok(), Some(Duration::new(864000, 0)));
assert_eq!(parse_xs_duration("P0Y").ok(), Some(Duration::new(0, 0)));
assert_eq!(parse_xs_duration("P1Y").ok(), Some(Duration::new(31536000, 0)));
assert_eq!(parse_xs_duration("P1Y0W0S").ok(), Some(Duration::new(31536000, 0)));
assert_eq!(parse_xs_duration("PT4H").ok(), Some(Duration::new(14400, 0)));
assert_eq!(parse_xs_duration("+PT4H").ok(), Some(Duration::new(14400, 0)));
assert_eq!(parse_xs_duration("PT0004H").ok(), Some(Duration::new(14400, 0)));
assert_eq!(parse_xs_duration("PT4H0M").ok(), Some(Duration::new(14400, 0)));
assert_eq!(parse_xs_duration("PT4H0S").ok(), Some(Duration::new(14400, 0)));
assert_eq!(parse_xs_duration("P23DT23H").ok(), Some(Duration::new(2070000, 0)));
assert_eq!(parse_xs_duration("P0Y0M0DT0H4M20.880S").ok(), Some(Duration::new(260, 880_000_000)));
assert_eq!(parse_xs_duration("P1Y2M3DT4H5M6.7S").ok(), Some(Duration::new(36993906, 700_000_000)));
assert_eq!(parse_xs_duration("P1Y2M3DT4H5M6,7S").ok(), Some(Duration::new(36993906, 700_000_000)));
}
#[test]
fn test_serialize_xs_duration() {
use std::time::Duration;
use super::MPD;
fn serialized_xs_duration(d: Duration) -> String {
let mpd = MPD {
minBufferTime: Some(d),
..Default::default()
};
let xml = mpd.to_string();
let doc = roxmltree::Document::parse(&xml).unwrap();
String::from(doc.root_element().attribute("minBufferTime").unwrap())
}
assert_eq!("PT0S", serialized_xs_duration(Duration::new(0, 0)));
assert_eq!("PT0.001S", serialized_xs_duration(Duration::new(0, 1_000_000)));
assert_eq!("PT42S", serialized_xs_duration(Duration::new(42, 0)));
assert_eq!("PT1.5S", serialized_xs_duration(Duration::new(1, 500_000_000)));
assert_eq!("PT30.03S", serialized_xs_duration(Duration::new(30, 30_000_000)));
assert_eq!("PT1M30.5S", serialized_xs_duration(Duration::new(90, 500_000_000)));
assert_eq!("PT5M44S", serialized_xs_duration(Duration::new(344, 0)));
assert_eq!("PT42M30S", serialized_xs_duration(Duration::new(2550, 0)));
assert_eq!("PT30M38S", serialized_xs_duration(Duration::new(1838, 0)));
assert_eq!("PT10M10S", serialized_xs_duration(Duration::new(610, 0)));
assert_eq!("PT1H0M0.04S", serialized_xs_duration(Duration::new(3600, 40_000_000)));
assert_eq!("PT3H11M53S", serialized_xs_duration(Duration::new(11513, 0)));
assert_eq!("PT4H0M0S", serialized_xs_duration(Duration::new(14400, 0)));
}
#[test]
fn test_parse_xs_datetime() {
use chrono::{DateTime, NaiveDate};
use chrono::offset::Utc;
use super::parse_xs_datetime;
let date = NaiveDate::from_ymd_opt(2023, 4, 19)
.unwrap()
.and_hms_opt(1, 3, 2)
.unwrap();
assert_eq!(parse_xs_datetime("2023-04-19T01:03:02Z").ok(),
Some(DateTime::<Utc>::from_naive_utc_and_offset(date, Utc)));
let date = NaiveDate::from_ymd_opt(2023, 4, 19)
.unwrap()
.and_hms_nano_opt(1, 3, 2, 958*1000*1000)
.unwrap();
assert_eq!(parse_xs_datetime("2023-04-19T01:03:02.958Z").ok(),
Some(DateTime::<Utc>::from_naive_utc_and_offset(date, Utc)));
}
}