diff --git a/src/attribute.rs b/src/attribute.rs index f2645e0..3c9807f 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -64,6 +64,7 @@ impl FromStr for AttributePairs { result.insert(key.to_string(), value.to_string()); } + #[cfg(test)] dbg!(&result); Ok(result) } diff --git a/src/master_playlist.rs b/src/master_playlist.rs index 418cdb6..e228e13 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -369,13 +369,13 @@ mod tests { #[test] fn test_parser() { r#"#EXTM3U -#EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 http://example.com/low/index.m3u8 -#EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 http://example.com/lo_mid/index.m3u8 -#EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 http://example.com/hi_mid/index.m3u8 -#EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=640x360 http://example.com/high/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5" http://example.com/audio/index.m3u8 @@ -387,13 +387,13 @@ http://example.com/audio/index.m3u8 #[test] fn test_display() { let input = r#"#EXTM3U -#EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=150000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 http://example.com/low/index.m3u8 -#EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=240000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 http://example.com/lo_mid/index.m3u8 -#EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=440000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=416x234 http://example.com/hi_mid/index.m3u8 -#EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2" +#EXT-X-STREAM-INF:BANDWIDTH=640000,CODECS="avc1.42e00a,mp4a.40.2",RESOLUTION=640x360 http://example.com/high/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5" http://example.com/audio/index.m3u8 diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index cbd60b8..989ac36 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -1,9 +1,9 @@ use std::fmt; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; use crate::attribute::AttributePairs; -use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion}; -use crate::utils::parse_u64; +use crate::types::{ProtocolVersion, StreamInf}; use crate::utils::{quote, tag, unquote}; use crate::Error; @@ -13,12 +13,7 @@ use crate::Error; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExtXIFrameStreamInf { uri: String, - bandwidth: u64, - average_bandwidth: Option, - codecs: Option, - resolution: Option, - hdcp_level: Option, - video: Option, + stream_inf: StreamInf, } impl ExtXIFrameStreamInf { @@ -28,12 +23,7 @@ impl ExtXIFrameStreamInf { pub fn new(uri: T, bandwidth: u64) -> Self { ExtXIFrameStreamInf { uri: uri.to_string(), - bandwidth, - average_bandwidth: None, - codecs: None, - resolution: None, - hdcp_level: None, - video: None, + stream_inf: StreamInf::new(bandwidth), } } @@ -66,189 +56,6 @@ impl ExtXIFrameStreamInf { self } - /// Returns the peak segment bit rate of the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// assert_eq!(stream.bandwidth(), 20); - /// ``` - pub const fn bandwidth(&self) -> u64 { - self.bandwidth - } - - /// Sets the group identifier for the video in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let mut stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// - /// stream.set_video(Some("video")); - /// assert_eq!(stream.video(), &Some("video".to_string())); - /// ``` - pub fn set_video(&mut self, value: Option) -> &mut Self { - self.video = value.map(|v| v.to_string()); - self - } - - /// Returns the group identifier for the video in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// assert_eq!(stream.video(), &None); - /// ``` - pub const fn video(&self) -> &Option { - &self.video - } - - /// Sets the peak segment bit rate of the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let mut stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// - /// stream.set_bandwidth(5); - /// assert_eq!(stream.bandwidth(), 5); - /// ``` - pub fn set_bandwidth(&mut self, value: u64) -> &mut Self { - self.bandwidth = value; - self - } - - /// Returns the average segment bit rate of the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// assert_eq!(stream.average_bandwidth(), None); - /// ``` - pub const fn average_bandwidth(&self) -> Option { - self.average_bandwidth - } - - /// Sets the average segment bit rate of the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let mut stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// - /// stream.set_average_bandwidth(Some(300)); - /// assert_eq!(stream.average_bandwidth(), Some(300)); - /// ``` - pub fn set_average_bandwidth(&mut self, value: Option) -> &mut Self { - self.average_bandwidth = value; - self - } - - /// A string that represents the list of codec types contained the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// assert_eq!(stream.codecs(), &None); - /// ``` - pub const fn codecs(&self) -> &Option { - &self.codecs - } - - /// A string that represents the list of codec types contained the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let mut stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// - /// stream.set_uri("../new/uri"); - /// assert_eq!(stream.uri(), &"../new/uri".to_string()); - /// ``` - pub fn set_codecs(&mut self, value: Option) -> &mut Self { - self.codecs = value.map(|v| v.to_string()); - self - } - - /// Returns the resolution of the stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// assert_eq!(stream.resolution(), None); - /// ``` - pub fn resolution(&self) -> Option<(usize, usize)> { - if let Some(res) = &self.resolution { - Some((res.width(), res.height())) - } else { - None - } - } - - /// Sets the resolution of the stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let mut stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// - /// stream.set_resolution(1920, 1080); - /// assert_eq!(stream.resolution(), Some((1920, 1080))); - /// ``` - pub fn set_resolution(&mut self, width: usize, height: usize) -> &mut Self { - if let Some(res) = &mut self.resolution { - res.set_width(width); - res.set_height(height); - } else { - self.resolution = Some(DecimalResolution::new(width, height)); - } - self - } - - /// The HDCP level of the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// assert_eq!(stream.uri(), &"https://www.example.com".to_string()); - /// ``` - pub const fn hdcp_level(&self) -> Option { - self.hdcp_level - } - - /// The HDCP level of the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXIFrameStreamInf; - /// # - /// let mut stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); - /// - /// stream.set_uri("../new/uri"); - /// assert_eq!(stream.uri(), &"../new/uri".to_string()); - /// ``` - pub fn set_hdcp_level>(&mut self, value: Option) -> &mut Self { - self.hdcp_level = value.map(|v| v.into()); - self - } - /// Returns the protocol compatibility version that this tag requires. pub const fn requires_version(&self) -> ProtocolVersion { ProtocolVersion::V1 @@ -258,24 +65,7 @@ impl ExtXIFrameStreamInf { impl fmt::Display for ExtXIFrameStreamInf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; - write!(f, "URI={}", quote(&self.uri))?; - write!(f, ",BANDWIDTH={}", self.bandwidth)?; - - if let Some(value) = &self.average_bandwidth { - write!(f, ",AVERAGE-BANDWIDTH={}", value)?; - } - if let Some(value) = &self.codecs { - write!(f, ",CODECS={}", quote(value))?; - } - if let Some(value) = &self.resolution { - write!(f, ",RESOLUTION={}", value)?; - } - if let Some(value) = &self.hdcp_level { - write!(f, ",HDCP-LEVEL={}", value)?; - } - if let Some(value) = &self.video { - write!(f, ",VIDEO={}", quote(value))?; - } + write!(f, "URI={},{}", quote(&self.uri), self.stream_inf)?; Ok(()) } } @@ -287,44 +77,37 @@ impl FromStr for ExtXIFrameStreamInf { let input = tag(input, Self::PREFIX)?; let mut uri = None; - let mut bandwidth = None; - let mut average_bandwidth = None; - let mut codecs = None; - let mut resolution = None; - let mut hdcp_level = None; - let mut video = None; for (key, value) in input.parse::()? { match key.as_str() { "URI" => uri = Some(unquote(value)), - "BANDWIDTH" => bandwidth = Some(parse_u64(value)?), - "AVERAGE-BANDWIDTH" => average_bandwidth = Some(parse_u64(value)?), - "CODECS" => codecs = Some(unquote(value)), - "RESOLUTION" => resolution = Some(value.parse()?), - "HDCP-LEVEL" => hdcp_level = Some(value.parse()?), - "VIDEO" => video = Some(unquote(value)), - _ => { - // [6.3.1. General Client Responsibilities] - // > ignore any attribute/value pair with an unrecognized AttributeName. - } + _ => {} } } let uri = uri.ok_or(Error::missing_value("URI"))?; - let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?; - Ok(ExtXIFrameStreamInf { + Ok(Self { uri, - bandwidth, - average_bandwidth, - codecs, - resolution, - hdcp_level, - video, + stream_inf: input.parse()?, }) } } +impl Deref for ExtXIFrameStreamInf { + type Target = StreamInf; + + fn deref(&self) -> &Self::Target { + &self.stream_inf + } +} + +impl DerefMut for ExtXIFrameStreamInf { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.stream_inf + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index 74c0325..dd237a7 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -1,11 +1,10 @@ use std::fmt; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; use crate::attribute::AttributePairs; -use crate::types::{ - ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion, -}; -use crate::utils::{parse_u64, quote, tag, unquote}; +use crate::types::{ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, StreamInf}; +use crate::utils::{quote, tag, unquote}; use crate::Error; /// [4.3.4.2. EXT-X-STREAM-INF] @@ -14,35 +13,30 @@ use crate::Error; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExtXStreamInf { uri: String, - bandwidth: u64, - average_bandwidth: Option, - codecs: Option, - resolution: Option, frame_rate: Option, - hdcp_level: Option, audio: Option, - video: Option, subtitles: Option, closed_captions: Option, + stream_inf: StreamInf, } impl ExtXStreamInf { pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:"; - /// Makes a new `ExtXStreamInf` tag. + /// Makes a new [ExtXStreamInf] tag. + /// ``` + /// # use hls_m3u8::tags::ExtXStreamInf; + /// # + /// let stream = ExtXStreamInf::new("https://www.example.com/", 20); + /// ``` pub fn new(uri: T, bandwidth: u64) -> Self { ExtXStreamInf { uri: uri.to_string(), - bandwidth, - average_bandwidth: None, - codecs: None, - resolution: None, frame_rate: None, - hdcp_level: None, audio: None, - video: None, subtitles: None, closed_captions: None, + stream_inf: StreamInf::new(bandwidth), } } @@ -51,51 +45,16 @@ impl ExtXStreamInf { &self.uri } - /// Returns the peak segment bit rate of the variant stream. - pub const fn bandwidth(&self) -> u64 { - self.bandwidth - } - - /// Returns the average segment bit rate of the variant stream. - pub const fn average_bandwidth(&self) -> Option { - self.average_bandwidth - } - - /// Returns a string that represents the list of codec types contained the variant stream. - pub fn codecs(&self) -> Option<&String> { - self.codecs.as_ref() - } - - /// Returns the optimal pixel resolution at which to display all the video in the variant - /// stream. - pub fn resolution(&self) -> Option<(usize, usize)> { - if let Some(res) = &self.resolution { - Some((res.width(), res.height())) - } else { - None - } - } - /// Returns the maximum frame rate for all the video in the variant stream. pub fn frame_rate(&self) -> Option { self.frame_rate.map_or(None, |v| Some(v.as_f64())) } - /// Returns the HDCP level of the variant stream. - pub const fn hdcp_level(&self) -> Option { - self.hdcp_level - } - /// Returns the group identifier for the audio in the variant stream. 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<&String> { - self.video.as_ref() - } - /// Returns the group identifier for the subtitles in the variant stream. pub fn subtitles(&self) -> Option<&String> { self.subtitles.as_ref() @@ -114,29 +73,13 @@ impl ExtXStreamInf { impl fmt::Display for ExtXStreamInf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", Self::PREFIX)?; - write!(f, "BANDWIDTH={}", self.bandwidth)?; - if let Some(value) = &self.average_bandwidth { - write!(f, ",AVERAGE-BANDWIDTH={}", value)?; - } - if let Some(value) = &self.resolution { - write!(f, ",RESOLUTION={}", value)?; - } - if let Some(value) = &self.codecs { - write!(f, ",CODECS={}", quote(value))?; - } + write!(f, "{}{}", Self::PREFIX, self.stream_inf)?; if let Some(value) = &self.frame_rate { write!(f, ",FRAME-RATE={:.3}", value.as_f64())?; } - if let Some(value) = &self.hdcp_level { - write!(f, ",HDCP-LEVEL={}", value)?; - } if let Some(value) = &self.audio { write!(f, ",AUDIO={}", quote(value))?; } - if let Some(value) = &self.video { - write!(f, ",VIDEO={}", quote(value))?; - } if let Some(value) = &self.subtitles { write!(f, ",SUBTITLES={}", quote(value))?; } @@ -154,58 +97,50 @@ impl FromStr for ExtXStreamInf { fn from_str(input: &str) -> Result { let mut lines = input.lines(); let first_line = lines.next().ok_or(Error::missing_value("first_line"))?; - let uri = lines.next().ok_or(Error::missing_value("second_line"))?; + let uri = lines.next().ok_or(Error::missing_value("URI"))?; - let first_line = tag(first_line, Self::PREFIX)?; + let input = tag(first_line, Self::PREFIX)?; - let mut bandwidth = None; - let mut average_bandwidth = None; - let mut codecs = None; - let mut resolution = None; let mut frame_rate = None; - let mut hdcp_level = None; let mut audio = None; - let mut video = None; let mut subtitles = None; let mut closed_captions = None; - for (key, value) in first_line.parse::()? { + for (key, value) in input.parse::()? { match key.as_str() { - "BANDWIDTH" => bandwidth = Some((parse_u64(value))?), - "AVERAGE-BANDWIDTH" => average_bandwidth = Some((parse_u64(value))?), - "CODECS" => codecs = Some(unquote(value)), - "RESOLUTION" => resolution = Some((value.parse())?), "FRAME-RATE" => frame_rate = Some((value.parse())?), - "HDCP-LEVEL" => hdcp_level = Some((value.parse())?), "AUDIO" => audio = Some(unquote(value)), - "VIDEO" => video = Some(unquote(value)), "SUBTITLES" => subtitles = Some(unquote(value)), "CLOSED-CAPTIONS" => closed_captions = Some((value.parse())?), - _ => { - // [6.3.1. General Client Responsibilities] - // > ignore any attribute/value pair with an unrecognized AttributeName. - } + _ => {} } } - let bandwidth = bandwidth.ok_or(Error::missing_value("EXT-X-BANDWIDTH"))?; - - Ok(ExtXStreamInf { + Ok(Self { uri: uri.to_string(), - bandwidth, - average_bandwidth, - codecs, - resolution, frame_rate, - hdcp_level, audio, - video, subtitles, closed_captions, + stream_inf: input.parse()?, }) } } +impl Deref for ExtXStreamInf { + type Target = StreamInf; + + fn deref(&self) -> &Self::Target { + &self.stream_inf + } +} + +impl DerefMut for ExtXStreamInf { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.stream_inf + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/types/mod.rs b/src/types/mod.rs index 6301ba4..f3c751f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -11,6 +11,7 @@ mod initialization_vector; mod media_type; mod protocol_version; mod signed_decimal_floating_point; +mod stream_inf; pub use byte_range::*; pub use closed_captions::*; @@ -24,3 +25,4 @@ pub use initialization_vector::*; pub use media_type::*; pub use protocol_version::*; pub(crate) use signed_decimal_floating_point::*; +pub use stream_inf::*; diff --git a/src/types/stream_inf.rs b/src/types/stream_inf.rs new file mode 100644 index 0000000..8b391a3 --- /dev/null +++ b/src/types/stream_inf.rs @@ -0,0 +1,286 @@ +use std::fmt; +use std::str::FromStr; + +use crate::attribute::AttributePairs; +use crate::types::{DecimalResolution, HdcpLevel}; +use crate::utils::parse_u64; +use crate::utils::{quote, unquote}; +use crate::Error; + +/// [4.3.4.2. EXT-X-STREAM-INF] +/// +/// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StreamInf { + bandwidth: u64, + average_bandwidth: Option, + codecs: Option, + resolution: Option, + hdcp_level: Option, + video: Option, +} + +impl StreamInf { + /// Creates a new [StreamInf]. + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let stream = StreamInf::new(20); + /// ``` + pub const fn new(bandwidth: u64) -> Self { + Self { + bandwidth, + average_bandwidth: None, + codecs: None, + resolution: None, + hdcp_level: None, + video: None, + } + } + + /// Returns the peak segment bit rate of the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let stream = StreamInf::new(20); + /// assert_eq!(stream.bandwidth(), 20); + /// ``` + pub const fn bandwidth(&self) -> u64 { + self.bandwidth + } + + /// Sets the peak segment bit rate of the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_bandwidth(5); + /// assert_eq!(stream.bandwidth(), 5); + /// ``` + pub fn set_bandwidth(&mut self, value: u64) -> &mut Self { + self.bandwidth = value; + self + } + + /// Returns the group identifier for the video in the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let stream = StreamInf::new(20); + /// assert_eq!(stream.video(), &None); + /// ``` + pub const fn video(&self) -> &Option { + &self.video + } + + /// Sets the group identifier for the video in the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_video(Some("video")); + /// assert_eq!(stream.video(), &Some("video".to_string())); + /// ``` + pub fn set_video(&mut self, value: Option) -> &mut Self { + self.video = value.map(|v| v.to_string()); + self + } + + /// Returns the average segment bit rate of the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let stream = StreamInf::new(20); + /// assert_eq!(stream.average_bandwidth(), None); + /// ``` + pub const fn average_bandwidth(&self) -> Option { + self.average_bandwidth + } + + /// Sets the average segment bit rate of the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_average_bandwidth(Some(300)); + /// assert_eq!(stream.average_bandwidth(), Some(300)); + /// ``` + pub fn set_average_bandwidth(&mut self, value: Option) -> &mut Self { + self.average_bandwidth = value; + self + } + + /// A string that represents the list of codec types contained the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let stream = StreamInf::new(20); + /// assert_eq!(stream.codecs(), &None); + /// ``` + pub const fn codecs(&self) -> &Option { + &self.codecs + } + + /// A string that represents the list of codec types contained the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_codecs(Some("mp4a.40.2,avc1.4d401e")); + /// assert_eq!(stream.codecs(), &Some("mp4a.40.2,avc1.4d401e".to_string())); + /// ``` + pub fn set_codecs(&mut self, value: Option) -> &mut Self { + self.codecs = value.map(|v| v.to_string()); + self + } + + /// Returns the resolution of the stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let stream = StreamInf::new(20); + /// assert_eq!(stream.resolution(), None); + /// ``` + pub fn resolution(&self) -> Option<(usize, usize)> { + if let Some(res) = &self.resolution { + Some((res.width(), res.height())) + } else { + None + } + } + + /// Sets the resolution of the stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_resolution(1920, 1080); + /// assert_eq!(stream.resolution(), Some((1920, 1080))); + /// ``` + pub fn set_resolution(&mut self, width: usize, height: usize) -> &mut Self { + if let Some(res) = &mut self.resolution { + res.set_width(width); + res.set_height(height); + } else { + self.resolution = Some(DecimalResolution::new(width, height)); + } + self + } + + /// The HDCP level of the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let stream = StreamInf::new(20); + /// assert_eq!(stream.hdcp_level(), None); + /// ``` + pub const fn hdcp_level(&self) -> Option { + self.hdcp_level + } + + /// The HDCP level of the variant stream. + /// + /// # Examples + /// ``` + /// # use hls_m3u8::types::{HdcpLevel, StreamInf}; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_hdcp_level(Some(HdcpLevel::None)); + /// assert_eq!(stream.hdcp_level(), Some(HdcpLevel::None)); + /// ``` + pub fn set_hdcp_level>(&mut self, value: Option) -> &mut Self { + self.hdcp_level = value.map(|v| v.into()); + self + } +} + +impl fmt::Display for StreamInf { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BANDWIDTH={}", self.bandwidth)?; + + if let Some(value) = &self.average_bandwidth { + write!(f, ",AVERAGE-BANDWIDTH={}", value)?; + } + if let Some(value) = &self.codecs { + write!(f, ",CODECS={}", quote(value))?; + } + if let Some(value) = &self.resolution { + write!(f, ",RESOLUTION={}", value)?; + } + if let Some(value) = &self.hdcp_level { + write!(f, ",HDCP-LEVEL={}", value)?; + } + if let Some(value) = &self.video { + write!(f, ",VIDEO={}", quote(value))?; + } + Ok(()) + } +} + +impl FromStr for StreamInf { + type Err = Error; + + fn from_str(input: &str) -> Result { + let mut bandwidth = None; + let mut average_bandwidth = None; + let mut codecs = None; + let mut resolution = None; + let mut hdcp_level = None; + let mut video = None; + + for (key, value) in input.parse::()? { + match key.as_str() { + "BANDWIDTH" => bandwidth = Some(parse_u64(value)?), + "AVERAGE-BANDWIDTH" => average_bandwidth = Some(parse_u64(value)?), + "CODECS" => codecs = Some(unquote(value)), + "RESOLUTION" => resolution = Some(value.parse()?), + "HDCP-LEVEL" => hdcp_level = Some(value.parse()?), + "VIDEO" => video = Some(unquote(value)), + _ => { + // [6.3.1. General Client Responsibilities] + // > ignore any attribute/value pair with an unrecognized AttributeName. + } + } + } + + let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?; + + Ok(Self { + bandwidth, + average_bandwidth, + codecs, + resolution, + hdcp_level, + video, + }) + } +}