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