1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-18 16:28:20 +00:00

move tags into their own modules

This commit is contained in:
Luro02 2019-09-06 12:55:00 +02:00
parent 4324cb79d0
commit 3ecbbd9acb
33 changed files with 2194 additions and 1908 deletions

View file

@ -38,6 +38,7 @@ mod line;
mod master_playlist;
mod media_playlist;
mod media_segment;
mod utils;
/// This crate specific `Result` type.
pub type Result<T> = std::result::Result<T, Error>;

42
src/tags/basic/m3u.rs Normal file
View file

@ -0,0 +1,42 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.1.1. EXTM3U]
///
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtM3u;
impl ExtM3u {
pub(crate) const PREFIX: &'static str = "#EXTM3U";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn extm3u() {
assert_eq!("#EXTM3U".parse::<ExtM3u>().ok(), Some(ExtM3u));
assert_eq!(ExtM3u.to_string(), "#EXTM3U");
assert_eq!(ExtM3u.requires_version(), ProtocolVersion::V1);
}
}

5
src/tags/basic/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod m3u;
mod version;
pub use m3u::*;
pub use version::*;

View file

@ -3,32 +3,6 @@ use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.1.1. EXTM3U]
///
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtM3u;
impl ExtM3u {
pub(crate) const PREFIX: &'static str = "#EXTM3U";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
/// [4.3.1.2. EXT-X-VERSION]
///
/// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
@ -36,6 +10,7 @@ impl FromStr for ExtM3u {
pub struct ExtXVersion {
version: ProtocolVersion,
}
impl ExtXVersion {
pub(crate) const PREFIX: &'static str = "#EXT-X-VERSION:";
@ -54,11 +29,13 @@ impl ExtXVersion {
ProtocolVersion::V1
}
}
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> {
@ -69,17 +46,11 @@ impl FromStr for ExtXVersion {
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn extm3u() {
assert_eq!("#EXTM3U".parse::<ExtM3u>().ok(), Some(ExtM3u));
assert_eq!(ExtM3u.to_string(), "#EXTM3U");
assert_eq!(ExtM3u.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_version() {
let tag = ExtXVersion::new(ProtocolVersion::V6);

View file

@ -1,918 +0,0 @@
use super::{parse_u64, parse_yes_or_no};
use crate::attribute::AttributePairs;
use crate::types::{
ClosedCaptions, DecimalFloatingPoint, DecimalResolution, DecryptionKey, HdcpLevel, InStreamId,
MediaType, ProtocolVersion, QuotedString, SessionData, SingleLineString,
};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// `ExtXMedia` builder.
#[derive(Debug, Clone)]
pub struct ExtXMediaBuilder {
media_type: Option<MediaType>,
uri: Option<QuotedString>,
group_id: Option<QuotedString>,
language: Option<QuotedString>,
assoc_language: Option<QuotedString>,
name: Option<QuotedString>,
default: bool,
autoselect: Option<bool>,
forced: Option<bool>,
instream_id: Option<InStreamId>,
characteristics: Option<QuotedString>,
channels: Option<QuotedString>,
}
impl ExtXMediaBuilder {
/// Makes a `ExtXMediaBuilder` instance.
pub fn new() -> Self {
ExtXMediaBuilder {
media_type: None,
uri: None,
group_id: None,
language: None,
assoc_language: None,
name: None,
default: false,
autoselect: None,
forced: None,
instream_id: None,
characteristics: None,
channels: None,
}
}
/// Sets the media type of the rendition.
pub fn media_type(&mut self, media_type: MediaType) -> &mut Self {
self.media_type = Some(media_type);
self
}
/// Sets the identifier that specifies the group to which the rendition belongs.
pub fn group_id(&mut self, group_id: QuotedString) -> &mut Self {
self.group_id = Some(group_id);
self
}
/// Sets a human-readable description of the rendition.
pub fn name(&mut self, name: QuotedString) -> &mut Self {
self.name = Some(name);
self
}
/// Sets the URI that identifies the media playlist.
pub fn uri(&mut self, uri: QuotedString) -> &mut Self {
self.uri = Some(uri);
self
}
/// Sets the name of the primary language used in the rendition.
pub fn language(&mut self, language: QuotedString) -> &mut Self {
self.language = Some(language);
self
}
/// Sets the name of a language associated with the rendition.
pub fn assoc_language(&mut self, language: QuotedString) -> &mut Self {
self.assoc_language = Some(language);
self
}
/// Sets the value of the `default` flag.
pub fn default(&mut self, b: bool) -> &mut Self {
self.default = b;
self
}
/// Sets the value of the `autoselect` flag.
pub fn autoselect(&mut self, b: bool) -> &mut Self {
self.autoselect = Some(b);
self
}
/// Sets the value of the `forced` flag.
pub fn forced(&mut self, b: bool) -> &mut Self {
self.forced = Some(b);
self
}
/// Sets the identifier that specifies a rendition within the segments in the media playlist.
pub fn instream_id(&mut self, id: InStreamId) -> &mut Self {
self.instream_id = Some(id);
self
}
/// Sets the string that represents uniform type identifiers (UTI).
pub fn characteristics(&mut self, characteristics: QuotedString) -> &mut Self {
self.characteristics = Some(characteristics);
self
}
/// Sets the string that represents the parameters of the rendition.
pub fn channels(&mut self, channels: QuotedString) -> &mut Self {
self.channels = Some(channels);
self
}
/// Builds a `ExtXMedia` instance.
pub fn finish(self) -> Result<ExtXMedia> {
let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput);
let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput);
let name = track_assert_some!(self.name, ErrorKind::InvalidInput);
if MediaType::ClosedCaptions == media_type {
track_assert_ne!(self.uri, None, ErrorKind::InvalidInput);
track_assert!(self.instream_id.is_some(), ErrorKind::InvalidInput);
} else {
track_assert!(self.instream_id.is_none(), ErrorKind::InvalidInput);
}
if self.default && self.autoselect.is_some() {
track_assert_eq!(self.autoselect, Some(true), ErrorKind::InvalidInput);
}
if MediaType::Subtitles != media_type {
track_assert_eq!(self.forced, None, ErrorKind::InvalidInput);
}
Ok(ExtXMedia {
media_type,
uri: self.uri,
group_id,
language: self.language,
assoc_language: self.assoc_language,
name,
default: self.default,
autoselect: self.autoselect.unwrap_or(false),
forced: self.forced.unwrap_or(false),
instream_id: self.instream_id,
characteristics: self.characteristics,
channels: self.channels,
})
}
}
impl Default for ExtXMediaBuilder {
fn default() -> Self {
Self::new()
}
}
/// [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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXMedia {
media_type: MediaType,
uri: Option<QuotedString>,
group_id: QuotedString,
language: Option<QuotedString>,
assoc_language: Option<QuotedString>,
name: QuotedString,
default: bool,
autoselect: bool,
forced: bool,
instream_id: Option<InStreamId>,
characteristics: Option<QuotedString>,
channels: Option<QuotedString>,
}
impl ExtXMedia {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
/// Makes a new `ExtXMedia` tag.
pub fn new(media_type: MediaType, group_id: QuotedString, name: QuotedString) -> Self {
ExtXMedia {
media_type,
uri: None,
group_id,
language: None,
assoc_language: None,
name,
default: false,
autoselect: false,
forced: false,
instream_id: None,
characteristics: None,
channels: None,
}
}
/// Returns the type of the media associated with this tag.
pub fn media_type(&self) -> MediaType {
self.media_type
}
/// Returns the identifier that specifies the group to which the rendition belongs.
pub fn group_id(&self) -> &QuotedString {
&self.group_id
}
/// Returns a human-readable description of the rendition.
pub fn name(&self) -> &QuotedString {
&self.name
}
/// Returns the URI that identifies the media playlist.
pub fn uri(&self) -> Option<&QuotedString> {
self.uri.as_ref()
}
/// Returns the name of the primary language used in the rendition.
pub fn language(&self) -> Option<&QuotedString> {
self.language.as_ref()
}
/// Returns the name of a language associated with the rendition.
pub fn assoc_language(&self) -> Option<&QuotedString> {
self.assoc_language.as_ref()
}
/// Returns whether this is the default rendition.
pub fn default(&self) -> bool {
self.default
}
/// Returns whether the client may choose to
/// play this rendition in the absence of explicit user preference.
pub fn autoselect(&self) -> bool {
self.autoselect
}
/// Returns whether the rendition contains content that is considered essential to play.
pub fn forced(&self) -> bool {
self.forced
}
/// Returns the identifier that specifies a rendition within the segments in the media playlist.
pub fn instream_id(&self) -> Option<InStreamId> {
self.instream_id
}
/// Returns a string that represents uniform type identifiers (UTI).
///
/// Each UTI indicates an individual characteristic of the rendition.
pub fn characteristics(&self) -> Option<&QuotedString> {
self.characteristics.as_ref()
}
/// Returns a string that represents the parameters of the rendition.
pub fn channels(&self) -> Option<&QuotedString> {
self.channels.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
match self.instream_id {
None
| Some(InStreamId::Cc1)
| Some(InStreamId::Cc2)
| Some(InStreamId::Cc3)
| Some(InStreamId::Cc4) => ProtocolVersion::V1,
_ => ProtocolVersion::V7,
}
}
}
impl fmt::Display for ExtXMedia {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "TYPE={}", self.media_type)?;
if let Some(ref x) = self.uri {
write!(f, ",URI={}", x)?;
}
write!(f, ",GROUP-ID={}", self.group_id)?;
if let Some(ref x) = self.language {
write!(f, ",LANGUAGE={}", x)?;
}
if let Some(ref x) = self.assoc_language {
write!(f, ",ASSOC-LANGUAGE={}", x)?;
}
write!(f, ",NAME={}", self.name)?;
if self.default {
write!(f, ",DEFAULT=YES")?;
}
if self.autoselect {
write!(f, ",AUTOSELECT=YES")?;
}
if self.forced {
write!(f, ",FORCED=YES")?;
}
if let Some(ref x) = self.instream_id {
write!(f, ",INSTREAM-ID=\"{}\"", x)?;
}
if let Some(ref x) = self.characteristics {
write!(f, ",CHARACTERISTICS={}", x)?;
}
if let Some(ref x) = self.channels {
write!(f, ",CHANNELS={}", x)?;
}
Ok(())
}
}
impl FromStr for ExtXMedia {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut builder = ExtXMediaBuilder::new();
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"TYPE" => {
builder.media_type(track!(value.parse())?);
}
"URI" => {
builder.uri(track!(value.parse())?);
}
"GROUP-ID" => {
builder.group_id(track!(value.parse())?);
}
"LANGUAGE" => {
builder.language(track!(value.parse())?);
}
"ASSOC-LANGUAGE" => {
builder.assoc_language(track!(value.parse())?);
}
"NAME" => {
builder.name(track!(value.parse())?);
}
"DEFAULT" => {
builder.default(track!(parse_yes_or_no(value))?);
}
"AUTOSELECT" => {
builder.autoselect(track!(parse_yes_or_no(value))?);
}
"FORCED" => {
builder.forced(track!(parse_yes_or_no(value))?);
}
"INSTREAM-ID" => {
let s: QuotedString = track!(value.parse())?;
builder.instream_id(track!(s.parse())?);
}
"CHARACTERISTICS" => {
builder.characteristics(track!(value.parse())?);
}
"CHANNELS" => {
builder.channels(track!(value.parse())?);
}
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
track!(builder.finish())
}
}
/// [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
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXStreamInf {
uri: SingleLineString,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
resolution: Option<DecimalResolution>,
frame_rate: Option<DecimalFloatingPoint>,
hdcp_level: Option<HdcpLevel>,
audio: Option<QuotedString>,
video: Option<QuotedString>,
subtitles: Option<QuotedString>,
closed_captions: Option<ClosedCaptions>,
}
impl ExtXStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
/// Makes a new `ExtXStreamInf` tag.
pub fn new(uri: SingleLineString, bandwidth: u64) -> Self {
ExtXStreamInf {
uri,
bandwidth,
average_bandwidth: None,
codecs: None,
resolution: None,
frame_rate: None,
hdcp_level: None,
audio: None,
video: None,
subtitles: None,
closed_captions: None,
}
}
/// Returns the URI that identifies the associated media playlist.
pub fn uri(&self) -> &SingleLineString {
&self.uri
}
/// Returns the peak segment bit rate of the variant stream.
pub fn bandwidth(&self) -> u64 {
self.bandwidth
}
/// Returns the average segment bit rate of the variant stream.
pub fn average_bandwidth(&self) -> Option<u64> {
self.average_bandwidth
}
/// Returns a string that represents the list of codec types contained the variant stream.
pub fn codecs(&self) -> Option<&QuotedString> {
self.codecs.as_ref()
}
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
pub fn resolution(&self) -> Option<DecimalResolution> {
self.resolution
}
/// Returns the maximum frame rate for all the video in the variant stream.
pub fn frame_rate(&self) -> Option<DecimalFloatingPoint> {
self.frame_rate
}
/// Returns the HDCP level of the variant stream.
pub fn hdcp_level(&self) -> Option<HdcpLevel> {
self.hdcp_level
}
/// Returns the group identifier for the audio in the variant stream.
pub fn audio(&self) -> Option<&QuotedString> {
self.audio.as_ref()
}
/// Returns the group identifier for the video in the variant stream.
pub fn video(&self) -> Option<&QuotedString> {
self.video.as_ref()
}
/// Returns the group identifier for the subtitles in the variant stream.
pub fn subtitles(&self) -> Option<&QuotedString> {
self.subtitles.as_ref()
}
/// Returns the value of `CLOSED-CAPTIONS` attribute.
pub fn closed_captions(&self) -> Option<&ClosedCaptions> {
self.closed_captions.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXStreamInf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "BANDWIDTH={}", self.bandwidth)?;
if let Some(ref x) = self.average_bandwidth {
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
}
if let Some(ref x) = self.codecs {
write!(f, ",CODECS={}", x)?;
}
if let Some(ref x) = self.resolution {
write!(f, ",RESOLUTION={}", x)?;
}
if let Some(ref x) = self.frame_rate {
write!(f, ",FRAME-RATE={:.3}", x.as_f64())?;
}
if let Some(ref x) = self.hdcp_level {
write!(f, ",HDCP-LEVEL={}", x)?;
}
if let Some(ref x) = self.audio {
write!(f, ",AUDIO={}", x)?;
}
if let Some(ref x) = self.video {
write!(f, ",VIDEO={}", x)?;
}
if let Some(ref x) = self.subtitles {
write!(f, ",SUBTITLES={}", x)?;
}
if let Some(ref x) = self.closed_captions {
write!(f, ",CLOSED-CAPTIONS={}", x)?;
}
write!(f, "\n{}", self.uri)?;
Ok(())
}
}
impl FromStr for ExtXStreamInf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let mut lines = s.splitn(2, '\n');
let first_line = lines.next().expect("Never fails").trim_end_matches('\r');
let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput);
track_assert!(
first_line.starts_with(Self::PREFIX),
ErrorKind::InvalidInput
);
let uri = track!(SingleLineString::new(second_line))?;
let mut bandwidth = None;
let mut average_bandwidth = None;
let mut codecs = None;
let mut resolution = None;
let mut frame_rate = None;
let mut hdcp_level = None;
let mut audio = None;
let mut video = None;
let mut subtitles = None;
let mut closed_captions = None;
let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"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())?),
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
"AUDIO" => audio = Some(track!(value.parse())?),
"VIDEO" => video = Some(track!(value.parse())?),
"SUBTITLES" => subtitles = Some(track!(value.parse())?),
"CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
Ok(ExtXStreamInf {
uri,
bandwidth,
average_bandwidth,
codecs,
resolution,
frame_rate,
hdcp_level,
audio,
video,
subtitles,
closed_captions,
})
}
}
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
///
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXIFrameStreamInf {
uri: QuotedString,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
resolution: Option<DecimalResolution>,
hdcp_level: Option<HdcpLevel>,
video: Option<QuotedString>,
}
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: u64) -> Self {
ExtXIFrameStreamInf {
uri,
bandwidth,
average_bandwidth: None,
codecs: None,
resolution: None,
hdcp_level: None,
video: None,
}
}
/// Returns the URI that identifies the associated media playlist.
pub fn uri(&self) -> &QuotedString {
&self.uri
}
/// Returns the peak segment bit rate of the variant stream.
pub fn bandwidth(&self) -> u64 {
self.bandwidth
}
/// Returns the average segment bit rate of the variant stream.
pub fn average_bandwidth(&self) -> Option<u64> {
self.average_bandwidth
}
/// Returns a string that represents the list of codec types contained the variant stream.
pub fn codecs(&self) -> Option<&QuotedString> {
self.codecs.as_ref()
}
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
pub fn resolution(&self) -> Option<DecimalResolution> {
self.resolution
}
/// Returns the HDCP level of the variant stream.
pub fn hdcp_level(&self) -> Option<HdcpLevel> {
self.hdcp_level
}
/// Returns the group identifier for the video in the variant stream.
pub fn video(&self) -> Option<&QuotedString> {
self.video.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXIFrameStreamInf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "URI={}", self.uri)?;
write!(f, ",BANDWIDTH={}", self.bandwidth)?;
if let Some(ref x) = self.average_bandwidth {
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
}
if let Some(ref x) = self.codecs {
write!(f, ",CODECS={}", x)?;
}
if let Some(ref x) = self.resolution {
write!(f, ",RESOLUTION={}", x)?;
}
if let Some(ref x) = self.hdcp_level {
write!(f, ",HDCP-LEVEL={}", x)?;
}
if let Some(ref x) = self.video {
write!(f, ",VIDEO={}", x)?;
}
Ok(())
}
}
impl FromStr for ExtXIFrameStreamInf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut uri = None;
let mut bandwidth = None;
let mut average_bandwidth = None;
let mut codecs = None;
let mut resolution = None;
let mut hdcp_level = None;
let mut video = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"URI" => uri = 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())?),
"VIDEO" => video = Some(track!(value.parse())?),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
Ok(ExtXIFrameStreamInf {
uri,
bandwidth,
average_bandwidth,
codecs,
resolution,
hdcp_level,
video,
})
}
}
/// [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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionData {
data_id: QuotedString,
data: SessionData,
language: Option<QuotedString>,
}
impl ExtXSessionData {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
/// Makes a new `ExtXSessionData` tag.
pub fn new(data_id: QuotedString, data: SessionData) -> Self {
ExtXSessionData {
data_id,
data,
language: None,
}
}
/// Makes a new `ExtXSessionData` with the given language.
pub fn with_language(data_id: QuotedString, data: SessionData, language: QuotedString) -> Self {
ExtXSessionData {
data_id,
data,
language: Some(language),
}
}
/// Returns the identifier of the data.
pub fn data_id(&self) -> &QuotedString {
&self.data_id
}
/// Returns the session data.
pub fn data(&self) -> &SessionData {
&self.data
}
/// Returns the language of the data.
pub fn language(&self) -> Option<&QuotedString> {
self.language.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXSessionData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "DATA-ID={}", self.data_id)?;
match self.data {
SessionData::Value(ref x) => write!(f, ",VALUE={}", x)?,
SessionData::Uri(ref x) => write!(f, ",URI={}", x)?,
}
if let Some(ref x) = self.language {
write!(f, ",LANGUAGE={}", x)?;
}
Ok(())
}
}
impl FromStr for ExtXSessionData {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut data_id = None;
let mut session_value = None;
let mut uri = None;
let mut language = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"DATA-ID" => data_id = Some(track!(value.parse())?),
"VALUE" => session_value = Some(track!(value.parse())?),
"URI" => uri = Some(track!(value.parse())?),
"LANGUAGE" => language = Some(track!(value.parse())?),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let data_id = track_assert_some!(data_id, ErrorKind::InvalidInput);
let data = if let Some(value) = session_value {
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
SessionData::Value(value)
} else if let Some(uri) = uri {
SessionData::Uri(uri)
} else {
track_panic!(ErrorKind::InvalidInput);
};
Ok(ExtXSessionData {
data_id,
data,
language,
})
}
}
/// [4.3.4.5. EXT-X-SESSION-KEY]
///
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionKey {
key: DecryptionKey,
}
impl ExtXSessionKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
/// Makes a new `ExtXSessionKey` tag.
pub fn new(key: DecryptionKey) -> Self {
ExtXSessionKey { key }
}
/// Returns a decryption key for the playlist.
pub fn key(&self) -> &DecryptionKey {
&self.key
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
self.key.requires_version()
}
}
impl fmt::Display for ExtXSessionKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.key)
}
}
impl FromStr for ExtXSessionKey {
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 key = track!(suffix.parse())?;
Ok(ExtXSessionKey { key })
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::{EncryptionMethod, InitializationVector};
#[test]
fn ext_x_media() {
let tag = ExtXMedia::new(MediaType::Audio, quoted_string("foo"), quoted_string("bar"));
let text = r#"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="foo",NAME="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_stream_inf() {
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);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_i_frame_stream_inf() {
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);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_session_data() {
let tag = ExtXSessionData::new(
quoted_string("foo"),
SessionData::Value(quoted_string("bar")),
);
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag =
ExtXSessionData::new(quoted_string("foo"), SessionData::Uri(quoted_string("bar")));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXSessionData::with_language(
quoted_string("foo"),
SessionData::Value(quoted_string("bar")),
quoted_string("baz"),
);
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_session_key() {
let tag = ExtXSessionKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: quoted_string("foo"),
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=0x000102030405060708090a0b0c0d0e0f"#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
}
fn quoted_string(s: &str) -> QuotedString {
QuotedString::new(s).unwrap()
}
}

View file

@ -0,0 +1,165 @@
use crate::utils::parse_u64;
use crate::attribute::AttributePairs;
use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion, QuotedString};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
///
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXIFrameStreamInf {
uri: QuotedString,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
resolution: Option<DecimalResolution>,
hdcp_level: Option<HdcpLevel>,
video: Option<QuotedString>,
}
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: u64) -> Self {
ExtXIFrameStreamInf {
uri,
bandwidth,
average_bandwidth: None,
codecs: None,
resolution: None,
hdcp_level: None,
video: None,
}
}
/// Returns the URI that identifies the associated media playlist.
pub fn uri(&self) -> &QuotedString {
&self.uri
}
/// Returns the peak segment bit rate of the variant stream.
pub fn bandwidth(&self) -> u64 {
self.bandwidth
}
/// Returns the average segment bit rate of the variant stream.
pub fn average_bandwidth(&self) -> Option<u64> {
self.average_bandwidth
}
/// Returns a string that represents the list of codec types contained the variant stream.
pub fn codecs(&self) -> Option<&QuotedString> {
self.codecs.as_ref()
}
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
pub fn resolution(&self) -> Option<DecimalResolution> {
self.resolution
}
/// Returns the HDCP level of the variant stream.
pub fn hdcp_level(&self) -> Option<HdcpLevel> {
self.hdcp_level
}
/// Returns the group identifier for the video in the variant stream.
pub fn video(&self) -> Option<&QuotedString> {
self.video.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXIFrameStreamInf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "URI={}", self.uri)?;
write!(f, ",BANDWIDTH={}", self.bandwidth)?;
if let Some(ref x) = self.average_bandwidth {
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
}
if let Some(ref x) = self.codecs {
write!(f, ",CODECS={}", x)?;
}
if let Some(ref x) = self.resolution {
write!(f, ",RESOLUTION={}", x)?;
}
if let Some(ref x) = self.hdcp_level {
write!(f, ",HDCP-LEVEL={}", x)?;
}
if let Some(ref x) = self.video {
write!(f, ",VIDEO={}", x)?;
}
Ok(())
}
}
impl FromStr for ExtXIFrameStreamInf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut uri = None;
let mut bandwidth = None;
let mut average_bandwidth = None;
let mut codecs = None;
let mut resolution = None;
let mut hdcp_level = None;
let mut video = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"URI" => uri = 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())?),
"VIDEO" => video = Some(track!(value.parse())?),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
Ok(ExtXIFrameStreamInf {
uri,
bandwidth,
average_bandwidth,
codecs,
resolution,
hdcp_level,
video,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_i_frame_stream_inf() {
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);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
fn quoted_string(s: &str) -> QuotedString {
QuotedString::new(s).unwrap()
}
}

View file

@ -0,0 +1,382 @@
use crate::utils::parse_yes_or_no;
use crate::attribute::AttributePairs;
use crate::types::{ InStreamId, MediaType, ProtocolVersion, QuotedString};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// `ExtXMedia` builder.
#[derive(Debug, Clone)]
pub struct ExtXMediaBuilder {
media_type: Option<MediaType>,
uri: Option<QuotedString>,
group_id: Option<QuotedString>,
language: Option<QuotedString>,
assoc_language: Option<QuotedString>,
name: Option<QuotedString>,
default: bool,
autoselect: Option<bool>,
forced: Option<bool>,
instream_id: Option<InStreamId>,
characteristics: Option<QuotedString>,
channels: Option<QuotedString>,
}
impl ExtXMediaBuilder {
/// Makes a `ExtXMediaBuilder` instance.
pub fn new() -> Self {
ExtXMediaBuilder {
media_type: None,
uri: None,
group_id: None,
language: None,
assoc_language: None,
name: None,
default: false,
autoselect: None,
forced: None,
instream_id: None,
characteristics: None,
channels: None,
}
}
/// Sets the media type of the rendition.
pub fn media_type(&mut self, media_type: MediaType) -> &mut Self {
self.media_type = Some(media_type);
self
}
/// Sets the identifier that specifies the group to which the rendition belongs.
pub fn group_id(&mut self, group_id: QuotedString) -> &mut Self {
self.group_id = Some(group_id);
self
}
/// Sets a human-readable description of the rendition.
pub fn name(&mut self, name: QuotedString) -> &mut Self {
self.name = Some(name);
self
}
/// Sets the URI that identifies the media playlist.
pub fn uri(&mut self, uri: QuotedString) -> &mut Self {
self.uri = Some(uri);
self
}
/// Sets the name of the primary language used in the rendition.
pub fn language(&mut self, language: QuotedString) -> &mut Self {
self.language = Some(language);
self
}
/// Sets the name of a language associated with the rendition.
pub fn assoc_language(&mut self, language: QuotedString) -> &mut Self {
self.assoc_language = Some(language);
self
}
/// Sets the value of the `default` flag.
pub fn default(&mut self, b: bool) -> &mut Self {
self.default = b;
self
}
/// Sets the value of the `autoselect` flag.
pub fn autoselect(&mut self, b: bool) -> &mut Self {
self.autoselect = Some(b);
self
}
/// Sets the value of the `forced` flag.
pub fn forced(&mut self, b: bool) -> &mut Self {
self.forced = Some(b);
self
}
/// Sets the identifier that specifies a rendition within the segments in the media playlist.
pub fn instream_id(&mut self, id: InStreamId) -> &mut Self {
self.instream_id = Some(id);
self
}
/// Sets the string that represents uniform type identifiers (UTI).
pub fn characteristics(&mut self, characteristics: QuotedString) -> &mut Self {
self.characteristics = Some(characteristics);
self
}
/// Sets the string that represents the parameters of the rendition.
pub fn channels(&mut self, channels: QuotedString) -> &mut Self {
self.channels = Some(channels);
self
}
/// Builds a `ExtXMedia` instance.
pub fn finish(self) -> Result<ExtXMedia> {
let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput);
let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput);
let name = track_assert_some!(self.name, ErrorKind::InvalidInput);
if MediaType::ClosedCaptions == media_type {
track_assert_ne!(self.uri, None, ErrorKind::InvalidInput);
track_assert!(self.instream_id.is_some(), ErrorKind::InvalidInput);
} else {
track_assert!(self.instream_id.is_none(), ErrorKind::InvalidInput);
}
if self.default && self.autoselect.is_some() {
track_assert_eq!(self.autoselect, Some(true), ErrorKind::InvalidInput);
}
if MediaType::Subtitles != media_type {
track_assert_eq!(self.forced, None, ErrorKind::InvalidInput);
}
Ok(ExtXMedia {
media_type,
uri: self.uri,
group_id,
language: self.language,
assoc_language: self.assoc_language,
name,
default: self.default,
autoselect: self.autoselect.unwrap_or(false),
forced: self.forced.unwrap_or(false),
instream_id: self.instream_id,
characteristics: self.characteristics,
channels: self.channels,
})
}
}
impl Default for ExtXMediaBuilder {
fn default() -> Self {
Self::new()
}
}
/// [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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXMedia {
media_type: MediaType,
uri: Option<QuotedString>,
group_id: QuotedString,
language: Option<QuotedString>,
assoc_language: Option<QuotedString>,
name: QuotedString,
default: bool,
autoselect: bool,
forced: bool,
instream_id: Option<InStreamId>,
characteristics: Option<QuotedString>,
channels: Option<QuotedString>,
}
impl ExtXMedia {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
/// Makes a new `ExtXMedia` tag.
pub fn new(media_type: MediaType, group_id: QuotedString, name: QuotedString) -> Self {
ExtXMedia {
media_type,
uri: None,
group_id,
language: None,
assoc_language: None,
name,
default: false,
autoselect: false,
forced: false,
instream_id: None,
characteristics: None,
channels: None,
}
}
/// Returns the type of the media associated with this tag.
pub fn media_type(&self) -> MediaType {
self.media_type
}
/// Returns the identifier that specifies the group to which the rendition belongs.
pub fn group_id(&self) -> &QuotedString {
&self.group_id
}
/// Returns a human-readable description of the rendition.
pub fn name(&self) -> &QuotedString {
&self.name
}
/// Returns the URI that identifies the media playlist.
pub fn uri(&self) -> Option<&QuotedString> {
self.uri.as_ref()
}
/// Returns the name of the primary language used in the rendition.
pub fn language(&self) -> Option<&QuotedString> {
self.language.as_ref()
}
/// Returns the name of a language associated with the rendition.
pub fn assoc_language(&self) -> Option<&QuotedString> {
self.assoc_language.as_ref()
}
/// Returns whether this is the default rendition.
pub fn default(&self) -> bool {
self.default
}
/// Returns whether the client may choose to
/// play this rendition in the absence of explicit user preference.
pub fn autoselect(&self) -> bool {
self.autoselect
}
/// Returns whether the rendition contains content that is considered essential to play.
pub fn forced(&self) -> bool {
self.forced
}
/// Returns the identifier that specifies a rendition within the segments in the media playlist.
pub fn instream_id(&self) -> Option<InStreamId> {
self.instream_id
}
/// Returns a string that represents uniform type identifiers (UTI).
///
/// Each UTI indicates an individual characteristic of the rendition.
pub fn characteristics(&self) -> Option<&QuotedString> {
self.characteristics.as_ref()
}
/// Returns a string that represents the parameters of the rendition.
pub fn channels(&self) -> Option<&QuotedString> {
self.channels.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
match self.instream_id {
None
| Some(InStreamId::Cc1)
| Some(InStreamId::Cc2)
| Some(InStreamId::Cc3)
| Some(InStreamId::Cc4) => ProtocolVersion::V1,
_ => ProtocolVersion::V7,
}
}
}
impl fmt::Display for ExtXMedia {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "TYPE={}", self.media_type)?;
if let Some(ref x) = self.uri {
write!(f, ",URI={}", x)?;
}
write!(f, ",GROUP-ID={}", self.group_id)?;
if let Some(ref x) = self.language {
write!(f, ",LANGUAGE={}", x)?;
}
if let Some(ref x) = self.assoc_language {
write!(f, ",ASSOC-LANGUAGE={}", x)?;
}
write!(f, ",NAME={}", self.name)?;
if self.default {
write!(f, ",DEFAULT=YES")?;
}
if self.autoselect {
write!(f, ",AUTOSELECT=YES")?;
}
if self.forced {
write!(f, ",FORCED=YES")?;
}
if let Some(ref x) = self.instream_id {
write!(f, ",INSTREAM-ID=\"{}\"", x)?;
}
if let Some(ref x) = self.characteristics {
write!(f, ",CHARACTERISTICS={}", x)?;
}
if let Some(ref x) = self.channels {
write!(f, ",CHANNELS={}", x)?;
}
Ok(())
}
}
impl FromStr for ExtXMedia {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut builder = ExtXMediaBuilder::new();
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"TYPE" => {
builder.media_type(track!(value.parse())?);
}
"URI" => {
builder.uri(track!(value.parse())?);
}
"GROUP-ID" => {
builder.group_id(track!(value.parse())?);
}
"LANGUAGE" => {
builder.language(track!(value.parse())?);
}
"ASSOC-LANGUAGE" => {
builder.assoc_language(track!(value.parse())?);
}
"NAME" => {
builder.name(track!(value.parse())?);
}
"DEFAULT" => {
builder.default(track!(parse_yes_or_no(value))?);
}
"AUTOSELECT" => {
builder.autoselect(track!(parse_yes_or_no(value))?);
}
"FORCED" => {
builder.forced(track!(parse_yes_or_no(value))?);
}
"INSTREAM-ID" => {
let s: QuotedString = track!(value.parse())?;
builder.instream_id(track!(s.parse())?);
}
"CHARACTERISTICS" => {
builder.characteristics(track!(value.parse())?);
}
"CHANNELS" => {
builder.channels(track!(value.parse())?);
}
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
track!(builder.finish())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_media() {
let tag = ExtXMedia::new(MediaType::Audio, quoted_string("foo"), quoted_string("bar"));
let text = r#"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="foo",NAME="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
fn quoted_string(s: &str) -> QuotedString {
QuotedString::new(s).unwrap()
}
}

View file

@ -0,0 +1,11 @@
mod i_frame_stream_inf;
mod media;
mod session_data;
mod session_key;
mod stream_inf;
pub use i_frame_stream_inf::*;
pub use media::*;
pub use session_data::*;
pub use session_key::*;
pub use stream_inf::*;

View file

@ -0,0 +1,152 @@
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, QuotedString, SessionData};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionData {
data_id: QuotedString,
data: SessionData,
language: Option<QuotedString>,
}
impl ExtXSessionData {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
/// Makes a new `ExtXSessionData` tag.
pub fn new(data_id: QuotedString, data: SessionData) -> Self {
ExtXSessionData {
data_id,
data,
language: None,
}
}
/// Makes a new `ExtXSessionData` with the given language.
pub fn with_language(data_id: QuotedString, data: SessionData, language: QuotedString) -> Self {
ExtXSessionData {
data_id,
data,
language: Some(language),
}
}
/// Returns the identifier of the data.
pub fn data_id(&self) -> &QuotedString {
&self.data_id
}
/// Returns the session data.
pub fn data(&self) -> &SessionData {
&self.data
}
/// Returns the language of the data.
pub fn language(&self) -> Option<&QuotedString> {
self.language.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXSessionData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "DATA-ID={}", self.data_id)?;
match self.data {
SessionData::Value(ref x) => write!(f, ",VALUE={}", x)?,
SessionData::Uri(ref x) => write!(f, ",URI={}", x)?,
}
if let Some(ref x) = self.language {
write!(f, ",LANGUAGE={}", x)?;
}
Ok(())
}
}
impl FromStr for ExtXSessionData {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut data_id = None;
let mut session_value = None;
let mut uri = None;
let mut language = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"DATA-ID" => data_id = Some(track!(value.parse())?),
"VALUE" => session_value = Some(track!(value.parse())?),
"URI" => uri = Some(track!(value.parse())?),
"LANGUAGE" => language = Some(track!(value.parse())?),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let data_id = track_assert_some!(data_id, ErrorKind::InvalidInput);
let data = if let Some(value) = session_value {
track_assert_eq!(uri, None, ErrorKind::InvalidInput);
SessionData::Value(value)
} else if let Some(uri) = uri {
SessionData::Uri(uri)
} else {
track_panic!(ErrorKind::InvalidInput);
};
Ok(ExtXSessionData {
data_id,
data,
language,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_session_data() {
let tag = ExtXSessionData::new(
quoted_string("foo"),
SessionData::Value(quoted_string("bar")),
);
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag =
ExtXSessionData::new(quoted_string("foo"), SessionData::Uri(quoted_string("bar")));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXSessionData::with_language(
quoted_string("foo"),
SessionData::Value(quoted_string("bar")),
quoted_string("baz"),
);
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
fn quoted_string(s: &str) -> QuotedString {
QuotedString::new(s).unwrap()
}
}

View file

@ -0,0 +1,76 @@
use crate::types::{DecryptionKey, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.4.5. EXT-X-SESSION-KEY]
///
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionKey {
key: DecryptionKey,
}
impl ExtXSessionKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
/// Makes a new `ExtXSessionKey` tag.
pub fn new(key: DecryptionKey) -> Self {
ExtXSessionKey { key }
}
/// Returns a decryption key for the playlist.
pub fn key(&self) -> &DecryptionKey {
&self.key
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
self.key.requires_version()
}
}
impl fmt::Display for ExtXSessionKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.key)
}
}
impl FromStr for ExtXSessionKey {
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 key = track!(suffix.parse())?;
Ok(ExtXSessionKey { key })
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::{EncryptionMethod, InitializationVector, QuotedString};
#[test]
fn ext_x_session_key() {
let tag = ExtXSessionKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: quoted_string("foo"),
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=0x000102030405060708090a0b0c0d0e0f"#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
}
fn quoted_string(s: &str) -> QuotedString {
QuotedString::new(s).unwrap()
}
}

View file

@ -0,0 +1,218 @@
use crate::utils::parse_u64;
use crate::attribute::AttributePairs;
use crate::types::{
ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel,
ProtocolVersion, QuotedString, SingleLineString,
};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [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
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtXStreamInf {
uri: SingleLineString,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
resolution: Option<DecimalResolution>,
frame_rate: Option<DecimalFloatingPoint>,
hdcp_level: Option<HdcpLevel>,
audio: Option<QuotedString>,
video: Option<QuotedString>,
subtitles: Option<QuotedString>,
closed_captions: Option<ClosedCaptions>,
}
impl ExtXStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
/// Makes a new `ExtXStreamInf` tag.
pub fn new(uri: SingleLineString, bandwidth: u64) -> Self {
ExtXStreamInf {
uri,
bandwidth,
average_bandwidth: None,
codecs: None,
resolution: None,
frame_rate: None,
hdcp_level: None,
audio: None,
video: None,
subtitles: None,
closed_captions: None,
}
}
/// Returns the URI that identifies the associated media playlist.
pub fn uri(&self) -> &SingleLineString {
&self.uri
}
/// Returns the peak segment bit rate of the variant stream.
pub fn bandwidth(&self) -> u64 {
self.bandwidth
}
/// Returns the average segment bit rate of the variant stream.
pub fn average_bandwidth(&self) -> Option<u64> {
self.average_bandwidth
}
/// Returns a string that represents the list of codec types contained the variant stream.
pub fn codecs(&self) -> Option<&QuotedString> {
self.codecs.as_ref()
}
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
pub fn resolution(&self) -> Option<DecimalResolution> {
self.resolution
}
/// Returns the maximum frame rate for all the video in the variant stream.
pub fn frame_rate(&self) -> Option<DecimalFloatingPoint> {
self.frame_rate
}
/// Returns the HDCP level of the variant stream.
pub fn hdcp_level(&self) -> Option<HdcpLevel> {
self.hdcp_level
}
/// Returns the group identifier for the audio in the variant stream.
pub fn audio(&self) -> Option<&QuotedString> {
self.audio.as_ref()
}
/// Returns the group identifier for the video in the variant stream.
pub fn video(&self) -> Option<&QuotedString> {
self.video.as_ref()
}
/// Returns the group identifier for the subtitles in the variant stream.
pub fn subtitles(&self) -> Option<&QuotedString> {
self.subtitles.as_ref()
}
/// Returns the value of `CLOSED-CAPTIONS` attribute.
pub fn closed_captions(&self) -> Option<&ClosedCaptions> {
self.closed_captions.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXStreamInf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "BANDWIDTH={}", self.bandwidth)?;
if let Some(ref x) = self.average_bandwidth {
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
}
if let Some(ref x) = self.codecs {
write!(f, ",CODECS={}", x)?;
}
if let Some(ref x) = self.resolution {
write!(f, ",RESOLUTION={}", x)?;
}
if let Some(ref x) = self.frame_rate {
write!(f, ",FRAME-RATE={:.3}", x.as_f64())?;
}
if let Some(ref x) = self.hdcp_level {
write!(f, ",HDCP-LEVEL={}", x)?;
}
if let Some(ref x) = self.audio {
write!(f, ",AUDIO={}", x)?;
}
if let Some(ref x) = self.video {
write!(f, ",VIDEO={}", x)?;
}
if let Some(ref x) = self.subtitles {
write!(f, ",SUBTITLES={}", x)?;
}
if let Some(ref x) = self.closed_captions {
write!(f, ",CLOSED-CAPTIONS={}", x)?;
}
write!(f, "\n{}", self.uri)?;
Ok(())
}
}
impl FromStr for ExtXStreamInf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let mut lines = s.splitn(2, '\n');
let first_line = lines.next().expect("Never fails").trim_end_matches('\r');
let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput);
track_assert!(
first_line.starts_with(Self::PREFIX),
ErrorKind::InvalidInput
);
let uri = track!(SingleLineString::new(second_line))?;
let mut bandwidth = None;
let mut average_bandwidth = None;
let mut codecs = None;
let mut resolution = None;
let mut frame_rate = None;
let mut hdcp_level = None;
let mut audio = None;
let mut video = None;
let mut subtitles = None;
let mut closed_captions = None;
let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"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())?),
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
"AUDIO" => audio = Some(track!(value.parse())?),
"VIDEO" => video = Some(track!(value.parse())?),
"SUBTITLES" => subtitles = Some(track!(value.parse())?),
"CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
Ok(ExtXStreamInf {
uri,
bandwidth,
average_bandwidth,
codecs,
resolution,
frame_rate,
hdcp_level,
audio,
video,
subtitles,
closed_captions,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_stream_inf() {
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);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -1,279 +0,0 @@
use crate::types::{PlaylistType, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
use trackable::error::ErrorKindExt;
/// [4.3.3.1. EXT-X-TARGETDURATION]
///
/// [4.3.3.1. EXT-X-TARGETDURATION]: https://tools.ietf.org/html/rfc8216#section-4.3.3.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXTargetDuration {
duration: Duration,
}
impl ExtXTargetDuration {
pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:";
/// Makes a new `ExtXTargetduration` tag.
///
/// Note that the nanoseconds part of the `duration` will be discarded.
pub fn new(duration: Duration) -> Self {
let duration = Duration::from_secs(duration.as_secs());
ExtXTargetDuration { duration }
}
/// Returns the maximum media segment duration in the associated playlist.
pub fn duration(&self) -> Duration {
self.duration
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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),
})
}
}
/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE]
///
/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.2
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXMediaSequence {
seq_num: u64,
}
impl ExtXMediaSequence {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:";
/// Makes a new `ExtXMediaSequence` tag.
pub fn new(seq_num: u64) -> Self {
ExtXMediaSequence { seq_num }
}
/// Returns the sequence number of the first media segment that appears in the associated playlist.
pub fn seq_num(self) -> u64 {
self.seq_num
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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 })
}
}
/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
///
/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXDiscontinuitySequence {
seq_num: u64,
}
impl ExtXDiscontinuitySequence {
pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY-SEQUENCE:";
/// Makes a new `ExtXDiscontinuitySequence` tag.
pub fn new(seq_num: u64) -> Self {
ExtXDiscontinuitySequence { seq_num }
}
/// Returns the discontinuity sequence number of
/// the first media segment that appears in the associated playlist.
pub fn seq_num(self) -> u64 {
self.seq_num
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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 })
}
}
/// [4.3.3.4. EXT-X-ENDLIST]
///
/// [4.3.3.4. EXT-X-ENDLIST]: https://tools.ietf.org/html/rfc8216#section-4.3.3.4
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXEndList;
impl ExtXEndList {
pub(crate) const PREFIX: &'static str = "#EXT-X-ENDLIST";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
/// [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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXPlaylistType {
playlist_type: PlaylistType,
}
impl ExtXPlaylistType {
pub(crate) const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:";
/// Makes a new `ExtXPlaylistType` tag.
pub fn new(playlist_type: PlaylistType) -> Self {
ExtXPlaylistType { playlist_type }
}
/// Returns the type of the associated media playlist.
pub fn playlist_type(self) -> PlaylistType {
self.playlist_type
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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 })
}
}
/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]
///
/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]: https://tools.ietf.org/html/rfc8216#section-4.3.3.6
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXIFramesOnly;
impl ExtXIFramesOnly {
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAMES-ONLY";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V4
}
}
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)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_targetduration() {
let tag = ExtXTargetDuration::new(Duration::from_secs(5));
let text = "#EXT-X-TARGETDURATION:5";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_media_sequence() {
let tag = ExtXMediaSequence::new(123);
let text = "#EXT-X-MEDIA-SEQUENCE:123";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_discontinuity_sequence() {
let tag = ExtXDiscontinuitySequence::new(123);
let text = "#EXT-X-DISCONTINUITY-SEQUENCE:123";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_endlist() {
let tag = ExtXEndList;
let text = "#EXT-X-ENDLIST";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_playlist_type() {
let tag = ExtXPlaylistType::new(PlaylistType::Vod);
let text = "#EXT-X-PLAYLIST-TYPE:VOD";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_i_frames_only() {
let tag = ExtXIFramesOnly;
let text = "#EXT-X-I-FRAMES-ONLY";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
}
}

View file

@ -0,0 +1,62 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
///
/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXDiscontinuitySequence {
seq_num: u64,
}
impl ExtXDiscontinuitySequence {
pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY-SEQUENCE:";
/// Makes a new `ExtXDiscontinuitySequence` tag.
pub fn new(seq_num: u64) -> Self {
ExtXDiscontinuitySequence { seq_num }
}
/// Returns the discontinuity sequence number of
/// the first media segment that appears in the associated playlist.
pub fn seq_num(self) -> u64 {
self.seq_num
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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 })
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_discontinuity_sequence() {
let tag = ExtXDiscontinuitySequence::new(123);
let text = "#EXT-X-DISCONTINUITY-SEQUENCE:123";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -0,0 +1,44 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.3.4. EXT-X-ENDLIST]
///
/// [4.3.3.4. EXT-X-ENDLIST]: https://tools.ietf.org/html/rfc8216#section-4.3.3.4
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXEndList;
impl ExtXEndList {
pub(crate) const PREFIX: &'static str = "#EXT-X-ENDLIST";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_endlist() {
let tag = ExtXEndList;
let text = "#EXT-X-ENDLIST";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -0,0 +1,47 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]
///
/// [4.3.3.6. EXT-X-I-FRAMES-ONLY]: https://tools.ietf.org/html/rfc8216#section-4.3.3.6
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXIFramesOnly;
impl ExtXIFramesOnly {
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAMES-ONLY";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V4
}
}
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)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_i_frames_only() {
let tag = ExtXIFramesOnly;
let text = "#EXT-X-I-FRAMES-ONLY";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
}
}

View file

@ -0,0 +1,61 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE]
///
/// [4.3.3.2. EXT-X-MEDIA-SEQUENCE]: https://tools.ietf.org/html/rfc8216#section-4.3.3.2
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXMediaSequence {
seq_num: u64,
}
impl ExtXMediaSequence {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:";
/// Makes a new `ExtXMediaSequence` tag.
pub fn new(seq_num: u64) -> Self {
ExtXMediaSequence { seq_num }
}
/// Returns the sequence number of the first media segment that appears in the associated playlist.
pub fn seq_num(self) -> u64 {
self.seq_num
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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 })
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_media_sequence() {
let tag = ExtXMediaSequence::new(123);
let text = "#EXT-X-MEDIA-SEQUENCE:123";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -0,0 +1,13 @@
mod discontinuity_sequence;
mod end_list;
mod i_frames_only;
mod media_sequence;
mod playlist_type;
mod target_duration;
pub use discontinuity_sequence::*;
pub use end_list::*;
pub use i_frames_only::*;
pub use media_sequence::*;
pub use playlist_type::*;
pub use target_duration::*;

View file

@ -0,0 +1,61 @@
use crate::types::{PlaylistType, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
/// [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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXPlaylistType {
playlist_type: PlaylistType,
}
impl ExtXPlaylistType {
pub(crate) const PREFIX: &'static str = "#EXT-X-PLAYLIST-TYPE:";
/// Makes a new `ExtXPlaylistType` tag.
pub fn new(playlist_type: PlaylistType) -> Self {
ExtXPlaylistType { playlist_type }
}
/// Returns the type of the associated media playlist.
pub fn playlist_type(self) -> PlaylistType {
self.playlist_type
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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 })
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_playlist_type() {
let tag = ExtXPlaylistType::new(PlaylistType::Vod);
let text = "#EXT-X-PLAYLIST-TYPE:VOD";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -0,0 +1,67 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
use trackable::error::ErrorKindExt;
/// [4.3.3.1. EXT-X-TARGETDURATION]
///
/// [4.3.3.1. EXT-X-TARGETDURATION]: https://tools.ietf.org/html/rfc8216#section-4.3.3.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXTargetDuration {
duration: Duration,
}
impl ExtXTargetDuration {
pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:";
/// Makes a new `ExtXTargetduration` tag.
///
/// Note that the nanoseconds part of the `duration` will be discarded.
pub fn new(duration: Duration) -> Self {
let duration = Duration::from_secs(duration.as_secs());
ExtXTargetDuration { duration }
}
/// Returns the maximum media segment duration in the associated playlist.
pub fn duration(&self) -> Duration {
self.duration
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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),
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_targetduration() {
let tag = ExtXTargetDuration::new(Duration::from_secs(5));
let text = "#EXT-X-TARGETDURATION:5";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -1,613 +0,0 @@
use crate::attribute::AttributePairs;
use crate::types::{
ByteRange, DecimalFloatingPoint, DecryptionKey, ProtocolVersion, QuotedString, SingleLineString,
};
use crate::{Error, ErrorKind, Result};
use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
use trackable::error::ErrorKindExt;
/// [4.3.2.1. EXTINF]
///
/// [4.3.2.1. EXTINF]: https://tools.ietf.org/html/rfc8216#section-4.3.2.1
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtInf {
duration: Duration,
title: Option<SingleLineString>,
}
impl ExtInf {
pub(crate) const PREFIX: &'static str = "#EXTINF:";
/// Makes a new `ExtInf` tag.
pub fn new(duration: Duration) -> Self {
ExtInf {
duration,
title: None,
}
}
/// Makes a new `ExtInf` tag with the given title.
pub fn with_title(duration: Duration, title: SingleLineString) -> Self {
ExtInf {
duration,
title: Some(title),
}
}
/// Returns the duration of the associated media segment.
pub fn duration(&self) -> Duration {
self.duration
}
/// Returns the title of the associated media segment.
pub fn title(&self) -> Option<&SingleLineString> {
self.title.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
if self.duration.subsec_nanos() == 0 {
ProtocolVersion::V1
} else {
ProtocolVersion::V3
}
}
}
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)
+ (f64::from(self.duration.subsec_nanos()) / 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 seconds: DecimalFloatingPoint =
may_invalid!(tokens.next().expect("Never fails").parse())?;
let duration = seconds.to_duration();
let title = if let Some(title) = tokens.next() {
Some(track!(SingleLineString::new(title))?)
} else {
None
};
Ok(ExtInf { duration, title })
}
}
/// [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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXByteRange {
range: ByteRange,
}
impl ExtXByteRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
/// Makes a new `ExtXByteRange` tag.
pub fn new(range: ByteRange) -> Self {
ExtXByteRange { range }
}
/// Returns the range of the associated media segment.
pub fn range(&self) -> ByteRange {
self.range
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V4
}
}
impl fmt::Display for ExtXByteRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.range)
}
}
impl FromStr for ExtXByteRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let range = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXByteRange { range })
}
}
/// [4.3.2.3. EXT-X-DISCONTINUITY]
///
/// [4.3.2.3. EXT-X-DISCONTINUITY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXDiscontinuity;
impl ExtXDiscontinuity {
pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
/// [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, PartialEq, Eq, Hash)]
pub struct ExtXKey {
key: Option<DecryptionKey>,
}
impl ExtXKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
/// Makes a new `ExtXKey` tag.
pub fn new(key: DecryptionKey) -> Self {
ExtXKey { key: Some(key) }
}
/// Makes a new `ExtXKey` tag without a decryption key.
///
/// This tag has the `METHDO=NONE` attribute.
pub fn new_without_key() -> Self {
ExtXKey { key: None }
}
/// Returns the decryption key for the following media segments and media initialization sections.
pub fn key(&self) -> Option<&DecryptionKey> {
self.key.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
self.key
.as_ref()
.map_or(ProtocolVersion::V1, |k| k.requires_version())
}
}
impl fmt::Display for ExtXKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
if let Some(ref key) = self.key {
write!(f, "{}", key)?;
} else {
write!(f, "METHOD=NONE")?;
}
Ok(())
}
}
impl FromStr for ExtXKey {
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;
if AttributePairs::parse(suffix).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) {
for attr in AttributePairs::parse(suffix) {
let (key, _) = track!(attr)?;
track_assert_ne!(key, "URI", ErrorKind::InvalidInput);
track_assert_ne!(key, "IV", ErrorKind::InvalidInput);
track_assert_ne!(key, "KEYFORMAT", ErrorKind::InvalidInput);
track_assert_ne!(key, "KEYFORMATVERSIONS", ErrorKind::InvalidInput);
}
Ok(ExtXKey { key: None })
} else {
let key = track!(suffix.parse())?;
Ok(ExtXKey { key: Some(key) })
}
}
}
/// [4.3.2.5. EXT-X-MAP]
///
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXMap {
uri: QuotedString,
range: Option<ByteRange>,
}
impl ExtXMap {
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
/// Makes a new `ExtXMap` tag.
pub fn new(uri: QuotedString) -> Self {
ExtXMap { uri, range: None }
}
/// Makes a new `ExtXMap` tag with the given range.
pub fn with_range(uri: QuotedString, range: ByteRange) -> Self {
ExtXMap {
uri,
range: Some(range),
}
}
/// Returns the URI that identifies a resource that contains the media initialization section.
pub fn uri(&self) -> &QuotedString {
&self.uri
}
/// Returns the range of the media initialization section.
pub fn range(&self) -> Option<ByteRange> {
self.range
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V6
}
}
impl fmt::Display for ExtXMap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "URI={}", self.uri)?;
if let Some(ref x) = self.range {
write!(f, ",BYTERANGE=\"{}\"", x)?;
}
Ok(())
}
}
impl FromStr for ExtXMap {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut uri = None;
let mut range = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"URI" => uri = Some(track!(value.parse())?),
"BYTERANGE" => {
let s: QuotedString = track!(value.parse())?;
range = Some(track!(s.parse())?);
}
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
Ok(ExtXMap { uri, range })
}
}
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
///
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXProgramDateTime {
date_time: SingleLineString,
}
impl ExtXProgramDateTime {
pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:";
/// Makes a new `ExtXProgramDateTime` tag.
pub fn new(date_time: SingleLineString) -> Self {
ExtXProgramDateTime { date_time }
}
/// Returns the date-time of the first sample of the associated media segment.
pub fn date_time(&self) -> &SingleLineString {
&self.date_time
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXProgramDateTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.date_time)
}
}
impl FromStr for ExtXProgramDateTime {
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;
Ok(ExtXProgramDateTime {
date_time: track!(SingleLineString::new(suffix))?,
})
}
}
/// [4.3.2.7. EXT-X-DATERANGE]
///
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
///
/// TODO: Implement properly
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXDateRange {
pub id: QuotedString,
pub class: Option<QuotedString>,
pub start_date: QuotedString,
pub end_date: Option<QuotedString>,
pub duration: Option<Duration>,
pub planned_duration: Option<Duration>,
pub scte35_cmd: Option<QuotedString>,
pub scte35_out: Option<QuotedString>,
pub scte35_in: Option<QuotedString>,
pub end_on_next: bool,
pub client_attributes: BTreeMap<String, String>,
}
impl ExtXDateRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXDateRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "ID={}", self.id)?;
if let Some(ref x) = self.class {
write!(f, ",CLASS={}", x)?;
}
write!(f, ",START-DATE={}", self.start_date)?;
if let Some(ref x) = self.end_date {
write!(f, ",END-DATE={}", x)?;
}
if let Some(x) = self.duration {
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
}
if let Some(x) = self.planned_duration {
write!(
f,
",PLANNED-DURATION={}",
DecimalFloatingPoint::from_duration(x)
)?;
}
if let Some(ref x) = self.scte35_cmd {
write!(f, ",SCTE35-CMD={}", x)?;
}
if let Some(ref x) = self.scte35_out {
write!(f, ",SCTE35-OUT={}", x)?;
}
if let Some(ref x) = self.scte35_in {
write!(f, ",SCTE35-IN={}", x)?;
}
if self.end_on_next {
write!(f, ",END-ON-NEXT=YES",)?;
}
for (k, v) in &self.client_attributes {
write!(f, ",{}={}", k, v)?;
}
Ok(())
}
}
impl FromStr for ExtXDateRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut id = None;
let mut class = None;
let mut start_date = None;
let mut end_date = None;
let mut duration = None;
let mut planned_duration = None;
let mut scte35_cmd = None;
let mut scte35_out = None;
let mut scte35_in = None;
let mut end_on_next = false;
let mut client_attributes = BTreeMap::new();
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"ID" => id = Some(track!(value.parse())?),
"CLASS" => class = Some(track!(value.parse())?),
"START-DATE" => start_date = Some(track!(value.parse())?),
"END-DATE" => end_date = Some(track!(value.parse())?),
"DURATION" => {
let seconds: DecimalFloatingPoint = track!(value.parse())?;
duration = Some(seconds.to_duration());
}
"PLANNED-DURATION" => {
let seconds: DecimalFloatingPoint = track!(value.parse())?;
planned_duration = Some(seconds.to_duration());
}
"SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?),
"SCTE35-OUT" => scte35_out = Some(track!(value.parse())?),
"SCTE35-IN" => scte35_in = Some(track!(value.parse())?),
"END-ON-NEXT" => {
track_assert_eq!(value, "YES", ErrorKind::InvalidInput);
end_on_next = true;
}
_ => {
if key.starts_with("X-") {
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
} else {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
}
let id = track_assert_some!(id, ErrorKind::InvalidInput);
let start_date = track_assert_some!(start_date, ErrorKind::InvalidInput);
if end_on_next {
track_assert!(class.is_some(), ErrorKind::InvalidInput);
}
Ok(ExtXDateRange {
id,
class,
start_date,
end_date,
duration,
planned_duration,
scte35_cmd,
scte35_out,
scte35_in,
end_on_next,
client_attributes,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::{EncryptionMethod, InitializationVector};
use std::time::Duration;
#[test]
fn extinf() {
let tag = ExtInf::new(Duration::from_secs(5));
assert_eq!("#EXTINF:5".parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), "#EXTINF:5");
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtInf::with_title(
Duration::from_secs(5),
SingleLineString::new("foo").unwrap(),
);
assert_eq!("#EXTINF:5,foo".parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), "#EXTINF:5,foo");
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtInf::new(Duration::from_millis(1234));
assert_eq!("#EXTINF:1.234".parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), "#EXTINF:1.234");
assert_eq!(tag.requires_version(), ProtocolVersion::V3);
}
#[test]
fn ext_x_byterange() {
let tag = ExtXByteRange::new(ByteRange {
length: 3,
start: None,
});
assert_eq!("#EXT-X-BYTERANGE:3".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3");
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
let tag = ExtXByteRange::new(ByteRange {
length: 3,
start: Some(5),
});
assert_eq!("#EXT-X-BYTERANGE:3@5".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3@5");
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
}
#[test]
fn ext_x_discontinuity() {
let tag = ExtXDiscontinuity;
assert_eq!("#EXT-X-DISCONTINUITY".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-DISCONTINUITY");
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_key() {
let tag = ExtXKey::new_without_key();
let text = "#EXT-X-KEY:METHOD=NONE";
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
iv: None,
key_format: None,
key_format_versions: None,
});
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
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=0x000102030405060708090a0b0c0d0e0f"#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
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=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V5);
}
#[test]
fn ext_x_map() {
let tag = ExtXMap::new(QuotedString::new("foo").unwrap());
let text = r#"#EXT-X-MAP:URI="foo""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
let tag = ExtXMap::with_range(
QuotedString::new("foo").unwrap(),
ByteRange {
length: 9,
start: Some(2),
},
);
let text = r#"#EXT-X-MAP:URI="foo",BYTERANGE="9@2""#;
track_try_unwrap!(ExtXMap::from_str(text));
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
}
#[test]
fn ext_x_program_date_time() {
let text = "#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00";
assert!(text.parse::<ExtXProgramDateTime>().is_ok());
let tag = text.parse::<ExtXProgramDateTime>().unwrap();
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -0,0 +1,71 @@
use crate::types::{ByteRange, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
/// [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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXByteRange {
range: ByteRange,
}
impl ExtXByteRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
/// Makes a new `ExtXByteRange` tag.
pub fn new(range: ByteRange) -> Self {
ExtXByteRange { range }
}
/// Returns the range of the associated media segment.
pub fn range(&self) -> ByteRange {
self.range
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V4
}
}
impl fmt::Display for ExtXByteRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.range)
}
}
impl FromStr for ExtXByteRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let range = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXByteRange { range })
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_byterange() {
let tag = ExtXByteRange::new(ByteRange {
length: 3,
start: None,
});
assert_eq!("#EXT-X-BYTERANGE:3".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3");
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
let tag = ExtXByteRange::new(ByteRange {
length: 3,
start: Some(5),
});
assert_eq!("#EXT-X-BYTERANGE:3@5".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3@5");
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
}
}

View file

@ -0,0 +1,158 @@
use crate::attribute::AttributePairs;
use crate::types::{DecimalFloatingPoint, ProtocolVersion, QuotedString};
use crate::{Error, ErrorKind, Result};
use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
/// [4.3.2.7. EXT-X-DATERANGE]
///
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
///
/// TODO: Implement properly
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXDateRange {
pub id: QuotedString,
pub class: Option<QuotedString>,
pub start_date: QuotedString,
pub end_date: Option<QuotedString>,
pub duration: Option<Duration>,
pub planned_duration: Option<Duration>,
pub scte35_cmd: Option<QuotedString>,
pub scte35_out: Option<QuotedString>,
pub scte35_in: Option<QuotedString>,
pub end_on_next: bool,
pub client_attributes: BTreeMap<String, String>,
}
impl ExtXDateRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXDateRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "ID={}", self.id)?;
if let Some(ref x) = self.class {
write!(f, ",CLASS={}", x)?;
}
write!(f, ",START-DATE={}", self.start_date)?;
if let Some(ref x) = self.end_date {
write!(f, ",END-DATE={}", x)?;
}
if let Some(x) = self.duration {
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
}
if let Some(x) = self.planned_duration {
write!(
f,
",PLANNED-DURATION={}",
DecimalFloatingPoint::from_duration(x)
)?;
}
if let Some(ref x) = self.scte35_cmd {
write!(f, ",SCTE35-CMD={}", x)?;
}
if let Some(ref x) = self.scte35_out {
write!(f, ",SCTE35-OUT={}", x)?;
}
if let Some(ref x) = self.scte35_in {
write!(f, ",SCTE35-IN={}", x)?;
}
if self.end_on_next {
write!(f, ",END-ON-NEXT=YES",)?;
}
for (k, v) in &self.client_attributes {
write!(f, ",{}={}", k, v)?;
}
Ok(())
}
}
impl FromStr for ExtXDateRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut id = None;
let mut class = None;
let mut start_date = None;
let mut end_date = None;
let mut duration = None;
let mut planned_duration = None;
let mut scte35_cmd = None;
let mut scte35_out = None;
let mut scte35_in = None;
let mut end_on_next = false;
let mut client_attributes = BTreeMap::new();
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"ID" => id = Some(track!(value.parse())?),
"CLASS" => class = Some(track!(value.parse())?),
"START-DATE" => start_date = Some(track!(value.parse())?),
"END-DATE" => end_date = Some(track!(value.parse())?),
"DURATION" => {
let seconds: DecimalFloatingPoint = track!(value.parse())?;
duration = Some(seconds.to_duration());
}
"PLANNED-DURATION" => {
let seconds: DecimalFloatingPoint = track!(value.parse())?;
planned_duration = Some(seconds.to_duration());
}
"SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?),
"SCTE35-OUT" => scte35_out = Some(track!(value.parse())?),
"SCTE35-IN" => scte35_in = Some(track!(value.parse())?),
"END-ON-NEXT" => {
track_assert_eq!(value, "YES", ErrorKind::InvalidInput);
end_on_next = true;
}
_ => {
if key.starts_with("X-") {
client_attributes.insert(key.split_at(2).1.to_owned(), value.to_owned());
} else {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
}
let id = track_assert_some!(id, ErrorKind::InvalidInput);
let start_date = track_assert_some!(start_date, ErrorKind::InvalidInput);
if end_on_next {
track_assert!(class.is_some(), ErrorKind::InvalidInput);
}
Ok(ExtXDateRange {
id,
class,
start_date,
end_date,
duration,
planned_duration,
scte35_cmd,
scte35_out,
scte35_in,
end_on_next,
client_attributes,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test] // TODO; write some tests
fn it_works() {
}
}

View file

@ -0,0 +1,43 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.2.3. EXT-X-DISCONTINUITY]
///
/// [4.3.2.3. EXT-X-DISCONTINUITY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXDiscontinuity;
impl ExtXDiscontinuity {
pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_discontinuity() {
let tag = ExtXDiscontinuity;
assert_eq!("#EXT-X-DISCONTINUITY".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-DISCONTINUITY");
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -0,0 +1,114 @@
use crate::types::{DecimalFloatingPoint, ProtocolVersion, SingleLineString};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use std::time::Duration;
use trackable::error::ErrorKindExt;
/// [4.3.2.1. EXTINF]
///
/// [4.3.2.1. EXTINF]: https://tools.ietf.org/html/rfc8216#section-4.3.2.1
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtInf {
duration: Duration,
title: Option<SingleLineString>,
}
impl ExtInf {
pub(crate) const PREFIX: &'static str = "#EXTINF:";
/// Makes a new `ExtInf` tag.
pub fn new(duration: Duration) -> Self {
ExtInf {
duration,
title: None,
}
}
/// Makes a new `ExtInf` tag with the given title.
pub fn with_title(duration: Duration, title: SingleLineString) -> Self {
ExtInf {
duration,
title: Some(title),
}
}
/// Returns the duration of the associated media segment.
pub fn duration(&self) -> Duration {
self.duration
}
/// Returns the title of the associated media segment.
pub fn title(&self) -> Option<&SingleLineString> {
self.title.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
if self.duration.subsec_nanos() == 0 {
ProtocolVersion::V1
} else {
ProtocolVersion::V3
}
}
}
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)
+ (f64::from(self.duration.subsec_nanos()) / 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 seconds: DecimalFloatingPoint =
may_invalid!(tokens.next().expect("Never fails").parse())?;
let duration = seconds.to_duration();
let title = if let Some(title) = tokens.next() {
Some(track!(SingleLineString::new(title))?)
} else {
None
};
Ok(ExtInf { duration, title })
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn extinf() {
let tag = ExtInf::new(Duration::from_secs(5));
assert_eq!("#EXTINF:5".parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), "#EXTINF:5");
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtInf::with_title(
Duration::from_secs(5),
SingleLineString::new("foo").unwrap(),
);
assert_eq!("#EXTINF:5,foo".parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), "#EXTINF:5,foo");
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtInf::new(Duration::from_millis(1234));
assert_eq!("#EXTINF:1.234".parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), "#EXTINF:1.234");
assert_eq!(tag.requires_version(), ProtocolVersion::V3);
}
}

View file

@ -0,0 +1,130 @@
use crate::attribute::AttributePairs;
use crate::types::{DecryptionKey, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [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, PartialEq, Eq, Hash)]
pub struct ExtXKey {
key: Option<DecryptionKey>,
}
impl ExtXKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
/// Makes a new `ExtXKey` tag.
pub fn new(key: DecryptionKey) -> Self {
ExtXKey { key: Some(key) }
}
/// Makes a new `ExtXKey` tag without a decryption key.
///
/// This tag has the `METHDO=NONE` attribute.
pub fn new_without_key() -> Self {
ExtXKey { key: None }
}
/// Returns the decryption key for the following media segments and media initialization sections.
pub fn key(&self) -> Option<&DecryptionKey> {
self.key.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
self.key
.as_ref()
.map_or(ProtocolVersion::V1, |k| k.requires_version())
}
}
impl fmt::Display for ExtXKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
if let Some(ref key) = self.key {
write!(f, "{}", key)?;
} else {
write!(f, "METHOD=NONE")?;
}
Ok(())
}
}
impl FromStr for ExtXKey {
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;
if AttributePairs::parse(suffix).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) {
for attr in AttributePairs::parse(suffix) {
let (key, _) = track!(attr)?;
track_assert_ne!(key, "URI", ErrorKind::InvalidInput);
track_assert_ne!(key, "IV", ErrorKind::InvalidInput);
track_assert_ne!(key, "KEYFORMAT", ErrorKind::InvalidInput);
track_assert_ne!(key, "KEYFORMATVERSIONS", ErrorKind::InvalidInput);
}
Ok(ExtXKey { key: None })
} else {
let key = track!(suffix.parse())?;
Ok(ExtXKey { key: Some(key) })
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::{EncryptionMethod, InitializationVector, QuotedString};
#[test]
fn ext_x_key() {
let tag = ExtXKey::new_without_key();
let text = "#EXT-X-KEY:METHOD=NONE";
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
iv: None,
key_format: None,
key_format_versions: None,
});
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
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=0x000102030405060708090a0b0c0d0e0f"#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
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=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V5);
}
}

