mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2025-01-03 03:38:40 +00:00
Support parsing of unknown tags on segments
This commit is contained in:
parent
06162a8554
commit
c1ff2b3730
6 changed files with 113 additions and 31 deletions
25
sample-playlists/media-playlist-with-cues-1.m3u8
Normal file
25
sample-playlists/media-playlist-with-cues-1.m3u8
Normal file
|
@ -0,0 +1,25 @@
|
|||
#EXTM3U
|
||||
# Borrowed from https://github.com/grafov/m3u8/pull/83
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
#EXT-X-TARGETDURATION:6
|
||||
#EXTINF:6.000,
|
||||
1.ts
|
||||
#EXT-X-DATERANGE:ID="20",START-DATE="2020-06-03T14:56:00Z",PLANNED-DURATION=19,SCTE35-OUT=0xFC302000000000000000FFF00F05000000147FFFFE001A17B0C0000000000061DFD67D
|
||||
#EXT-X-CUE-OUT:19.0
|
||||
#EXT-X-PROGRAM-DATE-TIME:2020-06-03T14:56:00Z
|
||||
#EXTINF:6.000,
|
||||
2.ts
|
||||
#EXTINF:6.000,
|
||||
3.ts
|
||||
#EXTINF:6.000,
|
||||
4.ts
|
||||
#EXT-X-CUE-IN
|
||||
#EXTINF:6.000,
|
||||
5.ts
|
||||
#EXTINF:6.000,
|
||||
6.ts
|
||||
#EXTINF:6.000,
|
||||
7.ts
|
||||
#EXTINF:6.000,
|
||||
8.ts
|
20
sample-playlists/media-playlist-with-cues.m3u8
Normal file
20
sample-playlists/media-playlist-with-cues.m3u8
Normal file
|
@ -0,0 +1,20 @@
|
|||
#EXTINF:10,
|
||||
http://media.example.com/fileSequence7796.ts
|
||||
#EXTINF:6,
|
||||
http://media.example.com/fileSequence7797.ts
|
||||
#EXT-X-CUE-OUT:DURATION=30
|
||||
#EXTINF:4,
|
||||
http://media.example.com/fileSequence7798.ts
|
||||
#EXTINF:10,
|
||||
http://media.example.com/fileSequence7799.ts
|
||||
#EXTINF:10,
|
||||
http://media.example.com/fileSequence7800.ts
|
||||
#EXTINF:6,
|
||||
http://media.example.com/fileSequence7801.ts
|
||||
#EXT-X-CUE-IN
|
||||
#EXTINF:4,
|
||||
http://media.example.com/fileSequence7802.ts
|
||||
#EXTINF:10,
|
||||
http://media.example.com/fileSequence7803.ts
|
||||
#EXTINF:3,
|
||||
http://media.example.com/fileSequence7804.ts
|
|
@ -6,4 +6,4 @@ pub mod playlist;
|
|||
mod parser;
|
||||
|
||||
#[cfg(feature = "parser")]
|
||||
pub use self::parser::*;
|
||||
pub use self::parser::*;
|
||||
|
|
|
@ -104,7 +104,7 @@ use playlist::*;
|
|||
/// ```
|
||||
/// 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();
|
||||
|
@ -129,8 +129,8 @@ pub fn parse_playlist(input: &[u8]) -> IResult<&[u8], Playlist> {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// ```
|
||||
|
@ -274,7 +274,6 @@ pub enum MasterPlaylistTag {
|
|||
SessionKey(SessionKey),
|
||||
Start(Start),
|
||||
IndependentSegments,
|
||||
Unknown(ExtTag),
|
||||
Comment(String),
|
||||
Uri(String),
|
||||
}
|
||||
|
@ -292,7 +291,6 @@ pub fn master_playlist_tag(input: &[u8]) -> IResult<&[u8], MasterPlaylistTag> {
|
|||
| 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)
|
||||
|
@ -330,9 +328,6 @@ pub fn master_playlist_from_tags(mut tags: Vec<MasterPlaylistTag>) -> MasterPlay
|
|||
MasterPlaylistTag::IndependentSegments => {
|
||||
master_playlist.independent_segments = true;
|
||||
}
|
||||
MasterPlaylistTag::Unknown(unknown) => {
|
||||
master_playlist.unknown_tags.push(unknown);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -392,6 +387,7 @@ pub enum MediaPlaylistTag {
|
|||
PlaylistType(MediaPlaylistType),
|
||||
IFramesOnly,
|
||||
Start(Start),
|
||||
Unknown(ExtTag),
|
||||
IndependentSegments,
|
||||
}
|
||||
|
||||
|
@ -474,7 +470,7 @@ pub fn media_playlist_from_tags(mut tags: Vec<MediaPlaylistTag>) -> MediaPlaylis
|
|||
next_segment.daterange = Some(d);
|
||||
}
|
||||
SegmentTag::Unknown(t) => {
|
||||
media_playlist.unknown_tags.push(t);
|
||||
next_segment.unknown_tags.push(t);
|
||||
}
|
||||
SegmentTag::Uri(u) => {
|
||||
next_segment.key = encryption_key.clone();
|
||||
|
@ -588,13 +584,14 @@ named!(pub start_tag<Start>,
|
|||
|
||||
named!(pub ext_tag<ExtTag>,
|
||||
do_parse!(
|
||||
tag!("#EXT-")
|
||||
>> tag: map_res!(take_until!(":"), from_utf8_slice)
|
||||
tag!("#EXT-")
|
||||
>> tag: map_res!(is_not!("\r\n:"), from_utf8_slice)
|
||||
>> opt!(tag!(":"))
|
||||
>> rest: opt!(map_res!(is_not!("\r\n"), from_utf8_slice))
|
||||
>> take!(1)
|
||||
>> rest: map_res!(is_not!("\r\n"), from_utf8_slice)
|
||||
>> take!(1)
|
||||
>>
|
||||
(ExtTag { tag: tag, rest: rest })
|
||||
>> (
|
||||
ExtTag { tag: tag, rest: rest }
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -69,7 +69,6 @@ pub struct MasterPlaylist {
|
|||
pub start: Option<Start>,
|
||||
pub independent_segments: bool,
|
||||
pub alternatives: Vec<AlternativeMedia>, // EXT-X-MEDIA tags
|
||||
pub unknown_tags: Vec<ExtTag>
|
||||
}
|
||||
|
||||
impl MasterPlaylist {
|
||||
|
@ -101,9 +100,6 @@ impl MasterPlaylist {
|
|||
if self.independent_segments {
|
||||
writeln!(w, "#EXT-X-INDEPENDENT-SEGMENTS")?;
|
||||
}
|
||||
for unknown_tag in &self.unknown_tags {
|
||||
write!(w, "#EXT-{}:{}\n", unknown_tag.tag, unknown_tag.rest)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -398,8 +394,6 @@ pub struct MediaPlaylist {
|
|||
pub start: Option<Start>,
|
||||
/// `#EXT-X-INDEPENDENT-SEGMENTS`
|
||||
pub independent_segments: bool,
|
||||
/// `#EXT-X-`
|
||||
pub unknown_tags: Vec<ExtTag>
|
||||
}
|
||||
|
||||
impl MediaPlaylist {
|
||||
|
@ -433,9 +427,6 @@ impl MediaPlaylist {
|
|||
for segment in &self.segments {
|
||||
segment.write_to(w)?;
|
||||
}
|
||||
for unknown_tag in &self.unknown_tags {
|
||||
write!(w, "#EXT-{}:{}\n", unknown_tag.tag, unknown_tag.rest)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -501,6 +492,8 @@ pub struct MediaSegment {
|
|||
pub program_date_time: Option<String>,
|
||||
/// `#EXT-X-DATERANGE:<attribute-list>`
|
||||
pub daterange: Option<String>,
|
||||
/// `#EXT-`
|
||||
pub unknown_tags: Vec<ExtTag>,
|
||||
}
|
||||
|
||||
impl MediaSegment {
|
||||
|
@ -534,6 +527,13 @@ impl MediaSegment {
|
|||
if let Some(ref v) = self.daterange {
|
||||
writeln!(w, "#EXT-X-DATERANGE:{}", v)?;
|
||||
}
|
||||
for unknown_tag in &self.unknown_tags {
|
||||
write!(w, "#EXT-{}", unknown_tag.tag)?;
|
||||
if let Some(v) = &unknown_tag.rest {
|
||||
writeln!(w, ":{}", v)?;
|
||||
}
|
||||
write!(w, "\n")?;
|
||||
}
|
||||
|
||||
write!(w, "#EXTINF:{},", self.duration)?;
|
||||
|
||||
|
@ -689,6 +689,6 @@ impl Start {
|
|||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct ExtTag {
|
||||
pub tag: String,
|
||||
pub rest: String,
|
||||
pub rest: Option<String>,
|
||||
}
|
||||
|
||||
|
|
50
tests/lib.rs
50
tests/lib.rs
|
@ -85,6 +85,26 @@ fn playlist_media_without_segments() {
|
|||
assert!(print_parse_playlist_test("media-playlist-without-segments.m3u8"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn playlist_media_with_cues() {
|
||||
assert!(print_parse_playlist_test("media-playlist-with-cues.m3u8"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn playlist_media_with_cues1() {
|
||||
assert!(print_parse_playlist_test("media-playlist-with-cues-1.m3u8"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn playlist_media_with_scte35() {
|
||||
assert!(print_parse_playlist_test("media-playlist-with-scte35.m3u8"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn playlist_media_with_scte35_1() {
|
||||
assert!(print_parse_playlist_test("media-playlist-with-scte35-1.m3u8"));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Playlist with no newline end
|
||||
|
||||
|
@ -114,10 +134,10 @@ fn playlist_type_is_master() {
|
|||
}
|
||||
|
||||
// #[test]
|
||||
// fn playlist_type_with_unkown_tag() {
|
||||
// fn playlist_type_with_unknown_tag() {
|
||||
// let input = get_sample_playlist("!!");
|
||||
// let result = is_master_playlist(input.as_bytes());
|
||||
// println!("Playlist_type_with_unkown_tag is master playlist: {:?}", result);
|
||||
// println!("Playlist_type_with_unknown_tag is master playlist: {:?}", result);
|
||||
// assert_eq!(true, result);
|
||||
// }
|
||||
|
||||
|
@ -195,6 +215,22 @@ fn test_key_value_pair() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_with_value() {
|
||||
assert_eq!(
|
||||
ext_tag(b"#EXT-X-CUE-OUT:DURATION=30\nxxx"),
|
||||
Result::Ok((b"xxx".as_bytes(), ExtTag { tag: "X-CUE-OUT".into(), rest: Some("DURATION=30".into()) }))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ext_without_value() {
|
||||
assert_eq!(
|
||||
ext_tag(b"#EXT-X-CUE-IN\nxxx"),
|
||||
Result::Ok((b"xxx".as_bytes(), ExtTag { tag: "X-CUE-IN".into(), rest: None }))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment() {
|
||||
assert_eq!(
|
||||
|
@ -213,7 +249,7 @@ fn quotes() {
|
|||
|
||||
#[test]
|
||||
fn consume_line_empty() {
|
||||
let expected = Result::Ok(("rest".as_bytes(), "".to_string()));
|
||||
let expected = Result::Ok(("rest".as_bytes(), "".to_string()));
|
||||
let actual = consume_line(b"\r\nrest");
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
@ -354,7 +390,6 @@ fn create_and_parse_master_playlist_full() {
|
|||
precise: Some("YES".into()),
|
||||
}),
|
||||
independent_segments: true,
|
||||
unknown_tags: vec![],
|
||||
});
|
||||
let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original);
|
||||
assert_eq!(playlist_original, playlist_parsed);
|
||||
|
@ -420,9 +455,14 @@ fn create_and_parse_media_playlist_full() {
|
|||
}),
|
||||
program_date_time: Some("broodlordinfestorgg".into()),
|
||||
daterange: None,
|
||||
unknown_tags: vec![
|
||||
ExtTag {
|
||||
tag: "X-CUE-OUT".into(),
|
||||
rest: Some("DURATION=2.002".into())
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
unknown_tags: vec![],
|
||||
});
|
||||
let playlist_parsed = print_create_and_parse_playlist(&mut playlist_original);
|
||||
assert_eq!(playlist_original, playlist_parsed);
|
||||
|
|
Loading…
Reference in a new issue