Merge pull request #33 from rafaelcaricio/apply-fmt-and-clippy

Apply cargo fmt and clippy suggestions
This commit is contained in:
rutgersc 2021-10-19 18:54:21 +02:00 committed by GitHub
commit 303d0ecfce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 399 additions and 312 deletions

View file

@ -1,7 +1,7 @@
extern crate nom;
extern crate m3u8_rs; extern crate m3u8_rs;
extern crate nom;
use m3u8_rs::playlist::{Playlist}; use m3u8_rs::playlist::Playlist;
use std::io::Read; use std::io::Read;
fn main() { fn main() {
@ -14,6 +14,6 @@ fn main() {
match parsed { match parsed {
Ok(Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl), Ok(Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl),
Ok(Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl), Ok(Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl),
Err(e) => println!("Error: {:?}", e) Err(e) => println!("Error: {:?}", e),
} }
} }

View file

@ -1,7 +1,7 @@
extern crate nom;
extern crate m3u8_rs; extern crate m3u8_rs;
extern crate nom;
use m3u8_rs::playlist::{Playlist}; use m3u8_rs::playlist::Playlist;
use std::io::Read; use std::io::Read;
fn main() { fn main() {
@ -13,7 +13,7 @@ fn main() {
let playlist = match parsed { let playlist = match parsed {
Result::Ok((i, playlist)) => playlist, Result::Ok((i, playlist)) => playlist,
Result::Err(e) => panic!("Parsing error: \n{}", e), Result::Err(e) => panic!("Parsing error: \n{}", e),
}; };
match playlist { match playlist {
@ -32,6 +32,6 @@ fn main_alt() {
match parsed { match parsed {
Result::Ok((i, Playlist::MasterPlaylist(pl))) => println!("Master playlist:\n{:?}", pl), Result::Ok((i, Playlist::MasterPlaylist(pl))) => println!("Master playlist:\n{:?}", pl),
Result::Ok((i, Playlist::MediaPlaylist(pl))) => println!("Media 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),
} }
} }

View file

@ -83,19 +83,21 @@ extern crate nom;
pub mod playlist; pub mod playlist;
use self::nom::character::complete::{digit1, multispace0, space0 }; 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::{line_ending, not_line_ending}; use self::nom::character::complete::{line_ending, not_line_ending};
use std::str; use self::nom::combinator::map;
use std::f32; use self::nom::IResult;
use std::string; use self::nom::{
use std::str::FromStr; alt, char, complete, delimited, do_parse, eof, is_a, is_not, many0, map, map_res, named,
use std::result::Result; none_of, opt, peek, tag, take, take_until, terminated,
use std::collections::HashMap; };
use playlist::*; 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. /// Parse an m3u8 playlist.
/// ///
@ -124,7 +126,7 @@ use playlist::*;
pub fn parse_playlist(input: &[u8]) -> IResult<&[u8], Playlist> { pub fn parse_playlist(input: &[u8]) -> IResult<&[u8], Playlist> {
match is_master_playlist(input) { match is_master_playlist(input) {
true => map(parse_master_playlist, Playlist::MasterPlaylist)(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 /// Parse input as a master playlist
pub fn parse_master_playlist_res(input: &[u8]) -> Result<MasterPlaylist, IResult<&[u8], MasterPlaylist>> { pub fn parse_master_playlist_res(
input: &[u8],
) -> Result<MasterPlaylist, IResult<&[u8], MasterPlaylist>> {
let parse_result = parse_master_playlist(input); let parse_result = parse_master_playlist(input);
match parse_result { match parse_result {
IResult::Ok((_, playlist)) => Ok(playlist), 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 /// Parse input as a media playlist
pub fn parse_media_playlist_res(input: &[u8]) -> Result<MediaPlaylist, IResult<&[u8], MediaPlaylist>> { pub fn parse_media_playlist_res(
input: &[u8],
) -> Result<MediaPlaylist, IResult<&[u8], MediaPlaylist>> {
let parse_result = parse_media_playlist(input); let parse_result = parse_media_playlist(input);
match parse_result { match parse_result {
IResult::Ok((_, playlist)) => Ok(playlist), 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(true, tagstring): Line contains a master playlist tag
/// - Some(false, tagstring): Line contains a media playlist tag /// - Some(false, tagstring): Line contains a media playlist tag
pub fn contains_master_tag(input: &[u8]) -> Option<(bool, String)> { pub fn contains_master_tag(input: &[u8]) -> Option<(bool, String)> {
let mut is_master_opt = None; let mut is_master_opt = None;
let mut current_input: &[u8] = input; let mut current_input: &[u8] = input;
@ -253,16 +258,20 @@ named!(pub is_master_playlist_tag_line(&[u8]) -> Option<(bool, String)>,
// Master Playlist Tags // Master Playlist Tags
// ----------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------
pub fn parse_master_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec<MasterPlaylistTag>> { pub fn parse_master_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec<MasterPlaylistTag>> {
do_parse!(input, do_parse!(
tags: many0!(complete!(do_parse!( m : master_playlist_tag >> multispace0 >> (m) ))) input,
>> opt!(eof!()) tags: many0!(complete!(do_parse!(
>> m: master_playlist_tag >> multispace0 >> (m)
( {let mut tags_rev: Vec<MasterPlaylistTag> = tags; tags_rev.reverse(); tags_rev } ) ))) >> opt!(eof!())
>> ({
let mut tags_rev: Vec<MasterPlaylistTag> = tags;
tags_rev.reverse();
tags_rev
})
) )
} }
/// Contains all the tags required to parse a master playlist. /// Contains all the tags required to parse a master playlist.
#[derive(Debug)] #[derive(Debug)]
pub enum MasterPlaylistTag { pub enum MasterPlaylistTag {
@ -280,23 +289,22 @@ pub enum MasterPlaylistTag {
} }
pub fn master_playlist_tag(input: &[u8]) -> IResult<&[u8], MasterPlaylistTag> { pub fn master_playlist_tag(input: &[u8]) -> IResult<&[u8], MasterPlaylistTag> {
alt!(input, alt!(
map!(m3u_tag, MasterPlaylistTag::M3U) input,
| map!(version_tag, MasterPlaylistTag::Version) map!(m3u_tag, MasterPlaylistTag::M3U)
| map!(version_tag, MasterPlaylistTag::Version)
| map!(variant_stream_tag, MasterPlaylistTag::VariantStream) | map!(variant_stream_tag, MasterPlaylistTag::VariantStream)
| map!(variant_i_frame_stream_tag, MasterPlaylistTag::VariantStream) | map!(variant_i_frame_stream_tag, MasterPlaylistTag::VariantStream)
| map!(alternative_media_tag, MasterPlaylistTag::AlternativeMedia) | map!(alternative_media_tag, MasterPlaylistTag::AlternativeMedia)
| map!(session_data_tag, MasterPlaylistTag::SessionData) | map!(session_data_tag, MasterPlaylistTag::SessionData)
| map!(session_key_tag, MasterPlaylistTag::SessionKey) | map!(session_key_tag, MasterPlaylistTag::SessionKey)
| map!(start_tag, MasterPlaylistTag::Start) | map!(start_tag, MasterPlaylistTag::Start)
| map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| MasterPlaylistTag::IndependentSegments) | map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| {
MasterPlaylistTag::IndependentSegments
| map!(ext_tag, MasterPlaylistTag::Unknown) })
| map!(ext_tag, MasterPlaylistTag::Unknown)
| map!(comment_tag, MasterPlaylistTag::Comment) | map!(comment_tag, MasterPlaylistTag::Comment)
| map!(consume_line, MasterPlaylistTag::Uri)
| map!(consume_line, MasterPlaylistTag::Uri)
) )
} }
@ -341,7 +349,6 @@ pub fn master_playlist_from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlay
master_playlist master_playlist
} }
named!(pub variant_stream_tag<VariantStream>, named!(pub variant_stream_tag<VariantStream>,
do_parse!(tag!("#EXT-X-STREAM-INF:") >> attributes: key_value_pairs >> do_parse!(tag!("#EXT-X-STREAM-INF:") >> attributes: key_value_pairs >>
( VariantStream::from_hashmap(attributes, false))) ( VariantStream::from_hashmap(attributes, false)))
@ -373,10 +380,16 @@ named!(pub session_key_tag<SessionKey>,
// ----------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------
pub fn parse_media_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec<MediaPlaylistTag>> { pub fn parse_media_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec<MediaPlaylistTag>> {
do_parse!(input, do_parse!(
tags: many0!(complete!(do_parse!(m:media_playlist_tag >> multispace0 >> (m) ))) >> opt!(eof!()) input,
>> tags: many0!(complete!(do_parse!(
( {let mut tags_rev: Vec<MediaPlaylistTag> = tags; tags_rev.reverse(); tags_rev } ) m: media_playlist_tag >> multispace0 >> (m)
))) >> opt!(eof!())
>> ({
let mut tags_rev: Vec<MediaPlaylistTag> = tags;
tags_rev.reverse();
tags_rev
})
) )
} }
@ -398,20 +411,35 @@ pub enum MediaPlaylistTag {
} }
pub fn media_playlist_tag(input: &[u8]) -> IResult<&[u8], MediaPlaylistTag> { pub fn media_playlist_tag(input: &[u8]) -> IResult<&[u8], MediaPlaylistTag> {
alt!(input, alt!(
map!(m3u_tag, MediaPlaylistTag::M3U) input,
| map!(version_tag, MediaPlaylistTag::Version) map!(m3u_tag, MediaPlaylistTag::M3U)
| map!(version_tag, MediaPlaylistTag::Version)
| map!(do_parse!(tag!("#EXT-X-TARGETDURATION:") >> n:float >> (n)), MediaPlaylistTag::TargetDuration) | map!(
| map!(do_parse!(tag!("#EXT-X-MEDIA-SEQUENCE:") >> n:number >> (n)), MediaPlaylistTag::MediaSequence) do_parse!(tag!("#EXT-X-TARGETDURATION:") >> n: float >> (n)),
| map!(do_parse!(tag!("#EXT-X-DISCONTINUITY-SEQUENCE:") >> n:number >> (n)), MediaPlaylistTag::DiscontinuitySequence) MediaPlaylistTag::TargetDuration
| map!(do_parse!(tag!("#EXT-X-PLAYLIST-TYPE:") >> t:playlist_type >> (t)), MediaPlaylistTag::PlaylistType) )
| map!(tag!("#EXT-X-I-FRAMES-ONLY"), |_| MediaPlaylistTag::IFramesOnly) | map!(
| map!(start_tag, MediaPlaylistTag::Start) do_parse!(tag!("#EXT-X-MEDIA-SEQUENCE:") >> n: number >> (n)),
| map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| MediaPlaylistTag::IndependentSegments) MediaPlaylistTag::MediaSequence
| map!(tag!("#EXT-X-ENDLIST"), |_| MediaPlaylistTag::EndList) )
| map!(
| map!(media_segment_tag, MediaPlaylistTag::Segment) 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<MediaPlaylistTag>) -> MediaPlaylis
let mut map = None; let mut map = None;
while let Some(tag) = tags.pop() { while let Some(tag) = tags.pop() {
match tag { match tag {
MediaPlaylistTag::Version(v) => { MediaPlaylistTag::Version(v) => {
media_playlist.version = v; media_playlist.version = v;
@ -451,45 +478,43 @@ pub fn media_playlist_from_tags(mut tags: Vec<MediaPlaylistTag>) -> MediaPlaylis
MediaPlaylistTag::IndependentSegments => { MediaPlaylistTag::IndependentSegments => {
media_playlist.independent_segments = true; media_playlist.independent_segments = true;
} }
MediaPlaylistTag::Segment(segment_tag) => { MediaPlaylistTag::Segment(segment_tag) => match segment_tag {
match segment_tag { SegmentTag::Extinf(d, t) => {
SegmentTag::Extinf(d, t) => { next_segment.duration = d;
next_segment.duration = d; next_segment.title = t;
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;
}
_ => (),
} }
} 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> { pub fn media_segment_tag(input: &[u8]) -> IResult<&[u8], SegmentTag> {
alt!(input, alt!(
map!(do_parse!(tag!("#EXTINF:") >> e:duration_title_tag >> (e)), |(a,b)| SegmentTag::Extinf(a,b)) input,
| map!(do_parse!(tag!("#EXT-X-BYTERANGE:") >> r:byte_range_val >> (r)), SegmentTag::ByteRange) map!(
| map!(tag!("#EXT-X-DISCONTINUITY"), |_| SegmentTag::Discontinuity) do_parse!(tag!("#EXTINF:") >> e: duration_title_tag >> (e)),
| map!(do_parse!(tag!("#EXT-X-KEY:") >> k: key >> (k)), SegmentTag::Key) |(a, b)| SegmentTag::Extinf(a, b)
| map!(do_parse!(tag!("#EXT-X-MAP:") >> m: extmap >> (m)), SegmentTag::Map) ) | map!(
| map!(do_parse!(tag!("#EXT-X-PROGRAM-DATE-TIME:") >> t:consume_line >> (t)), SegmentTag::ProgramDateTime) do_parse!(tag!("#EXT-X-BYTERANGE:") >> r: byte_range_val >> (r)),
| map!(do_parse!(tag!("#EXT-X-DATE-RANGE:") >> t:consume_line >> (t)), SegmentTag::DateRange) SegmentTag::ByteRange
) | map!(tag!("#EXT-X-DISCONTINUITY"), |_| SegmentTag::Discontinuity)
| map!(ext_tag, SegmentTag::Unknown) | map!(
| map!(comment_tag, SegmentTag::Comment) do_parse!(tag!("#EXT-X-KEY:") >> k: key >> (k)),
SegmentTag::Key
| map!(consume_line, SegmentTag::Uri) )
| 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)
) )
} }
@ -558,7 +598,7 @@ named!(pub duration_title_tag<(f32, Option<String>)>,
named!(pub key<Key>, map!(key_value_pairs, Key::from_hashmap)); named!(pub key<Key>, map!(key_value_pairs, Key::from_hashmap));
named!(pub extmap<Map>, map!(key_value_pairs, |attrs| Map { named!(pub extmap<Map>, map!(key_value_pairs, |attrs| Map {
uri: attrs.get("URI").map(|u| u.clone()).unwrap_or_default(), uri: attrs.get("URI").cloned().unwrap_or_default(),
byte_range: attrs.get("BYTERANGE").map(|range| { byte_range: attrs.get("BYTERANGE").map(|range| {
match byte_range_val(range.as_bytes()) { match byte_range_val(range.as_bytes()) {
IResult::Ok((_, br)) => br, IResult::Ok((_, br)) => br,
@ -596,7 +636,7 @@ named!(pub ext_tag<ExtTag>,
>> rest: opt!(map_res!(is_not!("\r\n"), from_utf8_slice)) >> rest: opt!(map_res!(is_not!("\r\n"), from_utf8_slice))
>> take!(1) >> take!(1)
>> ( >> (
ExtTag { tag: tag, rest: rest } ExtTag { tag, rest }
) )
) )
); );

View file

@ -3,32 +3,40 @@
//! The main type here is the `Playlist` enum. //! The main type here is the `Playlist` enum.
//! Which is either a `MasterPlaylist` or a `MediaPlaylist`. //! Which is either a `MasterPlaylist` or a `MediaPlaylist`.
use std::io::Write;
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr;
use std::fmt;
use std::f32; use std::f32;
use std::fmt;
use std::fmt::Display; use std::fmt::Display;
use std::io::Write;
use std::str::FromStr;
macro_rules! write_some_attribute_quoted { macro_rules! write_some_attribute_quoted {
($w:expr, $tag:expr, $o:expr) => ( ($w:expr, $tag:expr, $o:expr) => {
if let &Some(ref v) = $o { write!($w, "{}=\"{}\"", $tag, v) } else { Ok(()) } if let &Some(ref v) = $o {
); write!($w, "{}=\"{}\"", $tag, v)
} else {
Ok(())
}
};
} }
macro_rules! write_some_attribute { macro_rules! write_some_attribute {
($w:expr, $tag:expr, $o:expr) => ( ($w:expr, $tag:expr, $o:expr) => {
if let &Some(ref v) = $o { write!($w, "{}={}", $tag, v) } else { Ok(()) } if let &Some(ref v) = $o {
); write!($w, "{}={}", $tag, v)
} else {
Ok(())
}
};
} }
macro_rules! bool_default_false { macro_rules! bool_default_false {
($optional:expr) => ( ($optional:expr) => {
match $optional { match $optional {
Some(ref s) if s == "YES" => true, Some(ref s) if s == "YES" => true,
Some(_) | None => false, Some(_) | None => false,
} }
); };
} }
/// [Playlist](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.1), /// [Playlist](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.1),
@ -46,9 +54,9 @@ pub enum Playlist {
impl Playlist { impl Playlist {
pub fn write_to<T: Write>(&self, writer: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, writer: &mut T) -> std::io::Result<()> {
match self { match *self {
&Playlist::MasterPlaylist(ref pl) => pl.write_to(writer), Playlist::MasterPlaylist(ref pl) => pl.write_to(writer),
&Playlist::MediaPlaylist(ref pl) => pl.write_to(writer), Playlist::MediaPlaylist(ref pl) => pl.write_to(writer),
} }
} }
} }
@ -74,14 +82,12 @@ pub struct MasterPlaylist {
} }
impl MasterPlaylist { impl MasterPlaylist {
pub fn get_newest_variant(&mut self) -> Option<&mut VariantStream> { pub fn get_newest_variant(&mut self) -> Option<&mut VariantStream> {
self.variants.iter_mut().rev().find(|v| !v.is_i_frame) self.variants.iter_mut().rev().find(|v| !v.is_i_frame)
} }
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
writeln!(w, "{}" ,"#EXTM3U")?; writeln!(w, "#EXTM3U\n#EXT-X-VERSION:{}", self.version)?;
writeln!(w, "#EXT-X-VERSION:{}", self.version)?;
for alternative in &self.alternatives { for alternative in &self.alternatives {
alternative.write_to(w)?; alternative.write_to(w)?;
@ -147,10 +153,9 @@ pub struct VariantStream {
} }
impl VariantStream { impl VariantStream {
pub fn from_hashmap(mut attrs: HashMap<String, String>, is_i_frame: bool) -> VariantStream { pub fn from_hashmap(mut attrs: HashMap<String, String>, is_i_frame: bool) -> VariantStream {
VariantStream { VariantStream {
is_i_frame: is_i_frame, is_i_frame,
uri: attrs.remove("URI").unwrap_or_else(String::new), uri: attrs.remove("URI").unwrap_or_else(String::new),
bandwidth: attrs.remove("BANDWIDTH").unwrap_or_else(String::new), bandwidth: attrs.remove("BANDWIDTH").unwrap_or_else(String::new),
average_bandwidth: attrs.remove("AVERAGE-BANDWIDTH"), average_bandwidth: attrs.remove("AVERAGE-BANDWIDTH"),
@ -166,20 +171,17 @@ impl VariantStream {
} }
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
if self.is_i_frame { if self.is_i_frame {
write!(w, "#EXT-X-I-FRAME-STREAM-INF:")?; write!(w, "#EXT-X-I-FRAME-STREAM-INF:")?;
self.write_stream_inf_common_attributes(w)?; self.write_stream_inf_common_attributes(w)?;
writeln!(w, ",URI=\"{}\"", self.uri) writeln!(w, ",URI=\"{}\"", self.uri)
} } else {
else {
write!(w, "#EXT-X-STREAM-INF:")?; write!(w, "#EXT-X-STREAM-INF:")?;
self.write_stream_inf_common_attributes(w)?; self.write_stream_inf_common_attributes(w)?;
write_some_attribute_quoted!(w, ",AUDIO", &self.audio)?; write_some_attribute_quoted!(w, ",AUDIO", &self.audio)?;
write_some_attribute_quoted!(w, ",SUBTITLES", &self.subtitles)?; write_some_attribute_quoted!(w, ",SUBTITLES", &self.subtitles)?;
write_some_attribute_quoted!(w, ",CLOSED-CAPTIONS", &self.closed_captions)?; write_some_attribute_quoted!(w, ",CLOSED-CAPTIONS", &self.closed_captions)?;
write!(w, "\n")?; writeln!(w)?;
writeln!(w, "{}", self.uri) writeln!(w, "{}", self.uri)
} }
} }
@ -222,17 +224,17 @@ pub struct AlternativeMedia {
} }
impl AlternativeMedia { impl AlternativeMedia {
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> AlternativeMedia { pub fn from_hashmap(mut attrs: HashMap<String, String>) -> AlternativeMedia {
AlternativeMedia { AlternativeMedia {
media_type: attrs.get("TYPE") media_type: attrs
.get("TYPE")
.and_then(|s| AlternativeMediaType::from_str(s).ok()) .and_then(|s| AlternativeMediaType::from_str(s).ok())
.unwrap_or_else(Default::default), .unwrap_or_else(Default::default),
uri: attrs.remove("URI"), uri: attrs.remove("URI"),
group_id: attrs.remove("GROUP-ID").unwrap_or_else(String::new), group_id: attrs.remove("GROUP-ID").unwrap_or_else(String::new),
language: attrs.remove("LANGUAGE"), language: attrs.remove("LANGUAGE"),
assoc_language: attrs.remove("ASSOC-LANGUAGE"), assoc_language: attrs.remove("ASSOC-LANGUAGE"),
name: attrs.remove("NAME").unwrap_or(String::new()), name: attrs.remove("NAME").unwrap_or_default(),
default: bool_default_false!(attrs.remove("DEFAULT")), default: bool_default_false!(attrs.remove("DEFAULT")),
autoselect: bool_default_false!(attrs.remove("AUTOSELECT")), autoselect: bool_default_false!(attrs.remove("AUTOSELECT")),
forced: bool_default_false!(attrs.remove("FORCED")), forced: bool_default_false!(attrs.remove("FORCED")),
@ -250,13 +252,19 @@ impl AlternativeMedia {
write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?; write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?;
write_some_attribute_quoted!(w, ",ASSOC-LANGUAGE", &self.assoc_language)?; write_some_attribute_quoted!(w, ",ASSOC-LANGUAGE", &self.assoc_language)?;
write!(w, ",NAME=\"{}\"", self.name)?; write!(w, ",NAME=\"{}\"", self.name)?;
if self.default { write!(w, ",DEFAULT=YES")?; } if self.default {
if self.autoselect { write!(w, ",AUTOSELECT=YES")?; } write!(w, ",DEFAULT=YES")?;
if self.forced { write!(w, ",FORCED=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, ",INSTREAM-ID", &self.instream_id)?;
write_some_attribute_quoted!(w, ",CHARACTERISTICS", &self.characteristics)?; write_some_attribute_quoted!(w, ",CHARACTERISTICS", &self.characteristics)?;
write_some_attribute_quoted!(w, ",CHANNELS", &self.channels)?; write_some_attribute_quoted!(w, ",CHANNELS", &self.channels)?;
write!(w, "\n") writeln!(w)
} }
} }
@ -277,7 +285,10 @@ impl FromStr for AlternativeMediaType {
"VIDEO" => Ok(AlternativeMediaType::Video), "VIDEO" => Ok(AlternativeMediaType::Video),
"SUBTITLES" => Ok(AlternativeMediaType::Subtitles), "SUBTITLES" => Ok(AlternativeMediaType::Subtitles),
"CLOSEDCAPTIONS" => Ok(AlternativeMediaType::ClosedCaptions), "CLOSEDCAPTIONS" => Ok(AlternativeMediaType::ClosedCaptions),
_ => Err(format!("Unable to create AlternativeMediaType from {:?}", s)), _ => Err(format!(
"Unable to create AlternativeMediaType from {:?}",
s
)),
} }
} }
} }
@ -290,16 +301,19 @@ impl Default for AlternativeMediaType {
impl fmt::Display for AlternativeMediaType { impl fmt::Display for AlternativeMediaType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match self { write!(
&AlternativeMediaType::Audio => "AUDIO", f,
&AlternativeMediaType::Video => "VIDEO", "{}",
&AlternativeMediaType::Subtitles => "SUBTITLES", match *self {
&AlternativeMediaType::ClosedCaptions => "CLOSEDCAPTIONS", AlternativeMediaType::Audio => "AUDIO",
}) AlternativeMediaType::Video => "VIDEO",
AlternativeMediaType::Subtitles => "SUBTITLES",
AlternativeMediaType::ClosedCaptions => "CLOSEDCAPTIONS",
}
)
} }
} }
/// [`#EXT-X-SESSION-KEY:<attribute-list>`] /// [`#EXT-X-SESSION-KEY:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5) /// (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 /// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists
@ -312,14 +326,14 @@ impl SessionKey {
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
write!(w, "#EXT-X-SESSION-KEY:")?; write!(w, "#EXT-X-SESSION-KEY:")?;
self.0.write_attributes_to(w)?; self.0.write_attributes_to(w)?;
write!(w, "\n") writeln!(w)
} }
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum SessionDataField { pub enum SessionDataField {
Value(String), Value(String),
Uri(String) Uri(String),
} }
/// [`#EXT-X-SESSION-DATA:<attribute-list>`] /// [`#EXT-X-SESSION-DATA:<attribute-list>`]
@ -337,7 +351,7 @@ impl SessionData {
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Result<SessionData, String> { pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Result<SessionData, String> {
let data_id = match attrs.remove("DATA-ID") { let data_id = match attrs.remove("DATA-ID") {
Some(data_id) => 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"); let value = attrs.remove("VALUE");
@ -348,8 +362,18 @@ impl SessionData {
let field = match (value, uri) { let field = match (value, uri) {
(Some(value), None) => SessionDataField::Value(value), (Some(value), None) => SessionDataField::Value(value),
(None, Some(uri)) => SessionDataField::Uri(uri), (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]), (Some(_), Some(_)) => {
(None, None) => return Err(format!["EXT-X-SESSION-DATA tag {} must contain either a value or a uri", data_id]), 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 { Ok(SessionData {
@ -363,11 +387,11 @@ impl SessionData {
write!(w, "#EXT-X-SESSION-DATA:")?; write!(w, "#EXT-X-SESSION-DATA:")?;
write!(w, "DATA-ID=\"{}\"", self.data_id)?; write!(w, "DATA-ID=\"{}\"", self.data_id)?;
match &self.field { match &self.field {
SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?, SessionDataField::Value(value) => write!(w, ",VALUE=\"{}\"", value)?,
SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?, SessionDataField::Uri(uri) => write!(w, ",URI=\"{}\"", uri)?,
}; };
write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?; write_some_attribute_quoted!(w, ",LANGUAGE", &self.language)?;
write!(w, "\n") writeln!(w)
} }
} }
@ -402,17 +426,19 @@ pub struct MediaPlaylist {
} }
impl MediaPlaylist { impl MediaPlaylist {
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
writeln!(w, "{}" ,"#EXTM3U")?; writeln!(w, "#EXTM3U\n#EXT-X-VERSION:{}", self.version)?;
writeln!(w, "#EXT-X-VERSION:{}", self.version)?;
writeln!(w, "#EXT-X-TARGETDURATION:{}", self.target_duration)?; 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)?; writeln!(w, "#EXT-X-MEDIA-SEQUENCE:{}", self.media_sequence)?;
} }
if self.discontinuity_sequence != 0 { 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 { if self.end_list {
writeln!(w, "#EXT-X-ENDLIST")?; writeln!(w, "#EXT-X-ENDLIST")?;
@ -459,10 +485,14 @@ impl FromStr for MediaPlaylistType {
impl fmt::Display for MediaPlaylistType { impl fmt::Display for MediaPlaylistType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", match self { write!(
&MediaPlaylistType::Event => "EVENT", f,
&MediaPlaylistType::Vod => "VOD", "{}",
}) match *self {
MediaPlaylistType::Event => "EVENT",
MediaPlaylistType::Vod => "VOD",
}
)
} }
} }
@ -507,24 +537,23 @@ impl MediaSegment {
} }
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
if let Some(ref byte_range) = self.byte_range { if let Some(ref byte_range) = self.byte_range {
write!(w, "#EXT-X-BYTERANGE:")?; write!(w, "#EXT-X-BYTERANGE:")?;
byte_range.write_value_to(w)?; byte_range.write_value_to(w)?;
write!(w, "\n")?; writeln!(w)?;
} }
if self.discontinuity { if self.discontinuity {
writeln!(w, "{}", "#EXT-X-DISCONTINUITY")?; writeln!(w, "#EXT-X-DISCONTINUITY")?;
} }
if let Some(ref key) = self.key { if let Some(ref key) = self.key {
write!(w, "#EXT-X-KEY:")?; write!(w, "#EXT-X-KEY:")?;
key.write_attributes_to(w)?; key.write_attributes_to(w)?;
write!(w, "\n")?; writeln!(w)?;
} }
if let Some(ref map) = self.map { if let Some(ref map) = self.map {
write!(w, "#EXT-X-MAP:")?; write!(w, "#EXT-X-MAP:")?;
map.write_attributes_to(w)?; map.write_attributes_to(w)?;
write!(w, "\n")?; writeln!(w)?;
} }
if let Some(ref v) = self.program_date_time { if let Some(ref v) = self.program_date_time {
writeln!(w, "#EXT-X-PROGRAM-DATE-TIME:{}", v)?; writeln!(w, "#EXT-X-PROGRAM-DATE-TIME:{}", v)?;
@ -541,7 +570,7 @@ impl MediaSegment {
if let Some(ref v) = self.title { if let Some(ref v) = self.title {
writeln!(w, "{}", v)?; writeln!(w, "{}", v)?;
} else { } else {
write!(w, "\n")?; writeln!(w)?;
} }
writeln!(w, "{}", self.uri) writeln!(w, "{}", self.uri)
@ -613,7 +642,6 @@ impl Map {
} }
} }
/// [`#EXT-X-BYTERANGE:<n>[@<o>]`] /// [`#EXT-X-BYTERANGE:<n>[@<o>]`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.2) /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.2)
/// ///
@ -636,7 +664,6 @@ impl ByteRange {
} }
} }
/// [`#EXT-X-DATERANGE:<attribute-list>`] /// [`#EXT-X-DATERANGE:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.7) /// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.7)
/// ///
@ -675,14 +702,14 @@ impl Start {
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Start { pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Start {
Start { Start {
time_offset: attrs.remove("TIME-OFFSET").unwrap_or_else(String::new), time_offset: attrs.remove("TIME-OFFSET").unwrap_or_else(String::new),
precise: attrs.remove("PRECISE").or(Some("NO".to_string())), precise: attrs.remove("PRECISE").or_else(|| Some("NO".to_string())),
} }
} }
pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> { pub fn write_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
write!(w, "#EXT-X-START:TIME-OFFSET={}", self.time_offset)?; write!(w, "#EXT-X-START:TIME-OFFSET={}", self.time_offset)?;
write_some_attribute!(w, ",PRECISE", &self.precise)?; write_some_attribute!(w, ",PRECISE", &self.precise)?;
write!(w, "\n") writeln!(w)
} }
} }
@ -717,7 +744,10 @@ mod test {
let mut output = Vec::new(); let mut output = Vec::new();
write!(output, "{}", cue_out_tag).unwrap(); 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] #[test]
@ -730,6 +760,9 @@ mod test {
let mut output = Vec::new(); let mut output = Vec::new();
write!(output, "{}", cue_in_tag).unwrap(); 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"
)
} }
} }

View file

@ -1,20 +1,21 @@
#![allow(unused_variables, unused_imports, dead_code)] #![allow(unused_variables, unused_imports, dead_code)]
extern crate nom;
extern crate m3u8_rs; extern crate m3u8_rs;
extern crate nom;
use std::fs;
use std::path;
use m3u8_rs::*;
use m3u8_rs::playlist::*; use m3u8_rs::playlist::*;
use std::io::Read; use m3u8_rs::*;
use std::fs::File;
use nom::*; use nom::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::path;
fn all_sample_m3u_playlists() -> Vec<path::PathBuf> { fn all_sample_m3u_playlists() -> Vec<path::PathBuf> {
let path: std::path::PathBuf = ["sample-playlists"].iter().collect(); 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) .filter_map(Result::ok)
.map(|dir| dir.path()) .map(|dir| dir.path())
.filter(|path| path.extension().map_or(false, |ext| ext == "m3u8")) .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); println!("Parsing playlist file: {:?}", playlist_name);
let parsed = parse_playlist(input.as_bytes()); let parsed = parse_playlist(input.as_bytes());
if let Result::Ok((i,o)) = parsed { if let Result::Ok((i, o)) = parsed {
println!("{:?}", o); println!("{:?}", o);
true true
} } else {
else {
println!("Parsing failed:\n {:?}", parsed); println!("Parsing failed:\n {:?}", parsed);
false false
} }
@ -56,21 +56,23 @@ fn playlist_master_with_alternatives() {
assert!(print_parse_playlist_test("master-with-alternatives.m3u8")); assert!(print_parse_playlist_test("master-with-alternatives.m3u8"));
} }
#[test] #[test]
fn playlist_master_with_alternatives_2_3() { fn playlist_master_with_alternatives_2_3() {
assert!(print_parse_playlist_test("master-with-alternatives-2.m3u8")); assert!(print_parse_playlist_test("master-with-alternatives-2.m3u8"));
} }
#[test] #[test]
fn playlist_master_with_i_frame_stream_inf() { 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] #[test]
fn playlist_master_with_multiple_codecs() { 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 // -- Media playlists
@ -82,7 +84,9 @@ fn playlist_media_standard() {
#[test] #[test]
fn playlist_media_without_segments() { 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] #[test]
@ -102,7 +106,9 @@ fn playlist_media_with_scte35() {
#[test] #[test]
fn playlist_media_with_scte35_1() { 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] #[test]
fn playlist_not_ending_in_newline_master() { 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] #[test]
fn playlist_not_ending_in_newline_master1() { 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] #[test]
fn playlist_not_ending_in_newline_media() { 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); println!("{:?}", result);
} }
// ----------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------
// Other // Other
@ -203,7 +214,6 @@ fn test_key_value_pairs() {
println!("{:?}\n\n", res); println!("{:?}\n\n", res);
} }
#[test] #[test]
fn test_key_value_pair() { fn test_key_value_pair() {
assert_eq!( assert_eq!(
@ -219,7 +229,13 @@ fn test_key_value_pair() {
fn ext_with_value() { fn ext_with_value() {
assert_eq!( assert_eq!(
ext_tag(b"#EXT-X-CUE-OUT:DURATION=30\nxxx"), 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() { fn ext_without_value() {
assert_eq!( assert_eq!(
ext_tag(b"#EXT-X-CUE-IN\nxxx"), 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] #[test]
fn float_no_decimal() { fn float_no_decimal() {
assert_eq!( assert_eq!(float(b"33rest"), Result::Ok(("rest".as_bytes(), 33f32)));
float(b"33rest"),
Result::Ok(("rest".as_bytes(), 33f32))
);
} }
#[test] #[test]
fn float_should_ignore_trailing_dot() { fn float_should_ignore_trailing_dot() {
assert_eq!( assert_eq!(float(b"33.rest"), Result::Ok((".rest".as_bytes(), 33f32)));
float(b"33.rest"),
Result::Ok((".rest".as_bytes(), 33f32))
);
} }
#[test] #[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 m3u8_str: &str = std::str::from_utf8(&utf8).unwrap();
let playlist_parsed = match *playlist_original { let playlist_parsed = match *playlist_original {
Playlist::MasterPlaylist(_) => Playlist::MasterPlaylist(_) => {
Playlist::MasterPlaylist(parse_master_playlist_res(m3u8_str.as_bytes()).unwrap()), 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::MediaPlaylist(_) => {
Playlist::MediaPlaylist(parse_media_playlist_res(m3u8_str.as_bytes()).unwrap())
}
}; };
print!("\n\n---- utf8 result\n\n{}", m3u8_str); 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] #[test]
fn create_and_parse_master_playlist_empty() { 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); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original);
assert_eq!(playlist_original, playlist_parsed); assert_eq!(playlist_original, playlist_parsed);
} }
#[test] #[test]
fn create_and_parse_master_playlist_full() { fn create_and_parse_master_playlist_full() {
let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist { let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist {
version: 6, version: 6,
alternatives: vec! [ alternatives: vec![AlternativeMedia {
AlternativeMedia { media_type: AlternativeMediaType::Audio,
media_type: AlternativeMediaType::Audio, uri: Some("alt-media-uri".into()),
uri: Some("alt-media-uri".into()), group_id: "group-id".into(),
group_id: "group-id".into(), language: Some("language".into()),
language: Some("language".into()), assoc_language: Some("assoc-language".into()),
assoc_language: Some("assoc-language".into()), name: "Xmedia".into(),
name: "Xmedia".into(), default: true, // Its absence indicates an implicit value of NO
default: true, // Its absence indicates an implicit value of NO autoselect: 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
forced: true, // Its absence indicates an implicit value of NO instream_id: Some("instream_id".into()),
instream_id: Some("instream_id".into()), characteristics: Some("characteristics".into()),
characteristics: Some("characteristics".into()), channels: Some("channels".into()),
channels: Some("channels".into()), }],
} variants: vec![VariantStream {
], is_i_frame: false,
variants: vec![ uri: "masterplaylist-uri".into(),
VariantStream { bandwidth: "10010010".into(),
is_i_frame: false, average_bandwidth: Some("10010010".into()),
uri: "masterplaylist-uri".into(), codecs: Some("TheCODEC".into()),
bandwidth: "10010010".into(), resolution: Some("1000x3000".into()),
average_bandwidth: Some("10010010".into()), frame_rate: Some("60".into()),
codecs: Some("TheCODEC".into()), hdcp_level: Some("NONE".into()),
resolution: Some("1000x3000".into()), audio: Some("audio".into()),
frame_rate: Some("60".into()), video: Some("video".into()),
hdcp_level: Some("NONE".into()), subtitles: Some("subtitles".into()),
audio: Some("audio".into()), closed_captions: Some("closed_captions".into()),
video: Some("video".into()), }],
subtitles: Some("subtitles".into()), session_data: vec![SessionData {
closed_captions: Some("closed_captions".into()), data_id: "****".into(),
} field: SessionDataField::Value("%%%%".to_string()),
], language: Some("SessionDataLanguage".into()),
session_data: vec![ }],
SessionData { session_key: vec![SessionKey(Key {
data_id: "****".into(), method: "AES-128".into(),
field: SessionDataField::Value("%%%%".to_string()), uri: Some("https://secure.domain.com".into()),
language: Some("SessionDataLanguage".into()), iv: Some("0xb059217aa2649ce170b734".into()),
} keyformat: Some("xXkeyformatXx".into()),
], keyformatversions: Some("xXFormatVers".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 { start: Some(Start {
time_offset: "123123123".into(), time_offset: "123123123".into(),
precise: Some("YES".into()), precise: Some("YES".into()),
@ -398,7 +409,9 @@ fn create_and_parse_master_playlist_full() {
#[test] #[test]
fn create_and_parse_media_playlist_empty() { 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); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original);
assert_eq!(playlist_original, playlist_parsed); assert_eq!(playlist_original, playlist_parsed);
} }
@ -406,15 +419,13 @@ fn create_and_parse_media_playlist_empty() {
#[test] #[test]
fn create_and_parse_media_playlist_single_segment() { fn create_and_parse_media_playlist_single_segment() {
let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist { let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist {
segments: vec![ segments: vec![MediaSegment {
MediaSegment { uri: "20140311T113819-01-338559live.ts".into(),
uri: "20140311T113819-01-338559live.ts".into(), duration: 2.002,
duration: 2.002, title: Some("hey".into()),
title: Some("hey".into()), ..Default::default()
..Default::default() }],
}, ..Default::default()
],
..Default::default()
}); });
let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original);
assert_eq!(playlist_original, playlist_parsed); assert_eq!(playlist_original, playlist_parsed);
@ -422,7 +433,6 @@ fn create_and_parse_media_playlist_single_segment() {
#[test] #[test]
fn create_and_parse_media_playlist_full() { fn create_and_parse_media_playlist_full() {
let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist { let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist {
version: 4, version: 4,
target_duration: 3.0, target_duration: 3.0,
@ -436,34 +446,36 @@ fn create_and_parse_media_playlist_full() {
precise: Some("YES".into()), precise: Some("YES".into()),
}), }),
independent_segments: true, independent_segments: true,
segments: vec![ segments: vec![MediaSegment {
MediaSegment { uri: "20140311T113819-01-338559live.ts".into(),
uri: "20140311T113819-01-338559live.ts".into(), duration: 2.002,
duration: 2.002, title: Some("338559".into()),
title: Some("338559".into()), byte_range: Some(ByteRange {
byte_range: Some(ByteRange { length: 137116, offset: Some(4559) }), length: 137116,
discontinuity: true, offset: Some(4559),
key: Some(Key { }),
method: "AES-128".into(), discontinuity: true,
uri: Some("https://secure.domain.com".into()), key: Some(Key {
iv: Some("0xb059217aa2649ce170b734".into()), method: "AES-128".into(),
keyformat: Some("xXkeyformatXx".into()), uri: Some("https://secure.domain.com".into()),
keyformatversions: Some("xXFormatVers".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(), program_date_time: Some("broodlordinfestorgg".into()),
byte_range: Some(ByteRange { length: 137116, offset: Some(4559) }), daterange: None,
}), unknown_tags: vec![ExtTag {
program_date_time: Some("broodlordinfestorgg".into()), tag: "X-CUE-OUT".into(),
daterange: None, rest: Some("DURATION=2.002".into()),
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); let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original);
assert_eq!(playlist_original, playlist_parsed); assert_eq!(playlist_original, playlist_parsed);
@ -472,7 +484,6 @@ fn create_and_parse_media_playlist_full() {
// //
// Roundtrip // Roundtrip
#[test] #[test]
fn parsing_write_to_should_produce_the_same_structure() { fn parsing_write_to_should_produce_the_same_structure() {
for playlist in all_sample_m3u_playlists() { 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(); let actual = parse_playlist_res(&written).unwrap();
assert_eq!( assert_eq!(
expected, actual, expected,
actual,
"\n\nFailed parser input:\n\n{}\n\nOriginal input:\n\n{}", "\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
);
} }
} }