View file

@ -0,0 +1,112 @@
use crate::attribute::AttributePairs;
use crate::types::{ByteRange, ProtocolVersion, QuotedString};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.2.5. EXT-X-MAP]
///
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXMap {
uri: QuotedString,
range: Option<ByteRange>,
}
impl ExtXMap {
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
/// Makes a new `ExtXMap` tag.
pub fn new(uri: QuotedString) -> Self {
ExtXMap { uri, range: None }
}
/// Makes a new `ExtXMap` tag with the given range.
pub fn with_range(uri: QuotedString, range: ByteRange) -> Self {
ExtXMap {
uri,
range: Some(range),
}
}
/// Returns the URI that identifies a resource that contains the media initialization section.
pub fn uri(&self) -> &QuotedString {
&self.uri
}
/// Returns the range of the media initialization section.
pub fn range(&self) -> Option<ByteRange> {
self.range
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V6
}
}
impl fmt::Display for ExtXMap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "URI={}", self.uri)?;
if let Some(ref x) = self.range {
write!(f, ",BYTERANGE=\"{}\"", x)?;
}
Ok(())
}
}
impl FromStr for ExtXMap {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut uri = None;
let mut range = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"URI" => uri = Some(track!(value.parse())?),
"BYTERANGE" => {
let s: QuotedString = track!(value.parse())?;
range = Some(track!(s.parse())?);
}
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
let uri = track_assert_some!(uri, ErrorKind::InvalidInput);
Ok(ExtXMap { uri, range })
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_map() {
let tag = ExtXMap::new(QuotedString::new("foo").unwrap());
let text = r#"#EXT-X-MAP:URI="foo""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
let tag = ExtXMap::with_range(
QuotedString::new("foo").unwrap(),
ByteRange {
length: 9,
start: Some(2),
},
);
let text = r#"#EXT-X-MAP:URI="foo",BYTERANGE="9@2""#;
track_try_unwrap!(ExtXMap::from_str(text));
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
}
}

