diff --git a/Cargo.toml b/Cargo.toml index 50151fe..9f1d53b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,4 @@ documentation = "https://rutgersc.github.io/doc/m3u8_rs/index.html" license = "MIT" [dependencies] -nom = "^1.2.3" +nom = "5.1.0" diff --git a/README.md b/README.md index 172ec27..2c62075 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,8 @@ file.read_to_end(&mut bytes).unwrap(); let parsed = m3u8_rs::parse_playlist(&bytes); match parsed { - IResult::Done(i, Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{}", pl), - IResult::Done(i, Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{}", pl), + IResult::Ok((i, Playlist::MasterPlaylist(pl))) => println!("Master playlist:\n{}", pl), + IResult::Ok((i, Playlist::MediaPlaylist(pl))) => println!("Media playlist:\n{}", pl), IResult::Error(e) => panic!("Parsing error: \n{}", e), IResult::Incomplete(e) => panic!("Parsing error: \n{:?}", e), } diff --git a/examples/with_nom_result.rs b/examples/with_nom_result.rs index 0eaa49f..8e73c9e 100644 --- a/examples/with_nom_result.rs +++ b/examples/with_nom_result.rs @@ -3,7 +3,6 @@ extern crate m3u8_rs; use m3u8_rs::playlist::{Playlist}; use std::io::Read; -use nom::IResult; fn main() { let mut file = std::fs::File::open("playlist.m3u8").unwrap(); @@ -13,9 +12,8 @@ fn main() { let parsed = m3u8_rs::parse_playlist(&bytes); let playlist = match parsed { - IResult::Done(i, playlist) => playlist, - IResult::Error(e) => panic!("Parsing error: \n{}", e), - IResult::Incomplete(e) => panic!("Parsing error: \n{:?}", e), + Result::Ok((i, playlist)) => playlist, + Result::Err(e) => panic!("Parsing error: \n{}", e), }; match playlist { @@ -32,9 +30,8 @@ fn main_alt() { let parsed = m3u8_rs::parse_playlist(&bytes); match parsed { - IResult::Done(i, Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl), - IResult::Done(i, Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl), - IResult::Error(e) => panic!("Parsing error: \n{}", e), - IResult::Incomplete(e) => panic!("Parsing error: \n{:?}", e), + Result::Ok((i, Playlist::MasterPlaylist(pl))) => println!("Master playlist:\n{:?}", pl), + Result::Ok((i, Playlist::MediaPlaylist(pl))) => println!("Media playlist:\n{:?}", pl), + Result::Err(e) => panic!("Parsing error: \n{}", e), } } diff --git a/src/lib.rs b/src/lib.rs index b3ef030..dc7132e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! A library to parse m3u8 playlists (HTTP Live Streaming) [link] //! (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19). //! -//! #Examples +//! # Examples //! //! Parsing a playlist and let the parser figure out if it's a media or master playlist. //! @@ -17,19 +17,10 @@ //! let mut bytes: Vec = Vec::new(); //! file.read_to_end(&mut bytes).unwrap(); //! -//! // Option 1: fn parse_playlist_res(input) -> Result -//! match m3u8_rs::parse_playlist_res(&bytes) { -//! Ok(Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl), -//! Ok(Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl), -//! Err(e) => println!("Error: {:?}", e) -//! } -//! -//! // Option 2: fn parse_playlist(input) -> IResult<_, Playlist, _> //! match m3u8_rs::parse_playlist(&bytes) { -//! IResult::Done(i, Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl), -//! IResult::Done(i, Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl), -//! IResult::Error(e) => panic!("Parsing error: \n{}", e), -//! IResult::Incomplete(e) => panic!("Parsing error: \n{:?}", e), +//! Result::Ok((i, Playlist::MasterPlaylist(pl))) => println!("Master playlist:\n{:?}", pl), +//! Result::Ok((i, Playlist::MediaPlaylist(pl))) => println!("Media playlist:\n{:?}", pl), +//! Result::Err(e) => panic!("Parsing error: \n{}", e), //! } //! } //! ``` @@ -46,8 +37,8 @@ //! let mut file = std::fs::File::open("masterplaylist.m3u8").unwrap(); //! let mut bytes: Vec = Vec::new(); //! file.read_to_end(&mut bytes).unwrap(); -//! -//! if let IResult::Done(_, pl) = m3u8_rs::parse_master_playlist(&bytes) { +//! +//! if let Result::Ok((_, pl)) = m3u8_rs::parse_master_playlist(&bytes) { //! println!("{:?}", pl); //! } //! } @@ -61,7 +52,7 @@ //! use m3u8_rs::playlist::{MediaPlaylist, MediaPlaylistType, MediaSegment}; //! //! fn main() { -//! let playlist = MediaPlaylist { +//! let playlist = MediaPlaylist { //! version: 6, //! target_duration: 3.0, //! media_sequence: 338559, @@ -78,10 +69,10 @@ //! ], //! ..Default::default() //! }; -//! +//! //! //let mut v: Vec = Vec::new(); //! //playlist.write_to(&mut v).unwrap(); -//! +//! //! //let mut file = std::fs::File::open("playlist.m3u8").unwrap(); //! //playlist.write_to(&mut file).unwrap(); //! } @@ -92,7 +83,12 @@ extern crate nom; pub mod playlist; -use nom::*; +use nom::character::complete::{digit1, multispace0, space0 }; +use nom::{IResult}; +use nom::{ delimited,none_of,peek,is_not,complete,terminated,tag, + alt,do_parse,opt,named,map,map_res,eof,many0,take,take_until,char}; +use nom::combinator::map; +use nom::character::complete::{line_ending}; use std::str; use std::f32; use std::string; @@ -105,10 +101,14 @@ use playlist::*; // Playlist parser // ----------------------------------------------------------------------------------------------- -/// Parse a m3u8 playlist. +/// Parse an m3u8 playlist. /// -/// #Examples +/// # Examples /// +/// ``` +/// use std::io::Read; +/// use m3u8_rs::playlist::{Playlist}; +/// /// let mut file = std::fs::File::open("playlist.m3u8").unwrap(); /// let mut bytes: Vec = Vec::new(); /// file.read_to_end(&mut bytes).unwrap(); @@ -116,25 +116,26 @@ use playlist::*; /// let parsed = m3u8_rs::parse_playlist(&bytes); /// /// let playlist = match parsed { -/// IResult::Done(i, playlist) => playlist, -/// IResult::Error(e) => panic!("Parsing error: \n{}", e), -/// IResult::Incomplete(e) => panic!("Parsing error: \n{:?}", e), +/// Result::Ok((i, playlist)) => playlist, +/// Result::Err(e) => panic!("Parsing error: \n{}", e), /// }; /// /// match playlist { /// Playlist::MasterPlaylist(pl) => println!("Master playlist:\n{:?}", pl), /// Playlist::MediaPlaylist(pl) => println!("Media playlist:\n{:?}", pl), /// } +/// ``` pub fn parse_playlist(input: &[u8]) -> IResult<&[u8], Playlist> { match is_master_playlist(input) { - true => parse_master_playlist(input).map(Playlist::MasterPlaylist), - false => parse_media_playlist(input).map(Playlist::MediaPlaylist), + true => map(parse_master_playlist, Playlist::MasterPlaylist)(input), + false =>map(parse_media_playlist, Playlist::MediaPlaylist)(input), } } -/// Parse a m3u8 playlist just like `parse_playlist`. This returns a Result. -/// -/// #Examples +/// Parses an m3u8 playlist just like `parse_playlist`, except that this returns an [std::result::Result](std::result::Result) instead of a [nom::IResult](https://docs.rs/nom/1.2.3/nom/enum.IResult.html). +/// However, since [nom::IResult](nom::IResult) is now an [alias to Result](https://github.com/Geal/nom/blob/master/doc/upgrading_to_nom_5.md), this is no longer needed. +/// +/// # Examples /// /// ``` /// use m3u8_rs::playlist::{Playlist}; @@ -155,35 +156,35 @@ pub fn parse_playlist(input: &[u8]) -> IResult<&[u8], Playlist> { pub fn parse_playlist_res(input: &[u8]) -> Result> { let parse_result = parse_playlist(input); match parse_result { - IResult::Done(_, playlist) => Ok(playlist), + IResult::Ok((_, playlist)) => Ok(playlist), _ => Err(parse_result), } } /// Parse input as a master playlist pub fn parse_master_playlist(input: &[u8]) -> IResult<&[u8], MasterPlaylist> { - parse_master_playlist_tags(input).map(MasterPlaylist::from_tags) + map(parse_master_playlist_tags, MasterPlaylist::from_tags)(input) } /// Parse input as a master playlist pub fn parse_master_playlist_res(input: &[u8]) -> Result> { let parse_result = parse_master_playlist(input); match parse_result { - IResult::Done(_, playlist) => Ok(playlist), + IResult::Ok((_, playlist)) => Ok(playlist), _ => Err(parse_result), } } /// Parse input as a media playlist pub fn parse_media_playlist(input: &[u8]) -> IResult<&[u8], MediaPlaylist> { - parse_media_playlist_tags(input).map(MediaPlaylist::from_tags) + map(parse_media_playlist_tags, MediaPlaylist::from_tags)(input) } /// Parse input as a media playlist pub fn parse_media_playlist_res(input: &[u8]) -> Result> { let parse_result = parse_media_playlist(input); match parse_result { - IResult::Done(_, playlist) => Ok(playlist), + IResult::Ok((_, playlist)) => Ok(playlist), _ => Err(parse_result), } } @@ -208,7 +209,7 @@ pub fn contains_master_tag(input: &[u8]) -> Option<(bool, String)> { while is_master_opt == None { match is_master_playlist_tag_line(current_input) { - IResult::Done(rest, result) => { + IResult::Ok((rest, result)) => { current_input = rest; is_master_opt = result; // result can be None (no media or master tag found) } @@ -220,7 +221,7 @@ pub fn contains_master_tag(input: &[u8]) -> Option<(bool, String)> { } named!(pub is_master_playlist_tag_line(&[u8]) -> Option<(bool, String)>, - chain!( + do_parse!( tag: opt!(alt!( map!(tag!("#EXT-X-STREAM-INF"), |t| (true, t)) | map!(tag!("#EXT-X-I-FRAME-STREAM-INF"), |t| (true, t)) @@ -243,10 +244,11 @@ named!(pub is_master_playlist_tag_line(&[u8]) -> Option<(bool, String)>, | map!(tag!("#EXT-X-PROGRAM-DATE-TIME"), |t| (false, t)) | map!(tag!("#EXT-X-DATERANGE"), |t| (false, t)) )) - ~ consume_line - , || { + >> consume_line + >> + ( { tag.map(|(a,b)| (a, from_utf8_slice(b).unwrap())) - } + } ) ) ); @@ -254,13 +256,16 @@ named!(pub is_master_playlist_tag_line(&[u8]) -> Option<(bool, String)>, // Master Playlist Tags // ----------------------------------------------------------------------------------------------- -pub fn parse_master_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec> { - chain!(input, - mut tags: many0!(chain!(m:master_playlist_tag ~ multispace?, || m)) ~ eof?, - || { tags.reverse(); tags } +pub fn parse_master_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec> { + do_parse!(input, + tags: many0!(complete!(do_parse!( m : master_playlist_tag >> multispace0 >> (m) ))) + >> opt!(eof!()) + >> + ( {let mut tags_rev: Vec = tags; tags_rev.reverse(); tags_rev } ) ) } + /// Contains all the tags required to parse a master playlist. #[derive(Debug)] pub enum MasterPlaylistTag { @@ -298,28 +303,28 @@ pub fn master_playlist_tag(input: &[u8]) -> IResult<&[u8], MasterPlaylistTag> { } named!(pub variant_stream_tag, - chain!(tag!("#EXT-X-STREAM-INF:") ~ attributes: key_value_pairs, - || VariantStream::from_hashmap(attributes, false)) + do_parse!(tag!("#EXT-X-STREAM-INF:") >> attributes: key_value_pairs >> + ( VariantStream::from_hashmap(attributes, false))) ); named!(pub variant_i_frame_stream_tag, - chain!( tag!("#EXT-X-I-FRAME-STREAM-INF:") ~ attributes: key_value_pairs, - || VariantStream::from_hashmap(attributes, true)) + do_parse!( tag!("#EXT-X-I-FRAME-STREAM-INF:") >> attributes: key_value_pairs >> + ( VariantStream::from_hashmap(attributes, true))) ); named!(pub alternative_media_tag, - chain!( tag!("#EXT-X-MEDIA:") ~ attributes: key_value_pairs, - || AlternativeMedia::from_hashmap(attributes)) + do_parse!( tag!("#EXT-X-MEDIA:") >> attributes: key_value_pairs >> + ( AlternativeMedia::from_hashmap(attributes))) ); named!(pub session_data_tag, - chain!( tag!("#EXT-X-SESSION-DATA:") ~ attributes: key_value_pairs, - || SessionData::from_hashmap(attributes)) + do_parse!( tag!("#EXT-X-SESSION-DATA:") >> attributes: key_value_pairs >> + ( SessionData::from_hashmap(attributes))) ); named!(pub session_key_tag, - chain!( tag!("#EXT-X-SESSION-KEY:") ~ session_key: map!(key, SessionKey), - || session_key) + do_parse!( tag!("#EXT-X-SESSION-KEY:") >> session_key: map!(key, SessionKey) >> + ( session_key)) ); // ----------------------------------------------------------------------------------------------- @@ -327,9 +332,10 @@ named!(pub session_key_tag, // ----------------------------------------------------------------------------------------------- pub fn parse_media_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec> { - chain!(input, - mut tags: many0!(chain!(m:media_playlist_tag ~ multispace?, || m)) ~ eof?, - || { tags.reverse(); tags } + do_parse!(input, + tags: many0!(complete!(do_parse!(m:media_playlist_tag >> multispace0 >> (m) ))) >> opt!(eof!()) + >> + ( {let mut tags_rev: Vec = tags; tags_rev.reverse(); tags_rev } ) ) } @@ -354,10 +360,10 @@ pub fn media_playlist_tag(input: &[u8]) -> IResult<&[u8], MediaPlaylistTag> { map!(m3u_tag, MediaPlaylistTag::M3U) | map!(version_tag, MediaPlaylistTag::Version) - | map!(chain!(tag!("#EXT-X-TARGETDURATION:") ~ n:float,||n), MediaPlaylistTag::TargetDuration) - | map!(chain!(tag!("#EXT-X-MEDIA-SEQUENCE:") ~ n:number,||n), MediaPlaylistTag::MediaSequence) - | map!(chain!(tag!("#EXT-X-DISCONTINUITY-SEQUENCE:") ~ n:number,||n), MediaPlaylistTag::DiscontinuitySequence) - | map!(chain!(tag!("#EXT-X-PLAYLIST-TYPE:") ~ t:playlist_type, ||t), MediaPlaylistTag::PlaylistType) + | map!(do_parse!(tag!("#EXT-X-TARGETDURATION:") >> n:float >> (n)), MediaPlaylistTag::TargetDuration) + | map!(do_parse!(tag!("#EXT-X-MEDIA-SEQUENCE:") >> n:number >> (n)), MediaPlaylistTag::MediaSequence) + | map!(do_parse!(tag!("#EXT-X-DISCONTINUITY-SEQUENCE:") >> n:number >> (n)), MediaPlaylistTag::DiscontinuitySequence) + | map!(do_parse!(tag!("#EXT-X-PLAYLIST-TYPE:") >> t:playlist_type >> (t)), MediaPlaylistTag::PlaylistType) | map!(tag!("#EXT-X-I-FRAMES-ONLY"), |_| MediaPlaylistTag::IFramesOnly) | map!(start_tag, MediaPlaylistTag::Start) | map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| MediaPlaylistTag::IndependentSegments) @@ -369,7 +375,11 @@ pub fn media_playlist_tag(input: &[u8]) -> IResult<&[u8], MediaPlaylistTag> { named!(pub playlist_type, map_res!( - map_res!(take_until_either_and_consume!("\r\n"), str::from_utf8), + do_parse!( + p: map_res!(is_not!("\r\n"), str::from_utf8) + >> take!(1) + >> (p) + ), MediaPlaylistType::from_str ) ); @@ -395,13 +405,13 @@ pub enum SegmentTag { pub fn media_segment_tag(input: &[u8]) -> IResult<&[u8], SegmentTag> { alt!(input, - map!(chain!(tag!("#EXTINF:") ~ e:duration_title_tag,||e), |(a,b)| SegmentTag::Extinf(a,b)) - | map!(chain!(tag!("#EXT-X-BYTERANGE:") ~ r:byte_range_val, || r), SegmentTag::ByteRange) + map!(do_parse!(tag!("#EXTINF:") >> e:duration_title_tag >> (e)), |(a,b)| SegmentTag::Extinf(a,b)) + | map!(do_parse!(tag!("#EXT-X-BYTERANGE:") >> r:byte_range_val >> (r)), SegmentTag::ByteRange) | map!(tag!("#EXT-X-DISCONTINUITY"), |_| SegmentTag::Discontinuity) - | map!(chain!(tag!("#EXT-X-KEY:") ~ k:key, || k), SegmentTag::Key) - | map!(chain!(tag!("#EXT-X-MAP:") ~ m:map, || m), SegmentTag::Map) - | map!(chain!(tag!("#EXT-X-PROGRAM-DATE-TIME:") ~ t:consume_line, || t), SegmentTag::ProgramDateTime) - | map!(chain!(tag!("#EXT-X-DATE-RANGE:") ~ t:consume_line, || t), SegmentTag::DateRange) + | map!(do_parse!(tag!("#EXT-X-KEY:") >> k: key >> (k)), SegmentTag::Key) + | map!(do_parse!(tag!("#EXT-X-MAP:") >> m: extmap >> (m)), SegmentTag::Map) + | map!(do_parse!(tag!("#EXT-X-PROGRAM-DATE-TIME:") >> t:consume_line >> (t)), SegmentTag::ProgramDateTime) + | map!(do_parse!(tag!("#EXT-X-DATE-RANGE:") >> t:consume_line >> (t)), SegmentTag::DateRange) | map!(ext_tag, SegmentTag::Unknown) | map!(comment_tag, SegmentTag::Comment) @@ -411,53 +421,59 @@ pub fn media_segment_tag(input: &[u8]) -> IResult<&[u8], SegmentTag> { } named!(pub duration_title_tag<(f32, Option)>, - chain!( - duration: float - ~ tag!(",")? - ~ title: opt!(map_res!(take_until_either_and_consume!("\r\n,"), from_utf8_slice)) - ~ tag!(",")? - , - || (duration, title) + do_parse!( + duration: float + >> opt!(tag!(",")) + >> title: opt!(map_res!(is_not!("\r\n,"), from_utf8_slice)) + >> take!(1) + >> opt!(tag!(",")) + >> + (duration, title) ) ); named!(pub key, map!(key_value_pairs, Key::from_hashmap)); -named!(pub map, map!(key_value_pairs, Map::from_hashmap)); +named!(pub extmap, map!(key_value_pairs, Map::from_hashmap)); // ----------------------------------------------------------------------------------------------- // Basic tags // ----------------------------------------------------------------------------------------------- named!(pub m3u_tag, - map_res!(tag!("#EXTM3U"), from_utf8_slice) + map_res!(tag!("#EXTM3U"), from_utf8_slice) ); named!(pub version_tag, - chain!( - tag!("#EXT-X-VERSION:") ~ version: map_res!(digit, str::from_utf8), - || version.parse().unwrap_or_default() + do_parse!( + tag!("#EXT-X-VERSION:") >> version: map_res!(digit1, str::from_utf8) >> + (version.parse().unwrap_or_default()) ) ); named!(pub start_tag, - chain!(tag!("#EXT-X-START:") ~ attributes:key_value_pairs, || Start::from_hashmap(attributes)) + do_parse!(tag!("#EXT-X-START:") >> attributes:key_value_pairs >> + (Start::from_hashmap(attributes)) + ) ); named!(pub ext_tag, - chain!( + do_parse!( tag!("#EXT-") - ~ tag: map_res!(take_until_and_consume!(":"), from_utf8_slice) - ~ rest: map_res!(take_until_either_and_consume!("\r\n"), from_utf8_slice) - , - || ExtTag { tag: tag, rest: rest } + >> tag: map_res!(take_until!(":"), from_utf8_slice) + >> take!(1) + >> rest: map_res!(is_not!("\r\n"), from_utf8_slice) + >> take!(1) + >> + (ExtTag { tag: tag, rest: rest }) ) ); named!(pub comment_tag, - chain!( - tag!("#") ~ text: map_res!(take_until_either_and_consume!("\r\n"), from_utf8_slice), - || text + do_parse!( + tag!("#") >> text: map_res!(is_not!("\r\n"), from_utf8_slice) + >> take!(1) + >> (text) ) ); @@ -467,7 +483,7 @@ named!(pub comment_tag, named!(pub key_value_pairs(&[u8]) -> HashMap, map!( - many0!(chain!(space? ~ k:key_value_pair,|| k)) + many0!(do_parse!(space0 >> k:key_value_pair >> (k) )) , |pairs: Vec<(String, String)>| { pairs.into_iter().collect() @@ -476,13 +492,14 @@ named!(pub key_value_pairs(&[u8]) -> HashMap, ); named!(pub key_value_pair(&[u8]) -> (String, String), - chain!( + do_parse!( peek!(none_of!("\r\n")) - ~ left: map_res!(take_until_and_consume!("="), from_utf8_slice) - ~ right: alt!(quoted | unquoted) - ~ char!(',')? - , - || (left, right) + >> left: map_res!(take_until!("="), from_utf8_slice) + >> take!(1) + >> right: alt!(quoted | unquoted) + >> opt!(char!(',')) + >> + (left, right) ) ); @@ -491,31 +508,35 @@ named!(pub quoted, ); named!(pub unquoted, - map_res!(take_until_either!(",\r\n"), from_utf8_slice) + map_res!(is_not!(",\r\n"), from_utf8_slice) ); named!(pub consume_line, - map_res!(take_until_either_and_consume!("\r\n"), from_utf8_slice) + do_parse!( + line: map_res!(is_not!("\r\n"), from_utf8_slice) + >> line_ending + >> (line) + ) ); named!(pub number, - map_res!(map_res!(digit, str::from_utf8), str::FromStr::from_str) + map_res!(map_res!(digit1, str::from_utf8), str::FromStr::from_str) ); named!(pub byte_range_val, - chain!( + do_parse!( n: number - ~ o: opt!(chain!(char!('@') ~ n:number,||n)) - , - || ByteRange { length: n, offset: o } + >> o: opt!(do_parse!(char!('@') >> n:number >> (n) )) >> + (ByteRange { length: n, offset: o }) ) ); named!(pub float, - chain!( - left: map_res!(digit, str::from_utf8) - ~ right_opt: opt!(chain!(char!('.') ~ d:map_res!(digit, str::from_utf8),|| d)), - || + do_parse!( + left: map_res!(digit1, str::from_utf8) + >> right_opt: opt!(do_parse!(char!('.') >> d:map_res!(digit1, str::from_utf8) >> (d) )) + >> + ( match right_opt { Some(right) => { let mut num = String::from(left); @@ -524,7 +545,7 @@ named!(pub float, num.parse().unwrap() }, None => left.parse().unwrap(), - } + }) ) ); diff --git a/src/playlist.rs b/src/playlist.rs index 59030b1..fba1615 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -14,13 +14,13 @@ macro_rules! write_some_attribute_quoted { ($w:expr, $tag:expr, $o:expr) => ( if let &Some(ref v) = $o { write!($w, "{}=\"{}\"", $tag, v) } else { Ok(()) } ); -} +} macro_rules! write_some_attribute { ($w:expr, $tag:expr, $o:expr) => ( if let &Some(ref v) = $o { write!($w, "{}={}", $tag, v) } else { Ok(()) } ); -} +} macro_rules! bool_default_false { ($optional:expr) => ( @@ -29,7 +29,7 @@ macro_rules! bool_default_false { Some(_) | None => false, } ); -} +} /// [Playlist](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.1), /// can either be a `MasterPlaylist` or a `MediaPlaylist`. @@ -78,7 +78,6 @@ impl MasterPlaylist { let mut alternatives = vec![]; while let Some(tag) = tags.pop() { - match tag { MasterPlaylistTag::Version(v) => { master_playlist.version = v; @@ -138,7 +137,7 @@ impl MasterPlaylist { if let Some(ref start) = self.start { start.write_to(w)?; } - if self.independent_segments { + if self.independent_segments { writeln!(w, "#EXT-X-INDEPENDENT-SEGMENTS")?; } @@ -204,7 +203,7 @@ impl VariantStream { pub fn write_to(&self, w: &mut T) -> std::io::Result<()> { for alternative in &self.alternatives { - alternative.write_to(w)?; + alternative.write_to(w)?; } if self.is_i_frame { @@ -331,7 +330,7 @@ impl fmt::Display for AlternativeMediaType { &AlternativeMediaType::Subtitles => "SUBTITLES", &AlternativeMediaType::ClosedCaptions => "CLOSEDCAPTIONS", }) - } + } } @@ -420,7 +419,7 @@ impl MediaPlaylist { let mut map = None; while let Some(tag) = tags.pop() { - + match tag { MediaPlaylistTag::Version(v) => { media_playlist.version = v; @@ -496,25 +495,25 @@ impl MediaPlaylist { writeln!(w, "#EXT-X-VERSION:{}", self.version)?; writeln!(w, "#EXT-X-TARGETDURATION:{}", self.target_duration)?; - if self.media_sequence != 0 { + if self.media_sequence != 0 { writeln!(w, "#EXT-X-MEDIA-SEQUENCE:{}", self.media_sequence)?; } if self.discontinuity_sequence != 0 { writeln!(w, "#EXT-X-DISCONTINUITY-SEQUENCE:{}", self.discontinuity_sequence)?; } - if self.end_list { - writeln!(w, "#EXT-X-ENDLIST")?; + if self.end_list { + writeln!(w, "#EXT-X-ENDLIST")?; } if let Some(ref v) = self.playlist_type { writeln!(w, "#EXT-X-PLAYLIST-TYPE:{}", v)?; } - if self.i_frames_only { + if self.i_frames_only { writeln!(w, "#EXT-X-I-FRAMES-ONLY")?; } if let Some(ref start) = self.start { start.write_to(w)?; } - if self.independent_segments { + if self.independent_segments { writeln!(w, "#EXT-X-INDEPENDENT-SEGMENTS")?; } for segment in &self.segments { @@ -551,7 +550,7 @@ impl fmt::Display for MediaPlaylistType { &MediaPlaylistType::Event => "EVENT", &MediaPlaylistType::Vod => "VOD", }) - } + } } impl Default for MediaPlaylistType { @@ -599,8 +598,8 @@ impl MediaSegment { byte_range.write_value_to(w)?; write!(w, "\n")?; } - if self.discontinuity { - writeln!(w, "{}", "#EXT-X-DISCONTINUITY")?; + if self.discontinuity { + writeln!(w, "{}", "#EXT-X-DISCONTINUITY")?; } if let Some(ref key) = self.key { write!(w, "#EXT-X-KEY:")?; @@ -617,12 +616,14 @@ impl MediaSegment { } if let Some(ref v) = self.daterange { writeln!(w, "#EXT-X-DATERANGE:{}", v)?; - } + } write!(w, "#EXTINF:{},", self.duration)?; if let Some(ref v) = self.title { writeln!(w, "{}", v)?; + } else { + write!(w, "\n")?; } writeln!(w, "{}", self.uri) @@ -658,7 +659,7 @@ impl Key { } } - pub fn write_attributes_to(&self, w: &mut T) -> std::io::Result<()> { + pub fn write_attributes_to(&self, w: &mut T) -> std::io::Result<()> { write!(w, "METHOD={}", self.method)?; write_some_attribute_quoted!(w, ",URI", &self.uri)?; write_some_attribute!(w, ",IV", &self.iv)?; @@ -734,7 +735,7 @@ impl From for ByteRange { impl<'a> From<&'a str> for ByteRange { fn from(s: &'a str) -> Self { match byte_range_val(s.as_bytes()) { - IResult::Done(_, br) => br, + IResult::Ok((_, br)) => br, _ => panic!("Should not happen"), } } diff --git a/tests/lib.rs b/tests/lib.rs index f838471..4990398 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -41,7 +41,7 @@ fn print_parse_playlist_test(playlist_name: &str) -> bool { println!("Parsing playlist file: {:?}", playlist_name); let parsed = parse_playlist(input.as_bytes()); - if let IResult::Done(i,o) = parsed { + if let Result::Ok((i,o)) = parsed { println!("{:?}", o); true } @@ -116,9 +116,9 @@ fn playlist_types() { let input = getm3u(path); let is_master = is_master_playlist(input.as_bytes()); - assert!(path.to_lowercase().contains("master") == is_master); - println!("{:?} = {:?}", path, is_master); + + assert!(path.to_lowercase().contains("master") == is_master); } } @@ -146,7 +146,7 @@ fn test_key_value_pairs_trailing_equals() { fn test_key_value_pairs_multiple_quoted_values() { assert_eq!( key_value_pairs(b"BANDWIDTH=86000,URI=\"low/iframe.m3u8\",PROGRAM-ID=1,RESOLUTION=\"1x1\",VIDEO=1\nrest"), - IResult::Done( + Result::Ok(( "\nrest".as_bytes(), vec![ ("BANDWIDTH".to_string(), "86000".to_string()), @@ -155,7 +155,7 @@ fn test_key_value_pairs_multiple_quoted_values() { ("RESOLUTION".to_string(), "1x1".to_string()), ("VIDEO".to_string(), "1".to_string()) ].into_iter().collect::>() - ) + )) ); } @@ -176,10 +176,10 @@ fn test_key_value_pairs() { fn test_key_value_pair() { assert_eq!( key_value_pair(b"PROGRAM-ID=1,rest"), - IResult::Done( + Result::Ok(( "rest".as_bytes(), ("PROGRAM-ID".to_string(), "1".to_string()) - ) + )) ); } @@ -187,7 +187,7 @@ fn test_key_value_pair() { fn comment() { assert_eq!( comment_tag(b"#Hello\nxxx"), - IResult::Done("xxx".as_bytes(), "Hello".to_string()) + Result::Ok(("xxx".as_bytes(), "Hello".to_string())) ); } @@ -195,21 +195,39 @@ fn comment() { fn quotes() { assert_eq!( quoted(b"\"value\"rest"), - IResult::Done("rest".as_bytes(), "value".to_string()) + Result::Ok(("rest".as_bytes(), "value".to_string())) ); } #[test] -fn consume_empty_line() { - let line = consume_line(b"\r\nrest"); - println!("{:?}", line); +fn consume_line_empty() { + assert_eq!( + consume_line(b"\r\nrest"), + Result::Err(nom::Err::Error(("\r\nrest".as_bytes(), nom::error::ErrorKind::IsNot))) + ); +} + +#[test] +fn consume_line_n() { + assert_eq!( + consume_line(b"before\nrest"), + Result::Ok(("rest".as_bytes(), "before".into())) + ); +} + +#[test] +fn consume_line_rn() { + assert_eq!( + consume_line(b"before\r\nrest"), + Result::Ok(("rest".as_bytes(), "before".into())) + ); } #[test] fn float_() { assert_eq!( float(b"33.22rest"), - IResult::Done("rest".as_bytes(), 33.22f32) + Result::Ok(("rest".as_bytes(), 33.22f32)) ); } @@ -217,7 +235,7 @@ fn float_() { fn float_no_decimal() { assert_eq!( float(b"33rest"), - IResult::Done("rest".as_bytes(), 33f32) + Result::Ok(("rest".as_bytes(), 33f32)) ); } @@ -225,7 +243,7 @@ fn float_no_decimal() { fn float_should_ignore_trailing_dot() { assert_eq!( float(b"33.rest"), - IResult::Done(".rest".as_bytes(), 33f32) + Result::Ok((".rest".as_bytes(), 33f32)) ); } @@ -233,7 +251,7 @@ fn float_should_ignore_trailing_dot() { fn parse_duration_title() { assert_eq!( duration_title_tag(b"2.002,title\nrest"), - IResult::Done("rest".as_bytes(), (2.002f32, Some("title".to_string()))) + Result::Ok(("rest".as_bytes(), (2.002f32, Some("title".to_string())))) ); }