1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-22 07:10:59 +00:00

updated parser

This commit is contained in:
Luro02 2019-09-10 11:05:20 +02:00
parent 91c6698f16
commit 1a35463185
15 changed files with 289 additions and 175 deletions

View file

@ -17,6 +17,7 @@ codecov = {repository = "sile/hls_m3u8"}
[dependencies] [dependencies]
trackable = "0.2" trackable = "0.2"
getset = "0.0.8"
[dev-dependencies] [dev-dependencies]
clap = "2" clap = "2"

View file

@ -141,7 +141,7 @@ impl MediaPlaylistBuilder {
// CHECK: `#EXT-X-BYTE-RANGE` // CHECK: `#EXT-X-BYTE-RANGE`
if let Some(tag) = s.byte_range_tag() { if let Some(tag) = s.byte_range_tag() {
if tag.range().start.is_none() { if tag.to_range().start().is_none() {
let last_uri = track_assert_some!(last_range_uri, ErrorKind::InvalidInput); let last_uri = track_assert_some!(last_range_uri, ErrorKind::InvalidInput);
track_assert_eq!(last_uri, s.uri(), ErrorKind::InvalidInput); track_assert_eq!(last_uri, s.uri(), ErrorKind::InvalidInput);
} else { } else {

View file

@ -1,8 +1,10 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
/// [4.3.1.1. EXTM3U] /// [4.3.1.1. EXTM3U]
/// ///
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1 /// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
@ -27,8 +29,8 @@ impl fmt::Display for ExtM3u {
impl FromStr for ExtM3u { impl FromStr for ExtM3u {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); tag(input, Self::PREFIX)?;
Ok(ExtM3u) Ok(ExtM3u)
} }
} }

View file

@ -1,8 +1,10 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
/// [4.3.1.2. EXT-X-VERSION] /// [4.3.1.2. EXT-X-VERSION]
/// ///
/// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2 /// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
@ -37,10 +39,8 @@ impl fmt::Display for ExtXVersion {
impl FromStr for ExtXVersion { impl FromStr for ExtXVersion {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let version = tag(input, Self::PREFIX)?.parse()?;
let suffix = s.split_at(Self::PREFIX.len()).1;
let version = track!(suffix.parse())?;
Ok(ExtXVersion::new(version)) Ok(ExtXVersion::new(version))
} }
} }

View file

@ -1,22 +1,35 @@
use std::fmt;
use std::str::FromStr;
use getset::{Getters, MutGetters, Setters};
use crate::attribute::AttributePairs; use crate::attribute::AttributePairs;
use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion}; use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion};
use crate::utils::parse_u64; use crate::utils::parse_u64;
use crate::utils::{quote, unquote}; use crate::utils::{quote, tag, unquote};
use crate::{Error, ErrorKind, Result}; use crate::{Error, ErrorKind};
use std::fmt;
use std::str::FromStr;
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF] /// [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 /// [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)] #[derive(Getters, Setters, MutGetters, Debug, Clone, PartialEq, Eq, Hash)]
#[get = "pub"]
#[set = "pub"]
#[get_mut = "pub"]
pub struct ExtXIFrameStreamInf { pub struct ExtXIFrameStreamInf {
/// The URI, that identifies the associated media playlist.
uri: String, uri: String,
/// The peak segment bit rate of the variant stream.
bandwidth: u64, bandwidth: u64,
/// The average segment bit rate of the variant stream.
average_bandwidth: Option<u64>, average_bandwidth: Option<u64>,
/// A string that represents the list of codec types contained the variant stream.
codecs: Option<String>, codecs: Option<String>,
/// The optimal pixel resolution at which to display all the video in the variant stream.
resolution: Option<DecimalResolution>, resolution: Option<DecimalResolution>,
/// The HDCP level of the variant stream.
hdcp_level: Option<HdcpLevel>, hdcp_level: Option<HdcpLevel>,
/// The group identifier for the video in the variant stream.
video: Option<String>, video: Option<String>,
} }
@ -36,41 +49,6 @@ impl ExtXIFrameStreamInf {
} }
} }
/// Returns the URI that identifies the associated media playlist.
pub const fn uri(&self) -> &String {
&self.uri
}
/// Returns the peak segment bit rate of the variant stream.
pub const fn bandwidth(&self) -> u64 {
self.bandwidth
}
/// Returns the average segment bit rate of the variant stream.
pub const fn average_bandwidth(&self) -> Option<u64> {
self.average_bandwidth
}
/// Returns a string that represents the list of codec types contained the variant stream.
pub fn codecs(&self) -> Option<&String> {
self.codecs.as_ref()
}
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
pub const fn resolution(&self) -> Option<DecimalResolution> {
self.resolution
}
/// Returns the HDCP level of the variant stream.
pub const 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<&String> {
self.video.as_ref()
}
/// Returns the protocol compatibility version that this tag requires. /// Returns the protocol compatibility version that this tag requires.
pub const fn requires_version(&self) -> ProtocolVersion { pub const fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1 ProtocolVersion::V1
@ -105,8 +83,8 @@ impl fmt::Display for ExtXIFrameStreamInf {
impl FromStr for ExtXIFrameStreamInf { impl FromStr for ExtXIFrameStreamInf {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let input = tag(input, Self::PREFIX)?;
let mut uri = None; let mut uri = None;
let mut bandwidth = None; let mut bandwidth = None;
@ -115,7 +93,8 @@ impl FromStr for ExtXIFrameStreamInf {
let mut resolution = None; let mut resolution = None;
let mut hdcp_level = None; let mut hdcp_level = None;
let mut video = None; let mut video = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
let attrs = AttributePairs::parse(input);
for attr in attrs { for attr in attrs {
let (key, value) = track!(attr)?; let (key, value) = track!(attr)?;
match key { match key {
@ -167,7 +146,7 @@ mod test {
); );
assert_eq!(i_frame_stream_inf.uri(), "foo"); assert_eq!(i_frame_stream_inf.uri(), "foo");
assert_eq!(i_frame_stream_inf.bandwidth(), 1000); assert_eq!(*i_frame_stream_inf.bandwidth(), 1000);
// TODO: test all the optional fields // TODO: test all the optional fields
} }

View file

@ -1,7 +1,7 @@
use crate::attribute::AttributePairs; use crate::attribute::AttributePairs;
use crate::types::{InStreamId, MediaType, ProtocolVersion}; use crate::types::{InStreamId, MediaType, ProtocolVersion};
use crate::utils::{parse_yes_or_no, quote, unquote}; use crate::utils::{parse_yes_or_no, quote, tag, unquote};
use crate::{Error, ErrorKind, Result}; use crate::{Error, ErrorKind};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
@ -114,7 +114,7 @@ impl ExtXMediaBuilder {
} }
/// Builds a `ExtXMedia` instance. /// Builds a `ExtXMedia` instance.
pub fn finish(self) -> Result<ExtXMedia> { pub fn finish(self) -> crate::Result<ExtXMedia> {
let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput); let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput);
let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput); let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput);
let name = track_assert_some!(self.name, ErrorKind::InvalidInput); let name = track_assert_some!(self.name, ErrorKind::InvalidInput);
@ -309,11 +309,11 @@ impl fmt::Display for ExtXMedia {
impl FromStr for ExtXMedia { impl FromStr for ExtXMedia {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let input = tag(input, Self::PREFIX)?;
let mut builder = ExtXMediaBuilder::new(); let mut builder = ExtXMediaBuilder::new();
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1); let attrs = AttributePairs::parse(input);
for attr in attrs { for attr in attrs {
let (key, value) = track!(attr)?; let (key, value) = track!(attr)?;
match key { match key {

View file

@ -1,17 +1,26 @@
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, SessionData};
use crate::utils::{quote, unquote};
use crate::{Error, ErrorKind, Result};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use getset::{Getters, MutGetters, Setters};
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, SessionData};
use crate::utils::{quote, tag, unquote};
use crate::{Error, ErrorKind};
/// [4.3.4.4. EXT-X-SESSION-DATA] /// [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 /// [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)] #[derive(Getters, MutGetters, Setters, Debug, Clone, PartialEq, Eq, Hash)]
#[get = "pub"]
#[set = "pub"]
#[get_mut = "pub"]
pub struct ExtXSessionData { pub struct ExtXSessionData {
/// The identifier of the data.
data_id: String, data_id: String,
/// The session data.
data: SessionData, data: SessionData,
/// The language of the data.
language: Option<String>, language: Option<String>,
} }
@ -36,21 +45,6 @@ impl ExtXSessionData {
} }
} }
/// Returns the identifier of the data.
pub const fn data_id(&self) -> &String {
&self.data_id
}
/// Returns the session data.
pub const fn data(&self) -> &SessionData {
&self.data
}
/// Returns the language of the data.
pub fn language(&self) -> Option<&String> {
self.language.as_ref()
}
/// Returns the protocol compatibility version that this tag requires. /// Returns the protocol compatibility version that this tag requires.
pub const fn requires_version(&self) -> ProtocolVersion { pub const fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1 ProtocolVersion::V1
@ -75,14 +69,15 @@ impl fmt::Display for ExtXSessionData {
impl FromStr for ExtXSessionData { impl FromStr for ExtXSessionData {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let input = tag(input, Self::PREFIX)?;
let mut data_id = None; let mut data_id = None;
let mut session_value = None; let mut session_value = None;
let mut uri = None; let mut uri = None;
let mut language = None; let mut language = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
let attrs = AttributePairs::parse(input);
for attr in attrs { for attr in attrs {
let (key, value) = track!(attr)?; let (key, value) = track!(attr)?;
match key { match key {
@ -119,23 +114,38 @@ mod test {
use super::*; use super::*;
#[test] #[test]
fn ext_x_session_data() { fn test_display() {
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into())); let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="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.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into())); let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="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.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz"); let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz");
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="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.to_string(), text);
}
#[test]
fn test_parser() {
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
assert_eq!(text.parse::<ExtXSessionData>().unwrap(), tag);
let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
assert_eq!(text.parse::<ExtXSessionData>().unwrap(), tag);
let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz");
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#;
assert_eq!(text.parse::<ExtXSessionData>().unwrap(), tag);
}
#[test]
fn test_requires_version() {
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
assert_eq!(tag.requires_version(), ProtocolVersion::V1); assert_eq!(tag.requires_version(), ProtocolVersion::V1);
} }
} }

View file

@ -1,8 +1,9 @@
use crate::types::{DecryptionKey, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use crate::types::{DecryptionKey, ProtocolVersion};
use crate::utils::tag;
/// [4.3.4.5. EXT-X-SESSION-KEY] /// [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 /// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
@ -37,13 +38,11 @@ impl fmt::Display for ExtXSessionKey {
} }
impl FromStr for ExtXSessionKey { impl FromStr for ExtXSessionKey {
type Err = Error; type Err = crate::Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let key = tag(input, Self::PREFIX)?.parse()?;
let suffix = s.split_at(Self::PREFIX.len()).1; Ok(Self::new(key))
let key = track!(suffix.parse())?;
Ok(ExtXSessionKey { key })
} }
} }

View file

@ -1,12 +1,13 @@
use std::fmt;
use std::str::FromStr;
use crate::attribute::AttributePairs; use crate::attribute::AttributePairs;
use crate::types::{ use crate::types::{
ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion, ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion,
SingleLineString, SingleLineString,
}; };
use crate::utils::{parse_u64, quote, unquote}; use crate::utils::{parse_u64, quote, tag, unquote};
use crate::{Error, ErrorKind, Result}; use crate::{Error, ErrorKind};
use std::fmt;
use std::str::FromStr;
/// [4.3.4.2. EXT-X-STREAM-INF] /// [4.3.4.2. EXT-X-STREAM-INF]
/// ///
@ -146,16 +147,15 @@ impl fmt::Display for ExtXStreamInf {
impl FromStr for ExtXStreamInf { impl FromStr for ExtXStreamInf {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut lines = s.splitn(2, '\n'); let mut lines = input.lines();
let first_line = lines.next().expect("Never fails").trim_end_matches('\r'); let first_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO!
let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput); let second_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO!
tag(first_line, Self::PREFIX)?;
track_assert!(
first_line.starts_with(Self::PREFIX),
ErrorKind::InvalidInput
);
let uri = track!(SingleLineString::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;
@ -186,6 +186,7 @@ impl FromStr for ExtXStreamInf {
} }
} }
} }
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput); let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
Ok(ExtXStreamInf { Ok(ExtXStreamInf {
uri, uri,

View file

@ -1,8 +1,8 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use trackable::error::ErrorKindExt;
use crate::types::ProtocolVersion;
use crate::utils::tag;
/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE] /// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
/// ///
@ -39,12 +39,11 @@ impl fmt::Display for ExtXDiscontinuitySequence {
} }
impl FromStr for ExtXDiscontinuitySequence { impl FromStr for ExtXDiscontinuitySequence {
type Err = Error; type Err = crate::Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(input: &str) -> Result<Self, Self::Err> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); let seq_num = tag(input, Self::PREFIX)?.parse().unwrap(); // TODO!
let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; Ok(Self::new(seq_num))
Ok(ExtXDiscontinuitySequence { seq_num })
} }
} }

View file

@ -1,39 +1,72 @@
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
use crate::types::{ByteRange, ProtocolVersion}; use crate::types::{ByteRange, ProtocolVersion};
use crate::{Error, ErrorKind, Result}; use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
/// [4.3.2.2. EXT-X-BYTERANGE] /// [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 /// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXByteRange { pub struct ExtXByteRange(ByteRange);
range: ByteRange,
}
impl ExtXByteRange { impl ExtXByteRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:"; pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
/// Makes a new `ExtXByteRange` tag. /// Makes a new `ExtXByteRange` tag.
pub const fn new(range: ByteRange) -> Self { /// # Example
ExtXByteRange { range } /// ```
/// use hls_m3u8::tags::ExtXByteRange;
///
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// ```
pub const fn new(length: usize, start: Option<usize>) -> Self {
Self(ByteRange::new(length, start))
} }
/// Returns the range of the associated media segment. /// Converts the [ExtXByteRange] to a [ByteRange].
pub const fn range(&self) -> ByteRange { /// # Example
self.range /// ```
/// use hls_m3u8::tags::ExtXByteRange;
/// use hls_m3u8::types::ByteRange;
///
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// let range: ByteRange = byte_range.to_range();
/// ```
pub const fn to_range(&self) -> ByteRange {
self.0
} }
/// Returns the protocol compatibility version that this tag requires. /// Returns the protocol compatibility version that this tag requires.
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXByteRange;
/// use hls_m3u8::types::ProtocolVersion;
///
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// assert_eq!(byte_range.requires_version(), ProtocolVersion::V4);
/// ```
pub const fn requires_version(&self) -> ProtocolVersion { pub const fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V4 ProtocolVersion::V4
} }
} }
impl Deref for ExtXByteRange {
type Target = ByteRange;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for ExtXByteRange { impl fmt::Display for ExtXByteRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.range) write!(f, "{}", Self::PREFIX)?;
write!(f, "{}", self.0)?;
Ok(())
} }
} }
@ -41,9 +74,30 @@ impl FromStr for ExtXByteRange {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(s: &str) -> Result<Self> {
// check if the string starts with the PREFIX
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput); track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let range = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?; let byte_range = s.split_at(Self::PREFIX.len()).1;
Ok(ExtXByteRange { range }) let tokens = byte_range.splitn(2, '@').collect::<Vec<_>>();
if tokens.is_empty() {
Err(ErrorKind::InvalidInput)?;
}
let length = tokens[0]
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e))?;
let start = {
let mut result = None;
if tokens.len() == 2 {
result = Some(
tokens[1]
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e))?,
);
}
result
};
Ok(ExtXByteRange::new(length, start))
} }
} }
@ -52,21 +106,40 @@ mod test {
use super::*; use super::*;
#[test] #[test]
fn ext_x_byterange() { fn test_display() {
let tag = ExtXByteRange::new(ByteRange { let byte_range = ExtXByteRange::new(0, Some(5));
length: 3, assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:0@5".to_string());
start: None,
});
assert_eq!("#EXT-X-BYTERANGE:3".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3");
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
let tag = ExtXByteRange::new(ByteRange { let byte_range = ExtXByteRange::new(99999, Some(2));
length: 3, assert_eq!(
start: Some(5), byte_range.to_string(),
}); "#EXT-X-BYTERANGE:99999@2".to_string()
assert_eq!("#EXT-X-BYTERANGE:3@5".parse().ok(), Some(tag)); );
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3@5");
assert_eq!(tag.requires_version(), ProtocolVersion::V4); let byte_range = ExtXByteRange::new(99999, None);
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:99999".to_string());
}
#[test]
fn test_parse() {
let byte_range = ExtXByteRange::new(99999, Some(2));
assert_eq!(
byte_range,
"#EXT-X-BYTERANGE:99999@2".parse::<ExtXByteRange>().unwrap()
);
let byte_range = ExtXByteRange::new(99999, None);
assert_eq!(
byte_range,
"#EXT-X-BYTERANGE:99999".parse::<ExtXByteRange>().unwrap()
);
}
#[test]
fn test_deref() {
let byte_range = ExtXByteRange::new(0, Some(22));
assert_eq!(*byte_range.length(), 0);
assert_eq!(*byte_range.start(), Some(22));
} }
} }

