From 27d94faec4b5f5d75ce5e7f05faa23e6ef107b42 Mon Sep 17 00:00:00 2001 From: Luro02 <24826124+Luro02@users.noreply.github.com> Date: Sun, 2 Feb 2020 13:38:11 +0100 Subject: [PATCH] use shorthand #24 --- Cargo.toml | 1 + src/attribute.rs | 2 +- src/master_playlist.rs | 179 ++---- src/media_playlist.rs | 113 ++-- src/media_segment.rs | 120 +--- .../master_playlist/i_frame_stream_inf.rs | 5 +- src/tags/master_playlist/media.rs | 547 ++--------------- src/tags/master_playlist/session_data.rs | 139 +---- src/tags/master_playlist/session_key.rs | 20 +- src/tags/master_playlist/stream_inf.rs | 168 +---- .../media_playlist/discontinuity_sequence.rs | 10 +- src/tags/media_playlist/end_list.rs | 7 +- src/tags/media_playlist/i_frames_only.rs | 7 +- src/tags/media_playlist/media_sequence.rs | 4 + src/tags/media_playlist/target_duration.rs | 7 +- src/tags/media_segment/byte_range.rs | 12 +- src/tags/media_segment/date_range.rs | 575 +----------------- src/tags/media_segment/discontinuity.rs | 7 +- src/tags/media_segment/inf.rs | 8 + src/tags/media_segment/key.rs | 6 + src/tags/media_segment/map.rs | 111 ++-- src/tags/media_segment/program_date_time.rs | 7 +- src/tags/shared/independent_segments.rs | 6 +- src/tags/shared/start.rs | 11 +- src/traits.rs | 7 + src/types/byte_range.rs | 137 ++--- src/types/channels.rs | 15 +- src/types/decimal_floating_point.rs | 8 +- src/types/decimal_resolution.rs | 123 ---- src/types/decryption_key.rs | 349 ++++------- src/types/encryption_method.rs | 9 +- src/types/initialization_vector.rs | 150 ----- src/types/key_format.rs | 2 +- src/types/mod.rs | 6 +- src/types/protocol_version.rs | 3 + src/types/resolution.rs | 104 ++++ src/types/signed_decimal_floating_point.rs | 1 + src/types/stream_inf.rs | 292 ++++----- src/utils.rs | 23 + 39 files changed, 820 insertions(+), 2481 deletions(-) delete mode 100644 src/types/decimal_resolution.rs delete mode 100644 src/types/initialization_vector.rs create mode 100644 src/types/resolution.rs diff --git a/Cargo.toml b/Cargo.toml index e7703b6..2868525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ chrono = "0.4.9" strum = { version = "0.16.0", features = ["derive"] } derive_more = "0.15.0" hex = "0.4.0" +shorthand = "0.1" [dev-dependencies] clap = "2.33.0" diff --git a/src/attribute.rs b/src/attribute.rs index 228ca82..50a6d17 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -1,6 +1,6 @@ use core::iter::FusedIterator; -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash)] pub(crate) struct AttributePairs<'a> { string: &'a str, index: usize, diff --git a/src/master_playlist.rs b/src/master_playlist.rs index 513658d..b081256 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -3,6 +3,7 @@ use std::fmt; use std::str::FromStr; use derive_builder::Builder; +use shorthand::ShortHand; use crate::line::{Line, Lines, Tag}; use crate::tags::{ @@ -12,57 +13,65 @@ use crate::tags::{ use crate::types::{ClosedCaptions, MediaType, ProtocolVersion}; use crate::{Error, RequiredVersion}; -#[derive(Debug, Clone, Builder, PartialEq)] +/// Master playlist. +#[derive(ShortHand, Debug, Clone, Builder, PartialEq)] #[builder(build_fn(validate = "Self::validate"))] #[builder(setter(into, strip_option))] -/// Master playlist. +#[shorthand(enable(must_use, get_mut, collection_magic))] pub struct MasterPlaylist { - #[builder(default)] - /// Sets the [`ExtXIndependentSegments`] tag. + /// The [`ExtXIndependentSegments`] tag of the playlist. /// /// # Note + /// /// This tag is optional. + #[builder(default)] independent_segments_tag: Option, - #[builder(default)] - /// Sets the [`ExtXStart`] tag. + /// The [`ExtXStart`] tag of the playlist. /// /// # Note + /// /// This tag is optional. + #[builder(default)] start_tag: Option, - #[builder(default)] - /// Sets the [`ExtXMedia`] tag. + /// The [`ExtXMedia`] tags of the playlist. /// /// # Note + /// /// This tag is optional. + #[builder(default)] media_tags: Vec, - #[builder(default)] - /// Sets all [`ExtXStreamInf`] tags. + /// The [`ExtXStreamInf`] tags of the playlist. /// /// # Note + /// /// This tag is optional. + #[builder(default)] stream_inf_tags: Vec, - #[builder(default)] - /// Sets all [`ExtXIFrameStreamInf`] tags. + /// The [`ExtXIFrameStreamInf`] tags of the playlist. /// /// # Note + /// /// This tag is optional. + #[builder(default)] i_frame_stream_inf_tags: Vec, - #[builder(default)] - /// Sets all [`ExtXSessionData`] tags. + /// The [`ExtXSessionData`] tags of the playlist. /// /// # Note + /// /// This tag is optional. + #[builder(default)] session_data_tags: Vec, - #[builder(default)] - /// Sets all [`ExtXSessionKey`] tags. + /// The [`ExtXSessionKey`] tags of the playlist. /// /// # Note + /// /// This tag is optional. + #[builder(default)] session_key_tags: Vec, } impl MasterPlaylist { - /// Returns a Builder for a [`MasterPlaylist`]. + /// Returns a builder for a [`MasterPlaylist`]. /// /// # Example /// @@ -76,124 +85,6 @@ impl MasterPlaylist { /// # Ok::<(), Box>(()) /// ``` pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() } - - /// Returns the [`ExtXIndependentSegments`] tag contained in the playlist. - pub const fn independent_segments(&self) -> Option { - self.independent_segments_tag - } - - /// Sets the [`ExtXIndependentSegments`] tag contained in the playlist. - pub fn set_independent_segments(&mut self, value: Option) -> &mut Self - where - T: Into, - { - self.independent_segments_tag = value.map(Into::into); - self - } - - /// Returns the [`ExtXStart`] tag contained in the playlist. - pub const fn start(&self) -> Option { self.start_tag } - - /// Sets the [`ExtXStart`] tag contained in the playlist. - pub fn set_start(&mut self, value: Option) -> &mut Self - where - T: Into, - { - self.start_tag = value.map(Into::into); - self - } - - /// Returns the [`ExtXMedia`] tags contained in the playlist. - pub const fn media_tags(&self) -> &Vec { &self.media_tags } - - /// Appends an [`ExtXMedia`]. - pub fn push_media_tag(&mut self, value: ExtXMedia) -> &mut Self { - self.media_tags.push(value); - self - } - - /// Sets the [`ExtXMedia`] tags contained in the playlist. - pub fn set_media_tags(&mut self, value: Vec) -> &mut Self - where - T: Into, - { - self.media_tags = value.into_iter().map(Into::into).collect(); - self - } - - /// Returns the [`ExtXStreamInf`] tags contained in the playlist. - pub const fn stream_inf_tags(&self) -> &Vec { &self.stream_inf_tags } - - /// Appends an [`ExtXStreamInf`]. - pub fn push_stream_inf(&mut self, value: ExtXStreamInf) -> &mut Self { - self.stream_inf_tags.push(value); - self - } - - /// Sets the [`ExtXStreamInf`] tags contained in the playlist. - pub fn set_stream_inf_tags(&mut self, value: Vec) -> &mut Self - where - T: Into, - { - self.stream_inf_tags = value.into_iter().map(Into::into).collect(); - self - } - - /// Returns the [`ExtXIFrameStreamInf`] tags contained in the playlist. - pub const fn i_frame_stream_inf_tags(&self) -> &Vec { - &self.i_frame_stream_inf_tags - } - - /// Appends an [`ExtXIFrameStreamInf`]. - pub fn push_i_frame_stream_inf(&mut self, value: ExtXIFrameStreamInf) -> &mut Self { - self.i_frame_stream_inf_tags.push(value); - self - } - - /// Sets the [`ExtXIFrameStreamInf`] tags contained in the playlist. - pub fn set_i_frame_stream_inf_tags(&mut self, value: Vec) -> &mut Self - where - T: Into, - { - self.i_frame_stream_inf_tags = value.into_iter().map(Into::into).collect(); - self - } - - /// Returns the [`ExtXSessionData`] tags contained in the playlist. - pub const fn session_data_tags(&self) -> &Vec { &self.session_data_tags } - - /// Appends an [`ExtXSessionData`]. - pub fn push_session_data(&mut self, value: ExtXSessionData) -> &mut Self { - self.session_data_tags.push(value); - self - } - - /// Sets the [`ExtXSessionData`] tags contained in the playlist. - pub fn set_session_data_tags(&mut self, value: Vec) -> &mut Self - where - T: Into, - { - self.session_data_tags = value.into_iter().map(Into::into).collect(); - self - } - - /// Returns the [`ExtXSessionKey`] tags contained in the playlist. - pub const fn session_key_tags(&self) -> &Vec { &self.session_key_tags } - - /// Appends an [`ExtXSessionKey`]. - pub fn push_session_key(&mut self, value: ExtXSessionKey) -> &mut Self { - self.session_key_tags.push(value); - self - } - - /// Sets the [`ExtXSessionKey`] tags contained in the playlist. - pub fn set_session_key_tags(&mut self, value: Vec) -> &mut Self - where - T: Into, - { - self.session_key_tags = value.into_iter().map(Into::into).collect(); - self - } } impl RequiredVersion for MasterPlaylist { @@ -241,22 +132,22 @@ impl MasterPlaylistBuilder { return Err(Error::unmatched_group(group_id)); } } - match t.closed_captions() { - &Some(ClosedCaptions::GroupId(ref group_id)) => { + match &t.closed_captions() { + Some(ClosedCaptions::GroupId(group_id)) => { if !self.check_media_group(MediaType::ClosedCaptions, group_id) { return Err(Error::unmatched_group(group_id)); } } - &Some(ClosedCaptions::None) => { + Some(ClosedCaptions::None) => { has_none_closed_captions = true; } - None => {} + _ => {} } } if has_none_closed_captions && !value .iter() - .all(|t| t.closed_captions() == &Some(ClosedCaptions::None)) + .all(|t| t.closed_captions() == Some(&ClosedCaptions::None)) { return Err(Error::invalid_input()); } @@ -324,27 +215,35 @@ impl fmt::Display for MasterPlaylist { if self.required_version() != ProtocolVersion::V1 { writeln!(f, "{}", ExtXVersion::new(self.required_version()))?; } + for t in &self.media_tags { writeln!(f, "{}", t)?; } + for t in &self.stream_inf_tags { writeln!(f, "{}", t)?; } + for t in &self.i_frame_stream_inf_tags { writeln!(f, "{}", t)?; } + for t in &self.session_data_tags { writeln!(f, "{}", t)?; } + for t in &self.session_key_tags { writeln!(f, "{}", t)?; } + if let Some(value) = &self.independent_segments_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.start_tag { writeln!(f, "{}", value)?; } + Ok(()) } } diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 4fb97f4..f0c5bab 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use std::time::Duration; use derive_builder::Builder; +use shorthand::ShortHand; use crate::line::{Line, Lines, Tag}; use crate::media_segment::MediaSegment; @@ -14,44 +15,87 @@ use crate::types::ProtocolVersion; use crate::{Encrypted, Error, RequiredVersion}; /// Media playlist. -#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)] +#[derive(ShortHand, Debug, Clone, Builder, PartialEq, PartialOrd)] #[builder(build_fn(validate = "Self::validate"))] #[builder(setter(into, strip_option))] +#[shorthand(enable(must_use, collection_magic, get_mut))] pub struct MediaPlaylist { - /// Sets the [`ExtXTargetDuration`] tag. + /// The [`ExtXTargetDuration`] tag of the playlist. + /// + /// # Note + /// + /// This field is required. + #[shorthand(enable(copy))] target_duration_tag: ExtXTargetDuration, - #[builder(default)] /// Sets the [`ExtXMediaSequence`] tag. + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] media_sequence_tag: Option, - #[builder(default)] /// Sets the [`ExtXDiscontinuitySequence`] tag. + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] discontinuity_sequence_tag: Option, - #[builder(default)] /// Sets the [`ExtXPlaylistType`] tag. + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] playlist_type_tag: Option, - #[builder(default)] /// Sets the [`ExtXIFramesOnly`] tag. + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] i_frames_only_tag: Option, - #[builder(default)] /// Sets the [`ExtXIndependentSegments`] tag. + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] independent_segments_tag: Option, - #[builder(default)] /// Sets the [`ExtXStart`] tag. - start_tag: Option, + /// + /// # Note + /// + /// This field is optional. #[builder(default)] + start_tag: Option, /// Sets the [`ExtXEndList`] tag. + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] end_list_tag: Option, - /// Sets all [`MediaSegment`]s. + /// A list of all [`MediaSegment`]s. + /// + /// # Note + /// + /// This field is required. segments: Vec, - /// Sets the allowable excess duration of each media segment in the + /// The allowable excess duration of each media segment in the /// associated playlist. /// /// # Error + /// /// If there is a media segment of which duration exceeds /// `#EXT-X-TARGETDURATION + allowable_excess_duration`, /// the invocation of `MediaPlaylistBuilder::build()` method will fail. /// - /// The default value is `Duration::from_secs(0)`. + /// + /// # Note + /// + /// This field is optional and the default value is + /// `Duration::from_secs(0)`. #[builder(default = "Duration::from_secs(0)")] allowable_excess_duration: Duration, } @@ -68,6 +112,7 @@ impl MediaPlaylistBuilder { fn validate_media_segments(&self, target_duration: Duration) -> crate::Result<()> { let mut last_range_uri = None; + if let Some(segments) = &self.segments { for s in segments { // CHECK: `#EXT-X-TARGETDURATION` @@ -113,6 +158,7 @@ impl MediaPlaylistBuilder { } } } + Ok(()) } @@ -151,38 +197,6 @@ impl RequiredVersion for MediaPlaylistBuilder { impl MediaPlaylist { /// Returns a builder for [`MediaPlaylist`]. pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() } - - /// Returns the [`ExtXTargetDuration`] tag contained in the playlist. - pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag } - - /// Returns the `EXT-X-MEDIA-SEQUENCE` tag contained in the playlist. - pub const fn media_sequence_tag(&self) -> Option { self.media_sequence_tag } - - /// Returns the [`ExtXDiscontinuitySequence`] tag contained in the - /// playlist. - pub const fn discontinuity_sequence_tag(&self) -> Option { - self.discontinuity_sequence_tag - } - - /// Returns the [`ExtXPlaylistType`] tag contained in the playlist. - pub const fn playlist_type_tag(&self) -> Option { self.playlist_type_tag } - - /// Returns the [`ExtXIFramesOnly`] tag contained in the playlist. - pub const fn i_frames_only_tag(&self) -> Option { self.i_frames_only_tag } - - /// Returns the [`ExtXIndependentSegments`] tag contained in the playlist. - pub const fn independent_segments_tag(&self) -> Option { - self.independent_segments_tag - } - - /// Returns the [`ExtXStart`] tag contained in the playlist. - pub const fn start_tag(&self) -> Option { self.start_tag } - - /// Returns the [`ExtXEndList`] tag contained in the playlist. - pub const fn end_list_tag(&self) -> Option { self.end_list_tag } - - /// Returns the [`MediaSegment`]s contained in the playlist. - pub const fn segments(&self) -> &Vec { &self.segments } } impl RequiredVersion for MediaPlaylist { @@ -204,34 +218,45 @@ impl RequiredVersion for MediaPlaylist { impl fmt::Display for MediaPlaylist { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "{}", ExtM3u)?; + 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 { writeln!(f, "{}", value)?; } + if let Some(value) = &self.discontinuity_sequence_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.playlist_type_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.i_frames_only_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.independent_segments_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.start_tag { writeln!(f, "{}", value)?; } + for segment in &self.segments { write!(f, "{}", segment)?; } + if let Some(value) = &self.end_list_tag { writeln!(f, "{}", value)?; } + Ok(()) } } diff --git a/src/media_segment.rs b/src/media_segment.rs index ea4f160..803086d 100644 --- a/src/media_segment.rs +++ b/src/media_segment.rs @@ -1,6 +1,7 @@ use std::fmt; use derive_builder::Builder; +use shorthand::ShortHand; use crate::tags::{ ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime, @@ -8,127 +9,42 @@ use crate::tags::{ use crate::types::ProtocolVersion; use crate::{Encrypted, RequiredVersion}; -#[derive(Debug, Clone, Builder, PartialEq, PartialOrd)] -#[builder(setter(into, strip_option))] /// Media segment. +#[derive(ShortHand, Debug, Clone, Builder, PartialEq, PartialOrd)] +#[builder(setter(into, strip_option))] +#[shorthand(enable(must_use, get_mut, collection_magic))] pub struct MediaSegment { - #[builder(default)] /// Sets all [`ExtXKey`] tags. + #[builder(default)] keys: Vec, + /// The [`ExtXMap`] tag associated with the media segment. #[builder(default)] - /// Sets an [`ExtXMap`] tag. map_tag: Option, + /// The [`ExtXByteRange`] tag associated with the [`MediaSegment`]. #[builder(default)] - /// Sets an [`ExtXByteRange`] tag. byte_range_tag: Option, + /// The [`ExtXDateRange`] tag associated with the media segment. #[builder(default)] - /// Sets an [`ExtXDateRange`] tag. date_range_tag: Option, + /// The [`ExtXDiscontinuity`] tag associated with the media segment. #[builder(default)] - /// Sets an [`ExtXDiscontinuity`] tag. discontinuity_tag: Option, + /// The [`ExtXProgramDateTime`] tag associated with the media + /// segment. #[builder(default)] - /// Sets an [`ExtXProgramDateTime`] tag. program_date_time_tag: Option, - /// Sets an [`ExtInf`] tag. + /// The [`ExtInf`] tag associated with the [`MediaSegment`]. inf_tag: ExtInf, - /// Sets an `URI`. + /// The `URI` of the [`MediaSegment`]. + #[shorthand(enable(into))] uri: String, } impl MediaSegment { - /// Returns a Builder for a [`MasterPlaylist`]. + /// Returns a builder for a [`MasterPlaylist`]. /// /// [`MasterPlaylist`]: crate::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 { @@ -148,21 +64,27 @@ impl fmt::Display for MediaSegment { for value in &self.keys { writeln!(f, "{}", value)?; } + if let Some(value) = &self.map_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.byte_range_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.date_range_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.discontinuity_tag { writeln!(f, "{}", value)?; } + if let Some(value) = &self.program_date_time_tag { writeln!(f, "{}", value)?; } + writeln!(f, "{}", self.inf_tag)?; // TODO: there might be a `,` missing writeln!(f, "{}", self.uri)?; Ok(()) diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index 7f748ac..6fee28c 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -96,6 +96,7 @@ impl ExtXIFrameStreamInf { /// Makes a new [`ExtXIFrameStreamInf`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXIFrameStreamInf; /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); @@ -113,6 +114,7 @@ impl ExtXIFrameStreamInf { /// Returns the `URI`, that identifies the associated [`media playlist`]. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXIFrameStreamInf; /// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20); @@ -125,6 +127,7 @@ impl ExtXIFrameStreamInf { /// Sets the `URI`, that identifies the associated [`media playlist`]. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXIFrameStreamInf; /// # @@ -200,7 +203,7 @@ mod test { i_frame_stream_inf .set_average_bandwidth(Some(100_000)) .set_codecs(Some("mp4a.40.5")) - .set_resolution(1920, 1080) + .set_resolution(Some((1920, 1080))) .set_hdcp_level(Some(HdcpLevel::None)) .set_video(Some("video")); diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 027ddfb..7a5e2cc 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -2,6 +2,7 @@ use std::fmt; use std::str::FromStr; use derive_builder::Builder; +use shorthand::ShortHand; use crate::attribute::AttributePairs; use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion}; @@ -22,17 +23,19 @@ use crate::{Error, RequiredVersion}; /// [`Media Playlist`]: crate::MediaPlaylist /// [4.4.5.1. EXT-X-MEDIA]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.1 -#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash)] +#[shorthand(enable(must_use, into))] #[builder(setter(into))] #[builder(build_fn(validate = "Self::validate"))] pub struct ExtXMedia { - /// Sets the [`MediaType`] of the rendition. + /// The [`MediaType`] that is associated with this tag. /// /// # Note /// /// This attribute is **required**. + #[shorthand(enable(copy))] media_type: MediaType, - /// Sets the `URI` that identifies the [`Media Playlist`]. + /// The `URI` that identifies the [`Media Playlist`]. /// /// # Note /// @@ -44,14 +47,14 @@ pub struct ExtXMedia { /// [`Media Playlist`]: crate::MediaPlaylist #[builder(setter(strip_option), default)] uri: Option, - /// Sets the identifier, that specifies the group to which the rendition + /// The identifier that specifies the group to which the rendition /// belongs. /// /// # Note /// /// This attribute is **required**. group_id: String, - /// Sets the name of the primary language used in the rendition. + /// The name of the primary language used in the rendition. /// The value has to conform to [`RFC5646`]. /// /// # Note @@ -61,7 +64,10 @@ pub struct ExtXMedia { /// [`RFC5646`]: https://tools.ietf.org/html/rfc5646 #[builder(setter(strip_option), default)] language: Option, - /// Sets the name of a language associated with the rendition. + /// The name of a language associated with the rendition. + /// An associated language is often used in a different role, than the + /// language specified by the [`language`] attribute (e.g., written versus + /// spoken, or a fallback dialect). /// /// # Note /// @@ -70,7 +76,7 @@ pub struct ExtXMedia { /// [`language`]: #method.language #[builder(setter(strip_option), default)] assoc_language: Option, - /// Sets a human-readable description of the rendition. + /// A human-readable description of the rendition. /// /// # Note /// @@ -81,7 +87,10 @@ pub struct ExtXMedia { /// /// [`language`]: #method.language name: String, - /// Sets the value of the `default` flag. + /// The value of the `default` flag. + /// A value of `true` indicates, that the client should play + /// this rendition of the content in the absence of information + /// from the user indicating a different choice. /// /// # Note /// @@ -89,7 +98,8 @@ pub struct ExtXMedia { /// of `false`. #[builder(default)] is_default: bool, - /// Sets the value of the `autoselect` flag. + /// Whether the client may choose to play this rendition in the absence of + /// explicit user preference. /// /// # Note /// @@ -97,17 +107,37 @@ pub struct ExtXMedia { /// of `false`. #[builder(default)] is_autoselect: bool, - /// Sets the value of the `forced` flag. + /// Whether the rendition contains content that is considered + /// essential to play. #[builder(default)] is_forced: bool, - /// Sets the identifier that specifies a rendition within the segments in - /// the media playlist. + /// An [`InStreamId`] specifies a rendition within the + /// segments in the [`Media Playlist`]. + /// + /// [`Media Playlist`]: crate::MediaPlaylist #[builder(setter(strip_option), default)] + #[shorthand(enable(copy))] instream_id: Option, - /// Sets the string that represents uniform type identifiers (UTI). + /// The characteristics attribute, containing one or more Uniform Type + /// Identifiers (UTI) separated by comma. + /// Each [`UTI`] indicates an individual characteristic of the Rendition. + /// + /// A [`subtitles`] rendition may include the following characteristics: + /// "public.accessibility.transcribes-spoken-dialog", + /// "public.accessibility.describes-music-and-sound", and + /// "public.easy-to-read" (which indicates that the subtitles have + /// been edited for ease of reading). + /// + /// An AUDIO Rendition MAY include the following characteristic: + /// "public.accessibility.describes-video". + /// + /// The characteristics attribute may include private UTIs. + /// + /// [`UTI`]: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#ref-UTI + /// [`subtitles`]: crate::types::MediaType::Subtitles #[builder(setter(strip_option), default)] characteristics: Option, - /// Sets the parameters of the rendition. + /// The [`Channels`]. #[builder(setter(strip_option), default)] channels: Option, } @@ -173,495 +203,6 @@ impl ExtXMedia { /// Returns a builder for [`ExtXMedia`]. pub fn builder() -> ExtXMediaBuilder { ExtXMediaBuilder::default() } - - /// Returns the type of the media, associated with this tag. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// assert_eq!( - /// ExtXMedia::new(MediaType::Audio, "audio", "name").media_type(), - /// MediaType::Audio - /// ); - /// ``` - pub const fn media_type(&self) -> MediaType { self.media_type } - - /// Sets the type of the media, associated with this tag. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// - /// media.set_media_type(MediaType::Video); - /// - /// assert_eq!(media.media_type(), MediaType::Video); - /// ``` - pub fn set_media_type(&mut self, value: MediaType) -> &mut Self { - self.media_type = value; - self - } - - /// Returns the identifier that specifies the group to which the rendition - /// belongs. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// assert_eq!( - /// ExtXMedia::new(MediaType::Audio, "audio", "name").group_id(), - /// &"audio".to_string() - /// ); - /// ``` - pub const fn group_id(&self) -> &String { &self.group_id } - - /// Sets the identifier that specifies the group, to which the rendition - /// belongs. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// - /// media.set_group_id("video"); - /// - /// assert_eq!(media.group_id(), &"video".to_string()); - /// ``` - pub fn set_group_id>(&mut self, value: T) -> &mut Self { - self.group_id = value.into(); - self - } - - /// Returns a human-readable description of the rendition. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// assert_eq!( - /// ExtXMedia::new(MediaType::Audio, "audio", "name").name(), - /// &"name".to_string() - /// ); - /// ``` - pub const fn name(&self) -> &String { &self.name } - - /// Sets a human-readable description of the rendition. - /// - /// # Note - /// - /// If the [`language`] attribute is present, this attribute should be in - /// that language. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// - /// media.set_name("new_name"); - /// - /// assert_eq!(media.name(), &"new_name".to_string()); - /// ``` - /// - /// [`language`]: #method.language - pub fn set_name>(&mut self, value: T) -> &mut Self { - self.name = value.into(); - self - } - - /// Returns the `URI`, that identifies the [`Media Playlist`]. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.uri(), &None); - /// - /// media.set_uri(Some("https://www.example.com/")); - /// - /// assert_eq!(media.uri(), &Some("https://www.example.com/".into())); - /// ``` - /// - /// [`Media Playlist`]: crate::MediaPlaylist - pub const fn uri(&self) -> &Option { &self.uri } - - /// Sets the `URI`, that identifies the [`Media Playlist`]. - /// - /// # Note - /// - /// This attribute is **required**, if the [`MediaType`] is - /// [`MediaType::Subtitles`]. This attribute is **not allowed**, if the - /// [`MediaType`] is [`MediaType::ClosedCaptions`]. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.uri(), &None); - /// - /// media.set_uri(Some("https://www.example.com/")); - /// - /// assert_eq!(media.uri(), &Some("https://www.example.com/".into())); - /// ``` - /// - /// [`Media Playlist`]: crate::MediaPlaylist - pub fn set_uri>(&mut self, value: Option) -> &mut Self { - self.uri = value.map(Into::into); - self - } - - /// Returns the name of the primary language used in the rendition. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.language(), &None); - /// - /// media.set_language(Some("english")); - /// - /// assert_eq!(media.language(), &Some("english".into())); - /// ``` - pub const fn language(&self) -> &Option { &self.language } - - /// Sets the name of the primary language used in the rendition. - /// The value has to conform to [`RFC5646`]. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.language(), &None); - /// - /// media.set_language(Some("english")); - /// - /// assert_eq!(media.language(), &Some("english".into())); - /// ``` - /// - /// [`RFC5646`]: https://tools.ietf.org/html/rfc5646 - pub fn set_language>(&mut self, value: Option) -> &mut Self { - self.language = value.map(Into::into); - self - } - - /// Returns the name of a language associated with the rendition. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.assoc_language(), &None); - /// - /// media.set_assoc_language(Some("spanish")); - /// - /// assert_eq!(media.assoc_language(), &Some("spanish".into())); - /// ``` - pub const fn assoc_language(&self) -> &Option { &self.assoc_language } - - /// Sets the name of a language associated with the rendition. - /// An associated language is often used in a different role, than the - /// language specified by the [`language`] attribute (e.g., written versus - /// spoken, or a fallback dialect). - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.assoc_language(), &None); - /// - /// media.set_assoc_language(Some("spanish")); - /// - /// assert_eq!(media.assoc_language(), &Some("spanish".into())); - /// ``` - /// - /// [`language`]: #method.language - pub fn set_assoc_language>(&mut self, value: Option) -> &mut Self { - self.assoc_language = value.map(Into::into); - self - } - - /// Returns whether this is the `default` rendition. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.is_default(), false); - /// - /// media.set_default(true); - /// - /// assert_eq!(media.is_default(), true); - /// ``` - pub const fn is_default(&self) -> bool { self.is_default } - - /// Sets the `default` flag. - /// A value of `true` indicates, that the client should play - /// this rendition of the content in the absence of information - /// from the user indicating a different choice. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.is_default(), false); - /// - /// media.set_default(true); - /// - /// assert_eq!(media.is_default(), true); - /// ``` - pub fn set_default(&mut self, value: bool) -> &mut Self { - self.is_default = value; - self - } - - /// Returns whether the client may choose to - /// play this rendition in the absence of explicit user preference. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.is_autoselect(), false); - /// - /// media.set_autoselect(true); - /// - /// assert_eq!(media.is_autoselect(), true); - /// ``` - pub const fn is_autoselect(&self) -> bool { self.is_autoselect } - - /// Sets the `autoselect` flag. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.is_autoselect(), false); - /// - /// media.set_autoselect(true); - /// - /// assert_eq!(media.is_autoselect(), true); - /// ``` - pub fn set_autoselect(&mut self, value: bool) -> &mut Self { - self.is_autoselect = value; - self - } - - /// Returns whether the rendition contains content that is considered - /// essential to play. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.is_forced(), false); - /// - /// media.set_forced(true); - /// - /// assert_eq!(media.is_forced(), true); - /// ``` - pub const fn is_forced(&self) -> bool { self.is_forced } - - /// Sets the `forced` flag. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.is_forced(), false); - /// - /// media.set_forced(true); - /// - /// assert_eq!(media.is_forced(), true); - /// ``` - pub fn set_forced(&mut self, value: bool) -> &mut Self { - self.is_forced = value; - self - } - - /// Returns the identifier that specifies a rendition within the segments in - /// the [`Media Playlist`]. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::{InStreamId, MediaType}; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.instream_id(), None); - /// - /// media.set_instream_id(Some(InStreamId::Cc1)); - /// - /// assert_eq!(media.instream_id(), Some(InStreamId::Cc1)); - /// ``` - /// - /// [`Media Playlist`]: crate::MediaPlaylist - pub const fn instream_id(&self) -> Option { self.instream_id } - - /// Sets the [`InStreamId`], that specifies a rendition within the - /// segments in the [`Media Playlist`]. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::{InStreamId, MediaType}; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.instream_id(), None); - /// - /// media.set_instream_id(Some(InStreamId::Cc1)); - /// - /// assert_eq!(media.instream_id(), Some(InStreamId::Cc1)); - /// ``` - pub fn set_instream_id(&mut self, value: Option) -> &mut Self { - self.instream_id = value; - self - } - - /// Returns a string that represents uniform type identifiers (UTI). - /// - /// Each UTI indicates an individual characteristic of the rendition. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.characteristics(), &None); - /// - /// media.set_characteristics(Some("characteristic")); - /// - /// assert_eq!(media.characteristics(), &Some("characteristic".into())); - /// ``` - pub const fn characteristics(&self) -> &Option { &self.characteristics } - - /// Sets the characteristics attribute, containing one or more Uniform Type - /// Identifiers separated by comma. - /// Each [`UTI`] indicates an individual characteristic of the Rendition. - /// - /// A [`subtitles`] Rendition may include the following characteristics: - /// "public.accessibility.transcribes-spoken-dialog", - /// "public.accessibility.describes-music-and-sound", and - /// "public.easy-to-read" (which indicates that the subtitles have - /// been edited for ease of reading). - /// - /// An AUDIO Rendition MAY include the following characteristic: - /// "public.accessibility.describes-video". - /// - /// The characteristics attribute may include private UTIs. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::MediaType; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.characteristics(), &None); - /// - /// media.set_characteristics(Some("characteristic")); - /// - /// assert_eq!(media.characteristics(), &Some("characteristic".into())); - /// ``` - /// - /// [`UTI`]: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#ref-UTI - /// [`subtitles`]: crate::types::MediaType::Subtitles - pub fn set_characteristics>(&mut self, value: Option) -> &mut Self { - self.characteristics = value.map(Into::into); - self - } - - /// Returns the channels. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::{Channels, MediaType}; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.channels(), &None); - /// - /// media.set_channels(Some(Channels::new(6))); - /// - /// assert_eq!(media.channels(), &Some(Channels::new(6))); - /// ``` - pub const fn channels(&self) -> &Option { &self.channels } - - /// Sets the channels. - /// - /// # Example - /// - /// ``` - /// # use hls_m3u8::tags::ExtXMedia; - /// use hls_m3u8::types::{Channels, MediaType}; - /// - /// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name"); - /// # assert_eq!(media.channels(), &None); - /// - /// media.set_channels(Some(Channels::new(6))); - /// - /// assert_eq!(media.channels(), &Some(Channels::new(6))); - /// ``` - pub fn set_channels>(&mut self, value: Option) -> &mut Self { - self.channels = value.map(Into::into); - self - } } impl RequiredVersion for ExtXMedia { diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index d73e9ab..206b4da 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -2,6 +2,7 @@ use std::fmt; use std::str::FromStr; use derive_builder::Builder; +use shorthand::ShortHand; use crate::attribute::AttributePairs; use crate::types::ProtocolVersion; @@ -34,26 +35,31 @@ pub enum SessionData { /// /// [`Master Playlist`]: crate::MasterPlaylist /// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4 -#[derive(Builder, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] +#[derive(ShortHand, Builder, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] #[builder(setter(into))] +#[shorthand(enable(must_use, into))] pub struct ExtXSessionData { - /// The identifier of the data. - /// For more information look [`here`]. + /// Sets the `data_id` attribute, that should conform to a [reverse DNS] + /// naming convention, such as `com.example.movie.title`. /// /// # Note + /// + /// There is no central registration authority, so a value + /// should be choosen, that is unlikely to collide with others. + /// /// This field is required. /// - /// [`here`]: ExtXSessionData::set_data_id + /// [reverse DNS]: https://en.wikipedia.org/wiki/Reverse_domain_name_notation data_id: String, /// The data associated with the [`data_id`]. /// For more information look [`here`](SessionData). /// /// # Note - /// This field is required. /// - /// [`data_id`]: ExtXSessionDataBuilder::data_id + /// This field is required. data: SessionData, - /// The language of the [`data`](ExtXSessionDataBuilder::data). + /// The `language` attribute identifies the language of [`SessionData`]. + /// See [rfc5646](https://tools.ietf.org/html/rfc5646). #[builder(setter(into, strip_option), default)] language: Option, } @@ -107,6 +113,7 @@ impl ExtXSessionData { /// Makes a new [`ExtXSessionData`] tag, with the given language. /// /// # Example + /// /// ``` /// use hls_m3u8::tags::{ExtXSessionData, SessionData}; /// @@ -123,125 +130,9 @@ impl ExtXSessionData { language: Some(language.to_string()), } } - - /// Returns the `data_id`, that identifies a `data_value`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::{ExtXSessionData, SessionData}; - /// # - /// let data = ExtXSessionData::new( - /// "com.example.movie.title", - /// SessionData::Value("some data".to_string()), - /// ); - /// - /// assert_eq!(data.data_id(), &"com.example.movie.title".to_string()) - /// ``` - pub const fn data_id(&self) -> &String { &self.data_id } - - /// Returns the `data`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::{ExtXSessionData, SessionData}; - /// # - /// let data = ExtXSessionData::new( - /// "com.example.movie.title", - /// SessionData::Value("some data".to_string()), - /// ); - /// - /// assert_eq!(data.data(), &SessionData::Value("some data".to_string())) - /// ``` - pub const fn data(&self) -> &SessionData { &self.data } - - /// Returns the `language` tag, that identifies the language of - /// [`SessionData`]. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::{ExtXSessionData, SessionData}; - /// # - /// let data = ExtXSessionData::with_language( - /// "com.example.movie.title", - /// SessionData::Value("some data".to_string()), - /// "english", - /// ); - /// - /// assert_eq!(data.language(), &Some("english".to_string())) - /// ``` - pub const fn language(&self) -> &Option { &self.language } - - /// Sets the `language` attribute, that identifies the language of - /// [`SessionData`]. See [rfc5646](https://tools.ietf.org/html/rfc5646). - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::{ExtXSessionData, SessionData}; - /// # - /// let mut data = ExtXSessionData::new( - /// "com.example.movie.title", - /// SessionData::Value("some data".to_string()), - /// ); - /// - /// assert_eq!(data.language(), &None); - /// - /// data.set_language(Some("english")); - /// assert_eq!(data.language(), &Some("english".to_string())); - /// ``` - pub fn set_language(&mut self, value: Option) -> &mut Self { - self.language = value.map(|v| v.to_string()); - self - } - - /// Sets the `data_id` attribute, that should conform to a [reverse DNS] - /// naming convention, such as `com.example.movie.title`. - /// - /// # Note: - /// There is no central registration authority, so a value - /// should be choosen, that is unlikely to collide with others. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::{ExtXSessionData, SessionData}; - /// # - /// let mut data = ExtXSessionData::new( - /// "com.example.movie.title", - /// SessionData::Value("some data".to_string()), - /// ); - /// - /// assert_eq!(data.data_id(), &"com.example.movie.title".to_string()); - /// - /// data.set_data_id("com.other.movie.title"); - /// assert_eq!(data.data_id(), &"com.other.movie.title".to_string()); - /// ``` - /// [reverse DNS]: https://en.wikipedia.org/wiki/Reverse_domain_name_notation - pub fn set_data_id(&mut self, value: T) -> &mut Self { - self.data_id = value.to_string(); - self - } - - /// Sets the [`data`](ExtXSessionData::data) of this tag. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::{ExtXSessionData, SessionData}; - /// # - /// let mut data = ExtXSessionData::new( - /// "com.example.movie.title", - /// SessionData::Value("some data".to_string()), - /// ); - /// - /// assert_eq!(data.data(), &SessionData::Value("some data".to_string())); - /// - /// data.set_data(SessionData::Value("new data".to_string())); - /// assert_eq!(data.data(), &SessionData::Value("new data".to_string())); - /// ``` - pub fn set_data(&mut self, value: SessionData) -> &mut Self { - self.data = value; - self - } } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXSessionData { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } } diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index 6bd3731..0f23184 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -7,16 +7,12 @@ use crate::utils::tag; use crate::{Error, RequiredVersion}; /// # [4.3.4.5. EXT-X-SESSION-KEY] +/// /// The [`ExtXSessionKey`] tag allows encryption keys from [`Media Playlist`]s /// to be specified in a [`Master Playlist`]. This allows the client to /// preload these keys without having to read the [`Media Playlist`]s /// first. /// -/// Its format is: -/// ```text -/// #EXT-X-SESSION-KEY: -/// ``` -/// /// [`Media Playlist`]: crate::MediaPlaylist /// [`Master Playlist`]: crate::MasterPlaylist /// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5 @@ -29,12 +25,14 @@ impl ExtXSessionKey { /// Makes a new [`ExtXSessionKey`] tag. /// /// # Panic + /// /// An [`ExtXSessionKey`] should only be used, /// if the segments of the stream are encrypted. /// Therefore this function will panic, /// if the `method` is [`EncryptionMethod::None`]. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXSessionKey; /// use hls_m3u8::types::EncryptionMethod; @@ -50,6 +48,8 @@ impl ExtXSessionKey { } } +/// This tag requires the version returned by +/// [`DecryptionKey::required_version`]. impl RequiredVersion for ExtXSessionKey { fn required_version(&self) -> ProtocolVersion { self.0.required_version() } } @@ -57,8 +57,10 @@ impl RequiredVersion for ExtXSessionKey { impl fmt::Display for ExtXSessionKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.0.method == EncryptionMethod::None { + // TODO: this is bad practice, this function should never fail! return Err(fmt::Error); } + write!(f, "{}{}", Self::PREFIX, self.0) } } @@ -159,10 +161,10 @@ mod test { ); } - #[test] - #[should_panic] // ExtXSessionKey::new should panic, if the provided // EncryptionMethod is None! + #[test] + #[should_panic] fn test_new_panic() { ExtXSessionKey::new(EncryptionMethod::None, ""); } #[test] @@ -176,7 +178,7 @@ mod test { let key = ExtXSessionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); assert_eq!(key.method(), EncryptionMethod::Aes128); - assert_eq!(key.uri(), &Some("https://www.example.com/".into())); + assert_eq!(key.uri(), Some(&"https://www.example.com/".into())); } #[test] @@ -186,6 +188,6 @@ mod test { key.set_method(EncryptionMethod::None); assert_eq!(key.method(), EncryptionMethod::None); key.set_uri(Some("https://www.github.com/")); - assert_eq!(key.uri(), &Some("https://www.github.com/".into())); + assert_eq!(key.uri(), Some(&"https://www.github.com/".into())); } } diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index 7fcb885..df7b350 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -2,6 +2,8 @@ use std::fmt; use std::ops::{Deref, DerefMut}; use std::str::FromStr; +use shorthand::ShortHand; + use crate::attribute::AttributePairs; use crate::types::{ ClosedCaptions, DecimalFloatingPoint, HdcpLevel, ProtocolVersion, StreamInf, StreamInfBuilder, @@ -21,13 +23,20 @@ use crate::{Error, RequiredVersion}; /// Renditions SHOULD play this Rendition. /// /// [4.3.4.2. EXT-X-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.2 -#[derive(PartialOrd, Debug, Clone, PartialEq)] +#[derive(ShortHand, PartialOrd, Debug, Clone, PartialEq)] +#[shorthand(enable(must_use, into))] pub struct ExtXStreamInf { + /// The `URI` that identifies the associated media playlist. uri: String, + #[shorthand(enable(skip))] frame_rate: Option, + /// The group identifier for the audio in the variant stream. audio: Option, + /// The group identifier for the subtitles in the variant stream. subtitles: Option, + /// The value of the [`ClosedCaptions`] attribute. closed_captions: Option, + #[shorthand(enable(skip))] stream_inf: StreamInf, } @@ -135,6 +144,7 @@ impl ExtXStreamInf { /// Creates a new [`ExtXStreamInf`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXStreamInf; /// let stream = ExtXStreamInf::new("https://www.example.com/", 20); @@ -153,149 +163,20 @@ impl ExtXStreamInf { /// Returns a builder for [`ExtXStreamInf`]. pub fn builder() -> ExtXStreamInfBuilder { ExtXStreamInfBuilder::default() } - /// Returns the `URI` that identifies the associated media playlist. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// - /// assert_eq!(stream.uri(), &"https://www.example.com/".to_string()); - /// ``` - pub const fn uri(&self) -> &String { &self.uri } + /// The maximum frame rate for all the video in the variant stream. + #[must_use] + #[inline] + pub fn frame_rate(&self) -> Option { self.frame_rate.map(DecimalFloatingPoint::as_f64) } - /// Sets the `URI` that identifies the associated media playlist. + /// The maximum frame rate for all the video in the variant stream. /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); + /// # Panic /// - /// stream.set_uri("https://www.google.com/"); - /// assert_eq!(stream.uri(), &"https://www.google.com/".to_string()); - /// ``` - pub fn set_uri(&mut self, value: T) -> &mut Self { - self.uri = value.to_string(); - self - } - - /// Sets the maximum frame rate for all the video in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.frame_rate(), None); - /// - /// stream.set_frame_rate(Some(59.9)); - /// assert_eq!(stream.frame_rate(), Some(59.9)); - /// ``` - pub fn set_frame_rate(&mut self, value: Option) -> &mut Self { - self.frame_rate = value.map(Into::into); - self - } - - /// Returns the maximum frame rate for all the video in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.frame_rate(), None); - /// - /// stream.set_frame_rate(Some(59.9)); - /// assert_eq!(stream.frame_rate(), Some(59.9)); - /// ``` - pub fn frame_rate(&self) -> Option { self.frame_rate.map(|v| v.as_f64()) } - - /// Returns the group identifier for the audio in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.audio(), &None); - /// - /// stream.set_audio(Some("audio")); - /// assert_eq!(stream.audio(), &Some("audio".to_string())); - /// ``` - pub const fn audio(&self) -> &Option { &self.audio } - - /// Sets the group identifier for the audio in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.audio(), &None); - /// - /// stream.set_audio(Some("audio")); - /// assert_eq!(stream.audio(), &Some("audio".to_string())); - /// ``` - pub fn set_audio>(&mut self, value: Option) -> &mut Self { - self.audio = value.map(Into::into); - self - } - - /// Returns the group identifier for the subtitles in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.subtitles(), &None); - /// - /// stream.set_subtitles(Some("subs")); - /// assert_eq!(stream.subtitles(), &Some("subs".to_string())); - /// ``` - pub const fn subtitles(&self) -> &Option { &self.subtitles } - - /// Sets the group identifier for the subtitles in the variant stream. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.subtitles(), &None); - /// - /// stream.set_subtitles(Some("subs")); - /// assert_eq!(stream.subtitles(), &Some("subs".to_string())); - /// ``` - pub fn set_subtitles>(&mut self, value: Option) -> &mut Self { - self.subtitles = value.map(Into::into); - self - } - - /// Returns the value of [`ClosedCaptions`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// use hls_m3u8::types::ClosedCaptions; - /// - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.closed_captions(), &None); - /// - /// stream.set_closed_captions(Some(ClosedCaptions::None)); - /// assert_eq!(stream.closed_captions(), &Some(ClosedCaptions::None)); - /// ``` - pub const fn closed_captions(&self) -> &Option { &self.closed_captions } - - /// Sets the value of [`ClosedCaptions`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXStreamInf; - /// use hls_m3u8::types::ClosedCaptions; - /// - /// let mut stream = ExtXStreamInf::new("https://www.example.com/", 20); - /// # assert_eq!(stream.closed_captions(), &None); - /// - /// stream.set_closed_captions(Some(ClosedCaptions::None)); - /// assert_eq!(stream.closed_captions(), &Some(ClosedCaptions::None)); - /// ``` - pub fn set_closed_captions(&mut self, value: Option) -> &mut Self { - self.closed_captions = value; + /// This function panics, if the float is infinite or negative. + pub fn set_frame_rate>(&mut self, value_0: Option) -> &mut Self { + self.frame_rate = value_0.map(|v| { + DecimalFloatingPoint::new(v.into()).expect("the float must be positive and finite") + }); self } } @@ -308,18 +189,23 @@ impl RequiredVersion for ExtXStreamInf { impl fmt::Display for ExtXStreamInf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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.audio { write!(f, ",AUDIO={}", quote(value))?; } + if let Some(value) = &self.subtitles { write!(f, ",SUBTITLES={}", quote(value))?; } + if let Some(value) = &self.closed_captions { write!(f, ",CLOSED-CAPTIONS={}", value)?; } + write!(f, "\n{}", self.uri)?; Ok(()) } diff --git a/src/tags/media_playlist/discontinuity_sequence.rs b/src/tags/media_playlist/discontinuity_sequence.rs index c49b9d7..9869dee 100644 --- a/src/tags/media_playlist/discontinuity_sequence.rs +++ b/src/tags/media_playlist/discontinuity_sequence.rs @@ -11,12 +11,6 @@ use crate::RequiredVersion; /// different Renditions of the same Variant Stream or different Variant /// Streams that have [`ExtXDiscontinuity`] tags in their [`Media Playlist`]s. /// -/// Its format is: -/// ```text -/// #EXT-X-DISCONTINUITY-SEQUENCE: -/// ``` -/// where `number` is a [u64]. -/// /// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity /// [`Media Playlist`]: crate::MediaPlaylist /// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]: @@ -30,6 +24,7 @@ impl ExtXDiscontinuitySequence { /// Makes a new [ExtXDiscontinuitySequence] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXDiscontinuitySequence; /// let discontinuity_sequence = ExtXDiscontinuitySequence::new(5); @@ -40,6 +35,7 @@ impl ExtXDiscontinuitySequence { /// the first media segment that appears in the associated playlist. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXDiscontinuitySequence; /// let discontinuity_sequence = ExtXDiscontinuitySequence::new(5); @@ -51,6 +47,7 @@ impl ExtXDiscontinuitySequence { /// Sets the sequence number. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXDiscontinuitySequence; /// let mut discontinuity_sequence = ExtXDiscontinuitySequence::new(5); @@ -64,6 +61,7 @@ impl ExtXDiscontinuitySequence { } } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXDiscontinuitySequence { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } } diff --git a/src/tags/media_playlist/end_list.rs b/src/tags/media_playlist/end_list.rs index 5463f5e..3202870 100644 --- a/src/tags/media_playlist/end_list.rs +++ b/src/tags/media_playlist/end_list.rs @@ -6,14 +6,10 @@ use crate::utils::tag; use crate::{Error, RequiredVersion}; /// # [4.4.3.4. EXT-X-ENDLIST] +/// /// The [`ExtXEndList`] tag indicates, that no more [`Media Segment`]s will be /// added to the [`Media Playlist`] file. /// -/// Its format is: -/// ```text -/// #EXT-X-ENDLIST -/// ``` -/// /// [`Media Segment`]: crate::MediaSegment /// [`Media Playlist`]: crate::MediaPlaylist /// [4.4.3.4. EXT-X-ENDLIST]: @@ -25,6 +21,7 @@ impl ExtXEndList { pub(crate) const PREFIX: &'static str = "#EXT-X-ENDLIST"; } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXEndList { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } } diff --git a/src/tags/media_playlist/i_frames_only.rs b/src/tags/media_playlist/i_frames_only.rs index c9c4dc9..d7334cb 100644 --- a/src/tags/media_playlist/i_frames_only.rs +++ b/src/tags/media_playlist/i_frames_only.rs @@ -6,17 +6,13 @@ use crate::utils::tag; use crate::{Error, RequiredVersion}; /// # [4.4.3.6. EXT-X-I-FRAMES-ONLY] +/// /// The [`ExtXIFramesOnly`] tag indicates that each [`Media Segment`] in the /// Playlist describes a single I-frame. I-frames are encoded video /// frames, whose decoding does not depend on any other frame. I-frame /// Playlists can be used for trick play, such as fast forward, rapid /// reverse, and scrubbing. /// -/// Its format is: -/// ```text -/// #EXT-X-I-FRAMES-ONLY -/// ``` -/// /// [`Media Segment`]: crate::MediaSegment /// [4.4.3.6. EXT-X-I-FRAMES-ONLY]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.6 @@ -27,6 +23,7 @@ impl ExtXIFramesOnly { pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAMES-ONLY"; } +/// This tag requires [`ProtocolVersion::V4`]. impl RequiredVersion for ExtXIFramesOnly { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V4 } } diff --git a/src/tags/media_playlist/media_sequence.rs b/src/tags/media_playlist/media_sequence.rs index 5e26eb7..dfac8b7 100644 --- a/src/tags/media_playlist/media_sequence.rs +++ b/src/tags/media_playlist/media_sequence.rs @@ -6,6 +6,7 @@ use crate::utils::tag; use crate::{Error, RequiredVersion}; /// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE] +/// /// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of /// the first [`Media Segment`] that appears in a Playlist file. /// @@ -21,6 +22,7 @@ impl ExtXMediaSequence { /// Makes a new [`ExtXMediaSequence`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXMediaSequence; /// let media_sequence = ExtXMediaSequence::new(5); @@ -31,6 +33,7 @@ impl ExtXMediaSequence { /// that appears in the associated playlist. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXMediaSequence; /// let media_sequence = ExtXMediaSequence::new(5); @@ -42,6 +45,7 @@ impl ExtXMediaSequence { /// Sets the sequence number. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXMediaSequence; /// let mut media_sequence = ExtXMediaSequence::new(5); diff --git a/src/tags/media_playlist/target_duration.rs b/src/tags/media_playlist/target_duration.rs index 652afb4..0165154 100644 --- a/src/tags/media_playlist/target_duration.rs +++ b/src/tags/media_playlist/target_duration.rs @@ -8,11 +8,13 @@ use crate::utils::tag; use crate::{Error, RequiredVersion}; /// # [4.3.3.1. EXT-X-TARGETDURATION] +/// /// The [`ExtXTargetDuration`] tag specifies the maximum [`MediaSegment`] /// duration. /// /// [`MediaSegment`]: crate::MediaSegment -/// [4.3.3.1. EXT-X-TARGETDURATION]: https://tools.ietf.org/html/rfc8216#section-4.3.3.1 +/// [4.3.3.1. EXT-X-TARGETDURATION]: +/// https://tools.ietf.org/html/rfc8216#section-4.3.3.1 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct ExtXTargetDuration(Duration); @@ -22,6 +24,7 @@ impl ExtXTargetDuration { /// Makes a new [`ExtXTargetDuration`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXTargetDuration; /// use std::time::Duration; @@ -30,12 +33,14 @@ impl ExtXTargetDuration { /// ``` /// /// # Note + /// /// The nanoseconds part of the [`Duration`] will be discarded. pub const fn new(duration: Duration) -> Self { Self(Duration::from_secs(duration.as_secs())) } /// Returns the maximum media segment duration. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXTargetDuration; /// use std::time::Duration; diff --git a/src/tags/media_segment/byte_range.rs b/src/tags/media_segment/byte_range.rs index f2aafb4..3a04489 100644 --- a/src/tags/media_segment/byte_range.rs +++ b/src/tags/media_segment/byte_range.rs @@ -11,15 +11,6 @@ use crate::{Error, RequiredVersion}; /// The [`ExtXByteRange`] tag indicates that a [`Media Segment`] is a sub-range /// of the resource identified by its `URI`. /// -/// Its format is: -/// ```text -/// #EXT-X-BYTERANGE:[@] -/// ``` -/// -/// where `n` is a [usize] indicating the length of the sub-range in bytes. -/// If present, `o` is a [usize] indicating the start of the sub-range, -/// as a byte offset from the beginning of the resource. -/// /// [`Media Segment`]: crate::MediaSegment /// [4.4.2.2. EXT-X-BYTERANGE]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.2 @@ -32,6 +23,7 @@ impl ExtXByteRange { /// Makes a new [`ExtXByteRange`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXByteRange; /// let byte_range = ExtXByteRange::new(20, Some(5)); @@ -43,6 +35,7 @@ impl ExtXByteRange { /// Converts the [`ExtXByteRange`] to a [`ByteRange`]. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXByteRange; /// use hls_m3u8::types::ByteRange; @@ -53,6 +46,7 @@ impl ExtXByteRange { pub const fn to_range(&self) -> ByteRange { self.0 } } +/// This tag requires [`ProtocolVersion::V4`]. impl RequiredVersion for ExtXByteRange { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V4 } } diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index 4374ef7..61a2bfd 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -5,6 +5,7 @@ use std::time::Duration; use chrono::{DateTime, FixedOffset, SecondsFormat}; use derive_builder::Builder; +use shorthand::ShortHand; use crate::attribute::AttributePairs; use crate::types::{ProtocolVersion, Value}; @@ -12,84 +13,98 @@ use crate::utils::{quote, tag, unquote}; use crate::{Error, RequiredVersion}; /// # [4.3.2.7. EXT-X-DATERANGE] +/// /// The [`ExtXDateRange`] tag associates a date range (i.e., a range of /// time defined by a starting and ending date) with a set of attribute/ /// value pairs. /// /// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7 -#[derive(Builder, Debug, Clone, PartialEq, PartialOrd)] +#[derive(ShortHand, Builder, Debug, Clone, PartialEq, PartialOrd)] #[builder(setter(into))] +#[shorthand(enable(must_use, into))] pub struct ExtXDateRange { /// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist. /// /// # Note + /// /// This attribute is required. id: String, - #[builder(setter(strip_option), default)] /// A client-defined string that specifies some set of attributes and their /// associated value semantics. All [`ExtXDateRange`]s with the same class /// attribute value must adhere to these semantics. /// /// # Note + /// /// This attribute is optional. + #[builder(setter(strip_option), default)] class: Option, /// The date at which the [`ExtXDateRange`] begins. /// /// # Note + /// /// This attribute is required. start_date: DateTime, - #[builder(setter(strip_option), default)] /// The date at which the [`ExtXDateRange`] ends. It must be equal to or /// later than the value of the [`start-date`] attribute. /// /// # Note + /// /// This attribute is optional. /// /// [`start-date`]: #method.start_date - end_date: Option>, #[builder(setter(strip_option), default)] + end_date: Option>, /// The duration of the [`ExtXDateRange`]. A single instant in time (e.g., /// crossing a finish line) should be represented with a duration of 0. /// /// # Note + /// /// This attribute is optional. - duration: Option, #[builder(setter(strip_option), default)] + duration: Option, /// The expected duration of the [`ExtXDateRange`]. /// This attribute should be used to indicate the expected duration of a /// [`ExtXDateRange`] whose actual duration is not yet known. /// /// # Note + /// /// This attribute is optional. + #[builder(setter(strip_option), default)] planned_duration: Option, - #[builder(setter(strip_option), default)] - /// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1 + /// You can read about this attribute here + /// /// /// # Note + /// /// This attribute is optional. + #[builder(setter(strip_option), default)] scte35_cmd: Option, - #[builder(setter(strip_option), default)] - /// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1 + /// You can read about this attribute here + /// /// /// # Note + /// /// This attribute is optional. + #[builder(setter(strip_option), default)] scte35_out: Option, - #[builder(setter(strip_option), default)] - /// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1 + /// You can read about this attribute here + /// /// /// # Note + /// /// This attribute is optional. + #[builder(setter(strip_option), default)] scte35_in: Option, - #[builder(default)] /// This attribute indicates that the end of the range containing it is /// equal to the [`start-date`] of its following range. The following range /// is the [`ExtXDateRange`] of the same class, that has the earliest /// [`start-date`] after the [`start-date`] of the range in question. /// /// # Note + /// /// This attribute is optional. - end_on_next: bool, #[builder(default)] + end_on_next: bool, /// The `"X-"` prefix defines a namespace reserved for client-defined /// attributes. The client-attribute must be a uppercase characters. /// Clients should use a reverse-DNS syntax when defining their own @@ -97,7 +112,10 @@ pub struct ExtXDateRange { /// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`. /// /// # Note + /// /// This attribute is optional. + #[builder(default)] + #[shorthand(enable(collection_magic, get_mut))] client_attributes: BTreeMap, } @@ -127,6 +145,7 @@ impl ExtXDateRange { /// Makes a new [`ExtXDateRange`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXDateRange; /// use chrono::offset::TimeZone; @@ -159,535 +178,6 @@ impl ExtXDateRange { /// Returns a builder for [`ExtXDateRange`]. pub fn builder() -> ExtXDateRangeBuilder { ExtXDateRangeBuilder::default() } - - /// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// - /// assert_eq!(date_range.id(), &"id".to_string()); - /// ``` - pub const fn id(&self) -> &String { &self.id } - - /// A string that uniquely identifies an [`ExtXDateRange`] in the Playlist. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// - /// date_range.set_id("new_id"); - /// assert_eq!(date_range.id(), &"new_id".to_string()); - /// ``` - pub fn set_id(&mut self, value: T) -> &mut Self { - self.id = value.to_string(); - self - } - - /// A client-defined string that specifies some set of attributes and their - /// associated value semantics. All [`ExtXDateRange`]s with the same class - /// attribute value must adhere to these semantics. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.class(), &None); - /// - /// date_range.set_class(Some("example_class")); - /// assert_eq!(date_range.class(), &Some("example_class".to_string())); - /// ``` - pub const fn class(&self) -> &Option { &self.class } - - /// A client-defined string that specifies some set of attributes and their - /// associated value semantics. All [`ExtXDateRange`]s with the same class - /// attribute value must adhere to these semantics. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.class(), &None); - /// - /// date_range.set_class(Some("example_class")); - /// assert_eq!(date_range.class(), &Some("example_class".to_string())); - /// ``` - pub fn set_class(&mut self, value: Option) -> &mut Self { - self.class = value.map(|v| v.to_string()); - self - } - - /// The date at which the [`ExtXDateRange`] begins. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// - /// assert_eq!( - /// date_range.start_date(), - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31) - /// ); - /// ``` - pub const fn start_date(&self) -> DateTime { self.start_date } - - /// The date at which the [`ExtXDateRange`] begins. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// - /// date_range.set_start_date( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10), - /// ); - /// assert_eq!( - /// date_range.start_date(), - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10) - /// ); - /// ``` - pub fn set_start_date(&mut self, value: T) -> &mut Self - where - T: Into>, - { - self.start_date = value.into(); - self - } - - /// The date at which the [`ExtXDateRange`] ends. It must be equal to or - /// later than the value of the [`start-date`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.end_date(), None); - /// - /// date_range.set_end_date(Some( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10), - /// )); - /// assert_eq!( - /// date_range.end_date(), - /// Some( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10) - /// ) - /// ); - /// ``` - pub const fn end_date(&self) -> Option> { self.end_date } - - /// The date at which the [`ExtXDateRange`] ends. It must be equal to or - /// later than the value of the [`start-date`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.end_date(), None); - /// - /// date_range.set_end_date(Some( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10), - /// )); - /// assert_eq!( - /// date_range.end_date(), - /// Some( - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 10, 10) - /// .and_hms_milli(10, 10, 10, 10) - /// ) - /// ); - /// ``` - pub fn set_end_date(&mut self, value: Option) -> &mut Self - where - T: Into>, - { - self.end_date = value.map(Into::into); - self - } - - /// The duration of the [`ExtXDateRange`]. A single instant in time (e.g., - /// crossing a finish line) should be represented with a duration of 0. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use std::time::Duration; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.duration(), None); - /// - /// date_range.set_duration(Some(Duration::from_secs_f64(1.234))); - /// assert_eq!(date_range.duration(), Some(Duration::from_secs_f64(1.234))); - /// ``` - pub const fn duration(&self) -> Option { self.duration } - - /// The duration of the [`ExtXDateRange`]. A single instant in time (e.g., - /// crossing a finish line) should be represented with a duration of 0. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use std::time::Duration; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.duration(), None); - /// - /// date_range.set_duration(Some(Duration::from_secs_f64(1.234))); - /// assert_eq!(date_range.duration(), Some(Duration::from_secs_f64(1.234))); - /// ``` - pub fn set_duration(&mut self, value: Option) -> &mut Self { - self.duration = value; - self - } - - /// The expected duration of the [`ExtXDateRange`]. - /// This attribute should be used to indicate the expected duration of a - /// [`ExtXDateRange`] whose actual duration is not yet known. - /// The date at which the [`ExtXDateRange`] ends. It must be equal to or - /// later than the value of the [`start-date`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use std::time::Duration; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.planned_duration(), None); - /// - /// date_range.set_planned_duration(Some(Duration::from_secs_f64(1.2345))); - /// assert_eq!( - /// date_range.planned_duration(), - /// Some(Duration::from_secs_f64(1.2345)) - /// ); - /// ``` - pub const fn planned_duration(&self) -> Option { self.planned_duration } - - /// The expected duration of the [`ExtXDateRange`]. - /// This attribute should be used to indicate the expected duration of a - /// [`ExtXDateRange`] whose actual duration is not yet known. - /// The date at which the [`ExtXDateRange`] ends. It must be equal to or - /// later than the value of the [`start-date`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use std::time::Duration; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.planned_duration(), None); - /// - /// date_range.set_planned_duration(Some(Duration::from_secs_f64(1.2345))); - /// assert_eq!( - /// date_range.planned_duration(), - /// Some(Duration::from_secs_f64(1.2345)) - /// ); - /// ``` - pub fn set_planned_duration(&mut self, value: Option) -> &mut Self { - self.planned_duration = value; - self - } - - /// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf - pub const fn scte35_cmd(&self) -> &Option { &self.scte35_cmd } - - /// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf - pub const fn scte35_in(&self) -> &Option { &self.scte35_in } - - /// See here for reference: https://www.scte.org/SCTEDocs/Standards/ANSI_SCTE%2035%202019r1.pdf - pub const fn scte35_out(&self) -> &Option { &self.scte35_out } - - /// This attribute indicates that the end of the range containing it is - /// equal to the [`start-date`] of its following range. The following range - /// is the [`ExtXDateRange`] of the same class, that has the earliest - /// [`start-date`] after the [`start-date`] of the range in question. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use std::time::Duration; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.end_on_next(), false); - /// - /// date_range.set_end_on_next(true); - /// assert_eq!(date_range.end_on_next(), true); - /// ``` - pub const fn end_on_next(&self) -> bool { self.end_on_next } - - /// This attribute indicates that the end of the range containing it is - /// equal to the [`start-date`] of its following range. The following range - /// is the [`ExtXDateRange`] of the same class, that has the earliest - /// [`start-date`] after the [`start-date`] of the range in question. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use std::time::Duration; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.end_on_next(), false); - /// - /// date_range.set_end_on_next(true); - /// assert_eq!(date_range.end_on_next(), true); - /// ``` - pub fn set_end_on_next(&mut self, value: bool) -> &mut Self { - self.end_on_next = value; - self - } - - /// The "X-" prefix defines a namespace reserved for client-defined - /// attributes. The client-attribute must be a uppercase characters. - /// Clients should use a reverse-DNS syntax when defining their own - /// attribute names to avoid collisions. An example of a client-defined - /// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use std::collections::BTreeMap; - /// - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use hls_m3u8::types::Value; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.client_attributes(), &BTreeMap::new()); - /// - /// let mut attributes = BTreeMap::new(); - /// attributes.insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1)); - /// - /// date_range.set_client_attributes(attributes.clone()); - /// assert_eq!(date_range.client_attributes(), &attributes); - /// ``` - pub const fn client_attributes(&self) -> &BTreeMap { &self.client_attributes } - - /// The "X-" prefix defines a namespace reserved for client-defined - /// attributes. The client-attribute must be a uppercase characters. - /// Clients should use a reverse-DNS syntax when defining their own - /// attribute names to avoid collisions. An example of a client-defined - /// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use std::collections::BTreeMap; - /// - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use hls_m3u8::types::Value; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.client_attributes(), &BTreeMap::new()); - /// - /// let mut attributes = BTreeMap::new(); - /// attributes.insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1)); - /// - /// date_range - /// .client_attributes_mut() - /// .insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1)); - /// - /// assert_eq!(date_range.client_attributes(), &attributes); - /// ``` - pub fn client_attributes_mut(&mut self) -> &mut BTreeMap { - &mut self.client_attributes - } - - /// The "X-" prefix defines a namespace reserved for client-defined - /// attributes. The client-attribute must be a uppercase characters. - /// Clients should use a reverse-DNS syntax when defining their own - /// attribute names to avoid collisions. An example of a client-defined - /// attribute is `X-COM-EXAMPLE-AD-ID="XYZ123"`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXDateRange; - /// use std::collections::BTreeMap; - /// - /// use chrono::offset::TimeZone; - /// use chrono::{DateTime, FixedOffset}; - /// use hls_m3u8::types::Value; - /// - /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds - /// - /// let mut date_range = ExtXDateRange::new( - /// "id", - /// FixedOffset::east(8 * HOURS_IN_SECS) - /// .ymd(2010, 2, 19) - /// .and_hms_milli(14, 54, 23, 31), - /// ); - /// # assert_eq!(date_range.client_attributes(), &BTreeMap::new()); - /// - /// let mut attributes = BTreeMap::new(); - /// attributes.insert("X-COM-EXAMPLE-FLOAT".to_string(), Value::Float(1.1)); - /// - /// date_range.set_client_attributes(attributes.clone()); - /// assert_eq!(date_range.client_attributes(), &attributes); - /// ``` - pub fn set_client_attributes(&mut self, value: BTreeMap) -> &mut Self { - self.client_attributes = value; - self - } } /// This tag requires [`ProtocolVersion::V1`]. @@ -776,6 +266,7 @@ impl fmt::Display for ExtXDateRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; write!(f, "ID={}", quote(&self.id))?; + if let Some(value) = &self.class { write!(f, ",CLASS={}", quote(value))?; } diff --git a/src/tags/media_segment/discontinuity.rs b/src/tags/media_segment/discontinuity.rs index 5a40451..588ed37 100644 --- a/src/tags/media_segment/discontinuity.rs +++ b/src/tags/media_segment/discontinuity.rs @@ -6,14 +6,10 @@ use crate::utils::tag; use crate::{Error, RequiredVersion}; /// # [4.4.2.3. EXT-X-DISCONTINUITY] +/// /// The [`ExtXDiscontinuity`] tag indicates a discontinuity between the /// [`Media Segment`] that follows it and the one that preceded it. /// -/// Its format is: -/// ```text -/// #EXT-X-DISCONTINUITY -/// ``` -/// /// [`Media Segment`]: crate::MediaSegment /// [4.4.2.3. EXT-X-DISCONTINUITY]: /// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.3 @@ -24,6 +20,7 @@ impl ExtXDiscontinuity { pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY"; } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXDiscontinuity { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } } diff --git a/src/tags/media_segment/inf.rs b/src/tags/media_segment/inf.rs index fc1cb51..60af89d 100644 --- a/src/tags/media_segment/inf.rs +++ b/src/tags/media_segment/inf.rs @@ -25,6 +25,7 @@ impl ExtInf { /// Makes a new [`ExtInf`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtInf; /// use std::time::Duration; @@ -41,6 +42,7 @@ impl ExtInf { /// Makes a new [`ExtInf`] tag with the given title. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtInf; /// use std::time::Duration; @@ -57,6 +59,7 @@ impl ExtInf { /// Returns the duration of the associated media segment. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtInf; /// use std::time::Duration; @@ -70,6 +73,7 @@ impl ExtInf { /// Sets the duration of the associated media segment. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtInf; /// use std::time::Duration; @@ -88,6 +92,7 @@ impl ExtInf { /// Returns the title of the associated media segment. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtInf; /// use std::time::Duration; @@ -101,6 +106,7 @@ impl ExtInf { /// Sets the title of the associated media segment. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtInf; /// use std::time::Duration; @@ -117,6 +123,8 @@ impl ExtInf { } } +/// This tag requires [`ProtocolVersion::V1`], if the duration does not have +/// nanoseconds, otherwise it requires [`ProtocolVersion::V3`]. impl RequiredVersion for ExtInf { fn required_version(&self) -> ProtocolVersion { if self.duration.subsec_nanos() == 0 { diff --git a/src/tags/media_segment/key.rs b/src/tags/media_segment/key.rs index 4ebe50b..b875861 100644 --- a/src/tags/media_segment/key.rs +++ b/src/tags/media_segment/key.rs @@ -15,6 +15,7 @@ use crate::{Error, RequiredVersion}; /// same [`KeyFormat`] attribute (or the end of the Playlist file). /// /// # Note +/// /// In case of an empty key ([`EncryptionMethod::None`]), /// all attributes will be ignored. /// @@ -31,6 +32,7 @@ impl ExtXKey { /// Makes a new [`ExtXKey`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::types::EncryptionMethod; @@ -49,6 +51,7 @@ impl ExtXKey { /// Makes a new [`ExtXKey`] tag without a decryption key. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXKey; /// let key = ExtXKey::empty(); @@ -69,6 +72,7 @@ impl ExtXKey { /// [`None`]. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::types::EncryptionMethod; @@ -82,6 +86,8 @@ impl ExtXKey { pub fn is_empty(&self) -> bool { self.0.method() == EncryptionMethod::None } } +/// This tag requires the [`ProtocolVersion`] returned by +/// [`DecryptionKey::required_version`]. impl RequiredVersion for ExtXKey { fn required_version(&self) -> ProtocolVersion { self.0.required_version() } } diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index e8237a6..ec8a50e 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -1,6 +1,8 @@ use std::fmt; use std::str::FromStr; +use shorthand::ShortHand; + use crate::attribute::AttributePairs; use crate::tags::ExtXKey; use crate::types::{ByteRange, ProtocolVersion}; @@ -14,10 +16,45 @@ use crate::{Encrypted, Error, RequiredVersion}; /// /// [`MediaSegment`]: crate::MediaSegment /// [4.3.2.5. EXT-X-MAP]: https://tools.ietf.org/html/rfc8216#section-4.3.2.5 -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(ShortHand, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[shorthand(enable(must_use, into))] pub struct ExtXMap { + /// The `URI` that identifies a resource, that contains the media + /// initialization section. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::tags::ExtXMap; + /// let mut map = ExtXMap::new("https://prod.mediaspace.com/init.bin"); + /// # assert_eq!( + /// # map.uri(), + /// # &"https://prod.mediaspace.com/init.bin".to_string() + /// # ); + /// map.set_uri("https://www.google.com/init.bin"); + /// + /// assert_eq!(map.uri(), &"https://www.google.com/init.bin".to_string()); + /// ``` uri: String, + /// The range of the media initialization section. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::tags::ExtXMap; + /// use hls_m3u8::types::ByteRange; + /// + /// let mut map = ExtXMap::with_range( + /// "https://prod.mediaspace.com/init.bin", + /// ByteRange::new(9, Some(2)), + /// ); + /// + /// map.set_range(Some(ByteRange::new(1, None))); + /// assert_eq!(map.range(), Some(ByteRange::new(1, None))); + /// ``` + #[shorthand(enable(copy))] range: Option, + #[shorthand(enable(skip))] keys: Vec, } @@ -27,6 +64,7 @@ impl ExtXMap { /// Makes a new [`ExtXMap`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXMap; /// let map = ExtXMap::new("https://prod.mediaspace.com/init.bin"); @@ -42,6 +80,7 @@ impl ExtXMap { /// Makes a new [`ExtXMap`] tag with the given range. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXMap; /// use hls_m3u8::types::ByteRange; @@ -58,76 +97,6 @@ impl ExtXMap { keys: vec![], } } - - /// Returns the `URI` that identifies a resource, that contains the media - /// initialization section. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXMap; - /// let map = ExtXMap::new("https://prod.mediaspace.com/init.bin"); - /// - /// assert_eq!( - /// map.uri(), - /// &"https://prod.mediaspace.com/init.bin".to_string() - /// ); - /// ``` - pub const fn uri(&self) -> &String { &self.uri } - - /// Sets the `URI` that identifies a resource, that contains the media - /// initialization section. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXMap; - /// let mut map = ExtXMap::new("https://prod.mediaspace.com/init.bin"); - /// - /// map.set_uri("https://dev.mediaspace.com/init.bin"); - /// assert_eq!( - /// map.uri(), - /// &"https://dev.mediaspace.com/init.bin".to_string() - /// ); - /// ``` - pub fn set_uri(&mut self, value: T) -> &mut Self { - self.uri = value.to_string(); - self - } - - /// Returns the range of the media initialization section. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXMap; - /// use hls_m3u8::types::ByteRange; - /// - /// let map = ExtXMap::with_range( - /// "https://prod.mediaspace.com/init.bin", - /// ByteRange::new(9, Some(2)), - /// ); - /// - /// assert_eq!(map.range(), Some(ByteRange::new(9, Some(2)))); - /// ``` - pub const fn range(&self) -> Option { self.range } - - /// Sets the range of the media initialization section. - /// - /// # Example - /// ``` - /// # use hls_m3u8::tags::ExtXMap; - /// use hls_m3u8::types::ByteRange; - /// - /// let mut map = ExtXMap::with_range( - /// "https://prod.mediaspace.com/init.bin", - /// ByteRange::new(9, Some(2)), - /// ); - /// - /// map.set_range(Some(ByteRange::new(1, None))); - /// assert_eq!(map.range(), Some(ByteRange::new(1, None))); - /// ``` - pub fn set_range(&mut self, value: Option) -> &mut Self { - self.range = value; - self - } } impl Encrypted for ExtXMap { diff --git a/src/tags/media_segment/program_date_time.rs b/src/tags/media_segment/program_date_time.rs index 23ccc0b..a948ee9 100644 --- a/src/tags/media_segment/program_date_time.rs +++ b/src/tags/media_segment/program_date_time.rs @@ -9,11 +9,13 @@ use crate::utils::tag; use crate::{Error, RequiredVersion}; /// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME] +/// /// The [`ExtXProgramDateTime`] tag associates the first sample of a /// [`Media Segment`] with an absolute date and/or time. /// /// [`Media Segment`]: crate::MediaSegment -/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: https://tools.ietf.org/html/rfc8216#section-4.3.2.6 +/// [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]: +/// https://tools.ietf.org/html/rfc8216#section-4.3.2.6 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ExtXProgramDateTime(DateTime); @@ -23,6 +25,7 @@ impl ExtXProgramDateTime { /// Makes a new [`ExtXProgramDateTime`] tag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXProgramDateTime; /// use chrono::{FixedOffset, TimeZone}; @@ -41,6 +44,7 @@ impl ExtXProgramDateTime { /// segment. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXProgramDateTime; /// use chrono::{FixedOffset, TimeZone}; @@ -65,6 +69,7 @@ impl ExtXProgramDateTime { /// Sets the date-time of the first sample of the associated media segment. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXProgramDateTime; /// use chrono::{FixedOffset, TimeZone}; diff --git a/src/tags/shared/independent_segments.rs b/src/tags/shared/independent_segments.rs index 47b0f66..570c6bf 100644 --- a/src/tags/shared/independent_segments.rs +++ b/src/tags/shared/independent_segments.rs @@ -5,9 +5,10 @@ use crate::types::ProtocolVersion; use crate::utils::tag; use crate::{Error, RequiredVersion}; -/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS] +/// # [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS] /// -/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: https://tools.ietf.org/html/rfc8216#section-4.3.5.1 +/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]: +/// https://tools.ietf.org/html/rfc8216#section-4.3.5.1 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct ExtXIndependentSegments; @@ -15,6 +16,7 @@ impl ExtXIndependentSegments { pub(crate) const PREFIX: &'static str = "#EXT-X-INDEPENDENT-SEGMENTS"; } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXIndependentSegments { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } } diff --git a/src/tags/shared/start.rs b/src/tags/shared/start.rs index 42d0f07..994b4ed 100644 --- a/src/tags/shared/start.rs +++ b/src/tags/shared/start.rs @@ -6,7 +6,7 @@ use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint}; use crate::utils::{parse_yes_or_no, tag}; use crate::{Error, RequiredVersion}; -/// [4.3.5.2. EXT-X-START] +/// # [4.3.5.2. EXT-X-START] /// /// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2 #[derive(PartialOrd, Debug, Clone, Copy, PartialEq)] @@ -21,9 +21,11 @@ impl ExtXStart { /// Makes a new [`ExtXStart`] tag. /// /// # Panic + /// /// Panics if the time_offset value is infinite. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXStart; /// let start = ExtXStart::new(20.123456); @@ -38,9 +40,11 @@ impl ExtXStart { /// Makes a new [`ExtXStart`] tag with the given `precise` flag. /// /// # Panic + /// /// Panics if the time_offset value is infinite. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXStart; /// let start = ExtXStart::with_precise(20.123456, true); @@ -56,6 +60,7 @@ impl ExtXStart { /// Returns the time offset of the media segments in the playlist. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXStart; /// let start = ExtXStart::new(20.123456); @@ -66,6 +71,7 @@ impl ExtXStart { /// Sets the time offset of the media segments in the playlist. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXStart; /// let mut start = ExtXStart::new(20.123456); @@ -84,6 +90,7 @@ impl ExtXStart { /// presentation times are prior to the specified time offset. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXStart; /// let start = ExtXStart::with_precise(20.123456, true); @@ -94,6 +101,7 @@ impl ExtXStart { /// Sets the `precise` flag. /// /// # Example + /// /// ``` /// # use hls_m3u8::tags::ExtXStart; /// let mut start = ExtXStart::new(20.123456); @@ -109,6 +117,7 @@ impl ExtXStart { } } +/// This tag requires [`ProtocolVersion::V1`]. impl RequiredVersion for ExtXStart { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V1 } } diff --git a/src/traits.rs b/src/traits.rs index af8c5ca..a80de7f 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -4,6 +4,7 @@ use crate::types::{EncryptionMethod, ProtocolVersion}; /// A trait, that is implemented on all tags, that could be encrypted. /// /// # Example +/// /// ``` /// use hls_m3u8::tags::ExtXKey; /// use hls_m3u8::types::EncryptionMethod; @@ -76,7 +77,9 @@ pub trait Encrypted { /// Returns `true`, if the tag is encrypted. /// /// # Note + /// /// This will return `true`, if any of the keys satisfies + /// /// ```text /// key.method() != EncryptionMethod::None /// ``` @@ -92,6 +95,7 @@ pub trait Encrypted { /// Returns `false`, if the tag is not encrypted. /// /// # Note + /// /// This is the inverse of [`is_encrypted`]. /// /// [`is_encrypted`]: #method.is_encrypted @@ -99,7 +103,9 @@ pub trait Encrypted { } /// # Example +/// /// Implementing it: +/// /// ``` /// # use hls_m3u8::RequiredVersion; /// use hls_m3u8::types::ProtocolVersion; @@ -122,6 +128,7 @@ pub trait RequiredVersion { /// Returns the protocol compatibility version that this tag requires. /// /// # Note + /// /// This is for the latest working [`ProtocolVersion`] and a client, that /// only supports an older version would break. fn required_version(&self) -> ProtocolVersion; diff --git a/src/types/byte_range.rs b/src/types/byte_range.rs index 795514d..8ea8ddc 100644 --- a/src/types/byte_range.rs +++ b/src/types/byte_range.rs @@ -1,6 +1,8 @@ use std::fmt; use std::str::FromStr; +use shorthand::ShortHand; + use crate::Error; /// Byte range. @@ -8,9 +10,40 @@ use crate::Error; /// See: [4.3.2.2. EXT-X-BYTERANGE] /// /// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2 -#[derive(Copy, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] +#[derive(ShortHand, Copy, Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] +#[shorthand(enable(must_use))] pub struct ByteRange { + /// The length of the range. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::ByteRange; + /// # + /// let mut range = ByteRange::new(20, Some(3)); + /// # assert_eq!(range.length(), 20); + /// + /// range.set_length(10); + /// assert_eq!(range.length(), 10); + /// ``` length: usize, + /// The start of the range. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::ByteRange; + /// # + /// let mut range = ByteRange::new(20, None); + /// # assert_eq!(range.start(), None); + /// + /// range.set_start(Some(3)); + /// assert_eq!(range.start(), Some(3)); + /// ``` + // + // this is a workaround until this issue is fixed: + // https://github.com/Luro02/shorthand/issues/20 + #[shorthand(enable(copy), disable(option_as_ref))] start: Option, } @@ -18,73 +51,22 @@ impl ByteRange { /// Creates a new [`ByteRange`]. /// /// # Example + /// /// ``` /// # use hls_m3u8::types::ByteRange; /// ByteRange::new(22, Some(12)); /// ``` pub const fn new(length: usize, start: Option) -> Self { Self { length, start } } - - /// Returns the length of the range. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::ByteRange; - /// # - /// assert_eq!(ByteRange::new(20, Some(3)).length(), 20); - /// ``` - pub const fn length(&self) -> usize { self.length } - - /// Sets the length of the range. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::ByteRange; - /// # - /// let mut range = ByteRange::new(20, Some(3)); - /// - /// # assert_eq!(range.length(), 20); - /// range.set_length(10); - /// assert_eq!(range.length(), 10); - /// ``` - pub fn set_length(&mut self, value: usize) -> &mut Self { - self.length = value; - self - } - - /// Returns the start of the range. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::ByteRange; - /// # - /// assert_eq!(ByteRange::new(20, Some(3)).start(), Some(3)); - /// ``` - pub const fn start(&self) -> Option { self.start } - - /// Sets the start of the range. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::ByteRange; - /// # - /// let mut range = ByteRange::new(20, None); - /// - /// # assert_eq!(range.start(), None); - /// range.set_start(Some(3)); - /// assert_eq!(range.start(), Some(3)); - /// ``` - pub fn set_start(&mut self, value: Option) -> &mut Self { - self.start = value; - self - } } impl fmt::Display for ByteRange { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.length)?; - if let Some(x) = self.start { - write!(f, "@{}", x)?; + + if let Some(value) = self.start { + write!(f, "@{}", value)?; } + Ok(()) } } @@ -94,6 +76,7 @@ impl FromStr for ByteRange { fn from_str(s: &str) -> Result { let tokens = s.splitn(2, '@').collect::>(); + if tokens.is_empty() { return Err(Error::invalid_input()); } @@ -107,6 +90,7 @@ impl FromStr for ByteRange { None } }; + Ok(Self::new(length, start)) } } @@ -118,23 +102,32 @@ mod tests { #[test] fn test_display() { - let byte_range = ByteRange { - length: 0, - start: Some(5), - }; - assert_eq!(byte_range.to_string(), "0@5".to_string()); + assert_eq!( + ByteRange { + length: 0, + start: Some(5), + } + .to_string(), + "0@5".to_string() + ); - let byte_range = ByteRange { - length: 99999, - start: Some(2), - }; - assert_eq!(byte_range.to_string(), "99999@2".to_string()); + assert_eq!( + ByteRange { + length: 99999, + start: Some(2), + } + .to_string(), + "99999@2".to_string() + ); - let byte_range = ByteRange { - length: 99999, - start: None, - }; - assert_eq!(byte_range.to_string(), "99999".to_string()); + assert_eq!( + ByteRange { + length: 99999, + start: None, + } + .to_string(), + "99999".to_string() + ); } #[test] diff --git a/src/types/channels.rs b/src/types/channels.rs index d56fd01..e8136d3 100644 --- a/src/types/channels.rs +++ b/src/types/channels.rs @@ -6,13 +6,17 @@ use crate::Error; /// Specifies a list of parameters. /// /// # `MediaType::Audio` +/// /// The first parameter is a count of audio channels expressed as a [`u64`], /// indicating the maximum number of independent, simultaneous audio channels -/// present in any [`MediaSegment`] in the rendition. For example, an -/// `AC-3 5.1` rendition would have a `CHANNELS="6"` attribute. +/// present in any [`MediaSegment`] in the rendition. +/// +/// For example, an `AC-3 5.1` rendition would have a `CHANNELS="6"` attribute. /// /// # Example +/// /// Creating a `CHANNELS="6"` attribute +/// /// ``` /// # use hls_m3u8::types::Channels; /// let mut channels = Channels::new(6); @@ -34,6 +38,7 @@ impl Channels { /// Makes a new [`Channels`] struct. /// /// # Example + /// /// ``` /// # use hls_m3u8::types::Channels; /// let mut channels = Channels::new(6); @@ -48,6 +53,7 @@ impl Channels { /// Returns the channel number. /// /// # Example + /// /// ``` /// # use hls_m3u8::types::Channels; /// let mut channels = Channels::new(6); @@ -59,6 +65,7 @@ impl Channels { /// Sets the channel number. /// /// # Example + /// /// ``` /// # use hls_m3u8::types::Channels; /// let mut channels = Channels::new(3); @@ -77,9 +84,10 @@ impl FromStr for Channels { fn from_str(input: &str) -> Result { let parameters = input.split('/').collect::>(); + let channel_number = parameters .first() - .ok_or_else(|| Error::missing_attribute("First parameter of channels!"))? + .ok_or_else(|| Error::missing_attribute("first parameter of channels"))? .parse() .map_err(Error::parse_int)?; @@ -93,6 +101,7 @@ impl FromStr for Channels { impl fmt::Display for Channels { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.channel_number)?; + if !self.unknown.is_empty() { write!(f, "{}", self.unknown.join(","))?; } diff --git a/src/types/decimal_floating_point.rs b/src/types/decimal_floating_point.rs index af0ac36..53aa3c7 100644 --- a/src/types/decimal_floating_point.rs +++ b/src/types/decimal_floating_point.rs @@ -51,6 +51,7 @@ impl Deref for DecimalFloatingPoint { fn deref(&self) -> &Self::Target { &self.0 } } +#[doc(hidden)] impl From for DecimalFloatingPoint { fn from(value: f64) -> Self { let mut result = value; @@ -64,6 +65,7 @@ impl From for DecimalFloatingPoint { } } +#[doc(hidden)] impl From for DecimalFloatingPoint { fn from(value: f32) -> Self { f64::from(value).into() } } @@ -102,15 +104,13 @@ mod tests { #[test] pub fn test_parser() { - let decimal_floating_point = DecimalFloatingPoint::new(22.0).unwrap(); assert_eq!( - decimal_floating_point, + DecimalFloatingPoint::new(22.0).unwrap(), "22".parse::().unwrap() ); - let decimal_floating_point = DecimalFloatingPoint::new(4.1).unwrap(); assert_eq!( - decimal_floating_point, + DecimalFloatingPoint::new(4.1).unwrap(), "4.1".parse::().unwrap() ); diff --git a/src/types/decimal_resolution.rs b/src/types/decimal_resolution.rs deleted file mode 100644 index 50e44c1..0000000 --- a/src/types/decimal_resolution.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::str::FromStr; - -use derive_more::Display; - -use crate::Error; - -/// This is a simple wrapper type for the display resolution. (1920x1080, -/// 1280x720, ...). -/// -/// See: [4.2. Attribute Lists] -/// -/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 -#[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display)] -#[display(fmt = "{}x{}", width, height)] -pub struct DecimalResolution { - width: usize, - height: usize, -} - -impl DecimalResolution { - /// Creates a new [`DecimalResolution`]. - pub const fn new(width: usize, height: usize) -> Self { Self { width, height } } - - /// Horizontal pixel dimension. - pub const fn width(&self) -> usize { self.width } - - /// Sets Horizontal pixel dimension. - pub fn set_width(&mut self, value: usize) -> &mut Self { - self.width = value; - self - } - - /// Vertical pixel dimension. - pub const fn height(&self) -> usize { self.height } - - /// Sets Vertical pixel dimension. - pub fn set_height(&mut self, value: usize) -> &mut Self { - self.height = value; - self - } -} - -/// [`DecimalResolution`] can be constructed from a tuple; `(width, height)`. -impl From<(usize, usize)> for DecimalResolution { - fn from(value: (usize, usize)) -> Self { Self::new(value.0, value.1) } -} - -impl FromStr for DecimalResolution { - type Err = Error; - - fn from_str(input: &str) -> Result { - let tokens = input.splitn(2, 'x').collect::>(); - - if tokens.len() != 2 { - return Err(Error::custom(format!( - "InvalidInput: Expected input format: [width]x[height] (ex. 1920x1080), got {:?}", - input, - ))); - } - - Ok(Self { - width: tokens[0].parse().map_err(Error::parse_int)?, - height: tokens[1].parse().map_err(Error::parse_int)?, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn test_display() { - assert_eq!( - DecimalResolution::new(1920, 1080).to_string(), - "1920x1080".to_string() - ); - - assert_eq!( - DecimalResolution::new(1280, 720).to_string(), - "1280x720".to_string() - ); - } - - #[test] - fn test_parser() { - assert_eq!( - DecimalResolution::new(1920, 1080), - "1920x1080".parse::().unwrap() - ); - - assert_eq!( - DecimalResolution::new(1280, 720), - "1280x720".parse::().unwrap() - ); - - assert!("1280".parse::().is_err()); - } - - #[test] - fn test_width() { - assert_eq!(DecimalResolution::new(1920, 1080).width(), 1920); - assert_eq!(DecimalResolution::new(1920, 1080).set_width(12).width(), 12); - } - - #[test] - fn test_height() { - assert_eq!(DecimalResolution::new(1920, 1080).height(), 1080); - assert_eq!( - DecimalResolution::new(1920, 1080).set_height(12).height(), - 12 - ); - } - - #[test] - fn test_from() { - assert_eq!( - DecimalResolution::from((1920, 1080)), - DecimalResolution::new(1920, 1080) - ); - } -} diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index f5933d3..526624e 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -2,36 +2,138 @@ use std::fmt; use std::str::FromStr; use derive_builder::Builder; +use shorthand::ShortHand; use crate::attribute::AttributePairs; -use crate::types::{ - EncryptionMethod, InitializationVector, KeyFormat, KeyFormatVersions, ProtocolVersion, -}; -use crate::utils::{quote, unquote}; +use crate::types::{EncryptionMethod, KeyFormat, KeyFormatVersions, ProtocolVersion}; +use crate::utils::{parse_iv_from_str, quote, unquote}; use crate::{Error, RequiredVersion}; -#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[builder(setter(into), build_fn(validate = "Self::validate"))] -/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`] +/// A [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`] /// and [`ExtXKey`]. /// /// [`ExtXSessionKey`]: crate::tags::ExtXSessionKey /// [`ExtXKey`]: crate::tags::ExtXKey +#[derive(ShortHand, Builder, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[builder(setter(into), build_fn(validate = "Self::validate"))] +#[shorthand(enable(must_use, into))] pub struct DecryptionKey { - /// The [EncryptionMethod]. + /// The [`EncryptionMethod`]. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::DecryptionKey; + /// use hls_m3u8::types::EncryptionMethod; + /// + /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); + /// + /// key.set_method(EncryptionMethod::SampleAes); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string() + /// ); + /// ``` + /// + /// # Note + /// + /// This attribute is required. + #[shorthand(enable(copy))] pub(crate) method: EncryptionMethod, - #[builder(setter(into, strip_option), default)] /// An `URI`, that specifies how to obtain the key. + /// + /// # Example + /// ``` + /// # use hls_m3u8::types::DecryptionKey; + /// use hls_m3u8::types::EncryptionMethod; + /// + /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); + /// + /// key.set_uri(Some("http://www.google.com/")); + /// + /// assert_eq!( + /// key.to_string(), + /// "METHOD=AES-128,URI=\"http://www.google.com/\"".to_string() + /// ); + /// ``` + /// + /// # Note + /// + /// This attribute is required, if the [`EncryptionMethod`] is not `None`. + #[builder(setter(into, strip_option), default)] pub(crate) uri: Option, + /// The IV (Initialization Vector) for the key. + /// + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::DecryptionKey; + /// use hls_m3u8::types::EncryptionMethod; + /// + /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); + /// + /// # assert_eq!(key.iv(), None); + /// key.set_iv(Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])); + /// + /// assert_eq!( + /// key.iv(), + /// Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7]) + /// ); + /// ``` + /// + /// # Note + /// + /// This attribute is optional. #[builder(setter(into, strip_option), default)] - /// The IV (Initialization Vector) attribute. - pub(crate) iv: Option, - #[builder(setter(into, strip_option), default)] - /// A string that specifies how the key is + // TODO: workaround for https://github.com/Luro02/shorthand/issues/20 + #[shorthand(enable(copy), disable(option_as_ref))] + pub(crate) iv: Option<[u8; 16]>, + /// [`KeyFormat`] specifies how the key is /// represented in the resource identified by the `URI`. - pub(crate) key_format: Option, + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::DecryptionKey; + /// use hls_m3u8::types::{EncryptionMethod, KeyFormat}; + /// + /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); + /// + /// key.set_key_format(Some(KeyFormat::Identity)); + /// + /// assert_eq!(key.key_format(), Some(KeyFormat::Identity)); + /// ``` + /// + /// # Note + /// + /// This attribute is optional. + #[builder(setter(into, strip_option), default)] + #[shorthand(enable(copy))] + pub(crate) key_format: Option, + /// The [`KeyFormatVersions`] attribute. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::DecryptionKey; + /// use hls_m3u8::types::{EncryptionMethod, KeyFormatVersions}; + /// + /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); + /// + /// key.set_key_format_versions(Some(vec![1, 2, 3, 4, 5])); + /// + /// assert_eq!( + /// key.key_format_versions(), + /// Some(&KeyFormatVersions::from(vec![1, 2, 3, 4, 5])) + /// ); + /// ``` + /// + /// # Note + /// + /// This attribute is optional. #[builder(setter(into, strip_option), default)] - /// The [KeyFormatVersions] attribute. pub(crate) key_format_versions: Option, } @@ -48,6 +150,7 @@ impl DecryptionKey { /// Makes a new [`DecryptionKey`]. /// /// # Example + /// /// ``` /// # use hls_m3u8::types::DecryptionKey; /// use hls_m3u8::types::EncryptionMethod; @@ -64,213 +167,14 @@ impl DecryptionKey { } } - /// Returns the [`EncryptionMethod`]. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// assert_eq!(key.method(), EncryptionMethod::Aes128); - /// ``` - pub const fn method(&self) -> EncryptionMethod { self.method } - /// Returns a Builder to build a [DecryptionKey]. pub fn builder() -> DecryptionKeyBuilder { DecryptionKeyBuilder::default() } - - /// Sets the [`EncryptionMethod`]. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// key.set_method(EncryptionMethod::SampleAes); - /// - /// assert_eq!( - /// key.to_string(), - /// "METHOD=SAMPLE-AES,URI=\"https://www.example.com/\"".to_string() - /// ); - /// ``` - pub fn set_method(&mut self, value: EncryptionMethod) -> &mut Self { - self.method = value; - self - } - - /// Returns an `URI`, that specifies how to obtain the key. - /// - /// # Note - /// This attribute is required, if the [EncryptionMethod] is not `None`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// assert_eq!(key.uri(), &Some("https://www.example.com/".to_string())); - /// ``` - pub const fn uri(&self) -> &Option { &self.uri } - - /// Sets the `URI` attribute. - /// - /// # Note - /// This attribute is required, if the [`EncryptionMethod`] is not `None`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// key.set_uri(Some("http://www.google.com/")); - /// - /// assert_eq!( - /// key.to_string(), - /// "METHOD=AES-128,URI=\"http://www.google.com/\"".to_string() - /// ); - /// ``` - pub fn set_uri(&mut self, value: Option) -> &mut Self { - self.uri = value.map(|v| v.to_string()); - self - } - - /// Returns the IV (Initialization Vector) attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// # assert_eq!(key.iv(), None); - /// key.set_iv(Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])); - /// - /// assert_eq!( - /// key.iv(), - /// Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7]) - /// ); - /// ``` - pub fn iv(&self) -> Option<[u8; 16]> { - if let Some(iv) = &self.iv { - Some(iv.to_slice()) - } else { - None - } - } - - /// Sets the `IV` attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// key.set_iv(Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7])); - /// - /// assert_eq!( - /// key.to_string(), - /// "METHOD=AES-128,URI=\"https://www.example.com/\",IV=0x01020304050607080901020304050607" - /// .to_string() - /// ); - /// ``` - pub fn set_iv(&mut self, value: Option) -> &mut Self - where - T: Into<[u8; 16]>, - { - self.iv = value.map(|v| InitializationVector(v.into())); - self - } - - /// Returns a string that specifies how the key is - /// represented in the resource identified by the `URI`. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::{EncryptionMethod, KeyFormat}; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// key.set_key_format(Some(KeyFormat::Identity)); - /// - /// assert_eq!(key.key_format(), Some(KeyFormat::Identity)); - /// ``` - pub const fn key_format(&self) -> Option { self.key_format } - - /// Sets the [`KeyFormat`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::{EncryptionMethod, KeyFormat}; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// key.set_key_format(Some(KeyFormat::Identity)); - /// - /// assert_eq!(key.key_format(), Some(KeyFormat::Identity)); - /// ``` - pub fn set_key_format>(&mut self, value: Option) -> &mut Self { - self.key_format = value.map(Into::into); - self - } - - /// Returns the [`KeyFormatVersions`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::{EncryptionMethod, KeyFormatVersions}; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// key.set_key_format_versions(Some(vec![1, 2, 3, 4, 5])); - /// - /// assert_eq!( - /// key.key_format_versions(), - /// &Some(KeyFormatVersions::from(vec![1, 2, 3, 4, 5])) - /// ); - /// ``` - pub const fn key_format_versions(&self) -> &Option { - &self.key_format_versions - } - - /// Sets the [`KeyFormatVersions`] attribute. - /// - /// # Example - /// ``` - /// # use hls_m3u8::types::DecryptionKey; - /// use hls_m3u8::types::EncryptionMethod; - /// - /// let mut key = DecryptionKey::new(EncryptionMethod::Aes128, "https://www.example.com/"); - /// - /// key.set_key_format_versions(Some(vec![1, 2, 3, 4, 5])); - /// - /// assert_eq!( - /// key.to_string(), - /// "METHOD=AES-128,URI=\"https://www.example.com/\",KEYFORMATVERSIONS=\"1/2/3/4/5\"" - /// .to_string() - /// ); - /// ``` - pub fn set_key_format_versions>( - &mut self, - value: Option, - ) -> &mut Self { - self.key_format_versions = value.map(Into::into); - self - } } +/// This tag requires [`ProtocolVersion::V5`], if [`KeyFormat`] or +/// [`KeyFormatVersions`] is specified and [`ProtocolVersion::V2`] if an iv is +/// specified. +/// Otherwise [`ProtocolVersion::V1`] is required. impl RequiredVersion for DecryptionKey { fn required_version(&self) -> ProtocolVersion { if self.key_format.is_some() || self.key_format_versions.is_some() { @@ -305,7 +209,7 @@ impl FromStr for DecryptionKey { uri = Some(unquoted_uri); } } - "IV" => iv = Some(value.parse()?), + "IV" => iv = Some(parse_iv_from_str(value)?), "KEYFORMAT" => key_format = Some(value.parse()?), "KEYFORMATVERSIONS" => key_format_versions = Some(value.parse().unwrap()), _ => { @@ -338,12 +242,16 @@ impl fmt::Display for DecryptionKey { if self.method == EncryptionMethod::None { return Ok(()); } + if let Some(uri) = &self.uri { write!(f, ",URI={}", quote(uri))?; } + if let Some(value) = &self.iv { - write!(f, ",IV={}", value)?; + // TODO: use hex::encode_to_slice + write!(f, ",IV=0x{}", hex::encode(&value))?; } + if let Some(value) = &self.key_format { write!(f, ",KEYFORMAT={}", quote(value))?; } @@ -353,6 +261,7 @@ impl fmt::Display for DecryptionKey { write!(f, ",KEYFORMATVERSIONS={}", key_format_versions)?; } } + Ok(()) } } diff --git a/src/types/encryption_method.rs b/src/types/encryption_method.rs index a555009..acb4cc0 100644 --- a/src/types/encryption_method.rs +++ b/src/types/encryption_method.rs @@ -9,14 +9,15 @@ use strum::{Display, EnumString}; #[derive(Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)] #[strum(serialize_all = "SCREAMING-KEBAB-CASE")] pub enum EncryptionMethod { - /// `None` means that [MediaSegment]s are not encrypted. + /// `None` means that the [`MediaSegment`]s are not encrypted. /// /// [MediaSegment]: crate::MediaSegment None, /// `Aes128` signals that the [MediaSegment]s are completely encrypted - /// using the Advanced Encryption Standard ([AES_128]) with a 128-bit + /// using the Advanced Encryption Standard ([AES-128]) with a 128-bit /// key, Cipher Block Chaining (CBC), and /// [Public-Key Cryptography Standards #7 (PKCS7)] padding. + /// /// CBC is restarted on each segment boundary, using either the /// Initialization Vector (IV) attribute value or the Media Sequence /// Number as the IV. @@ -37,8 +38,8 @@ pub enum EncryptionMethod { /// and Enhanced [AC-3] media streams is described in the HTTP /// Live Streaming (HLS) [SampleEncryption specification]. /// - /// [MediaSegment]: crate::MediaSegment - /// [AES_128]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf + /// [`MediaSegment`]: crate::MediaSegment + /// [AES-128]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf /// [Common Encryption]: https://tools.ietf.org/html/rfc8216#ref-COMMON_ENC /// [H.264]: https://tools.ietf.org/html/rfc8216#ref-H_264 /// [AAC]: https://tools.ietf.org/html/rfc8216#ref-ISO_14496 diff --git a/src/types/initialization_vector.rs b/src/types/initialization_vector.rs deleted file mode 100644 index 971bcc9..0000000 --- a/src/types/initialization_vector.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::fmt; -use std::ops::Deref; -use std::str::FromStr; - -use crate::Error; - -/// Initialization vector. -/// -/// See: [4.3.2.4. EXT-X-KEY] -/// -/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4 -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InitializationVector(pub [u8; 16]); - -impl InitializationVector { - /// Converts the [InitializationVector] to a slice. - pub const fn to_slice(&self) -> [u8; 16] { self.0 } -} - -impl From<[u8; 16]> for InitializationVector { - fn from(value: [u8; 16]) -> Self { Self(value) } -} - -impl Deref for InitializationVector { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl AsRef<[u8]> for InitializationVector { - fn as_ref(&self) -> &[u8] { &self.0 } -} - -impl fmt::Display for InitializationVector { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "0x")?; - for b in &self.0 { - write!(f, "{:02x}", b)?; - } - Ok(()) - } -} - -impl FromStr for InitializationVector { - type Err = Error; - - fn from_str(input: &str) -> Result { - if !(input.starts_with("0x") || input.starts_with("0X")) { - return Err(Error::invalid_input()); - } - if input.len() - 2 != 32 { - return Err(Error::invalid_input()); - } - - let mut result = [0; 16]; - for (i, c) in input.as_bytes().chunks(2).skip(1).enumerate() { - let d = std::str::from_utf8(c).map_err(Error::custom)?; - let b = u8::from_str_radix(d, 16).map_err(Error::custom)?; - result[i] = b; - } - - Ok(Self(result)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn test_display() { - assert_eq!( - "0x10ef8f758ca555115584bb5b3c687f52".to_string(), - InitializationVector([ - 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82 - ]) - .to_string() - ); - } - - #[test] - fn test_parser() { - assert_eq!( - "0x10ef8f758ca555115584bb5b3c687f52" - .parse::() - .unwrap(), - InitializationVector([ - 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82 - ]) - ); - - assert_eq!( - "0X10ef8f758ca555115584bb5b3c687f52" - .parse::() - .unwrap(), - InitializationVector([ - 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82 - ]) - ); - - assert_eq!( - "0X10EF8F758CA555115584BB5B3C687F52" - .parse::() - .unwrap(), - InitializationVector([ - 16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82 - ]) - ); - - assert!("garbage".parse::().is_err()); - assert!("0xgarbage".parse::().is_err()); - assert!("0x12".parse::().is_err()); - assert!("0X10EF8F758CA555115584BB5B3C687F5Z" - .parse::() - .is_err()); - } - - #[test] - fn test_as_ref() { - assert_eq!( - InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).as_ref(), - &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ); - } - - #[test] - fn test_deref() { - assert_eq!( - InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).deref(), - &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ); - } - - #[test] - fn test_from() { - assert_eq!( - InitializationVector::from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - ); - } - - #[test] - fn test_to_slice() { - assert_eq!( - InitializationVector([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).to_slice(), - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ); - } -} diff --git a/src/types/key_format.rs b/src/types/key_format.rs index 3ffac49..3995db0 100644 --- a/src/types/key_format.rs +++ b/src/types/key_format.rs @@ -5,9 +5,9 @@ use crate::types::ProtocolVersion; use crate::utils::{quote, tag, unquote}; use crate::{Error, RequiredVersion}; -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] /// [`KeyFormat`] specifies, how the key is represented in the /// resource identified by the `URI`. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum KeyFormat { /// The key is a single packed array of 16 octets in binary format. Identity, diff --git a/src/types/mod.rs b/src/types/mod.rs index a1df2bd..5bcb7e9 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -3,16 +3,15 @@ mod byte_range; mod channels; mod closed_captions; mod decimal_floating_point; -mod decimal_resolution; mod decryption_key; mod encryption_method; mod hdcp_level; mod in_stream_id; -mod initialization_vector; mod key_format; mod key_format_versions; mod media_type; mod protocol_version; +mod resolution; mod signed_decimal_floating_point; mod stream_inf; mod value; @@ -21,16 +20,15 @@ pub use byte_range::*; pub use channels::*; pub use closed_captions::*; pub(crate) use decimal_floating_point::*; -pub(crate) use decimal_resolution::*; pub use decryption_key::*; pub use encryption_method::*; pub use hdcp_level::*; pub use in_stream_id::*; -pub use initialization_vector::*; pub use key_format::*; pub use key_format_versions::*; pub use media_type::*; pub use protocol_version::*; +pub use resolution::*; pub(crate) use signed_decimal_floating_point::*; pub use stream_inf::*; pub use value::*; diff --git a/src/types/protocol_version.rs b/src/types/protocol_version.rs index 1ea2a0e..5776ac1 100644 --- a/src/types/protocol_version.rs +++ b/src/types/protocol_version.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use crate::Error; /// # [7. Protocol Version Compatibility] +/// /// The [`ProtocolVersion`] specifies, which m3u8 revision is required, to parse /// a certain tag correctly. /// @@ -26,6 +27,7 @@ impl ProtocolVersion { /// this library. /// /// # Example + /// /// ``` /// # use hls_m3u8::types::ProtocolVersion; /// assert_eq!(ProtocolVersion::latest(), ProtocolVersion::V7); @@ -66,6 +68,7 @@ impl FromStr for ProtocolVersion { } } +/// The default is [`ProtocolVersion::V1`]. impl Default for ProtocolVersion { fn default() -> Self { Self::V1 } } diff --git a/src/types/resolution.rs b/src/types/resolution.rs new file mode 100644 index 0000000..b4ad382 --- /dev/null +++ b/src/types/resolution.rs @@ -0,0 +1,104 @@ +use std::str::FromStr; + +use derive_more::Display; +use shorthand::ShortHand; + +use crate::Error; + +/// This is a simple wrapper type for the display resolution. +/// +/// For example Full HD has a resolution of 1920x1080. +/// +/// See: [4.2. Attribute Lists] +/// +/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2 +#[derive(ShortHand, Ord, PartialOrd, Debug, Clone, Copy, PartialEq, Eq, Hash, Display)] +#[display(fmt = "{}x{}", width, height)] +#[shorthand(enable(must_use))] +pub struct Resolution { + /// Horizontal pixel dimension. + width: usize, + /// Vertical pixel dimension. + height: usize, +} + +impl Resolution { + /// Creates a new [`Resolution`]. + pub const fn new(width: usize, height: usize) -> Self { Self { width, height } } +} + +/// A [`Resolution`] can be constructed from a tuple `(width, height)`. +impl From<(usize, usize)> for Resolution { + fn from(value: (usize, usize)) -> Self { Self::new(value.0, value.1) } +} + +impl FromStr for Resolution { + type Err = Error; + + fn from_str(input: &str) -> Result { + let tokens = input.splitn(2, 'x').collect::>(); + + if tokens.len() != 2 { + return Err(Error::custom(format!( + "InvalidInput: Expected input format: [width]x[height] (ex. 1920x1080), got {:?}", + input, + ))); + } + + Ok(Self { + width: tokens[0].parse().map_err(Error::parse_int)?, + height: tokens[1].parse().map_err(Error::parse_int)?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_display() { + assert_eq!( + Resolution::new(1920, 1080).to_string(), + "1920x1080".to_string() + ); + + assert_eq!( + Resolution::new(1280, 720).to_string(), + "1280x720".to_string() + ); + } + + #[test] + fn test_parser() { + assert_eq!( + Resolution::new(1920, 1080), + "1920x1080".parse::().unwrap() + ); + + assert_eq!( + Resolution::new(1280, 720), + "1280x720".parse::().unwrap() + ); + + assert!("1280".parse::().is_err()); + } + + #[test] + fn test_width() { + assert_eq!(Resolution::new(1920, 1080).width(), 1920); + assert_eq!(Resolution::new(1920, 1080).set_width(12).width(), 12); + } + + #[test] + fn test_height() { + assert_eq!(Resolution::new(1920, 1080).height(), 1080); + assert_eq!(Resolution::new(1920, 1080).set_height(12).height(), 12); + } + + #[test] + fn test_from() { + assert_eq!(Resolution::from((1920, 1080)), Resolution::new(1920, 1080)); + } +} diff --git a/src/types/signed_decimal_floating_point.rs b/src/types/signed_decimal_floating_point.rs index 54a58e5..93cbb0c 100644 --- a/src/types/signed_decimal_floating_point.rs +++ b/src/types/signed_decimal_floating_point.rs @@ -13,6 +13,7 @@ impl SignedDecimalFloatingPoint { /// Makes a new [`SignedDecimalFloatingPoint`] instance. /// /// # Panics + /// /// The given value must be finite, otherwise this function will panic! pub fn new(value: f64) -> Self { if value.is_infinite() || value.is_nan() { diff --git a/src/types/stream_inf.rs b/src/types/stream_inf.rs index afe462e..15d1af3 100644 --- a/src/types/stream_inf.rs +++ b/src/types/stream_inf.rs @@ -2,44 +2,133 @@ use std::fmt; use std::str::FromStr; use derive_builder::Builder; +use shorthand::ShortHand; use crate::attribute::AttributePairs; -use crate::types::{DecimalResolution, HdcpLevel}; +use crate::types::{HdcpLevel, Resolution}; 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(Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash, Ord)] +#[derive(ShortHand, Builder, PartialOrd, Debug, Clone, PartialEq, Eq, Hash, Ord)] #[builder(setter(into, strip_option))] #[builder(derive(Debug, PartialEq))] +#[shorthand(enable(must_use, into))] pub struct StreamInf { - /// The maximum bandwidth of the stream. + /// The peak segment bit rate of the variant stream. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_bandwidth(5); + /// assert_eq!(stream.bandwidth(), 5); + /// ``` + /// + /// # Note + /// + /// This field is required. + #[shorthand(disable(into))] bandwidth: u64, - #[builder(default)] /// The average bandwidth of the stream. + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// # + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_average_bandwidth(Some(300)); + /// assert_eq!(stream.average_bandwidth(), Some(300)); + /// ``` + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] + #[shorthand(enable(copy), disable(into, option_as_ref))] average_bandwidth: Option, + /// A string that represents the list of codec types contained the variant + /// stream. + /// + /// # Example + /// + /// ``` + /// # 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())); + /// ``` + /// + /// # Note + /// + /// This field is optional. #[builder(default)] - /// Every media format in any of the renditions specified by the Variant - /// Stream. codecs: Option, - #[builder(default)] /// The resolution of the stream. - resolution: Option, + /// + /// # Example + /// + /// ``` + /// # use hls_m3u8::types::StreamInf; + /// use hls_m3u8::types::Resolution; + /// + /// let mut stream = StreamInf::new(20); + /// + /// stream.set_resolution(Some((1920, 1080))); + /// assert_eq!(stream.resolution(), Some(Resolution::new(1920, 1080))); + /// # stream.set_resolution(Some((1280, 10))); + /// # assert_eq!(stream.resolution(), Some(Resolution::new(1280, 10))); + /// ``` + /// + /// # Note + /// + /// This field is optional. #[builder(default)] - /// High-bandwidth Digital Content Protection + #[shorthand(enable(copy))] + resolution: Option, + /// High-bandwidth Digital Content Protection level of the variant stream. + /// + /// # Example + /// + /// ``` + /// # 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)); + /// ``` + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] + #[shorthand(enable(copy), disable(into))] hdcp_level: Option, - #[builder(default)] /// It indicates the set of video renditions, that should be used when /// playing the presentation. + /// + /// # Note + /// + /// This field is optional. + #[builder(default)] video: Option, } impl StreamInf { /// Creates a new [`StreamInf`]. /// - /// # Examples + /// # Example + /// /// ``` /// # use hls_m3u8::types::StreamInf; /// # @@ -55,183 +144,6 @@ impl StreamInf { 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))); - /// # stream.set_resolution(1280, 10); - /// # assert_eq!(stream.resolution(), Some((1280, 10))); - /// ``` - 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(Into::into); - self - } } impl fmt::Display for StreamInf { @@ -311,7 +223,7 @@ mod tests { let mut stream_inf = StreamInf::new(200); stream_inf.set_average_bandwidth(Some(15)); stream_inf.set_codecs(Some("mp4a.40.2,avc1.4d401e")); - stream_inf.set_resolution(1920, 1080); + stream_inf.set_resolution(Some((1920, 1080))); stream_inf.set_hdcp_level(Some(HdcpLevel::Type0)); stream_inf.set_video(Some("video")); @@ -332,7 +244,7 @@ mod tests { let mut stream_inf = StreamInf::new(200); stream_inf.set_average_bandwidth(Some(15)); stream_inf.set_codecs(Some("mp4a.40.2,avc1.4d401e")); - stream_inf.set_resolution(1920, 1080); + stream_inf.set_resolution(Some((1920, 1080))); stream_inf.set_hdcp_level(Some(HdcpLevel::Type0)); stream_inf.set_video(Some("video")); diff --git a/src/utils.rs b/src/utils.rs index d21d9e6..e794544 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -32,6 +32,29 @@ impl_from![ u8, i8, u16, i16, u32, i32, f32, f64 => crate::types::SignedDecimalFloatingPoint ]; +pub(crate) fn parse_iv_from_str(input: &str) -> crate::Result<[u8; 16]> { + if !(input.starts_with("0x") || input.starts_with("0X")) { + return Err(Error::invalid_input()); + } + + if input.len() - 2 != 32 { + return Err(Error::invalid_input()); + } + + let mut result = [0; 16]; + + // TODO: + // hex::decode_to_slice(value.as_bytes()[2..], &mut result)?; + + for (i, c) in input.as_bytes().chunks(2).skip(1).enumerate() { + let d = core::str::from_utf8(c).map_err(Error::custom)?; + let b = u8::from_str_radix(d, 16).map_err(Error::custom)?; + result[i] = b; + } + + Ok(result) +} + pub(crate) fn parse_yes_or_no>(s: T) -> crate::Result { match s.as_ref() { "YES" => Ok(true),