Merge pull request #36 from sdroege/nom-7

Port to nom 7
This commit is contained in:
rutgersc 2021-11-18 17:15:50 +01:00 committed by GitHub
commit 53e9439660
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 687 additions and 612 deletions

View file

@ -1,15 +1,16 @@
[package]
name = "m3u8-rs"
version = "2.1.0"
version = "3.0.0"
authors = ["Rutger"]
readme = "README.md"
repository = "https://github.com/rutgersc/m3u8-rs"
description = "A library for parsing m3u8 files (Apple's HTTP Live Streaming (HLS) protocol)."
documentation = "https://rutgersc.github.io/doc/m3u8_rs/index.html"
license = "MIT"
edition = "2018"
[dependencies]
nom = { version = "5.1.0", optional = true }
nom = { version = "7", optional = true }
[features]
default = ["parser"]

View file

@ -12,12 +12,6 @@ To use this library, add the following dependency to `Cargo.toml`:
m3u8-rs = "1.0.6"
```
And add the crate to `lib.rs`
```rust
extern crate m3u8_rs;
```
Also available on [crates.io](https://crates.io/crates/m3u8-rs)
# Documentation

View file

@ -1,7 +1,4 @@
extern crate m3u8_rs;
extern crate nom;
use m3u8_rs::playlist::Playlist;
use m3u8_rs::Playlist;
use std::io::Read;
fn main() {

View file

@ -1,7 +1,4 @@
extern crate m3u8_rs;
extern crate nom;
use m3u8_rs::playlist::Playlist;
use m3u8_rs::Playlist;
use std::io::Read;
fn main() {
@ -12,7 +9,7 @@ fn main() {
let parsed = m3u8_rs::parse_playlist(&bytes);
let playlist = match parsed {
Result::Ok((i, playlist)) => playlist,
Result::Ok((_i, playlist)) => playlist,
Result::Err(e) => panic!("Parsing error: \n{}", e),
};
@ -22,6 +19,7 @@ fn main() {
}
}
#[allow(unused)]
fn main_alt() {
let mut file = std::fs::File::open("playlist.m3u8").unwrap();
let mut bytes: Vec<u8> = Vec::new();
@ -30,8 +28,8 @@ fn main_alt() {
let parsed = m3u8_rs::parse_playlist(&bytes);
match parsed {
Result::Ok((i, Playlist::MasterPlaylist(pl))) => println!("Master playlist:\n{:?}", pl),
Result::Ok((i, Playlist::MediaPlaylist(pl))) => println!("Media playlist:\n{:?}", pl),
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),
}
}

View file

@ -1,3 +1,4 @@
#EXTM3U
#EXTINF:10,
http://media.example.com/fileSequence7796.ts
#EXTINF:6,

View file

@ -1,5 +1,3 @@
# https://developer.apple.com/library/ios/technotes/tn2288/_index.html
#
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
@ -12,4 +10,4 @@ ad1.ts
#EXTINF:10.0,
movieA.ts
#EXTINF:10.0,
movieB.ts
movieB.ts

View file

@ -1,7 +1,73 @@
#[path = "playlist.rs"]
pub mod playlist;
//! A library to parse m3u8 playlists [HTTP Live Streaming](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19).
//!
//! # Examples
//!
//! Parsing a playlist and let the parser figure out if it's a media or master playlist.
//!
//! ```
//! use m3u8_rs::Playlist;
//! use nom::IResult;
//! 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();
//!
//! match m3u8_rs::parse_playlist(&bytes) {
//! 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),
//! }
//! ```
//!
//! Parsing a master playlist directly
//!
//! ```
//! use std::io::Read;
//! use nom::IResult;
//!
//! let mut file = std::fs::File::open("masterplaylist.m3u8").unwrap();
//! let mut bytes: Vec<u8> = Vec::new();
//! file.read_to_end(&mut bytes).unwrap();
//!
//! if let Result::Ok((_, pl)) = m3u8_rs::parse_master_playlist(&bytes) {
//! println!("{:?}", pl);
//! }
//! ```
//!
//! Creating a playlist and writing it back to a vec/file
//!
//! ```
//! use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
//!
//! let playlist = MediaPlaylist {
//! 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()
//! };
//!
//! //let mut v: Vec<u8> = Vec::new();
//! //playlist.write_to(&mut v).unwrap();
//!
//! //let mut file = std::fs::File::open("playlist.m3u8").unwrap();
//! //playlist.write_to(&mut file).unwrap();
//! ```
mod playlist;
pub use playlist::*;
#[path = "parser.rs"]
#[cfg(feature = "parser")]
mod parser;

File diff suppressed because it is too large Load diff

View file

@ -65,8 +65,7 @@ impl Playlist {
// Master Playlist
// -----------------------------------------------------------------------------------------------
/// A [Master Playlist]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4)
/// A [Master Playlist](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4)
/// provides a set of Variant Streams, each of which
/// describes a different version of the same content.
#[derive(Debug, Default, PartialEq, Clone)]
@ -116,11 +115,8 @@ impl MasterPlaylist {
}
}
/// [`#EXT-X-STREAM-INF:<attribute-list>
/// <URI>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.2)
/// [`#EXT-X-I-FRAME-STREAM-INF:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.3)
/// [`#EXT-X-STREAM-INF:<attribute-list> <URI>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.2)
/// [`#EXT-X-I-FRAME-STREAM-INF:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.3)
///
/// A Variant Stream includes a Media Playlist that specifies media
/// encoded at a particular bit rate, in a particular format, and at a
@ -197,8 +193,7 @@ impl VariantStream {
}
}
/// [`#EXT-X-MEDIA:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.1)
/// [`#EXT-X-MEDIA:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.1)
///
/// The EXT-X-MEDIA tag is used to relate Media Playlists that contain
/// alternative Renditions (Section 4.3.4.2.1) of the same content. For
@ -229,7 +224,7 @@ impl AlternativeMedia {
media_type: attrs
.get("TYPE")
.and_then(|s| AlternativeMediaType::from_str(s).ok())
.unwrap_or_else(Default::default),
.unwrap_or_default(),
uri: attrs.remove("URI"),
group_id: attrs.remove("GROUP-ID").unwrap_or_else(String::new),
language: attrs.remove("LANGUAGE"),
@ -314,8 +309,7 @@ impl fmt::Display for AlternativeMediaType {
}
}
/// [`#EXT-X-SESSION-KEY:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5)
/// [`#EXT-X-SESSION-KEY:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.5)
/// The EXT-X-SESSION-KEY tag allows encryption keys from Media Playlists
/// to be specified in a Master Playlist. This allows the client to
/// preload these keys without having to read the Media Playlist(s) first.
@ -336,8 +330,7 @@ pub enum SessionDataField {
Uri(String),
}
/// [`#EXT-X-SESSION-DATA:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.4)
/// [`#EXT-X-SESSION-DATA:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.4.4)
/// The EXT-X-SESSION-DATA tag allows arbitrary session data to be carried
/// in a Master Playlist.
#[derive(Debug, PartialEq, Clone)]
@ -399,8 +392,7 @@ impl SessionData {
// Media Playlist
// -----------------------------------------------------------------------------------------------
/// A [Media Playlist]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.3)
/// A [Media Playlist](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.3)
/// contains a list of Media Segments, which when played
/// sequentially will play the multimedia presentation.
#[derive(Debug, Default, PartialEq, Clone)]
@ -463,8 +455,7 @@ impl MediaPlaylist {
}
}
/// [`#EXT-X-PLAYLIST-TYPE:<EVENT|VOD>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.3.5)
/// [`#EXT-X-PLAYLIST-TYPE:<EVENT|VOD>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.3.5)
#[derive(Debug, PartialEq, Clone)]
pub enum MediaPlaylistType {
Event,
@ -577,8 +568,7 @@ impl MediaSegment {
}
}
/// [`#EXT-X-KEY:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.4)
/// [`#EXT-X-KEY:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.4)
///
/// Media Segments MAY be encrypted. The EXT-X-KEY tag specifies how to
/// decrypt them. It applies to every Media Segment that appears between
@ -615,12 +605,10 @@ impl Key {
}
}
/// [`#EXT-X-MAP:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.5)
/// [`#EXT-X-MAP:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.5)
///
/// The EXT-X-MAP tag specifies how to obtain the Media Initialization Section
/// [(Section 3)]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-3)
/// [(Section 3)](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-3)
/// required to parse the applicable Media Segments.
/// It applies to every Media Segment that appears after it in the
/// Playlist until the next EXT-X-MAP tag or until the end of the
@ -642,8 +630,7 @@ impl Map {
}
}
/// [`#EXT-X-BYTERANGE:<n>[@<o>]`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.2)
/// [`#EXT-X-BYTERANGE:<n>[@<o>]`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.2)
///
/// The EXT-X-BYTERANGE tag indicates that a Media Segment is a sub-range
/// of the resource identified by its URI. It applies only to the next
@ -664,8 +651,7 @@ impl ByteRange {
}
}
/// [`#EXT-X-DATERANGE:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.7)
/// [`#EXT-X-DATERANGE:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.2.7)
///
/// The EXT-X-DATERANGE tag associates a Date Range (i.e. a range of time
/// defined by a starting and ending date) with a set of attribute /
@ -686,8 +672,7 @@ pub struct DateRange {
// Rest
// -----------------------------------------------------------------------------------------------
/// [`#EXT-X-START:<attribute-list>`]
/// (https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.5.2)
/// [`#EXT-X-START:<attribute-list>`](https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-4.3.5.2)
///
/// The EXT-X-START tag indicates a preferred point at which to start
/// playing a Playlist. By default, clients SHOULD start playback at

View file

@ -1,11 +1,7 @@
#![allow(unused_variables, unused_imports, dead_code)]
extern crate m3u8_rs;
extern crate nom;
use m3u8_rs::playlist::*;
use m3u8_rs::*;
use nom::*;
use nom::AsBytes;
use std::collections::HashMap;
use std::fs;
use std::fs::File;
@ -24,7 +20,7 @@ fn all_sample_m3u_playlists() -> Vec<path::PathBuf> {
fn getm3u(path: &str) -> String {
let mut buf = String::new();
let mut file = fs::File::open(path).expect(&format!("Can't find m3u8: {}", path));
let mut file = fs::File::open(path).unwrap_or_else(|_| panic!("Can't find m3u8: {}", path));
let u = file.read_to_string(&mut buf).expect("Can't read file");
buf
}
@ -142,7 +138,7 @@ fn playlist_not_ending_in_newline_media() {
fn playlist_type_is_master() {
let input = get_sample_playlist("master.m3u8");
let result = is_master_playlist(input.as_bytes());
assert_eq!(true, result);
assert!(result);
}
// #[test]
@ -166,158 +162,6 @@ fn playlist_types() {
}
}
// -----------------------------------------------------------------------------------------------
// Variant
#[test]
fn variant_stream() {
let input = b"#EXT-X-STREAM-INF:BANDWIDTH=300000,CODECS=\"xxx\"\n";
let result = variant_stream_tag(input);
println!("{:?}", result);
}
// -----------------------------------------------------------------------------------------------
// Other
#[test]
fn test_key_value_pairs_trailing_equals() {
let res = key_value_pairs(b"BANDWIDTH=395000,CODECS=\"avc1.4d001f,mp4a.40.2\"\r\nrest=");
println!("{:?}\n\n", res);
}
#[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\nrest"),
Result::Ok((
"\nrest".as_bytes(),
vec![
("BANDWIDTH".to_string(), "86000".to_string()),
("URI".to_string(), "low/iframe.m3u8".to_string()),
("PROGRAM-ID".to_string(), "1".to_string()),
("RESOLUTION".to_string(), "1x1".to_string()),
("VIDEO".to_string(), "1".to_string())
].into_iter().collect::<HashMap<String,String>>()
))
);
}
#[test]
fn test_key_value_pairs_quotes() {
let res = key_value_pairs(b"BANDWIDTH=300000,CODECS=\"avc1.42c015,mp4a.40.2\"\r\nrest");
println!("{:?}\n\n", res);
}
#[test]
fn test_key_value_pairs() {
let res = key_value_pairs(b"BANDWIDTH=300000,RESOLUTION=22x22,VIDEO=1\r\nrest=");
println!("{:?}\n\n", res);
}
#[test]
fn test_key_value_pair() {
assert_eq!(
key_value_pair(b"PROGRAM-ID=1,rest"),
Result::Ok((
"rest".as_bytes(),
("PROGRAM-ID".to_string(), "1".to_string())
))
);
}
#[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!(
comment_tag(b"#Hello\nxxx"),
Result::Ok(("xxx".as_bytes(), "Hello".to_string()))
);
}
#[test]
fn quotes() {
assert_eq!(
quoted(b"\"value\"rest"),
Result::Ok(("rest".as_bytes(), "value".to_string()))
);
}
#[test]
fn consume_line_empty() {
let expected = Result::Ok(("rest".as_bytes(), "".to_string()));
let actual = consume_line(b"\r\nrest");
assert_eq!(expected, actual);
}
#[test]
fn consume_line_n() {
assert_eq!(
consume_line(b"before\nrest"),
Result::Ok(("rest".as_bytes(), "before".into()))
);
}
#[test]
fn consume_line_rn() {
assert_eq!(
consume_line(b"before\r\nrest"),
Result::Ok(("rest".as_bytes(), "before".into()))
);
}
#[test]
fn float_() {
assert_eq!(
float(b"33.22rest"),
Result::Ok(("rest".as_bytes(), 33.22f32))
);
}
#[test]
fn float_no_decimal() {
assert_eq!(float(b"33rest"), Result::Ok(("rest".as_bytes(), 33f32)));
}
#[test]
fn float_should_ignore_trailing_dot() {
assert_eq!(float(b"33.rest"), Result::Ok((".rest".as_bytes(), 33f32)));
}
#[test]
fn parse_duration_title() {
assert_eq!(
duration_title_tag(b"2.002,title\nrest"),
Result::Ok(("rest".as_bytes(), (2.002f32, Some("title".to_string()))))
);
}
// -----------------------------------------------------------------------------------------------
// Creating playlists
@ -504,3 +348,29 @@ fn parsing_write_to_should_produce_the_same_structure() {
);
}
}
// Failure on arbitrary text files that don't start with #EXTM3U8
#[test]
fn parsing_text_file_should_fail() {
let s = "
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
";
let res = parse_master_playlist_res(s.as_bytes());
assert!(res.is_err());
}
#[test]
fn parsing_binary_data_should_fail_cleanly() {
let data = (0..1024).map(|i| (i % 255) as u8).collect::<Vec<u8>>();
let res = parse_master_playlist_res(&data);
assert!(res.is_err());
}