View file

@ -1,13 +1,16 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::{Error, Result};
/// [4.3.2.3. EXT-X-DISCONTINUITY] /// [4.3.2.3. EXT-X-DISCONTINUITY]
/// ///
/// [4.3.2.3. EXT-X-DISCONTINUITY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.3 /// [4.3.2.3. EXT-X-DISCONTINUITY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXDiscontinuity; pub struct ExtXDiscontinuity;
impl ExtXDiscontinuity { impl ExtXDiscontinuity {
pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY"; pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY";
@ -25,8 +28,9 @@ impl fmt::Display for ExtXDiscontinuity {
impl FromStr for ExtXDiscontinuity { impl FromStr for ExtXDiscontinuity {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput); fn from_str(input: &str) -> Result<Self> {
tag(input, Self::PREFIX)?;
Ok(ExtXDiscontinuity) Ok(ExtXDiscontinuity)
} }
} }

View file

@ -100,13 +100,7 @@ mod test {
assert_eq!(tag.to_string(), text); assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V6); assert_eq!(tag.requires_version(), ProtocolVersion::V6);
let tag = ExtXMap::with_range( let tag = ExtXMap::with_range("foo", ByteRange::new(9, Some(2)));
"foo",
ByteRange {
length: 9,
start: Some(2),
},
);
let text = r#"#EXT-X-MAP:URI="foo",BYTERANGE="9@2""#; let text = r#"#EXT-X-MAP:URI="foo",BYTERANGE="9@2""#;
track_try_unwrap!(ExtXMap::from_str(text)); track_try_unwrap!(ExtXMap::from_str(text));
assert_eq!(text.parse().ok(), Some(tag.clone())); assert_eq!(text.parse().ok(), Some(tag.clone()));

View file

@ -1,18 +1,32 @@
use crate::{Error, ErrorKind, Result};
use std::fmt; use std::fmt;
use std::str::{self, FromStr}; use std::str::{self, FromStr};
use getset::{Getters, MutGetters, Setters};
use trackable::error::ErrorKindExt; use trackable::error::ErrorKindExt;
use crate::{Error, ErrorKind, Result};
/// Byte range. /// Byte range.
/// ///
/// See: [4.3.2.2. EXT-X-BYTERANGE] /// 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 /// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
#[allow(missing_docs)] #[derive(Getters, Setters, MutGetters, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[get = "pub"]
#[set = "pub"]
#[get_mut = "pub"]
pub struct ByteRange { pub struct ByteRange {
pub length: usize, /// The length of the range.
pub start: Option<usize>, length: usize,
/// The start of the range.
start: Option<usize>,
}
impl ByteRange {
/// Creates a new [ByteRange].
pub const fn new(length: usize, start: Option<usize>) -> Self {
Self { length, start }
}
} }
impl fmt::Display for ByteRange { impl fmt::Display for ByteRange {
@ -27,20 +41,28 @@ impl fmt::Display for ByteRange {
impl FromStr for ByteRange { impl FromStr for ByteRange {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(s: &str) -> Result<Self> {
let mut tokens = s.splitn(2, '@'); let tokens = s.splitn(2, '@').collect::<Vec<_>>();
let length = tokens.next().expect("Never fails"); if tokens.is_empty() {
let start = if let Some(start) = tokens.next() { Err(ErrorKind::InvalidInput)?;
Some(track!(start }
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e)))?) let length = tokens[0]
} else { .parse()
None .map_err(|e| ErrorKind::InvalidInput.cause(e))?;
let start = {
let mut result = None;
if tokens.len() == 2 {
result = Some(
tokens[1]
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e))?,
);
}
result
}; };
Ok(ByteRange { Ok(ByteRange::new(length, start))
length: track!(length.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
start,
})
} }
} }

View file

@ -44,6 +44,19 @@ pub(crate) fn quote<T: ToString>(value: T) -> String {
format!("\"{}\"", value.to_string().replace("\"", "")) format!("\"{}\"", value.to_string().replace("\"", ""))
} }
/// Checks, if the given tag is at the start of the input. If this is the case, it will remove it
/// return the rest of the input, otherwise it will return an error.
pub(crate) fn tag<T>(input: &str, tag: T) -> crate::Result<&str>
where
T: AsRef<str>,
{
if !input.starts_with(tag.as_ref()) {
Err(ErrorKind::InvalidInput)?; // TODO!
}
let result = input.split_at(tag.as_ref().len()).1;
Ok(result)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -74,4 +87,21 @@ mod tests {
assert_eq!(quote("value"), "\"value\"".to_string()); assert_eq!(quote("value"), "\"value\"".to_string());
assert_eq!(quote("\"value\""), "\"value\"".to_string()); assert_eq!(quote("\"value\""), "\"value\"".to_string());
} }
#[test]
fn test_tag() {
let input = "HelloMyFriendThisIsASampleString";
let input = tag(input, "Hello").unwrap();
assert_eq!(input, "MyFriendThisIsASampleString");
let input = tag(input, "My").unwrap();
assert_eq!(input, "FriendThisIsASampleString");
let input = tag(input, "FriendThisIs").unwrap();
assert_eq!(input, "ASampleString");
let input = tag(input, "A").unwrap();
assert_eq!(input, "SampleString");
}
} }