mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2025-01-03 03:38:40 +00:00
parent
f104d431d9
commit
81398f86cd
3 changed files with 436 additions and 283 deletions
|
@ -9,7 +9,7 @@ documentation = "https://rutgersc.github.io/doc/m3u8_rs/index.html"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nom = { version = "5.1.0", optional = true }
|
nom = { version = "7", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["parser"]
|
default = ["parser"]
|
||||||
|
|
715
src/parser.rs
715
src/parser.rs
|
@ -83,14 +83,15 @@ extern crate nom;
|
||||||
|
|
||||||
pub mod playlist;
|
pub mod playlist;
|
||||||
|
|
||||||
use self::nom::character::complete::{digit1, multispace0, space0};
|
use self::nom::bytes::complete::{tag, take, is_not, is_a, take_while1, take_until};
|
||||||
use self::nom::character::complete::{line_ending, not_line_ending};
|
use self::nom::character::is_digit;
|
||||||
use self::nom::combinator::map;
|
use self::nom::character::complete::{digit1, multispace0, space0, line_ending, not_line_ending, char, none_of};
|
||||||
|
use self::nom::sequence::{delimited, preceded, tuple, pair, terminated};
|
||||||
|
use self::nom::combinator::{map, map_res, opt, peek, eof, complete};
|
||||||
|
use self::nom::multi::{fold_many0, many0};
|
||||||
|
use self::nom::branch::alt;
|
||||||
|
|
||||||
use self::nom::IResult;
|
use self::nom::IResult;
|
||||||
use self::nom::{
|
|
||||||
alt, char, complete, delimited, do_parse, eof, is_a, is_not, many0, map, map_res, named,
|
|
||||||
none_of, opt, peek, tag, take, take_until, terminated,
|
|
||||||
};
|
|
||||||
use playlist::*;
|
use playlist::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::f32;
|
use std::f32;
|
||||||
|
@ -221,55 +222,61 @@ pub fn contains_master_tag(input: &[u8]) -> Option<(bool, String)> {
|
||||||
is_master_opt
|
is_master_opt
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(pub is_master_playlist_tag_line(&[u8]) -> Option<(bool, String)>,
|
pub fn is_master_playlist_tag_line(i: &[u8]) -> IResult<&[u8], Option<(bool, String)>> {
|
||||||
do_parse!(
|
map(
|
||||||
opt!(is_a!("\r\n"))
|
tuple((
|
||||||
>> tag: opt!(alt!(
|
opt(is_a("\r\n")),
|
||||||
map!(tag!("#EXT-X-STREAM-INF"), |t| (true, t))
|
opt(alt((
|
||||||
| map!(tag!("#EXT-X-I-FRAME-STREAM-INF"), |t| (true, t))
|
map(tag("#EXT-X-STREAM-INF"), |t| (true, t)),
|
||||||
| map!(terminated!(tag!("#EXT-X-MEDIA"), is_not!("-")), |t| (true, t)) // terminated!() to prevent matching with #EXT-X-MEDIA-SEQUENCE for which we have a separate pattern below
|
map(tag("#EXT-X-I-FRAME-STREAM-INF"), |t| (true, t)),
|
||||||
| map!(tag!("#EXT-X-SESSION-KEY"), |t| (true, t))
|
map(terminated(tag("#EXT-X-MEDIA"), is_not("-")), |t| (true, t)), // terminated() to prevent matching with #EXT-X-MEDIA-SEQUENCE for which we have a separate pattern below
|
||||||
| map!(tag!("#EXT-X-SESSION-DATA"), |t| (true, t))
|
map(tag("#EXT-X-SESSION-KEY"), |t| (true, t)),
|
||||||
|
map(tag("#EXT-X-SESSION-DATA"), |t| (true, t)),
|
||||||
|
|
||||||
| map!(tag!("#EXT-X-TARGETDURATION"), |t| (false, t))
|
map(tag("#EXT-X-TARGETDURATION"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-MEDIA-SEQUENCE"), |t| (false, t))
|
map(tag("#EXT-X-MEDIA-SEQUENCE"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-DISCONTINUITY-SEQUENCE"), |t| (false, t))
|
map(tag("#EXT-X-DISCONTINUITY-SEQUENCE"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-ENDLIST"), |t| (false, t))
|
map(tag("#EXT-X-ENDLIST"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-PLAYLIST-TYPE"), |t| (false, t))
|
map(tag("#EXT-X-PLAYLIST-TYPE"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-I-FRAMES-ONLY"), |t| (false, t))
|
map(tag("#EXT-X-I-FRAMES-ONLY"), |t| (false, t)),
|
||||||
|
|
||||||
| map!(tag!("#EXTINF"), |t| (false, t))
|
map(tag("#EXTINF"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-BYTERANGE"), |t| (false, t))
|
map(tag("#EXT-X-BYTERANGE"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-DISCONTINUITY"), |t| (false, t))
|
map(tag("#EXT-X-DISCONTINUITY"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-KEY"), |t| (false, t))
|
map(tag("#EXT-X-KEY"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-MAP"), |t| (false, t))
|
map(tag("#EXT-X-MAP"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-PROGRAM-DATE-TIME"), |t| (false, t))
|
map(tag("#EXT-X-PROGRAM-DATE-TIME"), |t| (false, t)),
|
||||||
| map!(tag!("#EXT-X-DATERANGE"), |t| (false, t))
|
map(tag("#EXT-X-DATERANGE"), |t| (false, t)),
|
||||||
))
|
))),
|
||||||
>> consume_line
|
consume_line,
|
||||||
>>
|
)),
|
||||||
( {
|
|(_, tag, _)| tag.map(|(a,b)| (a, from_utf8_slice(b).unwrap())),
|
||||||
tag.map(|(a,b)| (a, from_utf8_slice(b).unwrap()))
|
)(i)
|
||||||
} )
|
}
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
// Master Playlist Tags
|
// Master Playlist Tags
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
pub fn parse_master_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec<MasterPlaylistTag>> {
|
pub fn parse_master_playlist_tags(i: &[u8]) -> IResult<&[u8], Vec<MasterPlaylistTag>> {
|
||||||
do_parse!(
|
map(
|
||||||
input,
|
tuple((
|
||||||
tags: many0!(complete!(do_parse!(
|
many0(
|
||||||
m: master_playlist_tag >> multispace0 >> (m)
|
complete(
|
||||||
))) >> opt!(eof!())
|
map(
|
||||||
>> ({
|
pair(master_playlist_tag, multispace0),
|
||||||
let mut tags_rev: Vec<MasterPlaylistTag> = tags;
|
|(tag, _)| tag,
|
||||||
tags_rev.reverse();
|
)
|
||||||
tags_rev
|
)
|
||||||
})
|
),
|
||||||
)
|
opt(eof),
|
||||||
|
)),
|
||||||
|
|(tags, _)| {
|
||||||
|
let mut tags_rev: Vec<MasterPlaylistTag> = tags;
|
||||||
|
tags_rev.reverse();
|
||||||
|
tags_rev
|
||||||
|
},
|
||||||
|
)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains all the tags required to parse a master playlist.
|
/// Contains all the tags required to parse a master playlist.
|
||||||
|
@ -288,24 +295,27 @@ pub enum MasterPlaylistTag {
|
||||||
Unknown(ExtTag),
|
Unknown(ExtTag),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn master_playlist_tag(input: &[u8]) -> IResult<&[u8], MasterPlaylistTag> {
|
pub fn master_playlist_tag(i: &[u8]) -> IResult<&[u8], MasterPlaylistTag> {
|
||||||
alt!(
|
// Don't accept empty inputs here
|
||||||
input,
|
peek(take(1usize))(i)?;
|
||||||
map!(m3u_tag, MasterPlaylistTag::M3U)
|
|
||||||
| map!(version_tag, MasterPlaylistTag::Version)
|
alt((
|
||||||
| map!(variant_stream_tag, MasterPlaylistTag::VariantStream)
|
map(m3u_tag, MasterPlaylistTag::M3U),
|
||||||
| map!(variant_i_frame_stream_tag, MasterPlaylistTag::VariantStream)
|
map(version_tag, MasterPlaylistTag::Version),
|
||||||
| map!(alternative_media_tag, MasterPlaylistTag::AlternativeMedia)
|
map(variant_stream_tag, MasterPlaylistTag::VariantStream),
|
||||||
| map!(session_data_tag, MasterPlaylistTag::SessionData)
|
map(variant_i_frame_stream_tag, MasterPlaylistTag::VariantStream),
|
||||||
| map!(session_key_tag, MasterPlaylistTag::SessionKey)
|
map(alternative_media_tag, MasterPlaylistTag::AlternativeMedia),
|
||||||
| map!(start_tag, MasterPlaylistTag::Start)
|
map(session_data_tag, MasterPlaylistTag::SessionData),
|
||||||
| map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| {
|
map(session_key_tag, MasterPlaylistTag::SessionKey),
|
||||||
MasterPlaylistTag::IndependentSegments
|
map(start_tag, MasterPlaylistTag::Start),
|
||||||
})
|
map(
|
||||||
| map!(ext_tag, MasterPlaylistTag::Unknown)
|
tag("#EXT-X-INDEPENDENT-SEGMENTS"),
|
||||||
| map!(comment_tag, MasterPlaylistTag::Comment)
|
|_| MasterPlaylistTag::IndependentSegments,
|
||||||
| map!(consume_line, MasterPlaylistTag::Uri)
|
),
|
||||||
)
|
map(ext_tag, MasterPlaylistTag::Unknown),
|
||||||
|
map(comment_tag, MasterPlaylistTag::Comment),
|
||||||
|
map(consume_line, MasterPlaylistTag::Uri),
|
||||||
|
))(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn master_playlist_from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist {
|
pub fn master_playlist_from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlaylist {
|
||||||
|
@ -349,48 +359,79 @@ pub fn master_playlist_from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlay
|
||||||
master_playlist
|
master_playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(pub variant_stream_tag<VariantStream>,
|
pub fn variant_stream_tag(i: &[u8]) -> IResult<&[u8], VariantStream> {
|
||||||
do_parse!(tag!("#EXT-X-STREAM-INF:") >> attributes: key_value_pairs >>
|
map(
|
||||||
( VariantStream::from_hashmap(attributes, false)))
|
pair(
|
||||||
);
|
tag("#EXT-X-STREAM-INF:"),
|
||||||
|
key_value_pairs,
|
||||||
|
),
|
||||||
|
|(_, attributes)| VariantStream::from_hashmap(attributes, false),
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub variant_i_frame_stream_tag<VariantStream>,
|
pub fn variant_i_frame_stream_tag(i: &[u8]) -> IResult<&[u8], VariantStream> {
|
||||||
do_parse!( tag!("#EXT-X-I-FRAME-STREAM-INF:") >> attributes: key_value_pairs >>
|
map(
|
||||||
( VariantStream::from_hashmap(attributes, true)))
|
pair(
|
||||||
);
|
tag("#EXT-X-I-FRAME-STREAM-INF:"),
|
||||||
|
key_value_pairs,
|
||||||
|
),
|
||||||
|
|(_, attributes)| VariantStream::from_hashmap(attributes, true),
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub alternative_media_tag<AlternativeMedia>,
|
pub fn alternative_media_tag(i: &[u8]) -> IResult<&[u8], AlternativeMedia> {
|
||||||
do_parse!( tag!("#EXT-X-MEDIA:") >> attributes: key_value_pairs >>
|
map(
|
||||||
( AlternativeMedia::from_hashmap(attributes)))
|
pair(
|
||||||
);
|
tag("#EXT-X-MEDIA:"),
|
||||||
|
key_value_pairs,
|
||||||
|
),
|
||||||
|
|(_, media)| AlternativeMedia::from_hashmap(media),
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub session_data_tag<SessionData>,
|
pub fn session_data_tag(i: &[u8]) -> IResult<&[u8], SessionData> {
|
||||||
do_parse!( tag!("#EXT-X-SESSION-DATA:") >>
|
map_res(
|
||||||
session_data: map_res!(key_value_pairs, |attrs| SessionData::from_hashmap(attrs)) >>
|
pair(
|
||||||
( session_data))
|
tag("#EXT-X-SESSION-DATA:"),
|
||||||
);
|
key_value_pairs,
|
||||||
|
),
|
||||||
|
|(_, session_data)| SessionData::from_hashmap(session_data),
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub session_key_tag<SessionKey>,
|
pub fn session_key_tag(i: &[u8]) -> IResult<&[u8], SessionKey> {
|
||||||
do_parse!( tag!("#EXT-X-SESSION-KEY:") >> session_key: map!(key, SessionKey) >>
|
map(
|
||||||
( session_key))
|
pair(
|
||||||
);
|
tag("#EXT-X-SESSION-KEY:"),
|
||||||
|
key,
|
||||||
|
),
|
||||||
|
|(_, key)| SessionKey(key),
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
// Media Playlist
|
// Media Playlist
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
pub fn parse_media_playlist_tags(input: &[u8]) -> IResult<&[u8], Vec<MediaPlaylistTag>> {
|
pub fn parse_media_playlist_tags(i: &[u8]) -> IResult<&[u8], Vec<MediaPlaylistTag>> {
|
||||||
do_parse!(
|
map(
|
||||||
input,
|
tuple((
|
||||||
tags: many0!(complete!(do_parse!(
|
many0(
|
||||||
m: media_playlist_tag >> multispace0 >> (m)
|
complete(
|
||||||
))) >> opt!(eof!())
|
map(
|
||||||
>> ({
|
pair(media_playlist_tag, multispace0),
|
||||||
let mut tags_rev: Vec<MediaPlaylistTag> = tags;
|
|(tag, _)| tag,
|
||||||
tags_rev.reverse();
|
)
|
||||||
tags_rev
|
)
|
||||||
})
|
),
|
||||||
)
|
opt(eof),
|
||||||
|
)),
|
||||||
|
|(tags, _)| {
|
||||||
|
let mut tags_rev: Vec<MediaPlaylistTag> = tags;
|
||||||
|
tags_rev.reverse();
|
||||||
|
tags_rev
|
||||||
|
},
|
||||||
|
)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains all the tags required to parse a media playlist.
|
/// Contains all the tags required to parse a media playlist.
|
||||||
|
@ -410,37 +451,56 @@ pub enum MediaPlaylistTag {
|
||||||
IndependentSegments,
|
IndependentSegments,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn media_playlist_tag(input: &[u8]) -> IResult<&[u8], MediaPlaylistTag> {
|
pub fn media_playlist_tag(i: &[u8]) -> IResult<&[u8], MediaPlaylistTag> {
|
||||||
alt!(
|
// Don't accept empty inputs here
|
||||||
input,
|
peek(take(1usize))(i)?;
|
||||||
map!(m3u_tag, MediaPlaylistTag::M3U)
|
|
||||||
| map!(version_tag, MediaPlaylistTag::Version)
|
alt((
|
||||||
| map!(
|
map(m3u_tag, MediaPlaylistTag::M3U),
|
||||||
do_parse!(tag!("#EXT-X-TARGETDURATION:") >> n: float >> (n)),
|
map(version_tag, MediaPlaylistTag::Version),
|
||||||
MediaPlaylistTag::TargetDuration
|
map(
|
||||||
)
|
pair(
|
||||||
| map!(
|
tag("#EXT-X-TARGETDURATION:"),
|
||||||
do_parse!(tag!("#EXT-X-MEDIA-SEQUENCE:") >> n: number >> (n)),
|
float,
|
||||||
MediaPlaylistTag::MediaSequence
|
),
|
||||||
)
|
|(_, duration)| MediaPlaylistTag::TargetDuration(duration),
|
||||||
| map!(
|
),
|
||||||
do_parse!(tag!("#EXT-X-DISCONTINUITY-SEQUENCE:") >> n: number >> (n)),
|
map(
|
||||||
MediaPlaylistTag::DiscontinuitySequence
|
pair(
|
||||||
)
|
tag("#EXT-X-MEDIA-SEQUENCE:"),
|
||||||
| map!(
|
number,
|
||||||
do_parse!(tag!("#EXT-X-PLAYLIST-TYPE:") >> t: playlist_type >> (t)),
|
),
|
||||||
MediaPlaylistTag::PlaylistType
|
|(_, sequence)| MediaPlaylistTag::MediaSequence(sequence),
|
||||||
)
|
),
|
||||||
| map!(tag!("#EXT-X-I-FRAMES-ONLY"), |_| {
|
map(
|
||||||
MediaPlaylistTag::IFramesOnly
|
pair(
|
||||||
})
|
tag("#EXT-X-DISCONTINUITY-SEQUENCE:"),
|
||||||
| map!(start_tag, MediaPlaylistTag::Start)
|
number,
|
||||||
| map!(tag!("#EXT-X-INDEPENDENT-SEGMENTS"), |_| {
|
),
|
||||||
MediaPlaylistTag::IndependentSegments
|
|(_, sequence)| MediaPlaylistTag::DiscontinuitySequence(sequence),
|
||||||
})
|
),
|
||||||
| map!(tag!("#EXT-X-ENDLIST"), |_| MediaPlaylistTag::EndList)
|
map(
|
||||||
| map!(media_segment_tag, MediaPlaylistTag::Segment)
|
pair(
|
||||||
)
|
tag("#EXT-X-PLAYLIST-TYPE:"),
|
||||||
|
playlist_type,
|
||||||
|
),
|
||||||
|
|(_, typ)| MediaPlaylistTag::PlaylistType(typ),
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
))(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn media_playlist_from_tags(mut tags: Vec<MediaPlaylistTag>) -> MediaPlaylist {
|
pub fn media_playlist_from_tags(mut tags: Vec<MediaPlaylistTag>) -> MediaPlaylist {
|
||||||
|
@ -521,16 +581,15 @@ pub fn media_playlist_from_tags(mut tags: Vec<MediaPlaylistTag>) -> MediaPlaylis
|
||||||
media_playlist
|
media_playlist
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(pub playlist_type<MediaPlaylistType>,
|
pub fn playlist_type(i: &[u8]) -> IResult<&[u8], MediaPlaylistType> {
|
||||||
map_res!(
|
map_res(
|
||||||
do_parse!(
|
tuple((
|
||||||
p: map_res!(is_not!("\r\n"), str::from_utf8)
|
map_res(is_not("\r\n"), str::from_utf8),
|
||||||
>> take!(1)
|
take(1usize),
|
||||||
>> (p)
|
)),
|
||||||
),
|
|(typ, _)| MediaPlaylistType::from_str(typ),
|
||||||
MediaPlaylistType::from_str
|
)(i)
|
||||||
)
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
// Media Segment
|
// Media Segment
|
||||||
|
@ -551,175 +610,269 @@ pub enum SegmentTag {
|
||||||
Uri(String),
|
Uri(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn media_segment_tag(input: &[u8]) -> IResult<&[u8], SegmentTag> {
|
pub fn media_segment_tag(i: &[u8]) -> IResult<&[u8], SegmentTag> {
|
||||||
alt!(
|
alt((
|
||||||
input,
|
map(
|
||||||
map!(
|
pair(
|
||||||
do_parse!(tag!("#EXTINF:") >> e: duration_title_tag >> (e)),
|
tag("#EXTINF:"),
|
||||||
|(a, b)| SegmentTag::Extinf(a, b)
|
duration_title_tag,
|
||||||
) | map!(
|
),
|
||||||
do_parse!(tag!("#EXT-X-BYTERANGE:") >> r: byte_range_val >> (r)),
|
|(_, (duration, title))| SegmentTag::Extinf(duration, title),
|
||||||
SegmentTag::ByteRange
|
),
|
||||||
) | map!(tag!("#EXT-X-DISCONTINUITY"), |_| SegmentTag::Discontinuity)
|
map(
|
||||||
| map!(
|
pair(
|
||||||
do_parse!(tag!("#EXT-X-KEY:") >> k: key >> (k)),
|
tag("#EXT-X-BYTERANGE:"),
|
||||||
SegmentTag::Key
|
byte_range_val,
|
||||||
)
|
),
|
||||||
| map!(
|
|(_, range)| SegmentTag::ByteRange(range),
|
||||||
do_parse!(tag!("#EXT-X-MAP:") >> m: extmap >> (m)),
|
),
|
||||||
SegmentTag::Map
|
map(
|
||||||
)
|
tag("#EXT-X-DISCONTINUITY"),
|
||||||
| map!(
|
|_| SegmentTag::Discontinuity
|
||||||
do_parse!(tag!("#EXT-X-PROGRAM-DATE-TIME:") >> t: consume_line >> (t)),
|
),
|
||||||
SegmentTag::ProgramDateTime
|
map(
|
||||||
)
|
pair(
|
||||||
| map!(
|
tag("#EXT-X-KEY:"),
|
||||||
do_parse!(tag!("#EXT-X-DATE-RANGE:") >> t: consume_line >> (t)),
|
key,
|
||||||
SegmentTag::DateRange
|
),
|
||||||
)
|
|(_, key)| SegmentTag::Key(key),
|
||||||
| map!(ext_tag, SegmentTag::Unknown)
|
),
|
||||||
| map!(comment_tag, SegmentTag::Comment)
|
map(
|
||||||
| map!(consume_line, SegmentTag::Uri)
|
pair(
|
||||||
)
|
tag("#EXT-X-MAP:"),
|
||||||
|
extmap,
|
||||||
|
),
|
||||||
|
|(_, map)| SegmentTag::Map(map),
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
pair(
|
||||||
|
tag("#EXT-X-PROGRAM-DATE-TIME:"),
|
||||||
|
consume_line,
|
||||||
|
),
|
||||||
|
|(_, line)| SegmentTag::ProgramDateTime(line),
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
pair(
|
||||||
|
tag("#EXT-X-DATE-RANGE:"),
|
||||||
|
consume_line,
|
||||||
|
),
|
||||||
|
|(_, line)| SegmentTag::DateRange(line),
|
||||||
|
),
|
||||||
|
map(ext_tag, SegmentTag::Unknown),
|
||||||
|
map(comment_tag, SegmentTag::Comment),
|
||||||
|
map(consume_line, SegmentTag::Uri),
|
||||||
|
))(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(pub duration_title_tag<(f32, Option<String>)>,
|
pub fn duration_title_tag(i: &[u8]) -> IResult<&[u8], (f32, Option<String>)> {
|
||||||
do_parse!(
|
map(
|
||||||
duration: float
|
tuple((
|
||||||
>> opt!(tag!(","))
|
float,
|
||||||
>> title: opt!(map_res!(is_not!("\r\n,"), from_utf8_slice))
|
opt(char(',')),
|
||||||
>> take!(1)
|
opt(
|
||||||
>> opt!(tag!(","))
|
map_res(is_not("\r\n,"), from_utf8_slice),
|
||||||
>>
|
),
|
||||||
(duration, title)
|
take(1usize),
|
||||||
)
|
opt(char(',')),
|
||||||
);
|
)),
|
||||||
|
|(duration, _, title, _, _)| (duration, title),
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub key<Key>, map!(key_value_pairs, Key::from_hashmap));
|
pub fn key(i: &[u8]) -> IResult<&[u8], Key> {
|
||||||
|
map(
|
||||||
|
key_value_pairs,
|
||||||
|
Key::from_hashmap,
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub extmap<Map>, map!(key_value_pairs, |attrs| Map {
|
pub fn extmap(i: &[u8]) -> IResult<&[u8], Map> {
|
||||||
uri: attrs.get("URI").cloned().unwrap_or_default(),
|
map_res(
|
||||||
byte_range: attrs.get("BYTERANGE").map(|range| {
|
key_value_pairs,
|
||||||
match byte_range_val(range.as_bytes()) {
|
|attrs| -> Result<Map, &str> {
|
||||||
IResult::Ok((_, br)) => br,
|
let uri = attrs.get("URI").cloned().unwrap_or_default();
|
||||||
_ => panic!("Should not happen"),
|
let byte_range = attrs.get("BYTERANGE").map(|range|
|
||||||
|
match byte_range_val(range.as_bytes()) {
|
||||||
|
IResult::Ok((_, range)) => Ok(range),
|
||||||
|
IResult::Err(_) => Err("invalid byte range"),
|
||||||
|
}
|
||||||
|
).transpose()?;
|
||||||
|
|
||||||
|
Ok(Map {
|
||||||
|
uri,
|
||||||
|
byte_range,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}),
|
)(i)
|
||||||
}));
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
// Basic tags
|
// Basic tags
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
named!(pub m3u_tag<String>,
|
pub fn m3u_tag(i: &[u8]) -> IResult<&[u8], String> {
|
||||||
map_res!(tag!("#EXTM3U"), from_utf8_slice)
|
map_res(
|
||||||
);
|
tag("#EXTM3U"),
|
||||||
|
from_utf8_slice,
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub version_tag<usize>,
|
pub fn version_tag(i: &[u8]) -> IResult<&[u8], usize> {
|
||||||
do_parse!(
|
map(
|
||||||
tag!("#EXT-X-VERSION:") >> version: map_res!(digit1, str::from_utf8) >>
|
pair(
|
||||||
(version.parse().unwrap_or_default())
|
tag("#EXT-X-VERSION:"),
|
||||||
)
|
map_res(digit1, str::from_utf8),
|
||||||
);
|
),
|
||||||
|
|(_, version)| {
|
||||||
|
version.parse().unwrap_or_default()
|
||||||
|
}
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub start_tag<Start>,
|
pub fn start_tag(i: &[u8]) -> IResult<&[u8], Start> {
|
||||||
do_parse!(tag!("#EXT-X-START:") >> attributes:key_value_pairs >>
|
map(
|
||||||
(Start::from_hashmap(attributes))
|
pair(
|
||||||
)
|
tag("#EXT-X-START:"),
|
||||||
);
|
key_value_pairs,
|
||||||
|
),
|
||||||
|
|(_, attributes)| {
|
||||||
|
Start::from_hashmap(attributes)
|
||||||
|
}
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub ext_tag<ExtTag>,
|
pub fn ext_tag(i: &[u8]) -> IResult<&[u8], ExtTag> {
|
||||||
do_parse!(
|
map(
|
||||||
tag!("#EXT-")
|
tuple((
|
||||||
>> tag: map_res!(is_not!("\r\n:"), from_utf8_slice)
|
tag("#EXT-"),
|
||||||
>> opt!(tag!(":"))
|
map_res(is_not("\r\n:"), from_utf8_slice),
|
||||||
>> rest: opt!(map_res!(is_not!("\r\n"), from_utf8_slice))
|
opt(char(':')),
|
||||||
>> take!(1)
|
opt(map_res(is_not("\r\n"), from_utf8_slice)),
|
||||||
>> (
|
take(1usize),
|
||||||
|
)),
|
||||||
|
|(_, tag, _, rest, _)| {
|
||||||
ExtTag { tag, rest }
|
ExtTag { tag, rest }
|
||||||
)
|
}
|
||||||
)
|
)(i)
|
||||||
);
|
}
|
||||||
|
|
||||||
named!(pub comment_tag<String>,
|
pub fn comment_tag(i: &[u8]) -> IResult<&[u8], String> {
|
||||||
do_parse!(
|
map(
|
||||||
tag!("#") >> text: map_res!(is_not!("\r\n"), from_utf8_slice)
|
pair(
|
||||||
>> take!(1)
|
preceded(
|
||||||
>> (text)
|
char('#'),
|
||||||
)
|
map_res(is_not("\r\n"), from_utf8_slice),
|
||||||
);
|
),
|
||||||
|
take(1usize),
|
||||||
|
),
|
||||||
|
|(text, _)| text,
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
// Util
|
// Util
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
named!(pub key_value_pairs(&[u8]) -> HashMap<String, String>,
|
pub fn key_value_pairs(i: &[u8]) -> IResult<&[u8], HashMap<String, String>> {
|
||||||
map!(
|
fold_many0(
|
||||||
many0!(do_parse!(space0 >> k:key_value_pair >> (k) ))
|
preceded(space0, key_value_pair),
|
||||||
,
|
HashMap::new,
|
||||||
|pairs: Vec<(String, String)>| {
|
|mut acc: HashMap<_, _>, (left, right)| {
|
||||||
pairs.into_iter().collect()
|
acc.insert(left, right);
|
||||||
|
acc
|
||||||
}
|
}
|
||||||
)
|
)(i)
|
||||||
);
|
}
|
||||||
|
|
||||||
named!(pub key_value_pair(&[u8]) -> (String, String),
|
pub fn key_value_pair(i: &[u8]) -> IResult<&[u8], (String, String)> {
|
||||||
do_parse!(
|
map(
|
||||||
peek!(none_of!("\r\n"))
|
tuple((
|
||||||
>> left: map_res!(take_until!("="), from_utf8_slice)
|
peek(none_of("\r\n")),
|
||||||
>> take!(1)
|
map_res(take_until("="), from_utf8_slice),
|
||||||
>> right: alt!(quoted | unquoted)
|
char('='),
|
||||||
>> opt!(char!(','))
|
alt((quoted, unquoted)),
|
||||||
>>
|
opt(char(',')),
|
||||||
(left, right)
|
)),
|
||||||
)
|
|(_, left, _, right, _)| {
|
||||||
);
|
(left, right)
|
||||||
|
}
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub quoted<String>,
|
pub fn quoted(i: &[u8]) -> IResult<&[u8], String> {
|
||||||
delimited!(char!('\"'), map_res!(is_not!("\""), from_utf8_slice), char!('\"'))
|
delimited(
|
||||||
);
|
char('\"'),
|
||||||
|
map_res(
|
||||||
|
is_not("\""),
|
||||||
|
from_utf8_slice
|
||||||
|
),
|
||||||
|
char('\"')
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub unquoted<String>,
|
pub fn unquoted(i: &[u8]) -> IResult<&[u8], String> {
|
||||||
map_res!(is_not!(",\r\n"), from_utf8_slice)
|
map_res(
|
||||||
);
|
is_not(",\r\n"),
|
||||||
|
from_utf8_slice
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub consume_line<String>,
|
pub fn consume_line(i: &[u8]) -> IResult<&[u8], String> {
|
||||||
do_parse!(
|
map(
|
||||||
line: map_res!(not_line_ending, from_utf8_slice)
|
pair(
|
||||||
>> opt!(line_ending)
|
map_res(not_line_ending, from_utf8_slice),
|
||||||
>> (line)
|
opt(line_ending),
|
||||||
)
|
),
|
||||||
);
|
|(line, _)| line,
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub number<i32>,
|
pub fn number(i: &[u8]) -> IResult<&[u8], i32> {
|
||||||
map_res!(map_res!(digit1, str::from_utf8), str::FromStr::from_str)
|
map_res(take_while1(is_digit),
|
||||||
);
|
|s| {
|
||||||
|
// Can't fail because we validated it above already
|
||||||
|
let s = str::from_utf8(s).unwrap();
|
||||||
|
str::parse::<i32>(s)
|
||||||
|
})(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub byte_range_val<ByteRange>,
|
pub fn byte_range_val(i: &[u8]) -> IResult<&[u8], ByteRange> {
|
||||||
do_parse!(
|
map(
|
||||||
n: number
|
pair(
|
||||||
>> o: opt!(do_parse!(char!('@') >> n:number >> (n) )) >>
|
number,
|
||||||
(ByteRange { length: n, offset: o })
|
opt(
|
||||||
)
|
preceded(char('@'), number)
|
||||||
);
|
),
|
||||||
|
),
|
||||||
|
|(n, o)| {
|
||||||
|
ByteRange { length: n, offset: o }
|
||||||
|
}
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
named!(pub float<f32>,
|
pub fn float(i: &[u8]) -> IResult<&[u8], f32> {
|
||||||
do_parse!(
|
map_res(
|
||||||
left: map_res!(digit1, str::from_utf8)
|
pair(
|
||||||
>> right_opt: opt!(do_parse!(char!('.') >> d:map_res!(digit1, str::from_utf8) >> (d) ))
|
take_while1(is_digit),
|
||||||
>>
|
opt(
|
||||||
(
|
preceded(char('.'), take_while1(is_digit))
|
||||||
match right_opt {
|
),
|
||||||
|
),
|
||||||
|
|(left, right): (&[u8], Option<&[u8]>)| match right {
|
||||||
Some(right) => {
|
Some(right) => {
|
||||||
let mut num = String::from(left);
|
let n = &i[..(left.len() + right.len() + 1)];
|
||||||
num.push('.');
|
// Can't fail because we validated it above already
|
||||||
num.push_str(right);
|
let n = str::from_utf8(n).unwrap();
|
||||||
num.parse().unwrap()
|
n.parse()
|
||||||
},
|
}
|
||||||
None => left.parse().unwrap(),
|
None => {
|
||||||
})
|
// Can't fail because we validated it above already
|
||||||
)
|
let left = str::from_utf8(left).unwrap();
|
||||||
);
|
left.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)(i)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_utf8_slice(s: &[u8]) -> Result<String, string::FromUtf8Error> {
|
pub fn from_utf8_slice(s: &[u8]) -> Result<String, string::FromUtf8Error> {
|
||||||
String::from_utf8(s.to_vec())
|
String::from_utf8(s.to_vec())
|
||||||
|
|
|
@ -5,7 +5,7 @@ extern crate nom;
|
||||||
|
|
||||||
use m3u8_rs::playlist::*;
|
use m3u8_rs::playlist::*;
|
||||||
use m3u8_rs::*;
|
use m3u8_rs::*;
|
||||||
use nom::*;
|
use nom::AsBytes;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
Loading…
Reference in a new issue