2021-11-17 13:59:31 +00:00
use nom ::branch ::alt ;
use nom ::bytes ::complete ::{ is_a , is_not , tag , take , take_until , take_while1 } ;
use nom ::character ::complete ::{
char , digit1 , line_ending , multispace0 , none_of , not_line_ending , space0 ,
} ;
2021-11-17 12:37:55 +00:00
use nom ::character ::is_digit ;
2021-11-17 13:59:31 +00:00
use nom ::combinator ::{ complete , eof , map , map_res , opt , peek } ;
2021-11-17 12:37:55 +00:00
use nom ::multi ::{ fold_many0 , many0 } ;
2021-11-17 13:59:31 +00:00
use nom ::sequence ::{ delimited , pair , preceded , terminated , tuple } ;
2021-11-17 12:10:18 +00:00
2021-11-17 12:37:55 +00:00
use crate ::playlist ::* ;
2021-11-17 13:59:31 +00:00
use nom ::IResult ;
2021-10-18 09:41:28 +00:00
use std ::collections ::HashMap ;
2021-04-18 17:03:13 +00:00
use std ::f32 ;
2022-04-14 09:07:05 +00:00
use std ::fmt ;
2022-06-30 07:41:58 +00:00
use std ::fmt ::Display ;
2021-04-18 17:03:13 +00:00
use std ::result ::Result ;
2021-10-18 09:41:28 +00:00
use std ::str ;
use std ::str ::FromStr ;
use std ::string ;
2021-04-18 17:03:13 +00:00
/// Parse an m3u8 playlist.
///
/// # Examples
///
/// ```
/// use std::io::Read;
2021-11-18 13:04:17 +00:00
/// use m3u8_rs::Playlist;
2021-10-12 21:06:47 +00:00
///
2021-04-18 17:03:13 +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 {
/// 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 > {
2021-11-19 09:29:46 +00:00
m3u_tag ( input ) ? ;
2021-04-18 17:03:13 +00:00
match is_master_playlist ( input ) {
true = > map ( parse_master_playlist , Playlist ::MasterPlaylist ) ( input ) ,
2021-10-18 09:41:28 +00:00
false = > map ( parse_media_playlist , Playlist ::MediaPlaylist ) ( input ) ,
2021-04-18 17:03:13 +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).
2021-10-12 21:06:47 +00:00
/// 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.
///
2021-04-18 17:03:13 +00:00
/// # Examples
///
/// ```
2021-11-18 13:04:17 +00:00
/// use m3u8_rs::Playlist;
2021-04-18 17:03:13 +00:00
/// 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 {
/// Ok(Playlist::MasterPlaylist(pl)) => println!("Master playlist:\n{:?}", pl),
/// Ok(Playlist::MediaPlaylist(pl)) => println!("Media playlist:\n{:?}", pl),
/// Err(e) => println!("Error: {:?}", e)
/// }
/// ```
2021-11-18 12:49:50 +00:00
pub fn parse_playlist_res ( input : & [ u8 ] ) -> Result < Playlist , nom ::Err < nom ::error ::Error < & [ u8 ] > > > {
2021-04-18 17:03:13 +00:00
let parse_result = parse_playlist ( input ) ;
match parse_result {
IResult ::Ok ( ( _ , playlist ) ) = > Ok ( playlist ) ,
2021-11-18 12:49:50 +00:00
IResult ::Err ( err ) = > Err ( err ) ,
2021-04-18 17:03:13 +00:00
}
}
/// Parse input as a master playlist
pub fn parse_master_playlist ( input : & [ u8 ] ) -> IResult < & [ u8 ] , MasterPlaylist > {
2021-11-17 17:14:58 +00:00
map (
pair (
complete ( pair ( m3u_tag , multispace0 ) ) ,
parse_master_playlist_tags ,
) ,
| ( _ , tags ) | master_playlist_from_tags ( tags ) ,
) ( input )
2021-04-18 17:03:13 +00:00
}
/// Parse input as a master playlist
2021-10-18 09:41:28 +00:00
pub fn parse_master_playlist_res (
input : & [ u8 ] ,
2021-11-18 12:49:50 +00:00
) -> Result < MasterPlaylist , nom ::Err < nom ::error ::Error < & [ u8 ] > > > {
2021-04-18 17:03:13 +00:00
let parse_result = parse_master_playlist ( input ) ;
match parse_result {
IResult ::Ok ( ( _ , playlist ) ) = > Ok ( playlist ) ,
2021-11-18 12:49:50 +00:00
IResult ::Err ( err ) = > Err ( err ) ,
2021-04-18 17:03:13 +00:00
}
}
/// Parse input as a media playlist
pub fn parse_media_playlist ( input : & [ u8 ] ) -> IResult < & [ u8 ] , MediaPlaylist > {
2021-11-17 17:14:58 +00:00
map (
pair (
complete ( pair ( m3u_tag , multispace0 ) ) ,
parse_media_playlist_tags ,
) ,
| ( _ , tags ) | media_playlist_from_tags ( tags ) ,
) ( input )
2021-04-18 17:03:13 +00:00
}
/// Parse input as a media playlist
2021-10-18 09:41:28 +00:00
pub fn parse_media_playlist_res (
input : & [ u8 ] ,
2021-11-18 12:49:50 +00:00
) -> Result < MediaPlaylist , nom ::Err < nom ::error ::Error < & [ u8 ] > > > {
2021-04-18 17:03:13 +00:00
let parse_result = parse_media_playlist ( input ) ;
match parse_result {
IResult ::Ok ( ( _ , playlist ) ) = > Ok ( playlist ) ,
2021-11-18 12:49:50 +00:00
IResult ::Err ( err ) = > Err ( err ) ,
2021-04-18 17:03:13 +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`.
///
2022-06-30 07:41:58 +00:00
/// - None: Unknown tag or empty line
2021-04-18 17:03:13 +00:00
/// - Some(true, tagstring): Line contains a master playlist tag
/// - Some(false, tagstring): Line contains a media playlist tag
2021-11-18 12:49:50 +00:00
fn contains_master_tag ( input : & [ u8 ] ) -> Option < ( bool , String ) > {
2021-11-19 09:29:46 +00:00
let ( input , _ ) = m3u_tag ( input ) . ok ( ) ? ;
2021-04-18 17:03:13 +00:00
let mut is_master_opt = None ;
let mut current_input : & [ u8 ] = input ;
2022-12-02 05:16:17 +00:00
while is_master_opt . is_none ( ) & & ! current_input . is_empty ( ) {
2021-04-18 17:03:13 +00:00
match is_master_playlist_tag_line ( current_input ) {
IResult ::Ok ( ( rest , result ) ) = > {
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
}
2021-11-18 12:49:50 +00:00
fn is_master_playlist_tag_line ( i : & [ u8 ] ) -> IResult < & [ u8 ] , Option < ( bool , String ) > > {
2021-11-17 12:10:18 +00:00
map (
tuple ( (
opt ( is_a ( " \r \n " ) ) ,
opt ( alt ( (
2021-11-17 13:59:31 +00:00
map ( tag ( " #EXT-X-STREAM-INF " ) , | t | ( true , t ) ) ,
map ( tag ( " #EXT-X-I-FRAME-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-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 ) ) ,
2021-11-17 12:10:18 +00:00
) ) ) ,
consume_line ,
) ) ,
2021-11-17 13:59:31 +00:00
| ( _ , tag , _ ) | tag . map ( | ( a , b ) | ( a , from_utf8_slice ( b ) . unwrap ( ) ) ) ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2021-04-18 17:03:13 +00:00
// -----------------------------------------------------------------------------------------------
// Master Playlist Tags
// -----------------------------------------------------------------------------------------------
2021-11-18 12:49:50 +00:00
fn parse_master_playlist_tags ( i : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < MasterPlaylistTag > > {
2021-11-17 12:10:18 +00:00
map (
tuple ( (
2021-11-17 13:59:31 +00:00
many0 ( complete ( map (
pair ( master_playlist_tag , multispace0 ) ,
| ( tag , _ ) | tag ,
) ) ) ,
2021-11-17 12:10:18 +00:00
opt ( eof ) ,
) ) ,
| ( tags , _ ) | {
let mut tags_rev : Vec < MasterPlaylistTag > = tags ;
tags_rev . reverse ( ) ;
tags_rev
} ,
) ( i )
2021-04-18 17:03:13 +00:00
}
/// Contains all the tags required to parse a master playlist.
2021-11-18 12:54:46 +00:00
#[ allow(clippy::large_enum_variant) ]
2021-04-18 17:03:13 +00:00
#[ derive(Debug) ]
2021-11-18 12:49:50 +00:00
enum MasterPlaylistTag {
2021-04-18 17:03:13 +00:00
Version ( usize ) ,
VariantStream ( VariantStream ) ,
AlternativeMedia ( AlternativeMedia ) ,
SessionData ( SessionData ) ,
SessionKey ( SessionKey ) ,
Start ( Start ) ,
IndependentSegments ,
2023-12-01 03:55:10 +00:00
Comment ( Option < String > ) ,
2021-04-18 17:03:13 +00:00
Uri ( String ) ,
2021-10-14 19:21:03 +00:00
Unknown ( ExtTag ) ,
2021-04-18 17:03:13 +00:00
}
2021-11-18 12:49:50 +00:00
fn master_playlist_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , MasterPlaylistTag > {
2021-11-17 12:10:18 +00:00
// Don't accept empty inputs here
peek ( take ( 1 usize ) ) ( i ) ? ;
alt ( (
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 ) ,
2021-11-17 13:59:31 +00:00
map ( tag ( " #EXT-X-INDEPENDENT-SEGMENTS " ) , | _ | {
MasterPlaylistTag ::IndependentSegments
} ) ,
2021-11-17 12:10:18 +00:00
map ( ext_tag , MasterPlaylistTag ::Unknown ) ,
map ( comment_tag , MasterPlaylistTag ::Comment ) ,
map ( consume_line , MasterPlaylistTag ::Uri ) ,
) ) ( i )
2021-04-18 17:03:13 +00:00
}
2021-11-18 12:49:50 +00:00
fn master_playlist_from_tags ( mut tags : Vec < MasterPlaylistTag > ) -> MasterPlaylist {
2021-04-18 17:03:13 +00:00
let mut master_playlist = MasterPlaylist ::default ( ) ;
while let Some ( tag ) = tags . pop ( ) {
match tag {
MasterPlaylistTag ::Version ( v ) = > {
2022-04-17 08:08:07 +00:00
master_playlist . version = Some ( v ) ;
2021-04-18 17:03:13 +00:00
}
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 ;
}
2021-10-14 19:21:03 +00:00
MasterPlaylistTag ::Unknown ( unknown ) = > {
master_playlist . unknown_tags . push ( unknown ) ;
}
2021-04-18 17:03:13 +00:00
_ = > ( ) ,
}
}
master_playlist
}
2021-11-18 12:49:50 +00:00
fn variant_stream_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , VariantStream > {
2022-04-17 06:20:01 +00:00
map_res (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-STREAM-INF: " ) , key_value_pairs ) ,
2021-11-17 12:10:18 +00:00
| ( _ , attributes ) | VariantStream ::from_hashmap ( attributes , false ) ,
) ( i )
}
2021-04-18 17:03:13 +00:00
2021-11-18 12:49:50 +00:00
fn variant_i_frame_stream_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , VariantStream > {
2022-04-17 06:20:01 +00:00
map_res (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-I-FRAME-STREAM-INF: " ) , key_value_pairs ) ,
2021-11-17 12:10:18 +00:00
| ( _ , attributes ) | VariantStream ::from_hashmap ( attributes , true ) ,
) ( i )
}
2021-04-18 17:03:13 +00:00
2021-11-18 12:49:50 +00:00
fn alternative_media_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , AlternativeMedia > {
2022-06-30 07:41:58 +00:00
map_res ( pair ( tag ( " #EXT-X-MEDIA: " ) , key_value_pairs ) , | ( _ , media ) | {
2021-11-17 13:59:31 +00:00
AlternativeMedia ::from_hashmap ( media )
} ) ( i )
2021-11-17 12:10:18 +00:00
}
2021-04-18 17:03:13 +00:00
2021-11-18 12:49:50 +00:00
fn session_data_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , SessionData > {
2021-11-17 12:10:18 +00:00
map_res (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-SESSION-DATA: " ) , key_value_pairs ) ,
2021-11-17 12:10:18 +00:00
| ( _ , session_data ) | SessionData ::from_hashmap ( session_data ) ,
) ( i )
}
2021-04-18 17:03:13 +00:00
2021-11-18 12:49:50 +00:00
fn session_key_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , SessionKey > {
2021-11-17 13:59:31 +00:00
map ( pair ( tag ( " #EXT-X-SESSION-KEY: " ) , key ) , | ( _ , key ) | {
SessionKey ( key )
} ) ( i )
2021-11-17 12:10:18 +00:00
}
2021-04-18 17:03:13 +00:00
// -----------------------------------------------------------------------------------------------
// Media Playlist
// -----------------------------------------------------------------------------------------------
2021-11-18 12:49:50 +00:00
fn parse_media_playlist_tags ( i : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < MediaPlaylistTag > > {
2021-11-17 12:10:18 +00:00
map (
tuple ( (
2021-11-17 13:59:31 +00:00
many0 ( complete ( map (
pair ( media_playlist_tag , multispace0 ) ,
| ( tag , _ ) | tag ,
) ) ) ,
2021-11-17 12:10:18 +00:00
opt ( eof ) ,
) ) ,
| ( tags , _ ) | {
let mut tags_rev : Vec < MediaPlaylistTag > = tags ;
tags_rev . reverse ( ) ;
tags_rev
} ,
) ( i )
2021-04-18 17:03:13 +00:00
}
/// Contains all the tags required to parse a media playlist.
#[ derive(Debug) ]
2021-11-18 12:49:50 +00:00
enum MediaPlaylistTag {
2021-04-18 17:03:13 +00:00
Version ( usize ) ,
Segment ( SegmentTag ) ,
TargetDuration ( f32 ) ,
2022-01-07 10:47:03 +00:00
MediaSequence ( u64 ) ,
DiscontinuitySequence ( u64 ) ,
2021-04-18 17:03:13 +00:00
EndList ,
PlaylistType ( MediaPlaylistType ) ,
IFramesOnly ,
Start ( Start ) ,
IndependentSegments ,
}
2021-11-18 12:49:50 +00:00
fn media_playlist_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , MediaPlaylistTag > {
2021-11-17 12:10:18 +00:00
// Don't accept empty inputs here
peek ( take ( 1 usize ) ) ( i ) ? ;
alt ( (
map ( version_tag , MediaPlaylistTag ::Version ) ,
map (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-TARGETDURATION: " ) , float ) ,
2021-11-17 12:10:18 +00:00
| ( _ , duration ) | MediaPlaylistTag ::TargetDuration ( duration ) ,
) ,
map (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-MEDIA-SEQUENCE: " ) , number ) ,
2021-11-17 12:10:18 +00:00
| ( _ , sequence ) | MediaPlaylistTag ::MediaSequence ( sequence ) ,
) ,
map (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-DISCONTINUITY-SEQUENCE: " ) , number ) ,
2021-11-17 12:10:18 +00:00
| ( _ , sequence ) | MediaPlaylistTag ::DiscontinuitySequence ( sequence ) ,
) ,
map (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-PLAYLIST-TYPE: " ) , playlist_type ) ,
2021-11-17 12:10:18 +00:00
| ( _ , typ ) | MediaPlaylistTag ::PlaylistType ( typ ) ,
) ,
2021-11-17 13:59:31 +00:00
map ( tag ( " #EXT-X-I-FRAMES-ONLY " ) , | _ | {
MediaPlaylistTag ::IFramesOnly
} ) ,
2021-11-17 12:10:18 +00:00
map ( start_tag , MediaPlaylistTag ::Start ) ,
2021-11-17 13:59:31 +00:00
map ( tag ( " #EXT-X-INDEPENDENT-SEGMENTS " ) , | _ | {
MediaPlaylistTag ::IndependentSegments
} ) ,
map ( tag ( " #EXT-X-ENDLIST " ) , | _ | MediaPlaylistTag ::EndList ) ,
2021-11-17 12:10:18 +00:00
map ( media_segment_tag , MediaPlaylistTag ::Segment ) ,
) ) ( i )
2021-04-18 17:03:13 +00:00
}
2021-11-18 12:49:50 +00:00
fn media_playlist_from_tags ( mut tags : Vec < MediaPlaylistTag > ) -> MediaPlaylist {
2021-04-18 17:03:13 +00:00
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 ) = > {
2022-04-17 08:08:07 +00:00
media_playlist . version = Some ( v ) ;
2021-04-18 17:03:13 +00:00
}
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 ;
}
2021-10-18 09:41:28 +00:00
MediaPlaylistTag ::Segment ( segment_tag ) = > match segment_tag {
SegmentTag ::Extinf ( d , t ) = > {
next_segment . duration = d ;
next_segment . title = t ;
2021-04-18 17:03:13 +00:00
}
2021-10-18 09:41:28 +00:00
SegmentTag ::ByteRange ( b ) = > {
next_segment . byte_range = Some ( b ) ;
}
SegmentTag ::Discontinuity = > {
next_segment . discontinuity = true ;
}
SegmentTag ::Key ( k ) = > {
encryption_key = Some ( k ) ;
}
SegmentTag ::Map ( m ) = > {
map = Some ( m ) ;
}
SegmentTag ::ProgramDateTime ( d ) = > {
next_segment . program_date_time = Some ( d ) ;
}
SegmentTag ::DateRange ( d ) = > {
next_segment . daterange = Some ( d ) ;
}
SegmentTag ::Unknown ( t ) = > {
next_segment . unknown_tags . push ( t ) ;
}
SegmentTag ::Uri ( u ) = > {
next_segment . key = encryption_key . clone ( ) ;
next_segment . map = map . clone ( ) ;
next_segment . uri = u ;
media_playlist . segments . push ( next_segment ) ;
next_segment = MediaSegment ::empty ( ) ;
encryption_key = None ;
map = None ;
}
_ = > ( ) ,
} ,
2021-04-18 17:03:13 +00:00
}
}
media_playlist
}
2021-11-18 12:49:50 +00:00
fn playlist_type ( i : & [ u8 ] ) -> IResult < & [ u8 ] , MediaPlaylistType > {
2021-11-17 12:10:18 +00:00
map_res (
2021-11-17 13:59:31 +00:00
tuple ( ( map_res ( is_not ( " \r \n " ) , str ::from_utf8 ) , take ( 1 usize ) ) ) ,
2021-11-17 12:10:18 +00:00
| ( typ , _ ) | MediaPlaylistType ::from_str ( typ ) ,
) ( i )
}
2021-04-18 17:03:13 +00:00
// -----------------------------------------------------------------------------------------------
// Media Segment
// -----------------------------------------------------------------------------------------------
/// All possible media segment tags.
#[ derive(Debug) ]
2021-11-18 12:49:50 +00:00
enum SegmentTag {
2021-04-18 17:03:13 +00:00
Extinf ( f32 , Option < String > ) ,
ByteRange ( ByteRange ) ,
Discontinuity ,
Key ( Key ) ,
Map ( Map ) ,
2022-07-20 08:30:34 +00:00
ProgramDateTime ( chrono ::DateTime < chrono ::FixedOffset > ) ,
2022-06-30 07:41:58 +00:00
DateRange ( DateRange ) ,
2021-04-18 17:03:13 +00:00
Unknown ( ExtTag ) ,
2023-12-01 03:55:10 +00:00
Comment ( Option < String > ) ,
2021-04-18 17:03:13 +00:00
Uri ( String ) ,
}
2021-11-18 12:49:50 +00:00
fn media_segment_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , SegmentTag > {
2021-11-17 12:10:18 +00:00
alt ( (
map (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXTINF: " ) , duration_title_tag ) ,
2021-11-17 12:10:18 +00:00
| ( _ , ( duration , title ) ) | SegmentTag ::Extinf ( duration , title ) ,
) ,
map (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-BYTERANGE: " ) , byte_range_val ) ,
2021-11-17 12:10:18 +00:00
| ( _ , range ) | SegmentTag ::ByteRange ( range ) ,
) ,
2021-11-17 13:59:31 +00:00
map ( tag ( " #EXT-X-DISCONTINUITY " ) , | _ | SegmentTag ::Discontinuity ) ,
map ( pair ( tag ( " #EXT-X-KEY: " ) , key ) , | ( _ , key ) | {
SegmentTag ::Key ( key )
} ) ,
map ( pair ( tag ( " #EXT-X-MAP: " ) , extmap ) , | ( _ , map ) | {
SegmentTag ::Map ( map )
} ) ,
2021-11-17 12:10:18 +00:00
map (
2022-07-20 08:30:34 +00:00
pair ( tag ( " #EXT-X-PROGRAM-DATE-TIME: " ) , program_date_time ) ,
| ( _ , pdt ) | SegmentTag ::ProgramDateTime ( pdt ) ,
2021-11-17 12:10:18 +00:00
) ,
2022-06-30 07:41:58 +00:00
map ( pair ( tag ( " #EXT-X-DATERANGE: " ) , daterange ) , | ( _ , range ) | {
SegmentTag ::DateRange ( range )
} ) ,
2021-11-17 12:10:18 +00:00
map ( ext_tag , SegmentTag ::Unknown ) ,
map ( comment_tag , SegmentTag ::Comment ) ,
map ( consume_line , SegmentTag ::Uri ) ,
) ) ( i )
}
2021-11-18 12:49:50 +00:00
fn duration_title_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , ( f32 , Option < String > ) > {
2021-11-17 12:10:18 +00:00
map (
tuple ( (
float ,
opt ( char ( ',' ) ) ,
2021-11-17 13:59:31 +00:00
opt ( map_res ( is_not ( " \r \n , " ) , from_utf8_slice ) ) ,
2021-11-17 12:10:18 +00:00
take ( 1 usize ) ,
opt ( char ( ',' ) ) ,
) ) ,
| ( duration , _ , title , _ , _ ) | ( duration , title ) ,
) ( i )
}
2021-11-18 12:49:50 +00:00
fn key ( i : & [ u8 ] ) -> IResult < & [ u8 ] , Key > {
2022-06-30 07:41:58 +00:00
map_res ( key_value_pairs , Key ::from_hashmap ) ( i )
}
2022-07-20 08:30:34 +00:00
fn program_date_time ( i : & [ u8 ] ) -> IResult < & [ u8 ] , chrono ::DateTime < chrono ::FixedOffset > > {
map_res ( consume_line , | s | chrono ::DateTime ::parse_from_rfc3339 ( & s ) ) ( i )
}
2022-06-30 07:41:58 +00:00
fn daterange ( i : & [ u8 ] ) -> IResult < & [ u8 ] , DateRange > {
map_res ( key_value_pairs , DateRange ::from_hashmap ) ( i )
2021-11-17 12:10:18 +00:00
}
2021-11-18 12:49:50 +00:00
fn extmap ( i : & [ u8 ] ) -> IResult < & [ u8 ] , Map > {
2022-06-30 07:41:58 +00:00
map_res ( key_value_pairs , | mut attrs | -> Result < Map , & str > {
let uri = match attrs . remove ( " URI " ) {
Some ( QuotedOrUnquoted ::Quoted ( s ) ) = > Ok ( s ) ,
Some ( QuotedOrUnquoted ::Unquoted ( _ ) ) = > {
Err ( " Can't create URI attribute from unquoted string " )
}
None = > Err ( " URI is empty " ) ,
} ? ;
2023-04-12 03:23:35 +00:00
let byte_range = match attrs . remove ( " BYTERANGE " ) {
Some ( QuotedOrUnquoted ::Quoted ( s ) ) = > match byte_range_val ( s . as_bytes ( ) ) {
IResult ::Ok ( ( _ , range ) ) = > Ok ( Some ( range ) ) ,
2022-06-30 07:41:58 +00:00
IResult ::Err ( _ ) = > Err ( " Invalid byte range " ) ,
2023-04-12 03:23:35 +00:00
} ,
Some ( QuotedOrUnquoted ::Unquoted ( _ ) ) = > {
Err ( " Can't create BYTERANGE attribute from unquoted string " )
}
None = > Ok ( None ) ,
} ? ;
2021-11-17 13:59:31 +00:00
2022-04-14 09:07:05 +00:00
Ok ( Map {
2022-06-30 07:41:58 +00:00
uri ,
2022-04-14 09:07:05 +00:00
byte_range ,
2022-06-30 07:41:58 +00:00
other_attributes : attrs ,
2022-04-14 09:07:05 +00:00
} )
2021-11-17 13:59:31 +00:00
} ) ( i )
2021-11-17 12:10:18 +00:00
}
2021-04-18 17:03:13 +00:00
// -----------------------------------------------------------------------------------------------
// Basic tags
// -----------------------------------------------------------------------------------------------
2021-11-18 12:49:50 +00:00
fn m3u_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , ( ) > {
2021-11-17 17:14:58 +00:00
map ( tag ( " #EXTM3U " ) , | _ | ( ) ) ( i )
2021-11-17 12:10:18 +00:00
}
2021-11-18 12:49:50 +00:00
fn version_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , usize > {
2021-11-17 12:10:18 +00:00
map (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-VERSION: " ) , map_res ( digit1 , str ::from_utf8 ) ) ,
| ( _ , version ) | version . parse ( ) . unwrap_or_default ( ) ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2021-11-18 12:49:50 +00:00
fn start_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , Start > {
2022-06-30 07:41:58 +00:00
map_res (
2021-11-17 13:59:31 +00:00
pair ( tag ( " #EXT-X-START: " ) , key_value_pairs ) ,
| ( _ , attributes ) | Start ::from_hashmap ( attributes ) ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2021-11-18 12:49:50 +00:00
fn ext_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , ExtTag > {
2021-11-17 12:10:18 +00:00
map (
tuple ( (
tag ( " #EXT- " ) ,
map_res ( is_not ( " \r \n : " ) , from_utf8_slice ) ,
opt ( char ( ':' ) ) ,
opt ( map_res ( is_not ( " \r \n " ) , from_utf8_slice ) ) ,
take ( 1 usize ) ,
) ) ,
2021-11-17 13:59:31 +00:00
| ( _ , tag , _ , rest , _ ) | ExtTag { tag , rest } ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2023-12-01 03:55:10 +00:00
fn comment_tag ( i : & [ u8 ] ) -> IResult < & [ u8 ] , Option < String > > {
2021-11-17 12:10:18 +00:00
map (
pair (
2023-12-01 03:55:10 +00:00
preceded ( char ( '#' ) , opt ( map_res ( is_not ( " \r \n " ) , from_utf8_slice ) ) ) ,
2021-11-17 12:10:18 +00:00
take ( 1 usize ) ,
) ,
| ( text , _ ) | text ,
) ( i )
}
2021-04-18 17:03:13 +00:00
// -----------------------------------------------------------------------------------------------
// Util
// -----------------------------------------------------------------------------------------------
2022-04-14 09:07:05 +00:00
fn key_value_pairs ( i : & [ u8 ] ) -> IResult < & [ u8 ] , HashMap < String , QuotedOrUnquoted > > {
2021-11-17 12:10:18 +00:00
fold_many0 (
preceded ( space0 , key_value_pair ) ,
HashMap ::new ,
| mut acc : HashMap < _ , _ > , ( left , right ) | {
acc . insert ( left , right ) ;
acc
2021-11-17 13:59:31 +00:00
} ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2022-04-14 09:07:05 +00:00
#[ derive(Debug, PartialEq, Eq, Clone) ]
pub enum QuotedOrUnquoted {
Unquoted ( String ) ,
Quoted ( String ) ,
}
impl Default for QuotedOrUnquoted {
fn default ( ) -> Self {
QuotedOrUnquoted ::Quoted ( String ::new ( ) )
}
}
2022-04-17 06:19:47 +00:00
impl QuotedOrUnquoted {
pub fn as_str ( & self ) -> & str {
match self {
QuotedOrUnquoted ::Quoted ( s ) = > s . as_str ( ) ,
QuotedOrUnquoted ::Unquoted ( s ) = > s . as_str ( ) ,
}
}
pub fn as_unquoted ( & self ) -> Option < & str > {
match self {
QuotedOrUnquoted ::Unquoted ( s ) = > Some ( s . as_str ( ) ) ,
_ = > None ,
}
}
pub fn as_quoted ( & self ) -> Option < & str > {
match self {
QuotedOrUnquoted ::Quoted ( s ) = > Some ( s . as_str ( ) ) ,
_ = > None ,
}
}
}
2022-04-14 09:07:05 +00:00
impl From < & str > for QuotedOrUnquoted {
fn from ( s : & str ) -> Self {
if s . starts_with ( '"' ) & & s . ends_with ( '"' ) {
2022-06-30 07:41:58 +00:00
return QuotedOrUnquoted ::Quoted (
s . strip_prefix ( '"' )
. and_then ( | s | s . strip_suffix ( '"' ) )
. unwrap_or_default ( )
. to_string ( ) ,
) ;
2022-04-14 09:07:05 +00:00
}
QuotedOrUnquoted ::Unquoted ( s . to_string ( ) )
}
}
2022-06-30 07:41:58 +00:00
impl Display for QuotedOrUnquoted {
2022-04-14 09:07:05 +00:00
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2022-06-30 07:41:58 +00:00
match self {
QuotedOrUnquoted ::Unquoted ( s ) = > write! ( f , " {} " , s ) ,
QuotedOrUnquoted ::Quoted ( u ) = > write! ( f , " \" {} \" " , u ) ,
}
2022-04-14 09:07:05 +00:00
}
}
fn key_value_pair ( i : & [ u8 ] ) -> IResult < & [ u8 ] , ( String , QuotedOrUnquoted ) > {
2021-11-17 12:10:18 +00:00
map (
tuple ( (
peek ( none_of ( " \r \n " ) ) ,
map_res ( take_until ( " = " ) , from_utf8_slice ) ,
char ( '=' ) ,
alt ( ( quoted , unquoted ) ) ,
opt ( char ( ',' ) ) ,
) ) ,
2021-11-17 13:59:31 +00:00
| ( _ , left , _ , right , _ ) | ( left , right ) ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2022-04-14 09:07:05 +00:00
fn quoted ( i : & [ u8 ] ) -> IResult < & [ u8 ] , QuotedOrUnquoted > {
2021-11-17 12:10:18 +00:00
delimited (
char ( '\"' ) ,
2022-04-14 09:07:05 +00:00
map_res ( is_not ( " \" " ) , quoted_from_utf8_slice ) ,
2021-11-17 13:59:31 +00:00
char ( '\"' ) ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2022-04-14 09:07:05 +00:00
fn unquoted ( i : & [ u8 ] ) -> IResult < & [ u8 ] , QuotedOrUnquoted > {
map_res ( is_not ( " , \r \n " ) , unquoted_from_utf8_slice ) ( i )
2021-11-17 12:10:18 +00:00
}
2021-11-18 12:49:50 +00:00
fn consume_line ( i : & [ u8 ] ) -> IResult < & [ u8 ] , String > {
2021-11-17 12:10:18 +00:00
map (
2021-11-17 13:59:31 +00:00
pair ( map_res ( not_line_ending , from_utf8_slice ) , opt ( line_ending ) ) ,
2021-11-17 12:10:18 +00:00
| ( line , _ ) | line ,
) ( i )
}
2022-01-07 10:47:03 +00:00
fn number ( i : & [ u8 ] ) -> IResult < & [ u8 ] , u64 > {
2021-11-17 13:59:31 +00:00
map_res ( take_while1 ( is_digit ) , | s | {
// Can't fail because we validated it above already
let s = str ::from_utf8 ( s ) . unwrap ( ) ;
2022-01-07 10:47:03 +00:00
str ::parse ::< u64 > ( s )
2021-11-17 12:10:18 +00:00
} ) ( i )
}
2021-11-18 12:49:50 +00:00
fn byte_range_val ( i : & [ u8 ] ) -> IResult < & [ u8 ] , ByteRange > {
2021-11-17 13:59:31 +00:00
map ( pair ( number , opt ( preceded ( char ( '@' ) , number ) ) ) , | ( n , o ) | {
ByteRange {
length : n ,
offset : o ,
2021-04-18 17:03:13 +00:00
}
2021-11-17 13:59:31 +00:00
} ) ( i )
2021-11-17 12:10:18 +00:00
}
2021-11-18 12:49:50 +00:00
fn float ( i : & [ u8 ] ) -> IResult < & [ u8 ] , f32 > {
2021-11-17 12:10:18 +00:00
map_res (
pair (
take_while1 ( is_digit ) ,
2021-11-17 13:59:31 +00:00
opt ( preceded ( char ( '.' ) , take_while1 ( is_digit ) ) ) ,
2021-11-17 12:10:18 +00:00
) ,
| ( left , right ) : ( & [ u8 ] , Option < & [ u8 ] > ) | match right {
2021-04-18 17:03:13 +00:00
Some ( right ) = > {
2021-11-17 12:10:18 +00:00
let n = & i [ .. ( left . len ( ) + right . len ( ) + 1 ) ] ;
// Can't fail because we validated it above already
let n = str ::from_utf8 ( n ) . unwrap ( ) ;
n . parse ( )
}
None = > {
// Can't fail because we validated it above already
let left = str ::from_utf8 ( left ) . unwrap ( ) ;
left . parse ( )
}
2021-11-17 13:59:31 +00:00
} ,
2021-11-17 12:10:18 +00:00
) ( i )
}
2021-04-18 17:03:13 +00:00
2021-11-18 12:49:50 +00:00
fn from_utf8_slice ( s : & [ u8 ] ) -> Result < String , string ::FromUtf8Error > {
2021-04-18 17:03:13 +00:00
String ::from_utf8 ( s . to_vec ( ) )
}
2021-11-18 12:49:50 +00:00
2022-04-14 09:07:05 +00:00
fn quoted_from_utf8_slice ( s : & [ u8 ] ) -> Result < QuotedOrUnquoted , string ::FromUtf8Error > {
match String ::from_utf8 ( s . to_vec ( ) ) {
Ok ( q ) = > Ok ( QuotedOrUnquoted ::Quoted ( q ) ) ,
Err ( e ) = > Err ( e ) ,
}
}
fn unquoted_from_utf8_slice ( s : & [ u8 ] ) -> Result < QuotedOrUnquoted , string ::FromUtf8Error > {
match String ::from_utf8 ( s . to_vec ( ) ) {
Ok ( q ) = > Ok ( QuotedOrUnquoted ::Unquoted ( q ) ) ,
Err ( e ) = > Err ( e ) ,
}
}
2021-11-18 12:49:50 +00:00
#[ cfg(test) ]
mod tests {
use super ::* ;
use nom ::AsBytes ;
// -----------------------------------------------------------------------------------------------
// Variant
#[ test ]
fn variant_stream ( ) {
let input = b " #EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS= \" xxx \" \n " ;
assert_eq! (
variant_stream_tag ( input ) ,
Result ::Ok ( (
" \n " . as_bytes ( ) ,
VariantStream {
is_i_frame : false ,
uri : " " . into ( ) ,
2022-04-17 06:20:01 +00:00
bandwidth : 300000 ,
2021-11-18 12:49:50 +00:00
average_bandwidth : None ,
codecs : Some ( " xxx " . into ( ) ) ,
resolution : None ,
frame_rate : None ,
hdcp_level : None ,
audio : None ,
video : None ,
subtitles : None ,
closed_captions : None ,
2022-06-30 07:41:58 +00:00
other_attributes : Default ::default ( ) ,
2021-11-18 12:49:50 +00:00
}
) )
) ;
}
// -----------------------------------------------------------------------------------------------
// Other
#[ test ]
fn test_key_value_pairs_trailing_equals ( ) {
assert_eq! (
key_value_pairs ( b " BANDWIDTH=395000,CODECS= \" avc1.4d001f,mp4a.40.2 \" \r \n rest= " ) ,
Result ::Ok ( (
" \r \n rest= " . as_bytes ( ) ,
2022-04-14 09:07:05 +00:00
vec! [
( " BANDWIDTH " , " 395000 " ) ,
( " CODECS " , " \" avc1.4d001f,mp4a.40.2 \" " )
]
2022-07-20 08:30:34 +00:00
. into_iter ( )
. map ( | ( k , v ) | ( String ::from ( k ) , v . into ( ) ) )
. collect ::< HashMap < _ , _ > > ( ) ,
2021-11-18 12:49:50 +00:00
) ) ,
) ;
}
#[ test ]
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 \n rest " ) ,
Result ::Ok ( (
" \n rest " . as_bytes ( ) ,
vec! [
( " BANDWIDTH " , " 86000 " ) ,
2022-04-14 09:07:05 +00:00
( " URI " , " \" low/iframe.m3u8 \" " ) ,
2021-11-18 12:49:50 +00:00
( " PROGRAM-ID " , " 1 " ) ,
2022-04-14 09:07:05 +00:00
( " RESOLUTION " , " \" 1x1 \" " ) ,
2021-11-18 12:49:50 +00:00
( " VIDEO " , " 1 " )
] . into_iter ( )
2022-06-30 07:41:58 +00:00
. map ( | ( k , v ) | ( String ::from ( k ) , v . into ( ) ) )
. collect ::< HashMap < _ , _ > > ( )
2021-11-18 12:49:50 +00:00
) )
) ;
}
#[ test ]
fn test_key_value_pairs_quotes ( ) {
assert_eq! (
key_value_pairs ( b " BANDWIDTH=300000,CODECS= \" avc1.42c015,mp4a.40.2 \" \r \n rest " ) ,
Result ::Ok ( (
" \r \n rest " . as_bytes ( ) ,
2022-04-14 09:07:05 +00:00
vec! [
( " BANDWIDTH " , " 300000 " ) ,
( " CODECS " , " \" avc1.42c015,mp4a.40.2 \" " )
]
2022-07-20 08:30:34 +00:00
. into_iter ( )
. map ( | ( k , v ) | ( String ::from ( k ) , v . into ( ) ) )
. collect ::< HashMap < _ , _ > > ( )
2021-11-18 12:49:50 +00:00
) )
) ;
}
#[ test ]
fn test_key_value_pairs ( ) {
assert_eq! (
key_value_pairs ( b " BANDWIDTH=300000,RESOLUTION=22x22,VIDEO=1 \r \n rest= " ) ,
Result ::Ok ( (
" \r \n rest= " . as_bytes ( ) ,
vec! [
( " BANDWIDTH " , " 300000 " ) ,
( " RESOLUTION " , " 22x22 " ) ,
( " VIDEO " , " 1 " )
]
2022-07-20 08:30:34 +00:00
. into_iter ( )
. map ( | ( k , v ) | ( String ::from ( k ) , v . into ( ) ) )
. collect ::< HashMap < _ , _ > > ( )
2021-11-18 12:49:50 +00:00
) )
) ;
}
#[ test ]
fn test_key_value_pair ( ) {
assert_eq! (
key_value_pair ( b " PROGRAM-ID=1,rest " ) ,
2022-04-14 09:07:05 +00:00
Result ::Ok ( ( " rest " . as_bytes ( ) , ( " PROGRAM-ID " . to_string ( ) , " 1 " . into ( ) ) ) )
2021-11-18 12:49:50 +00:00
) ;
}
#[ test ]
fn ext_with_value ( ) {
assert_eq! (
ext_tag ( b " #EXT-X-CUE-OUT:DURATION=30 \n xxx " ) ,
Result ::Ok ( (
b " xxx " . as_bytes ( ) ,
ExtTag {
tag : " X-CUE-OUT " . into ( ) ,
rest : Some ( " DURATION=30 " . into ( ) )
}
) )
) ;
}
#[ test ]
fn ext_without_value ( ) {
assert_eq! (
ext_tag ( b " #EXT-X-CUE-IN \n xxx " ) ,
Result ::Ok ( (
b " xxx " . as_bytes ( ) ,
ExtTag {
tag : " X-CUE-IN " . into ( ) ,
rest : None
}
) )
) ;
}
#[ test ]
fn comment ( ) {
assert_eq! (
comment_tag ( b " #Hello \n xxx " ) ,
2023-12-01 03:55:10 +00:00
Result ::Ok ( ( " xxx " . as_bytes ( ) , Some ( " Hello " . to_string ( ) ) ) )
2021-11-18 12:49:50 +00:00
) ;
}
2023-12-01 04:05:20 +00:00
#[ test ]
fn empty_comment ( ) {
assert_eq! (
comment_tag ( b " # \n xxx " ) ,
Result ::Ok ( ( " xxx " . as_bytes ( ) , None ) )
) ;
}
2021-11-18 12:49:50 +00:00
#[ test ]
fn quotes ( ) {
assert_eq! (
quoted ( b " \" value \" rest " ) ,
2022-04-14 09:07:05 +00:00
Result ::Ok ( ( " rest " . as_bytes ( ) , " \" value \" " . into ( ) ) )
2021-11-18 12:49:50 +00:00
) ;
}
#[ test ]
fn consume_line_empty ( ) {
let expected = Result ::Ok ( ( " rest " . as_bytes ( ) , " " . to_string ( ) ) ) ;
let actual = consume_line ( b " \r \n rest " ) ;
assert_eq! ( expected , actual ) ;
}
#[ test ]
fn consume_line_n ( ) {
assert_eq! (
consume_line ( b " before \n rest " ) ,
Result ::Ok ( ( " rest " . as_bytes ( ) , " before " . into ( ) ) )
) ;
}
#[ test ]
fn consume_line_rn ( ) {
assert_eq! (
consume_line ( b " before \r \n rest " ) ,
Result ::Ok ( ( " rest " . as_bytes ( ) , " before " . into ( ) ) )
) ;
}
#[ test ]
fn float_ ( ) {
assert_eq! (
float ( b " 33.22rest " ) ,
Result ::Ok ( ( " rest " . as_bytes ( ) , 33.22 f32 ) )
) ;
}
#[ test ]
fn float_no_decimal ( ) {
assert_eq! ( float ( b " 33rest " ) , Result ::Ok ( ( " rest " . as_bytes ( ) , 33 f32 ) ) ) ;
}
#[ test ]
fn float_should_ignore_trailing_dot ( ) {
assert_eq! ( float ( b " 33.rest " ) , Result ::Ok ( ( " .rest " . as_bytes ( ) , 33 f32 ) ) ) ;
}
#[ test ]
fn parse_duration_title ( ) {
assert_eq! (
duration_title_tag ( b " 2.002,title \n rest " ) ,
Result ::Ok ( ( " rest " . as_bytes ( ) , ( 2.002 f32 , Some ( " title " . to_string ( ) ) ) ) )
) ;
}
2022-12-02 05:16:17 +00:00
#[ test ]
fn incomplete_manifest ( ) {
2022-12-02 08:21:08 +00:00
assert! ( ! is_master_playlist (
" #EXTM3U \n #EXT-X-VERSION:5 \n #EXT-X-TARGETDU " . as_bytes ( )
) ) ;
2022-12-02 05:16:17 +00:00
}
2021-11-18 12:49:50 +00:00
}