1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-12-23 20:40:29 +00:00

Refactor line module

This commit is contained in:
Takeru Ohta 2018-02-15 00:50:57 +09:00
parent 32b262713e
commit 0fdcf41732
6 changed files with 146 additions and 163 deletions

View file

@ -1,19 +1,10 @@
use {ErrorKind, Result}; use std::fmt;
use tag::Tag; use std::str::FromStr;
use {Error, ErrorKind, Result};
use tag;
use types::SingleLineString;
// [rfc8216#section-4.1]
// > Playlist files MUST be encoded in UTF-8 [RFC3629]. They MUST NOT
// > contain any Byte Order Mark (BOM); clients SHOULD fail to parse
// > Playlists that contain a BOM or do not parse as UTF-8. Playlist
// > files MUST NOT contain UTF-8 control characters (U+0000 to U+001F and
// > U+007F to U+009F), with the exceptions of CR (U+000D) and LF
// > (U+000A). All character sequences MUST be normalized according to
// > Unicode normalization form "NFC" [UNICODE]. Note that US-ASCII
// > [US_ASCII] conforms to these rules.
// >
// > Lines in a Playlist file are terminated by either a single line feed
// > character or a carriage return character followed by a line feed
// > character.
#[derive(Debug)] #[derive(Debug)]
pub struct Lines<'a> { pub struct Lines<'a> {
input: &'a str, input: &'a str,
@ -27,9 +18,17 @@ impl<'a> Lines<'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;
for (i, c) in self.input.char_indices() { for (i, c) in self.input.char_indices() {
match c { match c {
'\n' => { '\n' => {
if !next_line_of_ext_x_stream_inf
&& self.input.starts_with(tag::ExtXStreamInf::PREFIX)
{
next_line_of_ext_x_stream_inf = true;
adjust = 0;
continue;
}
next_start = i + 1; next_start = i + 1;
end = i - adjust; end = i - adjust;
break; break;
@ -37,10 +36,8 @@ impl<'a> Lines<'a> {
'\r' => { '\r' => {
adjust = 1; adjust = 1;
} }
'\u{00}'...'\u{1F}' | '\u{7F}'...'\u{9f}' => {
track_panic!(ErrorKind::InvalidInput);
}
_ => { _ => {
track_assert!(!c.is_control(), ErrorKind::InvalidInput);
adjust = 0; adjust = 0;
} }
} }
@ -53,7 +50,8 @@ impl<'a> Lines<'a> {
} else if raw_line.starts_with("#") { } else if raw_line.starts_with("#") {
Line::Comment(raw_line) Line::Comment(raw_line)
} else { } else {
Line::Uri(raw_line) let uri = track!(SingleLineString::new(raw_line))?;
Line::Uri(uri)
}; };
self.input = &self.input[next_start..]; self.input = &self.input[next_start..];
Ok(line) Ok(line)
@ -77,22 +75,113 @@ pub enum Line<'a> {
Blank, Blank,
Comment(&'a str), Comment(&'a str),
Tag(Tag), Tag(Tag),
Uri(SingleLineString),
// TODO:
Uri(&'a str),
} }
// TODO #[derive(Debug, Clone, PartialEq, Eq)]
// #[cfg(test)] pub enum Tag {
// mod test { ExtM3u(tag::ExtM3u),
// use super::*; ExtXVersion(tag::ExtXVersion),
ExtInf(tag::ExtInf),
// #[test] ExtXByteRange(tag::ExtXByteRange),
// fn it_works() { ExtXDiscontinuity(tag::ExtXDiscontinuity),
// let mut lines = Lines::new("foo\nbar\r\nbaz"); ExtXKey(tag::ExtXKey),
// assert_eq!(lines.next().and_then(|x| x.ok()), Some("foo")); ExtXMap(tag::ExtXMap),
// assert_eq!(lines.next().and_then(|x| x.ok()), Some("bar")); ExtXProgramDateTime(tag::ExtXProgramDateTime),
// assert_eq!(lines.next().and_then(|x| x.ok()), Some("baz")); ExtXDateRange(tag::ExtXDateRange),
// assert_eq!(lines.next().and_then(|x| x.ok()), None); ExtXTargetDuration(tag::ExtXTargetDuration),
// } ExtXMediaSequence(tag::ExtXMediaSequence),
// } ExtXDiscontinuitySequence(tag::ExtXDiscontinuitySequence),
ExtXEndList(tag::ExtXEndList),
ExtXPlaylistType(tag::ExtXPlaylistType),
ExtXIFramesOnly(tag::ExtXIFramesOnly),
ExtXMedia(tag::ExtXMedia),
ExtXStreamInf(tag::ExtXStreamInf),
ExtXIFrameStreamInf(tag::ExtXIFrameStreamInf),
ExtXSessionData(tag::ExtXSessionData),
ExtXSessionKey(tag::ExtXSessionKey),
ExtXIndependentSegments(tag::ExtXIndependentSegments),
ExtXStart(tag::ExtXStart),
Unknown(SingleLineString),
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Tag::ExtM3u(ref t) => t.fmt(f),
Tag::ExtXVersion(ref t) => t.fmt(f),
Tag::ExtInf(ref t) => t.fmt(f),
Tag::ExtXByteRange(ref t) => t.fmt(f),
Tag::ExtXDiscontinuity(ref t) => t.fmt(f),
Tag::ExtXKey(ref t) => t.fmt(f),
Tag::ExtXMap(ref t) => t.fmt(f),
Tag::ExtXProgramDateTime(ref t) => t.fmt(f),
Tag::ExtXDateRange(ref t) => t.fmt(f),
Tag::ExtXTargetDuration(ref t) => t.fmt(f),
Tag::ExtXMediaSequence(ref t) => t.fmt(f),
Tag::ExtXDiscontinuitySequence(ref t) => t.fmt(f),
Tag::ExtXEndList(ref t) => t.fmt(f),
Tag::ExtXPlaylistType(ref t) => t.fmt(f),
Tag::ExtXIFramesOnly(ref t) => t.fmt(f),
Tag::ExtXMedia(ref t) => t.fmt(f),
Tag::ExtXStreamInf(ref t) => t.fmt(f),
Tag::ExtXIFrameStreamInf(ref t) => t.fmt(f),
Tag::ExtXSessionData(ref t) => t.fmt(f),
Tag::ExtXSessionKey(ref t) => t.fmt(f),
Tag::ExtXIndependentSegments(ref t) => t.fmt(f),
Tag::ExtXStart(ref t) => t.fmt(f),
Tag::Unknown(ref t) => t.fmt(f),
}
}
}
impl FromStr for Tag {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if s.starts_with(tag::ExtM3u::PREFIX) {
track!(s.parse().map(Tag::ExtM3u))
} else if s.starts_with(tag::ExtXVersion::PREFIX) {
track!(s.parse().map(Tag::ExtXVersion))
} else if s.starts_with(tag::ExtInf::PREFIX) {
track!(s.parse().map(Tag::ExtInf))
} else if s.starts_with(tag::ExtXByteRange::PREFIX) {
track!(s.parse().map(Tag::ExtXByteRange))
} else if s.starts_with(tag::ExtXDiscontinuity::PREFIX) {
track!(s.parse().map(Tag::ExtXDiscontinuity))
} else if s.starts_with(tag::ExtXKey::PREFIX) {
track!(s.parse().map(Tag::ExtXKey))
} else if s.starts_with(tag::ExtXMap::PREFIX) {
track!(s.parse().map(Tag::ExtXMap))
} else if s.starts_with(tag::ExtXProgramDateTime::PREFIX) {
track!(s.parse().map(Tag::ExtXProgramDateTime))
} else if s.starts_with(tag::ExtXTargetDuration::PREFIX) {
track!(s.parse().map(Tag::ExtXTargetDuration))
} else if s.starts_with(tag::ExtXDateRange::PREFIX) {
track!(s.parse().map(Tag::ExtXDateRange))
} else if s.starts_with(tag::ExtXMediaSequence::PREFIX) {
track!(s.parse().map(Tag::ExtXMediaSequence))
} else if s.starts_with(tag::ExtXDiscontinuitySequence::PREFIX) {
track!(s.parse().map(Tag::ExtXDiscontinuitySequence))
} else if s.starts_with(tag::ExtXEndList::PREFIX) {
track!(s.parse().map(Tag::ExtXEndList))
} else if s.starts_with(tag::ExtXPlaylistType::PREFIX) {
track!(s.parse().map(Tag::ExtXPlaylistType))
} else if s.starts_with(tag::ExtXIFramesOnly::PREFIX) {
track!(s.parse().map(Tag::ExtXIFramesOnly))
} else if s.starts_with(tag::ExtXMedia::PREFIX) {
track!(s.parse().map(Tag::ExtXMedia))
} else if s.starts_with(tag::ExtXStreamInf::PREFIX) {
track!(s.parse().map(Tag::ExtXStreamInf))
} else if s.starts_with(tag::ExtXIFrameStreamInf::PREFIX) {
track!(s.parse().map(Tag::ExtXIFrameStreamInf))
} else if s.starts_with(tag::ExtXSessionData::PREFIX) {
track!(s.parse().map(Tag::ExtXSessionData))
} else if s.starts_with(tag::ExtXSessionKey::PREFIX) {
track!(s.parse().map(Tag::ExtXSessionKey))
} else if s.starts_with(tag::ExtXIndependentSegments::PREFIX) {
track!(s.parse().map(Tag::ExtXIndependentSegments))
} else if s.starts_with(tag::ExtXStart::PREFIX) {
track!(s.parse().map(Tag::ExtXStart))
} else {
track!(SingleLineString::new(s)).map(Tag::Unknown)
}
}
}

