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

removed QuotedString

This commit is contained in:
Luro02 2019-09-08 11:30:52 +02:00
parent 861e7e4b74
commit 1966a7608d
15 changed files with 256 additions and 272 deletions

View file

@ -3,7 +3,7 @@ use crate::tags::{
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, MasterPlaylistTag,
};
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion, QuotedString};
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::collections::HashSet;
use std::fmt;
@ -22,6 +22,7 @@ pub struct MasterPlaylistBuilder {
session_data_tags: Vec<ExtXSessionData>,
session_key_tags: Vec<ExtXSessionKey>,
}
impl MasterPlaylistBuilder {
/// Makes a new `MasterPlaylistBuilder` instance.
pub fn new() -> Self {
@ -209,12 +210,13 @@ impl MasterPlaylistBuilder {
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
.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 {
fn default() -> Self {
Self::new()
@ -233,6 +235,7 @@ pub struct MasterPlaylist {
session_data_tags: Vec<ExtXSessionData>,
session_key_tags: Vec<ExtXSessionKey>,
}
impl MasterPlaylist {
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
pub fn version_tag(&self) -> ExtXVersion {
@ -274,6 +277,7 @@ impl MasterPlaylist {
&self.session_key_tags
}
}
impl fmt::Display for MasterPlaylist {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "{}", ExtM3u)?;

View file

@ -1,6 +1,7 @@
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::{quote, unquote};
use crate::{Error, ErrorKind, Result};
use std::fmt;
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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXIFrameStreamInf {
uri: QuotedString,
uri: String,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
codecs: Option<String>,
resolution: Option<DecimalResolution>,
hdcp_level: Option<HdcpLevel>,
video: Option<QuotedString>,
video: Option<String>,
}
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 {
pub fn new<T: ToString>(uri: T, bandwidth: u64) -> Self {
ExtXIFrameStreamInf {
uri,
uri: uri.to_string(),
bandwidth,
average_bandwidth: None,
codecs: None,
@ -36,7 +37,7 @@ impl ExtXIFrameStreamInf {
}
/// Returns the URI that identifies the associated media playlist.
pub fn uri(&self) -> &QuotedString {
pub fn uri(&self) -> &String {
&self.uri
}
@ -51,7 +52,7 @@ impl ExtXIFrameStreamInf {
}
/// 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()
}
@ -66,7 +67,7 @@ impl ExtXIFrameStreamInf {
}
/// 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()
}
@ -79,13 +80,14 @@ impl ExtXIFrameStreamInf {
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, "URI={}", quote(&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)?;
write!(f, ",CODECS={}", quote(x))?;
}
if let Some(ref x) = self.resolution {
write!(f, ",RESOLUTION={}", x)?;
@ -94,7 +96,7 @@ impl fmt::Display for ExtXIFrameStreamInf {
write!(f, ",HDCP-LEVEL={}", x)?;
}
if let Some(ref x) = self.video {
write!(f, ",VIDEO={}", x)?;
write!(f, ",VIDEO={}", quote(x))?;
}
Ok(())
}
@ -116,13 +118,13 @@ impl FromStr for ExtXIFrameStreamInf {
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"URI" => uri = Some(track!(value.parse())?),
"URI" => uri = Some(unquote(value)),
"BANDWIDTH" => 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())?),
"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]
// > ignore any attribute/value pair with an unrecognized AttributeName.
@ -150,14 +152,10 @@ mod test {
#[test]
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"#;
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

@ -1,6 +1,6 @@
use crate::attribute::AttributePairs;
use crate::types::{InStreamId, MediaType, ProtocolVersion, QuotedString};
use crate::utils::parse_yes_or_no;
use crate::types::{InStreamId, MediaType, ProtocolVersion};
use crate::utils::{parse_yes_or_no, quote, unquote};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
@ -9,17 +9,17 @@ use std::str::FromStr;
#[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>,
uri: Option<String>,
group_id: Option<String>,
language: Option<String>,
assoc_language: Option<String>,
name: Option<String>,
default: bool,
autoselect: Option<bool>,
forced: Option<bool>,
instream_id: Option<InStreamId>,
characteristics: Option<QuotedString>,
channels: Option<QuotedString>,
characteristics: Option<String>,
channels: Option<String>,
}
impl ExtXMediaBuilder {
@ -48,32 +48,32 @@ impl ExtXMediaBuilder {
}
/// 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);
pub fn group_id<T: ToString>(&mut self, group_id: T) -> &mut Self {
self.group_id = Some(group_id.to_string());
self
}
/// Sets a human-readable description of the rendition.
pub fn name(&mut self, name: QuotedString) -> &mut Self {
self.name = Some(name);
pub fn name<T: ToString>(&mut self, name: T) -> &mut Self {
self.name = Some(name.to_string());
self
}
/// Sets the URI that identifies the media playlist.
pub fn uri(&mut self, uri: QuotedString) -> &mut Self {
self.uri = Some(uri);
pub fn uri<T: ToString>(&mut self, uri: T) -> &mut Self {
self.uri = Some(uri.to_string());
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);
pub fn language<T: ToString>(&mut self, language: T) -> &mut Self {
self.language = Some(language.to_string());
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);
pub fn assoc_language<T: ToString>(&mut self, language: T) -> &mut Self {
self.assoc_language = Some(language.to_string());
self
}
@ -102,14 +102,14 @@ impl ExtXMediaBuilder {
}
/// Sets the string that represents uniform type identifiers (UTI).
pub fn characteristics(&mut self, characteristics: QuotedString) -> &mut Self {
self.characteristics = Some(characteristics);
pub fn characteristics<T: ToString>(&mut self, characteristics: T) -> &mut Self {
self.characteristics = Some(characteristics.to_string());
self
}
/// Sets the string that represents the parameters of the rendition.
pub fn channels(&mut self, channels: QuotedString) -> &mut Self {
self.channels = Some(channels);
pub fn channels<T: ToString>(&mut self, channels: T) -> &mut Self {
self.channels = Some(channels.to_string());
self
}
@ -159,31 +159,31 @@ impl Default for ExtXMediaBuilder {
#[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,
uri: Option<String>,
group_id: String,
language: Option<String>,
assoc_language: Option<String>,
name: String,
default: bool,
autoselect: bool,
forced: bool,
instream_id: Option<InStreamId>,
characteristics: Option<QuotedString>,
channels: Option<QuotedString>,
characteristics: Option<String>,
channels: Option<String>,
}
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 {
pub fn new<T: ToString>(media_type: MediaType, group_id: T, name: T) -> Self {
ExtXMedia {
media_type,
uri: None,
group_id,
group_id: group_id.to_string(),
language: None,
assoc_language: None,
name,
name: name.to_string(),
default: false,
autoselect: false,
forced: false,
@ -199,27 +199,27 @@ impl ExtXMedia {
}
/// 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
}
/// Returns a human-readable description of the rendition.
pub fn name(&self) -> &QuotedString {
pub fn name(&self) -> &String {
&self.name
}
/// Returns the URI that identifies the media playlist.
pub fn uri(&self) -> Option<&QuotedString> {
pub fn uri(&self) -> Option<&String> {
self.uri.as_ref()
}
/// 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()
}
/// 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()
}
@ -247,12 +247,12 @@ impl ExtXMedia {
/// Returns a string that represents uniform type identifiers (UTI).
///
/// 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()
}
/// 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()
}
@ -274,16 +274,16 @@ impl fmt::Display for ExtXMedia {
write!(f, "{}", Self::PREFIX)?;
write!(f, "TYPE={}", self.media_type)?;
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 {
write!(f, ",LANGUAGE={}", x)?;
write!(f, ",LANGUAGE={}", quote(x))?;
}
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 {
write!(f, ",DEFAULT=YES")?;
}
@ -294,13 +294,13 @@ impl fmt::Display for ExtXMedia {
write!(f, ",FORCED=YES")?;
}
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 {
write!(f, ",CHARACTERISTICS={}", x)?;
write!(f, ",CHARACTERISTICS={}", quote(x))?;
}
if let Some(ref x) = self.channels {
write!(f, ",CHANNELS={}", x)?;
write!(f, ",CHANNELS={}", quote(x))?;
}
Ok(())
}
@ -320,19 +320,19 @@ impl FromStr for ExtXMedia {
builder.media_type(track!(value.parse())?);
}
"URI" => {
builder.uri(track!(value.parse())?);
builder.uri(unquote(value));
}
"GROUP-ID" => {
builder.group_id(track!(value.parse())?);
builder.group_id(unquote(value));
}
"LANGUAGE" => {
builder.language(track!(value.parse())?);
builder.language(unquote(value));
}
"ASSOC-LANGUAGE" => {
builder.assoc_language(track!(value.parse())?);
builder.assoc_language(unquote(value));
}
"NAME" => {
builder.name(track!(value.parse())?);
builder.name(unquote(value));
}
"DEFAULT" => {
builder.default(track!(parse_yes_or_no(value))?);
@ -344,14 +344,13 @@ impl FromStr for ExtXMedia {
builder.forced(track!(parse_yes_or_no(value))?);
}
"INSTREAM-ID" => {
let s: QuotedString = track!(value.parse())?;
builder.instream_id(track!(s.parse())?);
builder.instream_id(unquote(value).parse()?);
}
"CHARACTERISTICS" => {
builder.characteristics(track!(value.parse())?);
builder.characteristics(unquote(value));
}
"CHANNELS" => {
builder.channels(track!(value.parse())?);
builder.channels(unquote(value));
}
_ => {
// [6.3.1. General Client Responsibilities]
@ -369,14 +368,10 @@ mod test {
#[test]
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""#;
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

@ -1,5 +1,6 @@
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 std::fmt;
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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionData {
data_id: QuotedString,
data_id: String,
data: SessionData,
language: Option<QuotedString>,
language: Option<String>,
}
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 {
pub fn new<T: ToString>(data_id: T, data: SessionData) -> Self {
ExtXSessionData {
data_id,
data_id: data_id.to_string(),
data,
language: None,
}
}
/// 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 {
data_id,
data_id: data_id.to_string(),
data,
language: Some(language),
language: Some(language.to_string()),
}
}
/// Returns the identifier of the data.
pub fn data_id(&self) -> &QuotedString {
pub fn data_id(&self) -> &String {
&self.data_id
}
@ -46,7 +47,7 @@ impl ExtXSessionData {
}
/// Returns the language of the data.
pub fn language(&self) -> Option<&QuotedString> {
pub fn language(&self) -> Option<&String> {
self.language.as_ref()
}
@ -59,13 +60,13 @@ impl ExtXSessionData {
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)?;
write!(f, "DATA-ID={}", quote(&self.data_id))?;
match self.data {
SessionData::Value(ref x) => write!(f, ",VALUE={}", x)?,
SessionData::Uri(ref x) => write!(f, ",URI={}", x)?,
SessionData::Value(ref x) => write!(f, ",VALUE={}", quote(x))?,
SessionData::Uri(ref x) => write!(f, ",URI={}", quote(x))?,
}
if let Some(ref x) = self.language {
write!(f, ",LANGUAGE={}", x)?;
write!(f, ",LANGUAGE={}", quote(x))?;
}
Ok(())
}
@ -84,10 +85,10 @@ impl FromStr for ExtXSessionData {
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())?),
"DATA-ID" => data_id = Some(unquote(value)),
"VALUE" => session_value = Some(unquote(value)),
"URI" => uri = Some(unquote(value)),
"LANGUAGE" => language = Some(unquote(value)),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
@ -118,34 +119,22 @@ mod test {
#[test]
fn ext_x_session_data() {
let tag = ExtXSessionData::new(
quoted_string("foo"),
SessionData::Value(quoted_string("bar")),
);
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
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 tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into()));
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 tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "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

@ -38,6 +38,7 @@ impl fmt::Display for ExtXSessionKey {
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;
@ -49,13 +50,13 @@ impl FromStr for ExtXSessionKey {
#[cfg(test)]
mod test {
use super::*;
use crate::types::{EncryptionMethod, InitializationVector, QuotedString};
use crate::types::{EncryptionMethod, InitializationVector};
#[test]
fn ext_x_session_key() {
let tag = ExtXSessionKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: quoted_string("foo"),
uri: "foo".to_string(),
iv: Some(InitializationVector([
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.requires_version(), ProtocolVersion::V2);
}
fn quoted_string(s: &str) -> QuotedString {
QuotedString::new(s).unwrap()
}
}

View file

@ -1,9 +1,9 @@
use crate::attribute::AttributePairs;
use crate::types::{
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 std::fmt;
use std::str::FromStr;
@ -16,13 +16,13 @@ pub struct ExtXStreamInf {
uri: SingleLineString,
bandwidth: u64,
average_bandwidth: Option<u64>,
codecs: Option<QuotedString>,
codecs: Option<String>,
resolution: Option<DecimalResolution>,
frame_rate: Option<DecimalFloatingPoint>,
hdcp_level: Option<HdcpLevel>,
audio: Option<QuotedString>,
video: Option<QuotedString>,
subtitles: Option<QuotedString>,
audio: Option<String>,
video: Option<String>,
subtitles: Option<String>,
closed_captions: Option<ClosedCaptions>,
}
@ -62,7 +62,7 @@ impl ExtXStreamInf {
}
/// 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()
}
@ -82,17 +82,17 @@ impl ExtXStreamInf {
}
/// 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()
}
/// 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()
}
/// 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()
}
@ -115,7 +115,7 @@ impl fmt::Display for ExtXStreamInf {
write!(f, ",AVERAGE-BANDWIDTH={}", x)?;
}
if let Some(ref x) = self.codecs {
write!(f, ",CODECS={}", x)?;
write!(f, ",CODECS={}", quote(x))?;
}
if let Some(ref x) = self.resolution {
write!(f, ",RESOLUTION={}", x)?;
@ -127,13 +127,13 @@ impl fmt::Display for ExtXStreamInf {
write!(f, ",HDCP-LEVEL={}", x)?;
}
if let Some(ref x) = self.audio {
write!(f, ",AUDIO={}", x)?;
write!(f, ",AUDIO={}", quote(x))?;
}
if let Some(ref x) = self.video {
write!(f, ",VIDEO={}", x)?;
write!(f, ",VIDEO={}", quote(x))?;
}
if let Some(ref x) = self.subtitles {
write!(f, ",SUBTITLES={}", x)?;
write!(f, ",SUBTITLES={}", quote(x))?;
}
if let Some(ref x) = self.closed_captions {
write!(f, ",CLOSED-CAPTIONS={}", x)?;
@ -171,13 +171,13 @@ impl FromStr for ExtXStreamInf {
match key {
"BANDWIDTH" => 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())?),
"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())?),
"AUDIO" => audio = Some(unquote(value)),
"VIDEO" => video = Some(unquote(value)),
"SUBTITLES" => subtitles = Some(unquote(value)),
"CLOSED-CAPTIONS" => closed_captions = Some(track!(value.parse())?),
_ => {
// [6.3.1. General Client Responsibilities]

View file

@ -1,5 +1,6 @@
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 std::collections::BTreeMap;
use std::fmt;
@ -14,15 +15,15 @@ use std::time::Duration;
#[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 id: String,
pub class: Option<String>,
pub start_date: String,
pub end_date: Option<String>,
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 scte35_cmd: Option<String>,
pub scte35_out: Option<String>,
pub scte35_in: Option<String>,
pub end_on_next: bool,
pub client_attributes: BTreeMap<String, String>,
}
@ -39,13 +40,13 @@ impl ExtXDateRange {
impl fmt::Display for ExtXDateRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "ID={}", self.id)?;
write!(f, "ID={}", quote(&self.id))?;
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 {
write!(f, ",END-DATE={}", x)?;
write!(f, ",END-DATE={}", quote(x))?;
}
if let Some(x) = self.duration {
write!(f, ",DURATION={}", DecimalFloatingPoint::from_duration(x))?;
@ -58,13 +59,13 @@ impl fmt::Display for ExtXDateRange {
)?;
}
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 {
write!(f, ",SCTE35-OUT={}", x)?;
write!(f, ",SCTE35-OUT={}", quote(x))?;
}
if let Some(ref x) = self.scte35_in {
write!(f, ",SCTE35-IN={}", x)?;
write!(f, ",SCTE35-IN={}", quote(x))?;
}
if self.end_on_next {
write!(f, ",END-ON-NEXT=YES",)?;
@ -96,10 +97,10 @@ impl FromStr for ExtXDateRange {
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())?),
"ID" => id = Some(unquote(value)),
"CLASS" => class = Some(unquote(value)),
"START-DATE" => start_date = Some(unquote(value)),
"END-DATE" => end_date = Some(unquote(value)),
"DURATION" => {
let seconds: DecimalFloatingPoint = track!(value.parse())?;
duration = Some(seconds.to_duration());
@ -108,9 +109,9 @@ impl FromStr for ExtXDateRange {
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())?),
"SCTE35-CMD" => scte35_cmd = Some(unquote(value)),
"SCTE35-OUT" => scte35_out = Some(unquote(value)),
"SCTE35-IN" => scte35_in = Some(unquote(value)),
"END-ON-NEXT" => {
track_assert_eq!(value, "YES", ErrorKind::InvalidInput);
end_on_next = true;

View file

@ -77,7 +77,7 @@ impl FromStr for ExtXKey {
#[cfg(test)]
mod test {
use super::*;
use crate::types::{EncryptionMethod, InitializationVector, QuotedString};
use crate::types::{EncryptionMethod, InitializationVector};
#[test]
fn ext_x_key() {
@ -89,7 +89,7 @@ mod test {
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
uri: "foo".to_string(),
iv: None,
key_format: None,
key_format_versions: None,
@ -101,7 +101,7 @@ mod test {
let tag = ExtXKey::new(DecryptionKey {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
uri: "foo".to_string(),
iv: Some(InitializationVector([
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 {
method: EncryptionMethod::Aes128,
uri: QuotedString::new("foo").unwrap(),
uri: "foo".to_string(),
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: Some("baz".to_string()),
key_format_versions: None,
});
let text = r#"#EXT-X-KEY:METHOD=AES-128,URI="foo",IV=0x000102030405060708090a0b0c0d0e0f,KEYFORMAT="baz""#;

View file

@ -1,5 +1,6 @@
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 std::fmt;
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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXMap {
uri: QuotedString,
uri: String,
range: Option<ByteRange>,
}
@ -17,20 +18,23 @@ 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 }
pub fn new<T: ToString>(uri: T) -> Self {
ExtXMap {
uri: uri.to_string(),
range: None,
}
}
/// 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 {
uri,
uri: uri.to_string(),
range: Some(range),
}
}
/// 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
}
@ -48,9 +52,9 @@ impl ExtXMap {
impl fmt::Display for ExtXMap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "URI={}", self.uri)?;
write!(f, "URI={}", quote(&self.uri))?;
if let Some(ref x) = self.range {
write!(f, ",BYTERANGE=\"{}\"", x)?;
write!(f, ",BYTERANGE={}", quote(x))?;
}
Ok(())
}
@ -67,10 +71,9 @@ impl FromStr for ExtXMap {
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"URI" => uri = Some(track!(value.parse())?),
"URI" => uri = Some(unquote(value)),
"BYTERANGE" => {
let s: QuotedString = track!(value.parse())?;
range = Some(track!(s.parse())?);
range = Some(track!(unquote(value).parse())?);
}
_ => {
// [6.3.1. General Client Responsibilities]
@ -90,14 +93,14 @@ mod test {
#[test]
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""#;
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(),
"foo",
ByteRange {
length: 9,
start: Some(2),

View file

@ -1,4 +1,4 @@
use crate::types::QuotedString;
use crate::utils::{quote, unquote};
use crate::{Error, Result};
use std::fmt;
use std::str::{self, FromStr};
@ -11,14 +11,14 @@ use std::str::{self, FromStr};
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ClosedCaptions {
GroupId(QuotedString),
GroupId(String),
None,
}
impl fmt::Display for ClosedCaptions {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ClosedCaptions::GroupId(ref x) => x.fmt(f),
ClosedCaptions::GroupId(ref x) => write!(f, "{}", quote(x)),
ClosedCaptions::None => "NONE".fmt(f),
}
}
@ -30,7 +30,7 @@ impl FromStr for ClosedCaptions {
if s == "NONE" {
Ok(ClosedCaptions::None)
} else {
Ok(ClosedCaptions::GroupId(track!(s.parse())?))
Ok(ClosedCaptions::GroupId(unquote(s)))
}
}
}
@ -44,7 +44,7 @@ mod tests {
let closed_captions = ClosedCaptions::None;
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());
}
@ -53,7 +53,7 @@ mod tests {
let closed_captions = ClosedCaptions::None;
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!(
closed_captions,
"\"value\"".parse::<ClosedCaptions>().unwrap()

View file

@ -1,5 +1,6 @@
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 std::fmt;
use std::str::{self, FromStr};
@ -13,10 +14,10 @@ use std::str::{self, FromStr};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DecryptionKey {
pub method: EncryptionMethod,
pub uri: QuotedString,
pub uri: String,
pub iv: Option<InitializationVector>,
pub key_format: Option<QuotedString>,
pub key_format_versions: Option<QuotedString>,
pub key_format: Option<String>,
pub key_format_versions: Option<String>,
}
impl DecryptionKey {
@ -34,15 +35,15 @@ impl DecryptionKey {
impl fmt::Display for DecryptionKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "METHOD={}", self.method)?;
write!(f, ",URI={}", self.uri)?;
write!(f, ",URI={}", quote(&self.uri))?;
if let Some(ref x) = self.iv {
write!(f, ",IV={}", x)?;
}
if let Some(ref x) = self.key_format {
write!(f, ",KEYFORMAT={}", x)?;
write!(f, ",KEYFORMAT={}", quote(x))?;
}
if let Some(ref x) = self.key_format_versions {
write!(f, ",KEYFORMATVERSIONS={}", x)?;
write!(f, ",KEYFORMATVERSIONS={}", quote(x))?;
}
Ok(())
}
@ -61,10 +62,10 @@ impl FromStr for DecryptionKey {
let (key, value) = track!(attr)?;
match key {
"METHOD" => method = Some(track!(value.parse())?),
"URI" => uri = Some(track!(value.parse())?),
"URI" => uri = Some(unquote(value)),
"IV" => iv = Some(track!(value.parse())?),
"KEYFORMAT" => key_format = Some(track!(value.parse())?),
"KEYFORMATVERSIONS" => key_format_versions = Some(track!(value.parse())?),
"KEYFORMAT" => key_format = Some(unquote(value)),
"KEYFORMATVERSIONS" => key_format_versions = Some(unquote(value)),
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.

View file

@ -12,7 +12,6 @@ mod initialization_vector;
mod media_type;
mod playlist_type;
mod protocol_version;
mod quoted_string;
mod session_data;
mod signed_decimal_floating_point;
mod single_line_string;
@ -30,7 +29,6 @@ pub use initialization_vector::*;
pub use media_type::*;
pub use playlist_type::*;
pub use protocol_version::*;
pub use quoted_string::*;
pub use session_data::*;
pub use signed_decimal_floating_point::*;
pub use single_line_string::*;

View file

@ -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))
}
}

View file

@ -1,5 +1,3 @@
use crate::types::QuotedString;
/// Session data.
///
/// See: [4.3.4.4. EXT-X-SESSION-DATA]
@ -8,6 +6,6 @@ use crate::types::QuotedString;
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SessionData {
Value(QuotedString),
Uri(QuotedString),
Value(String),
Uri(String),
}

View file

@ -1,15 +1,77 @@
use crate::{ErrorKind, Result};
use trackable::error::ErrorKindExt;
pub fn parse_yes_or_no(s: &str) -> Result<bool> {
match s {
pub(crate) fn parse_yes_or_no<T: AsRef<str>>(s: T) -> Result<bool> {
match s.as_ref() {
"YES" => Ok(true),
"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> {
let n = track!(s.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
pub(crate) fn parse_u64<T: AsRef<str>>(s: T) -> Result<u64> {
let n = track!(s
.as_ref()
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
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());
}
}