#![allow(non_snake_case)]
#[cfg(all(feature = "fetch", feature = "libav"))]
mod libav;
#[cfg(all(feature = "fetch", not(feature = "libav")))]
mod ffmpeg;
#[cfg(feature = "fetch")]
pub mod fetch;
#[cfg(all(feature = "fetch", feature = "libav"))]
use crate::libav::mux_audio_video;
#[cfg(all(feature = "fetch", not(feature = "libav")))]
use crate::ffmpeg::mux_audio_video;
use serde::{Serialize, Serializer, Deserialize};
use serde::de;
use serde_with::skip_serializing_none;
use regex::Regex;
use std::time::Duration;
use chrono::DateTime;
pub type XsDatetime = DateTime<chrono::offset::Utc>;
#[derive(thiserror::Error, Debug)]
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}")]
Io(#[source] std::io::Error, String),
#[error("network error {0}")]
Network(String),
#[error("muxing error {0}")]
Muxing(String),
#[error("unknown error {0}")]
Other(String),
}
fn parse_xs_duration(s: &str) -> Result<Duration, DashMpdError> {
let re = 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();
match re.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(s) = m.name("nanoseconds") {
let mut s = &s.as_str()[1..]; if s.len() > 9 {
s = &s[..9];
}
let padded = format!("{s:0<9}");
nsecs = padded.parse::<u32>().unwrap();
}
if let Some(s) = m.name("seconds") {
let seconds = s.as_str().parse::<u64>().unwrap();
secs += seconds;
}
if let Some(s) = m.name("minutes") {
let minutes = s.as_str().parse::<u64>().unwrap();
secs += minutes * 60;
}
if let Some(s) = m.name("hours") {
let hours = s.as_str().parse::<u64>().unwrap();
secs += hours * 60 * 60;
}
if let Some(s) = m.name("days") {
let days = s.as_str().parse::<u64>().unwrap();
secs += days * 60 * 60 * 24;
}
if let Some(s) = m.name("weeks") {
let weeks = s.as_str().parse::<u64>().unwrap();
secs += weeks * 60 * 60 * 24 * 7;
}
if let Some(s) = m.name("months") {
let months = s.as_str().parse::<u64>().unwrap();
secs += months * 60 * 60 * 24 * 30;
}
if let Some(s) = m.name("years") {
let years = s.as_str().parse::<u64>().unwrap();
secs += years * 60 * 60 * 24 * 365;
}
if let Some(s) = m.name("sign") {
if s.as_str() == "-" {
return Err(DashMpdError::InvalidDuration("can't represent negative durations".to_string()));
}
}
Ok(Duration::new(secs, nsecs))
},
None => Err(DashMpdError::InvalidDuration("couldn't parse XS duration".to_string())),
}
}
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 secs = xs.as_secs();
let ms = xs.subsec_millis();
serializer.serialize_str(&format!("PT{secs}.{ms:03}S"))
} 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 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)
.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),
}
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Title {
#[serde(rename = "$value")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Source {
#[serde(rename = "$value")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Copyright {
#[serde(rename = "$value")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct S {
#[serde(rename = "@t")]
pub t: Option<i64>,
#[serde(rename = "@d")]
pub d: i64,
#[serde(rename = "@r")]
pub r: Option<i64>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct SegmentTimeline {
#[serde(rename = "S")]
pub segments: Vec<S>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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)]
#[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>,
pub SegmentTimeline: Option<SegmentTimeline>,
#[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 = "@presentationTimeOffset")]
pub presentationTimeOffset: Option<u64>,
#[serde(rename = "@bitstreamSwitching")]
pub bitstreamSwitching: Option<bool>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Location {
#[serde(rename = "$value")]
pub url: String,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct BaseURL {
#[serde(rename = "$value")]
pub base: String,
#[serde(rename = "@serviceLocation")]
pub serviceLocation: Option<String>,
#[serde(rename = "@priority")]
pub priority: i64, }
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct SegmentBase {
#[serde(rename = "Initialization")]
pub initialization: Option<Initialization>,
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "@presentationTimeOffset")]
pub presentationTimeOffset: Option<u64>,
#[serde(rename = "@indexRange")]
pub indexRange: Option<String>,
#[serde(rename = "@indexRangeExact")]
pub indexRangeExact: Option<bool>,
#[serde(rename = "@availabilityTimeOffset")]
pub availabilityTimeOffset: Option<f64>,
#[serde(rename = "@availabilityTimeComplete")]
pub availabilityTimeComplete: Option<bool>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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)]
#[serde(default)]
pub struct SegmentList {
#[serde(rename = "@duration")]
pub duration: Option<u64>,
#[serde(rename = "@href")]
pub href: Option<String>,
#[serde(rename = "@actuate")]
pub actuate: Option<String>,
#[serde(rename = "@type")]
pub sltype: Option<String>,
#[serde(rename = "@show")]
pub show: Option<String>,
pub Initialization: Option<Initialization>,
#[serde(rename = "SegmentURL")]
pub segment_urls: Vec<SegmentURL>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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)]
#[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)]
#[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)]
#[serde(default)]
pub struct Representation {
#[serde(rename = "@id")]
pub id: 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 = "@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<u64>,
#[serde(rename = "@width")]
pub width: Option<u64>,
#[serde(rename = "@height")]
pub height: Option<u64>,
#[serde(rename = "@startWithSAP")]
pub startWithSAP: Option<u64>,
pub BaseURL: Vec<BaseURL>,
pub AudioChannelConfiguration: Option<AudioChannelConfiguration>,
#[serde(rename = "@mediaStreamStructureId")]
pub mediaStreamStructureId: Option<String>,
pub SegmentTemplate: Option<SegmentTemplate>,
pub SegmentBase: Option<SegmentBase>,
pub SegmentList: Option<SegmentList>,
pub Resync: Option<Resync>,
#[serde(rename = "@href")]
pub href: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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: Option<Accessibility>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct CencPssh {
#[serde(rename = "$value")]
pub content: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct ContentProtection {
#[serde(rename = "@robustness")]
pub robustness: Option<String>,
#[serde(rename = "@refId")]
pub refId: Option<String>,
#[serde(rename = "@ref")]
pub cpref: Option<String>,
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "pssh")]
pub cenc_pssh: Option<CencPssh>,
#[serde(rename = "@default_KID")]
pub default_KID: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Role {
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Viewpoint {
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Binary {
#[serde(rename = "$value")]
pub content: Vec<u8>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Signal {
#[serde(rename = "Binary")]
pub content: Vec<Binary>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Event {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@presentationTime")]
pub presentationTime: Option<u64>,
#[serde(rename = "@duration")]
pub duration: Option<u64>,
#[serde(rename = "@timescale")]
pub timescale: Option<u64>,
#[serde(rename = "Signal")]
pub signal: Vec<Signal>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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)]
#[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>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Label {
#[serde(rename = "$value")]
pub content: String,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct AdaptationSet {
#[serde(rename = "@id")]
pub id: Option<i64>,
pub label: Option<Label>,
pub BaseURL: Vec<BaseURL>,
#[serde(rename = "@href")]
pub href: Option<String>,
#[serde(rename = "@actuate")]
pub actuate: Option<String>,
#[serde(rename = "@group")]
pub group: Option<i64>,
#[serde(rename = "@selectionPriority")]
pub selectionPriority: Option<u64>,
#[serde(rename = "@contentType")]
pub contentType: Option<String>,
#[serde(rename = "@lang")]
pub lang: Option<String>,
#[serde(rename = "@par")]
pub par: Option<String>,
#[serde(rename = "@segmentAlignment")]
pub segmentAlignment: Option<bool>,
#[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<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>, pub SegmentTemplate: Option<SegmentTemplate>,
pub SegmentList: Option<SegmentList>,
pub ContentComponent: Vec<ContentComponent>,
pub ContentProtection: Vec<ContentProtection>,
pub Accessibility: Option<Accessibility>,
pub AudioChannelConfiguration: Option<AudioChannelConfiguration>,
#[serde(rename = "Representation")]
pub representations: Vec<Representation>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct AssetIdentifier {
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Period {
#[serde(rename = "@id")]
pub id: Option<String>,
#[serde(rename = "@start")]
pub start: Option<String>,
#[serde(deserialize_with = "deserialize_xs_duration", default)]
#[serde(serialize_with = "serialize_xs_duration")]
#[serde(rename = "@duration")]
pub duration: Option<Duration>,
#[serde(rename = "@bitstreamSwitching")]
pub bitstreamSwitching: Option<bool>,
pub BaseURL: Vec<BaseURL>,
#[serde(rename = "@href")]
pub href: Option<String>,
#[serde(rename = "@actuate")]
pub actuate: Option<String>,
pub SegmentTemplate: Option<SegmentTemplate>,
#[serde(rename = "AdaptationSet")]
pub adaptations: Vec<AdaptationSet>,
pub asset_identifier: Option<AssetIdentifier>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Reporting {
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
#[serde(rename = "@reportingUrl")]
pub reportingUrl: Option<String>,
#[serde(rename = "@probability")]
pub probability: Option<u64>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Range {
#[serde(rename = "@starttime")]
pub starttime: Option<Duration>,
#[serde(rename = "@duration")]
pub duration: Option<Duration>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[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)]
#[serde(default)]
pub struct Latency {
#[serde(rename = "@min")]
pub min: Option<f64>,
#[serde(rename = "@max")]
pub max: Option<f64>,
#[serde(rename = "@target")]
pub target: Option<f64>,
#[serde(rename = "@referenceId")]
pub referenceId: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct PlaybackRate {
#[serde(rename = "@min")]
pub min: f64,
#[serde(rename = "@max")]
pub max: f64,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct ServiceDescription {
#[serde(rename = "@id")]
pub id: Option<String>,
pub Latency: Option<Latency>,
pub PlaybackRate: Option<PlaybackRate>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct UTCTiming {
#[serde(rename = "@schemeIdUri")]
pub schemeIdUri: Option<String>,
#[serde(rename = "@value")]
pub value: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct LeapSecondInformation {
#[serde(rename = "@availabilityStartLeapOffset")]
pub availabilityStartLeapOffset: Option<i64>,
#[serde(rename = "@nextAvailabilityStartLeapOffset")]
pub nextAvailabilityStartLeapOffset: Option<i64>,
#[serde(deserialize_with = "deserialize_xs_datetime", default)]
#[serde(rename = "@nextLeapChangeTime")]
pub nextLeapChangeTime: Option<XsDatetime>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct MPD {
#[serde(rename = "@type")]
pub mpdtype: Option<String>,
#[serde(rename = "@xmlns")]
pub xmlns: Option<String>,
#[serde(rename = "@schemaLocation")]
pub schemaLocation: 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(deserialize_with = "deserialize_xs_duration", default)]
#[serde(serialize_with = "serialize_xs_duration")]
#[serde(rename = "@suggestedPresentationDelay")]
pub suggestedPresentationDelay: Option<Duration>,
#[serde(deserialize_with = "deserialize_xs_datetime", default)]
#[serde(rename = "@publishTime")]
pub publishTime: Option<XsDatetime>,
#[serde(deserialize_with = "deserialize_xs_datetime", default)]
#[serde(rename = "@availabilityStartTime")]
pub availabilityStartTime: Option<XsDatetime>,
#[serde(deserialize_with = "deserialize_xs_datetime", default)]
#[serde(rename = "@availabilityEndTime")]
pub availabilityEndTime: Option<XsDatetime>,
#[serde(rename = "Period", default)]
pub periods: Vec<Period>,
#[serde(rename = "BaseURL")]
pub base_url: Vec<BaseURL>,
pub locations: Vec<Location>,
pub ServiceDescription: Option<ServiceDescription>,
pub ProgramInformation: Option<ProgramInformation>,
pub Metrics: Vec<Metrics>,
pub UTCTiming: Vec<UTCTiming>,
pub LeapSecondInformation: Option<LeapSecondInformation>,
#[serde(rename = "EssentialProperty")]
pub essential_property: Vec<EssentialProperty>,
#[serde(rename = "SupplementalProperty")]
pub supplemental_property: Vec<SupplementalProperty>,
}
pub fn parse(xml: &str) -> Result<MPD, DashMpdError> {
let mpd: Result<MPD, quick_xml::DeError> = quick_xml::de::from_str(xml);
match mpd {
Ok(mpd) => Ok(mpd),
Err(e) => Err(DashMpdError::Parsing(e.to_string())),
}
}
pub fn is_audio_adaptation(a: &&AdaptationSet) -> bool {
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 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 let Some(mimetype) = &r.mimeType {
if mimetype.starts_with("video/") {
return true;
}
}
}
false
}
pub fn is_subtitle_adaptation(a: &&AdaptationSet) -> bool {
if let Some(mimetype) = &a.mimeType {
if mimetype.eq("text/vtt") ||
mimetype.eq("application/ttml+xml") ||
mimetype.eq("application/x-sami")
{
return true;
}
}
let mut text_ct = false;
if let Some(contentType) = &a.contentType {
if contentType.eq("text") {
text_ct = true;
}
}
for r in a.representations.iter() {
if let Some(ct) = &r.contentType {
if ct == "text" {
text_ct = true;
}
}
if text_ct {
if let Some(codecs) = &r.codecs {
if codecs == "wvtt" ||
codecs == "stpp" ||
codecs.starts_with("stpp.")
{
return true;
}
}
}
}
false
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SubtitleType {
Vtt,
Srt,
Sub,
Ass,
Ttxt,
Ttml,
Sami,
Wvtt,
Stpp,
Unknown,
}
pub fn subtitle_type(a: &&AdaptationSet) -> SubtitleType {
if let Some(mimetype) = &a.mimeType {
if mimetype.eq("text/vtt") {
return SubtitleType::Vtt;
} else if mimetype.eq("application/ttml+xml") {
return SubtitleType::Ttml;
} else if mimetype.eq("application/x-sami") {
return SubtitleType::Sami;
}
}
for r in a.representations.iter() {
if let Some(codecs) = &r.codecs {
if codecs == "wvtt" {
return SubtitleType::Wvtt;
}
if codecs == "stpp" {
return SubtitleType::Stpp;
}
if codecs.starts_with("stpp.") {
return SubtitleType::Stpp;
}
}
}
SubtitleType::Unknown
}
#[cfg(test)]
mod tests {
#[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("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("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("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!(parse_xs_duration("PW").is_err());
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("P0Y").ok(), Some(Duration::new(0, 0)));
assert_eq!(parse_xs_duration("P1Y").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("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)));
}
}