View file

@ -2,9 +2,9 @@ use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use {Error, ErrorKind, Result}; use {Error, ErrorKind, Result};
use line::{Line, Lines}; use line::{Line, Lines, Tag};
use tag::{ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData, use tag::{ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, Tag}; ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion};
use types::ProtocolVersion; use types::ProtocolVersion;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -132,16 +132,14 @@ impl FromStr for MasterPlaylist {
track_assert_eq!(start, None, ErrorKind::InvalidInput); track_assert_eq!(start, None, ErrorKind::InvalidInput);
start = Some(t); start = Some(t);
} }
Tag::Unknown(_) => {
// [6.3.1. General Client Responsibilities]
// > ignore any unrecognized tags.
}
} }
} }
Line::Uri(uri) => { Line::Uri(uri) => {
let (line, inf) = track_assert_some!(last_stream_inf, ErrorKind::InvalidInput); track_panic!(ErrorKind::InvalidInput, "Unexpected URI: {:?}", uri);
track_assert_eq!(line + 1, i, ErrorKind::InvalidInput);
stream_infs.push(ExtXStreamInfWithUri {
inf,
uri: uri.to_owned(),
});
last_stream_inf = None;
} }
} }
} }

View file

@ -2,11 +2,11 @@ use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use {Error, ErrorKind, Result}; use {Error, ErrorKind, Result};
use line::{Line, Lines}; use line::{Line, Lines, Tag};
use media_segment::{MediaSegment, MediaSegmentBuilder}; use media_segment::{MediaSegment, MediaSegmentBuilder};
use tag::{ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, use tag::{ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly,
ExtXIndependentSegments, ExtXMediaSequence, ExtXPlaylistType, ExtXStart, ExtXIndependentSegments, ExtXMediaSequence, ExtXPlaylistType, ExtXStart,
ExtXTargetDuration, ExtXVersion, Tag}; ExtXTargetDuration, ExtXVersion};
use types::ProtocolVersion; use types::ProtocolVersion;
// TODO: There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist. // TODO: There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist.
@ -167,10 +167,14 @@ impl FromStr for MediaPlaylist {
track_assert_eq!(start, None, ErrorKind::InvalidInput); track_assert_eq!(start, None, ErrorKind::InvalidInput);
start = Some(t); start = Some(t);
} }
Tag::Unknown(_) => {
// [6.3.1. General Client Responsibilities]
// > ignore any unrecognized tags.
}
} }
} }
Line::Uri(uri) => { Line::Uri(uri) => {
segment.uri(uri.to_owned()); segment.uri(uri);
segments.push(track!(segment.finish())?); segments.push(track!(segment.finish())?);
segment = MediaSegmentBuilder::new(); segment = MediaSegmentBuilder::new();
} }

