diff --git a/examples/simple.rs b/examples/simple.rs index da1524f..3774d6b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,7 +1,7 @@ -extern crate nom; extern crate m3u8_rs; +extern crate nom; -use m3u8_rs::playlist::{Playlist}; +use m3u8_rs::playlist::Playlist; use std::io::Read; fn main() { @@ -14,6 +14,6 @@ fn main() { match parsed { Ok(Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl), Ok(Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl), - Err(e) => println!("Error: {:?}", e) + Err(e) => println!("Error: {:?}", e), } } diff --git a/examples/with_nom_result.rs b/examples/with_nom_result.rs index 8e73c9e..cbe8d06 100644 --- a/examples/with_nom_result.rs +++ b/examples/with_nom_result.rs @@ -1,7 +1,7 @@ -extern crate nom; extern crate m3u8_rs; +extern crate nom; -use m3u8_rs::playlist::{Playlist}; +use m3u8_rs::playlist::Playlist; use std::io::Read; fn main() { @@ -13,7 +13,7 @@ fn main() { let playlist = match parsed { Result::Ok((i, playlist)) => playlist, - Result::Err(e) => panic!("Parsing error: \n{}", e), + Result::Err(e) => panic!("Parsing error: \n{}", e), }; match playlist { @@ -32,6 +32,6 @@ fn main_alt() { match parsed { 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), + Result::Err(e) => panic!("Parsing error: \n{}", e), } } diff --git a/src/parser.rs b/src/parser.rs index 064a85b..09f8f23 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -83,19 +83,21 @@ extern crate nom; pub mod playlist; -use self::nom::character::complete::{digit1, multispace0, space0 }; -use self::nom::{IResult}; -use self::nom::{ delimited,none_of,peek,is_a,is_not,complete,terminated,tag, - alt,do_parse,opt,named,map,map_res,eof,many0,take,take_until,char}; -use self::nom::combinator::map; +use self::nom::character::complete::{digit1, multispace0, space0}; use self::nom::character::complete::{line_ending, not_line_ending}; -use std::str; -use std::f32; -use std::string; -use std::str::FromStr; -use std::result::Result; -use std::collections::HashMap; +use self::nom::combinator::map; +use self::nom::IResult; +use self::nom::{ + alt, char, complete, delimited, do_parse, eof, is_a, is_not, many0, map, map_res, named, + none_of, opt, peek, tag, take, take_until, terminated, +}; use playlist::*; +use std::collections::HashMap; +use std::f32; +use std::result::Result; +use std::str; +use std::str::FromStr; +use std::string; /// Parse an m3u8 playlist. /// @@ -124,7 +126,7 @@ use playlist::*; pub fn parse_playlist(input: &[u8]) -> IResult<&[u8], Playlist> { match is_master_playlist(input) { true => map(parse_master_playlist, Playlist::MasterPlaylist)(input), - false =>map(parse_media_playlist, Playlist::MediaPlaylist)(input), + false => map(parse_media_playlist, Playlist::MediaPlaylist)(input), } } @@ -163,7 +165,9 @@ pub fn parse_master_playlist(input: &[u8]) -> IResult<&[u8], MasterPlaylist> { } /// Parse input as a master playlist -pub fn parse_master_playlist_res(input: &[u8]) -> Result> { +pub fn parse_master_playlist_res( + input: &[u8], +) -> Result> { let parse_result = parse_master_playlist(input); match parse_result { IResult::Ok((_, playlist)) => Ok(playlist), @@ -177,7 +181,9 @@ pub fn parse_media_playlist(input: &[u8]) -> IResult<&[u8], MediaPlaylist> { } /// Parse input as a media playlist -pub fn parse_media_playlist_res(input: &[u8]) -> Result> { +pub fn parse_media_playlist_res( + input: &[u8], +) -> Result> { let parse_result = parse_media_playlist(input); match parse_result { IResult::Ok((_, playlist)) => Ok(playlist), @@ -199,7 +205,6 @@ pub fn is_master_playlist(input: &[u8]) -> bool { /// - Some(true, tagstring): Line contains a master playlist tag /// - Some(false, tagstring): Line contains a media playlist tag pub fn contains_master_tag(input: &[u8]) -> Option<(bool, String)> { - let mut is_master_opt = None; let mut current_input: &[u8] = input; @@ -253,16 +258,20 @@ 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> { - 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 } ) +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 { @@ -280,23 +289,22 @@ pub enum MasterPlaylistTag { } pub fn master_playlist_tag(input: &[u8]) -> IResult<&[u8], MasterPlaylistTag> { - alt!(input, - map!(m3u_tag, MasterPlaylistTag::M3U) - | map!(version_tag, MasterPlaylistTag::Version) - - | map!(variant_stream_tag, MasterPlaylistTag::VariantStream) - | map!(variant_i_frame_stream_tag, MasterPlaylistTag::VariantStream) - | map!(alternative_media_tag, MasterPlaylistTag::AlternativeMedia) - | map!(session_data_tag, MasterPlaylistTag::SessionData) - | map!(session_key_tag, MasterPlaylistTag::SessionKey) - | map!(start_tag, MasterPlaylistTag::Start) - | map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| MasterPlaylistTag::IndependentSegments) - - | map!(ext_tag, MasterPlaylistTag::Unknown) - - | map!(comment_tag, MasterPlaylistTag::Comment) - - | map!(consume_line, MasterPlaylistTag::Uri) + alt!( + input, + map!(m3u_tag, MasterPlaylistTag::M3U) + | map!(version_tag, MasterPlaylistTag::Version) + | map!(variant_stream_tag, MasterPlaylistTag::VariantStream) + | map!(variant_i_frame_stream_tag, MasterPlaylistTag::VariantStream) + | map!(alternative_media_tag, MasterPlaylistTag::AlternativeMedia) + | map!(session_data_tag, MasterPlaylistTag::SessionData) + | map!(session_key_tag, MasterPlaylistTag::SessionKey) + | map!(start_tag, MasterPlaylistTag::Start) + | map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| { + MasterPlaylistTag::IndependentSegments + }) + | map!(ext_tag, MasterPlaylistTag::Unknown) + | map!(comment_tag, MasterPlaylistTag::Comment) + | map!(consume_line, MasterPlaylistTag::Uri) ) } @@ -341,7 +349,6 @@ pub fn master_playlist_from_tags(mut tags: Vec) -> MasterPlay master_playlist } - named!(pub variant_stream_tag, do_parse!(tag!("#EXT-X-STREAM-INF:") >> attributes: key_value_pairs >> ( VariantStream::from_hashmap(attributes, false))) @@ -373,10 +380,16 @@ named!(pub session_key_tag, // ----------------------------------------------------------------------------------------------- pub fn parse_media_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec> { - 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 } ) + 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 + }) ) } @@ -398,20 +411,35 @@ pub enum MediaPlaylistTag { } pub fn media_playlist_tag(input: &[u8]) -> IResult<&[u8], MediaPlaylistTag> { - alt!(input, - map!(m3u_tag, MediaPlaylistTag::M3U) - | map!(version_tag, MediaPlaylistTag::Version) - - | 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) - | map!(tag!("#EXT-X-ENDLIST"), |_| MediaPlaylistTag::EndList) - - | map!(media_segment_tag, MediaPlaylistTag::Segment) + alt!( + input, + map!(m3u_tag, MediaPlaylistTag::M3U) + | map!(version_tag, MediaPlaylistTag::Version) + | 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 + }) + | map!(tag!("#EXT-X-ENDLIST"), |_| MediaPlaylistTag::EndList) + | map!(media_segment_tag, MediaPlaylistTag::Segment) ) } @@ -422,7 +450,6 @@ pub fn media_playlist_from_tags(mut tags: Vec) -> MediaPlaylis let mut map = None; while let Some(tag) = tags.pop() { - match tag { MediaPlaylistTag::Version(v) => { media_playlist.version = v; @@ -451,45 +478,43 @@ pub fn media_playlist_from_tags(mut tags: Vec) -> MediaPlaylis MediaPlaylistTag::IndependentSegments => { media_playlist.independent_segments = true; } - MediaPlaylistTag::Segment(segment_tag) => { - match segment_tag { - SegmentTag::Extinf(d, t) => { - next_segment.duration = d; - next_segment.title = t; - } - SegmentTag::ByteRange(b) => { - next_segment.byte_range = Some(b); - } - SegmentTag::Discontinuity => { - next_segment.discontinuity = true; - } - SegmentTag::Key(k) => { - encryption_key = Some(k); - } - SegmentTag::Map(m) => { - map = Some(m); - } - SegmentTag::ProgramDateTime(d) => { - next_segment.program_date_time = Some(d); - } - SegmentTag::DateRange(d) => { - next_segment.daterange = Some(d); - } - SegmentTag::Unknown(t) => { - next_segment.unknown_tags.push(t); - } - SegmentTag::Uri(u) => { - next_segment.key = encryption_key.clone(); - next_segment.map = map.clone(); - next_segment.uri = u; - media_playlist.segments.push(next_segment); - next_segment = MediaSegment::empty(); - encryption_key = None; - map = None; - } - _ => (), + MediaPlaylistTag::Segment(segment_tag) => match segment_tag { + SegmentTag::Extinf(d, t) => { + next_segment.duration = d; + next_segment.title = t; } - } + SegmentTag::ByteRange(b) => { + next_segment.byte_range = Some(b); + } + SegmentTag::Discontinuity => { + next_segment.discontinuity = true; + } + SegmentTag::Key(k) => { + encryption_key = Some(k); + } + SegmentTag::Map(m) => { + map = Some(m); + } + SegmentTag::ProgramDateTime(d) => { + next_segment.program_date_time = Some(d); + } + SegmentTag::DateRange(d) => { + next_segment.daterange = Some(d); + } + SegmentTag::Unknown(t) => { + next_segment.unknown_tags.push(t); + } + SegmentTag::Uri(u) => { + next_segment.key = encryption_key.clone(); + next_segment.map = map.clone(); + next_segment.uri = u; + media_playlist.segments.push(next_segment); + next_segment = MediaSegment::empty(); + encryption_key = None; + map = None; + } + _ => (), + }, _ => (), } } @@ -527,19 +552,34 @@ pub enum SegmentTag { } pub fn media_segment_tag(input: &[u8]) -> IResult<&[u8], SegmentTag> { - alt!(input, - 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!(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) - - | map!(consume_line, SegmentTag::Uri) + alt!( + input, + 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!( + 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) + | map!(consume_line, SegmentTag::Uri) ) } diff --git a/src/playlist.rs b/src/playlist.rs index 9bd2f17..6e196ce 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -3,32 +3,40 @@ //! The main type here is the `Playlist` enum. //! Which is either a `MasterPlaylist` or a `MediaPlaylist`. -use std::io::Write; use std::collections::HashMap; -use std::str::FromStr; -use std::fmt; use std::f32; +use std::fmt; use std::fmt::Display; +use std::io::Write; +use std::str::FromStr; macro_rules! write_some_attribute_quoted { - ($w:expr, $tag:expr, $o:expr) => ( - if let &Some(ref v) = $o { write!($w, "{}=\"{}\"", $tag, v) } else { Ok(()) } - ); + ($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(()) } - ); + ($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) => ( + ($optional:expr) => { match $optional { Some(ref s) if s == "YES" => true, Some(_) | None => false, } - ); + }; } /// [Playlist](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.1), @@ -74,13 +82,12 @@ pub struct MasterPlaylist { } impl MasterPlaylist { - pub fn get_newest_variant(&mut self) -> Option<&mut VariantStream> { self.variants.iter_mut().rev().find(|v| !v.is_i_frame) } pub fn write_to(&self, w: &mut T) -> std::io::Result<()> { - writeln!(w, "{}" ,"#EXTM3U")?; + writeln!(w, "{}", "#EXTM3U")?; writeln!(w, "#EXT-X-VERSION:{}", self.version)?; for alternative in &self.alternatives { @@ -147,7 +154,6 @@ pub struct VariantStream { } impl VariantStream { - pub fn from_hashmap(mut attrs: HashMap, is_i_frame: bool) -> VariantStream { VariantStream { is_i_frame: is_i_frame, @@ -166,14 +172,11 @@ impl VariantStream { } pub fn write_to(&self, w: &mut T) -> std::io::Result<()> { - - if self.is_i_frame { write!(w, "#EXT-X-I-FRAME-STREAM-INF:")?; self.write_stream_inf_common_attributes(w)?; writeln!(w, ",URI=\"{}\"", self.uri) - } - else { + } else { write!(w, "#EXT-X-STREAM-INF:")?; self.write_stream_inf_common_attributes(w)?; write_some_attribute_quoted!(w, ",AUDIO", &self.audio)?; @@ -222,10 +225,10 @@ pub struct AlternativeMedia { } impl AlternativeMedia { - pub fn from_hashmap(mut attrs: HashMap) -> AlternativeMedia { AlternativeMedia { - media_type: attrs.get("TYPE") + media_type: attrs + .get("TYPE") .and_then(|s| AlternativeMediaType::from_str(s).ok()) .unwrap_or_else(Default::default), uri: attrs.remove("URI"), @@ -250,9 +253,15 @@ impl AlternativeMedia { write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?; write_some_attribute_quoted!(w, ",ASSOC-LANGUAGE", &self.assoc_language)?; write!(w, ",NAME=\"{}\"", self.name)?; - if self.default { write!(w, ",DEFAULT=YES")?; } - if self.autoselect { write!(w, ",AUTOSELECT=YES")?; } - if self.forced { write!(w, ",FORCED=YES")?; } + if self.default { + write!(w, ",DEFAULT=YES")?; + } + if self.autoselect { + write!(w, ",AUTOSELECT=YES")?; + } + if self.forced { + write!(w, ",FORCED=YES")?; + } write_some_attribute_quoted!(w, ",INSTREAM-ID", &self.instream_id)?; write_some_attribute_quoted!(w, ",CHARACTERISTICS", &self.characteristics)?; write_some_attribute_quoted!(w, ",CHANNELS", &self.channels)?; @@ -277,7 +286,10 @@ impl FromStr for AlternativeMediaType { "VIDEO" => Ok(AlternativeMediaType::Video), "SUBTITLES" => Ok(AlternativeMediaType::Subtitles), "CLOSEDCAPTIONS" => Ok(AlternativeMediaType::ClosedCaptions), - _ => Err(format!("Unable to create AlternativeMediaType from {:?}", s)), + _ => Err(format!( + "Unable to create AlternativeMediaType from {:?}", + s + )), } } } @@ -290,16 +302,19 @@ impl Default for AlternativeMediaType { impl fmt::Display for AlternativeMediaType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", match self { - &AlternativeMediaType::Audio => "AUDIO", - &AlternativeMediaType::Video => "VIDEO", - &AlternativeMediaType::Subtitles => "SUBTITLES", - &AlternativeMediaType::ClosedCaptions => "CLOSEDCAPTIONS", - }) + write!( + f, + "{}", + match self { + &AlternativeMediaType::Audio => "AUDIO", + &AlternativeMediaType::Video => "VIDEO", + &AlternativeMediaType::Subtitles => "SUBTITLES", + &AlternativeMediaType::ClosedCaptions => "CLOSEDCAPTIONS", + } + ) } } - /// [`#EXT-X-SESSION-KEY:`] /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5) /// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists @@ -319,7 +334,7 @@ impl SessionKey { #[derive(Debug, PartialEq, Clone)] pub enum SessionDataField { Value(String), - Uri(String) + Uri(String), } /// [`#EXT-X-SESSION-DATA:`] @@ -337,7 +352,7 @@ impl SessionData { pub fn from_hashmap(mut attrs: HashMap) -> Result { let data_id = match attrs.remove("DATA-ID") { Some(data_id) => data_id, - None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string()) + None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string()), }; let value = attrs.remove("VALUE"); @@ -348,8 +363,18 @@ impl SessionData { let field = match (value, uri) { (Some(value), None) => SessionDataField::Value(value), (None, Some(uri)) => SessionDataField::Uri(uri), - (Some(_), Some(_)) => return Err(format!["EXT-X-SESSION-DATA tag {} contains both a value and a uri", data_id]), - (None, None) => return Err(format!["EXT-X-SESSION-DATA tag {} must contain either a value or a uri", data_id]), + (Some(_), Some(_)) => { + return Err(format![ + "EXT-X-SESSION-DATA tag {} contains both a value and a uri", + data_id + ]) + } + (None, None) => { + return Err(format![ + "EXT-X-SESSION-DATA tag {} must contain either a value or a uri", + data_id + ]) + } }; Ok(SessionData { @@ -363,8 +388,8 @@ impl SessionData { write!(w, "#EXT-X-SESSION-DATA:")?; write!(w, "DATA-ID=\"{}\"", self.data_id)?; match &self.field { - SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?, - SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?, + SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?, + SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?, }; write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?; write!(w, "\n") @@ -402,9 +427,8 @@ pub struct MediaPlaylist { } impl MediaPlaylist { - pub fn write_to(&self, w: &mut T) -> std::io::Result<()> { - writeln!(w, "{}" ,"#EXTM3U")?; + writeln!(w, "{}", "#EXTM3U")?; writeln!(w, "#EXT-X-VERSION:{}", self.version)?; writeln!(w, "#EXT-X-TARGETDURATION:{}", self.target_duration)?; @@ -412,7 +436,11 @@ impl MediaPlaylist { writeln!(w, "#EXT-X-MEDIA-SEQUENCE:{}", self.media_sequence)?; } if self.discontinuity_sequence != 0 { - writeln!(w, "#EXT-X-DISCONTINUITY-SEQUENCE:{}", self.discontinuity_sequence)?; + writeln!( + w, + "#EXT-X-DISCONTINUITY-SEQUENCE:{}", + self.discontinuity_sequence + )?; } if self.end_list { writeln!(w, "#EXT-X-ENDLIST")?; @@ -459,10 +487,14 @@ impl FromStr for MediaPlaylistType { impl fmt::Display for MediaPlaylistType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", match self { - &MediaPlaylistType::Event => "EVENT", - &MediaPlaylistType::Vod => "VOD", - }) + write!( + f, + "{}", + match self { + &MediaPlaylistType::Event => "EVENT", + &MediaPlaylistType::Vod => "VOD", + } + ) } } @@ -507,7 +539,6 @@ impl MediaSegment { } pub fn write_to(&self, w: &mut T) -> std::io::Result<()> { - if let Some(ref byte_range) = self.byte_range { write!(w, "#EXT-X-BYTERANGE:")?; byte_range.write_value_to(w)?; @@ -613,7 +644,6 @@ impl Map { } } - /// [`#EXT-X-BYTERANGE:[@]`] /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.2) /// @@ -636,7 +666,6 @@ impl ByteRange { } } - /// [`#EXT-X-DATERANGE:`] /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.7) /// @@ -717,7 +746,10 @@ mod test { let mut output = Vec::new(); write!(output, "{}", cue_out_tag).unwrap(); - assert_eq!(std::str::from_utf8(output.as_slice()).unwrap(), "#EXT-X-CUE-OUT:DURATION=30") + assert_eq!( + std::str::from_utf8(output.as_slice()).unwrap(), + "#EXT-X-CUE-OUT:DURATION=30" + ) } #[test] @@ -730,6 +762,9 @@ mod test { let mut output = Vec::new(); write!(output, "{}", cue_in_tag).unwrap(); - assert_eq!(std::str::from_utf8(output.as_slice()).unwrap(), "#EXT-X-CUE-IN") + assert_eq!( + std::str::from_utf8(output.as_slice()).unwrap(), + "#EXT-X-CUE-IN" + ) } } diff --git a/tests/lib.rs b/tests/lib.rs index 5fecc42..d29492b 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,20 +1,21 @@ #![allow(unused_variables, unused_imports, dead_code)] -extern crate nom; extern crate m3u8_rs; +extern crate nom; -use std::fs; -use std::path; -use m3u8_rs::*; use m3u8_rs::playlist::*; -use std::io::Read; -use std::fs::File; +use m3u8_rs::*; use nom::*; use std::collections::HashMap; +use std::fs; +use std::fs::File; +use std::io::Read; +use std::path; fn all_sample_m3u_playlists() -> Vec { let path: std::path::PathBuf = ["sample-playlists"].iter().collect(); - fs::read_dir(path.to_str().unwrap()).unwrap() + fs::read_dir(path.to_str().unwrap()) + .unwrap() .filter_map(Result::ok) .map(|dir| dir.path()) .filter(|path| path.extension().map_or(false, |ext| ext == "m3u8")) @@ -41,11 +42,10 @@ fn print_parse_playlist_test(playlist_name: &str) -> bool { println!("Parsing playlist file: {:?}", playlist_name); let parsed = parse_playlist(input.as_bytes()); - if let Result::Ok((i,o)) = parsed { + if let Result::Ok((i, o)) = parsed { println!("{:?}", o); true - } - else { + } else { println!("Parsing failed:\n {:?}", parsed); false } @@ -56,21 +56,23 @@ fn playlist_master_with_alternatives() { assert!(print_parse_playlist_test("master-with-alternatives.m3u8")); } - #[test] fn playlist_master_with_alternatives_2_3() { assert!(print_parse_playlist_test("master-with-alternatives-2.m3u8")); } - #[test] fn playlist_master_with_i_frame_stream_inf() { - assert!(print_parse_playlist_test("master-with-i-frame-stream-inf.m3u8")); + assert!(print_parse_playlist_test( + "master-with-i-frame-stream-inf.m3u8" + )); } #[test] fn playlist_master_with_multiple_codecs() { - assert!(print_parse_playlist_test("master-with-multiple-codecs.m3u8")); + assert!(print_parse_playlist_test( + "master-with-multiple-codecs.m3u8" + )); } // -- Media playlists @@ -82,7 +84,9 @@ fn playlist_media_standard() { #[test] fn playlist_media_without_segments() { - assert!(print_parse_playlist_test("media-playlist-without-segments.m3u8")); + assert!(print_parse_playlist_test( + "media-playlist-without-segments.m3u8" + )); } #[test] @@ -102,7 +106,9 @@ fn playlist_media_with_scte35() { #[test] fn playlist_media_with_scte35_1() { - assert!(print_parse_playlist_test("media-playlist-with-scte35-1.m3u8")); + assert!(print_parse_playlist_test( + "media-playlist-with-scte35-1.m3u8" + )); } // ----------------------------------------------------------------------------------------------- @@ -110,17 +116,23 @@ fn playlist_media_with_scte35_1() { #[test] fn playlist_not_ending_in_newline_master() { - assert!(print_parse_playlist_test("master-not-ending-in-newline.m3u8")); + assert!(print_parse_playlist_test( + "master-not-ending-in-newline.m3u8" + )); } #[test] fn playlist_not_ending_in_newline_master1() { - assert!(print_parse_playlist_test("master-not-ending-in-newline-1.m3u8")); + assert!(print_parse_playlist_test( + "master-not-ending-in-newline-1.m3u8" + )); } #[test] fn playlist_not_ending_in_newline_media() { - assert!(print_parse_playlist_test("media-not-ending-in-newline.m3u8")); + assert!(print_parse_playlist_test( + "media-not-ending-in-newline.m3u8" + )); } // ----------------------------------------------------------------------------------------------- @@ -164,7 +176,6 @@ fn variant_stream() { println!("{:?}", result); } - // ----------------------------------------------------------------------------------------------- // Other @@ -203,7 +214,6 @@ fn test_key_value_pairs() { println!("{:?}\n\n", res); } - #[test] fn test_key_value_pair() { assert_eq!( @@ -219,7 +229,13 @@ fn test_key_value_pair() { fn ext_with_value() { assert_eq!( ext_tag(b"#EXT-X-CUE-OUT:DURATION=30\nxxx"), - Result::Ok((b"xxx".as_bytes(), ExtTag { tag: "X-CUE-OUT".into(), rest: Some("DURATION=30".into()) })) + Result::Ok(( + b"xxx".as_bytes(), + ExtTag { + tag: "X-CUE-OUT".into(), + rest: Some("DURATION=30".into()) + } + )) ); } @@ -227,7 +243,13 @@ fn ext_with_value() { fn ext_without_value() { assert_eq!( ext_tag(b"#EXT-X-CUE-IN\nxxx"), - Result::Ok((b"xxx".as_bytes(), ExtTag { tag: "X-CUE-IN".into(), rest: None })) + Result::Ok(( + b"xxx".as_bytes(), + ExtTag { + tag: "X-CUE-IN".into(), + rest: None + } + )) ); } @@ -280,18 +302,12 @@ fn float_() { #[test] fn float_no_decimal() { - assert_eq!( - float(b"33rest"), - Result::Ok(("rest".as_bytes(), 33f32)) - ); + assert_eq!(float(b"33rest"), Result::Ok(("rest".as_bytes(), 33f32))); } #[test] fn float_should_ignore_trailing_dot() { - assert_eq!( - float(b"33.rest"), - Result::Ok((".rest".as_bytes(), 33f32)) - ); + assert_eq!(float(b"33.rest"), Result::Ok((".rest".as_bytes(), 33f32))); } #[test] @@ -312,10 +328,12 @@ fn print_create_and_parse_playlist(playlist_original: &mut Playlist) -> Playlist let m3u8_str: &str = std::str::from_utf8(&utf8).unwrap(); let playlist_parsed = match *playlist_original { - Playlist::MasterPlaylist(_) => - Playlist::MasterPlaylist(parse_master_playlist_res(m3u8_str.as_bytes()).unwrap()), - Playlist::MediaPlaylist(_) => - Playlist::MediaPlaylist(parse_media_playlist_res(m3u8_str.as_bytes()).unwrap()), + Playlist::MasterPlaylist(_) => { + Playlist::MasterPlaylist(parse_master_playlist_res(m3u8_str.as_bytes()).unwrap()) + } + Playlist::MediaPlaylist(_) => { + Playlist::MediaPlaylist(parse_media_playlist_res(m3u8_str.as_bytes()).unwrap()) + } }; print!("\n\n---- utf8 result\n\n{}", m3u8_str); @@ -327,64 +345,57 @@ fn print_create_and_parse_playlist(playlist_original: &mut Playlist) -> Playlist #[test] fn create_and_parse_master_playlist_empty() { - let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist { ..Default::default() }); + let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist { + ..Default::default() + }); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original); assert_eq!(playlist_original, playlist_parsed); } #[test] fn create_and_parse_master_playlist_full() { - let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist { version: 6, - alternatives: vec! [ - AlternativeMedia { - media_type: AlternativeMediaType::Audio, - uri: Some("alt-media-uri".into()), - group_id: "group-id".into(), - language: Some("language".into()), - assoc_language: Some("assoc-language".into()), - name: "Xmedia".into(), - default: true, // Its absence indicates an implicit value of NO - autoselect: true, // Its absence indicates an implicit value of NO - forced: true, // Its absence indicates an implicit value of NO - instream_id: Some("instream_id".into()), - characteristics: Some("characteristics".into()), - channels: Some("channels".into()), - } - ], - variants: vec![ - VariantStream { - is_i_frame: false, - uri: "masterplaylist-uri".into(), - bandwidth: "10010010".into(), - average_bandwidth: Some("10010010".into()), - codecs: Some("TheCODEC".into()), - resolution: Some("1000x3000".into()), - frame_rate: Some("60".into()), - hdcp_level: Some("NONE".into()), - audio: Some("audio".into()), - video: Some("video".into()), - subtitles: Some("subtitles".into()), - closed_captions: Some("closed_captions".into()), - } - ], - session_data: vec![ - SessionData { - data_id: "****".into(), - field: SessionDataField::Value("%%%%".to_string()), - language: Some("SessionDataLanguage".into()), - } - ], - session_key: vec![ - SessionKey(Key { - method: "AES-128".into(), - uri: Some("https://secure.domain.com".into()), - iv: Some("0xb059217aa2649ce170b734".into()), - keyformat: Some("xXkeyformatXx".into()), - keyformatversions: Some("xXFormatVers".into()), - }) - ], + alternatives: vec![AlternativeMedia { + media_type: AlternativeMediaType::Audio, + uri: Some("alt-media-uri".into()), + group_id: "group-id".into(), + language: Some("language".into()), + assoc_language: Some("assoc-language".into()), + name: "Xmedia".into(), + default: true, // Its absence indicates an implicit value of NO + autoselect: true, // Its absence indicates an implicit value of NO + forced: true, // Its absence indicates an implicit value of NO + instream_id: Some("instream_id".into()), + characteristics: Some("characteristics".into()), + channels: Some("channels".into()), + }], + variants: vec![VariantStream { + is_i_frame: false, + uri: "masterplaylist-uri".into(), + bandwidth: "10010010".into(), + average_bandwidth: Some("10010010".into()), + codecs: Some("TheCODEC".into()), + resolution: Some("1000x3000".into()), + frame_rate: Some("60".into()), + hdcp_level: Some("NONE".into()), + audio: Some("audio".into()), + video: Some("video".into()), + subtitles: Some("subtitles".into()), + closed_captions: Some("closed_captions".into()), + }], + session_data: vec![SessionData { + data_id: "****".into(), + field: SessionDataField::Value("%%%%".to_string()), + language: Some("SessionDataLanguage".into()), + }], + session_key: vec![SessionKey(Key { + method: "AES-128".into(), + uri: Some("https://secure.domain.com".into()), + iv: Some("0xb059217aa2649ce170b734".into()), + keyformat: Some("xXkeyformatXx".into()), + keyformatversions: Some("xXFormatVers".into()), + })], start: Some(Start { time_offset: "123123123".into(), precise: Some("YES".into()), @@ -398,7 +409,9 @@ fn create_and_parse_master_playlist_full() { #[test] fn create_and_parse_media_playlist_empty() { - let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist { ..Default::default() }); + let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist { + ..Default::default() + }); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original); assert_eq!(playlist_original, playlist_parsed); } @@ -406,15 +419,13 @@ fn create_and_parse_media_playlist_empty() { #[test] fn create_and_parse_media_playlist_single_segment() { let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist { - segments: vec![ - MediaSegment { - uri: "20140311T113819-01-338559live.ts".into(), - duration: 2.002, - title: Some("hey".into()), - ..Default::default() - }, - ], - ..Default::default() + segments: vec![MediaSegment { + uri: "20140311T113819-01-338559live.ts".into(), + duration: 2.002, + title: Some("hey".into()), + ..Default::default() + }], + ..Default::default() }); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original); assert_eq!(playlist_original, playlist_parsed); @@ -422,7 +433,6 @@ fn create_and_parse_media_playlist_single_segment() { #[test] fn create_and_parse_media_playlist_full() { - let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist { version: 4, target_duration: 3.0, @@ -436,34 +446,36 @@ fn create_and_parse_media_playlist_full() { precise: Some("YES".into()), }), independent_segments: true, - segments: vec![ - MediaSegment { - uri: "20140311T113819-01-338559live.ts".into(), - duration: 2.002, - title: Some("338559".into()), - byte_range: Some(ByteRange { length: 137116, offset: Some(4559) }), - discontinuity: true, - key: Some(Key { - method: "AES-128".into(), - uri: Some("https://secure.domain.com".into()), - iv: Some("0xb059217aa2649ce170b734".into()), - keyformat: Some("xXkeyformatXx".into()), - keyformatversions: Some("xXFormatVers".into()), + segments: vec![MediaSegment { + uri: "20140311T113819-01-338559live.ts".into(), + duration: 2.002, + title: Some("338559".into()), + byte_range: Some(ByteRange { + length: 137116, + offset: Some(4559), + }), + discontinuity: true, + key: Some(Key { + method: "AES-128".into(), + uri: Some("https://secure.domain.com".into()), + iv: Some("0xb059217aa2649ce170b734".into()), + keyformat: Some("xXkeyformatXx".into()), + keyformatversions: Some("xXFormatVers".into()), + }), + map: Some(Map { + uri: "www.map-uri.com".into(), + byte_range: Some(ByteRange { + length: 137116, + offset: Some(4559), }), - map: Some(Map { - uri: "www.map-uri.com".into(), - byte_range: Some(ByteRange { length: 137116, offset: Some(4559) }), - }), - program_date_time: Some("broodlordinfestorgg".into()), - daterange: None, - unknown_tags: vec![ - ExtTag { - tag: "X-CUE-OUT".into(), - rest: Some("DURATION=2.002".into()) - } - ] - }, - ], + }), + program_date_time: Some("broodlordinfestorgg".into()), + daterange: None, + unknown_tags: vec![ExtTag { + tag: "X-CUE-OUT".into(), + rest: Some("DURATION=2.002".into()), + }], + }], }); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original); assert_eq!(playlist_original, playlist_parsed); @@ -472,7 +484,6 @@ fn create_and_parse_media_playlist_full() { // // Roundtrip - #[test] fn parsing_write_to_should_produce_the_same_structure() { for playlist in all_sample_m3u_playlists() { @@ -485,8 +496,11 @@ fn parsing_write_to_should_produce_the_same_structure() { let actual = parse_playlist_res(&written).unwrap(); assert_eq!( - expected, actual, + expected, + actual, "\n\nFailed parser input:\n\n{}\n\nOriginal input:\n\n{}", - std::str::from_utf8(&written).unwrap(), input); + std::str::from_utf8(&written).unwrap(), + input + ); } }