mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 08:31:00 +00:00
removed QuotedString
This commit is contained in:
parent
861e7e4b74
commit
1966a7608d
15 changed files with 256 additions and 272 deletions
|
@ -3,7 +3,7 @@ use crate::tags::{
|
||||||
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
|
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
|
||||||
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag,
|
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag,
|
||||||
};
|
};
|
||||||
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion, QuotedString};
|
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -22,6 +22,7 @@ pub struct MasterPlaylistBuilder {
|
||||||
session_data_tags: Vec<ExtXSessionData>,
|
session_data_tags: Vec<ExtXSessionData>,
|
||||||
session_key_tags: Vec<ExtXSessionKey>,
|
session_key_tags: Vec<ExtXSessionKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterPlaylistBuilder {
|
impl MasterPlaylistBuilder {
|
||||||
/// Makes a new `MasterPlaylistBuilder` instance.
|
/// Makes a new `MasterPlaylistBuilder` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -209,12 +210,13 @@ impl MasterPlaylistBuilder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_media_group(&self, media_type: MediaType, group_id: &QuotedString) -> bool {
|
fn check_media_group<T: ToString>(&self, media_type: MediaType, group_id: T) -> bool {
|
||||||
self.media_tags
|
self.media_tags
|
||||||
.iter()
|
.iter()
|
||||||
.any(|t| t.media_type() == media_type && t.group_id() == group_id)
|
.any(|t| t.media_type() == media_type && t.group_id() == &group_id.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MasterPlaylistBuilder {
|
impl Default for MasterPlaylistBuilder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
@ -233,6 +235,7 @@ pub struct MasterPlaylist {
|
||||||
session_data_tags: Vec<ExtXSessionData>,
|
session_data_tags: Vec<ExtXSessionData>,
|
||||||
session_key_tags: Vec<ExtXSessionKey>,
|
session_key_tags: Vec<ExtXSessionKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterPlaylist {
|
impl MasterPlaylist {
|
||||||
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
|
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
|
||||||
pub fn version_tag(&self) -> ExtXVersion {
|
pub fn version_tag(&self) -> ExtXVersion {
|
||||||
|
@ -274,6 +277,7 @@ impl MasterPlaylist {
|
||||||
&self.session_key_tags
|
&self.session_key_tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MasterPlaylist {
|
impl fmt::Display for MasterPlaylist {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
writeln!(f, "{}", ExtM3u)?;
|
writeln!(f, "{}", ExtM3u)?;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion, QuotedString};
|
use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion};
|
||||||
use crate::utils::parse_u64;
|
use crate::utils::parse_u64;
|
||||||
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -10,22 +11,22 @@ use std::str::FromStr;
|
||||||
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
|
/// [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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXIFrameStreamInf {
|
pub struct ExtXIFrameStreamInf {
|
||||||
uri: QuotedString,
|
uri: String,
|
||||||
bandwidth: u64,
|
bandwidth: u64,
|
||||||
average_bandwidth: Option<u64>,
|
average_bandwidth: Option<u64>,
|
||||||
codecs: Option<QuotedString>,
|
codecs: Option<String>,
|
||||||
resolution: Option<DecimalResolution>,
|
resolution: Option<DecimalResolution>,
|
||||||
hdcp_level: Option<HdcpLevel>,
|
hdcp_level: Option<HdcpLevel>,
|
||||||
video: Option<QuotedString>,
|
video: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtXIFrameStreamInf {
|
impl ExtXIFrameStreamInf {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
|
||||||
|
|
||||||
/// Makes a new `ExtXIFrameStreamInf` tag.
|
/// Makes a new `ExtXIFrameStreamInf` tag.
|
||||||
pub fn new(uri: QuotedString, bandwidth: u64) -> Self {
|
pub fn new<T: ToString>(uri: T, bandwidth: u64) -> Self {
|
||||||
ExtXIFrameStreamInf {
|
ExtXIFrameStreamInf {
|
||||||
uri,
|
uri: uri.to_string(),
|
||||||
bandwidth,
|
bandwidth,
|
||||||
average_bandwidth: None,
|
average_bandwidth: None,
|
||||||
codecs: None,
|
codecs: None,
|
||||||
|
@ -36,7 +37,7 @@ impl ExtXIFrameStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the URI that identifies the associated media playlist.
|
/// Returns the URI that identifies the associated media playlist.
|
||||||
pub fn uri(&self) -> &QuotedString {
|
pub fn uri(&self) -> &String {
|
||||||
&self.uri
|
&self.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ impl ExtXIFrameStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string that represents the list of codec types contained the variant stream.
|
/// Returns a string that represents the list of codec types contained the variant stream.
|
||||||
pub fn codecs(&self) -> Option<&QuotedString> {
|
pub fn codecs(&self) -> Option<&String> {
|
||||||
self.codecs.as_ref()
|
self.codecs.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ impl ExtXIFrameStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group identifier for the video in the variant stream.
|
/// Returns the group identifier for the video in the variant stream.
|
||||||
pub fn video(&self) -> Option<&QuotedString> {
|
pub fn video(&self) -> Option<&String> {
|
||||||
self.video.as_ref()
|
self.video.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,13 +80,14 @@ impl ExtXIFrameStreamInf {
|
||||||
impl fmt::Display for ExtXIFrameStreamInf {
|
impl fmt::Display for ExtXIFrameStreamInf {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", Self::PREFIX)?;
|
write!(f, "{}", Self::PREFIX)?;
|
||||||
write!(f, "URI={}", self.uri)?;
|
write!(f, "URI={}", quote(&self.uri))?;
|
||||||
write!(f, ",BANDWIDTH={}", self.bandwidth)?;
|
write!(f, ",BANDWIDTH={}", self.bandwidth)?;
|
||||||
|
|
||||||
if let Some(ref x) = self.average_bandwidth {
|
if let Some(ref x) = self.average_bandwidth {
|
||||||
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.codecs {
|
if let Some(ref x) = self.codecs {
|
||||||
write!(f, ",CODECS={}", x)?;
|
write!(f, ",CODECS={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.resolution {
|
if let Some(ref x) = self.resolution {
|
||||||
write!(f, ",RESOLUTION={}", x)?;
|
write!(f, ",RESOLUTION={}", x)?;
|
||||||
|
@ -94,7 +96,7 @@ impl fmt::Display for ExtXIFrameStreamInf {
|
||||||
write!(f, ",HDCP-LEVEL={}", x)?;
|
write!(f, ",HDCP-LEVEL={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.video {
|
if let Some(ref x) = self.video {
|
||||||
write!(f, ",VIDEO={}", x)?;
|
write!(f, ",VIDEO={}", quote(x))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -116,13 +118,13 @@ impl FromStr for ExtXIFrameStreamInf {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"URI" => uri = Some(track!(value.parse())?),
|
"URI" => uri = Some(unquote(value)),
|
||||||
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
"CODECS" => codecs = Some(unquote(value)),
|
||||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||||
"VIDEO" => video = Some(track!(value.parse())?),
|
"VIDEO" => video = Some(unquote(value)),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||||
|
@ -150,14 +152,10 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_i_frame_stream_inf() {
|
fn ext_x_i_frame_stream_inf() {
|
||||||
let tag = ExtXIFrameStreamInf::new(quoted_string("foo"), 1000);
|
let tag = ExtXIFrameStreamInf::new("foo", 1000);
|
||||||
let text = r#"#EXT-X-I-FRAME-STREAM-INF:URI="foo",BANDWIDTH=1000"#;
|
let text = r#"#EXT-X-I-FRAME-STREAM-INF:URI="foo",BANDWIDTH=1000"#;
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quoted_string(s: &str) -> QuotedString {
|
|
||||||
QuotedString::new(s).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{InStreamId, MediaType, ProtocolVersion, QuotedString};
|
use crate::types::{InStreamId, MediaType, ProtocolVersion};
|
||||||
use crate::utils::parse_yes_or_no;
|
use crate::utils::{parse_yes_or_no, quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -9,17 +9,17 @@ use std::str::FromStr;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ExtXMediaBuilder {
|
pub struct ExtXMediaBuilder {
|
||||||
media_type: Option<MediaType>,
|
media_type: Option<MediaType>,
|
||||||
uri: Option<QuotedString>,
|
uri: Option<String>,
|
||||||
group_id: Option<QuotedString>,
|
group_id: Option<String>,
|
||||||
language: Option<QuotedString>,
|
language: Option<String>,
|
||||||
assoc_language: Option<QuotedString>,
|
assoc_language: Option<String>,
|
||||||
name: Option<QuotedString>,
|
name: Option<String>,
|
||||||
default: bool,
|
default: bool,
|
||||||
autoselect: Option<bool>,
|
autoselect: Option<bool>,
|
||||||
forced: Option<bool>,
|
forced: Option<bool>,
|
||||||
instream_id: Option<InStreamId>,
|
instream_id: Option<InStreamId>,
|
||||||
characteristics: Option<QuotedString>,
|
characteristics: Option<String>,
|
||||||
channels: Option<QuotedString>,
|
channels: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtXMediaBuilder {
|
impl ExtXMediaBuilder {
|
||||||
|
@ -48,32 +48,32 @@ impl ExtXMediaBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the identifier that specifies the group to which the rendition belongs.
|
/// Sets the identifier that specifies the group to which the rendition belongs.
|
||||||
pub fn group_id(&mut self, group_id: QuotedString) -> &mut Self {
|
pub fn group_id<T: ToString>(&mut self, group_id: T) -> &mut Self {
|
||||||
self.group_id = Some(group_id);
|
self.group_id = Some(group_id.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a human-readable description of the rendition.
|
/// Sets a human-readable description of the rendition.
|
||||||
pub fn name(&mut self, name: QuotedString) -> &mut Self {
|
pub fn name<T: ToString>(&mut self, name: T) -> &mut Self {
|
||||||
self.name = Some(name);
|
self.name = Some(name.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the URI that identifies the media playlist.
|
/// Sets the URI that identifies the media playlist.
|
||||||
pub fn uri(&mut self, uri: QuotedString) -> &mut Self {
|
pub fn uri<T: ToString>(&mut self, uri: T) -> &mut Self {
|
||||||
self.uri = Some(uri);
|
self.uri = Some(uri.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the name of the primary language used in the rendition.
|
/// Sets the name of the primary language used in the rendition.
|
||||||
pub fn language(&mut self, language: QuotedString) -> &mut Self {
|
pub fn language<T: ToString>(&mut self, language: T) -> &mut Self {
|
||||||
self.language = Some(language);
|
self.language = Some(language.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the name of a language associated with the rendition.
|
/// Sets the name of a language associated with the rendition.
|
||||||
pub fn assoc_language(&mut self, language: QuotedString) -> &mut Self {
|
pub fn assoc_language<T: ToString>(&mut self, language: T) -> &mut Self {
|
||||||
self.assoc_language = Some(language);
|
self.assoc_language = Some(language.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,14 +102,14 @@ impl ExtXMediaBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the string that represents uniform type identifiers (UTI).
|
/// Sets the string that represents uniform type identifiers (UTI).
|
||||||
pub fn characteristics(&mut self, characteristics: QuotedString) -> &mut Self {
|
pub fn characteristics<T: ToString>(&mut self, characteristics: T) -> &mut Self {
|
||||||
self.characteristics = Some(characteristics);
|
self.characteristics = Some(characteristics.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the string that represents the parameters of the rendition.
|
/// Sets the string that represents the parameters of the rendition.
|
||||||
pub fn channels(&mut self, channels: QuotedString) -> &mut Self {
|
pub fn channels<T: ToString>(&mut self, channels: T) -> &mut Self {
|
||||||
self.channels = Some(channels);
|
self.channels = Some(channels.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,31 +159,31 @@ impl Default for ExtXMediaBuilder {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXMedia {
|
pub struct ExtXMedia {
|
||||||
media_type: MediaType,
|
media_type: MediaType,
|
||||||
uri: Option<QuotedString>,
|
uri: Option<String>,
|
||||||
group_id: QuotedString,
|
group_id: String,
|
||||||
language: Option<QuotedString>,
|
language: Option<String>,
|
||||||
assoc_language: Option<QuotedString>,
|
assoc_language: Option<String>,
|
||||||
name: QuotedString,
|
name: String,
|
||||||
default: bool,
|
default: bool,
|
||||||
autoselect: bool,
|
autoselect: bool,
|
||||||
forced: bool,
|
forced: bool,
|
||||||
instream_id: Option<InStreamId>,
|
instream_id: Option<InStreamId>,
|
||||||
characteristics: Option<QuotedString>,
|
characteristics: Option<String>,
|
||||||
channels: Option<QuotedString>,
|
channels: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtXMedia {
|
impl ExtXMedia {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
|
||||||
|
|
||||||
/// Makes a new `ExtXMedia` tag.
|
/// Makes a new `ExtXMedia` tag.
|
||||||
pub fn new(media_type: MediaType, group_id: QuotedString, name: QuotedString) -> Self {
|
pub fn new<T: ToString>(media_type: MediaType, group_id: T, name: T) -> Self {
|
||||||
ExtXMedia {
|
ExtXMedia {
|
||||||
media_type,
|
media_type,
|
||||||
uri: None,
|
uri: None,
|
||||||
group_id,
|
group_id: group_id.to_string(),
|
||||||
language: None,
|
language: None,
|
||||||
assoc_language: None,
|
assoc_language: None,
|
||||||
name,
|
name: name.to_string(),
|
||||||
default: false,
|
default: false,
|
||||||
autoselect: false,
|
autoselect: false,
|
||||||
forced: false,
|
forced: false,
|
||||||
|
@ -199,27 +199,27 @@ impl ExtXMedia {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the identifier that specifies the group to which the rendition belongs.
|
/// Returns the identifier that specifies the group to which the rendition belongs.
|
||||||
pub fn group_id(&self) -> &QuotedString {
|
pub fn group_id(&self) -> &String {
|
||||||
&self.group_id
|
&self.group_id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a human-readable description of the rendition.
|
/// Returns a human-readable description of the rendition.
|
||||||
pub fn name(&self) -> &QuotedString {
|
pub fn name(&self) -> &String {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the URI that identifies the media playlist.
|
/// Returns the URI that identifies the media playlist.
|
||||||
pub fn uri(&self) -> Option<&QuotedString> {
|
pub fn uri(&self) -> Option<&String> {
|
||||||
self.uri.as_ref()
|
self.uri.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the name of the primary language used in the rendition.
|
/// Returns the name of the primary language used in the rendition.
|
||||||
pub fn language(&self) -> Option<&QuotedString> {
|
pub fn language(&self) -> Option<&String> {
|
||||||
self.language.as_ref()
|
self.language.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the name of a language associated with the rendition.
|
/// Returns the name of a language associated with the rendition.
|
||||||
pub fn assoc_language(&self) -> Option<&QuotedString> {
|
pub fn assoc_language(&self) -> Option<&String> {
|
||||||
self.assoc_language.as_ref()
|
self.assoc_language.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,12 +247,12 @@ impl ExtXMedia {
|
||||||
/// Returns a string that represents uniform type identifiers (UTI).
|
/// Returns a string that represents uniform type identifiers (UTI).
|
||||||
///
|
///
|
||||||
/// Each UTI indicates an individual characteristic of the rendition.
|
/// Each UTI indicates an individual characteristic of the rendition.
|
||||||
pub fn characteristics(&self) -> Option<&QuotedString> {
|
pub fn characteristics(&self) -> Option<&String> {
|
||||||
self.characteristics.as_ref()
|
self.characteristics.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string that represents the parameters of the rendition.
|
/// Returns a string that represents the parameters of the rendition.
|
||||||
pub fn channels(&self) -> Option<&QuotedString> {
|
pub fn channels(&self) -> Option<&String> {
|
||||||
self.channels.as_ref()
|
self.channels.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,16 +274,16 @@ impl fmt::Display for ExtXMedia {
|
||||||
write!(f, "{}", Self::PREFIX)?;
|
write!(f, "{}", Self::PREFIX)?;
|
||||||
write!(f, "TYPE={}", self.media_type)?;
|
write!(f, "TYPE={}", self.media_type)?;
|
||||||
if let Some(ref x) = self.uri {
|
if let Some(ref x) = self.uri {
|
||||||
write!(f, ",URI={}", x)?;
|
write!(f, ",URI={}", quote(x))?;
|
||||||
}
|
}
|
||||||
write!(f, ",GROUP-ID={}", self.group_id)?;
|
write!(f, ",GROUP-ID={}", quote(&self.group_id))?;
|
||||||
if let Some(ref x) = self.language {
|
if let Some(ref x) = self.language {
|
||||||
write!(f, ",LANGUAGE={}", x)?;
|
write!(f, ",LANGUAGE={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.assoc_language {
|
if let Some(ref x) = self.assoc_language {
|
||||||
write!(f, ",ASSOC-LANGUAGE={}", x)?;
|
write!(f, ",ASSOC-LANGUAGE={}", quote(x))?;
|
||||||
}
|
}
|
||||||
write!(f, ",NAME={}", self.name)?;
|
write!(f, ",NAME={}", quote(&self.name))?;
|
||||||
if self.default {
|
if self.default {
|
||||||
write!(f, ",DEFAULT=YES")?;
|
write!(f, ",DEFAULT=YES")?;
|
||||||
}
|
}
|
||||||
|
@ -294,13 +294,13 @@ impl fmt::Display for ExtXMedia {
|
||||||
write!(f, ",FORCED=YES")?;
|
write!(f, ",FORCED=YES")?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.instream_id {
|
if let Some(ref x) = self.instream_id {
|
||||||
write!(f, ",INSTREAM-ID=\"{}\"", x)?;
|
write!(f, ",INSTREAM-ID={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.characteristics {
|
if let Some(ref x) = self.characteristics {
|
||||||
write!(f, ",CHARACTERISTICS={}", x)?;
|
write!(f, ",CHARACTERISTICS={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.channels {
|
if let Some(ref x) = self.channels {
|
||||||
write!(f, ",CHANNELS={}", x)?;
|
write!(f, ",CHANNELS={}", quote(x))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -320,19 +320,19 @@ impl FromStr for ExtXMedia {
|
||||||
builder.media_type(track!(value.parse())?);
|
builder.media_type(track!(value.parse())?);
|
||||||
}
|
}
|
||||||
"URI" => {
|
"URI" => {
|
||||||
builder.uri(track!(value.parse())?);
|
builder.uri(unquote(value));
|
||||||
}
|
}
|
||||||
"GROUP-ID" => {
|
"GROUP-ID" => {
|
||||||
builder.group_id(track!(value.parse())?);
|
builder.group_id(unquote(value));
|
||||||
}
|
}
|
||||||
"LANGUAGE" => {
|
"LANGUAGE" => {
|
||||||
builder.language(track!(value.parse())?);
|
builder.language(unquote(value));
|
||||||
}
|
}
|
||||||
"ASSOC-LANGUAGE" => {
|
"ASSOC-LANGUAGE" => {
|
||||||
builder.assoc_language(track!(value.parse())?);
|
builder.assoc_language(unquote(value));
|
||||||
}
|
}
|
||||||
"NAME" => {
|
"NAME" => {
|
||||||
builder.name(track!(value.parse())?);
|
builder.name(unquote(value));
|
||||||
}
|
}
|
||||||
"DEFAULT" => {
|
"DEFAULT" => {
|
||||||
builder.default(track!(parse_yes_or_no(value))?);
|
builder.default(track!(parse_yes_or_no(value))?);
|
||||||
|
@ -344,14 +344,13 @@ impl FromStr for ExtXMedia {
|
||||||
builder.forced(track!(parse_yes_or_no(value))?);
|
builder.forced(track!(parse_yes_or_no(value))?);
|
||||||
}
|
}
|
||||||
"INSTREAM-ID" => {
|
"INSTREAM-ID" => {
|
||||||
let s: QuotedString = track!(value.parse())?;
|
builder.instream_id(unquote(value).parse()?);
|
||||||
builder.instream_id(track!(s.parse())?);
|
|
||||||
}
|
}
|
||||||
"CHARACTERISTICS" => {
|
"CHARACTERISTICS" => {
|
||||||
builder.characteristics(track!(value.parse())?);
|
builder.characteristics(unquote(value));
|
||||||
}
|
}
|
||||||
"CHANNELS" => {
|
"CHANNELS" => {
|
||||||
builder.channels(track!(value.parse())?);
|
builder.channels(unquote(value));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
|
@ -369,14 +368,10 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_media() {
|
fn ext_x_media() {
|
||||||
let tag = ExtXMedia::new(MediaType::Audio, quoted_string("foo"), quoted_string("bar"));
|
let tag = ExtXMedia::new(MediaType::Audio, "foo", "bar");
|
||||||
let text = r#"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="foo",NAME="bar""#;
|
let text = r#"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="foo",NAME="bar""#;
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quoted_string(s: &str) -> QuotedString {
|
|
||||||
QuotedString::new(s).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{ProtocolVersion, QuotedString, SessionData};
|
use crate::types::{ProtocolVersion, SessionData};
|
||||||
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -9,34 +10,34 @@ use std::str::FromStr;
|
||||||
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
/// [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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXSessionData {
|
pub struct ExtXSessionData {
|
||||||
data_id: QuotedString,
|
data_id: String,
|
||||||
data: SessionData,
|
data: SessionData,
|
||||||
language: Option<QuotedString>,
|
language: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtXSessionData {
|
impl ExtXSessionData {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
|
||||||
|
|
||||||
/// Makes a new `ExtXSessionData` tag.
|
/// Makes a new `ExtXSessionData` tag.
|
||||||
pub fn new(data_id: QuotedString, data: SessionData) -> Self {
|
pub fn new<T: ToString>(data_id: T, data: SessionData) -> Self {
|
||||||
ExtXSessionData {
|
ExtXSessionData {
|
||||||
data_id,
|
data_id: data_id.to_string(),
|
||||||
data,
|
data,
|
||||||
language: None,
|
language: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new `ExtXSessionData` with the given language.
|
/// Makes a new `ExtXSessionData` with the given language.
|
||||||
pub fn with_language(data_id: QuotedString, data: SessionData, language: QuotedString) -> Self {
|
pub fn with_language<T: ToString>(data_id: T, data: SessionData, language: T) -> Self {
|
||||||
ExtXSessionData {
|
ExtXSessionData {
|
||||||
data_id,
|
data_id: data_id.to_string(),
|
||||||
data,
|
data,
|
||||||
language: Some(language),
|
language: Some(language.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the identifier of the data.
|
/// Returns the identifier of the data.
|
||||||
pub fn data_id(&self) -> &QuotedString {
|
pub fn data_id(&self) -> &String {
|
||||||
&self.data_id
|
&self.data_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ impl ExtXSessionData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the language of the data.
|
/// Returns the language of the data.
|
||||||
pub fn language(&self) -> Option<&QuotedString> {
|
pub fn language(&self) -> Option<&String> {
|
||||||
self.language.as_ref()
|
self.language.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,13 +60,13 @@ impl ExtXSessionData {
|
||||||
impl fmt::Display for ExtXSessionData {
|
impl fmt::Display for ExtXSessionData {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", Self::PREFIX)?;
|
write!(f, "{}", Self::PREFIX)?;
|
||||||
write!(f, "DATA-ID={}", self.data_id)?;
|
write!(f, "DATA-ID={}", quote(&self.data_id))?;
|
||||||
match self.data {
|
match self.data {
|
||||||
SessionData::Value(ref x) => write!(f, ",VALUE={}", x)?,
|
SessionData::Value(ref x) => write!(f, ",VALUE={}", quote(x))?,
|
||||||
SessionData::Uri(ref x) => write!(f, ",URI={}", x)?,
|
SessionData::Uri(ref x) => write!(f, ",URI={}", quote(x))?,
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.language {
|
if let Some(ref x) = self.language {
|
||||||
write!(f, ",LANGUAGE={}", x)?;
|
write!(f, ",LANGUAGE={}", quote(x))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -84,10 +85,10 @@ impl FromStr for ExtXSessionData {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"DATA-ID" => data_id = Some(track!(value.parse())?),
|
"DATA-ID" => data_id = Some(unquote(value)),
|
||||||
"VALUE" => session_value = Some(track!(value.parse())?),
|
"VALUE" => session_value = Some(unquote(value)),
|
||||||
"URI" => uri = Some(track!(value.parse())?),
|
"URI" => uri = Some(unquote(value)),
|
||||||
"LANGUAGE" => language = Some(track!(value.parse())?),
|
"LANGUAGE" => language = Some(unquote(value)),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||||
|
@ -118,34 +119,22 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_session_data() {
|
fn ext_x_session_data() {
|
||||||
let tag = ExtXSessionData::new(
|
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
|
||||||
quoted_string("foo"),
|
|
||||||
SessionData::Value(quoted_string("bar")),
|
|
||||||
);
|
|
||||||
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
|
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||||
|
|
||||||
let tag =
|
let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into()));
|
||||||
ExtXSessionData::new(quoted_string("foo"), SessionData::Uri(quoted_string("bar")));
|
|
||||||
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
|
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||||
|
|
||||||
let tag = ExtXSessionData::with_language(
|
let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz");
|
||||||
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""#;
|
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#;
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quoted_string(s: &str) -> QuotedString {
|
|
||||||
QuotedString::new(s).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ impl fmt::Display for ExtXSessionKey {
|
||||||
|
|
||||||
impl FromStr for ExtXSessionKey {
|
impl FromStr for ExtXSessionKey {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
|
||||||
let suffix = s.split_at(Self::PREFIX.len()).1;
|
let suffix = s.split_at(Self::PREFIX.len()).1;
|
||||||
|
@ -49,13 +50,13 @@ impl FromStr for ExtXSessionKey {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::types::{EncryptionMethod, InitializationVector, QuotedString};
|
use crate::types::{EncryptionMethod, InitializationVector};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_session_key() {
|
fn ext_x_session_key() {
|
||||||
let tag = ExtXSessionKey::new(DecryptionKey {
|
let tag = ExtXSessionKey::new(DecryptionKey {
|
||||||
method: EncryptionMethod::Aes128,
|
method: EncryptionMethod::Aes128,
|
||||||
uri: quoted_string("foo"),
|
uri: "foo".to_string(),
|
||||||
iv: Some(InitializationVector([
|
iv: Some(InitializationVector([
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
])),
|
])),
|
||||||
|
@ -68,8 +69,4 @@ mod test {
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quoted_string(s: &str) -> QuotedString {
|
|
||||||
QuotedString::new(s).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion,
|
ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion,
|
||||||
QuotedString, SingleLineString,
|
SingleLineString,
|
||||||
};
|
};
|
||||||
use crate::utils::parse_u64;
|
use crate::utils::{parse_u64, quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -16,13 +16,13 @@ pub struct ExtXStreamInf {
|
||||||
uri: SingleLineString,
|
uri: SingleLineString,
|
||||||
bandwidth: u64,
|
bandwidth: u64,
|
||||||
average_bandwidth: Option<u64>,
|
average_bandwidth: Option<u64>,
|
||||||
codecs: Option<QuotedString>,
|
codecs: Option<String>,
|
||||||
resolution: Option<DecimalResolution>,
|
resolution: Option<DecimalResolution>,
|
||||||
frame_rate: Option<DecimalFloatingPoint>,
|
frame_rate: Option<DecimalFloatingPoint>,
|
||||||
hdcp_level: Option<HdcpLevel>,
|
hdcp_level: Option<HdcpLevel>,
|
||||||
audio: Option<QuotedString>,
|
audio: Option<String>,
|
||||||
video: Option<QuotedString>,
|
video: Option<String>,
|
||||||
subtitles: Option<QuotedString>,
|
subtitles: Option<String>,
|
||||||
closed_captions: Option<ClosedCaptions>,
|
closed_captions: Option<ClosedCaptions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ impl ExtXStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a string that represents the list of codec types contained the variant stream.
|
/// Returns a string that represents the list of codec types contained the variant stream.
|
||||||
pub fn codecs(&self) -> Option<&QuotedString> {
|
pub fn codecs(&self) -> Option<&String> {
|
||||||
self.codecs.as_ref()
|
self.codecs.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,17 +82,17 @@ impl ExtXStreamInf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group identifier for the audio in the variant stream.
|
/// Returns the group identifier for the audio in the variant stream.
|
||||||
pub fn audio(&self) -> Option<&QuotedString> {
|
pub fn audio(&self) -> Option<&String> {
|
||||||
self.audio.as_ref()
|
self.audio.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group identifier for the video in the variant stream.
|
/// Returns the group identifier for the video in the variant stream.
|
||||||
pub fn video(&self) -> Option<&QuotedString> {
|
pub fn video(&self) -> Option<&String> {
|
||||||
self.video.as_ref()
|
self.video.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the group identifier for the subtitles in the variant stream.
|
/// Returns the group identifier for the subtitles in the variant stream.
|
||||||
pub fn subtitles(&self) -> Option<&QuotedString> {
|
pub fn subtitles(&self) -> Option<&String> {
|
||||||
self.subtitles.as_ref()
|
self.subtitles.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ impl fmt::Display for ExtXStreamInf {
|
||||||
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.codecs {
|
if let Some(ref x) = self.codecs {
|
||||||
write!(f, ",CODECS={}", x)?;
|
write!(f, ",CODECS={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.resolution {
|
if let Some(ref x) = self.resolution {
|
||||||
write!(f, ",RESOLUTION={}", x)?;
|
write!(f, ",RESOLUTION={}", x)?;
|
||||||
|
@ -127,13 +127,13 @@ impl fmt::Display for ExtXStreamInf {
|
||||||
write!(f, ",HDCP-LEVEL={}", x)?;
|
write!(f, ",HDCP-LEVEL={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.audio {
|
if let Some(ref x) = self.audio {
|
||||||
write!(f, ",AUDIO={}", x)?;
|
write!(f, ",AUDIO={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.video {
|
if let Some(ref x) = self.video {
|
||||||
write!(f, ",VIDEO={}", x)?;
|
write!(f, ",VIDEO={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.subtitles {
|
if let Some(ref x) = self.subtitles {
|
||||||
write!(f, ",SUBTITLES={}", x)?;
|
write!(f, ",SUBTITLES={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.closed_captions {
|
if let Some(ref x) = self.closed_captions {
|
||||||
write!(f, ",CLOSED-CAPTIONS={}", x)?;
|
write!(f, ",CLOSED-CAPTIONS={}", x)?;
|
||||||
|
@ -171,13 +171,13 @@ impl FromStr for ExtXStreamInf {
|
||||||
match key {
|
match key {
|
||||||
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
"BANDWIDTH" => bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(track!(parse_u64(value))?),
|
||||||
"CODECS" => codecs = Some(track!(value.parse())?),
|
"CODECS" => codecs = Some(unquote(value)),
|
||||||
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
"RESOLUTION" => resolution = Some(track!(value.parse())?),
|
||||||
"FRAME-RATE" => frame_rate = Some(track!(value.parse())?),
|
"FRAME-RATE" => frame_rate = Some(track!(value.parse())?),
|
||||||
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
"HDCP-LEVEL" => hdcp_level = Some(track!(value.parse())?),
|
||||||
"AUDIO" => audio = Some(track!(value.parse())?),
|
"AUDIO" => audio = Some(unquote(value)),
|
||||||
"VIDEO" => video = Some(track!(value.parse())?),
|
"VIDEO" => video = Some(unquote(value)),
|
||||||
"SUBTITLES" => subtitles = Some(track!(value.parse())?),
|
"SUBTITLES" => subtitles = Some(unquote(value)),
|
||||||
"CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?),
|
"CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{DecimalFloatingPoint, ProtocolVersion, QuotedString};
|
use crate::types::{DecimalFloatingPoint, ProtocolVersion};
|
||||||
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -14,15 +15,15 @@ use std::time::Duration;
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXDateRange {
|
pub struct ExtXDateRange {
|
||||||
pub id: QuotedString,
|
pub id: String,
|
||||||
pub class: Option<QuotedString>,
|
pub class: Option<String>,
|
||||||
pub start_date: QuotedString,
|
pub start_date: String,
|
||||||
pub end_date: Option<QuotedString>,
|
pub end_date: Option<String>,
|
||||||
pub duration: Option<Duration>,
|
pub duration: Option<Duration>,
|
||||||
pub planned_duration: Option<Duration>,
|
pub planned_duration: Option<Duration>,
|
||||||
pub scte35_cmd: Option<QuotedString>,
|
pub scte35_cmd: Option<String>,
|
||||||
pub scte35_out: Option<QuotedString>,
|
pub scte35_out: Option<String>,
|
||||||
pub scte35_in: Option<QuotedString>,
|
pub scte35_in: Option<String>,
|
||||||
pub end_on_next: bool,
|
pub end_on_next: bool,
|
||||||
pub client_attributes: BTreeMap<String, String>,
|
pub client_attributes: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
@ -39,13 +40,13 @@ impl ExtXDateRange {
|
||||||
impl fmt::Display for ExtXDateRange {
|
impl fmt::Display for ExtXDateRange {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", Self::PREFIX)?;
|
write!(f, "{}", Self::PREFIX)?;
|
||||||
write!(f, "ID={}", self.id)?;
|
write!(f, "ID={}", quote(&self.id))?;
|
||||||
if let Some(ref x) = self.class {
|
if let Some(ref x) = self.class {
|
||||||
write!(f, ",CLASS={}", x)?;
|
write!(f, ",CLASS={}", quote(x))?;
|
||||||
}
|
}
|
||||||
write!(f, ",START-DATE={}", self.start_date)?;
|
write!(f, ",START-DATE={}", quote(&self.start_date))?;
|
||||||
if let Some(ref x) = self.end_date {
|
if let Some(ref x) = self.end_date {
|
||||||
write!(f, ",END-DATE={}", x)?;
|
write!(f, ",END-DATE={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(x) = self.duration {
|
if let Some(x) = self.duration {
|
||||||
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
|
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
|
||||||
|
@ -58,13 +59,13 @@ impl fmt::Display for ExtXDateRange {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.scte35_cmd {
|
if let Some(ref x) = self.scte35_cmd {
|
||||||
write!(f, ",SCTE35-CMD={}", x)?;
|
write!(f, ",SCTE35-CMD={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.scte35_out {
|
if let Some(ref x) = self.scte35_out {
|
||||||
write!(f, ",SCTE35-OUT={}", x)?;
|
write!(f, ",SCTE35-OUT={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.scte35_in {
|
if let Some(ref x) = self.scte35_in {
|
||||||
write!(f, ",SCTE35-IN={}", x)?;
|
write!(f, ",SCTE35-IN={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if self.end_on_next {
|
if self.end_on_next {
|
||||||
write!(f, ",END-ON-NEXT=YES",)?;
|
write!(f, ",END-ON-NEXT=YES",)?;
|
||||||
|
@ -96,10 +97,10 @@ impl FromStr for ExtXDateRange {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"ID" => id = Some(track!(value.parse())?),
|
"ID" => id = Some(unquote(value)),
|
||||||
"CLASS" => class = Some(track!(value.parse())?),
|
"CLASS" => class = Some(unquote(value)),
|
||||||
"START-DATE" => start_date = Some(track!(value.parse())?),
|
"START-DATE" => start_date = Some(unquote(value)),
|
||||||
"END-DATE" => end_date = Some(track!(value.parse())?),
|
"END-DATE" => end_date = Some(unquote(value)),
|
||||||
"DURATION" => {
|
"DURATION" => {
|
||||||
let seconds: DecimalFloatingPoint = track!(value.parse())?;
|
let seconds: DecimalFloatingPoint = track!(value.parse())?;
|
||||||
duration = Some(seconds.to_duration());
|
duration = Some(seconds.to_duration());
|
||||||
|
@ -108,9 +109,9 @@ impl FromStr for ExtXDateRange {
|
||||||
let seconds: DecimalFloatingPoint = track!(value.parse())?;
|
let seconds: DecimalFloatingPoint = track!(value.parse())?;
|
||||||
planned_duration = Some(seconds.to_duration());
|
planned_duration = Some(seconds.to_duration());
|
||||||
}
|
}
|
||||||
"SCTE35-CMD" => scte35_cmd = Some(track!(value.parse())?),
|
"SCTE35-CMD" => scte35_cmd = Some(unquote(value)),
|
||||||
"SCTE35-OUT" => scte35_out = Some(track!(value.parse())?),
|
"SCTE35-OUT" => scte35_out = Some(unquote(value)),
|
||||||
"SCTE35-IN" => scte35_in = Some(track!(value.parse())?),
|
"SCTE35-IN" => scte35_in = Some(unquote(value)),
|
||||||
"END-ON-NEXT" => {
|
"END-ON-NEXT" => {
|
||||||
track_assert_eq!(value, "YES", ErrorKind::InvalidInput);
|
track_assert_eq!(value, "YES", ErrorKind::InvalidInput);
|
||||||
end_on_next = true;
|
end_on_next = true;
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl FromStr for ExtXKey {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::types::{EncryptionMethod, InitializationVector, QuotedString};
|
use crate::types::{EncryptionMethod, InitializationVector};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_key() {
|
fn ext_x_key() {
|
||||||
|
@ -89,7 +89,7 @@ mod test {
|
||||||
|
|
||||||
let tag = ExtXKey::new(DecryptionKey {
|
let tag = ExtXKey::new(DecryptionKey {
|
||||||
method: EncryptionMethod::Aes128,
|
method: EncryptionMethod::Aes128,
|
||||||
uri: QuotedString::new("foo").unwrap(),
|
uri: "foo".to_string(),
|
||||||
iv: None,
|
iv: None,
|
||||||
key_format: None,
|
key_format: None,
|
||||||
key_format_versions: None,
|
key_format_versions: None,
|
||||||
|
@ -101,7 +101,7 @@ mod test {
|
||||||
|
|
||||||
let tag = ExtXKey::new(DecryptionKey {
|
let tag = ExtXKey::new(DecryptionKey {
|
||||||
method: EncryptionMethod::Aes128,
|
method: EncryptionMethod::Aes128,
|
||||||
uri: QuotedString::new("foo").unwrap(),
|
uri: "foo".to_string(),
|
||||||
iv: Some(InitializationVector([
|
iv: Some(InitializationVector([
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
])),
|
])),
|
||||||
|
@ -115,11 +115,11 @@ mod test {
|
||||||
|
|
||||||
let tag = ExtXKey::new(DecryptionKey {
|
let tag = ExtXKey::new(DecryptionKey {
|
||||||
method: EncryptionMethod::Aes128,
|
method: EncryptionMethod::Aes128,
|
||||||
uri: QuotedString::new("foo").unwrap(),
|
uri: "foo".to_string(),
|
||||||
iv: Some(InitializationVector([
|
iv: Some(InitializationVector([
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
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: Some("baz".to_string()),
|
||||||
key_format_versions: None,
|
key_format_versions: None,
|
||||||
});
|
});
|
||||||
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#;
|
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{ByteRange, ProtocolVersion, QuotedString};
|
use crate::types::{ByteRange, ProtocolVersion};
|
||||||
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -9,7 +10,7 @@ use std::str::FromStr;
|
||||||
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
|
/// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExtXMap {
|
pub struct ExtXMap {
|
||||||
uri: QuotedString,
|
uri: String,
|
||||||
range: Option<ByteRange>,
|
range: Option<ByteRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,20 +18,23 @@ impl ExtXMap {
|
||||||
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
|
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
|
||||||
|
|
||||||
/// Makes a new `ExtXMap` tag.
|
/// Makes a new `ExtXMap` tag.
|
||||||
pub fn new(uri: QuotedString) -> Self {
|
pub fn new<T: ToString>(uri: T) -> Self {
|
||||||
ExtXMap { uri, range: None }
|
ExtXMap {
|
||||||
|
uri: uri.to_string(),
|
||||||
|
range: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes a new `ExtXMap` tag with the given range.
|
/// Makes a new `ExtXMap` tag with the given range.
|
||||||
pub fn with_range(uri: QuotedString, range: ByteRange) -> Self {
|
pub fn with_range<T: ToString>(uri: T, range: ByteRange) -> Self {
|
||||||
ExtXMap {
|
ExtXMap {
|
||||||
uri,
|
uri: uri.to_string(),
|
||||||
range: Some(range),
|
range: Some(range),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the URI that identifies a resource that contains the media initialization section.
|
/// Returns the URI that identifies a resource that contains the media initialization section.
|
||||||
pub fn uri(&self) -> &QuotedString {
|
pub fn uri(&self) -> &String {
|
||||||
&self.uri
|
&self.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,9 +52,9 @@ impl ExtXMap {
|
||||||
impl fmt::Display for ExtXMap {
|
impl fmt::Display for ExtXMap {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", Self::PREFIX)?;
|
write!(f, "{}", Self::PREFIX)?;
|
||||||
write!(f, "URI={}", self.uri)?;
|
write!(f, "URI={}", quote(&self.uri))?;
|
||||||
if let Some(ref x) = self.range {
|
if let Some(ref x) = self.range {
|
||||||
write!(f, ",BYTERANGE=\"{}\"", x)?;
|
write!(f, ",BYTERANGE={}", quote(x))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -67,10 +71,9 @@ impl FromStr for ExtXMap {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"URI" => uri = Some(track!(value.parse())?),
|
"URI" => uri = Some(unquote(value)),
|
||||||
"BYTERANGE" => {
|
"BYTERANGE" => {
|
||||||
let s: QuotedString = track!(value.parse())?;
|
range = Some(track!(unquote(value).parse())?);
|
||||||
range = Some(track!(s.parse())?);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
|
@ -90,14 +93,14 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ext_x_map() {
|
fn ext_x_map() {
|
||||||
let tag = ExtXMap::new(QuotedString::new("foo").unwrap());
|
let tag = ExtXMap::new("foo");
|
||||||
let text = r#"#EXT-X-MAP:URI="foo""#;
|
let text = r#"#EXT-X-MAP:URI="foo""#;
|
||||||
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
assert_eq!(text.parse().ok(), Some(tag.clone()));
|
||||||
assert_eq!(tag.to_string(), text);
|
assert_eq!(tag.to_string(), text);
|
||||||
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
|
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
|
||||||
|
|
||||||
let tag = ExtXMap::with_range(
|
let tag = ExtXMap::with_range(
|
||||||
QuotedString::new("foo").unwrap(),
|
"foo",
|
||||||
ByteRange {
|
ByteRange {
|
||||||
length: 9,
|
length: 9,
|
||||||
start: Some(2),
|
start: Some(2),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::types::QuotedString;
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
|
@ -11,14 +11,14 @@ use std::str::{self, FromStr};
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum ClosedCaptions {
|
pub enum ClosedCaptions {
|
||||||
GroupId(QuotedString),
|
GroupId(String),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ClosedCaptions {
|
impl fmt::Display for ClosedCaptions {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
ClosedCaptions::GroupId(ref x) => x.fmt(f),
|
ClosedCaptions::GroupId(ref x) => write!(f, "{}", quote(x)),
|
||||||
ClosedCaptions::None => "NONE".fmt(f),
|
ClosedCaptions::None => "NONE".fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ impl FromStr for ClosedCaptions {
|
||||||
if s == "NONE" {
|
if s == "NONE" {
|
||||||
Ok(ClosedCaptions::None)
|
Ok(ClosedCaptions::None)
|
||||||
} else {
|
} else {
|
||||||
Ok(ClosedCaptions::GroupId(track!(s.parse())?))
|
Ok(ClosedCaptions::GroupId(unquote(s)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ mod tests {
|
||||||
let closed_captions = ClosedCaptions::None;
|
let closed_captions = ClosedCaptions::None;
|
||||||
assert_eq!(closed_captions.to_string(), "NONE".to_string());
|
assert_eq!(closed_captions.to_string(), "NONE".to_string());
|
||||||
|
|
||||||
let closed_captions = ClosedCaptions::GroupId(QuotedString::new("value").unwrap());
|
let closed_captions = ClosedCaptions::GroupId("value".into());
|
||||||
assert_eq!(closed_captions.to_string(), "\"value\"".to_string());
|
assert_eq!(closed_captions.to_string(), "\"value\"".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ mod tests {
|
||||||
let closed_captions = ClosedCaptions::None;
|
let closed_captions = ClosedCaptions::None;
|
||||||
assert_eq!(closed_captions, "NONE".parse::<ClosedCaptions>().unwrap());
|
assert_eq!(closed_captions, "NONE".parse::<ClosedCaptions>().unwrap());
|
||||||
|
|
||||||
let closed_captions = ClosedCaptions::GroupId(QuotedString::new("value").unwrap());
|
let closed_captions = ClosedCaptions::GroupId("value".into());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
closed_captions,
|
closed_captions,
|
||||||
"\"value\"".parse::<ClosedCaptions>().unwrap()
|
"\"value\"".parse::<ClosedCaptions>().unwrap()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::attribute::AttributePairs;
|
use crate::attribute::AttributePairs;
|
||||||
use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion, QuotedString};
|
use crate::types::{EncryptionMethod, InitializationVector, ProtocolVersion};
|
||||||
|
use crate::utils::{quote, unquote};
|
||||||
use crate::{Error, ErrorKind, Result};
|
use crate::{Error, ErrorKind, Result};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
|
@ -13,10 +14,10 @@ use std::str::{self, FromStr};
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct DecryptionKey {
|
pub struct DecryptionKey {
|
||||||
pub method: EncryptionMethod,
|
pub method: EncryptionMethod,
|
||||||
pub uri: QuotedString,
|
pub uri: String,
|
||||||
pub iv: Option<InitializationVector>,
|
pub iv: Option<InitializationVector>,
|
||||||
pub key_format: Option<QuotedString>,
|
pub key_format: Option<String>,
|
||||||
pub key_format_versions: Option<QuotedString>,
|
pub key_format_versions: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DecryptionKey {
|
impl DecryptionKey {
|
||||||
|
@ -34,15 +35,15 @@ impl DecryptionKey {
|
||||||
impl fmt::Display for DecryptionKey {
|
impl fmt::Display for DecryptionKey {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "METHOD={}", self.method)?;
|
write!(f, "METHOD={}", self.method)?;
|
||||||
write!(f, ",URI={}", self.uri)?;
|
write!(f, ",URI={}", quote(&self.uri))?;
|
||||||
if let Some(ref x) = self.iv {
|
if let Some(ref x) = self.iv {
|
||||||
write!(f, ",IV={}", x)?;
|
write!(f, ",IV={}", x)?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.key_format {
|
if let Some(ref x) = self.key_format {
|
||||||
write!(f, ",KEYFORMAT={}", x)?;
|
write!(f, ",KEYFORMAT={}", quote(x))?;
|
||||||
}
|
}
|
||||||
if let Some(ref x) = self.key_format_versions {
|
if let Some(ref x) = self.key_format_versions {
|
||||||
write!(f, ",KEYFORMATVERSIONS={}", x)?;
|
write!(f, ",KEYFORMATVERSIONS={}", quote(x))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -61,10 +62,10 @@ impl FromStr for DecryptionKey {
|
||||||
let (key, value) = track!(attr)?;
|
let (key, value) = track!(attr)?;
|
||||||
match key {
|
match key {
|
||||||
"METHOD" => method = Some(track!(value.parse())?),
|
"METHOD" => method = Some(track!(value.parse())?),
|
||||||
"URI" => uri = Some(track!(value.parse())?),
|
"URI" => uri = Some(unquote(value)),
|
||||||
"IV" => iv = Some(track!(value.parse())?),
|
"IV" => iv = Some(track!(value.parse())?),
|
||||||
"KEYFORMAT" => key_format = Some(track!(value.parse())?),
|
"KEYFORMAT" => key_format = Some(unquote(value)),
|
||||||
"KEYFORMATVERSIONS" => key_format_versions = Some(track!(value.parse())?),
|
"KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)),
|
||||||
_ => {
|
_ => {
|
||||||
// [6.3.1. General Client Responsibilities]
|
// [6.3.1. General Client Responsibilities]
|
||||||
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
// > ignore any attribute/value pair with an unrecognized AttributeName.
|
||||||
|
|
|
@ -12,7 +12,6 @@ mod initialization_vector;
|
||||||
mod media_type;
|
mod media_type;
|
||||||
mod playlist_type;
|
mod playlist_type;
|
||||||
mod protocol_version;
|
mod protocol_version;
|
||||||
mod quoted_string;
|
|
||||||
mod session_data;
|
mod session_data;
|
||||||
mod signed_decimal_floating_point;
|
mod signed_decimal_floating_point;
|
||||||
mod single_line_string;
|
mod single_line_string;
|
||||||
|
@ -30,7 +29,6 @@ pub use initialization_vector::*;
|
||||||
pub use media_type::*;
|
pub use media_type::*;
|
||||||
pub use playlist_type::*;
|
pub use playlist_type::*;
|
||||||
pub use protocol_version::*;
|
pub use protocol_version::*;
|
||||||
pub use quoted_string::*;
|
|
||||||
pub use session_data::*;
|
pub use session_data::*;
|
||||||
pub use signed_decimal_floating_point::*;
|
pub use signed_decimal_floating_point::*;
|
||||||
pub use single_line_string::*;
|
pub use single_line_string::*;
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
use crate::{Error, ErrorKind, Result};
|
|
||||||
use std::fmt;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::str::{self, FromStr};
|
|
||||||
|
|
||||||
/// Quoted string.
|
|
||||||
///
|
|
||||||
/// See: [4.2. Attribute Lists]
|
|
||||||
///
|
|
||||||
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct QuotedString(String);
|
|
||||||
|
|
||||||
impl QuotedString {
|
|
||||||
/// Makes a new `QuotedString` instance.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// If the given string contains any control characters or double-quote character,
|
|
||||||
/// this function will return an error which has the kind `ErrorKind::InvalidInput`.
|
|
||||||
pub fn new<T: Into<String>>(s: T) -> Result<Self> {
|
|
||||||
let s = s.into();
|
|
||||||
track_assert!(
|
|
||||||
!s.chars().any(|c| c.is_control() || c == '"'),
|
|
||||||
ErrorKind::InvalidInput
|
|
||||||
);
|
|
||||||
Ok(QuotedString(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for QuotedString {
|
|
||||||
type Target = str;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for QuotedString {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for QuotedString {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{:?}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for QuotedString {
|
|
||||||
type Err = Error;
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
let len = s.len();
|
|
||||||
let bytes = s.as_bytes();
|
|
||||||
track_assert!(len >= 2, ErrorKind::InvalidInput);
|
|
||||||
track_assert_eq!(bytes[0], b'"', ErrorKind::InvalidInput);
|
|
||||||
track_assert_eq!(bytes[len - 1], b'"', ErrorKind::InvalidInput);
|
|
||||||
|
|
||||||
let s = unsafe { str::from_utf8_unchecked(&bytes[1..len - 1]) };
|
|
||||||
track!(QuotedString::new(s))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
use crate::types::QuotedString;
|
|
||||||
|
|
||||||
/// Session data.
|
/// Session data.
|
||||||
///
|
///
|
||||||
/// See: [4.3.4.4. EXT-X-SESSION-DATA]
|
/// See: [4.3.4.4. EXT-X-SESSION-DATA]
|
||||||
|
@ -8,6 +6,6 @@ use crate::types::QuotedString;
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum SessionData {
|
pub enum SessionData {
|
||||||
Value(QuotedString),
|
Value(String),
|
||||||
Uri(QuotedString),
|
Uri(String),
|
||||||
}
|
}
|
||||||
|
|
72
src/utils.rs
72
src/utils.rs
|
@ -1,15 +1,77 @@
|
||||||
use crate::{ErrorKind, Result};
|
use crate::{ErrorKind, Result};
|
||||||
use trackable::error::ErrorKindExt;
|
use trackable::error::ErrorKindExt;
|
||||||
|
|
||||||
pub fn parse_yes_or_no(s: &str) -> Result<bool> {
|
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> Result<bool> {
|
||||||
match s {
|
match s.as_ref() {
|
||||||
"YES" => Ok(true),
|
"YES" => Ok(true),
|
||||||
"NO" => Ok(false),
|
"NO" => Ok(false),
|
||||||
_ => track_panic!(ErrorKind::InvalidInput, "Unexpected value: {:?}", s),
|
_ => track_panic!(
|
||||||
|
ErrorKind::InvalidInput,
|
||||||
|
"Unexpected value: {:?}",
|
||||||
|
s.as_ref()
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_u64(s: &str) -> Result<u64> {
|
pub(crate) fn parse_u64<T: AsRef<str>>(s: T) -> Result<u64> {
|
||||||
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
let n = track!(s
|
||||||
|
.as_ref()
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
|
||||||
Ok(n)
|
Ok(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// According to the documentation the following characters are forbidden
|
||||||
|
/// inside a quoted string:
|
||||||
|
/// - carriage return (`\r`)
|
||||||
|
/// - new line (`\n`)
|
||||||
|
/// - double quotes (`"`)
|
||||||
|
///
|
||||||
|
/// Therefore it is safe to simply remove any occurence of those characters.
|
||||||
|
/// [rfc8216#section-4.2](https://tools.ietf.org/html/rfc8216#section-4.2)
|
||||||
|
pub(crate) fn unquote<T: ToString>(value: T) -> String {
|
||||||
|
value
|
||||||
|
.to_string()
|
||||||
|
.replace("\"", "")
|
||||||
|
.replace("\n", "")
|
||||||
|
.replace("\r", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts a string inside quotes.
|
||||||
|
pub(crate) fn quote<T: ToString>(value: T) -> String {
|
||||||
|
// the replace is for the case, that quote is called on an already quoted string, which could
|
||||||
|
// cause problems!
|
||||||
|
format!("\"{}\"", value.to_string().replace("\"", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_yes_or_no() {
|
||||||
|
assert!(parse_yes_or_no("YES").unwrap());
|
||||||
|
assert!(!parse_yes_or_no("NO").unwrap());
|
||||||
|
// TODO: test for error
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_u64() {
|
||||||
|
assert_eq!(parse_u64("1").unwrap(), 1);
|
||||||
|
assert_eq!(parse_u64("25").unwrap(), 25);
|
||||||
|
// TODO: test for error
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unquote() {
|
||||||
|
assert_eq!(unquote("\"TestValue\""), "TestValue".to_string());
|
||||||
|
assert_eq!(unquote("\"TestValue\n\""), "TestValue".to_string());
|
||||||
|
assert_eq!(unquote("\"TestValue\n\r\""), "TestValue".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quote() {
|
||||||
|
assert_eq!(quote("value"), "\"value\"".to_string());
|
||||||
|
assert_eq!(quote("\"value\""), "\"value\"".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue