mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-22 07:10:59 +00:00
New Error type
This commit is contained in:
parent
1a35463185
commit
c8f3df1228
42 changed files with 731 additions and 529 deletions
|
@ -16,8 +16,8 @@ travis-ci = {repository = "sile/hls_m3u8"}
|
||||||
codecov = {repository = "sile/hls_m3u8"}
|
codecov = {repository = "sile/hls_m3u8"}
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
trackable = "0.2"
|
|
||||||
getset = "0.0.8"
|
getset = "0.0.8"
|
||||||
|
failure = "0.1.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
clap = "2"
|
clap = "2"
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate hls_m3u8;
|
extern crate hls_m3u8;
|
||||||
#[macro_use]
|
|
||||||
extern crate trackable;
|
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use hls_m3u8::{MasterPlaylist, MediaPlaylist};
|
use hls_m3u8::{MasterPlaylist, MediaPlaylist};
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use trackable::error::Failure;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let matches = App::new("parse")
|
let matches = App::new("parse")
|
||||||
|
@ -19,17 +16,15 @@ fn main() {
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
let mut m3u8 = String::new();
|
let mut m3u8 = String::new();
|
||||||
track_try_unwrap!(io::stdin()
|
io::stdin().read_to_string(&mut m3u8).unwrap();
|
||||||
.read_to_string(&mut m3u8)
|
|
||||||
.map_err(Failure::from_error));
|
|
||||||
|
|
||||||
match matches.value_of("M3U8_TYPE").unwrap() {
|
match matches.value_of("M3U8_TYPE").unwrap() {
|
||||||
"media" => {
|
"media" => {
|
||||||
let playlist: MediaPlaylist = track_try_unwrap!(m3u8.parse());
|
let playlist: MediaPlaylist = m3u8.parse().unwrap();
|
||||||
println!("{}", playlist);
|
println!("{}", playlist);
|
||||||
}
|
}
|
||||||
"master" => {
|
"master" => {
|
||||||
let playlist: MasterPlaylist = track_try_unwrap!(m3u8.parse());
|
let playlist: MasterPlaylist = m3u8.parse().unwrap();
|
||||||
println!("{}", playlist);
|
println!("{}", playlist);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{ErrorKind, Result};
|
use crate::{Error, Result};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
|
@ -25,18 +25,13 @@ impl<'a> AttributePairs<'a> {
|
||||||
return Ok(key);
|
return Ok(key);
|
||||||
}
|
}
|
||||||
b'A'..=b'Z' | b'0'..=b'9' | b'-' => {}
|
b'A'..=b'Z' | b'0'..=b'9' | b'-' => {}
|
||||||
_ => track_panic!(
|
_ => {
|
||||||
ErrorKind::InvalidInput,
|
return Err(Error::invalid_attribute(self.input.to_string()));
|
||||||
"Malformed attribute name: {:?}",
|
}
|
||||||
self.input
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
track_panic!(
|
|
||||||
ErrorKind::InvalidInput,
|
Err(Error::missing_value(self.input.to_string()))
|
||||||
"No attribute value: {:?}",
|
|
||||||
self.input
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_raw_value(&mut self) -> &'a str {
|
fn parse_raw_value(&mut self) -> &'a str {
|
||||||
|
@ -64,19 +59,15 @@ impl<'a> AttributePairs<'a> {
|
||||||
}
|
}
|
||||||
impl<'a> Iterator for AttributePairs<'a> {
|
impl<'a> Iterator for AttributePairs<'a> {
|
||||||
type Item = Result<(&'a str, &'a str)>;
|
type Item = Result<(&'a str, &'a str)>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.input.is_empty() {
|
if self.input.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = || -> Result<(&'a str, &'a str)> {
|
let result = || -> Result<(&'a str, &'a str)> {
|
||||||
let key = track!(self.parse_name())?;
|
let key = self.parse_name()?;
|
||||||
track_assert!(
|
self.visited_keys.insert(key);
|
||||||
self.visited_keys.insert(key),
|
|
||||||
ErrorKind::InvalidInput,
|
|
||||||
"Duplicate attribute key: {:?}",
|
|
||||||
key
|
|
||||||
);
|
|
||||||
|
|
||||||
let value = self.parse_raw_value();
|
let value = self.parse_raw_value();
|
||||||
Ok((key, value))
|
Ok((key, value))
|
||||||
|
|
186
src/error.rs
186
src/error.rs
|
@ -1,13 +1,179 @@
|
||||||
use trackable::error::{ErrorKind as TrackableErrorKind, TrackableError};
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// This crate specific `Error` type.
|
use failure::{Backtrace, Context, Fail};
|
||||||
#[derive(Debug, Clone, TrackableError)]
|
|
||||||
pub struct Error(TrackableError<ErrorKind>);
|
|
||||||
|
|
||||||
/// Possible error kinds.
|
/// This crate specific `Result` type.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub enum ErrorKind {
|
#[derive(Debug, Fail, Clone)]
|
||||||
InvalidInput,
|
pub enum AttributeError {
|
||||||
|
#[fail(display = "The attribute has an invalid name; {:?}", _0)]
|
||||||
|
InvalidAttribute(String),
|
||||||
|
#[fail(display = "A value is missing for the attribute: {}", _0)]
|
||||||
|
MissingValue(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Fail, Clone)]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
#[fail(display = "AttributeError: {}", _0)]
|
||||||
|
AttributeError(AttributeError),
|
||||||
|
|
||||||
|
#[fail(display = "UnknownError: {}", _0)]
|
||||||
|
UnknownError(String),
|
||||||
|
|
||||||
|
#[fail(display = "A value is missing for the attribute {}", _0)]
|
||||||
|
MissingValue(String),
|
||||||
|
|
||||||
|
#[fail(display = "Invalid Input")]
|
||||||
|
InvalidInput,
|
||||||
|
|
||||||
|
#[fail(display = "ParseIntError: {}", _0)]
|
||||||
|
ParseIntError(String),
|
||||||
|
|
||||||
|
#[fail(display = "ParseFloatError: {}", _0)]
|
||||||
|
ParseFloatError(String),
|
||||||
|
|
||||||
|
#[fail(display = "MissingTag: Expected {} at the start of {:?}", tag, input)]
|
||||||
|
MissingTag { tag: String, input: String },
|
||||||
|
|
||||||
|
#[fail(display = "CustomError: {}", _0)]
|
||||||
|
Custom(String),
|
||||||
|
|
||||||
|
#[fail(display = "Unmatched Group: {:?}", _0)]
|
||||||
|
UnmatchedGroup(String),
|
||||||
|
|
||||||
|
#[fail(display = "Unknown Protocol version: {:?}", _0)]
|
||||||
|
UnknownProtocolVersion(String),
|
||||||
|
|
||||||
|
/// Hints that destructuring should not be exhaustive.
|
||||||
|
///
|
||||||
|
/// This enum may grow additional variants, so this makes sure clients
|
||||||
|
/// don't count on exhaustive matching. (Otherwise, adding a new variant
|
||||||
|
/// could break existing code.)
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[fail(display = "Invalid error")]
|
||||||
|
__Nonexhaustive,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
inner: Context<ErrorKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fail for Error {
|
||||||
|
fn cause(&self) -> Option<&dyn Fail> {
|
||||||
|
self.inner.cause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backtrace(&self) -> Option<&Backtrace> {
|
||||||
|
self.inner.backtrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ErrorKind> for Error {
|
||||||
|
fn from(kind: ErrorKind) -> Error {
|
||||||
|
Error::from(Context::new(kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Context<ErrorKind>> for Error {
|
||||||
|
fn from(inner: Context<ErrorKind>) -> Error {
|
||||||
|
Error { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! from_error {
|
||||||
|
( $( $f:tt ),* ) => {
|
||||||
|
$(
|
||||||
|
impl From<$f> for ErrorKind {
|
||||||
|
fn from(value: $f) -> Self {
|
||||||
|
Self::$f(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
from_error!(AttributeError);
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub(crate) fn invalid_attribute<T: ToString>(value: T) -> Self {
|
||||||
|
Self::from(ErrorKind::from(AttributeError::InvalidAttribute(
|
||||||
|
value.to_string(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn missing_attribute_value<T: ToString>(value: T) -> Self {
|
||||||
|
Self::from(ErrorKind::from(AttributeError::MissingValue(
|
||||||
|
value.to_string(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unknown<T>(value: T) -> Self
|
||||||
|
where
|
||||||
|
T: error::Error,
|
||||||
|
{
|
||||||
|
Self::from(ErrorKind::UnknownError(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn missing_value<T: ToString>(value: T) -> Self {
|
||||||
|
Self::from(ErrorKind::MissingValue(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn invalid_input() -> Self {
|
||||||
|
Self::from(ErrorKind::InvalidInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_int_error<T: ToString>(value: T) -> Self {
|
||||||
|
Self::from(ErrorKind::ParseIntError(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_float_error<T: ToString>(value: T) -> Self {
|
||||||
|
Self::from(ErrorKind::ParseFloatError(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn missing_tag<T, U>(tag: T, input: U) -> Self
|
||||||
|
where
|
||||||
|
T: ToString,
|
||||||
|
U: ToString,
|
||||||
|
{
|
||||||
|
Self::from(ErrorKind::MissingTag {
|
||||||
|
tag: tag.to_string(),
|
||||||
|
input: input.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unmatched_group<T: ToString>(value: T) -> Self {
|
||||||
|
Self::from(ErrorKind::UnmatchedGroup(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn custom<T>(value: T) -> Self
|
||||||
|
where
|
||||||
|
T: fmt::Display,
|
||||||
|
{
|
||||||
|
Self::from(ErrorKind::Custom(value.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unknown_protocol_version<T: ToString>(value: T) -> Self {
|
||||||
|
Self::from(ErrorKind::UnknownProtocolVersion(value.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::num::ParseIntError> for Error {
|
||||||
|
fn from(value: ::std::num::ParseIntError) -> Self {
|
||||||
|
Error::parse_int_error(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::num::ParseFloatError> for Error {
|
||||||
|
fn from(value: ::std::num::ParseFloatError) -> Self {
|
||||||
|
Error::parse_float_error(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl TrackableErrorKind for ErrorKind {}
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
clippy::nursery,
|
clippy::nursery,
|
||||||
clippy::cargo
|
clippy::cargo
|
||||||
)]
|
)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
//! [HLS] m3u8 parser/generator.
|
//! [HLS] m3u8 parser/generator.
|
||||||
//!
|
//!
|
||||||
//! [HLS]: https://tools.ietf.org/html/rfc8216
|
//! [HLS]: https://tools.ietf.org/html/rfc8216
|
||||||
|
@ -25,9 +26,6 @@
|
||||||
//!
|
//!
|
||||||
//! assert!(m3u8.parse::<MediaPlaylist>().is_ok());
|
//! assert!(m3u8.parse::<MediaPlaylist>().is_ok());
|
||||||
//! ```
|
//! ```
|
||||||
#![warn(missing_docs)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate trackable;
|
|
||||||
|
|
||||||
pub use error::{Error, ErrorKind};
|
pub use error::{Error, ErrorKind};
|
||||||
pub use master_playlist::{MasterPlaylist, MasterPlaylistBuilder};
|
pub use master_playlist::{MasterPlaylist, MasterPlaylistBuilder};
|
||||||
|
@ -45,5 +43,4 @@ mod media_playlist;
|
||||||
mod media_segment;
|
mod media_segment;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
/// This crate specific `Result` type.
|
pub use error::Result;
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
77
src/line.rs
77
src/line.rs
|
@ -1,9 +1,10 @@
|
||||||
use crate::tags;
|
|
||||||
use crate::types::SingleLineString;
|
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::tags;
|
||||||
|
use crate::types::SingleLineString;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Lines<'a> {
|
pub struct Lines<'a> {
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
|
@ -13,11 +14,12 @@ impl<'a> Lines<'a> {
|
||||||
Lines { input }
|
Lines { input }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_line(&mut self) -> Result<Line<'a>> {
|
fn read_line(&mut self) -> crate::Result<Line<'a>> {
|
||||||
let mut end = self.input.len();
|
let mut end = self.input.len();
|
||||||
let mut next_start = self.input.len();
|
let mut next_start = self.input.len();
|
||||||
let mut adjust = 0;
|
let mut adjust = 0;
|
||||||
let mut next_line_of_ext_x_stream_inf = false;
|
let mut next_line_of_ext_x_stream_inf = false;
|
||||||
|
|
||||||
for (i, c) in self.input.char_indices() {
|
for (i, c) in self.input.char_indices() {
|
||||||
match c {
|
match c {
|
||||||
'\n' => {
|
'\n' => {
|
||||||
|
@ -36,33 +38,39 @@ impl<'a> Lines<'a> {
|
||||||
adjust = 1;
|
adjust = 1;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
track_assert!(!c.is_control(), ErrorKind::InvalidInput);
|
if c.is_control() {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
adjust = 0;
|
adjust = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let raw_line = &self.input[..end];
|
let raw_line = &self.input[..end];
|
||||||
|
|
||||||
let line = if raw_line.is_empty() {
|
let line = if raw_line.is_empty() {
|
||||||
Line::Blank
|
Line::Blank
|
||||||
} else if raw_line.starts_with("#EXT") {
|
} else if raw_line.starts_with("#EXT") {
|
||||||
Line::Tag(track!(raw_line.parse())?)
|
Line::Tag((raw_line.parse())?)
|
||||||
} else if raw_line.starts_with('#') {
|
} else if raw_line.starts_with('#') {
|
||||||
Line::Comment(raw_line)
|
Line::Comment(raw_line)
|
||||||
} else {
|
} else {
|
||||||
let uri = track!(SingleLineString::new(raw_line))?;
|
let uri = SingleLineString::new(raw_line)?;
|
||||||
Line::Uri(uri)
|
Line::Uri(uri)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.input = &self.input[next_start..];
|
self.input = &self.input[next_start..];
|
||||||
Ok(line)
|
Ok(line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> Iterator for Lines<'a> {
|
impl<'a> Iterator for Lines<'a> {
|
||||||
type Item = Result<Line<'a>>;
|
type Item = crate::Result<Line<'a>>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.input.is_empty() {
|
if self.input.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
match track!(self.read_line()) {
|
|
||||||
|
match self.read_line() {
|
||||||
Err(e) => Some(Err(e)),
|
Err(e) => Some(Err(e)),
|
||||||
Ok(line) => Some(Ok(line)),
|
Ok(line) => Some(Ok(line)),
|
||||||
}
|
}
|
||||||
|
@ -105,6 +113,7 @@ pub enum Tag {
|
||||||
ExtXStart(tags::ExtXStart),
|
ExtXStart(tags::ExtXStart),
|
||||||
Unknown(SingleLineString),
|
Unknown(SingleLineString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Tag {
|
impl fmt::Display for Tag {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -134,55 +143,57 @@ impl fmt::Display for Tag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Tag {
|
impl FromStr for Tag {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if s.starts_with(tags::ExtM3u::PREFIX) {
|
if s.starts_with(tags::ExtM3u::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtM3u))
|
(s.parse().map(Tag::ExtM3u))
|
||||||
} else if s.starts_with(tags::ExtXVersion::PREFIX) {
|
} else if s.starts_with(tags::ExtXVersion::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXVersion))
|
(s.parse().map(Tag::ExtXVersion))
|
||||||
} else if s.starts_with(tags::ExtInf::PREFIX) {
|
} else if s.starts_with(tags::ExtInf::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtInf))
|
(s.parse().map(Tag::ExtInf))
|
||||||
} else if s.starts_with(tags::ExtXByteRange::PREFIX) {
|
} else if s.starts_with(tags::ExtXByteRange::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXByteRange))
|
(s.parse().map(Tag::ExtXByteRange))
|
||||||
} else if s.starts_with(tags::ExtXDiscontinuity::PREFIX) {
|
} else if s.starts_with(tags::ExtXDiscontinuity::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXDiscontinuity))
|
(s.parse().map(Tag::ExtXDiscontinuity))
|
||||||
} else if s.starts_with(tags::ExtXKey::PREFIX) {
|
} else if s.starts_with(tags::ExtXKey::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXKey))
|
(s.parse().map(Tag::ExtXKey))
|
||||||
} else if s.starts_with(tags::ExtXMap::PREFIX) {
|
} else if s.starts_with(tags::ExtXMap::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXMap))
|
(s.parse().map(Tag::ExtXMap))
|
||||||
} else if s.starts_with(tags::ExtXProgramDateTime::PREFIX) {
|
} else if s.starts_with(tags::ExtXProgramDateTime::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXProgramDateTime))
|
(s.parse().map(Tag::ExtXProgramDateTime))
|
||||||
} else if s.starts_with(tags::ExtXTargetDuration::PREFIX) {
|
} else if s.starts_with(tags::ExtXTargetDuration::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXTargetDuration))
|
(s.parse().map(Tag::ExtXTargetDuration))
|
||||||
} else if s.starts_with(tags::ExtXDateRange::PREFIX) {
|
} else if s.starts_with(tags::ExtXDateRange::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXDateRange))
|
(s.parse().map(Tag::ExtXDateRange))
|
||||||
} else if s.starts_with(tags::ExtXMediaSequence::PREFIX) {
|
} else if s.starts_with(tags::ExtXMediaSequence::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXMediaSequence))
|
(s.parse().map(Tag::ExtXMediaSequence))
|
||||||
} else if s.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) {
|
} else if s.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXDiscontinuitySequence))
|
(s.parse().map(Tag::ExtXDiscontinuitySequence))
|
||||||
} else if s.starts_with(tags::ExtXEndList::PREFIX) {
|
} else if s.starts_with(tags::ExtXEndList::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXEndList))
|
(s.parse().map(Tag::ExtXEndList))
|
||||||
} else if s.starts_with(tags::ExtXPlaylistType::PREFIX) {
|
} else if s.starts_with(tags::ExtXPlaylistType::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXPlaylistType))
|
(s.parse().map(Tag::ExtXPlaylistType))
|
||||||
} else if s.starts_with(tags::ExtXIFramesOnly::PREFIX) {
|
} else if s.starts_with(tags::ExtXIFramesOnly::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXIFramesOnly))
|
(s.parse().map(Tag::ExtXIFramesOnly))
|
||||||
} else if s.starts_with(tags::ExtXMedia::PREFIX) {
|
} else if s.starts_with(tags::ExtXMedia::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXMedia))
|
(s.parse().map(Tag::ExtXMedia))
|
||||||
} else if s.starts_with(tags::ExtXStreamInf::PREFIX) {
|
} else if s.starts_with(tags::ExtXStreamInf::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXStreamInf))
|
(s.parse().map(Tag::ExtXStreamInf))
|
||||||
} else if s.starts_with(tags::ExtXIFrameStreamInf::PREFIX) {
|
} else if s.starts_with(tags::ExtXIFrameStreamInf::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXIFrameStreamInf))
|
(s.parse().map(Tag::ExtXIFrameStreamInf))
|
||||||
} else if s.starts_with(tags::ExtXSessionData::PREFIX) {
|
} else if s.starts_with(tags::ExtXSessionData::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXSessionData))
|
(s.parse().map(Tag::ExtXSessionData))
|
||||||
} else if s.starts_with(tags::ExtXSessionKey::PREFIX) {
|
} else if s.starts_with(tags::ExtXSessionKey::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXSessionKey))
|
(s.parse().map(Tag::ExtXSessionKey))
|
||||||
} else if s.starts_with(tags::ExtXIndependentSegments::PREFIX) {
|
} else if s.starts_with(tags::ExtXIndependentSegments::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXIndependentSegments))
|
(s.parse().map(Tag::ExtXIndependentSegments))
|
||||||
} else if s.starts_with(tags::ExtXStart::PREFIX) {
|
} else if s.starts_with(tags::ExtXStart::PREFIX) {
|
||||||
track!(s.parse().map(Tag::ExtXStart))
|
(s.parse().map(Tag::ExtXStart))
|
||||||
} else {
|
} else {
|
||||||
track!(SingleLineString::new(s)).map(Tag::Unknown)
|
SingleLineString::new(s).map(Tag::Unknown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fmt;
|
||||||
|
use std::iter;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::line::{Line, Lines, Tag};
|
use crate::line::{Line, Lines, Tag};
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
|
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
|
||||||
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag,
|
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag,
|
||||||
};
|
};
|
||||||
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
|
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, Result};
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::fmt;
|
|
||||||
use std::iter;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
/// Master playlist builder.
|
/// Master playlist builder.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -71,18 +72,16 @@ impl MasterPlaylistBuilder {
|
||||||
pub fn finish(self) -> Result<MasterPlaylist> {
|
pub fn finish(self) -> Result<MasterPlaylist> {
|
||||||
let required_version = self.required_version();
|
let required_version = self.required_version();
|
||||||
let specified_version = self.version.unwrap_or(required_version);
|
let specified_version = self.version.unwrap_or(required_version);
|
||||||
track_assert!(
|
|
||||||
required_version <= specified_version,
|
|
||||||
ErrorKind::InvalidInput,
|
|
||||||
"required_version:{}, specified_version:{}",
|
|
||||||
required_version,
|
|
||||||
specified_version,
|
|
||||||
);
|
|
||||||
|
|
||||||
track!(self.validate_stream_inf_tags())?;
|
if required_version <= specified_version {
|
||||||
track!(self.validate_i_frame_stream_inf_tags())?;
|
// "required_version:{}, specified_version:{}"
|
||||||
track!(self.validate_session_data_tags())?;
|
return Err(Error::invalid_input());
|
||||||
track!(self.validate_session_key_tags())?;
|
}
|
||||||
|
|
||||||
|
(self.validate_stream_inf_tags())?;
|
||||||
|
(self.validate_i_frame_stream_inf_tags())?;
|
||||||
|
(self.validate_session_data_tags())?;
|
||||||
|
(self.validate_session_key_tags())?;
|
||||||
|
|
||||||
Ok(MasterPlaylist {
|
Ok(MasterPlaylist {
|
||||||
version_tag: ExtXVersion::new(specified_version),
|
version_tag: ExtXVersion::new(specified_version),
|
||||||
|
@ -121,37 +120,25 @@ impl MasterPlaylistBuilder {
|
||||||
let mut has_none_closed_captions = false;
|
let mut has_none_closed_captions = false;
|
||||||
for t in &self.stream_inf_tags {
|
for t in &self.stream_inf_tags {
|
||||||
if let Some(group_id) = t.audio() {
|
if let Some(group_id) = t.audio() {
|
||||||
track_assert!(
|
if !self.check_media_group(MediaType::Audio, group_id) {
|
||||||
self.check_media_group(MediaType::Audio, group_id),
|
return Err(Error::unmatched_group(group_id));
|
||||||
ErrorKind::InvalidInput,
|
}
|
||||||
"Unmatched audio group: {:?}",
|
|
||||||
group_id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if let Some(group_id) = t.video() {
|
if let Some(group_id) = t.video() {
|
||||||
track_assert!(
|
if !self.check_media_group(MediaType::Video, group_id) {
|
||||||
self.check_media_group(MediaType::Video, group_id),
|
return Err(Error::unmatched_group(group_id));
|
||||||
ErrorKind::InvalidInput,
|
}
|
||||||
"Unmatched video group: {:?}",
|
|
||||||
group_id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if let Some(group_id) = t.subtitles() {
|
if let Some(group_id) = t.subtitles() {
|
||||||
track_assert!(
|
if !self.check_media_group(MediaType::Subtitles, group_id) {
|
||||||
self.check_media_group(MediaType::Subtitles, group_id),
|
return Err(Error::unmatched_group(group_id));
|
||||||
ErrorKind::InvalidInput,
|
}
|
||||||
"Unmatched subtitles group: {:?}",
|
|
||||||
group_id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
match t.closed_captions() {
|
match t.closed_captions() {
|
||||||
Some(&ClosedCaptions::GroupId(ref group_id)) => {
|
Some(&ClosedCaptions::GroupId(ref group_id)) => {
|
||||||
track_assert!(
|
if !self.check_media_group(MediaType::ClosedCaptions, group_id) {
|
||||||
self.check_media_group(MediaType::ClosedCaptions, group_id),
|
return Err(Error::unmatched_group(group_id));
|
||||||
ErrorKind::InvalidInput,
|
}
|
||||||
"Unmatched closed-captions group: {:?}",
|
|
||||||
group_id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Some(&ClosedCaptions::None) => {
|
Some(&ClosedCaptions::None) => {
|
||||||
has_none_closed_captions = true;
|
has_none_closed_captions = true;
|
||||||
|
@ -160,53 +147,49 @@ impl MasterPlaylistBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if has_none_closed_captions {
|
if has_none_closed_captions {
|
||||||
track_assert!(
|
if !self
|
||||||
self.stream_inf_tags
|
.stream_inf_tags
|
||||||
.iter()
|
.iter()
|
||||||
.all(|t| t.closed_captions() == Some(&ClosedCaptions::None)),
|
.all(|t| t.closed_captions() == Some(&ClosedCaptions::None))
|
||||||
ErrorKind::InvalidInput
|
{
|
||||||
);
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_i_frame_stream_inf_tags(&self) -> Result<()> {
|
fn validate_i_frame_stream_inf_tags(&self) -> Result<()> {
|
||||||
for t in &self.i_frame_stream_inf_tags {
|
for t in &self.i_frame_stream_inf_tags {
|
||||||
if let Some(group_id) = t.video() {
|
if let Some(group_id) = t.video() {
|
||||||
track_assert!(
|
if !self.check_media_group(MediaType::Video, group_id) {
|
||||||
self.check_media_group(MediaType::Video, group_id),
|
return Err(Error::unmatched_group(group_id));
|
||||||
ErrorKind::InvalidInput,
|
}
|
||||||
"Unmatched video group: {:?}",
|
|
||||||
group_id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_session_data_tags(&self) -> Result<()> {
|
fn validate_session_data_tags(&self) -> Result<()> {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for t in &self.session_data_tags {
|
for t in &self.session_data_tags {
|
||||||
track_assert!(
|
if !set.insert((t.data_id(), t.language())) {
|
||||||
set.insert((t.data_id(), t.language())),
|
return Err(Error::custom(format!("Conflict: {}", t)));
|
||||||
ErrorKind::InvalidInput,
|
}
|
||||||
"Conflict: {}",
|
|
||||||
t
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_session_key_tags(&self) -> Result<()> {
|
fn validate_session_key_tags(&self) -> Result<()> {
|
||||||
let mut set = HashSet::new();
|
let mut set = HashSet::new();
|
||||||
for t in &self.session_key_tags {
|
for t in &self.session_key_tags {
|
||||||
track_assert!(
|
if !set.insert(t.key()) {
|
||||||
set.insert(t.key()),
|
return Err(Error::custom(format!("Conflict: {}", t)));
|
||||||
ErrorKind::InvalidInput,
|
}
|
||||||
"Conflict: {}",
|
|
||||||
t
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,24 +291,29 @@ impl fmt::Display for MasterPlaylist {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for MasterPlaylist {
|
impl FromStr for MasterPlaylist {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
let mut builder = MasterPlaylistBuilder::new();
|
let mut builder = MasterPlaylistBuilder::new();
|
||||||
for (i, line) in Lines::new(s).enumerate() {
|
for (i, line) in Lines::new(s).enumerate() {
|
||||||
match track!(line)? {
|
match (line)? {
|
||||||
Line::Blank | Line::Comment(_) => {}
|
Line::Blank | Line::Comment(_) => {}
|
||||||
Line::Tag(tag) => {
|
Line::Tag(tag) => {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
track_assert_eq!(tag, Tag::ExtM3u(ExtM3u), ErrorKind::InvalidInput);
|
if tag != Tag::ExtM3u(ExtM3u) {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match tag {
|
match tag {
|
||||||
Tag::ExtM3u(_) => {
|
Tag::ExtM3u(_) => {
|
||||||
track_panic!(ErrorKind::InvalidInput);
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
Tag::ExtXVersion(t) => {
|
Tag::ExtXVersion(t) => {
|
||||||
track_assert_eq!(builder.version, None, ErrorKind::InvalidInput);
|
if builder.version.is_some() {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
builder.version(t.version());
|
builder.version(t.version());
|
||||||
}
|
}
|
||||||
Tag::ExtInf(_)
|
Tag::ExtInf(_)
|
||||||
|
@ -341,7 +329,7 @@ impl FromStr for MasterPlaylist {
|
||||||
| Tag::ExtXEndList(_)
|
| Tag::ExtXEndList(_)
|
||||||
| Tag::ExtXPlaylistType(_)
|
| Tag::ExtXPlaylistType(_)
|
||||||
| Tag::ExtXIFramesOnly(_) => {
|
| Tag::ExtXIFramesOnly(_) => {
|
||||||
track_panic!(ErrorKind::InvalidInput, "{}", tag)
|
return Err(Error::invalid_input()); // TODO: why?
|
||||||
}
|
}
|
||||||
Tag::ExtXMedia(t) => {
|
Tag::ExtXMedia(t) => {
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
|
@ -359,28 +347,29 @@ impl FromStr for MasterPlaylist {
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXIndependentSegments(t) => {
|
Tag::ExtXIndependentSegments(t) => {
|
||||||
track_assert_eq!(
|
if builder.independent_segments_tag.is_some() {
|
||||||
builder.independent_segments_tag,
|
return Err(Error::invalid_input());
|
||||||
None,
|
}
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXStart(t) => {
|
Tag::ExtXStart(t) => {
|
||||||
track_assert_eq!(builder.start_tag, None, ErrorKind::InvalidInput);
|
if builder.start_tag.is_some() {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::Unknown(_) => {
|
Tag::Unknown(_) => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any unrecognized tags.
|
// > ignore any unrecognized tags.
|
||||||
|
// TODO: collect custom tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Line::Uri(uri) => {
|
Line::Uri(uri) => {
|
||||||
track_panic!(ErrorKind::InvalidInput, "Unexpected URI: {:?}", uri);
|
return Err(Error::custom(format!("Unexpected URI: {:?}", uri)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
track!(builder.finish())
|
builder.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::iter;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::line::{Line, Lines, Tag};
|
use crate::line::{Line, Lines, Tag};
|
||||||
use crate::media_segment::{MediaSegment, MediaSegmentBuilder};
|
use crate::media_segment::{MediaSegment, MediaSegmentBuilder};
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
|
@ -6,11 +11,7 @@ use crate::tags::{
|
||||||
MediaPlaylistTag,
|
MediaPlaylistTag,
|
||||||
};
|
};
|
||||||
use crate::types::ProtocolVersion;
|
use crate::types::ProtocolVersion;
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::Error;
|
||||||
use std::fmt;
|
|
||||||
use std::iter;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
/// Media playlist builder.
|
/// Media playlist builder.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -89,20 +90,18 @@ impl MediaPlaylistBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `MediaPlaylist` instance.
|
/// Builds a `MediaPlaylist` instance.
|
||||||
pub fn finish(self) -> Result<MediaPlaylist> {
|
pub fn finish(self) -> crate::Result<MediaPlaylist> {
|
||||||
let required_version = self.required_version();
|
let required_version = self.required_version();
|
||||||
let specified_version = self.version.unwrap_or(required_version);
|
let specified_version = self.version.unwrap_or(required_version);
|
||||||
track_assert!(
|
if !(required_version <= specified_version) {
|
||||||
required_version <= specified_version,
|
return Err(Error::custom(format!(
|
||||||
ErrorKind::InvalidInput,
|
"required_version:{}, specified_version:{}",
|
||||||
"required_version:{}, specified_version:{}",
|
required_version, specified_version
|
||||||
required_version,
|
)));
|
||||||
specified_version,
|
}
|
||||||
);
|
|
||||||
|
|
||||||
let target_duration_tag =
|
let target_duration_tag = self.target_duration_tag.ok_or(Error::invalid_input())?;
|
||||||
track_assert_some!(self.target_duration_tag, ErrorKind::InvalidInput);
|
self.validate_media_segments(target_duration_tag.duration())?;
|
||||||
track!(self.validate_media_segments(target_duration_tag.duration()))?;
|
|
||||||
|
|
||||||
Ok(MediaPlaylist {
|
Ok(MediaPlaylist {
|
||||||
version_tag: ExtXVersion::new(specified_version),
|
version_tag: ExtXVersion::new(specified_version),
|
||||||
|
@ -118,7 +117,7 @@ impl MediaPlaylistBuilder {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_media_segments(&self, target_duration: Duration) -> Result<()> {
|
fn validate_media_segments(&self, target_duration: Duration) -> crate::Result<()> {
|
||||||
let mut last_range_uri = None;
|
let mut last_range_uri = None;
|
||||||
for s in &self.segments {
|
for s in &self.segments {
|
||||||
// CHECK: `#EXT-X-TARGETDURATION`
|
// CHECK: `#EXT-X-TARGETDURATION`
|
||||||
|
@ -129,21 +128,24 @@ impl MediaPlaylistBuilder {
|
||||||
Duration::from_secs(segment_duration.as_secs() + 1)
|
Duration::from_secs(segment_duration.as_secs() + 1)
|
||||||
};
|
};
|
||||||
let max_segment_duration = target_duration + self.options.allowable_excess_duration;
|
let max_segment_duration = target_duration + self.options.allowable_excess_duration;
|
||||||
track_assert!(
|
|
||||||
rounded_segment_duration <= max_segment_duration,
|
if !(rounded_segment_duration <= max_segment_duration) {
|
||||||
ErrorKind::InvalidInput,
|
return Err(Error::custom(format!(
|
||||||
"Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}",
|
"Too large segment duration: actual={:?}, max={:?}, target_duration={:?}, uri={:?}",
|
||||||
segment_duration,
|
segment_duration,
|
||||||
max_segment_duration,
|
max_segment_duration,
|
||||||
target_duration,
|
target_duration,
|
||||||
s.uri()
|
s.uri()
|
||||||
);
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
// 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.to_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 = last_range_uri.ok_or(Error::invalid_input())?;
|
||||||
track_assert_eq!(last_uri, s.uri(), ErrorKind::InvalidInput);
|
if last_uri != s.uri() {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
last_range_uri = Some(s.uri());
|
last_range_uri = Some(s.uri());
|
||||||
}
|
}
|
||||||
|
@ -292,8 +294,9 @@ impl fmt::Display for MediaPlaylist {
|
||||||
|
|
||||||
impl FromStr for MediaPlaylist {
|
impl FromStr for MediaPlaylist {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
track!(MediaPlaylistOptions::new().parse(s))
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
MediaPlaylistOptions::new().parse(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,7 +308,7 @@ pub struct MediaPlaylistOptions {
|
||||||
|
|
||||||
impl MediaPlaylistOptions {
|
impl MediaPlaylistOptions {
|
||||||
/// Makes a new `MediaPlaylistOptions` with the default settings.
|
/// Makes a new `MediaPlaylistOptions` with the default settings.
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
MediaPlaylistOptions {
|
MediaPlaylistOptions {
|
||||||
allowable_excess_duration: Duration::from_secs(0),
|
allowable_excess_duration: Duration::from_secs(0),
|
||||||
}
|
}
|
||||||
|
@ -327,7 +330,7 @@ impl MediaPlaylistOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the given M3U8 text with the specified settings.
|
/// Parses the given M3U8 text with the specified settings.
|
||||||
pub fn parse(&self, m3u8: &str) -> Result<MediaPlaylist> {
|
pub fn parse(&self, m3u8: &str) -> crate::Result<MediaPlaylist> {
|
||||||
let mut builder = MediaPlaylistBuilder::new();
|
let mut builder = MediaPlaylistBuilder::new();
|
||||||
builder.options(self.clone());
|
builder.options(self.clone());
|
||||||
|
|
||||||
|
@ -335,17 +338,21 @@ impl MediaPlaylistOptions {
|
||||||
let mut has_partial_segment = false;
|
let mut has_partial_segment = false;
|
||||||
let mut has_discontinuity_tag = false;
|
let mut has_discontinuity_tag = false;
|
||||||
for (i, line) in Lines::new(m3u8).enumerate() {
|
for (i, line) in Lines::new(m3u8).enumerate() {
|
||||||
match track!(line)? {
|
match (line)? {
|
||||||
Line::Blank | Line::Comment(_) => {}
|
Line::Blank | Line::Comment(_) => {}
|
||||||
Line::Tag(tag) => {
|
Line::Tag(tag) => {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
track_assert_eq!(tag, Tag::ExtM3u(ExtM3u), ErrorKind::InvalidInput);
|
if tag != Tag::ExtM3u(ExtM3u) {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match tag {
|
match tag {
|
||||||
Tag::ExtM3u(_) => track_panic!(ErrorKind::InvalidInput),
|
Tag::ExtM3u(_) => return Err(Error::invalid_input()),
|
||||||
Tag::ExtXVersion(t) => {
|
Tag::ExtXVersion(t) => {
|
||||||
track_assert_eq!(builder.version, None, ErrorKind::InvalidInput);
|
if builder.version.is_some() {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
builder.version(t.version());
|
builder.version(t.version());
|
||||||
}
|
}
|
||||||
Tag::ExtInf(t) => {
|
Tag::ExtInf(t) => {
|
||||||
|
@ -378,45 +385,30 @@ impl MediaPlaylistOptions {
|
||||||
segment.tag(t);
|
segment.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXTargetDuration(t) => {
|
Tag::ExtXTargetDuration(t) => {
|
||||||
track_assert_eq!(
|
|
||||||
builder.target_duration_tag,
|
|
||||||
None,
|
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXMediaSequence(t) => {
|
Tag::ExtXMediaSequence(t) => {
|
||||||
track_assert_eq!(
|
if builder.segments.is_empty() {
|
||||||
builder.media_sequence_tag,
|
return Err(Error::invalid_input());
|
||||||
None,
|
}
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXDiscontinuitySequence(t) => {
|
Tag::ExtXDiscontinuitySequence(t) => {
|
||||||
track_assert!(builder.segments.is_empty(), ErrorKind::InvalidInput);
|
if builder.segments.is_empty() {
|
||||||
track_assert!(!has_discontinuity_tag, ErrorKind::InvalidInput);
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
|
if has_discontinuity_tag {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXEndList(t) => {
|
Tag::ExtXEndList(t) => {
|
||||||
track_assert_eq!(builder.end_list_tag, None, ErrorKind::InvalidInput);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXPlaylistType(t) => {
|
Tag::ExtXPlaylistType(t) => {
|
||||||
track_assert_eq!(
|
|
||||||
builder.playlist_type_tag,
|
|
||||||
None,
|
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXIFramesOnly(t) => {
|
Tag::ExtXIFramesOnly(t) => {
|
||||||
track_assert_eq!(
|
|
||||||
builder.i_frames_only_tag,
|
|
||||||
None,
|
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXMedia(_)
|
Tag::ExtXMedia(_)
|
||||||
|
@ -424,18 +416,12 @@ impl MediaPlaylistOptions {
|
||||||
| Tag::ExtXIFrameStreamInf(_)
|
| Tag::ExtXIFrameStreamInf(_)
|
||||||
| Tag::ExtXSessionData(_)
|
| Tag::ExtXSessionData(_)
|
||||||
| Tag::ExtXSessionKey(_) => {
|
| Tag::ExtXSessionKey(_) => {
|
||||||
track_panic!(ErrorKind::InvalidInput, "{}", tag)
|
return Err(Error::custom(tag));
|
||||||
}
|
}
|
||||||
Tag::ExtXIndependentSegments(t) => {
|
Tag::ExtXIndependentSegments(t) => {
|
||||||
track_assert_eq!(
|
|
||||||
builder.independent_segments_tag,
|
|
||||||
None,
|
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::ExtXStart(t) => {
|
Tag::ExtXStart(t) => {
|
||||||
track_assert_eq!(builder.start_tag, None, ErrorKind::InvalidInput);
|
|
||||||
builder.tag(t);
|
builder.tag(t);
|
||||||
}
|
}
|
||||||
Tag::Unknown(_) => {
|
Tag::Unknown(_) => {
|
||||||
|
@ -446,14 +432,16 @@ impl MediaPlaylistOptions {
|
||||||
}
|
}
|
||||||
Line::Uri(uri) => {
|
Line::Uri(uri) => {
|
||||||
segment.uri(uri);
|
segment.uri(uri);
|
||||||
builder.segment(track!(segment.finish())?);
|
builder.segment((segment.finish())?);
|
||||||
segment = MediaSegmentBuilder::new();
|
segment = MediaSegmentBuilder::new();
|
||||||
has_partial_segment = false;
|
has_partial_segment = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
track_assert!(!has_partial_segment, ErrorKind::InvalidInput);
|
if has_partial_segment {
|
||||||
track!(builder.finish())
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
|
builder.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime,
|
ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime,
|
||||||
MediaSegmentTag,
|
MediaSegmentTag,
|
||||||
};
|
};
|
||||||
use crate::types::{ProtocolVersion, SingleLineString};
|
use crate::types::{ProtocolVersion, SingleLineString};
|
||||||
use crate::{ErrorKind, Result};
|
use crate::Error;
|
||||||
use std::fmt;
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
/// Media segment builder.
|
/// Media segment builder.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -56,9 +57,10 @@ impl MediaSegmentBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `MediaSegment` instance.
|
/// Builds a `MediaSegment` instance.
|
||||||
pub fn finish(self) -> Result<MediaSegment> {
|
pub fn finish(self) -> crate::Result<MediaSegment> {
|
||||||
let uri = track_assert_some!(self.uri, ErrorKind::InvalidInput);
|
let uri = self.uri.ok_or(Error::missing_value("self.uri"))?;
|
||||||
let inf_tag = track_assert_some!(self.inf_tag, ErrorKind::InvalidInput);
|
let inf_tag = self.inf_tag.ok_or(Error::missing_value("self.inf_tag"))?;
|
||||||
|
|
||||||
Ok(MediaSegment {
|
Ok(MediaSegment {
|
||||||
key_tags: self.key_tags,
|
key_tags: self.key_tags,
|
||||||
map_tag: self.map_tag,
|
map_tag: self.map_tag,
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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, tag, unquote};
|
use crate::utils::{quote, tag, unquote};
|
||||||
use crate::{Error, ErrorKind};
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
|
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
|
||||||
///
|
///
|
||||||
|
@ -96,14 +96,14 @@ impl FromStr for ExtXIFrameStreamInf {
|
||||||
|
|
||||||
let attrs = AttributePairs::parse(input);
|
let attrs = AttributePairs::parse(input);
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = attr?;
|
||||||
match key {
|
match key {
|
||||||
"URI" => uri = Some(unquote(value)),
|
"URI" => uri = Some(unquote(value)),
|
||||||
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
"BANDWIDTH" => bandwidth = Some(parse_u64(value)?),
|
||||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(parse_u64(value)?),
|
||||||
"CODECS" => codecs = Some(unquote(value)),
|
"CODECS" => codecs = Some(unquote(value)),
|
||||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
"RESOLUTION" => resolution = Some(value.parse()?),
|
||||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
"HDCP-LEVEL" => hdcp_level = Some(value.parse()?),
|
||||||
"VIDEO" => video = Some(unquote(value)),
|
"VIDEO" => video = Some(unquote(value)),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
|
@ -112,8 +112,9 @@ impl FromStr for ExtXIFrameStreamInf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
|
let uri = uri.ok_or(Error::missing_value("URI"))?;
|
||||||
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
|
let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?;
|
||||||
|
|
||||||
Ok(ExtXIFrameStreamInf {
|
Ok(ExtXIFrameStreamInf {
|
||||||
uri,
|
uri,
|
||||||
bandwidth,
|
bandwidth,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
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, tag, unquote};
|
use crate::utils::{parse_yes_or_no, quote, tag, unquote};
|
||||||
use crate::{Error, ErrorKind};
|
use crate::Error;
|
||||||
use std::fmt;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
/// `ExtXMedia` builder.
|
/// `ExtXMedia` builder.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -115,21 +116,38 @@ impl ExtXMediaBuilder {
|
||||||
|
|
||||||
/// Builds a `ExtXMedia` instance.
|
/// Builds a `ExtXMedia` instance.
|
||||||
pub fn finish(self) -> crate::Result<ExtXMedia> {
|
pub fn finish(self) -> crate::Result<ExtXMedia> {
|
||||||
let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput);
|
let media_type = self
|
||||||
let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput);
|
.media_type
|
||||||
let name = track_assert_some!(self.name, ErrorKind::InvalidInput);
|
.ok_or(Error::missing_value("self.media_type"))?;
|
||||||
|
let group_id = self.group_id.ok_or(Error::missing_value("self.group_id"))?;
|
||||||
|
let name = self.name.ok_or(Error::missing_value("self.name"))?;
|
||||||
|
|
||||||
if MediaType::ClosedCaptions == media_type {
|
if MediaType::ClosedCaptions == media_type {
|
||||||
track_assert_ne!(self.uri, None, ErrorKind::InvalidInput);
|
if let None = self.uri {
|
||||||
track_assert!(self.instream_id.is_some(), ErrorKind::InvalidInput);
|
return Err(Error::missing_value("self.uri"));
|
||||||
|
}
|
||||||
|
self.instream_id
|
||||||
|
.ok_or(Error::missing_value("self.instream_id"))?;
|
||||||
} else {
|
} else {
|
||||||
track_assert!(self.instream_id.is_none(), ErrorKind::InvalidInput);
|
if let Some(_) = &self.instream_id {
|
||||||
|
Err(Error::invalid_input())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.default && self.autoselect.is_some() {
|
if self.default && self.autoselect.is_some() {
|
||||||
track_assert_eq!(self.autoselect, Some(true), ErrorKind::InvalidInput);
|
if let Some(value) = &self.autoselect {
|
||||||
|
if *value {
|
||||||
|
Err(Error::invalid_input())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if MediaType::Subtitles != media_type {
|
if MediaType::Subtitles != media_type {
|
||||||
track_assert_eq!(self.forced, None, ErrorKind::InvalidInput);
|
if self.forced.is_some() {
|
||||||
|
Err(Error::invalid_input())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ExtXMedia {
|
Ok(ExtXMedia {
|
||||||
media_type,
|
media_type,
|
||||||
uri: self.uri,
|
uri: self.uri,
|
||||||
|
@ -315,10 +333,10 @@ impl FromStr for ExtXMedia {
|
||||||
let mut builder = ExtXMediaBuilder::new();
|
let mut builder = ExtXMediaBuilder::new();
|
||||||
let attrs = AttributePairs::parse(input);
|
let attrs = AttributePairs::parse(input);
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = attr?;
|
||||||
match key {
|
match key {
|
||||||
"TYPE" => {
|
"TYPE" => {
|
||||||
builder.media_type(track!(value.parse())?);
|
builder.media_type(value.parse()?);
|
||||||
}
|
}
|
||||||
"URI" => {
|
"URI" => {
|
||||||
builder.uri(unquote(value));
|
builder.uri(unquote(value));
|
||||||
|
@ -336,13 +354,13 @@ impl FromStr for ExtXMedia {
|
||||||
builder.name(unquote(value));
|
builder.name(unquote(value));
|
||||||
}
|
}
|
||||||
"DEFAULT" => {
|
"DEFAULT" => {
|
||||||
builder.default(track!(parse_yes_or_no(value))?);
|
builder.default((parse_yes_or_no(value))?);
|
||||||
}
|
}
|
||||||
"AUTOSELECT" => {
|
"AUTOSELECT" => {
|
||||||
builder.autoselect(track!(parse_yes_or_no(value))?);
|
builder.autoselect((parse_yes_or_no(value))?);
|
||||||
}
|
}
|
||||||
"FORCED" => {
|
"FORCED" => {
|
||||||
builder.forced(track!(parse_yes_or_no(value))?);
|
builder.forced((parse_yes_or_no(value))?);
|
||||||
}
|
}
|
||||||
"INSTREAM-ID" => {
|
"INSTREAM-ID" => {
|
||||||
builder.instream_id(unquote(value).parse()?);
|
builder.instream_id(unquote(value).parse()?);
|
||||||
|
@ -359,7 +377,7 @@ impl FromStr for ExtXMedia {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
track!(builder.finish())
|
(builder.finish())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use getset::{Getters, MutGetters, Setters};
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{ProtocolVersion, SessionData};
|
use crate::types::{ProtocolVersion, SessionData};
|
||||||
use crate::utils::{quote, tag, unquote};
|
use crate::utils::{quote, tag, unquote};
|
||||||
use crate::{Error, ErrorKind};
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.4.4. EXT-X-SESSION-DATA]
|
/// [4.3.4.4. EXT-X-SESSION-DATA]
|
||||||
///
|
///
|
||||||
|
@ -79,7 +79,7 @@ impl FromStr for ExtXSessionData {
|
||||||
|
|
||||||
let attrs = AttributePairs::parse(input);
|
let attrs = AttributePairs::parse(input);
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = attr?;
|
||||||
match key {
|
match key {
|
||||||
"DATA-ID" => data_id = Some(unquote(value)),
|
"DATA-ID" => data_id = Some(unquote(value)),
|
||||||
"VALUE" => session_value = Some(unquote(value)),
|
"VALUE" => session_value = Some(unquote(value)),
|
||||||
|
@ -92,15 +92,21 @@ impl FromStr for ExtXSessionData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_id = track_assert_some!(data_id, ErrorKind::InvalidInput);
|
let data_id = data_id.ok_or(Error::missing_value("DATA-ID"))?;
|
||||||
let data = if let Some(value) = session_value {
|
let data = {
|
||||||
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
|
if let Some(value) = session_value {
|
||||||
SessionData::Value(value)
|
if uri.is_some() {
|
||||||
} else if let Some(uri) = uri {
|
return Err(Error::invalid_input());
|
||||||
SessionData::Uri(uri)
|
} else {
|
||||||
} else {
|
SessionData::Value(value)
|
||||||
track_panic!(ErrorKind::InvalidInput);
|
}
|
||||||
|
} else if let Some(uri) = uri {
|
||||||
|
SessionData::Uri(uri)
|
||||||
|
} else {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ExtXSessionData {
|
Ok(ExtXSessionData {
|
||||||
data_id,
|
data_id,
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::types::{
|
||||||
SingleLineString,
|
SingleLineString,
|
||||||
};
|
};
|
||||||
use crate::utils::{parse_u64, quote, tag, unquote};
|
use crate::utils::{parse_u64, quote, tag, unquote};
|
||||||
use crate::{Error, ErrorKind};
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.4.2. EXT-X-STREAM-INF]
|
/// [4.3.4.2. EXT-X-STREAM-INF]
|
||||||
///
|
///
|
||||||
|
@ -149,12 +149,12 @@ impl FromStr for ExtXStreamInf {
|
||||||
|
|
||||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let mut lines = input.lines();
|
let mut lines = input.lines();
|
||||||
let first_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO!
|
let first_line = lines.next().ok_or(Error::invalid_input())?; // TODO!
|
||||||
let second_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO!
|
let second_line = lines.next().ok_or(Error::invalid_input())?; // TODO!
|
||||||
|
|
||||||
tag(first_line, Self::PREFIX)?;
|
tag(first_line, Self::PREFIX)?;
|
||||||
|
|
||||||
let uri = track!(SingleLineString::new(second_line))?;
|
let uri = SingleLineString::new(second_line)?;
|
||||||
|
|
||||||
let mut bandwidth = None;
|
let mut bandwidth = None;
|
||||||
let mut average_bandwidth = None;
|
let mut average_bandwidth = None;
|
||||||
|
@ -166,20 +166,21 @@ impl FromStr for ExtXStreamInf {
|
||||||
let mut video = None;
|
let mut video = None;
|
||||||
let mut subtitles = None;
|
let mut subtitles = None;
|
||||||
let mut closed_captions = None;
|
let mut closed_captions = None;
|
||||||
|
|
||||||
let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1);
|
let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1);
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = (attr)?;
|
||||||
match key {
|
match key {
|
||||||
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
"BANDWIDTH" => bandwidth = Some((parse_u64(value))?),
|
||||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
"AVERAGE-BANDWIDTH" => average_bandwidth = Some((parse_u64(value))?),
|
||||||
"CODECS" => codecs = Some(unquote(value)),
|
"CODECS" => codecs = Some(unquote(value)),
|
||||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
"RESOLUTION" => resolution = Some((value.parse())?),
|
||||||
"FRAME-RATE" => frame_rate = Some(track!(value.parse())?),
|
"FRAME-RATE" => frame_rate = Some((value.parse())?),
|
||||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
"HDCP-LEVEL" => hdcp_level = Some((value.parse())?),
|
||||||
"AUDIO" => audio = Some(unquote(value)),
|
"AUDIO" => audio = Some(unquote(value)),
|
||||||
"VIDEO" => video = Some(unquote(value)),
|
"VIDEO" => video = Some(unquote(value)),
|
||||||
"SUBTITLES" => subtitles = Some(unquote(value)),
|
"SUBTITLES" => subtitles = Some(unquote(value)),
|
||||||
"CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?),
|
"CLOSED-CAPTIONS" => closed_captions = Some((value.parse())?),
|
||||||
_ => {
|
_ => {
|
||||||
// [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.
|
||||||
|
@ -187,7 +188,8 @@ impl FromStr for ExtXStreamInf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
|
let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?;
|
||||||
|
|
||||||
Ok(ExtXStreamInf {
|
Ok(ExtXStreamInf {
|
||||||
uri,
|
uri,
|
||||||
bandwidth,
|
bandwidth,
|
||||||
|
|
|
@ -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.3.4. EXT-X-ENDLIST]
|
/// [4.3.3.4. EXT-X-ENDLIST]
|
||||||
///
|
///
|
||||||
/// [4.3.3.4. EXT-X-ENDLIST]: https://tools.ietf.org/html/rfc8216#section-4.3.3.4
|
/// [4.3.3.4. EXT-X-ENDLIST]: https://tools.ietf.org/html/rfc8216#section-4.3.3.4
|
||||||
|
@ -26,8 +28,8 @@ impl fmt::Display for ExtXEndList {
|
||||||
impl FromStr for ExtXEndList {
|
impl FromStr for ExtXEndList {
|
||||||
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(ExtXEndList)
|
Ok(ExtXEndList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.3.6. EXT-X-I-FRAMES-ONLY]
|
/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]
|
||||||
///
|
///
|
||||||
/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]: https://tools.ietf.org/html/rfc8216#section-4.3.3.6
|
/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]: https://tools.ietf.org/html/rfc8216#section-4.3.3.6
|
||||||
|
@ -27,8 +29,8 @@ impl fmt::Display for ExtXIFramesOnly {
|
||||||
impl FromStr for ExtXIFramesOnly {
|
impl FromStr for ExtXIFramesOnly {
|
||||||
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(ExtXIFramesOnly)
|
Ok(ExtXIFramesOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
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;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE]
|
/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE]
|
||||||
///
|
///
|
||||||
|
@ -40,10 +41,10 @@ impl fmt::Display for ExtXMediaSequence {
|
||||||
impl FromStr for ExtXMediaSequence {
|
impl FromStr for ExtXMediaSequence {
|
||||||
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 seq_num = tag(input, Self::PREFIX)?.parse()?;
|
||||||
let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
|
|
||||||
Ok(ExtXMediaSequence { seq_num })
|
Ok(ExtXMediaSequence::new(seq_num))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::types::{PlaylistType, 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::{PlaylistType, ProtocolVersion};
|
||||||
|
use crate::utils::tag;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]
|
/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]
|
||||||
///
|
///
|
||||||
|
@ -40,10 +41,10 @@ impl fmt::Display for ExtXPlaylistType {
|
||||||
impl FromStr for ExtXPlaylistType {
|
impl FromStr for ExtXPlaylistType {
|
||||||
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)?.parse()?;
|
||||||
let playlist_type = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
|
|
||||||
Ok(ExtXPlaylistType { playlist_type })
|
Ok(ExtXPlaylistType::new(input))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +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 std::time::Duration;
|
use std::time::Duration;
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
use crate::types::ProtocolVersion;
|
||||||
|
use crate::utils::tag;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.3.1. EXT-X-TARGETDURATION]
|
/// [4.3.3.1. EXT-X-TARGETDURATION]
|
||||||
///
|
///
|
||||||
|
@ -43,11 +44,11 @@ impl fmt::Display for ExtXTargetDuration {
|
||||||
|
|
||||||
impl FromStr for ExtXTargetDuration {
|
impl FromStr for ExtXTargetDuration {
|
||||||
type Err = Error;
|
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 duration = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
|
let input = tag(input, Self::PREFIX)?.parse()?;
|
||||||
Ok(ExtXTargetDuration {
|
Ok(ExtXTargetDuration {
|
||||||
duration: Duration::from_secs(duration),
|
duration: Duration::from_secs(input),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,9 @@ use std::fmt;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
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::utils::tag;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.2.2. EXT-X-BYTERANGE]
|
/// [4.3.2.2. EXT-X-BYTERANGE]
|
||||||
///
|
///
|
||||||
|
@ -73,26 +72,20 @@ impl fmt::Display for ExtXByteRange {
|
||||||
impl FromStr for ExtXByteRange {
|
impl FromStr for ExtXByteRange {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
// check if the string starts with the PREFIX
|
let input = tag(input, Self::PREFIX)?;
|
||||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
|
||||||
let byte_range = s.split_at(Self::PREFIX.len()).1;
|
let tokens = input.splitn(2, '@').collect::<Vec<_>>();
|
||||||
let tokens = byte_range.splitn(2, '@').collect::<Vec<_>>();
|
|
||||||
if tokens.is_empty() {
|
if tokens.is_empty() {
|
||||||
Err(ErrorKind::InvalidInput)?;
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = tokens[0]
|
let length = tokens[0].parse()?;
|
||||||
.parse()
|
|
||||||
.map_err(|e| ErrorKind::InvalidInput.cause(e))?;
|
|
||||||
let start = {
|
let start = {
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
if tokens.len() == 2 {
|
if tokens.len() == 2 {
|
||||||
result = Some(
|
result = Some(tokens[1].parse()?);
|
||||||
tokens[1]
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| ErrorKind::InvalidInput.cause(e))?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::attribute::AttributePairs;
|
|
||||||
use crate::types::{DecimalFloatingPoint, ProtocolVersion};
|
|
||||||
use crate::utils::{quote, unquote};
|
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::attribute::AttributePairs;
|
||||||
|
use crate::types::{DecimalFloatingPoint, ProtocolVersion};
|
||||||
|
use crate::utils::{quote, tag, unquote};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.2.7. EXT-X-DATERANGE]
|
/// [4.3.2.7. EXT-X-DATERANGE]
|
||||||
///
|
///
|
||||||
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
|
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
|
||||||
|
@ -79,8 +80,9 @@ impl fmt::Display for ExtXDateRange {
|
||||||
|
|
||||||
impl FromStr for ExtXDateRange {
|
impl FromStr for ExtXDateRange {
|
||||||
type Err = Error;
|
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 id = None;
|
let mut id = None;
|
||||||
let mut class = None;
|
let mut class = None;
|
||||||
|
@ -92,28 +94,32 @@ impl FromStr for ExtXDateRange {
|
||||||
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 = false;
|
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(input);
|
||||||
|
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = attr?;
|
||||||
match key {
|
match key {
|
||||||
"ID" => id = Some(unquote(value)),
|
"ID" => id = Some(unquote(value)),
|
||||||
"CLASS" => class = Some(unquote(value)),
|
"CLASS" => class = Some(unquote(value)),
|
||||||
"START-DATE" => start_date = Some(unquote(value)),
|
"START-DATE" => start_date = Some(unquote(value)),
|
||||||
"END-DATE" => end_date = Some(unquote(value)),
|
"END-DATE" => end_date = Some(unquote(value)),
|
||||||
"DURATION" => {
|
"DURATION" => {
|
||||||
let seconds: DecimalFloatingPoint = track!(value.parse())?;
|
let seconds: DecimalFloatingPoint = (value.parse())?;
|
||||||
duration = Some(seconds.to_duration());
|
duration = Some(seconds.to_duration());
|
||||||
}
|
}
|
||||||
"PLANNED-DURATION" => {
|
"PLANNED-DURATION" => {
|
||||||
let seconds: DecimalFloatingPoint = track!(value.parse())?;
|
let seconds: DecimalFloatingPoint = (value.parse())?;
|
||||||
planned_duration = Some(seconds.to_duration());
|
planned_duration = Some(seconds.to_duration());
|
||||||
}
|
}
|
||||||
"SCTE35-CMD" => scte35_cmd = Some(unquote(value)),
|
"SCTE35-CMD" => scte35_cmd = Some(unquote(value)),
|
||||||
"SCTE35-OUT" => scte35_out = Some(unquote(value)),
|
"SCTE35-OUT" => scte35_out = Some(unquote(value)),
|
||||||
"SCTE35-IN" => scte35_in = Some(unquote(value)),
|
"SCTE35-IN" => scte35_in = Some(unquote(value)),
|
||||||
"END-ON-NEXT" => {
|
"END-ON-NEXT" => {
|
||||||
track_assert_eq!(value, "YES", ErrorKind::InvalidInput);
|
if value != "YES" {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
end_on_next = true;
|
end_on_next = true;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -127,10 +133,12 @@ impl FromStr for ExtXDateRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = track_assert_some!(id, ErrorKind::InvalidInput);
|
let id = id.ok_or(Error::missing_value("EXT-X-ID"))?;
|
||||||
let start_date = track_assert_some!(start_date, ErrorKind::InvalidInput);
|
let start_date = start_date.ok_or(Error::missing_value("EXT-X-START-DATE"))?;
|
||||||
if end_on_next {
|
if end_on_next {
|
||||||
track_assert!(class.is_some(), ErrorKind::InvalidInput);
|
if class.is_none() {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(ExtXDateRange {
|
Ok(ExtXDateRange {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::types::{DecimalFloatingPoint, ProtocolVersion, SingleLineString};
|
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
use crate::types::{DecimalFloatingPoint, ProtocolVersion, SingleLineString};
|
||||||
|
use crate::utils::tag;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.2.1. EXTINF]
|
/// [4.3.2.1. EXTINF]
|
||||||
///
|
///
|
||||||
|
@ -70,18 +71,20 @@ impl fmt::Display for ExtInf {
|
||||||
|
|
||||||
impl FromStr for ExtInf {
|
impl FromStr for ExtInf {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
|
||||||
let mut tokens = s.split_at(Self::PREFIX.len()).1.splitn(2, ',');
|
|
||||||
|
|
||||||
let seconds: DecimalFloatingPoint =
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
may_invalid!(tokens.next().expect("Never fails").parse())?;
|
let input = tag(input, Self::PREFIX)?;
|
||||||
|
let mut tokens = input.splitn(2, ',');
|
||||||
|
|
||||||
|
let seconds: DecimalFloatingPoint = tokens.next().expect("Never fails").parse()?;
|
||||||
let duration = seconds.to_duration();
|
let duration = seconds.to_duration();
|
||||||
|
|
||||||
let title = if let Some(title) = tokens.next() {
|
let title = {
|
||||||
Some(track!(SingleLineString::new(title))?)
|
if let Some(title) = tokens.next() {
|
||||||
} else {
|
Some((SingleLineString::new(title))?)
|
||||||
None
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(ExtInf { duration, title })
|
Ok(ExtInf { duration, title })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::attribute::AttributePairs;
|
|
||||||
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::attribute::AttributePairs;
|
||||||
|
use crate::types::{DecryptionKey, ProtocolVersion};
|
||||||
|
use crate::utils::tag;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.2.4. EXT-X-KEY]
|
/// [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
|
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
||||||
|
@ -55,21 +57,19 @@ impl fmt::Display for ExtXKey {
|
||||||
impl FromStr for ExtXKey {
|
impl FromStr for ExtXKey {
|
||||||
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 suffix = s.split_at(Self::PREFIX.len()).1;
|
|
||||||
|
|
||||||
if AttributePairs::parse(suffix).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) {
|
if AttributePairs::parse(input).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) {
|
||||||
for attr in AttributePairs::parse(suffix) {
|
for attr in AttributePairs::parse(input) {
|
||||||
let (key, _) = track!(attr)?;
|
let (key, _) = attr?;
|
||||||
track_assert_ne!(key, "URI", ErrorKind::InvalidInput);
|
if key == "URI" || key == "IV" || key == "KEYFORMAT" || key == "KEYFORMATVERSIONS" {
|
||||||
track_assert_ne!(key, "IV", ErrorKind::InvalidInput);
|
return Err(Error::invalid_input());
|
||||||
track_assert_ne!(key, "KEYFORMAT", ErrorKind::InvalidInput);
|
}
|
||||||
track_assert_ne!(key, "KEYFORMATVERSIONS", ErrorKind::InvalidInput);
|
|
||||||
}
|
}
|
||||||
Ok(ExtXKey { key: None })
|
Ok(ExtXKey { key: None })
|
||||||
} else {
|
} else {
|
||||||
let key = track!(suffix.parse())?;
|
let key = input.parse()?;
|
||||||
Ok(ExtXKey { key: Some(key) })
|
Ok(ExtXKey { key: Some(key) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::attribute::AttributePairs;
|
|
||||||
use crate::types::{ByteRange, ProtocolVersion};
|
|
||||||
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 crate::attribute::AttributePairs;
|
||||||
|
use crate::types::{ByteRange, ProtocolVersion};
|
||||||
|
use crate::utils::{quote, tag, unquote};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.2.5. EXT-X-MAP]
|
/// [4.3.2.5. EXT-X-MAP]
|
||||||
///
|
///
|
||||||
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
|
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
|
||||||
|
@ -63,18 +64,18 @@ impl fmt::Display for ExtXMap {
|
||||||
impl FromStr for ExtXMap {
|
impl FromStr for ExtXMap {
|
||||||
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 range = None;
|
let mut range = 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) = (attr)?;
|
||||||
match key {
|
match key {
|
||||||
"URI" => uri = Some(unquote(value)),
|
"URI" => uri = Some(unquote(value)),
|
||||||
"BYTERANGE" => {
|
"BYTERANGE" => {
|
||||||
range = Some(track!(unquote(value).parse())?);
|
range = Some((unquote(value).parse())?);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
|
@ -83,7 +84,7 @@ impl FromStr for ExtXMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
|
let uri = uri.ok_or(Error::missing_value("EXT-X-URI"))?;
|
||||||
Ok(ExtXMap { uri, range })
|
Ok(ExtXMap { uri, range })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +103,8 @@ mod test {
|
||||||
|
|
||||||
let tag = ExtXMap::with_range("foo", ByteRange::new(9, Some(2)));
|
let tag = ExtXMap::with_range("foo", ByteRange::new(9, 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));
|
ExtXMap::from_str(text).unwrap();
|
||||||
|
|
||||||
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);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::types::{ProtocolVersion, SingleLineString};
|
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::types::{ProtocolVersion, SingleLineString};
|
||||||
|
use crate::utils::tag;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
|
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
|
||||||
///
|
///
|
||||||
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6
|
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6
|
||||||
|
@ -39,11 +41,13 @@ impl fmt::Display for ExtXProgramDateTime {
|
||||||
impl FromStr for ExtXProgramDateTime {
|
impl FromStr for ExtXProgramDateTime {
|
||||||
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 suffix = s.split_at(Self::PREFIX.len()).1;
|
|
||||||
|
// TODO: parse with chrono
|
||||||
|
|
||||||
Ok(ExtXProgramDateTime {
|
Ok(ExtXProgramDateTime {
|
||||||
date_time: track!(SingleLineString::new(suffix))?,
|
date_time: (SingleLineString::new(input))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,6 @@
|
||||||
//!
|
//!
|
||||||
//! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3
|
//! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3
|
||||||
|
|
||||||
macro_rules! may_invalid {
|
|
||||||
($expr:expr) => {
|
|
||||||
$expr.map_err(|e| track!(Error::from(ErrorKind::InvalidInput.cause(e))))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_from {
|
macro_rules! impl_from {
|
||||||
($to:ident, $from:ident) => {
|
($to:ident, $from:ident) => {
|
||||||
impl From<$from> for $to {
|
impl From<$from> for $to {
|
||||||
|
|
|
@ -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.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
||||||
///
|
///
|
||||||
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1
|
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1
|
||||||
|
@ -26,8 +28,8 @@ impl fmt::Display for ExtXIndependentSegments {
|
||||||
impl FromStr for ExtXIndependentSegments {
|
impl FromStr for ExtXIndependentSegments {
|
||||||
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(ExtXIndependentSegments)
|
Ok(ExtXIndependentSegments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::attribute::AttributePairs;
|
|
||||||
use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint};
|
|
||||||
use crate::utils::parse_yes_or_no;
|
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::attribute::AttributePairs;
|
||||||
|
use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint};
|
||||||
|
use crate::utils::{parse_yes_or_no, tag};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [4.3.5.2. EXT-X-START]
|
/// [4.3.5.2. EXT-X-START]
|
||||||
///
|
///
|
||||||
/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
|
/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
|
||||||
|
@ -64,17 +65,19 @@ impl fmt::Display for ExtXStart {
|
||||||
impl FromStr for ExtXStart {
|
impl FromStr for ExtXStart {
|
||||||
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 time_offset = None;
|
let mut time_offset = None;
|
||||||
let mut precise = false;
|
let mut precise = false;
|
||||||
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) = (attr)?;
|
||||||
match key {
|
match key {
|
||||||
"TIME-OFFSET" => time_offset = Some(track!(value.parse())?),
|
"TIME-OFFSET" => time_offset = Some((value.parse())?),
|
||||||
"PRECISE" => precise = track!(parse_yes_or_no(value))?,
|
"PRECISE" => precise = (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.
|
||||||
|
@ -82,7 +85,8 @@ impl FromStr for ExtXStart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let time_offset = track_assert_some!(time_offset, ErrorKind::InvalidInput);
|
let time_offset = time_offset.ok_or(Error::missing_value("EXT-X-TIME-OFFSET"))?;
|
||||||
|
|
||||||
Ok(ExtXStart {
|
Ok(ExtXStart {
|
||||||
time_offset,
|
time_offset,
|
||||||
precise,
|
precise,
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use getset::{Getters, MutGetters, Setters};
|
use getset::{Getters, MutGetters, Setters};
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::Error;
|
||||||
|
|
||||||
/// Byte range.
|
/// Byte range.
|
||||||
///
|
///
|
||||||
|
@ -42,23 +41,18 @@ 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, Self::Err> {
|
||||||
let tokens = s.splitn(2, '@').collect::<Vec<_>>();
|
let tokens = s.splitn(2, '@').collect::<Vec<_>>();
|
||||||
if tokens.is_empty() {
|
if tokens.is_empty() {
|
||||||
Err(ErrorKind::InvalidInput)?;
|
return Err(Error::invalid_input());
|
||||||
}
|
}
|
||||||
|
|
||||||
let length = tokens[0]
|
let length = tokens[0].parse()?;
|
||||||
.parse()
|
|
||||||
.map_err(|e| ErrorKind::InvalidInput.cause(e))?;
|
|
||||||
let start = {
|
let start = {
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
if tokens.len() == 2 {
|
if tokens.len() == 2 {
|
||||||
result = Some(
|
result = Some(tokens[1].parse()?);
|
||||||
tokens[1]
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| ErrorKind::InvalidInput.cause(e))?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Non-negative decimal floating-point number.
|
/// Non-negative decimal floating-point number.
|
||||||
///
|
///
|
||||||
|
@ -19,9 +19,10 @@ impl DecimalFloatingPoint {
|
||||||
///
|
///
|
||||||
/// The given value must have a positive sign and be finite,
|
/// The given value must have a positive sign and be finite,
|
||||||
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
||||||
pub fn new(n: f64) -> Result<Self> {
|
pub fn new(n: f64) -> crate::Result<Self> {
|
||||||
track_assert!(n.is_sign_positive(), ErrorKind::InvalidInput);
|
if !n.is_sign_positive() || !n.is_finite() {
|
||||||
track_assert!(n.is_finite(), ErrorKind::InvalidInput);
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
Ok(DecimalFloatingPoint(n))
|
Ok(DecimalFloatingPoint(n))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,12 +60,12 @@ impl fmt::Display for DecimalFloatingPoint {
|
||||||
|
|
||||||
impl FromStr for DecimalFloatingPoint {
|
impl FromStr for DecimalFloatingPoint {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
track_assert!(
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
s.chars().all(|c| c.is_digit(10) || c == '.'),
|
if !input.chars().all(|c| c.is_digit(10) || c == '.') {
|
||||||
ErrorKind::InvalidInput
|
return Err(Error::invalid_input());
|
||||||
);
|
}
|
||||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
let n = input.parse()?;
|
||||||
Ok(DecimalFloatingPoint(n))
|
Ok(DecimalFloatingPoint(n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Decimal resolution.
|
/// Decimal resolution.
|
||||||
///
|
///
|
||||||
|
@ -25,13 +25,15 @@ impl fmt::Display for DecimalResolution {
|
||||||
|
|
||||||
impl FromStr for DecimalResolution {
|
impl FromStr for DecimalResolution {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
let mut tokens = s.splitn(2, 'x');
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let width = tokens.next().expect("Never fails");
|
let mut tokens = input.splitn(2, 'x');
|
||||||
let height = track_assert_some!(tokens.next(), ErrorKind::InvalidInput);
|
let width = tokens.next().ok_or(Error::missing_value("width"))?;
|
||||||
|
let height = tokens.next().ok_or(Error::missing_value("height"))?;
|
||||||
|
|
||||||
Ok(DecimalResolution {
|
Ok(DecimalResolution {
|
||||||
width: track!(width.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
|
width: width.parse().map_err(|e| Error::custom(e))?,
|
||||||
height: track!(height.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
|
height: height.parse().map_err(|e| Error::custom(e))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::{self, FromStr};
|
||||||
|
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion};
|
use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion};
|
||||||
use crate::utils::{quote, unquote};
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::Error;
|
||||||
use std::fmt;
|
|
||||||
use std::str::{self, FromStr};
|
|
||||||
|
|
||||||
/// Decryption key.
|
/// Decryption key.
|
||||||
///
|
///
|
||||||
|
@ -51,19 +52,21 @@ impl fmt::Display for DecryptionKey {
|
||||||
|
|
||||||
impl FromStr for DecryptionKey {
|
impl FromStr for DecryptionKey {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
let mut method = None;
|
let mut method = None;
|
||||||
let mut uri = None;
|
let mut uri = None;
|
||||||
let mut iv = None;
|
let mut iv = None;
|
||||||
let mut key_format = None;
|
let mut key_format = None;
|
||||||
let mut key_format_versions = None;
|
let mut key_format_versions = None;
|
||||||
let attrs = AttributePairs::parse(s);
|
|
||||||
|
let attrs = AttributePairs::parse(input);
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = (attr)?;
|
||||||
match key {
|
match key {
|
||||||
"METHOD" => method = Some(track!(value.parse())?),
|
"METHOD" => method = Some((value.parse())?),
|
||||||
"URI" => uri = Some(unquote(value)),
|
"URI" => uri = Some(unquote(value)),
|
||||||
"IV" => iv = Some(track!(value.parse())?),
|
"IV" => iv = Some((value.parse())?),
|
||||||
"KEYFORMAT" => key_format = Some(unquote(value)),
|
"KEYFORMAT" => key_format = Some(unquote(value)),
|
||||||
"KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)),
|
"KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)),
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -72,8 +75,10 @@ impl FromStr for DecryptionKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let method = track_assert_some!(method, ErrorKind::InvalidInput);
|
|
||||||
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
|
let method = method.ok_or(Error::missing_value("EXT-X-METHOD"))?;
|
||||||
|
let uri = uri.ok_or(Error::missing_value("EXT-X-URI"))?;
|
||||||
|
|
||||||
Ok(DecryptionKey {
|
Ok(DecryptionKey {
|
||||||
method,
|
method,
|
||||||
uri,
|
uri,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Encryption method.
|
/// Encryption method.
|
||||||
///
|
///
|
||||||
|
@ -25,15 +26,15 @@ impl fmt::Display for EncryptionMethod {
|
||||||
|
|
||||||
impl FromStr for EncryptionMethod {
|
impl FromStr for EncryptionMethod {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
match s {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
match input {
|
||||||
"AES-128" => Ok(EncryptionMethod::Aes128),
|
"AES-128" => Ok(EncryptionMethod::Aes128),
|
||||||
"SAMPLE-AES" => Ok(EncryptionMethod::SampleAes),
|
"SAMPLE-AES" => Ok(EncryptionMethod::SampleAes),
|
||||||
_ => track_panic!(
|
_ => Err(Error::custom(format!(
|
||||||
ErrorKind::InvalidInput,
|
|
||||||
"Unknown encryption method: {:?}",
|
"Unknown encryption method: {:?}",
|
||||||
s
|
input
|
||||||
),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// HDCP level.
|
/// HDCP level.
|
||||||
///
|
///
|
||||||
|
@ -25,11 +26,12 @@ impl fmt::Display for HdcpLevel {
|
||||||
|
|
||||||
impl FromStr for HdcpLevel {
|
impl FromStr for HdcpLevel {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
match s {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
match input {
|
||||||
"TYPE-0" => Ok(HdcpLevel::Type0),
|
"TYPE-0" => Ok(HdcpLevel::Type0),
|
||||||
"NONE" => Ok(HdcpLevel::None),
|
"NONE" => Ok(HdcpLevel::None),
|
||||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown HDCP level: {:?}", s),
|
_ => Err(Error::custom(format!("Unknown HDCP level: {:?}", input))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::{self, FromStr};
|
use std::str::FromStr;
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Hexadecimal sequence.
|
/// Hexadecimal sequence.
|
||||||
///
|
///
|
||||||
|
@ -49,20 +49,24 @@ impl fmt::Display for HexadecimalSequence {
|
||||||
|
|
||||||
impl FromStr for HexadecimalSequence {
|
impl FromStr for HexadecimalSequence {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
track_assert!(
|
|
||||||
s.starts_with("0x") || s.starts_with("0X"),
|
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
track_assert!(s.len() % 2 == 0, ErrorKind::InvalidInput);
|
|
||||||
|
|
||||||
let mut v = Vec::with_capacity(s.len() / 2 - 1);
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
for c in s.as_bytes().chunks(2).skip(1) {
|
if !(input.starts_with("0x") || input.starts_with("0X")) {
|
||||||
let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
return Err(Error::invalid_input());
|
||||||
let b =
|
|
||||||
track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
|
||||||
v.push(b);
|
|
||||||
}
|
}
|
||||||
Ok(HexadecimalSequence(v))
|
|
||||||
|
if input.len() % 2 != 0 {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = Vec::with_capacity(input.len() / 2 - 1);
|
||||||
|
|
||||||
|
for c in input.as_bytes().chunks(2).skip(1) {
|
||||||
|
let d = String::from_utf8(c.to_vec()).map_err(|e| Error::custom(e))?;
|
||||||
|
let b = u8::from_str_radix(d.as_str(), 16)?;
|
||||||
|
result.push(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HexadecimalSequence(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Identifier of a rendition within the segments in a media playlist.
|
/// Identifier of a rendition within the segments in a media playlist.
|
||||||
///
|
///
|
||||||
|
@ -87,8 +88,9 @@ impl fmt::Display for InStreamId {
|
||||||
|
|
||||||
impl FromStr for InStreamId {
|
impl FromStr for InStreamId {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
Ok(match s {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match input {
|
||||||
"CC1" => InStreamId::Cc1,
|
"CC1" => InStreamId::Cc1,
|
||||||
"CC2" => InStreamId::Cc2,
|
"CC2" => InStreamId::Cc2,
|
||||||
"CC3" => InStreamId::Cc3,
|
"CC3" => InStreamId::Cc3,
|
||||||
|
@ -156,7 +158,7 @@ impl FromStr for InStreamId {
|
||||||
"SERVICE61" => InStreamId::Service61,
|
"SERVICE61" => InStreamId::Service61,
|
||||||
"SERVICE62" => InStreamId::Service62,
|
"SERVICE62" => InStreamId::Service62,
|
||||||
"SERVICE63" => InStreamId::Service63,
|
"SERVICE63" => InStreamId::Service63,
|
||||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown instream id: {:?}", s),
|
_ => return Err(Error::custom(format!("Unknown instream id: {:?}", input))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Initialization vector.
|
/// Initialization vector.
|
||||||
///
|
///
|
||||||
|
@ -37,20 +37,22 @@ impl fmt::Display for InitializationVector {
|
||||||
|
|
||||||
impl FromStr for InitializationVector {
|
impl FromStr for InitializationVector {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
track_assert!(
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
s.starts_with("0x") || s.starts_with("0X"),
|
if !(s.starts_with("0x") || s.starts_with("0X")) {
|
||||||
ErrorKind::InvalidInput
|
return Err(Error::invalid_input());
|
||||||
);
|
}
|
||||||
track_assert_eq!(s.len() - 2, 32, ErrorKind::InvalidInput);
|
if s.len() - 2 != 32 {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
|
|
||||||
let mut v = [0; 16];
|
let mut v = [0; 16];
|
||||||
for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() {
|
for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() {
|
||||||
let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
let d = str::from_utf8(c).map_err(|e| Error::custom(e))?;
|
||||||
let b =
|
let b = u8::from_str_radix(d, 16).map_err(|e| Error::custom(e))?;
|
||||||
track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
|
||||||
v[i] = b;
|
v[i] = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(InitializationVector(v))
|
Ok(InitializationVector(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Media type.
|
/// Media type.
|
||||||
///
|
///
|
||||||
/// See: [4.3.4.1. EXT-X-MEDIA]
|
/// See: [4.3.4.1. EXT-X-MEDIA]
|
||||||
|
@ -29,13 +30,16 @@ impl fmt::Display for MediaType {
|
||||||
|
|
||||||
impl FromStr for MediaType {
|
impl FromStr for MediaType {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
Ok(match s {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match input {
|
||||||
"AUDIO" => MediaType::Audio,
|
"AUDIO" => MediaType::Audio,
|
||||||
"VIDEO" => MediaType::Video,
|
"VIDEO" => MediaType::Video,
|
||||||
"SUBTITLES" => MediaType::Subtitles,
|
"SUBTITLES" => MediaType::Subtitles,
|
||||||
"CLOSED-CAPTIONS" => MediaType::ClosedCaptions,
|
"CLOSED-CAPTIONS" => MediaType::ClosedCaptions,
|
||||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown media type: {:?}", s),
|
_ => {
|
||||||
|
return Err(Error::invalid_input());
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Playlist type.
|
/// Playlist type.
|
||||||
///
|
///
|
||||||
|
@ -25,11 +26,12 @@ impl fmt::Display for PlaylistType {
|
||||||
|
|
||||||
impl FromStr for PlaylistType {
|
impl FromStr for PlaylistType {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
match s {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
match input {
|
||||||
"EVENT" => Ok(PlaylistType::Event),
|
"EVENT" => Ok(PlaylistType::Event),
|
||||||
"VOD" => Ok(PlaylistType::Vod),
|
"VOD" => Ok(PlaylistType::Vod),
|
||||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown playlist type: {:?}", s),
|
_ => Err(Error::custom(format!("Unknown playlist type: {:?}", input))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// [7. Protocol Version Compatibility]
|
/// [7. Protocol Version Compatibility]
|
||||||
///
|
///
|
||||||
/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7
|
/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7
|
||||||
|
@ -32,8 +33,9 @@ impl fmt::Display for ProtocolVersion {
|
||||||
}
|
}
|
||||||
impl FromStr for ProtocolVersion {
|
impl FromStr for ProtocolVersion {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
Ok(match s {
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match input {
|
||||||
"1" => ProtocolVersion::V1,
|
"1" => ProtocolVersion::V1,
|
||||||
"2" => ProtocolVersion::V2,
|
"2" => ProtocolVersion::V2,
|
||||||
"3" => ProtocolVersion::V3,
|
"3" => ProtocolVersion::V3,
|
||||||
|
@ -41,7 +43,7 @@ impl FromStr for ProtocolVersion {
|
||||||
"5" => ProtocolVersion::V5,
|
"5" => ProtocolVersion::V5,
|
||||||
"6" => ProtocolVersion::V6,
|
"6" => ProtocolVersion::V6,
|
||||||
"7" => ProtocolVersion::V7,
|
"7" => ProtocolVersion::V7,
|
||||||
_ => track_panic!(ErrorKind::InvalidInput, "Unknown protocol version: {:?}", s),
|
_ => return Err(Error::unknown_protocol_version(input)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
/// Signed decimal floating-point number.
|
/// Signed decimal floating-point number.
|
||||||
///
|
///
|
||||||
|
@ -18,9 +18,12 @@ impl SignedDecimalFloatingPoint {
|
||||||
///
|
///
|
||||||
/// The given value must be finite,
|
/// The given value must be finite,
|
||||||
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
||||||
pub fn new(n: f64) -> Result<Self> {
|
pub fn new(n: f64) -> crate::Result<Self> {
|
||||||
track_assert!(n.is_finite(), ErrorKind::InvalidInput);
|
if !n.is_finite() {
|
||||||
Ok(SignedDecimalFloatingPoint(n))
|
Err(Error::invalid_input())
|
||||||
|
} else {
|
||||||
|
Ok(SignedDecimalFloatingPoint(n))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts `DecimalFloatingPoint` to `f64`.
|
/// Converts `DecimalFloatingPoint` to `f64`.
|
||||||
|
@ -45,12 +48,8 @@ impl fmt::Display for SignedDecimalFloatingPoint {
|
||||||
|
|
||||||
impl FromStr for SignedDecimalFloatingPoint {
|
impl FromStr for SignedDecimalFloatingPoint {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
track_assert!(
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
s.chars().all(|c| c.is_digit(10) || c == '.' || c == '-'),
|
SignedDecimalFloatingPoint::new(input.parse().map_err(Error::parse_float_error)?)
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
|
||||||
Ok(SignedDecimalFloatingPoint(n))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{ErrorKind, Result};
|
use crate::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
@ -17,10 +17,13 @@ impl SingleLineString {
|
||||||
///
|
///
|
||||||
/// If the given string contains any control characters,
|
/// If the given string contains any control characters,
|
||||||
/// this function will return an error which has the kind `ErrorKind::InvalidInput`.
|
/// 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) -> crate::Result<Self> {
|
||||||
let s = s.into();
|
let s = s.into();
|
||||||
track_assert!(!s.chars().any(|c| c.is_control()), ErrorKind::InvalidInput);
|
if s.chars().any(|c| c.is_control()) {
|
||||||
Ok(SingleLineString(s))
|
Err(Error::invalid_input())
|
||||||
|
} else {
|
||||||
|
Ok(SingleLineString(s))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
src/utils.rs
22
src/utils.rs
|
@ -1,23 +1,15 @@
|
||||||
use crate::{ErrorKind, Result};
|
use crate::{Error, Result};
|
||||||
use trackable::error::ErrorKindExt;
|
|
||||||
|
|
||||||
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> Result<bool> {
|
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> Result<bool> {
|
||||||
match s.as_ref() {
|
match s.as_ref() {
|
||||||
"YES" => Ok(true),
|
"YES" => Ok(true),
|
||||||
"NO" => Ok(false),
|
"NO" => Ok(false),
|
||||||
_ => track_panic!(
|
_ => Err(Error::invalid_input()),
|
||||||
ErrorKind::InvalidInput,
|
|
||||||
"Unexpected value: {:?}",
|
|
||||||
s.as_ref()
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_u64<T: AsRef<str>>(s: T) -> Result<u64> {
|
pub(crate) fn parse_u64<T: AsRef<str>>(s: T) -> Result<u64> {
|
||||||
let n = track!(s
|
let n = s.as_ref().parse().map_err(Error::unknown)?; // TODO: Error::number
|
||||||
.as_ref()
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
|
||||||
Ok(n)
|
Ok(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,13 +37,17 @@ pub(crate) fn quote<T: ToString>(value: T) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks, if the given tag is at the start of the input. If this is the case, it will remove it
|
/// 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.
|
/// and return the rest of the input.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
/// This function will return `Error::MissingTag`, if the input doesn't start with the tag, that
|
||||||
|
/// has been passed to this function.
|
||||||
pub(crate) fn tag<T>(input: &str, tag: T) -> crate::Result<&str>
|
pub(crate) fn tag<T>(input: &str, tag: T) -> crate::Result<&str>
|
||||||
where
|
where
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
{
|
{
|
||||||
if !input.starts_with(tag.as_ref()) {
|
if !input.starts_with(tag.as_ref()) {
|
||||||
Err(ErrorKind::InvalidInput)?; // TODO!
|
return Err(Error::missing_tag(tag.as_ref(), input));
|
||||||
}
|
}
|
||||||
let result = input.split_at(tag.as_ref().len()).1;
|
let result = input.split_at(tag.as_ref().len()).1;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|
Loading…
Reference in a new issue