View file

@ -0,0 +1,15 @@
mod inf;
mod byte_range;
mod date_range;
mod key;
mod map;
mod program_date_time;
mod discontinuity;
pub use inf::*;
pub use byte_range::*;
pub use date_range::*;
pub use key::*;
pub use map::*;
pub use program_date_time::*;
pub use discontinuity::*;

View file

@ -0,0 +1,63 @@
use crate::types::{ProtocolVersion, SingleLineString};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
///
/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXProgramDateTime {
date_time: SingleLineString,
}
impl ExtXProgramDateTime {
pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:";
/// Makes a new `ExtXProgramDateTime` tag.
pub fn new(date_time: SingleLineString) -> Self {
ExtXProgramDateTime { date_time }
}
/// Returns the date-time of the first sample of the associated media segment.
pub fn date_time(&self) -> &SingleLineString {
&self.date_time
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXProgramDateTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.date_time)
}
}
impl FromStr for ExtXProgramDateTime {
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;
Ok(ExtXProgramDateTime {
date_time: track!(SingleLineString::new(suffix))?,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_program_date_time() {
let text = "#EXT-X-PROGRAM-DATE-TIME:2010-02-19T14:54:23.031+08:00";
assert!(text.parse::<ExtXProgramDateTime>().is_ok());
let tag = text.parse::<ExtXProgramDateTime>().unwrap();
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -1,8 +1,6 @@
//! [4.3. Playlist Tags]
//!
//! [4.3. Playlist Tags]: https://tools.ietf.org/html/rfc8216#section-4.3
use crate::{ErrorKind, Result};
use trackable::error::ErrorKindExt;
macro_rules! may_invalid {
($expr:expr) => {
@ -20,24 +18,17 @@ macro_rules! impl_from {
};
}
pub use self::basic::{ExtM3u, ExtXVersion};
pub use self::master_playlist::{
ExtXIFrameStreamInf, ExtXMedia, ExtXSessionData, ExtXSessionKey, ExtXStreamInf,
};
pub use self::media_or_master_playlist::{ExtXIndependentSegments, ExtXStart};
pub use self::media_playlist::{
ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, ExtXMediaSequence, ExtXPlaylistType,
ExtXTargetDuration,
};
pub use self::media_segment::{
ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime,
};
mod basic;
mod master_playlist;
mod media_or_master_playlist;
mod media_playlist;
mod media_segment;
mod shared;
pub use basic::*;
pub use master_playlist::*;
pub use media_playlist::*;
pub use media_segment::*;
pub use shared::*;
/// [4.3.4. Master Playlist Tags]
///
@ -114,16 +105,3 @@ impl_from!(MediaSegmentTag, ExtXDiscontinuity);
impl_from!(MediaSegmentTag, ExtXKey);
impl_from!(MediaSegmentTag, ExtXMap);
impl_from!(MediaSegmentTag, ExtXProgramDateTime);
fn parse_yes_or_no(s: &str) -> Result<bool> {
match s {
"YES" => Ok(true),
"NO" => Ok(false),
_ => 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)
}

View file

@ -0,0 +1,46 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
///
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXIndependentSegments;
impl ExtXIndependentSegments {
pub(crate) const PREFIX: &'static str = "#EXT-X-INDEPENDENT-SEGMENTS";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_independent_segments() {
let tag = ExtXIndependentSegments;
let text = "#EXT-X-INDEPENDENT-SEGMENTS";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

5
src/tags/shared/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod independent_segments;
mod start;
pub use start::*;
pub use independent_segments::*;

View file

@ -1,36 +1,10 @@
use super::parse_yes_or_no;
use crate::utils::parse_yes_or_no;
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
///
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXIndependentSegments;
impl ExtXIndependentSegments {
pub(crate) const PREFIX: &'static str = "#EXT-X-INDEPENDENT-SEGMENTS";
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(self) -> ProtocolVersion {
ProtocolVersion::V1
}
}
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)
}
}
/// [4.3.5.2. EXT-X-START]
///
/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
@ -39,6 +13,7 @@ pub struct ExtXStart {
time_offset: SignedDecimalFloatingPoint,
precise: bool,
}
impl ExtXStart {
pub(crate) const PREFIX: &'static str = "#EXT-X-START:";
@ -74,6 +49,7 @@ impl ExtXStart {
ProtocolVersion::V1
}
}
impl fmt::Display for ExtXStart {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
@ -84,6 +60,7 @@ impl fmt::Display for ExtXStart {
Ok(())
}
}
impl FromStr for ExtXStart {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
@ -116,15 +93,6 @@ impl FromStr for ExtXStart {
mod test {
use super::*;
#[test]
fn ext_x_independent_segments() {
let tag = ExtXIndependentSegments;
let text = "#EXT-X-INDEPENDENT-SEGMENTS";
assert_eq!(text.parse().ok(), Some(tag));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
#[test]
fn ext_x_start() {
let tag = ExtXStart::new(SignedDecimalFloatingPoint::new(-1.23).unwrap());

15
src/utils.rs Normal file
View file

@ -0,0 +1,15 @@
use crate::{ErrorKind, Result};
use trackable::error::ErrorKindExt;
pub fn parse_yes_or_no(s: &str) -> Result<bool> {
match s {
"YES" => Ok(true),
"NO" => Ok(false),
_ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s),
}
}
pub fn parse_u64(s: &str) -> Result<u64> {
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
Ok(n)
}