diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 43aed53..0000000 --- a/src/types.rs +++ /dev/null @@ -1,840 +0,0 @@ -//! Miscellaneous types. -use crate::attribute::AttributePairs; -use crate::{Error, ErrorKind, Result}; -use std::fmt; -use std::ops::Deref; -use std::str::{self, FromStr}; -use std::time::Duration; -use trackable::error::ErrorKindExt; - -/// String that represents a single line in a playlist file. -/// -/// See: [4.1. Definition of a Playlist] -/// -/// [4.1. Definition of a Playlist]: https://tools.ietf.org/html/rfc8216#section-4.1 -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SingleLineString(String); -impl SingleLineString { - /// Makes a new `SingleLineString` instance. - /// - /// # Errors - /// - /// If the given string contains any control characters, - /// this function will return an error which has the kind `ErrorKind::InvalidInput`. - pub fn new>(s: T) -> Result { - let s = s.into(); - track_assert!(!s.chars().any(|c| c.is_control()), ErrorKind::InvalidInput); - Ok(SingleLineString(s)) - } -} -impl Deref for SingleLineString { - type Target = str; - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl AsRef for SingleLineString { - fn as_ref(&self) -> &str { - &self.0 - } -} -impl fmt::Display for SingleLineString { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -/// 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>(s: T) -> Result { - 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 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 { - 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 { - 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 { - 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) + (f64::from(duration.subsec_nanos()) / 1_000_000_000.0); - DecimalFloatingPoint(n) - } -} -impl From 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 { - 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 { - track_assert!(n.is_finite(), ErrorKind::InvalidInput); - Ok(SignedDecimalFloatingPoint(n)) - } - - /// Converts `DecimalFloatingPoint` to `f64`. - pub fn as_f64(self) -> f64 { - self.0 - } -} -impl From 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 { - 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); -impl HexadecimalSequence { - /// Makes a new `HexadecimalSequence` instance. - pub fn new>>(v: T) -> Self { - HexadecimalSequence(v.into()) - } - - /// Converts into the underlying byte sequence. - pub fn into_bytes(self) -> Vec { - 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 { - 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 { - 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 -#[allow(missing_docs)] -#[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 { - 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), - }) - } -} - -/// Byte range. -/// -/// See: [4.3.2.2. EXT-X-BYTERANGE] -/// -/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2 -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ByteRange { - pub length: usize, - pub start: Option, -} -impl fmt::Display for ByteRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.length)?; - if let Some(x) = self.start { - write!(f, "@{}", x)?; - } - Ok(()) - } -} -impl FromStr for ByteRange { - type Err = Error; - fn from_str(s: &str) -> Result { - let mut tokens = s.splitn(2, '@'); - let length = tokens.next().expect("Never fails"); - let start = if let Some(start) = tokens.next() { - Some(track!(start - .parse() - .map_err(|e| ErrorKind::InvalidInput.cause(e)))?) - } else { - None - }; - Ok(ByteRange { - length: track!(length.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, - start, - }) - } -} - -/// Decryption key. -/// -/// 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 -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct DecryptionKey { - pub method: EncryptionMethod, - pub uri: QuotedString, - pub iv: Option, - pub key_format: Option, - pub key_format_versions: Option, -} -impl DecryptionKey { - pub(crate) fn requires_version(&self) -> ProtocolVersion { - if self.key_format.is_some() | self.key_format_versions.is_some() { - ProtocolVersion::V5 - } else if self.iv.is_some() { - ProtocolVersion::V2 - } else { - ProtocolVersion::V1 - } - } -} -impl fmt::Display for DecryptionKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "METHOD={}", self.method)?; - write!(f, ",URI={}", self.uri)?; - if let Some(ref x) = self.iv { - write!(f, ",IV={}", x)?; - } - if let Some(ref x) = self.key_format { - write!(f, ",KEYFORMAT={}", x)?; - } - if let Some(ref x) = self.key_format_versions { - write!(f, ",KEYFORMATVERSIONS={}", x)?; - } - Ok(()) - } -} -impl FromStr for DecryptionKey { - type Err = Error; - fn from_str(s: &str) -> Result { - let mut method = None; - let mut uri = None; - let mut iv = None; - let mut key_format = None; - let mut key_format_versions = None; - let attrs = AttributePairs::parse(s); - for attr in attrs { - let (key, value) = track!(attr)?; - match key { - "METHOD" => method = Some(track!(value.parse())?), - "URI" => uri = Some(track!(value.parse())?), - "IV" => iv = Some(track!(value.parse())?), - "KEYFORMAT" => key_format = Some(track!(value.parse())?), - "KEYFORMATVERSIONS" => key_format_versions = Some(track!(value.parse())?), - _ => { - // [6.3.1. General Client Responsibilities] - // > ignore any attribute/value pair with an unrecognized AttributeName. - } - } - } - let method = track_assert_some!(method, ErrorKind::InvalidInput); - let uri = track_assert_some!(uri, ErrorKind::InvalidInput); - Ok(DecryptionKey { - method, - uri, - iv, - key_format, - key_format_versions, - }) - } -} - -/// Encryption method. -/// -/// 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 -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum EncryptionMethod { - Aes128, - SampleAes, -} -impl fmt::Display for EncryptionMethod { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - 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 { - match s { - "AES-128" => Ok(EncryptionMethod::Aes128), - "SAMPLE-AES" => Ok(EncryptionMethod::SampleAes), - _ => track_panic!( - ErrorKind::InvalidInput, - "Unknown encryption method: {:?}", - s - ), - } - } -} - -/// Playlist type. -/// -/// See: [4.3.3.5. EXT-X-PLAYLIST-TYPE] -/// -/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5 -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -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 { - match s { - "EVENT" => Ok(PlaylistType::Event), - "VOD" => Ok(PlaylistType::Vod), - _ => track_panic!(ErrorKind::InvalidInput, "Unknown playlist type: {:?}", s), - } - } -} - -/// Media type. -/// -/// See: [4.3.4.1. EXT-X-MEDIA] -/// -/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1 -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum MediaType { - Audio, - Video, - Subtitles, - ClosedCaptions, -} -impl fmt::Display for MediaType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - MediaType::Audio => "AUDIO".fmt(f), - MediaType::Video => "VIDEO".fmt(f), - MediaType::Subtitles => "SUBTITLES".fmt(f), - MediaType::ClosedCaptions => "CLOSED-CAPTIONS".fmt(f), - } - } -} -impl FromStr for MediaType { - type Err = Error; - fn from_str(s: &str) -> Result { - Ok(match s { - "AUDIO" => MediaType::Audio, - "VIDEO" => MediaType::Video, - "SUBTITLES" => MediaType::Subtitles, - "CLOSED-CAPTIONS" => MediaType::ClosedCaptions, - _ => track_panic!(ErrorKind::InvalidInput, "Unknown media type: {:?}", s), - }) - } -} - -/// Identifier of a rendition within the segments in a media playlist. -/// -/// See: [4.3.4.1. EXT-X-MEDIA] -/// -/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1 -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum InStreamId { - Cc1, - Cc2, - Cc3, - Cc4, - Service1, - Service2, - Service3, - Service4, - Service5, - Service6, - Service7, - Service8, - Service9, - Service10, - Service11, - Service12, - Service13, - Service14, - Service15, - Service16, - Service17, - Service18, - Service19, - Service20, - Service21, - Service22, - Service23, - Service24, - Service25, - Service26, - Service27, - Service28, - Service29, - Service30, - Service31, - Service32, - Service33, - Service34, - Service35, - Service36, - Service37, - Service38, - Service39, - Service40, - Service41, - Service42, - Service43, - Service44, - Service45, - Service46, - Service47, - Service48, - Service49, - Service50, - Service51, - Service52, - Service53, - Service54, - Service55, - Service56, - Service57, - Service58, - Service59, - Service60, - Service61, - Service62, - Service63, -} -impl fmt::Display for InStreamId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - format!("{:?}", self).to_uppercase().fmt(f) - } -} -impl FromStr for InStreamId { - type Err = Error; - fn from_str(s: &str) -> Result { - Ok(match s { - "CC1" => InStreamId::Cc1, - "CC2" => InStreamId::Cc2, - "CC3" => InStreamId::Cc3, - "CC4" => InStreamId::Cc4, - "SERVICE1" => InStreamId::Service1, - "SERVICE2" => InStreamId::Service2, - "SERVICE3" => InStreamId::Service3, - "SERVICE4" => InStreamId::Service4, - "SERVICE5" => InStreamId::Service5, - "SERVICE6" => InStreamId::Service6, - "SERVICE7" => InStreamId::Service7, - "SERVICE8" => InStreamId::Service8, - "SERVICE9" => InStreamId::Service9, - "SERVICE10" => InStreamId::Service10, - "SERVICE11" => InStreamId::Service11, - "SERVICE12" => InStreamId::Service12, - "SERVICE13" => InStreamId::Service13, - "SERVICE14" => InStreamId::Service14, - "SERVICE15" => InStreamId::Service15, - "SERVICE16" => InStreamId::Service16, - "SERVICE17" => InStreamId::Service17, - "SERVICE18" => InStreamId::Service18, - "SERVICE19" => InStreamId::Service19, - "SERVICE20" => InStreamId::Service20, - "SERVICE21" => InStreamId::Service21, - "SERVICE22" => InStreamId::Service22, - "SERVICE23" => InStreamId::Service23, - "SERVICE24" => InStreamId::Service24, - "SERVICE25" => InStreamId::Service25, - "SERVICE26" => InStreamId::Service26, - "SERVICE27" => InStreamId::Service27, - "SERVICE28" => InStreamId::Service28, - "SERVICE29" => InStreamId::Service29, - "SERVICE30" => InStreamId::Service30, - "SERVICE31" => InStreamId::Service31, - "SERVICE32" => InStreamId::Service32, - "SERVICE33" => InStreamId::Service33, - "SERVICE34" => InStreamId::Service34, - "SERVICE35" => InStreamId::Service35, - "SERVICE36" => InStreamId::Service36, - "SERVICE37" => InStreamId::Service37, - "SERVICE38" => InStreamId::Service38, - "SERVICE39" => InStreamId::Service39, - "SERVICE40" => InStreamId::Service40, - "SERVICE41" => InStreamId::Service41, - "SERVICE42" => InStreamId::Service42, - "SERVICE43" => InStreamId::Service43, - "SERVICE44" => InStreamId::Service44, - "SERVICE45" => InStreamId::Service45, - "SERVICE46" => InStreamId::Service46, - "SERVICE47" => InStreamId::Service47, - "SERVICE48" => InStreamId::Service48, - "SERVICE49" => InStreamId::Service49, - "SERVICE50" => InStreamId::Service50, - "SERVICE51" => InStreamId::Service51, - "SERVICE52" => InStreamId::Service52, - "SERVICE53" => InStreamId::Service53, - "SERVICE54" => InStreamId::Service54, - "SERVICE55" => InStreamId::Service55, - "SERVICE56" => InStreamId::Service56, - "SERVICE57" => InStreamId::Service57, - "SERVICE58" => InStreamId::Service58, - "SERVICE59" => InStreamId::Service59, - "SERVICE60" => InStreamId::Service60, - "SERVICE61" => InStreamId::Service61, - "SERVICE62" => InStreamId::Service62, - "SERVICE63" => InStreamId::Service63, - _ => track_panic!(ErrorKind::InvalidInput, "Unknown instream id: {:?}", s), - }) - } -} - -/// HDCP level. -/// -/// See: [4.3.4.2. EXT-X-STREAM-INF] -/// -/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 -#[allow(missing_docs)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum HdcpLevel { - Type0, - None, -} -impl fmt::Display for HdcpLevel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - HdcpLevel::Type0 => "TYPE-0".fmt(f), - HdcpLevel::None => "NONE".fmt(f), - } - } -} -impl FromStr for HdcpLevel { - type Err = Error; - fn from_str(s: &str) -> Result { - match s { - "TYPE-0" => Ok(HdcpLevel::Type0), - "NONE" => Ok(HdcpLevel::None), - _ => track_panic!(ErrorKind::InvalidInput, "Unknown HDCP level: {:?}", s), - } - } -} - -/// The identifier of a closed captions group or its absence. -/// -/// See: [4.3.4.2. EXT-X-STREAM-INF] -/// -/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ClosedCaptions { - GroupId(QuotedString), - None, -} -impl fmt::Display for ClosedCaptions { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ClosedCaptions::GroupId(ref x) => x.fmt(f), - ClosedCaptions::None => "NONE".fmt(f), - } - } -} -impl FromStr for ClosedCaptions { - type Err = Error; - fn from_str(s: &str) -> Result { - if s == "NONE" { - Ok(ClosedCaptions::None) - } else { - Ok(ClosedCaptions::GroupId(track!(s.parse())?)) - } - } -} - -/// Session data. -/// -/// See: [4.3.4.4. EXT-X-SESSION-DATA] -/// -/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4 -#[allow(missing_docs)] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum SessionData { - Value(QuotedString), - Uri(QuotedString), -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn single_line_string() { - assert!(SingleLineString::new("foo").is_ok()); - assert!(SingleLineString::new("b\rar").is_err()); - } -} diff --git a/src/types/byte_range.rs b/src/types/byte_range.rs new file mode 100644 index 0000000..b8db803 --- /dev/null +++ b/src/types/byte_range.rs @@ -0,0 +1,45 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; +use trackable::error::ErrorKindExt; + +/// Byte range. +/// +/// See: [4.3.2.2. EXT-X-BYTERANGE] +/// +/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2 +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ByteRange { + pub length: usize, + pub start: Option, +} + +impl fmt::Display for ByteRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.length)?; + if let Some(x) = self.start { + write!(f, "@{}", x)?; + } + Ok(()) + } +} + +impl FromStr for ByteRange { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut tokens = s.splitn(2, '@'); + let length = tokens.next().expect("Never fails"); + let start = if let Some(start) = tokens.next() { + Some(track!(start + .parse() + .map_err(|e| ErrorKind::InvalidInput.cause(e)))?) + } else { + None + }; + Ok(ByteRange { + length: track!(length.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?, + start, + }) + } +} diff --git a/src/types/closed_captions.rs b/src/types/closed_captions.rs new file mode 100644 index 0000000..6563ed4 --- /dev/null +++ b/src/types/closed_captions.rs @@ -0,0 +1,36 @@ +use crate::{Error, Result}; +use std::fmt; +use std::str::{self, FromStr}; +use crate::types::QuotedString; + +/// The identifier of a closed captions group or its absence. +/// +/// See: [4.3.4.2. EXT-X-STREAM-INF] +/// +/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ClosedCaptions { + GroupId(QuotedString), + None, +} + +impl fmt::Display for ClosedCaptions { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ClosedCaptions::GroupId(ref x) => x.fmt(f), + ClosedCaptions::None => "NONE".fmt(f), + } + } +} + +impl FromStr for ClosedCaptions { + type Err = Error; + fn from_str(s: &str) -> Result { + if s == "NONE" { + Ok(ClosedCaptions::None) + } else { + Ok(ClosedCaptions::GroupId(track!(s.parse())?)) + } + } +} diff --git a/src/types/decimal_floating_point.rs b/src/types/decimal_floating_point.rs new file mode 100644 index 0000000..08c49fe --- /dev/null +++ b/src/types/decimal_floating_point.rs @@ -0,0 +1,70 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; +use std::time::Duration; +use trackable::error::ErrorKindExt; + +/// 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 { + 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) + (f64::from(duration.subsec_nanos()) / 1_000_000_000.0); + DecimalFloatingPoint(n) + } +} + +impl From 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 { + 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)) + } +} diff --git a/src/types/decimal_resolution.rs b/src/types/decimal_resolution.rs new file mode 100644 index 0000000..e340451 --- /dev/null +++ b/src/types/decimal_resolution.rs @@ -0,0 +1,37 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; +use trackable::error::ErrorKindExt; + +/// 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 { + 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)))?, + }) + } +} diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs new file mode 100644 index 0000000..8b30328 --- /dev/null +++ b/src/types/decryption_key.rs @@ -0,0 +1,84 @@ +use crate::attribute::AttributePairs; +use crate::{Error, ErrorKind, Result}; +use crate::types::{QuotedString, InitializationVector, ProtocolVersion, EncryptionMethod}; +use std::fmt; +use std::str::{self, FromStr}; + +/// Decryption key. +/// +/// 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 +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DecryptionKey { + pub method: EncryptionMethod, + pub uri: QuotedString, + pub iv: Option, + pub key_format: Option, + pub key_format_versions: Option, +} + +impl DecryptionKey { + pub(crate) fn requires_version(&self) -> ProtocolVersion { + if self.key_format.is_some() | self.key_format_versions.is_some() { + ProtocolVersion::V5 + } else if self.iv.is_some() { + ProtocolVersion::V2 + } else { + ProtocolVersion::V1 + } + } +} + +impl fmt::Display for DecryptionKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "METHOD={}", self.method)?; + write!(f, ",URI={}", self.uri)?; + if let Some(ref x) = self.iv { + write!(f, ",IV={}", x)?; + } + if let Some(ref x) = self.key_format { + write!(f, ",KEYFORMAT={}", x)?; + } + if let Some(ref x) = self.key_format_versions { + write!(f, ",KEYFORMATVERSIONS={}", x)?; + } + Ok(()) + } +} + +impl FromStr for DecryptionKey { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut method = None; + let mut uri = None; + let mut iv = None; + let mut key_format = None; + let mut key_format_versions = None; + let attrs = AttributePairs::parse(s); + for attr in attrs { + let (key, value) = track!(attr)?; + match key { + "METHOD" => method = Some(track!(value.parse())?), + "URI" => uri = Some(track!(value.parse())?), + "IV" => iv = Some(track!(value.parse())?), + "KEYFORMAT" => key_format = Some(track!(value.parse())?), + "KEYFORMATVERSIONS" => key_format_versions = Some(track!(value.parse())?), + _ => { + // [6.3.1. General Client Responsibilities] + // > ignore any attribute/value pair with an unrecognized AttributeName. + } + } + } + let method = track_assert_some!(method, ErrorKind::InvalidInput); + let uri = track_assert_some!(uri, ErrorKind::InvalidInput); + Ok(DecryptionKey { + method, + uri, + iv, + key_format, + key_format_versions, + }) + } +} diff --git a/src/types/encryption_method.rs b/src/types/encryption_method.rs new file mode 100644 index 0000000..edd2911 --- /dev/null +++ b/src/types/encryption_method.rs @@ -0,0 +1,39 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; + +/// Encryption method. +/// +/// 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 +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum EncryptionMethod { + Aes128, + SampleAes, +} + +impl fmt::Display for EncryptionMethod { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + 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 { + match s { + "AES-128" => Ok(EncryptionMethod::Aes128), + "SAMPLE-AES" => Ok(EncryptionMethod::SampleAes), + _ => track_panic!( + ErrorKind::InvalidInput, + "Unknown encryption method: {:?}", + s + ), + } + } +} diff --git a/src/types/hdcp_level.rs b/src/types/hdcp_level.rs new file mode 100644 index 0000000..dacec45 --- /dev/null +++ b/src/types/hdcp_level.rs @@ -0,0 +1,35 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; + +/// HDCP level. +/// +/// See: [4.3.4.2. EXT-X-STREAM-INF] +/// +/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum HdcpLevel { + Type0, + None, +} + +impl fmt::Display for HdcpLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + HdcpLevel::Type0 => "TYPE-0".fmt(f), + HdcpLevel::None => "NONE".fmt(f), + } + } +} + +impl FromStr for HdcpLevel { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "TYPE-0" => Ok(HdcpLevel::Type0), + "NONE" => Ok(HdcpLevel::None), + _ => track_panic!(ErrorKind::InvalidInput, "Unknown HDCP level: {:?}", s), + } + } +} diff --git a/src/types/hexadecimal_sequence.rs b/src/types/hexadecimal_sequence.rs new file mode 100644 index 0000000..930adca --- /dev/null +++ b/src/types/hexadecimal_sequence.rs @@ -0,0 +1,68 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::ops::Deref; +use std::str::{self, FromStr}; +use trackable::error::ErrorKindExt; + +/// 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); + +impl HexadecimalSequence { + /// Makes a new `HexadecimalSequence` instance. + pub fn new>>(v: T) -> Self { + HexadecimalSequence(v.into()) + } + + /// Converts into the underlying byte sequence. + pub fn into_bytes(self) -> Vec { + 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 { + 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)) + } +} diff --git a/src/types/in_stream_id.rs b/src/types/in_stream_id.rs new file mode 100644 index 0000000..8b4aa56 --- /dev/null +++ b/src/types/in_stream_id.rs @@ -0,0 +1,162 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; + +/// Identifier of a rendition within the segments in a media playlist. +/// +/// See: [4.3.4.1. EXT-X-MEDIA] +/// +/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1 +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InStreamId { + Cc1, + Cc2, + Cc3, + Cc4, + Service1, + Service2, + Service3, + Service4, + Service5, + Service6, + Service7, + Service8, + Service9, + Service10, + Service11, + Service12, + Service13, + Service14, + Service15, + Service16, + Service17, + Service18, + Service19, + Service20, + Service21, + Service22, + Service23, + Service24, + Service25, + Service26, + Service27, + Service28, + Service29, + Service30, + Service31, + Service32, + Service33, + Service34, + Service35, + Service36, + Service37, + Service38, + Service39, + Service40, + Service41, + Service42, + Service43, + Service44, + Service45, + Service46, + Service47, + Service48, + Service49, + Service50, + Service51, + Service52, + Service53, + Service54, + Service55, + Service56, + Service57, + Service58, + Service59, + Service60, + Service61, + Service62, + Service63, +} + +impl fmt::Display for InStreamId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + format!("{:?}", self).to_uppercase().fmt(f) + } +} + +impl FromStr for InStreamId { + type Err = Error; + fn from_str(s: &str) -> Result { + Ok(match s { + "CC1" => InStreamId::Cc1, + "CC2" => InStreamId::Cc2, + "CC3" => InStreamId::Cc3, + "CC4" => InStreamId::Cc4, + "SERVICE1" => InStreamId::Service1, + "SERVICE2" => InStreamId::Service2, + "SERVICE3" => InStreamId::Service3, + "SERVICE4" => InStreamId::Service4, + "SERVICE5" => InStreamId::Service5, + "SERVICE6" => InStreamId::Service6, + "SERVICE7" => InStreamId::Service7, + "SERVICE8" => InStreamId::Service8, + "SERVICE9" => InStreamId::Service9, + "SERVICE10" => InStreamId::Service10, + "SERVICE11" => InStreamId::Service11, + "SERVICE12" => InStreamId::Service12, + "SERVICE13" => InStreamId::Service13, + "SERVICE14" => InStreamId::Service14, + "SERVICE15" => InStreamId::Service15, + "SERVICE16" => InStreamId::Service16, + "SERVICE17" => InStreamId::Service17, + "SERVICE18" => InStreamId::Service18, + "SERVICE19" => InStreamId::Service19, + "SERVICE20" => InStreamId::Service20, + "SERVICE21" => InStreamId::Service21, + "SERVICE22" => InStreamId::Service22, + "SERVICE23" => InStreamId::Service23, + "SERVICE24" => InStreamId::Service24, + "SERVICE25" => InStreamId::Service25, + "SERVICE26" => InStreamId::Service26, + "SERVICE27" => InStreamId::Service27, + "SERVICE28" => InStreamId::Service28, + "SERVICE29" => InStreamId::Service29, + "SERVICE30" => InStreamId::Service30, + "SERVICE31" => InStreamId::Service31, + "SERVICE32" => InStreamId::Service32, + "SERVICE33" => InStreamId::Service33, + "SERVICE34" => InStreamId::Service34, + "SERVICE35" => InStreamId::Service35, + "SERVICE36" => InStreamId::Service36, + "SERVICE37" => InStreamId::Service37, + "SERVICE38" => InStreamId::Service38, + "SERVICE39" => InStreamId::Service39, + "SERVICE40" => InStreamId::Service40, + "SERVICE41" => InStreamId::Service41, + "SERVICE42" => InStreamId::Service42, + "SERVICE43" => InStreamId::Service43, + "SERVICE44" => InStreamId::Service44, + "SERVICE45" => InStreamId::Service45, + "SERVICE46" => InStreamId::Service46, + "SERVICE47" => InStreamId::Service47, + "SERVICE48" => InStreamId::Service48, + "SERVICE49" => InStreamId::Service49, + "SERVICE50" => InStreamId::Service50, + "SERVICE51" => InStreamId::Service51, + "SERVICE52" => InStreamId::Service52, + "SERVICE53" => InStreamId::Service53, + "SERVICE54" => InStreamId::Service54, + "SERVICE55" => InStreamId::Service55, + "SERVICE56" => InStreamId::Service56, + "SERVICE57" => InStreamId::Service57, + "SERVICE58" => InStreamId::Service58, + "SERVICE59" => InStreamId::Service59, + "SERVICE60" => InStreamId::Service60, + "SERVICE61" => InStreamId::Service61, + "SERVICE62" => InStreamId::Service62, + "SERVICE63" => InStreamId::Service63, + _ => track_panic!(ErrorKind::InvalidInput, "Unknown instream id: {:?}", s), + }) + } +} diff --git a/src/types/initialization_vector.rs b/src/types/initialization_vector.rs new file mode 100644 index 0000000..9cb64fe --- /dev/null +++ b/src/types/initialization_vector.rs @@ -0,0 +1,56 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::ops::Deref; +use std::str::{self, FromStr}; +use trackable::error::ErrorKindExt; + +/// 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 { + 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)) + } +} diff --git a/src/types/media_type.rs b/src/types/media_type.rs new file mode 100644 index 0000000..103abde --- /dev/null +++ b/src/types/media_type.rs @@ -0,0 +1,41 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; + +/// Media type. +/// +/// See: [4.3.4.1. EXT-X-MEDIA] +/// +/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1 +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MediaType { + Audio, + Video, + Subtitles, + ClosedCaptions, +} + +impl fmt::Display for MediaType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + MediaType::Audio => "AUDIO".fmt(f), + MediaType::Video => "VIDEO".fmt(f), + MediaType::Subtitles => "SUBTITLES".fmt(f), + MediaType::ClosedCaptions => "CLOSED-CAPTIONS".fmt(f), + } + } +} + +impl FromStr for MediaType { + type Err = Error; + fn from_str(s: &str) -> Result { + Ok(match s { + "AUDIO" => MediaType::Audio, + "VIDEO" => MediaType::Video, + "SUBTITLES" => MediaType::Subtitles, + "CLOSED-CAPTIONS" => MediaType::ClosedCaptions, + _ => track_panic!(ErrorKind::InvalidInput, "Unknown media type: {:?}", s), + }) + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..d3f30d2 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,36 @@ +//! Miscellaneous types. +mod byte_range; +mod closed_captions; +mod decimal_floating_point; +mod decimal_resolution; +mod decryption_key; +mod encryption_method; +mod hdcp_level; +mod hexadecimal_sequence; +mod in_stream_id; +mod initialization_vector; +mod media_type; +mod playlist_type; +mod protocol_version; +mod quoted_string; +mod session_data; +mod signed_decimal_floating_point; +mod single_line_string; + +pub use byte_range::*; +pub use closed_captions::*; +pub use decimal_floating_point::*; +pub use decimal_resolution::*; +pub use decryption_key::*; +pub use encryption_method::*; +pub use hdcp_level::*; +pub use hexadecimal_sequence::*; +pub use in_stream_id::*; +pub use initialization_vector::*; +pub use media_type::*; +pub use playlist_type::*; +pub use protocol_version::*; +pub use quoted_string::*; +pub use session_data::*; +pub use signed_decimal_floating_point::*; +pub use single_line_string::*; diff --git a/src/types/playlist_type.rs b/src/types/playlist_type.rs new file mode 100644 index 0000000..e9dbb00 --- /dev/null +++ b/src/types/playlist_type.rs @@ -0,0 +1,35 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; + +/// Playlist type. +/// +/// See: [4.3.3.5. EXT-X-PLAYLIST-TYPE] +/// +/// [4.3.3.5. EXT-X-PLAYLIST-TYPE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.5 +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +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 { + match s { + "EVENT" => Ok(PlaylistType::Event), + "VOD" => Ok(PlaylistType::Vod), + _ => track_panic!(ErrorKind::InvalidInput, "Unknown playlist type: {:?}", s), + } + } +} diff --git a/src/types/protocol_version.rs b/src/types/protocol_version.rs new file mode 100644 index 0000000..312707b --- /dev/null +++ b/src/types/protocol_version.rs @@ -0,0 +1,47 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; + +/// [7. Protocol Version Compatibility] +/// +/// [7. Protocol Version Compatibility]: https://tools.ietf.org/html/rfc8216#section-7 +#[allow(missing_docs)] +#[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 { + 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), + }) + } +} diff --git a/src/types/quoted_string.rs b/src/types/quoted_string.rs new file mode 100644 index 0000000..07c6118 --- /dev/null +++ b/src/types/quoted_string.rs @@ -0,0 +1,62 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::ops::Deref; +use std::str::{self, FromStr}; + +/// 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>(s: T) -> Result { + 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 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 { + 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)) + } +} diff --git a/src/types/session_data.rs b/src/types/session_data.rs new file mode 100644 index 0000000..25c90e1 --- /dev/null +++ b/src/types/session_data.rs @@ -0,0 +1,13 @@ +use crate::types::QuotedString; + +/// Session data. +/// +/// See: [4.3.4.4. EXT-X-SESSION-DATA] +/// +/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4 +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SessionData { + Value(QuotedString), + Uri(QuotedString), +} diff --git a/src/types/signed_decimal_floating_point.rs b/src/types/signed_decimal_floating_point.rs new file mode 100644 index 0000000..3c50998 --- /dev/null +++ b/src/types/signed_decimal_floating_point.rs @@ -0,0 +1,56 @@ +use crate::{Error, ErrorKind, Result}; +use std::fmt; +use std::str::{self, FromStr}; +use trackable::error::ErrorKindExt; + +/// 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 { + track_assert!(n.is_finite(), ErrorKind::InvalidInput); + Ok(SignedDecimalFloatingPoint(n)) + } + + /// Converts `DecimalFloatingPoint` to `f64`. + pub fn as_f64(self) -> f64 { + self.0 + } +} + +impl From 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 { + 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)) + } +} diff --git a/src/types/single_line_string.rs b/src/types/single_line_string.rs new file mode 100644 index 0000000..3f99d35 --- /dev/null +++ b/src/types/single_line_string.rs @@ -0,0 +1,55 @@ +use crate::{ErrorKind, Result}; +use std::fmt; +use std::ops::Deref; + +/// String that represents a single line in a playlist file. +/// +/// See: [4.1. Definition of a Playlist] +/// +/// [4.1. Definition of a Playlist]: https://tools.ietf.org/html/rfc8216#section-4.1 +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SingleLineString(String); + +impl SingleLineString { + /// Makes a new `SingleLineString` instance. + /// + /// # Errors + /// + /// If the given string contains any control characters, + /// this function will return an error which has the kind `ErrorKind::InvalidInput`. + pub fn new>(s: T) -> Result { + let s = s.into(); + track_assert!(!s.chars().any(|c| c.is_control()), ErrorKind::InvalidInput); + Ok(SingleLineString(s)) + } +} + +impl Deref for SingleLineString { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for SingleLineString { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for SingleLineString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn single_line_string() { + assert!(SingleLineString::new("foo").is_ok()); + assert!(SingleLineString::new("b\rar").is_err()); + } +}