mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 08:31:00 +00:00
updated parser
This commit is contained in:
parent
91c6698f16
commit
1a35463185
15 changed files with 289 additions and 175 deletions
|
@ -17,6 +17,7 @@ codecov = {repository = "sile/hls_m3u8"}
|
|||
|
||||
[dependencies]
|
||||
trackable = "0.2"
|
||||
getset = "0.0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
clap = "2"
|
||||
|
|
|
@ -141,7 +141,7 @@ impl MediaPlaylistBuilder {
|
|||
|
||||
// CHECK: `#EXT-X-BYTE-RANGE`
|
||||
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);
|
||||
track_assert_eq!(last_uri, s.uri(), ErrorKind::InvalidInput);
|
||||
} else {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use crate::types::ProtocolVersion;
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
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]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
|
||||
|
@ -27,8 +29,8 @@ impl fmt::Display for ExtM3u {
|
|||
impl FromStr for ExtM3u {
|
||||
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, Self::Err> {
|
||||
tag(input, Self::PREFIX)?;
|
||||
Ok(ExtM3u)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use crate::types::ProtocolVersion;
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
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]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
|
||||
|
@ -37,10 +39,8 @@ impl fmt::Display for ExtXVersion {
|
|||
impl FromStr for ExtXVersion {
|
||||
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 version = track!(suffix.parse())?;
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let version = tag(input, Self::PREFIX)?.parse()?;
|
||||
Ok(ExtXVersion::new(version))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,35 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use getset::{Getters, MutGetters, Setters};
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion};
|
||||
use crate::utils::parse_u64;
|
||||
use crate::utils::{quote, unquote};
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use crate::utils::{quote, tag, unquote};
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
/// [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)]
|
||||
#[derive(Getters, Setters, MutGetters, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[get = "pub"]
|
||||
#[set = "pub"]
|
||||
#[get_mut = "pub"]
|
||||
pub struct ExtXIFrameStreamInf {
|
||||
/// The URI, that identifies the associated media playlist.
|
||||
uri: String,
|
||||
/// The peak segment bit rate of the variant stream.
|
||||
bandwidth: u64,
|
||||
/// The average segment bit rate of the variant stream.
|
||||
average_bandwidth: Option<u64>,
|
||||
/// A string that represents the list of codec types contained the variant stream.
|
||||
codecs: Option<String>,
|
||||
/// The optimal pixel resolution at which to display all the video in the variant stream.
|
||||
resolution: Option<DecimalResolution>,
|
||||
/// The HDCP level of the variant stream.
|
||||
hdcp_level: Option<HdcpLevel>,
|
||||
/// The group identifier for the video in the variant stream.
|
||||
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.
|
||||
pub const fn requires_version(&self) -> ProtocolVersion {
|
||||
ProtocolVersion::V1
|
||||
|
@ -105,8 +83,8 @@ impl fmt::Display for ExtXIFrameStreamInf {
|
|||
impl FromStr for ExtXIFrameStreamInf {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let input = tag(input, Self::PREFIX)?;
|
||||
|
||||
let mut uri = None;
|
||||
let mut bandwidth = None;
|
||||
|
@ -115,7 +93,8 @@ impl FromStr for ExtXIFrameStreamInf {
|
|||
let mut resolution = None;
|
||||
let mut hdcp_level = 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 {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
|
@ -167,7 +146,7 @@ mod test {
|
|||
);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{InStreamId, MediaType, ProtocolVersion};
|
||||
use crate::utils::{parse_yes_or_no, quote, unquote};
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use crate::utils::{parse_yes_or_no, quote, tag, unquote};
|
||||
use crate::{Error, ErrorKind};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -114,7 +114,7 @@ impl ExtXMediaBuilder {
|
|||
}
|
||||
|
||||
/// 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 group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput);
|
||||
let name = track_assert_some!(self.name, ErrorKind::InvalidInput);
|
||||
|
@ -309,11 +309,11 @@ impl fmt::Display for ExtXMedia {
|
|||
impl FromStr for ExtXMedia {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let input = tag(input, Self::PREFIX)?;
|
||||
|
||||
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 {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
|
|
|
@ -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::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]: 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 {
|
||||
/// The identifier of the data.
|
||||
data_id: String,
|
||||
/// The session data.
|
||||
data: SessionData,
|
||||
/// The language of the data.
|
||||
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.
|
||||
pub const fn requires_version(&self) -> ProtocolVersion {
|
||||
ProtocolVersion::V1
|
||||
|
@ -75,14 +69,15 @@ impl fmt::Display for ExtXSessionData {
|
|||
impl FromStr for ExtXSessionData {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let input = tag(input, Self::PREFIX)?;
|
||||
|
||||
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);
|
||||
|
||||
let attrs = AttributePairs::parse(input);
|
||||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
|
@ -119,23 +114,38 @@ mod test {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ext_x_session_data() {
|
||||
fn test_display() {
|
||||
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().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
|
||||
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().ok(), Some(tag.clone()));
|
||||
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 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);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::types::{DecryptionKey, ProtocolVersion};
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
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]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
|
||||
|
@ -37,13 +38,11 @@ impl fmt::Display for ExtXSessionKey {
|
|||
}
|
||||
|
||||
impl FromStr for ExtXSessionKey {
|
||||
type Err = Error;
|
||||
type Err = crate::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 })
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let key = tag(input, Self::PREFIX)?.parse()?;
|
||||
Ok(Self::new(key))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::attribute::AttributePairs;
|
||||
use crate::types::{
|
||||
ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion,
|
||||
SingleLineString,
|
||||
};
|
||||
use crate::utils::{parse_u64, quote, unquote};
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use crate::utils::{parse_u64, quote, tag, unquote};
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
/// [4.3.4.2. EXT-X-STREAM-INF]
|
||||
///
|
||||
|
@ -146,16 +147,15 @@ impl fmt::Display for ExtXStreamInf {
|
|||
impl FromStr for ExtXStreamInf {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let mut lines = s.splitn(2, '\n');
|
||||
let first_line = lines.next().expect("Never fails").trim_end_matches('\r');
|
||||
let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput);
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let mut lines = input.lines();
|
||||
let first_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO!
|
||||
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 mut bandwidth = None;
|
||||
let mut average_bandwidth = None;
|
||||
let mut codecs = None;
|
||||
|
@ -186,6 +186,7 @@ impl FromStr for ExtXStreamInf {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
|
||||
Ok(ExtXStreamInf {
|
||||
uri,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::types::ProtocolVersion;
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use trackable::error::ErrorKindExt;
|
||||
|
||||
use crate::types::ProtocolVersion;
|
||||
use crate::utils::tag;
|
||||
|
||||
/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
|
||||
///
|
||||
|
@ -39,12 +39,11 @@ impl fmt::Display for ExtXDiscontinuitySequence {
|
|||
}
|
||||
|
||||
impl FromStr for ExtXDiscontinuitySequence {
|
||||
type Err = Error;
|
||||
type Err = crate::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
|
||||
Ok(ExtXDiscontinuitySequence { seq_num })
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let seq_num = tag(input, Self::PREFIX)?.parse().unwrap(); // TODO!
|
||||
Ok(Self::new(seq_num))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::{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]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXByteRange {
|
||||
range: ByteRange,
|
||||
}
|
||||
pub struct ExtXByteRange(ByteRange);
|
||||
|
||||
impl ExtXByteRange {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
|
||||
|
||||
/// Makes a new `ExtXByteRange` tag.
|
||||
pub const fn new(range: ByteRange) -> Self {
|
||||
ExtXByteRange { range }
|
||||
/// # Example
|
||||
/// ```
|
||||
/// 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.
|
||||
pub const fn range(&self) -> ByteRange {
|
||||
self.range
|
||||
/// Converts the [ExtXByteRange] to a [ByteRange].
|
||||
/// # Example
|
||||
/// ```
|
||||
/// 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.
|
||||
/// # 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 {
|
||||
ProtocolVersion::V4
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ExtXByteRange {
|
||||
type Target = ByteRange;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtXByteRange {
|
||||
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;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
// check if the string starts with the PREFIX
|
||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||
let range = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
|
||||
Ok(ExtXByteRange { range })
|
||||
let byte_range = s.split_at(Self::PREFIX.len()).1;
|
||||
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::*;
|
||||
|
||||
#[test]
|
||||
fn ext_x_byterange() {
|
||||
let tag = ExtXByteRange::new(ByteRange {
|
||||
length: 3,
|
||||
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);
|
||||
fn test_display() {
|
||||
let byte_range = ExtXByteRange::new(0, Some(5));
|
||||
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:0@5".to_string());
|
||||
|
||||
let tag = ExtXByteRange::new(ByteRange {
|
||||
length: 3,
|
||||
start: Some(5),
|
||||
});
|
||||
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, Some(2));
|
||||
assert_eq!(
|
||||
byte_range.to_string(),
|
||||
"#EXT-X-BYTERANGE:99999@2".to_string()
|
||||
);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use crate::types::ProtocolVersion;
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
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]: https://tools.ietf.org/html/rfc8216#section-4.3.2.3
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXDiscontinuity;
|
||||
|
||||
impl ExtXDiscontinuity {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY";
|
||||
|
||||
|
@ -25,8 +28,9 @@ impl fmt::Display for ExtXDiscontinuity {
|
|||
|
||||
impl FromStr for ExtXDiscontinuity {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,13 +100,7 @@ mod test {
|
|||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
|
||||
|
||||
let tag = ExtXMap::with_range(
|
||||
"foo",
|
||||
ByteRange {
|
||||
length: 9,
|
||||
start: Some(2),
|
||||
},
|
||||
);
|
||||
let tag = ExtXMap::with_range("foo", ByteRange::new(9, Some(2)));
|
||||
let text = r#"#EXT-X-MAP:URI="foo",BYTERANGE="9@2""#;
|
||||
track_try_unwrap!(ExtXMap::from_str(text));
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
use crate::{Error, ErrorKind, Result};
|
||||
use std::fmt;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use getset::{Getters, MutGetters, Setters};
|
||||
use trackable::error::ErrorKindExt;
|
||||
|
||||
use crate::{Error, ErrorKind, Result};
|
||||
|
||||
/// 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(Getters, Setters, MutGetters, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[get = "pub"]
|
||||
#[set = "pub"]
|
||||
#[get_mut = "pub"]
|
||||
pub struct ByteRange {
|
||||
pub length: usize,
|
||||
pub start: Option<usize>,
|
||||
/// The length of the range.
|
||||
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 {
|
||||
|
@ -27,20 +41,28 @@ impl fmt::Display for ByteRange {
|
|||
|
||||
impl FromStr for ByteRange {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let mut tokens = s.splitn(2, '@');
|
||||
let length = tokens.next().expect("Never fails");
|
||||
let start = if let Some(start) = tokens.next() {
|
||||
Some(track!(start
|
||||
let tokens = s.splitn(2, '@').collect::<Vec<_>>();
|
||||
if tokens.is_empty() {
|
||||
Err(ErrorKind::InvalidInput)?;
|
||||
}
|
||||
|
||||
let length = tokens[0]
|
||||
.parse()
|
||||
.map_err(|e| ErrorKind::InvalidInput.cause(e)))?)
|
||||
} else {
|
||||
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 {
|
||||
length: track!(length.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
|
||||
start,
|
||||
})
|
||||
Ok(ByteRange::new(length, start))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
30
src/utils.rs
30
src/utils.rs
|
@ -44,6 +44,19 @@ pub(crate) fn quote<T: ToString>(value: T) -> String {
|
|||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -74,4 +87,21 @@ mod tests {
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue