mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-26 00:51:00 +00:00
Refactor attribute
module
This commit is contained in:
parent
9f6d4e7ed7
commit
32b262713e
6 changed files with 365 additions and 223 deletions
195
src/attribute.rs
195
src/attribute.rs
|
@ -1,18 +1,19 @@
|
|||
use std::fmt;
|
||||
use std::str::{self, FromStr};
|
||||
use std::time::Duration;
|
||||
use std::u8;
|
||||
use trackable::error::ErrorKindExt;
|
||||
use std::collections::HashSet;
|
||||
use std::str;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use {ErrorKind, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AttributePairs<'a> {
|
||||
input: &'a str,
|
||||
visited_keys: HashSet<&'a str>,
|
||||
}
|
||||
impl<'a> AttributePairs<'a> {
|
||||
pub fn parse(input: &'a str) -> Self {
|
||||
AttributePairs { input }
|
||||
AttributePairs {
|
||||
input,
|
||||
visited_keys: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_name(&mut self) -> Result<&'a str> {
|
||||
|
@ -59,183 +60,17 @@ impl<'a> Iterator for AttributePairs<'a> {
|
|||
}
|
||||
|
||||
let result = || -> Result<(&'a str, &'a str)> {
|
||||
// TODO: check key duplications
|
||||
let key = track!(self.parse_name())?;
|
||||
track_assert!(
|
||||
self.visited_keys.insert(key),
|
||||
ErrorKind::InvalidInput,
|
||||
"Duplicate attribute key: {:?}",
|
||||
key
|
||||
);
|
||||
|
||||
let value = self.parse_raw_value();
|
||||
Ok((key, value))
|
||||
}();
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: export and rename
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct QuotedString(String);
|
||||
impl QuotedString {
|
||||
pub fn new<T: Into<String>>(s: T) -> Result<Self> {
|
||||
// TODO: validate
|
||||
Ok(QuotedString(s.into()))
|
||||
}
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
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>);
|
||||
impl HexadecimalSequence {
|
||||
pub fn new<T: Into<Vec<u8>>>(v: T) -> Self {
|
||||
HexadecimalSequence(v.into())
|
||||
}
|
||||
}
|
||||
impl fmt::Display for HexadecimalSequence {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "0x")?;
|
||||
for b in &self.0 {
|
||||
write!(f, "{:02x}", b)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for HexadecimalSequence {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(
|
||||
s.starts_with("0x") || s.starts_with("0X"),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
track_assert!(s.len() % 2 == 0, ErrorKind::InvalidInput);
|
||||
|
||||
let mut v = Vec::with_capacity(s.len() / 2 - 1);
|
||||
for c in s.as_bytes().chunks(2).skip(1) {
|
||||
let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
let b =
|
||||
track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
v.push(b);
|
||||
}
|
||||
Ok(HexadecimalSequence(v))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: delete
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DecimalInteger(pub u64);
|
||||
impl fmt::Display for DecimalInteger {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
impl FromStr for DecimalInteger {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
Ok(DecimalInteger(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct DecimalFloatingPoint(f64);
|
||||
impl DecimalFloatingPoint {
|
||||
pub fn to_duration(&self) -> Duration {
|
||||
let secs = self.0 as u64;
|
||||
let nanos = (self.0.fract() * 1_000_000_000.0) as u32;
|
||||
Duration::new(secs, nanos)
|
||||
}
|
||||
pub fn from_duration(duration: Duration) -> Self {
|
||||
let n = (duration.as_secs() as f64) + (duration.subsec_nanos() as f64 / 1_000_000_000.0);
|
||||
DecimalFloatingPoint(n)
|
||||
}
|
||||
pub fn as_f64(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl Eq for DecimalFloatingPoint {}
|
||||
impl fmt::Display for DecimalFloatingPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl FromStr for DecimalFloatingPoint {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(
|
||||
s.chars().all(|c| match c {
|
||||
'0'...'9' | '.' => true,
|
||||
_ => false,
|
||||
}),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
Ok(DecimalFloatingPoint(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct SignedDecimalFloatingPoint(pub f64);
|
||||
impl Eq for SignedDecimalFloatingPoint {}
|
||||
impl fmt::Display for SignedDecimalFloatingPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl FromStr for SignedDecimalFloatingPoint {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(
|
||||
s.chars().all(|c| match c {
|
||||
'0'...'9' | '.' | '-' => true,
|
||||
_ => false,
|
||||
}),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
Ok(SignedDecimalFloatingPoint(n))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DecimalResolution {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
impl fmt::Display for DecimalResolution {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}x{}", self.width, self.height)
|
||||
}
|
||||
}
|
||||
impl FromStr for DecimalResolution {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let mut tokens = s.splitn(2, 'x');
|
||||
let width = tokens.next().expect("Never fails");
|
||||
let height = track_assert_some!(tokens.next(), ErrorKind::InvalidInput);
|
||||
Ok(DecimalResolution {
|
||||
width: track!(width.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
|
||||
height: track!(height.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use attribute::{AttributePairs, DecimalFloatingPoint, DecimalInteger, DecimalResolution,
|
||||
QuotedString};
|
||||
use types::{ClosedCaptions, DecryptionKey, HdcpLevel, InStreamId, MediaType, ProtocolVersion,
|
||||
SessionData, SingleLineString};
|
||||
use super::parse_yes_or_no;
|
||||
use attribute::AttributePairs;
|
||||
use types::{ClosedCaptions, DecimalFloatingPoint, DecimalResolution, DecryptionKey, HdcpLevel,
|
||||
InStreamId, MediaType, ProtocolVersion, QuotedString, SessionData, SingleLineString};
|
||||
use super::{parse_yes_or_no, parse_u64};
|
||||
|
||||
/// [4.3.4.1. EXT-X-MEDIA]
|
||||
///
|
||||
|
@ -192,7 +191,7 @@ impl FromStr for ExtXMedia {
|
|||
"FORCED" => forced = Some(track!(parse_yes_or_no(value))?),
|
||||
"INSTREAM-ID" => {
|
||||
let s: QuotedString = track!(value.parse())?;
|
||||
instream_id = Some(track!(s.as_str().parse())?);
|
||||
instream_id = Some(track!(s.parse())?);
|
||||
}
|
||||
"CHARACTERISTICS" => characteristics = Some(track!(value.parse())?),
|
||||
"CHANNELS" => channels = Some(track!(value.parse())?),
|
||||
|
@ -240,8 +239,8 @@ impl FromStr for ExtXMedia {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXStreamInf {
|
||||
uri: SingleLineString,
|
||||
bandwidth: DecimalInteger,
|
||||
average_bandwidth: Option<DecimalInteger>,
|
||||
bandwidth: u64,
|
||||
average_bandwidth: Option<u64>,
|
||||
codecs: Option<QuotedString>,
|
||||
resolution: Option<DecimalResolution>,
|
||||
frame_rate: Option<DecimalFloatingPoint>,
|
||||
|
@ -255,7 +254,7 @@ impl ExtXStreamInf {
|
|||
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
|
||||
|
||||
/// Makes a new `ExtXStreamInf` tag.
|
||||
pub fn new(uri: SingleLineString, bandwidth: DecimalInteger) -> Self {
|
||||
pub fn new(uri: SingleLineString, bandwidth: u64) -> Self {
|
||||
ExtXStreamInf {
|
||||
uri,
|
||||
bandwidth,
|
||||
|
@ -277,12 +276,12 @@ impl ExtXStreamInf {
|
|||
}
|
||||
|
||||
/// Returns the peak segment bit rate of the variant stream.
|
||||
pub fn bandwidth(&self) -> DecimalInteger {
|
||||
pub fn bandwidth(&self) -> u64 {
|
||||
self.bandwidth
|
||||
}
|
||||
|
||||
/// Returns the average segment bit rate of the variant stream.
|
||||
pub fn average_bandwidth(&self) -> Option<DecimalInteger> {
|
||||
pub fn average_bandwidth(&self) -> Option<u64> {
|
||||
self.average_bandwidth
|
||||
}
|
||||
|
||||
|
@ -392,8 +391,8 @@ impl FromStr for ExtXStreamInf {
|
|||
for attr in attrs {
|
||||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
||||
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||
"FRAME-RATE" => frame_rate = Some(track!(value.parse())?),
|
||||
|
@ -431,8 +430,8 @@ impl FromStr for ExtXStreamInf {
|
|||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXIFrameStreamInf {
|
||||
uri: QuotedString,
|
||||
bandwidth: DecimalInteger,
|
||||
average_bandwidth: Option<DecimalInteger>,
|
||||
bandwidth: u64,
|
||||
average_bandwidth: Option<u64>,
|
||||
codecs: Option<QuotedString>,
|
||||
resolution: Option<DecimalResolution>,
|
||||
hdcp_level: Option<HdcpLevel>,
|
||||
|
@ -442,7 +441,7 @@ impl ExtXIFrameStreamInf {
|
|||
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
||||
|
||||
/// Makes a new `ExtXIFrameStreamInf` tag.
|
||||
pub fn new(uri: QuotedString, bandwidth: DecimalInteger) -> Self {
|
||||
pub fn new(uri: QuotedString, bandwidth: u64) -> Self {
|
||||
ExtXIFrameStreamInf {
|
||||
uri,
|
||||
bandwidth,
|
||||
|
@ -460,12 +459,12 @@ impl ExtXIFrameStreamInf {
|
|||
}
|
||||
|
||||
/// Returns the peak segment bit rate of the variant stream.
|
||||
pub fn bandwidth(&self) -> DecimalInteger {
|
||||
pub fn bandwidth(&self) -> u64 {
|
||||
self.bandwidth
|
||||
}
|
||||
|
||||
/// Returns the average segment bit rate of the variant stream.
|
||||
pub fn average_bandwidth(&self) -> Option<DecimalInteger> {
|
||||
pub fn average_bandwidth(&self) -> Option<u64> {
|
||||
self.average_bandwidth
|
||||
}
|
||||
|
||||
|
@ -534,8 +533,8 @@ impl FromStr for ExtXIFrameStreamInf {
|
|||
let (key, value) = track!(attr)?;
|
||||
match key {
|
||||
"URI" => uri = Some(track!(value.parse())?),
|
||||
"BANDWIDTH" => bandwidth = Some(track!(value.parse())?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(value.parse())?),
|
||||
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||
|
@ -708,8 +707,7 @@ impl FromStr for ExtXSessionKey {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use attribute::HexadecimalSequence;
|
||||
use types::EncryptionMethod;
|
||||
use types::{EncryptionMethod, InitializationVector};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -723,7 +721,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn ext_x_stream_inf() {
|
||||
let tag = ExtXStreamInf::new(SingleLineString::new("foo").unwrap(), DecimalInteger(1000));
|
||||
let tag = ExtXStreamInf::new(SingleLineString::new("foo").unwrap(), 1000);
|
||||
let text = "#EXT-X-STREAM-INF:BANDWIDTH=1000\nfoo";
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
|
@ -732,7 +730,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn ext_x_i_frame_stream_inf() {
|
||||
let tag = ExtXIFrameStreamInf::new(quoted_string("foo"), DecimalInteger(1000));
|
||||
let tag = ExtXIFrameStreamInf::new(quoted_string("foo"), 1000);
|
||||
let text = r#"#EXT-X-I-FRAME-STREAM-INF:URI="foo",BANDWIDTH=1000"#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
|
@ -773,11 +771,14 @@ mod test {
|
|||
let tag = ExtXSessionKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: quoted_string("foo"),
|
||||
iv: Some(HexadecimalSequence::new(vec![0, 1, 2])),
|
||||
iv: Some(InitializationVector([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
])),
|
||||
key_format: None,
|
||||
key_format_versions: None,
|
||||
});
|
||||
let text = r#"#EXT-X-SESSION-KEY:METHOD=AES-128,URI="foo",IV=0x000102"#;
|
||||
let text =
|
||||
r#"#EXT-X-SESSION-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f"#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||
|
|
|
@ -2,8 +2,8 @@ use std::fmt;
|
|||
use std::str::FromStr;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use attribute::{AttributePairs, SignedDecimalFloatingPoint};
|
||||
use types::ProtocolVersion;
|
||||
use attribute::AttributePairs;
|
||||
use types::{ProtocolVersion, SignedDecimalFloatingPoint};
|
||||
use super::parse_yes_or_no;
|
||||
|
||||
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
|
||||
|
@ -128,13 +128,13 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn ext_x_start() {
|
||||
let tag = ExtXStart::new(SignedDecimalFloatingPoint(-1.23));
|
||||
let tag = ExtXStart::new(SignedDecimalFloatingPoint::new(-1.23).unwrap());
|
||||
let text = "#EXT-X-START:TIME-OFFSET=-1.23";
|
||||
assert_eq!(text.parse().ok(), Some(tag));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||
|
||||
let tag = ExtXStart::with_precise(SignedDecimalFloatingPoint(1.23), YesOrNo::Yes);
|
||||
let tag = ExtXStart::with_precise(SignedDecimalFloatingPoint::new(1.23).unwrap(), true);
|
||||
let text = "#EXT-X-START:TIME-OFFSET=1.23,PRECISE=YES";
|
||||
assert_eq!(text.parse().ok(), Some(tag));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
|
|
|
@ -6,8 +6,9 @@ use chrono::{DateTime, FixedOffset, NaiveDate};
|
|||
use trackable::error::ErrorKindExt;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use attribute::{AttributePairs, DecimalFloatingPoint, QuotedString};
|
||||
use types::{ByteRange, DecryptionKey, ProtocolVersion, SingleLineString};
|
||||
use attribute::AttributePairs;
|
||||
use types::{ByteRange, DecimalFloatingPoint, DecryptionKey, ProtocolVersion, QuotedString,
|
||||
SingleLineString};
|
||||
|
||||
/// [4.3.2.1. EXTINF]
|
||||
///
|
||||
|
@ -434,14 +435,14 @@ impl FromStr for ExtXDateRange {
|
|||
"START-DATE" => {
|
||||
let s: QuotedString = track!(value.parse())?;
|
||||
start_date = Some(track!(
|
||||
NaiveDate::parse_from_str(s.as_str(), "%Y-%m-%d")
|
||||
NaiveDate::parse_from_str(&s, "%Y-%m-%d")
|
||||
.map_err(|e| ErrorKind::InvalidInput.cause(e))
|
||||
)?);
|
||||
}
|
||||
"END-DATE" => {
|
||||
let s: QuotedString = track!(value.parse())?;
|
||||
end_date = Some(track!(
|
||||
NaiveDate::parse_from_str(s.as_str(), "%Y-%m-%d")
|
||||
NaiveDate::parse_from_str(&s, "%Y-%m-%d")
|
||||
.map_err(|e| ErrorKind::InvalidInput.cause(e))
|
||||
)?);
|
||||
}
|
||||
|
@ -496,8 +497,7 @@ impl FromStr for ExtXDateRange {
|
|||
mod test {
|
||||
use std::time::Duration;
|
||||
|
||||
use attribute::HexadecimalSequence;
|
||||
use types::EncryptionMethod;
|
||||
use types::{EncryptionMethod, InitializationVector};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -571,11 +571,13 @@ mod test {
|
|||
let tag = ExtXKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: QuotedString::new("foo").unwrap(),
|
||||
iv: Some(HexadecimalSequence::new(vec![0, 1, 2])),
|
||||
iv: Some(InitializationVector([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
])),
|
||||
key_format: None,
|
||||
key_format_versions: None,
|
||||
});
|
||||
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102"#;
|
||||
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f"#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||
|
@ -583,11 +585,13 @@ mod test {
|
|||
let tag = ExtXKey::new(DecryptionKey {
|
||||
method: EncryptionMethod::Aes128,
|
||||
uri: QuotedString::new("foo").unwrap(),
|
||||
iv: Some(HexadecimalSequence::new(vec![0, 1, 2])),
|
||||
iv: Some(InitializationVector([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
])),
|
||||
key_format: Some(QuotedString::new("baz").unwrap()),
|
||||
key_format_versions: None,
|
||||
});
|
||||
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102,KEYFORMAT="baz""#;
|
||||
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#;
|
||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||
assert_eq!(tag.to_string(), text);
|
||||
assert_eq!(tag.requires_version(), ProtocolVersion::V5);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use trackable::error::ErrorKindExt;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
|
||||
|
@ -237,3 +238,8 @@ fn parse_yes_or_no(s: &str) -> Result<bool> {
|
|||
_ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_u64(s: &str) -> Result<u64> {
|
||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
Ok(n)
|
||||
}
|
||||
|
|
302
src/types.rs
302
src/types.rs
|
@ -1,11 +1,12 @@
|
|||
//! Miscellaneous types.
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::str::{self, FromStr};
|
||||
use std::time::Duration;
|
||||
use trackable::error::ErrorKindExt;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use attribute::{AttributePairs, HexadecimalSequence, QuotedString};
|
||||
use attribute::AttributePairs;
|
||||
|
||||
/// String that represents a single line in a playlist file.
|
||||
///
|
||||
|
@ -44,6 +45,301 @@ impl fmt::Display for SingleLineString {
|
|||
}
|
||||
}
|
||||
|
||||
/// Quoted string.
|
||||
///
|
||||
/// See: [4.2. Attribute Lists]
|
||||
///
|
||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct QuotedString(String);
|
||||
impl QuotedString {
|
||||
/// Makes a new `QuotedString` instance.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the given string contains any control characters or double-quote character,
|
||||
/// this function will return an error which has the kind `ErrorKind::InvalidInput`.
|
||||
pub fn new<T: Into<String>>(s: T) -> Result<Self> {
|
||||
let s = s.into();
|
||||
track_assert!(
|
||||
!s.chars().any(|c| c.is_control() || c == '"'),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
Ok(QuotedString(s))
|
||||
}
|
||||
}
|
||||
impl Deref for QuotedString {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for QuotedString {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
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!(QuotedString::new(s))
|
||||
}
|
||||
}
|
||||
|
||||
/// Decimal resolution.
|
||||
///
|
||||
/// See: [4.2. Attribute Lists]
|
||||
///
|
||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DecimalResolution {
|
||||
/// Horizontal pixel dimension.
|
||||
pub width: usize,
|
||||
|
||||
/// Vertical pixel dimension.
|
||||
pub height: usize,
|
||||
}
|
||||
impl fmt::Display for DecimalResolution {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}x{}", self.width, self.height)
|
||||
}
|
||||
}
|
||||
impl FromStr for DecimalResolution {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let mut tokens = s.splitn(2, 'x');
|
||||
let width = tokens.next().expect("Never fails");
|
||||
let height = track_assert_some!(tokens.next(), ErrorKind::InvalidInput);
|
||||
Ok(DecimalResolution {
|
||||
width: track!(width.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
|
||||
height: track!(height.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-negative decimal floating-point number.
|
||||
///
|
||||
/// See: [4.2. Attribute Lists]
|
||||
///
|
||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct DecimalFloatingPoint(f64);
|
||||
impl DecimalFloatingPoint {
|
||||
/// Makes a new `DecimalFloatingPoint` instance.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The given value must have a positive sign and be finite,
|
||||
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
||||
pub fn new(n: f64) -> Result<Self> {
|
||||
track_assert!(n.is_sign_positive(), ErrorKind::InvalidInput);
|
||||
track_assert!(n.is_finite(), ErrorKind::InvalidInput);
|
||||
Ok(DecimalFloatingPoint(n))
|
||||
}
|
||||
|
||||
/// Converts `DecimalFloatingPoint` to `f64`.
|
||||
pub fn as_f64(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn to_duration(&self) -> Duration {
|
||||
let secs = self.0 as u64;
|
||||
let nanos = (self.0.fract() * 1_000_000_000.0) as u32;
|
||||
Duration::new(secs, nanos)
|
||||
}
|
||||
|
||||
pub(crate) fn from_duration(duration: Duration) -> Self {
|
||||
let n = (duration.as_secs() as f64) + (duration.subsec_nanos() as f64 / 1_000_000_000.0);
|
||||
DecimalFloatingPoint(n)
|
||||
}
|
||||
}
|
||||
impl From<u32> for DecimalFloatingPoint {
|
||||
fn from(f: u32) -> Self {
|
||||
DecimalFloatingPoint(f64::from(f))
|
||||
}
|
||||
}
|
||||
impl Eq for DecimalFloatingPoint {}
|
||||
impl fmt::Display for DecimalFloatingPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl FromStr for DecimalFloatingPoint {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(
|
||||
s.chars().all(|c| c.is_digit(10) || c == '.'),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
Ok(DecimalFloatingPoint(n))
|
||||
}
|
||||
}
|
||||
|
||||
/// Signed decimal floating-point number.
|
||||
///
|
||||
/// See: [4.2. Attribute Lists]
|
||||
///
|
||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct SignedDecimalFloatingPoint(f64);
|
||||
impl SignedDecimalFloatingPoint {
|
||||
/// Makes a new `SignedDecimalFloatingPoint` instance.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The given value must be finite,
|
||||
/// otherwise this function will return an error that has the kind `ErrorKind::InvalidInput`.
|
||||
pub fn new(n: f64) -> Result<Self> {
|
||||
track_assert!(n.is_finite(), ErrorKind::InvalidInput);
|
||||
Ok(SignedDecimalFloatingPoint(n))
|
||||
}
|
||||
|
||||
/// Converts `DecimalFloatingPoint` to `f64`.
|
||||
pub fn as_f64(&self) -> f64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl From<i32> for SignedDecimalFloatingPoint {
|
||||
fn from(f: i32) -> Self {
|
||||
SignedDecimalFloatingPoint(f64::from(f))
|
||||
}
|
||||
}
|
||||
impl Eq for SignedDecimalFloatingPoint {}
|
||||
impl fmt::Display for SignedDecimalFloatingPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
impl FromStr for SignedDecimalFloatingPoint {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(
|
||||
s.chars().all(|c| c.is_digit(10) || c == '.' || c == '-'),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
Ok(SignedDecimalFloatingPoint(n))
|
||||
}
|
||||
}
|
||||
|
||||
/// Hexadecimal sequence.
|
||||
///
|
||||
/// See: [4.2. Attribute Lists]
|
||||
///
|
||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct HexadecimalSequence(Vec<u8>);
|
||||
impl HexadecimalSequence {
|
||||
/// Makes a new `HexadecimalSequence` instance.
|
||||
pub fn new<T: Into<Vec<u8>>>(v: T) -> Self {
|
||||
HexadecimalSequence(v.into())
|
||||
}
|
||||
|
||||
/// Converts into the underlying byte sequence.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl Deref for HexadecimalSequence {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for HexadecimalSequence {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl fmt::Display for HexadecimalSequence {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "0x")?;
|
||||
for b in &self.0 {
|
||||
write!(f, "{:02x}", b)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for HexadecimalSequence {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(
|
||||
s.starts_with("0x") || s.starts_with("0X"),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
track_assert!(s.len() % 2 == 0, ErrorKind::InvalidInput);
|
||||
|
||||
let mut v = Vec::with_capacity(s.len() / 2 - 1);
|
||||
for c in s.as_bytes().chunks(2).skip(1) {
|
||||
let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
let b =
|
||||
track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
v.push(b);
|
||||
}
|
||||
Ok(HexadecimalSequence(v))
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialization vector.
|
||||
///
|
||||
/// See: [4.3.2.4. EXT-X-KEY]
|
||||
///
|
||||
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct InitializationVector(pub [u8; 16]);
|
||||
impl Deref for InitializationVector {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for InitializationVector {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl fmt::Display for InitializationVector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "0x")?;
|
||||
for b in &self.0 {
|
||||
write!(f, "{:02x}", b)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for InitializationVector {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
track_assert!(
|
||||
s.starts_with("0x") || s.starts_with("0X"),
|
||||
ErrorKind::InvalidInput
|
||||
);
|
||||
track_assert_eq!(s.len() - 2, 32, ErrorKind::InvalidInput);
|
||||
|
||||
let mut v = [0; 16];
|
||||
for (i, c) in s.as_bytes().chunks(2).skip(1).enumerate() {
|
||||
let d = track!(str::from_utf8(c).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
let b =
|
||||
track!(u8::from_str_radix(d, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||
v[i] = b;
|
||||
}
|
||||
Ok(InitializationVector(v))
|
||||
}
|
||||
}
|
||||
|
||||
/// [7. Protocol Version Compatibility]
|
||||
///
|
||||
/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7
|
||||
|
@ -137,7 +433,7 @@ impl FromStr for ByteRange {
|
|||
pub struct DecryptionKey {
|
||||
pub method: EncryptionMethod,
|
||||
pub uri: QuotedString,
|
||||
pub iv: Option<HexadecimalSequence>, // TODO: iv
|
||||
pub iv: Option<InitializationVector>,
|
||||
pub key_format: Option<QuotedString>,
|
||||
pub key_format_versions: Option<QuotedString>,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue