mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2024-12-22 22:46:37 +00:00
Merge pull request #33 from rafaelcaricio/apply-fmt-and-clippy
Apply cargo fmt and clippy suggestions
This commit is contained in:
commit
303d0ecfce
5 changed files with 399 additions and 312 deletions
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
156
src/parser.rs
156
src/parser.rs
|
@ -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;
|
||||||
|
|
||||||
|
@ -254,15 +259,19 @@ named!(pub is_master_playlist_tag_line(&[u8]) -> Option<(bool, String)>,
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
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,22 +289,21 @@ 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!(
|
||||||
|
input,
|
||||||
map!(m3u_tag, MasterPlaylistTag::M3U)
|
map!(m3u_tag, MasterPlaylistTag::M3U)
|
||||||
| map!(version_tag, MasterPlaylistTag::Version)
|
| 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,19 +411,34 @@ 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!(
|
||||||
|
input,
|
||||||
map!(m3u_tag, MediaPlaylistTag::M3U)
|
map!(m3u_tag, MediaPlaylistTag::M3U)
|
||||||
| map!(version_tag, MediaPlaylistTag::Version)
|
| map!(version_tag, MediaPlaylistTag::Version)
|
||||||
|
| map!(
|
||||||
| map!(do_parse!(tag!("#EXT-X-TARGETDURATION:") >> n:float >> (n)), MediaPlaylistTag::TargetDuration)
|
do_parse!(tag!("#EXT-X-TARGETDURATION:") >> n: float >> (n)),
|
||||||
| map!(do_parse!(tag!("#EXT-X-MEDIA-SEQUENCE:") >> n:number >> (n)), MediaPlaylistTag::MediaSequence)
|
MediaPlaylistTag::TargetDuration
|
||||||
| 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!(
|
||||||
| map!(tag!("#EXT-X-I-FRAMES-ONLY"), |_| MediaPlaylistTag::IFramesOnly)
|
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!(start_tag, MediaPlaylistTag::Start)
|
||||||
| map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| MediaPlaylistTag::IndependentSegments)
|
| map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| {
|
||||||
|
MediaPlaylistTag::IndependentSegments
|
||||||
|
})
|
||||||
| map!(tag!("#EXT-X-ENDLIST"), |_| MediaPlaylistTag::EndList)
|
| map!(tag!("#EXT-X-ENDLIST"), |_| MediaPlaylistTag::EndList)
|
||||||
|
|
||||||
| map!(media_segment_tag, MediaPlaylistTag::Segment)
|
| 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,8 +478,7 @@ 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;
|
||||||
|
@ -488,8 +514,7 @@ pub fn media_playlist_from_tags(mut tags: Vec<MediaPlaylistTag>) -> MediaPlaylis
|
||||||
map = None;
|
map = None;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -527,18 +552,33 @@ 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!(
|
||||||
|
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!(ext_tag, SegmentTag::Unknown)
|
||||||
| map!(comment_tag, SegmentTag::Comment)
|
| map!(comment_tag, SegmentTag::Comment)
|
||||||
|
|
||||||
| map!(consume_line, SegmentTag::Uri)
|
| 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 }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
163
src/playlist.rs
163
src/playlist.rs
|
@ -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 {
|
||||||
|
@ -367,7 +391,7 @@ impl SessionData {
|
||||||
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"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
160
tests/lib.rs
160
tests/lib.rs
|
@ -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,18 +345,18 @@ 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(),
|
||||||
|
@ -351,10 +369,8 @@ fn create_and_parse_master_playlist_full() {
|
||||||
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 {
|
||||||
variants: vec![
|
|
||||||
VariantStream {
|
|
||||||
is_i_frame: false,
|
is_i_frame: false,
|
||||||
uri: "masterplaylist-uri".into(),
|
uri: "masterplaylist-uri".into(),
|
||||||
bandwidth: "10010010".into(),
|
bandwidth: "10010010".into(),
|
||||||
|
@ -367,24 +383,19 @@ fn create_and_parse_master_playlist_full() {
|
||||||
video: Some("video".into()),
|
video: Some("video".into()),
|
||||||
subtitles: Some("subtitles".into()),
|
subtitles: Some("subtitles".into()),
|
||||||
closed_captions: Some("closed_captions".into()),
|
closed_captions: Some("closed_captions".into()),
|
||||||
}
|
}],
|
||||||
],
|
session_data: vec![SessionData {
|
||||||
session_data: vec![
|
|
||||||
SessionData {
|
|
||||||
data_id: "****".into(),
|
data_id: "****".into(),
|
||||||
field: SessionDataField::Value("%%%%".to_string()),
|
field: SessionDataField::Value("%%%%".to_string()),
|
||||||
language: Some("SessionDataLanguage".into()),
|
language: Some("SessionDataLanguage".into()),
|
||||||
}
|
}],
|
||||||
],
|
session_key: vec![SessionKey(Key {
|
||||||
session_key: vec![
|
|
||||||
SessionKey(Key {
|
|
||||||
method: "AES-128".into(),
|
method: "AES-128".into(),
|
||||||
uri: Some("https://secure.domain.com".into()),
|
uri: Some("https://secure.domain.com".into()),
|
||||||
iv: Some("0xb059217aa2649ce170b734".into()),
|
iv: Some("0xb059217aa2649ce170b734".into()),
|
||||||
keyformat: Some("xXkeyformatXx".into()),
|
keyformat: Some("xXkeyformatXx".into()),
|
||||||
keyformatversions: Some("xXFormatVers".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,14 +419,12 @@ 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);
|
||||||
|
@ -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,12 +446,14 @@ 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 { length: 137116, offset: Some(4559) }),
|
byte_range: Some(ByteRange {
|
||||||
|
length: 137116,
|
||||||
|
offset: Some(4559),
|
||||||
|
}),
|
||||||
discontinuity: true,
|
discontinuity: true,
|
||||||
key: Some(Key {
|
key: Some(Key {
|
||||||
method: "AES-128".into(),
|
method: "AES-128".into(),
|
||||||
|
@ -452,18 +464,18 @@ fn create_and_parse_media_playlist_full() {
|
||||||
}),
|
}),
|
||||||
map: Some(Map {
|
map: Some(Map {
|
||||||
uri: "www.map-uri.com".into(),
|
uri: "www.map-uri.com".into(),
|
||||||
byte_range: Some(ByteRange { length: 137116, offset: Some(4559) }),
|
byte_range: Some(ByteRange {
|
||||||
|
length: 137116,
|
||||||
|
offset: Some(4559),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
program_date_time: Some("broodlordinfestorgg".into()),
|
program_date_time: Some("broodlordinfestorgg".into()),
|
||||||
daterange: None,
|
daterange: None,
|
||||||
unknown_tags: vec![
|
unknown_tags: vec![ExtTag {
|
||||||
ExtTag {
|
|
||||||
tag: "X-CUE-OUT".into(),
|
tag: "X-CUE-OUT".into(),
|
||||||
rest: Some("DURATION=2.002".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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue