1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-17 07:52:58 +00:00

Initial commit

This commit is contained in:
Takeru Ohta 2018-02-11 15:10:52 +09:00
commit c067448428
18 changed files with 973 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "hls_m3u8"
version = "0.1.0"
authors = ["Takeru Ohta <phjgt308@gmail.com>"]
[dependencies]
trackable = "0.2"

9
README.md Normal file
View file

@ -0,0 +1,9 @@
hls_m3u8
=========
References
-----------
- [HTTP Live Streaming][rfc8216]
[rfc8216]: https://tools.ietf.org/html/rfc8216

View file

@ -0,0 +1,12 @@
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXTINF:9.009,
http://media.example.com/first.ts
#EXTINF:9.009,
http://media.example.com/second.ts
#EXTINF:3.003,
http://media.example.com/third.ts
#EXT-X-ENDLIST
# 8.1. Simple Media Playlist

View file

@ -0,0 +1,13 @@
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:2680
#EXTINF:7.975,
https://priv.example.com/fileSequence2680.ts
#EXTINF:7.941,
https://priv.example.com/fileSequence2681.ts
#EXTINF:7.975,
https://priv.example.com/fileSequence2682.ts
# 8.2. Live Media Playlist Using HTTPS

View file

@ -0,0 +1,20 @@
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:7794
#EXT-X-TARGETDURATION:15
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"
#EXTINF:2.833,
http://media.example.com/fileSequence52-A.ts
#EXTINF:15.0,
http://media.example.com/fileSequence52-B.ts
#EXTINF:13.333,
http://media.example.com/fileSequence52-C.ts
#EXT-X-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=53"
#EXTINF:15.0,
http://media.example.com/fileSequence53-A.ts
# 8.3. Playlist with Encrypted Media Segments

View file

@ -0,0 +1,11 @@
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000
http://example.com/low.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000
http://example.com/mid.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=7680000,AVERAGE-BANDWIDTH=6000000
http://example.com/hi.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"
http://example.com/audio-only.m3u8
# 8.4. Master Playlist

View file

@ -0,0 +1,14 @@
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000
low/audio-video.m3u8
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=86000,URI="low/iframe.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=2560000
mid/audio-video.m3u8
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=150000,URI="mid/iframe.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=7680000
hi/audio-video.m3u8
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=550000,URI="hi/iframe.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"
audio-only.m3u8
# 8.5. Master Playlist with I-Frames

View file

@ -0,0 +1,14 @@
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="English", DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en", URI="main/english-audio.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Deutsch", DEFAULT=NO,AUTOSELECT=YES,LANGUAGE="de", URI="main/german-audio.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Commentary", DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="en", URI="commentary/audio-only.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS="...",AUDIO="aac"
low/video-only.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS="...",AUDIO="aac"
mid/video-only.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS="...",AUDIO="aac"
hi/video-only.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5",AUDIO="aac"
main/english-audio.m3u8
# 8.6. Master Playlist with Alternative Audio

View file

@ -0,0 +1,23 @@
#EXTM3U
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Main", DEFAULT=YES,URI="low/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Centerfield", DEFAULT=NO,URI="low/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="low",NAME="Dugout", DEFAULT=NO,URI="low/dugout/audio-video.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS="...",VIDEO="low"
low/main/audio-video.m3u8
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Main", DEFAULT=YES,URI="mid/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Centerfield", DEFAULT=NO,URI="mid/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="mid",NAME="Dugout", DEFAULT=NO,URI="mid/dugout/audio-video.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=2560000,CODECS="...",VIDEO="mid"
mid/main/audio-video.m3u8
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Main", DEFAULT=YES,URI="hi/main/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Centerfield", DEFAULT=NO,URI="hi/centerfield/audio-video.m3u8"
#EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="hi",NAME="Dugout", DEFAULT=NO,URI="hi/dugout/audio-video.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS="...",VIDEO="hi"
hi/main/audio-video.m3u8
# 8.7. Master Playlist with Alternative Video

18
examples/parse.rs Normal file
View file

@ -0,0 +1,18 @@
extern crate hls_m3u8;
#[macro_use]
extern crate trackable;
use std::io::{self, Read};
use trackable::error::Failure;
fn main() {
let mut m3u8 = String::new();
track_try_unwrap!(
io::stdin()
.read_to_string(&mut m3u8)
.map_err(Failure::from_error)
);
for line in hls_m3u8::line::Lines::new(&m3u8) {
println!("{:?}", track_try_unwrap!(line));
}
}

95
src/attribute.rs Normal file
View file

@ -0,0 +1,95 @@
use std::fmt;
use std::str::{self, FromStr};
use {Error, ErrorKind, Result};
#[derive(Debug)]
pub struct AttributePairs<'a> {
input: &'a str,
}
impl<'a> AttributePairs<'a> {
pub fn parse(input: &'a str) -> Self {
AttributePairs { input }
}
fn parse_name(&mut self) -> Result<&'a str> {
for i in 0..self.input.len() {
match self.input.as_bytes()[i] {
b'=' => {
let (key, _) = self.input.split_at(i);
let (_, rest) = self.input.split_at(i + 1);
self.input = rest;
return Ok(key);
}
b'A'...b'Z' | b'0'...b'9' | b'-' => {}
_ => track_panic!(
ErrorKind::InvalidInput,
"Malformed attribute name: {:?}",
self.input
),
}
}
track_panic!(
ErrorKind::InvalidInput,
"No attribute value: {:?}",
self.input
);
}
fn parse_raw_value(&mut self) -> &'a str {
let (value_end, next) = if let Some(i) = self.input.bytes().position(|c| c == b',') {
(i, i + 1)
} else {
(self.input.len(), self.input.len())
};
let (value, _) = self.input.split_at(value_end);
let (_, rest) = self.input.split_at(next);
self.input = rest;
value
}
}
impl<'a> Iterator for AttributePairs<'a> {
type Item = Result<(&'a str, &'a str)>;
fn next(&mut self) -> Option<Self::Item> {
if self.input.is_empty() {
return None;
}
let result = || -> Result<(&'a str, &'a str)> {
let key = track!(self.parse_name())?;
let value = self.parse_raw_value();
Ok((key, value))
}();
Some(result)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct QuotedString(String);
impl fmt::Display for QuotedString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl FromStr for QuotedString {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let len = s.len();
let bytes = s.as_bytes();
track_assert!(len >= 2, ErrorKind::InvalidInput);
track_assert_eq!(bytes[0], b'"', ErrorKind::InvalidInput);
track_assert_eq!(bytes[len - 1], b'"', ErrorKind::InvalidInput);
let s = unsafe { str::from_utf8_unchecked(&bytes[1..len - 1]) };
track_assert!(
s.chars().all(|c| c != '\r' && c != '\n' && c != '"'),
ErrorKind::InvalidInput,
"{:?}",
s
);
Ok(QuotedString(s.to_owned()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HexadecimalSequence(Vec<u8>);

12
src/error.rs Normal file
View file

@ -0,0 +1,12 @@
use trackable::error::{ErrorKind as TrackableErrorKind, TrackableError};
#[derive(Debug, Clone)]
pub struct Error(TrackableError<ErrorKind>);
derive_traits_for_trackable_error_newtype!(Error, ErrorKind);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
InvalidInput,
Other,
}
impl TrackableErrorKind for ErrorKind {}

18
src/lib.rs Normal file
View file

@ -0,0 +1,18 @@
#[macro_use]
extern crate trackable;
// pub mod playlist;
// pub mod media_playlist;
// pub mod master_playlist;
// pub mod media_segment;
pub use error::{Error, ErrorKind};
pub mod attribute;
pub mod string;
pub mod tag;
pub mod version;
mod error;
pub mod line;
pub type Result<T> = std::result::Result<T, Error>;

98
src/line.rs Normal file
View file

@ -0,0 +1,98 @@
use {ErrorKind, Result};
use tag::Tag;
// [rfc8216#section-4.1]
// > Playlist files MUST be encoded in UTF-8 [RFC3629]. They MUST NOT
// > contain any Byte Order Mark (BOM); clients SHOULD fail to parse
// > Playlists that contain a BOM or do not parse as UTF-8. Playlist
// > files MUST NOT contain UTF-8 control characters (U+0000 to U+001F and
// > U+007F to U+009F), with the exceptions of CR (U+000D) and LF
// > (U+000A). All character sequences MUST be normalized according to
// > Unicode normalization form "NFC" [UNICODE]. Note that US-ASCII
// > [US_ASCII] conforms to these rules.
// >
// > Lines in a Playlist file are terminated by either a single line feed
// > character or a carriage return character followed by a line feed
// > character.
#[derive(Debug)]
pub struct Lines<'a> {
input: &'a str,
}
impl<'a> Lines<'a> {
pub fn new(input: &'a str) -> Self {
Lines { input }
}
fn read_line(&mut self) -> Result<Line<'a>> {
let mut end = self.input.len();
let mut next_start = self.input.len();
let mut adjust = 0;
for (i, c) in self.input.char_indices() {
match c {
'\n' => {
next_start = i + 1;
end = i - adjust;
break;
}
'\r' => {
adjust = 1;
}
'\u{00}'...'\u{1F}' | '\u{7F}'...'\u{9f}' => {
track_panic!(ErrorKind::InvalidInput);
}
_ => {
adjust = 0;
}
}
}
let raw_line = &self.input[..end];
let line = if raw_line.is_empty() {
Line::Blank
} else if raw_line.starts_with("#EXT") {
Line::Tag(track!(raw_line.parse())?)
} else if raw_line.starts_with("#") {
Line::Comment(raw_line)
} else {
Line::Uri(raw_line)
};
self.input = &self.input[next_start..];
Ok(line)
}
}
impl<'a> Iterator for Lines<'a> {
type Item = Result<Line<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.input.is_empty() {
return None;
}
match track!(self.read_line()) {
Err(e) => Some(Err(e)),
Ok(line) => Some(Ok(line)),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Line<'a> {
Blank,
Comment(&'a str),
Tag(Tag),
// TODO:
Uri(&'a str),
}
// TODO
// #[cfg(test)]
// mod test {
// use super::*;
// #[test]
// fn it_works() {
// let mut lines = Lines::new("foo\nbar\r\nbaz");
// assert_eq!(lines.next().and_then(|x| x.ok()), Some("foo"));
// assert_eq!(lines.next().and_then(|x| x.ok()), Some("bar"));
// assert_eq!(lines.next().and_then(|x| x.ok()), Some("baz"));
// assert_eq!(lines.next().and_then(|x| x.ok()), None);
// }
// }

32
src/string.rs Normal file
View file

@ -0,0 +1,32 @@
use std::fmt;
use std::ops::Deref;
use Result;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct M3u8String(String);
impl M3u8String {
pub fn new<T: Into<String>>(s: T) -> Result<Self> {
// TODO: validate
Ok(M3u8String(s.into()))
}
pub unsafe fn new_unchecked<T: Into<String>>(s: T) -> Self {
M3u8String(s.into())
}
}
impl Deref for M3u8String {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<str> for M3u8String {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for M3u8String {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}

528
src/tag.rs Normal file
View file

@ -0,0 +1,528 @@
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
use trackable::error::ErrorKindExt;
use {Error, ErrorKind, Result};
use attribute::{AttributePairs, QuotedString};
use string::M3u8String;
use version::ProtocolVersion;
macro_rules! may_invalid {
($expr:expr) => {
$expr.map_err(|e| track!(Error::from(ErrorKind::InvalidInput.cause(e))))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tag {
ExtM3u(ExtM3u),
ExtXVersion(ExtXVersion),
ExtInf(ExtInf),
ExtXByteRange(ExtXByteRange),
ExtXDiscontinuity(ExtXDiscontinuity),
ExtXKey(ExtXKey),
ExtXTargetDuration(ExtXTargetDuration),
ExtXMediaSequence(ExtXMediaSequence),
ExtXDiscontinuitySequence(ExtXDiscontinuitySequence),
ExtXEndList(ExtXEndList),
ExtXPlaylistType(ExtXPlaylistType),
ExtXIFramesOnly(ExtXIFramesOnly),
ExtXIndependentSegments(ExtXIndependentSegments),
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Tag::ExtM3u(ref t) => t.fmt(f),
Tag::ExtXVersion(ref t) => t.fmt(f),
Tag::ExtInf(ref t) => t.fmt(f),
Tag::ExtXByteRange(ref t) => t.fmt(f),
Tag::ExtXDiscontinuity(ref t) => t.fmt(f),
Tag::ExtXKey(ref t) => t.fmt(f),
Tag::ExtXTargetDuration(ref t) => t.fmt(f),
Tag::ExtXMediaSequence(ref t) => t.fmt(f),
Tag::ExtXDiscontinuitySequence(ref t) => t.fmt(f),
Tag::ExtXEndList(ref t) => t.fmt(f),
Tag::ExtXPlaylistType(ref t) => t.fmt(f),
Tag::ExtXIFramesOnly(ref t) => t.fmt(f),
Tag::ExtXIndependentSegments(ref t) => t.fmt(f),
}
}
}
impl FromStr for Tag {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if s.starts_with(ExtM3u::PREFIX) {
track!(s.parse().map(Tag::ExtM3u))
} else if s.starts_with(ExtXVersion::PREFIX) {
track!(s.parse().map(Tag::ExtXVersion))
} else if s.starts_with(ExtInf::PREFIX) {
track!(s.parse().map(Tag::ExtInf))
} else if s.starts_with(ExtXByteRange::PREFIX) {
track!(s.parse().map(Tag::ExtXByteRange))
} else if s.starts_with(ExtXDiscontinuity::PREFIX) {
track!(s.parse().map(Tag::ExtXDiscontinuity))
} else if s.starts_with(ExtXKey::PREFIX) {
track!(s.parse().map(Tag::ExtXKey))
} else if s.starts_with(ExtXTargetDuration::PREFIX) {
track!(s.parse().map(Tag::ExtXTargetDuration))
} else if s.starts_with(ExtXMediaSequence::PREFIX) {
track!(s.parse().map(Tag::ExtXMediaSequence))
} else if s.starts_with(ExtXDiscontinuitySequence::PREFIX) {
track!(s.parse().map(Tag::ExtXDiscontinuitySequence))
} else if s.starts_with(ExtXEndList::PREFIX) {
track!(s.parse().map(Tag::ExtXEndList))
} else if s.starts_with(ExtXPlaylistType::PREFIX) {
track!(s.parse().map(Tag::ExtXPlaylistType))
} else if s.starts_with(ExtXIFramesOnly::PREFIX) {
track!(s.parse().map(Tag::ExtXIFramesOnly))
} else if s.starts_with(ExtXIndependentSegments::PREFIX) {
track!(s.parse().map(Tag::ExtXIndependentSegments))
} else {
// TODO: ignore any unrecognized tags. (section-6.3.1)
track_panic!(ErrorKind::InvalidInput, "Unknown tag: {:?}", s)
}
}
}
// TODO: MediaSegmentTag
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtM3u;
impl ExtM3u {
const PREFIX: &'static str = "#EXTM3U";
}
impl fmt::Display for ExtM3u {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Self::PREFIX.fmt(f)
}
}
impl FromStr for ExtM3u {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput);
Ok(ExtM3u)
}
}
// TODO: A Playlist file MUST NOT contain more than one EXT-X-VERSION tag
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXVersion {
pub version: ProtocolVersion,
}
impl ExtXVersion {
const PREFIX: &'static str = "#EXT-X-VERSION:";
}
impl fmt::Display for ExtXVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.version)
}
}
impl FromStr for ExtXVersion {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let suffix = s.split_at(Self::PREFIX.len()).1;
let version = track!(suffix.parse())?;
Ok(ExtXVersion { version })
}
}
// TODO: This tag is REQUIRED for each Media Segment
// TODO: if the compatibility version number is less than 3, durations MUST be integers.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtInf {
pub duration: Duration,
pub title: Option<M3u8String>,
}
impl ExtInf {
const PREFIX: &'static str = "#EXTINF:";
// TODO: pub fn required_version(&self) -> ProtocolVersion;
}
impl fmt::Display for ExtInf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
let duration = (self.duration.as_secs() as f64)
+ (self.duration.subsec_nanos() as f64 / 1_000_000_000.0);
write!(f, "{}", duration)?;
if let Some(ref title) = self.title {
write!(f, ",{}", title)?;
}
Ok(())
}
}
impl FromStr for ExtInf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut tokens = s.split_at(Self::PREFIX.len()).1.splitn(2, ',');
let duration: f64 = may_invalid!(tokens.next().expect("Never fails").parse())?;
let duration = Duration::new(duration as u64, (duration.fract() * 1_000_000_000.0) as u32);
let title = if let Some(title) = tokens.next() {
Some(track!(M3u8String::new(title))?)
} else {
None
};
Ok(ExtInf { duration, title })
}
}
// TODO: If o is not present, a previous Media Segment MUST appear in the Playlist file
// TDOO: Use of the EXT-X-BYTERANGE tag REQUIRES a compatibility version number of 4 or greater.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXByteRange {
pub length: usize,
pub offset: Option<usize>,
}
impl ExtXByteRange {
const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
}
impl fmt::Display for ExtXByteRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.length)?;
if let Some(offset) = self.offset {
write!(f, "@{}", offset)?;
}
Ok(())
}
}
impl FromStr for ExtXByteRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut tokens = s.split_at(Self::PREFIX.len()).1.splitn(2, '@');
let length = may_invalid!(tokens.next().expect("Never fails").parse())?;
let offset = if let Some(offset) = tokens.next() {
Some(may_invalid!(offset.parse())?)
} else {
None
};
Ok(ExtXByteRange { length, offset })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXDiscontinuity;
impl ExtXDiscontinuity {
const PREFIX: &'static str = "#EXT-X-DISCONTINUITY";
}
impl fmt::Display for ExtXDiscontinuity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Self::PREFIX.fmt(f)
}
}
impl FromStr for ExtXDiscontinuity {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput);
Ok(ExtXDiscontinuity)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXKey {
pub method: EncryptionMethod,
pub uri: Option<QuotedString>,
}
impl ExtXKey {
const PREFIX: &'static str = "#EXT-X-KEY:";
}
impl fmt::Display for ExtXKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
unimplemented!()
}
}
impl FromStr for ExtXKey {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut method = None;
let mut uri = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"METHOD" => {
method = Some(track!(value.parse())?);
}
"URI" => {
uri = Some(track!(value.parse())?);
}
"IV" => unimplemented!(),
"KEYFORMAT" => unimplemented!(),
"KEYFORMATVERSIONS" => unimplemented!(),
_ => {
// [6.3.1] ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let method = track_assert_some!(method, ErrorKind::InvalidInput);
if let EncryptionMethod::None = method {
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
} else {
track_assert!(uri.is_some(), ErrorKind::InvalidInput);
};
Ok(ExtXKey { method, uri })
}
}
// TODO: move
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EncryptionMethod {
None,
Aes128,
SampleAes,
}
impl fmt::Display for EncryptionMethod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
EncryptionMethod::None => "NONE".fmt(f),
EncryptionMethod::Aes128 => "AES-128".fmt(f),
EncryptionMethod::SampleAes => "SAMPLE-AES".fmt(f),
}
}
}
impl FromStr for EncryptionMethod {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"NONE" => Ok(EncryptionMethod::None),
"AES-128" => Ok(EncryptionMethod::Aes128),
"SAMPLE-AES" => Ok(EncryptionMethod::SampleAes),
_ => track_panic!(
ErrorKind::InvalidInput,
"Unknown encryption method: {:?}",
s
),
}
}
}
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
// TODO:
// #[derive(Debug, Clone, PartialEq, Eq)]
// pub struct ExtXProgramDateTime { date_time }
// impl ExtXProgramDateTime {
// const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:";
// }
// impl fmt::Display for ExtXProgramDateTime {
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// write!(f, "{}{}", Self::PREFIX, self.length)?;
// if let Some(offset) = self.offset {
// write!(f, "@{}", offset)?;
// }
// Ok(())
// }
// }
// impl FromStr for ExtXProgramDateTime {
// type Err = Error;
// fn from_str(s: &str) -> Result<Self> {
// track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
// let mut tokens = s.split_at(Self::PREFIX.len()).1.splitn(2, '@');
// let length = may_invalid!(tokens.next().expect("Never fails").parse())?;
// let offset = if let Some(offset) = tokens.next() {
// Some(may_invalid!(offset.parse())?)
// } else {
// None
// };
// Ok(ExtXByteRange { length, offset })
// }
// }
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
// TODO: he EXT-X-TARGETDURATION tag is REQUIRED.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXTargetDuration {
pub duration: Duration,
}
impl ExtXTargetDuration {
const PREFIX: &'static str = "#EXT-X-TARGETDURATION:";
}
impl fmt::Display for ExtXTargetDuration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.duration.as_secs())
}
}
impl FromStr for ExtXTargetDuration {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let duration = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXTargetDuration {
duration: Duration::from_secs(duration),
})
}
}
// TODO: The EXT-X-MEDIA-SEQUENCE tag MUST appear before the first Media Segment in the Playlist.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXMediaSequence {
pub seq_num: u64,
}
impl ExtXMediaSequence {
const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:";
}
impl fmt::Display for ExtXMediaSequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.seq_num)
}
}
impl FromStr for ExtXMediaSequence {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXMediaSequence { seq_num })
}
}
// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before the first Media Segment in the Playlist.
// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before any EXT-X-DISCONTINUITY tag.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXDiscontinuitySequence {
pub seq_num: u64,
}
impl ExtXDiscontinuitySequence {
const PREFIX: &'static str = "#EXT-X-DISCONTINUITY-SEQUENCE:";
}
impl fmt::Display for ExtXDiscontinuitySequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.seq_num)
}
}
impl FromStr for ExtXDiscontinuitySequence {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXDiscontinuitySequence { seq_num })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXEndList;
impl ExtXEndList {
const PREFIX: &'static str = "#EXT-X-ENDLIST";
}
impl fmt::Display for ExtXEndList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Self::PREFIX.fmt(f)
}
}
impl FromStr for ExtXEndList {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput);
Ok(ExtXEndList)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXPlaylistType {
pub playlist_type: PlaylistType,
}
impl ExtXPlaylistType {
const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:";
}
impl fmt::Display for ExtXPlaylistType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.playlist_type)
}
}
impl FromStr for ExtXPlaylistType {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let playlist_type = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXPlaylistType { playlist_type })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaylistType {
Event,
Vod,
}
impl fmt::Display for PlaylistType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PlaylistType::Event => write!(f, "EVENT"),
PlaylistType::Vod => write!(f, "VOD"),
}
}
}
impl FromStr for PlaylistType {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"EVENT" => Ok(PlaylistType::Event),
"VOD" => Ok(PlaylistType::Vod),
_ => track_panic!(ErrorKind::InvalidInput, "Unknown playlist type: {:?}", s),
}
}
}
// TODO: Media resources containing I-frame segments MUST begin with ...
// TODO: Use of the EXT-X-I-FRAMES-ONLY REQUIRES a compatibility version number of 4 or greater.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXIFramesOnly;
impl ExtXIFramesOnly {
const PREFIX: &'static str = "#EXT-X-I-FRAMES-ONLY";
}
impl fmt::Display for ExtXIFramesOnly {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Self::PREFIX.fmt(f)
}
}
impl FromStr for ExtXIFramesOnly {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput);
Ok(ExtXIFramesOnly)
}
}
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.4.2
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
// 4.3.5. Media or Master Playlist Tags
// TODO: A tag that appears in both MUST have the same value; otherwise, clients SHOULD ignore the value in the Media Playlist(s).
// TODO: These tags MUST NOT appear more than once in a Playlist.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXIndependentSegments;
impl ExtXIndependentSegments {
const PREFIX: &'static str = "#EXT-X-INDEPENDENT-SEGMENTS";
}
impl fmt::Display for ExtXIndependentSegments {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Self::PREFIX.fmt(f)
}
}
impl FromStr for ExtXIndependentSegments {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput);
Ok(ExtXIndependentSegments)
}
}
// TODO: https://tools.ietf.org/html/rfc8216#section-4.3.5.2

45
src/version.rs Normal file
View file

@ -0,0 +1,45 @@
use std::fmt;
use std::str::FromStr;
use {Error, ErrorKind, Result};
// https://tools.ietf.org/html/rfc8216#section-7
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ProtocolVersion {
V1,
V2,
V3,
V4,
V5,
V6,
V7,
}
impl fmt::Display for ProtocolVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let n = match *self {
ProtocolVersion::V1 => 1,
ProtocolVersion::V2 => 2,
ProtocolVersion::V3 => 3,
ProtocolVersion::V4 => 4,
ProtocolVersion::V5 => 5,
ProtocolVersion::V6 => 6,
ProtocolVersion::V7 => 7,
};
write!(f, "{}", n)
}
}
impl FromStr for ProtocolVersion {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(match s {
"1" => ProtocolVersion::V1,
"2" => ProtocolVersion::V2,
"3" => ProtocolVersion::V3,
"4" => ProtocolVersion::V4,
"5" => ProtocolVersion::V5,
"6" => ProtocolVersion::V6,
"7" => ProtocolVersion::V7,
_ => track_panic!(ErrorKind::InvalidInput, "Unknown protocol version: {:?}", s),
})
}
}