View file

@ -4,10 +4,11 @@ use std::iter;
use {ErrorKind, Result}; use {ErrorKind, Result};
use tag::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, use tag::{ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap,
ExtXProgramDateTime, MediaSegmentTag}; ExtXProgramDateTime, MediaSegmentTag};
use types::SingleLineString;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MediaSegmentBuilder { pub struct MediaSegmentBuilder {
uri: Option<String>, uri: Option<SingleLineString>,
ext_inf: Option<ExtInf>, ext_inf: Option<ExtInf>,
ext_x_byterange: Option<ExtXByteRange>, ext_x_byterange: Option<ExtXByteRange>,
ext_x_daterange: Option<ExtXDateRange>, ext_x_daterange: Option<ExtXDateRange>,
@ -29,7 +30,7 @@ impl MediaSegmentBuilder {
ext_x_program_date_time: None, ext_x_program_date_time: None,
} }
} }
pub fn uri(&mut self, uri: String) -> &mut Self { pub fn uri(&mut self, uri: SingleLineString) -> &mut Self {
self.uri = Some(uri); self.uri = Some(uri);
self self
} }
@ -62,7 +63,7 @@ impl MediaSegmentBuilder {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MediaSegment { pub struct MediaSegment {
pub uri: String, // TODO pub uri: SingleLineString,
pub ext_inf: ExtInf, pub ext_inf: ExtInf,
pub tags: Vec<MediaSegmentTag>, pub tags: Vec<MediaSegmentTag>,
} }

View file

@ -368,8 +368,8 @@ impl fmt::Display for ExtXStreamInf {
impl FromStr for ExtXStreamInf { impl FromStr for ExtXStreamInf {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(s: &str) -> Result<Self> {
let mut lines = s.splitn(2, '\n'); // TODO: let mut lines = s.splitn(2, '\n');
let first_line = lines.next().expect("Never fails"); let first_line = lines.next().expect("Never fails").trim_right_matches('\r');
let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput); let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput);
track_assert!( track_assert!(

View file

@ -2,10 +2,9 @@
//! //!
//! [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
use std::fmt; use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt; use trackable::error::ErrorKindExt;
use {Error, ErrorKind, Result}; use {ErrorKind, Result};
macro_rules! may_invalid { macro_rules! may_invalid {
($expr:expr) => { ($expr:expr) => {
@ -123,114 +122,6 @@ impl_from!(MediaSegmentTag, ExtXKey);
impl_from!(MediaSegmentTag, ExtXMap); impl_from!(MediaSegmentTag, ExtXMap);
impl_from!(MediaSegmentTag, ExtXProgramDateTime); impl_from!(MediaSegmentTag, ExtXProgramDateTime);
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tag {
ExtM3u(ExtM3u),
ExtXVersion(ExtXVersion),
ExtInf(ExtInf),
ExtXByteRange(ExtXByteRange),
ExtXDiscontinuity(ExtXDiscontinuity),
ExtXKey(ExtXKey),
ExtXMap(ExtXMap),
ExtXProgramDateTime(ExtXProgramDateTime),
ExtXDateRange(ExtXDateRange),
ExtXTargetDuration(ExtXTargetDuration),
ExtXMediaSequence(ExtXMediaSequence),
ExtXDiscontinuitySequence(ExtXDiscontinuitySequence),
ExtXEndList(ExtXEndList),
ExtXPlaylistType(ExtXPlaylistType),
ExtXIFramesOnly(ExtXIFramesOnly),
ExtXMedia(ExtXMedia),
ExtXStreamInf(ExtXStreamInf),
ExtXIFrameStreamInf(ExtXIFrameStreamInf),
ExtXSessionData(ExtXSessionData),
ExtXSessionKey(ExtXSessionKey),
ExtXIndependentSegments(ExtXIndependentSegments),
ExtXStart(ExtXStart),
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Tag::ExtM3u(ref t) => t.fmt(f),
Tag::ExtXVersion(ref t) => t.fmt(f),
Tag::ExtInf(ref t) => t.fmt(f),
Tag::ExtXByteRange(ref t) => t.fmt(f),
Tag::ExtXDiscontinuity(ref t) => t.fmt(f),
Tag::ExtXKey(ref t) => t.fmt(f),
Tag::ExtXMap(ref t) => t.fmt(f),
Tag::ExtXProgramDateTime(ref t) => t.fmt(f),
Tag::ExtXDateRange(ref t) => t.fmt(f),
Tag::ExtXTargetDuration(ref t) => t.fmt(f),
Tag::ExtXMediaSequence(ref t) => t.fmt(f),
Tag::ExtXDiscontinuitySequence(ref t) => t.fmt(f),
Tag::ExtXEndList(ref t) => t.fmt(f),
Tag::ExtXPlaylistType(ref t) => t.fmt(f),
Tag::ExtXIFramesOnly(ref t) => t.fmt(f),
Tag::ExtXMedia(ref t) => t.fmt(f),
Tag::ExtXStreamInf(ref t) => t.fmt(f),
Tag::ExtXIFrameStreamInf(ref t) => t.fmt(f),
Tag::ExtXSessionData(ref t) => t.fmt(f),
Tag::ExtXSessionKey(ref t) => t.fmt(f),
Tag::ExtXIndependentSegments(ref t) => t.fmt(f),
Tag::ExtXStart(ref t) => t.fmt(f),
}
}
}
impl FromStr for Tag {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if s.starts_with(ExtM3u::PREFIX) {
track!(s.parse().map(Tag::ExtM3u))
} else if s.starts_with(ExtXVersion::PREFIX) {
track!(s.parse().map(Tag::ExtXVersion))
} else if s.starts_with(ExtInf::PREFIX) {
track!(s.parse().map(Tag::ExtInf))
} else if s.starts_with(ExtXByteRange::PREFIX) {
track!(s.parse().map(Tag::ExtXByteRange))
} else if s.starts_with(ExtXDiscontinuity::PREFIX) {
track!(s.parse().map(Tag::ExtXDiscontinuity))
} else if s.starts_with(ExtXKey::PREFIX) {
track!(s.parse().map(Tag::ExtXKey))
} else if s.starts_with(ExtXMap::PREFIX) {
track!(s.parse().map(Tag::ExtXMap))
} else if s.starts_with(ExtXProgramDateTime::PREFIX) {
track!(s.parse().map(Tag::ExtXProgramDateTime))
} else if s.starts_with(ExtXTargetDuration::PREFIX) {
track!(s.parse().map(Tag::ExtXTargetDuration))
} else if s.starts_with(ExtXDateRange::PREFIX) {
track!(s.parse().map(Tag::ExtXDateRange))
} else if s.starts_with(ExtXMediaSequence::PREFIX) {
track!(s.parse().map(Tag::ExtXMediaSequence))
} else if s.starts_with(ExtXDiscontinuitySequence::PREFIX) {
track!(s.parse().map(Tag::ExtXDiscontinuitySequence))
} else if s.starts_with(ExtXEndList::PREFIX) {
track!(s.parse().map(Tag::ExtXEndList))
} else if s.starts_with(ExtXPlaylistType::PREFIX) {
track!(s.parse().map(Tag::ExtXPlaylistType))
} else if s.starts_with(ExtXIFramesOnly::PREFIX) {
track!(s.parse().map(Tag::ExtXIFramesOnly))
} else if s.starts_with(ExtXMedia::PREFIX) {
track!(s.parse().map(Tag::ExtXMedia))
} else if s.starts_with(ExtXStreamInf::PREFIX) {
track!(s.parse().map(Tag::ExtXStreamInf))
} else if s.starts_with(ExtXIFrameStreamInf::PREFIX) {
track!(s.parse().map(Tag::ExtXIFrameStreamInf))
} else if s.starts_with(ExtXSessionData::PREFIX) {
track!(s.parse().map(Tag::ExtXSessionData))
} else if s.starts_with(ExtXSessionKey::PREFIX) {
track!(s.parse().map(Tag::ExtXSessionKey))
} else if s.starts_with(ExtXIndependentSegments::PREFIX) {
track!(s.parse().map(Tag::ExtXIndependentSegments))
} else if s.starts_with(ExtXStart::PREFIX) {
track!(s.parse().map(Tag::ExtXStart))
} else {
// TODO: ignore any unrecognized tags. (section-6.3.1)
track_panic!(ErrorKind::InvalidInput, "Unknown tag: {:?}", s)
}
}
}
fn parse_yes_or_no(s: &str) -> Result<bool> { fn parse_yes_or_no(s: &str) -> Result<bool> {
match s { match s {
"YES" => Ok(true), "YES" => Ok(true),