mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 08:31:00 +00:00
Add tag::master_playlist
module
This commit is contained in:
parent
27d152fc53
commit
93f6db8904
6 changed files with 1072 additions and 632 deletions
|
@ -141,8 +141,9 @@ impl FromStr for HexadecimalSequence {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: delete
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DecimalInteger(u64);
|
||||
pub struct DecimalInteger(pub u64);
|
||||
impl fmt::Display for DecimalInteger {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
|
@ -168,6 +169,9 @@ impl DecimalFloatingPoint {
|
|||
let n = (duration.as_secs() as f64) + (duration.subsec_nanos() as f64 / 1_000_000_000.0);
|
||||
DecimalFloatingPoint(n)
|
||||
}
|
||||
pub fn as_f64(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl Eq for DecimalFloatingPoint {}
|
||||
impl fmt::Display for DecimalFloatingPoint {
|
||||
|
|
|
@ -110,10 +110,12 @@ impl FromStr for MasterPlaylist {
|
|||
media_tags.push(t);
|
||||
}
|
||||
Tag::ExtXStreamInf(t) => {
|
||||
// TODO: It MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag
|
||||
track_assert_eq!(last_stream_inf, None, ErrorKind::InvalidInput);
|
||||
last_stream_inf = Some((i, t));
|
||||
}
|
||||
Tag::ExtXIFrameStreamInf(t) => {
|
||||
// TODO: It MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag
|
||||
i_frame_stream_infs.push(t);
|
||||
}
|
||||
Tag::ExtXSessionData(t) => {
|
||||
|
|
788
src/tag/master_playlist.rs
Normal file
788
src/tag/master_playlist.rs
Normal file
|
@ -0,0 +1,788 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution,
|
||||
QuotedString};
|
||||
use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, M3u8String, MediaType,
|
||||
ProtocolVersion, SessionData, YesOrNo};
|
||||
|
||||
/// [4.3.4.1. EXT-X-MEDIA]
|
||||
///
|
||||
/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
|
||||
///
|
||||
/// TODO: Add `ExtXMediaBuilder`
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXMedia {
|
||||
media_type: MediaType,
|
||||
uri: Option<QuotedString>,
|
||||
group_id: QuotedString,
|
||||
language: Option<QuotedString>,
|
||||
assoc_language: Option<QuotedString>,
|
||||
name: QuotedString,
|
||||
default: YesOrNo,
|
||||
autoselect: YesOrNo,
|
||||
forced: YesOrNo,
|
||||
instream_id: Option<InStreamId>,
|
||||
characteristics: Option<QuotedString>,
|
||||
channels: Option<QuotedString>,
|
||||
}
|
||||
impl ExtXMedia {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
|
||||
|
||||
/// Makes a new `ExtXMedia` tag.
|
||||
pub fn new(media_type: MediaType, group_id: QuotedString, name: QuotedString) -> Self {
|
||||
ExtXMedia {
|
||||
media_type,
|
||||
uri: None,
|
||||
group_id,
|
||||
language: None,
|
||||
assoc_language: None,
|
||||
name,
|
||||
default: YesOrNo::No,
|
||||
autoselect: YesOrNo::No,
|
||||
forced: YesOrNo::No,
|
||||
instream_id: None,
|
||||
characteristics: None,
|
||||
channels: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type of the media associated with this tag.
|
||||
pub fn media_type(&self) -> MediaType {
|
||||
self.media_type
|
||||
}
|
||||
|
||||
/// Returns the identifier that specifies the group to which the rendition belongs.
|
||||
pub fn group_id(&self) -> &QuotedString {
|
||||
&self.group_id
|
||||
}
|
||||
|
||||
/// Returns a human-readable description of the rendition.
|
||||
pub fn name(&self) -> &QuotedString {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns the URI that identifies the media playlist.
|
||||
pub fn uri(&self) -> Option<&QuotedString> {
|
||||
self.uri.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the name of the primary language used in the rendition.
|
||||
pub fn language(&self) -> Option<&QuotedString> {
|
||||
self.language.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the name of a language associated with the rendition.
|
||||
pub fn assoc_language(&self) -> Option<&QuotedString> {
|
||||
self.assoc_language.as_ref()
|
||||
}
|
||||
|
||||
/// Returns whether this is the default rendition.
|
||||
pub fn default(&self) -> YesOrNo {
|
||||
self.default
|
||||
}
|
||||
|
||||
/// Returns whether the client may choose to
|
||||
/// play this rendition in the absence of explicit user preference.
|
||||
pub fn autoselect(&self) -> YesOrNo {
|
||||
self.autoselect
|
||||
}
|
||||
|
||||
/// Returns whether the rendition contains content that is considered essential to play.
|
||||
pub fn forced(&self) -> YesOrNo {
|
||||
self.forced
|
||||
}
|
||||
|
||||
/// Returns the identifier that specifies a rendition within the segments in the media playlist.
|
||||
pub fn instream_id(&self) -> Option<InStreamId> {
|
||||
self.instream_id
|
||||
}
|
||||
|
||||
/// Returns a string that represents uniform type identifiers (UTI).
|
||||
///
|
||||
/// Each UTI indicates an individual characteristic of the rendition.
|
||||
pub fn characteristics(&self) -> Option<&QuotedString> {
|
||||
self.characteristics.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a string that represents the parameters of the rendition.
|
||||
pub fn channels(&self) -> Option<&QuotedString> {
|
||||
self.channels.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
pub fn requires_version(&self) -> ProtocolVersion {
|
||||
match self.instream_id {
|
||||
None
|
||||
| Some(InStreamId::Cc1)
|
||||
| Some(InStreamId::Cc2)
|
||||
| Some(InStreamId::Cc3)
|
||||
| Some(InStreamId::Cc4) => ProtocolVersion::V1,
|
||||
_ => ProtocolVersion::V7,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ExtXMedia {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "TYPE={}", self.media_type)?;
|
||||
if let Some(ref x) = self.uri {
|
||||
write!(f, ",URI={}", x)?;
|
||||
}
|
||||
write!(f, ",GROUP-ID={}", self.group_id)?;
|
||||
if let Some(ref x) = self.language {
|
||||
write!(f, ",LANGUAGE={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.assoc_language {
|
||||
write!(f, ",ASSOC-LANGUAGE={}", x)?;
|
||||
}
|
||||
write!(f, ",NAME={}", self.name)?;
|
||||
if YesOrNo::Yes == self.default {
|
||||
write!(f, ",DEFAULT=YES")?;
|
||||
}
|
||||
if YesOrNo::Yes == self.autoselect {
|
||||
write!(f, ",AUTOSELECT=YES")?;
|
||||
}
|
||||
if YesOrNo::Yes == self.forced {
|
||||
write!(f, ",FORCED=YES")?;
|
||||
}
|
||||
if let Some(ref x) = self.instream_id {
|
||||
write!(f, ",INSTREAM-ID=\"{}\"", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.characteristics {
|
||||
write!(f, ",CHARACTERISTICS={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.channels {
|
||||
write!(f, ",CHANNELS={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXMedia {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut media_type = None;
|
||||
let mut uri = None;
|
||||
let mut group_id = None;
|
||||
let mut language = None;
|
||||
let mut assoc_language = None;
|
||||
let mut name = None;
|
||||
let mut default = None;
|
||||
let mut autoselect = None;
|
||||
let mut forced = None;
|
||||
let mut instream_id = None;
|
||||
let mut characteristics = None;
|
||||
let mut channels = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"TYPE" => media_type = Some(track!(value.parse())?),
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"GROUP-ID" => group_id = Some(track!(value.parse())?),
|
||||
"LANGUAGE" => language = Some(track!(value.parse())?),
|
||||
"ASSOC-LANGUAGE" => assoc_language = Some(track!(value.parse())?),
|
||||
"NAME" => name = Some(track!(value.parse())?),
|
||||
"DEFAULT" => default = Some(track!(value.parse())?),
|
||||
"AUTOSELECT" => autoselect = Some(track!(value.parse())?),
|
||||
"FORCED" => forced = Some(track!(value.parse())?),
|
||||
"INSTREAM-ID" => {
|
||||
let s: QuotedString = track!(value.parse())?;
|
||||
instream_id = Some(track!(s.as_str().parse())?);
|
||||
}
|
||||
"CHARACTERISTICS" => characteristics = Some(track!(value.parse())?),
|
||||
"CHANNELS" => channels = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
// [6.3.1. General Client Responsibilities]
|
||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
let media_type = track_assert_some!(media_type, ErrorKind::InvalidInput);
|
||||
let group_id = track_assert_some!(group_id, ErrorKind::InvalidInput);
|
||||
let name = track_assert_some!(name, ErrorKind::InvalidInput);
|
||||
if MediaType::ClosedCaptions == media_type {
|
||||
track_assert_ne!(uri, None, ErrorKind::InvalidInput);
|
||||
track_assert!(instream_id.is_some(), ErrorKind::InvalidInput);
|
||||
} else {
|
||||
track_assert!(instream_id.is_none(), ErrorKind::InvalidInput);
|
||||
}
|
||||
if default == Some(YesOrNo::Yes) && autoselect.is_some() {
|
||||
track_assert_eq!(autoselect, Some(YesOrNo::Yes), ErrorKind::InvalidInput);
|
||||
}
|
||||
if MediaType::Subtitles != media_type {
|
||||
track_assert_eq!(forced, None, ErrorKind::InvalidInput);
|
||||
}
|
||||
Ok(ExtXMedia {
|
||||
media_type,
|
||||
uri,
|
||||
group_id,
|
||||
language,
|
||||
assoc_language,
|
||||
name,
|
||||
default: default.unwrap_or(YesOrNo::No),
|
||||
autoselect: autoselect.unwrap_or(YesOrNo::No),
|
||||
forced: forced.unwrap_or(YesOrNo::No),
|
||||
instream_id,
|
||||
characteristics,
|
||||
channels,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [4.3.4.2. EXT-X-STREAM-INF]
|
||||
///
|
||||
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXStreamInf {
|
||||
uri: M3u8String,
|
||||
bandwidth: DecimalInteger,
|
||||
average_bandwidth: Option<DecimalInteger>,
|
||||
codecs: Option<QuotedString>,
|
||||
resolution: Option<DecimalResolution>,
|
||||
frame_rate: Option<DecimalFloatingPoint>,
|
||||
hdcp_level: Option<HdcpLevel>,
|
||||
audio: Option<QuotedString>,
|
||||
video: Option<QuotedString>,
|
||||
subtitles: Option<QuotedString>,
|
||||
closed_captions: Option<ClosedCaptions>,
|
||||
}
|
||||
impl ExtXStreamInf {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
||||
|
||||
/// Makes a new `ExtXStreamInf` tag.
|
||||
pub fn new(uri: M3u8String, bandwidth: DecimalInteger) -> Self {
|
||||
ExtXStreamInf {
|
||||
uri,
|
||||
bandwidth,
|
||||
average_bandwidth: None,
|
||||
codecs: None,
|
||||
resolution: None,
|
||||
frame_rate: None,
|
||||
hdcp_level: None,
|
||||
audio: None,
|
||||
video: None,
|
||||
subtitles: None,
|
||||
closed_captions: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the URI that identifies the associated media playlist.
|
||||
pub fn uri(&self) -> &M3u8String {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
/// Returns the peak segment bit rate of the variant stream.
|
||||
pub fn bandwidth(&self) -> DecimalInteger {
|
||||
self.bandwidth
|
||||
}
|
||||
|
||||
/// Returns the average segment bit rate of the variant stream.
|
||||
pub fn average_bandwidth(&self) -> Option<DecimalInteger> {
|
||||
self.average_bandwidth
|
||||
}
|
||||
|
||||
/// Returns a string that represents the list of codec types contained the variant stream.
|
||||
pub fn codecs(&self) -> Option<&QuotedString> {
|
||||
self.codecs.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
|
||||
pub fn resolution(&self) -> Option<DecimalResolution> {
|
||||
self.resolution
|
||||
}
|
||||
|
||||
/// Returns the maximum frame rate for all the video in the variant stream.
|
||||
pub fn frame_rate(&self) -> Option<DecimalFloatingPoint> {
|
||||
self.frame_rate
|
||||
}
|
||||
|
||||
/// Returns the HDCP level of the variant stream.
|
||||
pub fn hdcp_level(&self) -> Option<HdcpLevel> {
|
||||
self.hdcp_level
|
||||
}
|
||||
|
||||
/// Returns the group identifier for the audio in the variant stream.
|
||||
pub fn audio(&self) -> Option<&QuotedString> {
|
||||
self.audio.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the group identifier for the video in the variant stream.
|
||||
pub fn video(&self) -> Option<&QuotedString> {
|
||||
self.video.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the group identifier for the subtitles in the variant stream.
|
||||
pub fn subtitles(&self) -> Option<&QuotedString> {
|
||||
self.subtitles.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the value of `CLOSED-CAPTIONS` attribute.
|
||||
pub fn closed_captions(&self) -> Option<&ClosedCaptions> {
|
||||
self.closed_captions.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
pub fn requires_version(&self) -> ProtocolVersion {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ExtXStreamInf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "BANDWIDTH={}", self.bandwidth)?;
|
||||
if let Some(ref x) = self.average_bandwidth {
|
||||
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.codecs {
|
||||
write!(f, ",CODECS={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.resolution {
|
||||
write!(f, ",RESOLUTION={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.frame_rate {
|
||||
write!(f, ",FRAME-RATE={:.3}", x.as_f64())?;
|
||||
}
|
||||
if let Some(ref x) = self.hdcp_level {
|
||||
write!(f, ",HDCP-LEVEL={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.audio {
|
||||
write!(f, ",AUDIO={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.video {
|
||||
write!(f, ",VIDEO={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.subtitles {
|
||||
write!(f, ",SUBTITLES={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.closed_captions {
|
||||
write!(f, ",CLOSED-CAPTIONS={}", x)?;
|
||||
}
|
||||
write!(f, "\n{}", self.uri)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXStreamInf {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let mut lines = s.splitn(2, '\n'); // TODO:
|
||||
let first_line = lines.next().expect("Never fails");
|
||||
let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput);
|
||||
|
||||
track_assert!(
|
||||
first_line.starts_with(Self::PREFIX),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
let uri = track!(M3u8String::new(second_line))?;
|
||||
let mut bandwidth = None;
|
||||
let mut average_bandwidth = None;
|
||||
let mut codecs = None;
|
||||
let mut resolution = None;
|
||||
let mut frame_rate = None;
|
||||
let mut hdcp_level = None;
|
||||
let mut audio = None;
|
||||
let mut video = None;
|
||||
let mut subtitles = None;
|
||||
let mut closed_captions = None;
|
||||
let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||
"FRAME-RATE" => frame_rate = Some(track!(value.parse())?),
|
||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||
"AUDIO" => audio = Some(track!(value.parse())?),
|
||||
"VIDEO" => video = Some(track!(value.parse())?),
|
||||
"SUBTITLES" => subtitles = Some(track!(value.parse())?),
|
||||
"CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
// [6.3.1. General Client Responsibilities]
|
||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
|
||||
Ok(ExtXStreamInf {
|
||||
uri,
|
||||
bandwidth,
|
||||
average_bandwidth,
|
||||
codecs,
|
||||
resolution,
|
||||
frame_rate,
|
||||
hdcp_level,
|
||||
audio,
|
||||
video,
|
||||
subtitles,
|
||||
closed_captions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
|
||||
///
|
||||
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXIFrameStreamInf {
|
||||
uri: QuotedString,
|
||||
bandwidth: DecimalInteger,
|
||||
average_bandwidth: Option<DecimalInteger>,
|
||||
codecs: Option<QuotedString>,
|
||||
resolution: Option<DecimalResolution>,
|
||||
hdcp_level: Option<HdcpLevel>,
|
||||
video: Option<QuotedString>,
|
||||
}
|
||||
impl ExtXIFrameStreamInf {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
||||
|
||||
/// Makes a new `ExtXIFrameStreamInf` tag.
|
||||
pub fn new(uri: QuotedString, bandwidth: DecimalInteger) -> Self {
|
||||
ExtXIFrameStreamInf {
|
||||
uri,
|
||||
bandwidth,
|
||||
average_bandwidth: None,
|
||||
codecs: None,
|
||||
resolution: None,
|
||||
hdcp_level: None,
|
||||
video: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the URI that identifies the associated media playlist.
|
||||
pub fn uri(&self) -> &QuotedString {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
/// Returns the peak segment bit rate of the variant stream.
|
||||
pub fn bandwidth(&self) -> DecimalInteger {
|
||||
self.bandwidth
|
||||
}
|
||||
|
||||
/// Returns the average segment bit rate of the variant stream.
|
||||
pub fn average_bandwidth(&self) -> Option<DecimalInteger> {
|
||||
self.average_bandwidth
|
||||
}
|
||||
|
||||
/// Returns a string that represents the list of codec types contained the variant stream.
|
||||
pub fn codecs(&self) -> Option<&QuotedString> {
|
||||
self.codecs.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
|
||||
pub fn resolution(&self) -> Option<DecimalResolution> {
|
||||
self.resolution
|
||||
}
|
||||
|
||||
/// Returns the HDCP level of the variant stream.
|
||||
pub fn hdcp_level(&self) -> Option<HdcpLevel> {
|
||||
self.hdcp_level
|
||||
}
|
||||
|
||||
/// Returns the group identifier for the video in the variant stream.
|
||||
pub fn video(&self) -> Option<&QuotedString> {
|
||||
self.video.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
pub fn requires_version(&self) -> ProtocolVersion {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ExtXIFrameStreamInf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "URI={}", self.uri)?;
|
||||
write!(f, ",BANDWIDTH={}", self.bandwidth)?;
|
||||
if let Some(ref x) = self.average_bandwidth {
|
||||
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.codecs {
|
||||
write!(f, ",CODECS={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.resolution {
|
||||
write!(f, ",RESOLUTION={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.hdcp_level {
|
||||
write!(f, ",HDCP-LEVEL={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.video {
|
||||
write!(f, ",VIDEO={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXIFrameStreamInf {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut uri = None;
|
||||
let mut bandwidth = None;
|
||||
let mut average_bandwidth = None;
|
||||
let mut codecs = None;
|
||||
let mut resolution = None;
|
||||
let mut hdcp_level = None;
|
||||
let mut video = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||
"VIDEO" => video = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
// [6.3.1. General Client Responsibilities]
|
||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
|
||||
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
|
||||
Ok(ExtXIFrameStreamInf {
|
||||
uri,
|
||||
bandwidth,
|
||||
average_bandwidth,
|
||||
codecs,
|
||||
resolution,
|
||||
hdcp_level,
|
||||
video,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [4.3.4.4. EXT-X-SESSION-DATA]
|
||||
///
|
||||
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXSessionData {
|
||||
data_id: QuotedString,
|
||||
data: SessionData,
|
||||
language: Option<QuotedString>,
|
||||
}
|
||||
impl ExtXSessionData {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
|
||||
|
||||
/// Makes a new `ExtXSessionData` tag.
|
||||
pub fn new(data_id: QuotedString, data: SessionData) -> Self {
|
||||
ExtXSessionData {
|
||||
data_id,
|
||||
data,
|
||||
language: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `ExtXSessionData` with the given language.
|
||||
pub fn with_language(data_id: QuotedString, data: SessionData, language: QuotedString) -> Self {
|
||||
ExtXSessionData {
|
||||
data_id,
|
||||
data,
|
||||
language: Some(language),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the identifier of the data.
|
||||
pub fn data_id(&self) -> &QuotedString {
|
||||
&self.data_id
|
||||
}
|
||||
|
||||
/// Returns the session data.
|
||||
pub fn data(&self) -> &SessionData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Returns the language of the data.
|
||||
pub fn language(&self) -> Option<&QuotedString> {
|
||||
self.language.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
pub fn requires_version(&self) -> ProtocolVersion {
|
||||
ProtocolVersion::V1
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ExtXSessionData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "DATA-ID={}", self.data_id)?;
|
||||
match self.data {
|
||||
SessionData::Value(ref x) => write!(f, ",VALUE={}", x)?,
|
||||
SessionData::Uri(ref x) => write!(f, ",URI={}", x)?,
|
||||
}
|
||||
if let Some(ref x) = self.language {
|
||||
write!(f, ",LANGUAGE={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXSessionData {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut data_id = None;
|
||||
let mut session_value = None;
|
||||
let mut uri = None;
|
||||
let mut language = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"DATA-ID" => data_id = Some(track!(value.parse())?),
|
||||
"VALUE" => session_value = Some(track!(value.parse())?),
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"LANGUAGE" => language = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
// [6.3.1. General Client Responsibilities]
|
||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data_id = track_assert_some!(data_id, ErrorKind::InvalidInput);
|
||||
let data = if let Some(value) = session_value {
|
||||
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
|
||||
SessionData::Value(value)
|
||||
} else if let Some(uri) = uri {
|
||||
SessionData::Uri(uri)
|
||||
} else {
|
||||
track_panic!(ErrorKind::InvalidInput);
|
||||
};
|
||||
Ok(ExtXSessionData {
|
||||
data_id,
|
||||
data,
|
||||
language,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [4.3.4.5. EXT-X-SESSION-KEY]
|
||||
///
|
||||
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXSessionKey {
|
||||
key: DecryptionKey,
|
||||
}
|
||||
impl ExtXSessionKey {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
|
||||
|
||||
/// Makes a new `ExtXSessionKey` tag.
|
||||
pub fn new(key: DecryptionKey) -> Self {
|
||||
ExtXSessionKey { key }
|
||||
}
|
||||
|
||||
/// Returns a decryption key for the playlist.
|
||||
pub fn key(&self) -> &DecryptionKey {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
pub fn requires_version(&self) -> ProtocolVersion {
|
||||
self.key.requires_version()
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ExtXSessionKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}{}", Self::PREFIX, self.key)
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXSessionKey {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
let suffix = s.split_at(Self::PREFIX.len()).1;
|
||||
let key = track!(suffix.parse())?;
|
||||
Ok(ExtXSessionKey { key })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use attribute::HexadecimalSequence;
|
||||
use types::EncryptionMethod;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ext_x_media() {
|
||||
let tag = ExtXMedia::new(MediaType::Audio, quoted_string("foo"), quoted_string("bar"));
|
||||
let text = r#"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="foo",NAME="bar""#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_x_stream_inf() {
|
||||
let tag = ExtXStreamInf::new(M3u8String::new("foo").unwrap(), DecimalInteger(1000));
|
||||
let text = "#EXT-X-STREAM-INF:BANDWIDTH=1000\nfoo";
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_x_i_frame_stream_inf() {
|
||||
let tag = ExtXIFrameStreamInf::new(quoted_string("foo"), DecimalInteger(1000));
|
||||
let text = r#"#EXT-X-I-FRAME-STREAM-INF:URI="foo",BANDWIDTH=1000"#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_x_session_data() {
|
||||
let tag = ExtXSessionData::new(
|
||||
quoted_string("foo"),
|
||||
SessionData::Value(quoted_string("bar")),
|
||||
);
|
||||
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
|
||||
let tag =
|
||||
ExtXSessionData::new(quoted_string("foo"), SessionData::Uri(quoted_string("bar")));
|
||||
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
|
||||
let tag = ExtXSessionData::with_language(
|
||||
quoted_string("foo"),
|
||||
SessionData::Value(quoted_string("bar")),
|
||||
quoted_string("baz"),
|
||||
);
|
||||
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_x_session_key() {
|
||||
let tag = ExtXSessionKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: quoted_string("foo"),
|
||||
iv: Some(HexadecimalSequence::new(vec![0, 1, 2])),
|
||||
key_format: None,
|
||||
key_format_versions: None,
|
||||
});
|
||||
let text = r#"#EXT-X-SESSION-KEY:METHOD=AES-128,URI="foo",IV=0x000102"#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||
}
|
||||
|
||||
fn quoted_string(s: &str) -> QuotedString {
|
||||
QuotedString::new(s).unwrap()
|
||||
}
|
||||
}
|
|
@ -283,14 +283,8 @@ impl FromStr for ExtXMap {
|
|||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"URI" => {
|
||||
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
|
||||
uri = Some(track!(value.parse())?);
|
||||
}
|
||||
"BYTERANGE" => {
|
||||
track_assert_eq!(range, None, ErrorKind::InvalidInput);
|
||||
range = Some(track!(value.parse())?);
|
||||
}
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"BYTERANGE" => range = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
// [6.3.1. General Client Responsibilities]
|
||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
|
@ -435,12 +429,8 @@ impl FromStr for ExtXDateRange {
|
|||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"ID" => {
|
||||
id = Some(track!(value.parse())?);
|
||||
}
|
||||
"CLASS" => {
|
||||
class = Some(track!(value.parse())?);
|
||||
}
|
||||
"ID" => id = Some(track!(value.parse())?),
|
||||
"CLASS" => class = Some(track!(value.parse())?),
|
||||
"START-DATE" => {
|
||||
let s: QuotedString = track!(value.parse())?;
|
||||
start_date = Some(track!(
|
||||
|
@ -463,18 +453,10 @@ impl FromStr for ExtXDateRange {
|
|||
let seconds: DecimalFloatingPoint = track!(value.parse())?;
|
||||
planned_duration = Some(seconds.to_duration());
|
||||
}
|
||||
"SCTE35-CMD" => {
|
||||
scte35_cmd = Some(track!(value.parse())?);
|
||||
}
|
||||
"SCTE35-OUT" => {
|
||||
scte35_out = Some(track!(value.parse())?);
|
||||
}
|
||||
"SCTE35-IN" => {
|
||||
scte35_in = Some(track!(value.parse())?);
|
||||
}
|
||||
"END-ON-NEXT" => {
|
||||
end_on_next = Some(track!(value.parse())?);
|
||||
}
|
||||
"SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?),
|
||||
"SCTE35-OUT" => scte35_out = Some(track!(value.parse())?),
|
||||
"SCTE35-IN" => scte35_in = Some(track!(value.parse())?),
|
||||
"END-ON-NEXT" => end_on_next = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
if key.starts_with("X-") {
|
||||
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
|
||||
|
|
610
src/tag/mod.rs
610
src/tag/mod.rs
|
@ -5,8 +5,8 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution,
|
||||
HexadecimalSequence, QuotedString, SignedDecimalFloatingPoint};
|
||||
use attribute::{AttributePairs, SignedDecimalFloatingPoint};
|
||||
use types::YesOrNo;
|
||||
|
||||
macro_rules! may_invalid {
|
||||
($expr:expr) => {
|
||||
|
@ -25,12 +25,15 @@ macro_rules! impl_from {
|
|||
}
|
||||
|
||||
pub use self::basic::{ExtM3u, ExtXVersion};
|
||||
pub use self::master_playlist::{ExtXIFrameStreamInf, ExtXMedia, ExtXSessionData, ExtXSessionKey,
|
||||
ExtXStreamInf};
|
||||
pub use self::media_playlist::{ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly,
|
||||
ExtXMediaSequence, ExtXPlaylistType, ExtXTargetDuration};
|
||||
pub use self::media_segment::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey,
|
||||
ExtXMap, ExtXProgramDateTime};
|
||||
|
||||
mod basic;
|
||||
mod master_playlist;
|
||||
mod media_playlist;
|
||||
mod media_segment;
|
||||
|
||||
|
@ -227,580 +230,6 @@ impl FromStr for Tag {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MediaType {
|
||||
Audio,
|
||||
Video,
|
||||
Subtitles,
|
||||
ClosedCaptions,
|
||||
}
|
||||
impl fmt::Display for MediaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
MediaType::Audio => "AUDIO".fmt(f),
|
||||
MediaType::Video => "VIDEO".fmt(f),
|
||||
MediaType::Subtitles => "SUBTITLES".fmt(f),
|
||||
MediaType::ClosedCaptions => "CLOSED-CAPTIONS".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for MediaType {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(match s {
|
||||
"AUDIO" => MediaType::Audio,
|
||||
"VIDEO" => MediaType::Video,
|
||||
"SUBTITLES" => MediaType::Subtitles,
|
||||
"CLOSED-CAPTIONS" => MediaType::ClosedCaptions,
|
||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown media type: {:?}", s),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum YesOrNo {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
impl fmt::Display for YesOrNo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
YesOrNo::Yes => "YES".fmt(f),
|
||||
YesOrNo::No => "NO".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for YesOrNo {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"YES" => Ok(YesOrNo::Yes),
|
||||
"NO" => Ok(YesOrNo::No),
|
||||
_ => track_panic!(
|
||||
ErrorKind::InvalidInput,
|
||||
"Unexpected enumerated-string: {:?}",
|
||||
s
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXMedia {
|
||||
media_type: MediaType,
|
||||
uri: Option<QuotedString>,
|
||||
group_id: QuotedString,
|
||||
language: Option<QuotedString>,
|
||||
assoc_language: Option<QuotedString>,
|
||||
name: QuotedString,
|
||||
default: YesOrNo,
|
||||
autoselect: YesOrNo,
|
||||
forced: Option<YesOrNo>,
|
||||
instream_id: Option<QuotedString>, // TODO: `InStreamId` type
|
||||
characteristics: Option<QuotedString>,
|
||||
channels: Option<QuotedString>,
|
||||
}
|
||||
impl ExtXMedia {
|
||||
const PREFIX: &'static str = "#EXT-X-MEDIA:";
|
||||
}
|
||||
impl fmt::Display for ExtXMedia {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "TYPE={}", self.media_type)?;
|
||||
if let Some(ref x) = self.uri {
|
||||
write!(f, ",URI={}", x)?;
|
||||
}
|
||||
write!(f, ",GROUP_ID={}", self.group_id)?;
|
||||
if let Some(ref x) = self.language {
|
||||
write!(f, ",LANGUAGE={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.assoc_language {
|
||||
write!(f, ",ASSOC-LANGUAGE={}", x)?;
|
||||
}
|
||||
write!(f, ",NAME={}", self.name)?;
|
||||
if YesOrNo::Yes == self.default {
|
||||
write!(f, ",DEFAULT={}", self.default)?;
|
||||
}
|
||||
if YesOrNo::Yes == self.autoselect {
|
||||
write!(f, ",AUTOSELECT={}", self.autoselect)?;
|
||||
}
|
||||
if let Some(ref x) = self.forced {
|
||||
write!(f, ",FORCED={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.instream_id {
|
||||
write!(f, ",INSTREAM-ID={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.characteristics {
|
||||
write!(f, ",CHARACTERISTICS={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.channels {
|
||||
write!(f, ",CHANNELS={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXMedia {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut media_type = None;
|
||||
let mut uri = None;
|
||||
let mut group_id = None;
|
||||
let mut language = None;
|
||||
let mut assoc_language = None;
|
||||
let mut name = None;
|
||||
let mut default = None;
|
||||
let mut autoselect = None;
|
||||
let mut forced = None;
|
||||
let mut instream_id = None;
|
||||
let mut characteristics = None;
|
||||
let mut channels = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"TYPE" => media_type = Some(track!(value.parse())?),
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"GROUP-ID" => group_id = Some(track!(value.parse())?),
|
||||
"LANGUAGE" => language = Some(track!(value.parse())?),
|
||||
"ASSOC-LANGUAGE" => assoc_language = Some(track!(value.parse())?),
|
||||
"NAME" => name = Some(track!(value.parse())?),
|
||||
"DEFAULT" => default = Some(track!(value.parse())?),
|
||||
"AUTOSELECT" => autoselect = Some(track!(value.parse())?),
|
||||
"FORCED" => forced = Some(track!(value.parse())?),
|
||||
"INSTREAM-ID" => instream_id = Some(track!(value.parse())?),
|
||||
"CHARACTERISTICS" => characteristics = Some(track!(value.parse())?),
|
||||
"CHANNELS" => channels = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
// [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
let media_type = track_assert_some!(media_type, ErrorKind::InvalidInput);
|
||||
let group_id = track_assert_some!(group_id, ErrorKind::InvalidInput);
|
||||
let name = track_assert_some!(name, ErrorKind::InvalidInput);
|
||||
if MediaType::ClosedCaptions == media_type {
|
||||
track_assert_ne!(uri, None, ErrorKind::InvalidInput);
|
||||
track_assert!(instream_id.is_some(), ErrorKind::InvalidInput);
|
||||
} else {
|
||||
track_assert!(instream_id.is_none(), ErrorKind::InvalidInput);
|
||||
}
|
||||
if MediaType::Subtitles != media_type {
|
||||
track_assert_eq!(forced, None, ErrorKind::InvalidInput);
|
||||
}
|
||||
Ok(ExtXMedia {
|
||||
media_type,
|
||||
uri,
|
||||
group_id,
|
||||
language,
|
||||
assoc_language,
|
||||
name,
|
||||
default: default.unwrap_or(YesOrNo::No),
|
||||
autoselect: autoselect.unwrap_or(YesOrNo::No),
|
||||
forced,
|
||||
instream_id,
|
||||
characteristics,
|
||||
channels,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum HdcpLevel {
|
||||
Type0,
|
||||
None,
|
||||
}
|
||||
impl fmt::Display for HdcpLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
HdcpLevel::Type0 => "TYPE-0".fmt(f),
|
||||
HdcpLevel::None => "NONE".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for HdcpLevel {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"TYPE-0" => Ok(HdcpLevel::Type0),
|
||||
"NONE" => Ok(HdcpLevel::None),
|
||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown HDCP level: {:?}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ClosedCaptions {
|
||||
GroupId(QuotedString),
|
||||
None,
|
||||
}
|
||||
impl fmt::Display for ClosedCaptions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ClosedCaptions::GroupId(ref x) => x.fmt(f),
|
||||
ClosedCaptions::None => "NONE".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for ClosedCaptions {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if s == "NONE" {
|
||||
Ok(ClosedCaptions::None)
|
||||
} else {
|
||||
Ok(ClosedCaptions::GroupId(track!(s.parse())?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The URI line is REQUIRED.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXStreamInf {
|
||||
bandwidth: DecimalInteger,
|
||||
average_bandwidth: Option<DecimalInteger>,
|
||||
codecs: Option<QuotedString>,
|
||||
resolution: Option<DecimalResolution>,
|
||||
|
||||
// TODO: rounded to three decimal places
|
||||
frame_rate: Option<DecimalFloatingPoint>,
|
||||
hdcp_level: Option<HdcpLevel>,
|
||||
audio: Option<QuotedString>,
|
||||
video: Option<QuotedString>,
|
||||
subtitles: Option<QuotedString>,
|
||||
closed_captions: Option<ClosedCaptions>,
|
||||
}
|
||||
impl ExtXStreamInf {
|
||||
const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
||||
}
|
||||
impl fmt::Display for ExtXStreamInf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "BANDWIDTH={}", self.bandwidth)?;
|
||||
if let Some(ref x) = self.average_bandwidth {
|
||||
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.codecs {
|
||||
write!(f, ",CODECS={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.resolution {
|
||||
write!(f, ",RESOLUTION={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.frame_rate {
|
||||
write!(f, ",FRAME-RATE={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.hdcp_level {
|
||||
write!(f, ",HDCP-LEVEL={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.audio {
|
||||
write!(f, ",AUDIO={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.video {
|
||||
write!(f, ",VIDEO={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.subtitles {
|
||||
write!(f, ",SUBTITLES={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.closed_captions {
|
||||
write!(f, ",CLOSED-CAPTIONS={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXStreamInf {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut bandwidth = None;
|
||||
let mut average_bandwidth = None;
|
||||
let mut codecs = None;
|
||||
let mut resolution = None;
|
||||
let mut frame_rate = None;
|
||||
let mut hdcp_level = None;
|
||||
let mut audio = None;
|
||||
let mut video = None;
|
||||
let mut subtitles = None;
|
||||
let mut closed_captions = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||
"FRAME-RATE" => frame_rate = Some(track!(value.parse())?),
|
||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||
"AUDIO" => {
|
||||
// TODO: It MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag
|
||||
audio = Some(track!(value.parse())?);
|
||||
}
|
||||
"VIDEO" => {
|
||||
// TODO: It MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag
|
||||
video = Some(track!(value.parse())?);
|
||||
}
|
||||
"SUBTITLES" => {
|
||||
// TODO: It MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag
|
||||
subtitles = Some(track!(value.parse())?);
|
||||
}
|
||||
"CLOSED-CAPTIONS" => {
|
||||
// TODO: validate
|
||||
closed_captions = Some(track!(value.parse())?);
|
||||
}
|
||||
_ => {
|
||||
// [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
|
||||
Ok(ExtXStreamInf {
|
||||
bandwidth,
|
||||
average_bandwidth,
|
||||
codecs,
|
||||
resolution,
|
||||
frame_rate,
|
||||
hdcp_level,
|
||||
audio,
|
||||
video,
|
||||
subtitles,
|
||||
closed_captions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: That Playlist file MUST contain an EXT-X-I-FRAMES-ONLY tag.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXIFrameStreamInf {
|
||||
uri: QuotedString,
|
||||
bandwidth: DecimalInteger,
|
||||
average_bandwidth: Option<DecimalInteger>,
|
||||
codecs: Option<QuotedString>,
|
||||
resolution: Option<DecimalResolution>,
|
||||
hdcp_level: Option<HdcpLevel>,
|
||||
video: Option<QuotedString>,
|
||||
}
|
||||
impl ExtXIFrameStreamInf {
|
||||
const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
||||
}
|
||||
impl fmt::Display for ExtXIFrameStreamInf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "URI={}", self.uri)?;
|
||||
write!(f, ",BANDWIDTH={}", self.bandwidth)?;
|
||||
if let Some(ref x) = self.average_bandwidth {
|
||||
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.codecs {
|
||||
write!(f, ",CODECS={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.resolution {
|
||||
write!(f, ",RESOLUTION={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.hdcp_level {
|
||||
write!(f, ",HDCP-LEVEL={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.video {
|
||||
write!(f, ",VIDEO={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXIFrameStreamInf {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut uri = None;
|
||||
let mut bandwidth = None;
|
||||
let mut average_bandwidth = None;
|
||||
let mut codecs = None;
|
||||
let mut resolution = None;
|
||||
let mut hdcp_level = None;
|
||||
let mut video = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||
"VIDEO" => {
|
||||
// TODO:
|
||||
video = Some(track!(value.parse())?);
|
||||
}
|
||||
_ => {
|
||||
// [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
|
||||
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
|
||||
Ok(ExtXIFrameStreamInf {
|
||||
uri,
|
||||
bandwidth,
|
||||
average_bandwidth,
|
||||
codecs,
|
||||
resolution,
|
||||
hdcp_level,
|
||||
video,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SessionData {
|
||||
Value(QuotedString),
|
||||
Uri(QuotedString),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXSessionData {
|
||||
pub data_id: QuotedString,
|
||||
pub data: SessionData,
|
||||
pub language: Option<QuotedString>,
|
||||
}
|
||||
impl ExtXSessionData {
|
||||
const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
|
||||
}
|
||||
impl fmt::Display for ExtXSessionData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "DATA-ID={}", self.data_id)?;
|
||||
match self.data {
|
||||
SessionData::Value(ref x) => write!(f, ",VALUE={}", x)?,
|
||||
SessionData::Uri(ref x) => write!(f, ",URI={}", x)?,
|
||||
}
|
||||
if let Some(ref x) = self.language {
|
||||
write!(f, ",LANGUAGE={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXSessionData {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut data_id = None;
|
||||
let mut session_value = None;
|
||||
let mut uri = None;
|
||||
let mut language = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"DATA-ID" => data_id = Some(track!(value.parse())?),
|
||||
"VALUE" => session_value = Some(track!(value.parse())?),
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"LANGUAGE" => language = Some(track!(value.parse())?),
|
||||
_ => {
|
||||
// [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data_id = track_assert_some!(data_id, ErrorKind::InvalidInput);
|
||||
let data = if let Some(value) = session_value {
|
||||
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
|
||||
SessionData::Value(value)
|
||||
} else if let Some(uri) = uri {
|
||||
SessionData::Uri(uri)
|
||||
} else {
|
||||
track_panic!(ErrorKind::InvalidInput);
|
||||
};
|
||||
Ok(ExtXSessionData {
|
||||
data_id,
|
||||
data,
|
||||
language,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXSessionKey {
|
||||
pub method: SessionEncryptionMethod,
|
||||
pub uri: QuotedString,
|
||||
pub iv: Option<HexadecimalSequence>,
|
||||
pub key_format: Option<QuotedString>,
|
||||
pub key_format_versions: Option<QuotedString>,
|
||||
}
|
||||
impl ExtXSessionKey {
|
||||
const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
|
||||
}
|
||||
impl fmt::Display for ExtXSessionKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
write!(f, "METHOD={}", self.method)?;
|
||||
write!(f, ",URI={}", self.uri)?;
|
||||
if let Some(ref x) = self.iv {
|
||||
write!(f, ",IV={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.key_format {
|
||||
write!(f, ",KEYFORMAT={}", x)?;
|
||||
}
|
||||
if let Some(ref x) = self.key_format_versions {
|
||||
write!(f, ",KEYFORMATVERSIONS={}", x)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for ExtXSessionKey {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
|
||||
let mut method = None;
|
||||
let mut uri = None;
|
||||
let mut iv = None;
|
||||
let mut key_format = None;
|
||||
let mut key_format_versions = None;
|
||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"METHOD" => {
|
||||
track_assert_eq!(method, None, ErrorKind::InvalidInput);
|
||||
method = Some(track!(value.parse())?);
|
||||
}
|
||||
"URI" => {
|
||||
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
|
||||
uri = Some(track!(value.parse())?);
|
||||
}
|
||||
"IV" => {
|
||||
// TODO: validate length(128-bit)
|
||||
track_assert_eq!(iv, None, ErrorKind::InvalidInput);
|
||||
iv = Some(track!(value.parse())?);
|
||||
}
|
||||
"KEYFORMAT" => {
|
||||
track_assert_eq!(key_format, None, ErrorKind::InvalidInput);
|
||||
key_format = Some(track!(value.parse())?);
|
||||
}
|
||||
"KEYFORMATVERSIONS" => {
|
||||
track_assert_eq!(key_format_versions, None, ErrorKind::InvalidInput);
|
||||
key_format_versions = Some(track!(value.parse())?);
|
||||
}
|
||||
_ => {
|
||||
// [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName.
|
||||
}
|
||||
}
|
||||
}
|
||||
let method = track_assert_some!(method, ErrorKind::InvalidInput);
|
||||
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
|
||||
Ok(ExtXSessionKey {
|
||||
method,
|
||||
uri,
|
||||
iv,
|
||||
key_format,
|
||||
key_format_versions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 4.3.5. Media or Master Playlist Tags
|
||||
// TODO: A tag that appears in both MUST have the same value; otherwise, clients SHOULD ignore the value in the Media Playlist(s).
|
||||
// TODO: These tags MUST NOT appear more than once in a Playlist.
|
||||
|
@ -867,32 +296,3 @@ impl FromStr for ExtXStart {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum SessionEncryptionMethod {
|
||||
Aes128,
|
||||
SampleAes,
|
||||
}
|
||||
impl fmt::Display for SessionEncryptionMethod {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
SessionEncryptionMethod::Aes128 => "AES-128".fmt(f),
|
||||
SessionEncryptionMethod::SampleAes => "SAMPLE-AES".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for SessionEncryptionMethod {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"AES-128" => Ok(SessionEncryptionMethod::Aes128),
|
||||
"SAMPLE-AES" => Ok(SessionEncryptionMethod::SampleAes),
|
||||
_ => track_panic!(
|
||||
ErrorKind::InvalidInput,
|
||||
"Unknown encryption method: {:?}",
|
||||
s
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
264
src/types.rs
264
src/types.rs
|
@ -261,3 +261,267 @@ impl FromStr for PlaylistType {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MediaType {
|
||||
Audio,
|
||||
Video,
|
||||
Subtitles,
|
||||
ClosedCaptions,
|
||||
}
|
||||
impl fmt::Display for MediaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
MediaType::Audio => "AUDIO".fmt(f),
|
||||
MediaType::Video => "VIDEO".fmt(f),
|
||||
MediaType::Subtitles => "SUBTITLES".fmt(f),
|
||||
MediaType::ClosedCaptions => "CLOSED-CAPTIONS".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for MediaType {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(match s {
|
||||
"AUDIO" => MediaType::Audio,
|
||||
"VIDEO" => MediaType::Video,
|
||||
"SUBTITLES" => MediaType::Subtitles,
|
||||
"CLOSED-CAPTIONS" => MediaType::ClosedCaptions,
|
||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown media type: {:?}", s),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum YesOrNo {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
impl fmt::Display for YesOrNo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
YesOrNo::Yes => "YES".fmt(f),
|
||||
YesOrNo::No => "NO".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for YesOrNo {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"YES" => Ok(YesOrNo::Yes),
|
||||
"NO" => Ok(YesOrNo::No),
|
||||
_ => track_panic!(
|
||||
ErrorKind::InvalidInput,
|
||||
"Unexpected enumerated-string: {:?}",
|
||||
s
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum InStreamId {
|
||||
Cc1,
|
||||
Cc2,
|
||||
Cc3,
|
||||
Cc4,
|
||||
Service1,
|
||||
Service2,
|
||||
Service3,
|
||||
Service4,
|
||||
Service5,
|
||||
Service6,
|
||||
Service7,
|
||||
Service8,
|
||||
Service9,
|
||||
Service10,
|
||||
Service11,
|
||||
Service12,
|
||||
Service13,
|
||||
Service14,
|
||||
Service15,
|
||||
Service16,
|
||||
Service17,
|
||||
Service18,
|
||||
Service19,
|
||||
Service20,
|
||||
Service21,
|
||||
Service22,
|
||||
Service23,
|
||||
Service24,
|
||||
Service25,
|
||||
Service26,
|
||||
Service27,
|
||||
Service28,
|
||||
Service29,
|
||||
Service30,
|
||||
Service31,
|
||||
Service32,
|
||||
Service33,
|
||||
Service34,
|
||||
Service35,
|
||||
Service36,
|
||||
Service37,
|
||||
Service38,
|
||||
Service39,
|
||||
Service40,
|
||||
Service41,
|
||||
Service42,
|
||||
Service43,
|
||||
Service44,
|
||||
Service45,
|
||||
Service46,
|
||||
Service47,
|
||||
Service48,
|
||||
Service49,
|
||||
Service50,
|
||||
Service51,
|
||||
Service52,
|
||||
Service53,
|
||||
Service54,
|
||||
Service55,
|
||||
Service56,
|
||||
Service57,
|
||||
Service58,
|
||||
Service59,
|
||||
Service60,
|
||||
Service61,
|
||||
Service62,
|
||||
Service63,
|
||||
}
|
||||
impl fmt::Display for InStreamId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
format!("{:?}", self).to_uppercase().fmt(f)
|
||||
}
|
||||
}
|
||||
impl FromStr for InStreamId {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(match s {
|
||||
"CC1" => InStreamId::Cc1,
|
||||
"CC2" => InStreamId::Cc2,
|
||||
"CC3" => InStreamId::Cc3,
|
||||
"CC4" => InStreamId::Cc4,
|
||||
"SERVICE1" => InStreamId::Service1,
|
||||
"SERVICE2" => InStreamId::Service2,
|
||||
"SERVICE3" => InStreamId::Service3,
|
||||
"SERVICE4" => InStreamId::Service4,
|
||||
"SERVICE5" => InStreamId::Service5,
|
||||
"SERVICE6" => InStreamId::Service6,
|
||||
"SERVICE7" => InStreamId::Service7,
|
||||
"SERVICE8" => InStreamId::Service8,
|
||||
"SERVICE9" => InStreamId::Service9,
|
||||
"SERVICE10" => InStreamId::Service10,
|
||||
"SERVICE11" => InStreamId::Service11,
|
||||
"SERVICE12" => InStreamId::Service12,
|
||||
"SERVICE13" => InStreamId::Service13,
|
||||
"SERVICE14" => InStreamId::Service14,
|
||||
"SERVICE15" => InStreamId::Service15,
|
||||
"SERVICE16" => InStreamId::Service16,
|
||||
"SERVICE17" => InStreamId::Service17,
|
||||
"SERVICE18" => InStreamId::Service18,
|
||||
"SERVICE19" => InStreamId::Service19,
|
||||
"SERVICE20" => InStreamId::Service20,
|
||||
"SERVICE21" => InStreamId::Service21,
|
||||
"SERVICE22" => InStreamId::Service22,
|
||||
"SERVICE23" => InStreamId::Service23,
|
||||
"SERVICE24" => InStreamId::Service24,
|
||||
"SERVICE25" => InStreamId::Service25,
|
||||
"SERVICE26" => InStreamId::Service26,
|
||||
"SERVICE27" => InStreamId::Service27,
|
||||
"SERVICE28" => InStreamId::Service28,
|
||||
"SERVICE29" => InStreamId::Service29,
|
||||
"SERVICE30" => InStreamId::Service30,
|
||||
"SERVICE31" => InStreamId::Service31,
|
||||
"SERVICE32" => InStreamId::Service32,
|
||||
"SERVICE33" => InStreamId::Service33,
|
||||
"SERVICE34" => InStreamId::Service34,
|
||||
"SERVICE35" => InStreamId::Service35,
|
||||
"SERVICE36" => InStreamId::Service36,
|
||||
"SERVICE37" => InStreamId::Service37,
|
||||
"SERVICE38" => InStreamId::Service38,
|
||||
"SERVICE39" => InStreamId::Service39,
|
||||
"SERVICE40" => InStreamId::Service40,
|
||||
"SERVICE41" => InStreamId::Service41,
|
||||
"SERVICE42" => InStreamId::Service42,
|
||||
"SERVICE43" => InStreamId::Service43,
|
||||
"SERVICE44" => InStreamId::Service44,
|
||||
"SERVICE45" => InStreamId::Service45,
|
||||
"SERVICE46" => InStreamId::Service46,
|
||||
"SERVICE47" => InStreamId::Service47,
|
||||
"SERVICE48" => InStreamId::Service48,
|
||||
"SERVICE49" => InStreamId::Service49,
|
||||
"SERVICE50" => InStreamId::Service50,
|
||||
"SERVICE51" => InStreamId::Service51,
|
||||
"SERVICE52" => InStreamId::Service52,
|
||||
"SERVICE53" => InStreamId::Service53,
|
||||
"SERVICE54" => InStreamId::Service54,
|
||||
"SERVICE55" => InStreamId::Service55,
|
||||
"SERVICE56" => InStreamId::Service56,
|
||||
"SERVICE57" => InStreamId::Service57,
|
||||
"SERVICE58" => InStreamId::Service58,
|
||||
"SERVICE59" => InStreamId::Service59,
|
||||
"SERVICE60" => InStreamId::Service60,
|
||||
"SERVICE61" => InStreamId::Service61,
|
||||
"SERVICE62" => InStreamId::Service62,
|
||||
"SERVICE63" => InStreamId::Service63,
|
||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown instream id: {:?}", s),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum HdcpLevel {
|
||||
Type0,
|
||||
None,
|
||||
}
|
||||
impl fmt::Display for HdcpLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
HdcpLevel::Type0 => "TYPE-0".fmt(f),
|
||||
HdcpLevel::None => "NONE".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for HdcpLevel {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
"TYPE-0" => Ok(HdcpLevel::Type0),
|
||||
"NONE" => Ok(HdcpLevel::None),
|
||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown HDCP level: {:?}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ClosedCaptions {
|
||||
GroupId(QuotedString),
|
||||
None,
|
||||
}
|
||||
impl fmt::Display for ClosedCaptions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ClosedCaptions::GroupId(ref x) => x.fmt(f),
|
||||
ClosedCaptions::None => "NONE".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for ClosedCaptions {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if s == "NONE" {
|
||||
Ok(ClosedCaptions::None)
|
||||
} else {
|
||||
Ok(ClosedCaptions::GroupId(track!(s.parse())?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SessionData {
|
||||
Value(QuotedString),
|
||||
Uri(QuotedString),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue