2016-06-03 18:56:45 +00:00
//! A library to parse m3u8 playlists (HTTP Live Streaming) [link]
//! (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19).
//!
2020-03-06 19:56:14 +00:00
//! # Examples
2016-06-03 18:56:45 +00:00
//!
//! Parsing a playlist and let the parser figure out if it's a media or master playlist.
//!
//! ```
//! extern crate nom;
2017-02-17 13:50:50 +00:00
//! extern crate m3u8_rs;
2016-06-03 18:56:45 +00:00
//! use m3u8_rs::playlist::Playlist;
//! use nom::IResult;
//! use std::io::Read;
//!
2017-02-17 16:22:09 +00:00
//! fn main() {
//! let mut file = std::fs::File::open("playlist.m3u8").unwrap();
//! let mut bytes: Vec<u8> = Vec::new();
//! file.read_to_end(&mut bytes).unwrap();
2016-06-03 18:56:45 +00:00
//!
2017-02-17 16:22:09 +00:00
//! match m3u8_rs::parse_playlist(&bytes) {
2020-03-06 19:56:14 +00:00
//! 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),
2017-02-17 16:22:09 +00:00
//! }
2016-06-03 18:56:45 +00:00
//! }
//! ```
//!
//! Parsing a master playlist directly
//!
//! ```
//! extern crate nom;
2017-02-17 13:50:50 +00:00
//! extern crate m3u8_rs;
2016-06-03 18:56:45 +00:00
//! use std::io::Read;
//! use nom::IResult;
//!
2017-02-17 16:22:09 +00:00
//! fn main() {
//! let mut file = std::fs::File::open("masterplaylist.m3u8").unwrap();
//! let mut bytes: Vec<u8> = Vec::new();
//! file.read_to_end(&mut bytes).unwrap();
2020-02-27 06:52:20 +00:00
//!
2020-03-06 19:56:14 +00:00
//! if let Result::Ok((_, pl)) = m3u8_rs::parse_master_playlist(&bytes) {
2017-02-17 16:22:09 +00:00
//! println!("{:?}", pl);
//! }
2016-06-03 18:56:45 +00:00
//! }
//!
//! ```
2017-02-17 14:16:08 +00:00
//!
//! Creating a playlist and writing it back to a vec/file
//!
//! ```
2017-02-17 16:22:09 +00:00
//! extern crate m3u8_rs;
//! use m3u8_rs::playlist::{MediaPlaylist, MediaPlaylistType, MediaSegment};
2017-02-17 14:16:08 +00:00
//!
2017-02-17 16:22:09 +00:00
//! fn main() {
2020-02-27 06:52:20 +00:00
//! let playlist = MediaPlaylist {
2017-02-17 16:22:09 +00:00
//! version: 6,
//! target_duration: 3.0,
//! media_sequence: 338559,
//! discontinuity_sequence: 1234,
//! end_list: true,
//! playlist_type: Some(MediaPlaylistType::Vod),
//! segments: vec![
//! MediaSegment {
//! uri: "20140311T113819-01-338559live.ts".into(),
//! duration: 2.002,
//! title: Some("title".into()),
//! ..Default::default()
//! },
//! ],
//! ..Default::default()
//! };
2020-02-27 06:52:20 +00:00
//!
2017-02-17 16:22:09 +00:00
//! //let mut v: Vec<u8> = Vec::new();
//! //playlist.write_to(&mut v).unwrap();
2020-02-27 06:52:20 +00:00
//!
2017-02-17 16:22:09 +00:00
//! //let mut file = std::fs::File::open("playlist.m3u8").unwrap();
//! //playlist.write_to(&mut file).unwrap();
//! }
2017-02-17 14:16:08 +00:00
//!
//! ```
2016-06-03 18:56:45 +00:00
extern crate nom ;
pub mod playlist ;
2021-04-18 16:51:29 +00:00
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 ,
2020-02-27 01:05:14 +00:00
alt , do_parse , opt , named , map , map_res , eof , many0 , take , take_until , char } ;
2021-04-18 16:51:29 +00:00
use self ::nom ::combinator ::map ;
use self ::nom ::character ::complete ::{ line_ending , not_line_ending } ;
2016-06-03 18:56:45 +00:00
use std ::str ;
use std ::f32 ;
use std ::string ;
use std ::str ::FromStr ;
use std ::result ::Result ;
use std ::collections ::HashMap ;
use playlist ::* ;
// -----------------------------------------------------------------------------------------------
// Playlist parser
// -----------------------------------------------------------------------------------------------
2020-03-06 19:56:14 +00:00
/// Parse an m3u8 playlist.
2016-06-03 18:56:45 +00:00
///
2020-03-06 19:56:14 +00:00
/// # Examples
2016-06-03 18:56:45 +00:00
///
2020-03-06 19:56:14 +00:00
/// ```
/// use std::io::Read;
/// use m3u8_rs::playlist::{Playlist};
///
2016-06-03 18:56:45 +00:00
/// let mut file = std::fs::File::open("playlist.m3u8").unwrap();
/// let mut bytes: Vec<u8> = Vec::new();
/// file.read_to_end(&mut bytes).unwrap();
///
/// let parsed = m3u8_rs::parse_playlist(&bytes);
///
/// let playlist = match parsed {
2020-03-06 19:56:14 +00:00
/// Result::Ok((i, playlist)) => playlist,
/// Result::Err(e) => panic!("Parsing error: \n{}", e),
2016-06-03 18:56:45 +00:00
/// };
///
/// match playlist {
2017-02-17 13:50:50 +00:00
/// Playlist::MasterPlaylist(pl) => println!("Master playlist:\n{:?}", pl),
/// Playlist::MediaPlaylist(pl) => println!("Media playlist:\n{:?}", pl),
2016-06-03 18:56:45 +00:00
/// }
2020-03-06 19:56:14 +00:00
/// ```
2016-06-03 18:56:45 +00:00
pub fn parse_playlist ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Playlist > {
match is_master_playlist ( input ) {
2020-02-27 06:52:20 +00:00
true = > map ( parse_master_playlist , Playlist ::MasterPlaylist ) ( input ) ,
false = > map ( parse_media_playlist , Playlist ::MediaPlaylist ) ( input ) ,
2016-06-03 18:56:45 +00:00
}
}
2020-03-06 19:56:14 +00:00
/// 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
2016-06-03 18:56:45 +00:00
///
/// ```
/// use m3u8_rs::playlist::{Playlist};
/// use std::io::Read;
///
/// let mut file = std::fs::File::open("playlist.m3u8").unwrap();
/// let mut bytes: Vec<u8> = Vec::new();
/// file.read_to_end(&mut bytes).unwrap();
///
/// let parsed = m3u8_rs::parse_playlist_res(&bytes);
///
/// match parsed {
2017-02-17 13:50:50 +00:00
/// Ok(Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl),
/// Ok(Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl),
2016-06-03 18:56:45 +00:00
/// Err(e) => println!("Error: {:?}", e)
/// }
/// ```
pub fn parse_playlist_res ( input : & [ u8 ] ) -> Result < Playlist , IResult < & [ u8 ] , Playlist > > {
let parse_result = parse_playlist ( input ) ;
match parse_result {
2020-02-12 22:38:15 +00:00
IResult ::Ok ( ( _ , playlist ) ) = > Ok ( playlist ) ,
2016-06-03 18:56:45 +00:00
_ = > Err ( parse_result ) ,
}
}
/// Parse input as a master playlist
pub fn parse_master_playlist ( input : & [ u8 ] ) -> IResult < & [ u8 ] , MasterPlaylist > {
2021-04-18 16:51:29 +00:00
map ( parse_master_playlist_tags , master_playlist_from_tags ) ( input )
2016-06-03 18:56:45 +00:00
}
2017-02-17 13:50:50 +00:00
/// 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 {
2020-02-12 22:38:15 +00:00
IResult ::Ok ( ( _ , playlist ) ) = > Ok ( playlist ) ,
2017-02-17 13:50:50 +00:00
_ = > Err ( parse_result ) ,
}
}
2016-06-03 18:56:45 +00:00
/// Parse input as a media playlist
pub fn parse_media_playlist ( input : & [ u8 ] ) -> IResult < & [ u8 ] , MediaPlaylist > {
2021-04-18 16:51:29 +00:00
map ( parse_media_playlist_tags , media_playlist_from_tags ) ( input )
2016-06-03 18:56:45 +00:00
}
2017-02-17 13:50:50 +00:00
/// 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 {
2020-02-12 22:38:15 +00:00
IResult ::Ok ( ( _ , playlist ) ) = > Ok ( playlist ) ,
2017-02-17 13:50:50 +00:00
_ = > Err ( parse_result ) ,
}
}
2016-06-03 18:56:45 +00:00
/// When a media tag or no master tag is found, this returns false.
pub fn is_master_playlist ( input : & [ u8 ] ) -> bool {
// Assume it's not a master playlist
contains_master_tag ( input ) . map ( | t | t . 0 ) . unwrap_or ( false )
}
/// Scans input looking for either a master or media `#EXT` tag.
///
/// Returns `Some(true/false)` when a master/media tag is found. Otherwise returns `None`.
///
/// - None: Unkown tag or empty line
/// - Some(true, tagstring): Line contains a master playlist tag
/// - Some(false, tagstring): Line contains a media playlist tag
pub fn contains_master_tag ( input : & [ u8 ] ) -> Option < ( bool , String ) > {
let mut is_master_opt = None ;
let mut current_input : & [ u8 ] = input ;
while is_master_opt = = None {
match is_master_playlist_tag_line ( current_input ) {
2020-02-12 22:38:15 +00:00
IResult ::Ok ( ( rest , result ) ) = > {
2016-06-03 18:56:45 +00:00
current_input = rest ;
is_master_opt = result ; // result can be None (no media or master tag found)
}
_ = > break , // Parser error encountered, can't read any more lines.
}
}
is_master_opt
}
named! ( pub is_master_playlist_tag_line ( & [ u8 ] ) -> Option < ( bool , String ) > ,
2020-02-12 20:04:20 +00:00
do_parse! (
2021-03-16 19:41:35 +00:00
opt! ( is_a! ( " \r \n " ) )
> > tag : opt ! ( alt! (
2016-06-03 18:56:45 +00:00
map! ( tag! ( " #EXT-X-STREAM-INF " ) , | t | ( true , t ) )
| map! ( tag! ( " #EXT-X-I-FRAME-STREAM-INF " ) , | t | ( true , t ) )
2019-03-08 05:52:34 +00:00
| 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
2016-06-03 18:56:45 +00:00
| 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-MEDIA-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-PLAYLIST-TYPE " ) , | t | ( false , t ) )
| map! ( tag! ( " #EXT-X-I-FRAMES-ONLY " ) , | t | ( false , t ) )
| map! ( tag! ( " #EXTINF " ) , | t | ( false , t ) )
| map! ( tag! ( " #EXT-X-BYTERANGE " ) , | t | ( false , t ) )
| map! ( tag! ( " #EXT-X-DISCONTINUITY " ) , | t | ( false , t ) )
| map! ( tag! ( " #EXT-X-KEY " ) , | 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-DATERANGE " ) , | t | ( false , t ) )
2020-02-27 01:05:14 +00:00
) )
> > consume_line
> >
2020-02-12 20:04:20 +00:00
( {
2016-06-03 18:56:45 +00:00
tag . map ( | ( a , b ) | ( a , from_utf8_slice ( b ) . unwrap ( ) ) )
2020-02-12 20:04:20 +00:00
} )
2016-06-03 18:56:45 +00:00
)
) ;
// -----------------------------------------------------------------------------------------------
// Master Playlist Tags
// -----------------------------------------------------------------------------------------------
2020-02-27 01:05:14 +00:00
pub fn parse_master_playlist_tags ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < MasterPlaylistTag > > {
2020-02-12 20:04:20 +00:00
do_parse! ( input ,
2020-02-27 01:05:14 +00:00
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 } )
2016-06-03 18:56:45 +00:00
)
}
2020-02-27 01:05:14 +00:00
2016-06-03 18:56:45 +00:00
/// Contains all the tags required to parse a master playlist.
#[ derive(Debug) ]
pub enum MasterPlaylistTag {
M3U ( String ) ,
Version ( usize ) ,
VariantStream ( VariantStream ) ,
AlternativeMedia ( AlternativeMedia ) ,
SessionData ( SessionData ) ,
SessionKey ( SessionKey ) ,
Start ( Start ) ,
IndependentSegments ,
Unknown ( ExtTag ) ,
Comment ( String ) ,
Uri ( String ) ,
}
pub fn master_playlist_tag ( input : & [ u8 ] ) -> IResult < & [ u8 ] , MasterPlaylistTag > {
alt! ( input ,
map! ( m3u_tag , MasterPlaylistTag ::M3U )
| map! ( version_tag , MasterPlaylistTag ::Version )
| map! ( variant_stream_tag , MasterPlaylistTag ::VariantStream )
| map! ( variant_i_frame_stream_tag , MasterPlaylistTag ::VariantStream )
| map! ( alternative_media_tag , MasterPlaylistTag ::AlternativeMedia )
| map! ( session_data_tag , MasterPlaylistTag ::SessionData )
| map! ( session_key_tag , MasterPlaylistTag ::SessionKey )
| map! ( start_tag , MasterPlaylistTag ::Start )
| map! ( tag! ( " #EXT-X-INDEPENDENT-SEGMENTS " ) , | _ | MasterPlaylistTag ::IndependentSegments )
| map! ( ext_tag , MasterPlaylistTag ::Unknown )
| map! ( comment_tag , MasterPlaylistTag ::Comment )
| map! ( consume_line , MasterPlaylistTag ::Uri )
)
}
2021-04-18 16:51:29 +00:00
pub fn master_playlist_from_tags ( mut tags : Vec < MasterPlaylistTag > ) -> MasterPlaylist {
let mut master_playlist = MasterPlaylist ::default ( ) ;
while let Some ( tag ) = tags . pop ( ) {
match tag {
MasterPlaylistTag ::Version ( v ) = > {
master_playlist . version = v ;
}
MasterPlaylistTag ::AlternativeMedia ( v ) = > {
master_playlist . alternatives . push ( v ) ;
}
MasterPlaylistTag ::VariantStream ( stream ) = > {
master_playlist . variants . push ( stream ) ;
}
MasterPlaylistTag ::Uri ( uri ) = > {
if let Some ( stream ) = master_playlist . get_newest_variant ( ) {
stream . uri = uri ;
}
}
MasterPlaylistTag ::SessionData ( data ) = > {
master_playlist . session_data . push ( data ) ;
}
MasterPlaylistTag ::SessionKey ( key ) = > {
master_playlist . session_key . push ( key ) ;
}
MasterPlaylistTag ::Start ( s ) = > {
master_playlist . start = Some ( s ) ;
}
MasterPlaylistTag ::IndependentSegments = > {
master_playlist . independent_segments = true ;
}
MasterPlaylistTag ::Unknown ( unknown ) = > {
master_playlist . unknown_tags . push ( unknown ) ;
}
_ = > ( ) ,
}
}
master_playlist
}
2016-06-03 18:56:45 +00:00
named! ( pub variant_stream_tag < VariantStream > ,
2020-02-12 20:04:20 +00:00
do_parse! ( tag! ( " #EXT-X-STREAM-INF: " ) > > attributes : key_value_pairs > >
( VariantStream ::from_hashmap ( attributes , false ) ) )
2016-06-03 18:56:45 +00:00
) ;
named! ( pub variant_i_frame_stream_tag < VariantStream > ,
2020-02-12 20:04:20 +00:00
do_parse! ( tag! ( " #EXT-X-I-FRAME-STREAM-INF: " ) > > attributes : key_value_pairs > >
( VariantStream ::from_hashmap ( attributes , true ) ) )
2016-06-03 18:56:45 +00:00
) ;
named! ( pub alternative_media_tag < AlternativeMedia > ,
2020-02-12 20:04:20 +00:00
do_parse! ( tag! ( " #EXT-X-MEDIA: " ) > > attributes : key_value_pairs > >
( AlternativeMedia ::from_hashmap ( attributes ) ) )
2016-06-03 18:56:45 +00:00
) ;
named! ( pub session_data_tag < SessionData > ,
2021-04-19 16:35:45 +00:00
do_parse! ( tag! ( " #EXT-X-SESSION-DATA: " ) > >
session_data : map_res ! ( key_value_pairs , | attrs | SessionData ::from_hashmap ( attrs ) ) > >
( session_data ) )
2016-06-03 18:56:45 +00:00
) ;
named! ( pub session_key_tag < SessionKey > ,
2020-02-12 20:04:20 +00:00
do_parse! ( tag! ( " #EXT-X-SESSION-KEY: " ) > > session_key : map ! ( key , SessionKey ) > >
( session_key ) )
2016-06-03 18:56:45 +00:00
) ;
// -----------------------------------------------------------------------------------------------
// Media Playlist
// -----------------------------------------------------------------------------------------------
pub fn parse_media_playlist_tags ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < MediaPlaylistTag > > {
2020-02-12 20:04:20 +00:00
do_parse! ( input ,
2020-02-27 01:05:14 +00:00
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 } )
2016-06-03 18:56:45 +00:00
)
}
/// Contains all the tags required to parse a media playlist.
#[ derive(Debug) ]
pub enum MediaPlaylistTag {
M3U ( String ) ,
Version ( usize ) ,
Segment ( SegmentTag ) ,
TargetDuration ( f32 ) ,
MediaSequence ( i32 ) ,
DiscontinuitySequence ( i32 ) ,
EndList ,
PlaylistType ( MediaPlaylistType ) ,
IFramesOnly ,
Start ( Start ) ,
IndependentSegments ,
}
pub fn media_playlist_tag ( input : & [ u8 ] ) -> IResult < & [ u8 ] , MediaPlaylistTag > {
alt! ( input ,
map! ( m3u_tag , MediaPlaylistTag ::M3U )
| map! ( version_tag , MediaPlaylistTag ::Version )
2020-02-12 20:04:20 +00:00
| 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 )
2016-06-03 18:56:45 +00:00
| map! ( tag! ( " #EXT-X-I-FRAMES-ONLY " ) , | _ | MediaPlaylistTag ::IFramesOnly )
| map! ( start_tag , MediaPlaylistTag ::Start )
| map! ( tag! ( " #EXT-X-INDEPENDENT-SEGMENTS " ) , | _ | MediaPlaylistTag ::IndependentSegments )
2017-02-17 13:50:50 +00:00
| map! ( tag! ( " #EXT-X-ENDLIST " ) , | _ | MediaPlaylistTag ::EndList )
2016-06-03 18:56:45 +00:00
| map! ( media_segment_tag , MediaPlaylistTag ::Segment )
)
}
2021-04-18 16:51:29 +00:00
pub fn media_playlist_from_tags ( mut tags : Vec < MediaPlaylistTag > ) -> MediaPlaylist {
let mut media_playlist = MediaPlaylist ::default ( ) ;
let mut next_segment = MediaSegment ::empty ( ) ;
let mut encryption_key = None ;
let mut map = None ;
while let Some ( tag ) = tags . pop ( ) {
match tag {
MediaPlaylistTag ::Version ( v ) = > {
media_playlist . version = v ;
}
MediaPlaylistTag ::TargetDuration ( d ) = > {
media_playlist . target_duration = d ;
}
MediaPlaylistTag ::MediaSequence ( n ) = > {
media_playlist . media_sequence = n ;
}
MediaPlaylistTag ::DiscontinuitySequence ( n ) = > {
media_playlist . discontinuity_sequence = n ;
}
MediaPlaylistTag ::EndList = > {
media_playlist . end_list = true ;
}
MediaPlaylistTag ::PlaylistType ( t ) = > {
media_playlist . playlist_type = Some ( t ) ;
}
MediaPlaylistTag ::IFramesOnly = > {
media_playlist . i_frames_only = true ;
}
MediaPlaylistTag ::Start ( s ) = > {
media_playlist . start = Some ( s ) ;
}
MediaPlaylistTag ::IndependentSegments = > {
media_playlist . independent_segments = true ;
}
MediaPlaylistTag ::Segment ( segment_tag ) = > {
match segment_tag {
SegmentTag ::Extinf ( d , t ) = > {
next_segment . duration = d ;
next_segment . title = t ;
}
SegmentTag ::ByteRange ( b ) = > {
next_segment . byte_range = Some ( b ) ;
}
SegmentTag ::Discontinuity = > {
next_segment . discontinuity = true ;
}
SegmentTag ::Key ( k ) = > {
encryption_key = Some ( k ) ;
}
SegmentTag ::Map ( m ) = > {
map = Some ( m ) ;
}
SegmentTag ::ProgramDateTime ( d ) = > {
next_segment . program_date_time = Some ( d ) ;
}
SegmentTag ::DateRange ( d ) = > {
next_segment . daterange = Some ( d ) ;
}
SegmentTag ::Unknown ( t ) = > {
media_playlist . unknown_tags . push ( t ) ;
}
SegmentTag ::Uri ( u ) = > {
next_segment . key = encryption_key . clone ( ) ;
next_segment . map = map . clone ( ) ;
next_segment . uri = u ;
media_playlist . segments . push ( next_segment ) ;
next_segment = MediaSegment ::empty ( ) ;
encryption_key = None ;
map = None ;
}
_ = > ( ) ,
}
}
_ = > ( ) ,
}
}
media_playlist
}
2017-02-17 13:50:50 +00:00
named! ( pub playlist_type < MediaPlaylistType > ,
2016-06-03 18:56:45 +00:00
map_res! (
2020-02-27 01:05:14 +00:00
do_parse! (
p : map_res ! ( is_not! ( " \r \n " ) , str ::from_utf8 )
> > take! ( 1 )
> > ( p )
) ,
2017-02-17 13:50:50 +00:00
MediaPlaylistType ::from_str
)
2016-06-03 18:56:45 +00:00
) ;
// -----------------------------------------------------------------------------------------------
// Media Segment
// -----------------------------------------------------------------------------------------------
/// All possible media segment tags.
#[ derive(Debug) ]
pub enum SegmentTag {
Extinf ( f32 , Option < String > ) ,
ByteRange ( ByteRange ) ,
Discontinuity ,
Key ( Key ) ,
Map ( Map ) ,
ProgramDateTime ( String ) ,
DateRange ( String ) ,
Unknown ( ExtTag ) ,
Comment ( String ) ,
Uri ( String ) ,
}
pub fn media_segment_tag ( input : & [ u8 ] ) -> IResult < & [ u8 ] , SegmentTag > {
alt! ( input ,
2020-02-12 20:04:20 +00:00
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 )
2016-06-03 18:56:45 +00:00
| map! ( tag! ( " #EXT-X-DISCONTINUITY " ) , | _ | SegmentTag ::Discontinuity )
2020-02-27 06:52:20 +00:00
| map! ( do_parse! ( tag! ( " #EXT-X-KEY: " ) > > k : key > > ( k ) ) , SegmentTag ::Key )
| map! ( do_parse! ( tag! ( " #EXT-X-MAP: " ) > > m : extmap > > ( m ) ) , SegmentTag ::Map )
2020-02-12 20:04:20 +00:00
| 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 )
2016-06-03 18:56:45 +00:00
| map! ( ext_tag , SegmentTag ::Unknown )
| map! ( comment_tag , SegmentTag ::Comment )
| map! ( consume_line , SegmentTag ::Uri )
)
}
named! ( pub duration_title_tag < ( f32 , Option < String > ) > ,
2020-02-12 20:04:20 +00:00
do_parse! (
duration : float
> > opt! ( tag! ( " , " ) )
2020-02-27 01:05:14 +00:00
> > title : opt ! ( map_res! ( is_not! ( " \r \n , " ) , from_utf8_slice ) )
> > take! ( 1 )
2020-02-12 20:04:20 +00:00
> > opt! ( tag! ( " , " ) )
> >
( duration , title )
2016-06-03 18:56:45 +00:00
)
) ;
named! ( pub key < Key > , map! ( key_value_pairs , Key ::from_hashmap ) ) ;
2021-04-18 16:51:29 +00:00
named! ( pub extmap < Map > , map! ( key_value_pairs , | attrs | Map {
uri : attrs . get ( " URI " ) . map ( | u | u . clone ( ) ) . unwrap_or_default ( ) ,
byte_range : attrs . get ( " BYTERANGE " ) . map ( | range | {
match byte_range_val ( range . as_bytes ( ) ) {
IResult ::Ok ( ( _ , br ) ) = > br ,
_ = > panic! ( " Should not happen " ) ,
}
} ) ,
} ) ) ;
2016-06-03 18:56:45 +00:00
// -----------------------------------------------------------------------------------------------
// Basic tags
// -----------------------------------------------------------------------------------------------
named! ( pub m3u_tag < String > ,
2020-02-27 01:05:14 +00:00
map_res! ( tag! ( " #EXTM3U " ) , from_utf8_slice )
2016-06-03 18:56:45 +00:00
) ;
named! ( pub version_tag < usize > ,
2020-02-12 20:04:20 +00:00
do_parse! (
2020-02-27 01:05:14 +00:00
tag! ( " #EXT-X-VERSION: " ) > > version : map_res ! ( digit1 , str ::from_utf8 ) > >
2020-02-12 20:04:20 +00:00
( version . parse ( ) . unwrap_or_default ( ) )
2016-06-03 18:56:45 +00:00
)
) ;
named! ( pub start_tag < Start > ,
2020-02-12 20:04:20 +00:00
do_parse! ( tag! ( " #EXT-X-START: " ) > > attributes :key_value_pairs > >
( Start ::from_hashmap ( attributes ) )
)
2016-06-03 18:56:45 +00:00
) ;
named! ( pub ext_tag < ExtTag > ,
2020-02-12 20:04:20 +00:00
do_parse! (
2016-06-03 18:56:45 +00:00
tag! ( " #EXT- " )
2020-02-12 20:04:20 +00:00
> > tag : map_res ! ( take_until! ( " : " ) , from_utf8_slice )
2020-02-27 01:05:14 +00:00
> > take! ( 1 )
> > rest : map_res ! ( is_not! ( " \r \n " ) , from_utf8_slice )
> > take! ( 1 )
2020-02-12 20:04:20 +00:00
> >
( ExtTag { tag : tag , rest : rest } )
2016-06-03 18:56:45 +00:00
)
) ;
named! ( pub comment_tag < String > ,
2020-02-12 20:04:20 +00:00
do_parse! (
2020-02-27 01:05:14 +00:00
tag! ( " # " ) > > text : map_res ! ( is_not! ( " \r \n " ) , from_utf8_slice )
> > take! ( 1 )
2020-02-12 20:04:20 +00:00
> > ( text )
2016-06-03 18:56:45 +00:00
)
) ;
// -----------------------------------------------------------------------------------------------
// Util
// -----------------------------------------------------------------------------------------------
named! ( pub key_value_pairs ( & [ u8 ] ) -> HashMap < String , String > ,
map! (
2020-02-27 01:05:14 +00:00
many0! ( do_parse! ( space0 > > k :key_value_pair > > ( k ) ) )
2016-06-03 18:56:45 +00:00
,
| pairs : Vec < ( String , String ) > | {
pairs . into_iter ( ) . collect ( )
}
)
) ;
named! ( pub key_value_pair ( & [ u8 ] ) -> ( String , String ) ,
2020-02-12 20:04:20 +00:00
do_parse! (
2016-06-03 18:56:45 +00:00
peek! ( none_of! ( " \r \n " ) )
2020-02-12 20:04:20 +00:00
> > left : map_res ! ( take_until! ( " = " ) , from_utf8_slice )
2020-02-27 01:05:14 +00:00
> > take! ( 1 )
2020-02-12 20:04:20 +00:00
> > right : alt ! ( quoted | unquoted )
> > opt! ( char ! ( ',' ) )
> >
( left , right )
2016-06-03 18:56:45 +00:00
)
) ;
named! ( pub quoted < String > ,
delimited! ( char ! ( '\"' ) , map_res! ( is_not! ( " \" " ) , from_utf8_slice ) , char ! ( '\"' ) )
) ;
named! ( pub unquoted < String > ,
2020-02-27 01:05:14 +00:00
map_res! ( is_not! ( " , \r \n " ) , from_utf8_slice )
2016-06-03 18:56:45 +00:00
) ;
named! ( pub consume_line < String > ,
2020-02-27 01:05:14 +00:00
do_parse! (
2021-04-18 09:59:31 +00:00
line : map_res ! ( not_line_ending , from_utf8_slice )
> > opt! ( line_ending )
2020-03-06 19:59:15 +00:00
> > ( line )
2020-02-27 01:05:14 +00:00
)
2016-06-03 18:56:45 +00:00
) ;
named! ( pub number < i32 > ,
2020-02-27 01:05:14 +00:00
map_res! ( map_res! ( digit1 , str ::from_utf8 ) , str ::FromStr ::from_str )
2016-06-03 18:56:45 +00:00
) ;
2017-02-17 13:50:50 +00:00
named! ( pub byte_range_val < ByteRange > ,
2020-02-12 20:04:20 +00:00
do_parse! (
2016-06-03 18:56:45 +00:00
n : number
2020-02-27 01:05:14 +00:00
> > o : opt ! ( do_parse! ( char ! ( '@' ) > > n :number > > ( n ) ) ) > >
2020-02-12 20:04:20 +00:00
( ByteRange { length : n , offset : o } )
2016-06-03 18:56:45 +00:00
)
) ;
named! ( pub float < f32 > ,
2020-02-12 20:04:20 +00:00
do_parse! (
2020-02-27 01:05:14 +00:00
left : map_res ! ( digit1 , str ::from_utf8 )
> > right_opt : opt ! ( do_parse! ( char ! ( '.' ) > > d :map_res ! ( digit1 , str ::from_utf8 ) > > ( d ) ) )
> >
2020-02-12 20:04:20 +00:00
(
2016-06-03 18:56:45 +00:00
match right_opt {
Some ( right ) = > {
let mut num = String ::from ( left ) ;
num . push ( '.' ) ;
num . push_str ( right ) ;
num . parse ( ) . unwrap ( )
} ,
None = > left . parse ( ) . unwrap ( ) ,
2020-02-12 20:04:20 +00:00
} )
2016-06-03 18:56:45 +00:00
)
) ;
pub fn from_utf8_slice ( s : & [ u8 ] ) -> Result < String , string ::FromUtf8Error > {
String ::from_utf8 ( s . to_vec ( ) )
}
pub fn from_utf8_slice2 ( s : & [ u8 ] ) -> Result < String , str ::Utf8Error > {
str ::from_utf8 ( s ) . map ( String ::from )
}