diff --git a/src/error.rs b/src/error.rs index c661819..eaf3645 100644 --- a/src/error.rs +++ b/src/error.rs @@ -165,17 +165,6 @@ impl Error { pub(crate) fn io(value: T) -> Self { Self::from(ErrorKind::Io(value.to_string())) } - pub(crate) fn required_version(required_version: T, specified_version: U) -> Self - where - T: ToString, - U: ToString, - { - Self::from(ErrorKind::VersionError( - required_version.to_string(), - specified_version.to_string(), - )) - } - pub(crate) fn builder_error(value: T) -> Self { Self::from(ErrorKind::BuilderError(value.to_string())) } diff --git a/src/lib.rs b/src/lib.rs index 2996b1a..2fc3c24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,8 @@ pub use media_segment::{MediaSegment, MediaSegmentBuilder}; pub mod tags; pub mod types; +#[macro_use] +mod utils; mod attribute; mod error; mod line; @@ -51,7 +53,6 @@ mod master_playlist; mod media_playlist; mod media_segment; mod traits; -mod utils; pub use error::Result; pub use traits::*; diff --git a/src/master_playlist.rs b/src/master_playlist.rs index 8c4e295..ff2a944 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -17,8 +17,6 @@ use crate::{Error, RequiredVersion}; #[builder(setter(into, strip_option))] /// Master playlist. pub struct MasterPlaylist { - #[builder(default, setter(skip))] - version_tag: ExtXVersion, #[builder(default)] /// Sets the [`ExtXIndependentSegments`] tag. /// @@ -199,17 +197,6 @@ impl MasterPlaylist { } } -macro_rules! required_version { - ( $( $tag:expr ),* ) => { - ::core::iter::empty() - $( - .chain(::core::iter::once($tag.required_version())) - )* - .max() - .unwrap_or_default() - } -} - impl RequiredVersion for MasterPlaylist { fn required_version(&self) -> ProtocolVersion { required_version![ @@ -392,8 +379,6 @@ impl FromStr for MasterPlaylist { // This tag can be ignored, because the // MasterPlaylist will automatically set the // ExtXVersion tag to correct version! - - // builder.version(t.version()); } Tag::ExtInf(_) | Tag::ExtXByteRange(_) diff --git a/src/media_playlist.rs b/src/media_playlist.rs index dba09db..dc360df 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -1,5 +1,4 @@ use std::fmt; -use std::iter; use std::str::FromStr; use std::time::Duration; @@ -19,15 +18,6 @@ use crate::{Encrypted, Error, RequiredVersion}; #[builder(build_fn(validate = "Self::validate"))] #[builder(setter(into, strip_option))] pub struct MediaPlaylist { - /// Sets the protocol compatibility version of the resulting playlist. - /// - /// If the resulting playlist has tags which requires a compatibility - /// version greater than `version`, - /// `build()` method will fail with an `ErrorKind::InvalidInput` error. - /// - /// The default is the maximum version among the tags in the playlist. - #[builder(setter(name = "version"))] - version_tag: ExtXVersion, /// Sets the [`ExtXTargetDuration`] tag. target_duration_tag: ExtXTargetDuration, #[builder(default)] @@ -68,13 +58,6 @@ pub struct MediaPlaylist { impl MediaPlaylistBuilder { fn validate(&self) -> Result<(), String> { - let required_version = self.required_version(); - let specified_version = self.version_tag.map_or(required_version, |p| p.version()); - - if required_version > specified_version { - return Err(Error::required_version(required_version, specified_version).to_string()); - } - if let Some(target_duration) = &self.target_duration_tag { self.validate_media_segments(target_duration.duration()) .map_err(|e| e.to_string())?; @@ -89,10 +72,12 @@ impl MediaPlaylistBuilder { for s in segments { // CHECK: `#EXT-X-TARGETDURATION` let segment_duration = s.inf_tag().duration(); - let rounded_segment_duration = if segment_duration.subsec_nanos() < 500_000_000 { - Duration::from_secs(segment_duration.as_secs()) - } else { - Duration::from_secs(segment_duration.as_secs() + 1) + let rounded_segment_duration = { + if segment_duration.subsec_nanos() < 500_000_000 { + Duration::from_secs(segment_duration.as_secs()) + } else { + Duration::from_secs(segment_duration.as_secs() + 1) + } }; let max_segment_duration = { @@ -149,62 +134,17 @@ impl MediaPlaylistBuilder { impl RequiredVersion for MediaPlaylistBuilder { fn required_version(&self) -> ProtocolVersion { - iter::empty() - .chain( - self.target_duration_tag - .iter() - .map(|p| p.required_version()), - ) - .chain( - self.media_sequence_tag - .flatten() - .iter() - .map(|p| p.required_version()), - ) - .chain( - self.discontinuity_sequence_tag - .flatten() - .iter() - .map(|p| p.required_version()), - ) - .chain( - self.playlist_type_tag - .flatten() - .iter() - .map(|p| p.required_version()), - ) - .chain( - self.i_frames_only_tag - .flatten() - .iter() - .map(|p| p.required_version()), - ) - .chain( - self.independent_segments_tag - .flatten() - .iter() - .map(|p| p.required_version()), - ) - .chain( - self.start_tag - .flatten() - .iter() - .map(|p| p.required_version()), - ) - .chain( - self.end_list_tag - .flatten() - .iter() - .map(|p| p.required_version()), - ) - .chain(self.segments.iter().map(|t| { - t.iter() - .map(|p| p.required_version()) - .max() - .unwrap_or(ProtocolVersion::V1) - })) - .max() - .unwrap_or_else(ProtocolVersion::latest) + required_version![ + self.target_duration_tag, + self.media_sequence_tag, + self.discontinuity_sequence_tag, + self.playlist_type_tag, + self.i_frames_only_tag, + self.independent_segments_tag, + self.start_tag, + self.end_list_tag, + self.segments + ] } } @@ -212,9 +152,6 @@ impl MediaPlaylist { /// Returns a builder for [`MediaPlaylist`]. pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() } - /// Returns the [`ExtXVersion`] tag contained in the playlist. - pub const fn version_tag(&self) -> ExtXVersion { self.version_tag } - /// Returns the [`ExtXTargetDuration`] tag contained in the playlist. pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag } @@ -248,11 +185,27 @@ impl MediaPlaylist { pub const fn segments(&self) -> &Vec { &self.segments } } +impl RequiredVersion for MediaPlaylist { + fn required_version(&self) -> ProtocolVersion { + required_version![ + self.target_duration_tag, + self.media_sequence_tag, + self.discontinuity_sequence_tag, + self.playlist_type_tag, + self.i_frames_only_tag, + self.independent_segments_tag, + self.start_tag, + self.end_list_tag, + self.segments + ] + } +} + impl fmt::Display for MediaPlaylist { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "{}", ExtM3u)?; - if self.version_tag.version() != ProtocolVersion::V1 { - writeln!(f, "{}", self.version_tag)?; + if self.required_version() != ProtocolVersion::V1 { + writeln!(f, "{}", ExtXVersion::new(self.required_version()))?; } writeln!(f, "{}", self.target_duration_tag)?; if let Some(value) = &self.media_sequence_tag { @@ -292,9 +245,8 @@ fn parse_media_playlist( let mut has_partial_segment = false; let mut has_discontinuity_tag = false; - let mut has_version = false; // m3u8 files without ExtXVersion tags are ProtocolVersion::V1 - let mut available_key_tags = vec![]; + let mut available_key_tags: Vec = vec![]; for (i, line) in input.parse::()?.into_iter().enumerate() { match line { @@ -307,10 +259,6 @@ fn parse_media_playlist( } match tag { Tag::ExtM3u(_) => return Err(Error::invalid_input()), - Tag::ExtXVersion(t) => { - builder.version(t.version()); - has_version = true; - } Tag::ExtInf(t) => { has_partial_segment = true; segment.inf_tag(t); @@ -326,9 +274,7 @@ fn parse_media_playlist( } Tag::ExtXKey(t) => { has_partial_segment = true; - if !available_key_tags.is_empty() { - available_key_tags.push(t); - } else { + if available_key_tags.is_empty() { // An ExtXKey applies to every MediaSegment and to every Media // Initialization Section declared by an EXT-X-MAP tag, that appears // between it and the next EXT-X-KEY tag in the Playlist file with the @@ -343,6 +289,8 @@ fn parse_media_playlist( } }) .collect(); + } else { + available_key_tags.push(t); } } Tag::ExtXMap(mut t) => { @@ -396,7 +344,7 @@ fn parse_media_playlist( Tag::ExtXStart(t) => { builder.start_tag(t); } - Tag::Unknown(_) => { + Tag::Unknown(_) | Tag::ExtXVersion(_) => { // [6.3.1. General Client Responsibilities] // > ignore any unrecognized tags. } @@ -416,10 +364,6 @@ fn parse_media_playlist( return Err(Error::invalid_input()); } - if !has_version { - builder.version(ProtocolVersion::V1); - } - builder.segments(segments); builder.build().map_err(Error::builder_error) } diff --git a/src/media_segment.rs b/src/media_segment.rs index 7e3722d..2efa2b8 100644 --- a/src/media_segment.rs +++ b/src/media_segment.rs @@ -1,5 +1,4 @@ use std::fmt; -use std::iter; use derive_builder::Builder; @@ -37,6 +36,99 @@ pub struct MediaSegment { uri: String, } +impl MediaSegment { + /// Returns a Builder for a [`MasterPlaylist`]. + pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() } + + /// Returns the `URI` of the media segment. + pub const fn uri(&self) -> &String { &self.uri } + + /// Sets the `URI` of the media segment. + pub fn set_uri(&mut self, value: T) -> &mut Self + where + T: Into, + { + self.uri = value.into(); + self + } + + /// Returns the [`ExtInf`] tag associated with the media segment. + pub const fn inf_tag(&self) -> &ExtInf { &self.inf_tag } + + /// Sets the [`ExtInf`] tag associated with the media segment. + pub fn set_inf_tag(&mut self, value: T) -> &mut Self + where + T: Into, + { + self.inf_tag = value.into(); + self + } + + /// Returns the [`ExtXByteRange`] tag associated with the media segment. + pub const fn byte_range_tag(&self) -> Option { self.byte_range_tag } + + /// Sets the [`ExtXByteRange`] tag associated with the media segment. + pub fn set_byte_range_tag(&mut self, value: Option) -> &mut Self + where + T: Into, + { + self.byte_range_tag = value.map(Into::into); + self + } + + /// Returns the [`ExtXDateRange`] tag associated with the media segment. + pub const fn date_range_tag(&self) -> &Option { &self.date_range_tag } + + /// Sets the [`ExtXDateRange`] tag associated with the media segment. + pub fn set_date_range_tag(&mut self, value: Option) -> &mut Self + where + T: Into, + { + self.date_range_tag = value.map(Into::into); + self + } + + /// Returns the [`ExtXDiscontinuity`] tag associated with the media segment. + pub const fn discontinuity_tag(&self) -> Option { self.discontinuity_tag } + + /// Sets the [`ExtXDiscontinuity`] tag associated with the media segment. + pub fn set_discontinuity_tag(&mut self, value: Option) -> &mut Self + where + T: Into, + { + self.discontinuity_tag = value.map(Into::into); + self + } + + /// Returns the [`ExtXProgramDateTime`] tag associated with the media + /// segment. + pub const fn program_date_time_tag(&self) -> Option { + self.program_date_time_tag + } + + /// Sets the [`ExtXProgramDateTime`] tag associated with the media + /// segment. + pub fn set_program_date_time_tag(&mut self, value: Option) -> &mut Self + where + T: Into, + { + self.program_date_time_tag = value.map(Into::into); + self + } + + /// Returns the [`ExtXMap`] tag associated with the media segment. + pub const fn map_tag(&self) -> &Option { &self.map_tag } + + /// Sets the [`ExtXMap`] tag associated with the media segment. + pub fn set_map_tag(&mut self, value: Option) -> &mut Self + where + T: Into, + { + self.map_tag = value.map(Into::into); + self + } +} + impl MediaSegmentBuilder { /// Pushes an [`ExtXKey`] tag. pub fn push_key_tag>(&mut self, value: VALUE) -> &mut Self { @@ -75,51 +167,17 @@ impl fmt::Display for MediaSegment { } } -impl MediaSegment { - /// Creates a [`MediaSegmentBuilder`]. - pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() } - - /// Returns the `URI` of the media segment. - pub const fn uri(&self) -> &String { &self.uri } - - /// Returns the [`ExtInf`] tag associated with the media segment. - pub const fn inf_tag(&self) -> &ExtInf { &self.inf_tag } - - /// Returns the [`ExtXByteRange`] tag associated with the media segment. - pub const fn byte_range_tag(&self) -> Option { self.byte_range_tag } - - /// Returns the [`ExtXDateRange`] tag associated with the media segment. - pub const fn date_range_tag(&self) -> &Option { &self.date_range_tag } - - /// Returns the [`ExtXDiscontinuity`] tag associated with the media segment. - pub const fn discontinuity_tag(&self) -> Option { self.discontinuity_tag } - - /// Returns the [`ExtXProgramDateTime`] tag associated with the media - /// segment. - pub const fn program_date_time_tag(&self) -> Option { - self.program_date_time_tag - } - - /// Returns the [`ExtXMap`] tag associated with the media segment. - pub const fn map_tag(&self) -> &Option { &self.map_tag } -} - impl RequiredVersion for MediaSegment { fn required_version(&self) -> ProtocolVersion { - iter::empty() - .chain(self.keys.iter().map(|t| t.required_version())) - .chain(self.map_tag.iter().map(|t| t.required_version())) - .chain(self.byte_range_tag.iter().map(|t| t.required_version())) - .chain(self.date_range_tag.iter().map(|t| t.required_version())) - .chain(self.discontinuity_tag.iter().map(|t| t.required_version())) - .chain( - self.program_date_time_tag - .iter() - .map(|t| t.required_version()), - ) - .chain(iter::once(self.inf_tag.required_version())) - .max() - .unwrap_or_else(ProtocolVersion::latest) + required_version![ + self.keys, + self.map_tag, + self.byte_range_tag, + self.date_range_tag, + self.discontinuity_tag, + self.program_date_time_tag, + self.inf_tag + ] } } diff --git a/src/utils.rs b/src/utils.rs index a48a2eb..9eb7fae 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,16 @@ use crate::Error; +macro_rules! required_version { + ( $( $tag:expr ),* ) => { + ::core::iter::empty() + $( + .chain(::core::iter::once($tag.required_version())) + )* + .max() + .unwrap_or_default() + } +} + macro_rules! impl_from { ( $($( $type:tt ),* => $target:path ),* ) => { use ::core::convert::From;