mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-26 09:00:58 +00:00
Refactor types
module
This commit is contained in:
parent
80537c3e43
commit
9f6d4e7ed7
5 changed files with 169 additions and 136 deletions
|
@ -4,8 +4,9 @@ use std::str::FromStr;
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution,
|
use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution,
|
||||||
QuotedString};
|
QuotedString};
|
||||||
use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, M3u8String, MediaType,
|
use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, MediaType, ProtocolVersion,
|
||||||
ProtocolVersion, SessionData, YesOrNo};
|
SessionData, SingleLineString};
|
||||||
|
use super::parse_yes_or_no;
|
||||||
|
|
||||||
/// [4.3.4.1. EXT-X-MEDIA]
|
/// [4.3.4.1. EXT-X-MEDIA]
|
||||||
///
|
///
|
||||||
|
@ -20,9 +21,9 @@ pub struct ExtXMedia {
|
||||||
language: Option<QuotedString>,
|
language: Option<QuotedString>,
|
||||||
assoc_language: Option<QuotedString>,
|
assoc_language: Option<QuotedString>,
|
||||||
name: QuotedString,
|
name: QuotedString,
|
||||||
default: YesOrNo,
|
default: bool,
|
||||||
autoselect: YesOrNo,
|
autoselect: bool,
|
||||||
forced: YesOrNo,
|
forced: bool,
|
||||||
instream_id: Option<InStreamId>,
|
instream_id: Option<InStreamId>,
|
||||||
characteristics: Option<QuotedString>,
|
characteristics: Option<QuotedString>,
|
||||||
channels: Option<QuotedString>,
|
channels: Option<QuotedString>,
|
||||||
|
@ -39,9 +40,9 @@ impl ExtXMedia {
|
||||||
language: None,
|
language: None,
|
||||||
assoc_language: None,
|
assoc_language: None,
|
||||||
name,
|
name,
|
||||||
default: YesOrNo::No,
|
default: false,
|
||||||
autoselect: YesOrNo::No,
|
autoselect: false,
|
||||||
forced: YesOrNo::No,
|
forced: false,
|
||||||
instream_id: None,
|
instream_id: None,
|
||||||
characteristics: None,
|
characteristics: None,
|
||||||
channels: None,
|
channels: None,
|
||||||
|
@ -79,18 +80,18 @@ impl ExtXMedia {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this is the default rendition.
|
/// Returns whether this is the default rendition.
|
||||||
pub fn default(&self) -> YesOrNo {
|
pub fn default(&self) -> bool {
|
||||||
self.default
|
self.default
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the client may choose to
|
/// Returns whether the client may choose to
|
||||||
/// play this rendition in the absence of explicit user preference.
|
/// play this rendition in the absence of explicit user preference.
|
||||||
pub fn autoselect(&self) -> YesOrNo {
|
pub fn autoselect(&self) -> bool {
|
||||||
self.autoselect
|
self.autoselect
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the rendition contains content that is considered essential to play.
|
/// Returns whether the rendition contains content that is considered essential to play.
|
||||||
pub fn forced(&self) -> YesOrNo {
|
pub fn forced(&self) -> bool {
|
||||||
self.forced
|
self.forced
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,13 +139,13 @@ impl fmt::Display for ExtXMedia {
|
||||||
write!(f, ",ASSOC-LANGUAGE={}", x)?;
|
write!(f, ",ASSOC-LANGUAGE={}", x)?;
|
||||||
}
|
}
|
||||||
write!(f, ",NAME={}", self.name)?;
|
write!(f, ",NAME={}", self.name)?;
|
||||||
if YesOrNo::Yes == self.default {
|
if self.default {
|
||||||
write!(f, ",DEFAULT=YES")?;
|
write!(f, ",DEFAULT=YES")?;
|
||||||
}
|
}
|
||||||
if YesOrNo::Yes == self.autoselect {
|
if self.autoselect {
|
||||||
write!(f, ",AUTOSELECT=YES")?;
|
write!(f, ",AUTOSELECT=YES")?;
|
||||||
}
|
}
|
||||||
if YesOrNo::Yes == self.forced {
|
if self.forced {
|
||||||
write!(f, ",FORCED=YES")?;
|
write!(f, ",FORCED=YES")?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.instream_id {
|
if let Some(ref x) = self.instream_id {
|
||||||
|
@ -170,7 +171,7 @@ impl FromStr for ExtXMedia {
|
||||||
let mut language = None;
|
let mut language = None;
|
||||||
let mut assoc_language = None;
|
let mut assoc_language = None;
|
||||||
let mut name = None;
|
let mut name = None;
|
||||||
let mut default = None;
|
let mut default = false;
|
||||||
let mut autoselect = None;
|
let mut autoselect = None;
|
||||||
let mut forced = None;
|
let mut forced = None;
|
||||||
let mut instream_id = None;
|
let mut instream_id = None;
|
||||||
|
@ -186,9 +187,9 @@ impl FromStr for ExtXMedia {
|
||||||
"LANGUAGE" => language = Some(track!(value.parse())?),
|
"LANGUAGE" => language = Some(track!(value.parse())?),
|
||||||
"ASSOC-LANGUAGE" => assoc_language = Some(track!(value.parse())?),
|
"ASSOC-LANGUAGE" => assoc_language = Some(track!(value.parse())?),
|
||||||
"NAME" => name = Some(track!(value.parse())?),
|
"NAME" => name = Some(track!(value.parse())?),
|
||||||
"DEFAULT" => default = Some(track!(value.parse())?),
|
"DEFAULT" => default = track!(parse_yes_or_no(value))?,
|
||||||
"AUTOSELECT" => autoselect = Some(track!(value.parse())?),
|
"AUTOSELECT" => autoselect = Some(track!(parse_yes_or_no(value))?),
|
||||||
"FORCED" => forced = Some(track!(value.parse())?),
|
"FORCED" => forced = Some(track!(parse_yes_or_no(value))?),
|
||||||
"INSTREAM-ID" => {
|
"INSTREAM-ID" => {
|
||||||
let s: QuotedString = track!(value.parse())?;
|
let s: QuotedString = track!(value.parse())?;
|
||||||
instream_id = Some(track!(s.as_str().parse())?);
|
instream_id = Some(track!(s.as_str().parse())?);
|
||||||
|
@ -210,8 +211,8 @@ impl FromStr for ExtXMedia {
|
||||||
} else {
|
} else {
|
||||||
track_assert!(instream_id.is_none(), ErrorKind::InvalidInput);
|
track_assert!(instream_id.is_none(), ErrorKind::InvalidInput);
|
||||||
}
|
}
|
||||||
if default == Some(YesOrNo::Yes) && autoselect.is_some() {
|
if default && autoselect.is_some() {
|
||||||
track_assert_eq!(autoselect, Some(YesOrNo::Yes), ErrorKind::InvalidInput);
|
track_assert_eq!(autoselect, Some(true), ErrorKind::InvalidInput);
|
||||||
}
|
}
|
||||||
if MediaType::Subtitles != media_type {
|
if MediaType::Subtitles != media_type {
|
||||||
track_assert_eq!(forced, None, ErrorKind::InvalidInput);
|
track_assert_eq!(forced, None, ErrorKind::InvalidInput);
|
||||||
|
@ -223,9 +224,9 @@ impl FromStr for ExtXMedia {
|
||||||
language,
|
language,
|
||||||
assoc_language,
|
assoc_language,
|
||||||
name,
|
name,
|
||||||
default: default.unwrap_or(YesOrNo::No),
|
default,
|
||||||
autoselect: autoselect.unwrap_or(YesOrNo::No),
|
autoselect: autoselect.unwrap_or(false),
|
||||||
forced: forced.unwrap_or(YesOrNo::No),
|
forced: forced.unwrap_or(false),
|
||||||
instream_id,
|
instream_id,
|
||||||
characteristics,
|
characteristics,
|
||||||
channels,
|
channels,
|
||||||
|
@ -238,7 +239,7 @@ impl FromStr for ExtXMedia {
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ExtXStreamInf {
|
pub struct ExtXStreamInf {
|
||||||
uri: M3u8String,
|
uri: SingleLineString,
|
||||||
bandwidth: DecimalInteger,
|
bandwidth: DecimalInteger,
|
||||||
average_bandwidth: Option<DecimalInteger>,
|
average_bandwidth: Option<DecimalInteger>,
|
||||||
codecs: Option<QuotedString>,
|
codecs: Option<QuotedString>,
|
||||||
|
@ -254,7 +255,7 @@ impl ExtXStreamInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
||||||
|
|
||||||
/// Makes a new `ExtXStreamInf` tag.
|
/// Makes a new `ExtXStreamInf` tag.
|
||||||
pub fn new(uri: M3u8String, bandwidth: DecimalInteger) -> Self {
|
pub fn new(uri: SingleLineString, bandwidth: DecimalInteger) -> Self {
|
||||||
ExtXStreamInf {
|
ExtXStreamInf {
|
||||||
uri,
|
uri,
|
||||||
bandwidth,
|
bandwidth,
|
||||||
|
@ -271,7 +272,7 @@ impl ExtXStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the URI that identifies the associated media playlist.
|
/// Returns the URI that identifies the associated media playlist.
|
||||||
pub fn uri(&self) -> &M3u8String {
|
pub fn uri(&self) -> &SingleLineString {
|
||||||
&self.uri
|
&self.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +377,7 @@ impl FromStr for ExtXStreamInf {
|
||||||
first_line.starts_with(Self::PREFIX),
|
first_line.starts_with(Self::PREFIX),
|
||||||
ErrorKind::InvalidInput
|
ErrorKind::InvalidInput
|
||||||
);
|
);
|
||||||
let uri = track!(M3u8String::new(second_line))?;
|
let uri = track!(SingleLineString::new(second_line))?;
|
||||||
let mut bandwidth = None;
|
let mut bandwidth = None;
|
||||||
let mut average_bandwidth = None;
|
let mut average_bandwidth = None;
|
||||||
let mut codecs = None;
|
let mut codecs = None;
|
||||||
|
@ -722,7 +723,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_stream_inf() {
|
fn ext_x_stream_inf() {
|
||||||
let tag = ExtXStreamInf::new(M3u8String::new("foo").unwrap(), DecimalInteger(1000));
|
let tag = ExtXStreamInf::new(SingleLineString::new("foo").unwrap(), DecimalInteger(1000));
|
||||||
let text = "#EXT-X-STREAM-INF:BANDWIDTH=1000\nfoo";
|
let text = "#EXT-X-STREAM-INF:BANDWIDTH=1000\nfoo";
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
|
|
|
@ -3,7 +3,8 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, SignedDecimalFloatingPoint};
|
use attribute::{AttributePairs, SignedDecimalFloatingPoint};
|
||||||
use types::{ProtocolVersion, YesOrNo};
|
use types::ProtocolVersion;
|
||||||
|
use super::parse_yes_or_no;
|
||||||
|
|
||||||
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
||||||
///
|
///
|
||||||
|
@ -37,7 +38,7 @@ impl FromStr for ExtXIndependentSegments {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct ExtXStart {
|
pub struct ExtXStart {
|
||||||
time_offset: SignedDecimalFloatingPoint,
|
time_offset: SignedDecimalFloatingPoint,
|
||||||
precise: YesOrNo,
|
precise: bool,
|
||||||
}
|
}
|
||||||
impl ExtXStart {
|
impl ExtXStart {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-START:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-START:";
|
||||||
|
@ -46,12 +47,12 @@ impl ExtXStart {
|
||||||
pub fn new(time_offset: SignedDecimalFloatingPoint) -> Self {
|
pub fn new(time_offset: SignedDecimalFloatingPoint) -> Self {
|
||||||
ExtXStart {
|
ExtXStart {
|
||||||
time_offset,
|
time_offset,
|
||||||
precise: YesOrNo::No,
|
precise: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new `ExtXStart` tag with the given `precise` flag.
|
/// Makes a new `ExtXStart` tag with the given `precise` flag.
|
||||||
pub fn with_precise(time_offset: SignedDecimalFloatingPoint, precise: YesOrNo) -> Self {
|
pub fn with_precise(time_offset: SignedDecimalFloatingPoint, precise: bool) -> Self {
|
||||||
ExtXStart {
|
ExtXStart {
|
||||||
time_offset,
|
time_offset,
|
||||||
precise,
|
precise,
|
||||||
|
@ -65,7 +66,7 @@ impl ExtXStart {
|
||||||
|
|
||||||
/// Returns whether clients should not render media stream whose presentation times are
|
/// Returns whether clients should not render media stream whose presentation times are
|
||||||
/// prior to the specified time offset.
|
/// prior to the specified time offset.
|
||||||
pub fn precise(&self) -> YesOrNo {
|
pub fn precise(&self) -> bool {
|
||||||
self.precise
|
self.precise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +79,8 @@ impl fmt::Display for ExtXStart {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", Self::PREFIX)?;
|
write!(f, "{}", Self::PREFIX)?;
|
||||||
write!(f, "TIME-OFFSET={}", self.time_offset)?;
|
write!(f, "TIME-OFFSET={}", self.time_offset)?;
|
||||||
if self.precise == YesOrNo::Yes {
|
if self.precise {
|
||||||
write!(f, ",PRECISE={}", self.precise)?;
|
write!(f, ",PRECISE=YES")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -90,13 +91,13 @@ impl FromStr for ExtXStart {
|
||||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||||
|
|
||||||
let mut time_offset = None;
|
let mut time_offset = None;
|
||||||
let mut precise = None;
|
let mut precise = false;
|
||||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"TIME-OFFSET" => time_offset = Some(track!(value.parse())?),
|
"TIME-OFFSET" => time_offset = Some(track!(value.parse())?),
|
||||||
"PRECISE" => precise = Some(track!(value.parse())?),
|
"PRECISE" => precise = track!(parse_yes_or_no(value))?,
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||||
|
@ -107,7 +108,7 @@ impl FromStr for ExtXStart {
|
||||||
let time_offset = track_assert_some!(time_offset, ErrorKind::InvalidInput);
|
let time_offset = track_assert_some!(time_offset, ErrorKind::InvalidInput);
|
||||||
Ok(ExtXStart {
|
Ok(ExtXStart {
|
||||||
time_offset,
|
time_offset,
|
||||||
precise: precise.unwrap_or(YesOrNo::No),
|
precise,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use trackable::error::ErrorKindExt;
|
||||||
|
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, DecimalFloatingPoint, QuotedString};
|
use attribute::{AttributePairs, DecimalFloatingPoint, QuotedString};
|
||||||
use types::{ByteRange, DecryptionKey, M3u8String, ProtocolVersion, Yes};
|
use types::{ByteRange, DecryptionKey, ProtocolVersion, SingleLineString};
|
||||||
|
|
||||||
/// [4.3.2.1. EXTINF]
|
/// [4.3.2.1. EXTINF]
|
||||||
///
|
///
|
||||||
|
@ -15,7 +15,7 @@ use types::{ByteRange, DecryptionKey, M3u8String, ProtocolVersion, Yes};
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtInf {
|
pub struct ExtInf {
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
title: Option<M3u8String>,
|
title: Option<SingleLineString>,
|
||||||
}
|
}
|
||||||
impl ExtInf {
|
impl ExtInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXTINF:";
|
pub(crate) const PREFIX: &'static str = "#EXTINF:";
|
||||||
|
@ -29,7 +29,7 @@ impl ExtInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new `ExtInf` tag with the given title.
|
/// Makes a new `ExtInf` tag with the given title.
|
||||||
pub fn with_title(duration: Duration, title: M3u8String) -> Self {
|
pub fn with_title(duration: Duration, title: SingleLineString) -> Self {
|
||||||
ExtInf {
|
ExtInf {
|
||||||
duration,
|
duration,
|
||||||
title: Some(title),
|
title: Some(title),
|
||||||
|
@ -42,7 +42,7 @@ impl ExtInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the title of the associated media segment.
|
/// Returns the title of the associated media segment.
|
||||||
pub fn title(&self) -> Option<&M3u8String> {
|
pub fn title(&self) -> Option<&SingleLineString> {
|
||||||
self.title.as_ref()
|
self.title.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ impl FromStr for ExtInf {
|
||||||
let duration = seconds.to_duration();
|
let duration = seconds.to_duration();
|
||||||
|
|
||||||
let title = if let Some(title) = tokens.next() {
|
let title = if let Some(title) = tokens.next() {
|
||||||
Some(track!(M3u8String::new(title))?)
|
Some(track!(SingleLineString::new(title))?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -355,7 +355,7 @@ pub struct ExtXDateRange {
|
||||||
pub scte35_cmd: Option<QuotedString>,
|
pub scte35_cmd: Option<QuotedString>,
|
||||||
pub scte35_out: Option<QuotedString>,
|
pub scte35_out: Option<QuotedString>,
|
||||||
pub scte35_in: Option<QuotedString>,
|
pub scte35_in: Option<QuotedString>,
|
||||||
pub end_on_next: Option<Yes>,
|
pub end_on_next: bool,
|
||||||
pub client_attributes: BTreeMap<String, String>,
|
pub client_attributes: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
impl ExtXDateRange {
|
impl ExtXDateRange {
|
||||||
|
@ -375,11 +375,11 @@ impl fmt::Display for ExtXDateRange {
|
||||||
}
|
}
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
",START_DATE={:?}",
|
",START-DATE={:?}",
|
||||||
self.start_date.format("%Y-%m-%d").to_string()
|
self.start_date.format("%Y-%m-%d").to_string()
|
||||||
)?;
|
)?;
|
||||||
if let Some(ref x) = self.end_date {
|
if let Some(ref x) = self.end_date {
|
||||||
write!(f, ",END_DATE={:?}", x.format("%Y-%m-%d").to_string())?;
|
write!(f, ",END-DATE={:?}", x.format("%Y-%m-%d").to_string())?;
|
||||||
}
|
}
|
||||||
if let Some(x) = self.duration {
|
if let Some(x) = self.duration {
|
||||||
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
|
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
|
||||||
|
@ -387,21 +387,21 @@ impl fmt::Display for ExtXDateRange {
|
||||||
if let Some(x) = self.planned_duration {
|
if let Some(x) = self.planned_duration {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
",PLANNED_DURATION={}",
|
",PLANNED-DURATION={}",
|
||||||
DecimalFloatingPoint::from_duration(x)
|
DecimalFloatingPoint::from_duration(x)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.scte35_cmd {
|
if let Some(ref x) = self.scte35_cmd {
|
||||||
write!(f, ",SCTE35_CMD={}", x)?;
|
write!(f, ",SCTE35-CMD={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.scte35_out {
|
if let Some(ref x) = self.scte35_out {
|
||||||
write!(f, ",SCTE35_OUT={}", x)?;
|
write!(f, ",SCTE35-OUT={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.scte35_in {
|
if let Some(ref x) = self.scte35_in {
|
||||||
write!(f, ",SCTE35_IN={}", x)?;
|
write!(f, ",SCTE35-IN={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.end_on_next {
|
if self.end_on_next {
|
||||||
write!(f, ",END_ON_NEXT={}", x)?;
|
write!(f, ",END-ON-NEXT=YES",)?;
|
||||||
}
|
}
|
||||||
for (k, v) in &self.client_attributes {
|
for (k, v) in &self.client_attributes {
|
||||||
write!(f, ",{}={}", k, v)?;
|
write!(f, ",{}={}", k, v)?;
|
||||||
|
@ -423,7 +423,7 @@ impl FromStr for ExtXDateRange {
|
||||||
let mut scte35_cmd = None;
|
let mut scte35_cmd = None;
|
||||||
let mut scte35_out = None;
|
let mut scte35_out = None;
|
||||||
let mut scte35_in = None;
|
let mut scte35_in = None;
|
||||||
let mut end_on_next = None;
|
let mut end_on_next = false;
|
||||||
let mut client_attributes = BTreeMap::new();
|
let mut client_attributes = BTreeMap::new();
|
||||||
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
|
@ -456,7 +456,10 @@ impl FromStr for ExtXDateRange {
|
||||||
"SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?),
|
"SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?),
|
||||||
"SCTE35-OUT" => scte35_out = Some(track!(value.parse())?),
|
"SCTE35-OUT" => scte35_out = Some(track!(value.parse())?),
|
||||||
"SCTE35-IN" => scte35_in = Some(track!(value.parse())?),
|
"SCTE35-IN" => scte35_in = Some(track!(value.parse())?),
|
||||||
"END-ON-NEXT" => end_on_next = Some(track!(value.parse())?),
|
"END-ON-NEXT" => {
|
||||||
|
track_assert_eq!(value, "YES", ErrorKind::InvalidInput);
|
||||||
|
end_on_next = true;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if key.starts_with("X-") {
|
if key.starts_with("X-") {
|
||||||
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
|
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
|
||||||
|
@ -470,7 +473,7 @@ impl FromStr for ExtXDateRange {
|
||||||
|
|
||||||
let id = track_assert_some!(id, ErrorKind::InvalidInput);
|
let id = track_assert_some!(id, ErrorKind::InvalidInput);
|
||||||
let start_date = track_assert_some!(start_date, ErrorKind::InvalidInput);
|
let start_date = track_assert_some!(start_date, ErrorKind::InvalidInput);
|
||||||
if end_on_next.is_some() {
|
if end_on_next {
|
||||||
track_assert!(class.is_some(), ErrorKind::InvalidInput);
|
track_assert!(class.is_some(), ErrorKind::InvalidInput);
|
||||||
}
|
}
|
||||||
Ok(ExtXDateRange {
|
Ok(ExtXDateRange {
|
||||||
|
@ -504,7 +507,10 @@ mod test {
|
||||||
assert_eq!(tag.to_string(), "#EXTINF:5");
|
assert_eq!(tag.to_string(), "#EXTINF:5");
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||||
|
|
||||||
let tag = ExtInf::with_title(Duration::from_secs(5), M3u8String::new("foo").unwrap());
|
let tag = ExtInf::with_title(
|
||||||
|
Duration::from_secs(5),
|
||||||
|
SingleLineString::new("foo").unwrap(),
|
||||||
|
);
|
||||||
assert_eq!("#EXTINF:5,foo".parse().ok(), Some(tag.clone()));
|
assert_eq!("#EXTINF:5,foo".parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), "#EXTINF:5,foo");
|
assert_eq!(tag.to_string(), "#EXTINF:5,foo");
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||||
|
|
|
@ -229,3 +229,11 @@ impl FromStr for Tag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_yes_or_no(s: &str) -> Result<bool> {
|
||||||
|
match s {
|
||||||
|
"YES" => Ok(true),
|
||||||
|
"NO" => Ok(false),
|
||||||
|
_ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
177
src/types.rs
177
src/types.rs
|
@ -7,51 +7,47 @@ use trackable::error::ErrorKindExt;
|
||||||
use {Error, ErrorKind, Result};
|
use {Error, ErrorKind, Result};
|
||||||
use attribute::{AttributePairs, HexadecimalSequence, QuotedString};
|
use attribute::{AttributePairs, HexadecimalSequence, QuotedString};
|
||||||
|
|
||||||
// TODO: rename
|
/// String that represents a single line in a playlist file.
|
||||||
|
///
|
||||||
|
/// See: [4.1. Definition of a Playlist]
|
||||||
|
///
|
||||||
|
/// [4.1. Definition of a Playlist]: https://tools.ietf.org/html/rfc8216#section-4.1
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct M3u8String(String);
|
pub struct SingleLineString(String);
|
||||||
impl M3u8String {
|
impl SingleLineString {
|
||||||
|
/// Makes a new `SingleLineString` instance.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If the given string contains any control characters,
|
||||||
|
/// this function will return an error which has the kind `ErrorKind::InvalidInput`.
|
||||||
pub fn new<T: Into<String>>(s: T) -> Result<Self> {
|
pub fn new<T: Into<String>>(s: T) -> Result<Self> {
|
||||||
// TODO: validate
|
let s = s.into();
|
||||||
Ok(M3u8String(s.into()))
|
track_assert!(!s.chars().any(|c| c.is_control()), ErrorKind::InvalidInput);
|
||||||
}
|
Ok(SingleLineString(s))
|
||||||
pub unsafe fn new_unchecked<T: Into<String>>(s: T) -> Self {
|
|
||||||
M3u8String(s.into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Deref for M3u8String {
|
impl Deref for SingleLineString {
|
||||||
type Target = str;
|
type Target = str;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<str> for M3u8String {
|
impl AsRef<str> for SingleLineString {
|
||||||
fn as_ref(&self) -> &str {
|
fn as_ref(&self) -> &str {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl fmt::Display for M3u8String {
|
impl fmt::Display for SingleLineString {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
/// [7. Protocol Version Compatibility]
|
||||||
pub struct Yes;
|
///
|
||||||
impl fmt::Display for Yes {
|
/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
#[allow(missing_docs)]
|
||||||
"YES".fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for Yes {
|
|
||||||
type Err = Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
track_assert_eq!(s, "YES", ErrorKind::InvalidInput);
|
|
||||||
Ok(Yes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc8216#section-7
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum ProtocolVersion {
|
pub enum ProtocolVersion {
|
||||||
V1,
|
V1,
|
||||||
|
@ -92,6 +88,12 @@ impl FromStr for ProtocolVersion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Byte range.
|
||||||
|
///
|
||||||
|
/// See: [4.3.2.2. EXT-X-BYTERANGE]
|
||||||
|
///
|
||||||
|
/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct ByteRange {
|
pub struct ByteRange {
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
|
@ -125,16 +127,22 @@ impl FromStr for ByteRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decryption key.
|
||||||
|
///
|
||||||
|
/// See: [4.3.2.4. EXT-X-KEY]
|
||||||
|
///
|
||||||
|
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct DecryptionKey {
|
pub struct DecryptionKey {
|
||||||
pub method: EncryptionMethod,
|
pub method: EncryptionMethod,
|
||||||
pub uri: QuotedString,
|
pub uri: QuotedString,
|
||||||
pub iv: Option<HexadecimalSequence>,
|
pub iv: Option<HexadecimalSequence>, // TODO: iv
|
||||||
pub key_format: Option<QuotedString>,
|
pub key_format: Option<QuotedString>,
|
||||||
pub key_format_versions: Option<QuotedString>,
|
pub key_format_versions: Option<QuotedString>,
|
||||||
}
|
}
|
||||||
impl DecryptionKey {
|
impl DecryptionKey {
|
||||||
pub fn requires_version(&self) -> ProtocolVersion {
|
pub(crate) fn requires_version(&self) -> ProtocolVersion {
|
||||||
if self.key_format.is_some() | self.key_format_versions.is_some() {
|
if self.key_format.is_some() | self.key_format_versions.is_some() {
|
||||||
ProtocolVersion::V5
|
ProtocolVersion::V5
|
||||||
} else if self.iv.is_some() {
|
} else if self.iv.is_some() {
|
||||||
|
@ -172,29 +180,14 @@ impl FromStr for DecryptionKey {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"METHOD" => {
|
"METHOD" => method = Some(track!(value.parse())?),
|
||||||
track_assert_eq!(method, None, ErrorKind::InvalidInput);
|
"URI" => uri = Some(track!(value.parse())?),
|
||||||
method = Some(track!(value.parse())?);
|
"IV" => iv = Some(track!(value.parse())?),
|
||||||
}
|
"KEYFORMAT" => key_format = Some(track!(value.parse())?),
|
||||||
"URI" => {
|
"KEYFORMATVERSIONS" => key_format_versions = Some(track!(value.parse())?),
|
||||||
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.
|
// [6.3.1. General Client Responsibilities]
|
||||||
|
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,6 +203,12 @@ impl FromStr for DecryptionKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Encryption method.
|
||||||
|
///
|
||||||
|
/// See: [4.3.2.4. EXT-X-KEY]
|
||||||
|
///
|
||||||
|
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum EncryptionMethod {
|
pub enum EncryptionMethod {
|
||||||
Aes128,
|
Aes128,
|
||||||
|
@ -238,6 +237,12 @@ impl FromStr for EncryptionMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Playlist type.
|
||||||
|
///
|
||||||
|
/// See: [4.3.3.5. EXT-X-PLAYLIST-TYPE]
|
||||||
|
///
|
||||||
|
/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum PlaylistType {
|
pub enum PlaylistType {
|
||||||
Event,
|
Event,
|
||||||
|
@ -262,6 +267,12 @@ impl FromStr for PlaylistType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Media type.
|
||||||
|
///
|
||||||
|
/// See: [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
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum MediaType {
|
pub enum MediaType {
|
||||||
Audio,
|
Audio,
|
||||||
|
@ -292,35 +303,12 @@ impl FromStr for MediaType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove
|
/// Identifier of a rendition within the segments in a media playlist.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
///
|
||||||
pub enum YesOrNo {
|
/// See: [4.3.4.1. EXT-X-MEDIA]
|
||||||
Yes,
|
///
|
||||||
No,
|
/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
|
||||||
}
|
#[allow(missing_docs)]
|
||||||
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum InStreamId {
|
pub enum InStreamId {
|
||||||
Cc1,
|
Cc1,
|
||||||
|
@ -472,6 +460,12 @@ impl FromStr for InStreamId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// HDCP level.
|
||||||
|
///
|
||||||
|
/// See: [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
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum HdcpLevel {
|
pub enum HdcpLevel {
|
||||||
Type0,
|
Type0,
|
||||||
|
@ -496,6 +490,12 @@ impl FromStr for HdcpLevel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The identifier of a closed captions group or its absence.
|
||||||
|
///
|
||||||
|
/// See: [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
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum ClosedCaptions {
|
pub enum ClosedCaptions {
|
||||||
GroupId(QuotedString),
|
GroupId(QuotedString),
|
||||||
|
@ -520,8 +520,25 @@ impl FromStr for ClosedCaptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Session data.
|
||||||
|
///
|
||||||
|
/// See: [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
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum SessionData {
|
pub enum SessionData {
|
||||||
Value(QuotedString),
|
Value(QuotedString),
|
||||||
Uri(QuotedString),
|
Uri(QuotedString),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_line_string() {
|
||||||
|
assert!(SingleLineString::new("foo").is_ok());
|
||||||
|
assert!(SingleLineString::new("b\rar").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue