diff --git a/src/error.rs b/src/error.rs index 0d85eca..4a7f48d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -76,6 +76,10 @@ pub enum ErrorKind { /// An unexpected value. UnexpectedAttribute(String), + #[fail(display = "Unexpected Tag: {:?}", _0)] + /// An unexpected tag. + UnexpectedTag(String), + /// Hints that destructuring should not be exhaustive. /// /// This enum may grow additional variants, so this makes sure clients @@ -119,6 +123,10 @@ impl Error { Self::from(ErrorKind::UnexpectedAttribute(value.to_string())) } + pub(crate) fn unexpected_tag(value: T) -> Self { + Self::from(ErrorKind::UnexpectedTag(value.to_string())) + } + pub(crate) fn invalid_input() -> Self { Self::from(ErrorKind::InvalidInput) } pub(crate) fn parse_int_error(value: T) -> Self { diff --git a/src/lib.rs b/src/lib.rs index 54a0964..2996b1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![forbid(unsafe_code)] +#![feature(option_flattening)] #![warn( //clippy::pedantic, clippy::nursery, @@ -49,6 +50,8 @@ mod line; mod master_playlist; mod media_playlist; mod media_segment; +mod traits; mod utils; pub use error::Result; +pub use traits::*; diff --git a/src/master_playlist.rs b/src/master_playlist.rs index 2af4ff7..865b797 100644 --- a/src/master_playlist.rs +++ b/src/master_playlist.rs @@ -10,8 +10,8 @@ use crate::tags::{ ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData, ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion, }; -use crate::types::{ClosedCaptions, MediaType, ProtocolVersion, RequiredVersion}; -use crate::Error; +use crate::types::{ClosedCaptions, MediaType, ProtocolVersion}; +use crate::{Error, RequiredVersion}; /// Master playlist. #[derive(Debug, Clone, Builder)] diff --git a/src/media_playlist.rs b/src/media_playlist.rs index 31dde47..6630420 100644 --- a/src/media_playlist.rs +++ b/src/media_playlist.rs @@ -11,8 +11,8 @@ use crate::tags::{ ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, ExtXIndependentSegments, ExtXMediaSequence, ExtXPlaylistType, ExtXStart, ExtXTargetDuration, ExtXVersion, }; -use crate::types::{ProtocolVersion, RequiredVersion}; -use crate::Error; +use crate::types::ProtocolVersion; +use crate::{Encrypted, Error, RequiredVersion}; /// Media playlist. #[derive(Debug, Clone, Builder)] @@ -28,30 +28,30 @@ pub struct MediaPlaylist { /// The default is the maximum version among the tags in the playlist. #[builder(setter(name = "version"))] version_tag: ExtXVersion, - /// Sets the [ExtXTargetDuration] tag. + /// Sets the [`ExtXTargetDuration`] tag. target_duration_tag: ExtXTargetDuration, #[builder(default)] - /// Sets the [ExtXMediaSequence] tag. + /// Sets the [`ExtXMediaSequence`] tag. media_sequence_tag: Option, #[builder(default)] - /// Sets the [ExtXDiscontinuitySequence] tag. + /// Sets the [`ExtXDiscontinuitySequence`] tag. discontinuity_sequence_tag: Option, #[builder(default)] - /// Sets the [ExtXPlaylistType] tag. + /// Sets the [`ExtXPlaylistType`] tag. playlist_type_tag: Option, #[builder(default)] - /// Sets the [ExtXIFramesOnly] tag. + /// Sets the [`ExtXIFramesOnly`] tag. i_frames_only_tag: Option, #[builder(default)] - /// Sets the [ExtXIndependentSegments] tag. + /// Sets the [`ExtXIndependentSegments`] tag. independent_segments_tag: Option, #[builder(default)] - /// Sets the [ExtXStart] tag. + /// Sets the [`ExtXStart`] tag. start_tag: Option, #[builder(default)] - /// Sets the [ExtXEndList] tag. + /// Sets the [`ExtXEndList`] tag. end_list_tag: Option, - /// Sets all [MediaSegment]s. + /// Sets all [`MediaSegment`]s. segments: Vec, /// Sets the allowable excess duration of each media segment in the /// associated playlist. @@ -69,10 +69,7 @@ pub struct MediaPlaylist { impl MediaPlaylistBuilder { fn validate(&self) -> Result<(), String> { let required_version = self.required_version(); - let specified_version = self - .version_tag - .unwrap_or_else(|| required_version.into()) - .version(); + let specified_version = self.version_tag.map_or(required_version, |p| p.version()); if required_version > specified_version { return Err(Error::custom(format!( @@ -138,72 +135,6 @@ impl MediaPlaylistBuilder { Ok(()) } - fn required_version(&self) -> ProtocolVersion { - iter::empty() - .chain( - self.target_duration_tag - .iter() - .map(|t| t.required_version()), - ) - .chain(self.media_sequence_tag.iter().map(|t| { - if let Some(p) = t { - p.required_version() - } else { - ProtocolVersion::V1 - } - })) - .chain(self.discontinuity_sequence_tag.iter().map(|t| { - if let Some(p) = t { - p.required_version() - } else { - ProtocolVersion::V1 - } - })) - .chain(self.playlist_type_tag.iter().map(|t| { - if let Some(p) = t { - p.required_version() - } else { - ProtocolVersion::V1 - } - })) - .chain(self.i_frames_only_tag.iter().map(|t| { - if let Some(p) = t { - p.required_version() - } else { - ProtocolVersion::V1 - } - })) - .chain(self.independent_segments_tag.iter().map(|t| { - if let Some(p) = t { - p.required_version() - } else { - ProtocolVersion::V1 - } - })) - .chain(self.start_tag.iter().map(|t| { - if let Some(p) = t { - p.required_version() - } else { - ProtocolVersion::V1 - } - })) - .chain(self.end_list_tag.iter().map(|t| { - if let Some(p) = t { - p.required_version() - } else { - ProtocolVersion::V1 - } - })) - .chain(self.segments.iter().map(|t| { - t.iter() - .map(|s| s.required_version()) - .max() - .unwrap_or(ProtocolVersion::V1) - })) - .max() - .unwrap_or_else(ProtocolVersion::latest) - } - /// Adds a media segment to the resulting playlist. pub fn push_segment>(&mut self, value: VALUE) -> &mut Self { if let Some(segments) = &mut self.segments { @@ -214,50 +145,111 @@ impl MediaPlaylistBuilder { self } - /// Parse the rest of the [MediaPlaylist] from an m3u8 file. + /// Parse the rest of the [`MediaPlaylist`] from an m3u8 file. pub fn parse(&mut self, input: &str) -> crate::Result { parse_media_playlist(input, self) } } +impl RequiredVersion for MediaPlaylistBuilder { + fn required_version(&self) -> ProtocolVersion { + iter::empty() + .chain( + self.target_duration_tag + .iter() + .map(|p| p.required_version()), + ) + .chain( + self.media_sequence_tag + .flatten() + .iter() + .map(|p| p.required_version()), + ) + .chain( + self.discontinuity_sequence_tag + .flatten() + .iter() + .map(|p| p.required_version()), + ) + .chain( + self.playlist_type_tag + .flatten() + .iter() + .map(|p| p.required_version()), + ) + .chain( + self.i_frames_only_tag + .flatten() + .iter() + .map(|p| p.required_version()), + ) + .chain( + self.independent_segments_tag + .flatten() + .iter() + .map(|p| p.required_version()), + ) + .chain( + self.start_tag + .flatten() + .iter() + .map(|p| p.required_version()), + ) + .chain( + self.end_list_tag + .flatten() + .iter() + .map(|p| p.required_version()), + ) + .chain(self.segments.iter().map(|t| { + t.iter() + .map(|p| p.required_version()) + .max() + .unwrap_or(ProtocolVersion::V1) + })) + .max() + .unwrap_or_else(ProtocolVersion::latest) + } +} + impl MediaPlaylist { - /// Creates a [MediaPlaylistBuilder]. + /// Returns a builder for [`MediaPlaylist`]. pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() } - /// Returns the `EXT-X-VERSION` tag contained in the playlist. + /// Returns the [`ExtXVersion`] tag contained in the playlist. pub const fn version_tag(&self) -> ExtXVersion { self.version_tag } - /// Returns the `EXT-X-TARGETDURATION` tag contained in the playlist. + /// 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 `EXT-X-DISCONTINUITY-SEQUENCE` tag contained in the + /// Returns the [`ExtXDiscontinuitySequence`] tag contained in the /// playlist. pub const fn discontinuity_sequence_tag(&self) -> Option { self.discontinuity_sequence_tag } - /// Returns the `EXT-X-PLAYLIST-TYPE` tag contained in the playlist. + /// Returns the [`ExtXPlaylistType`] tag contained in the playlist. pub const fn playlist_type_tag(&self) -> Option { self.playlist_type_tag } - /// Returns the `EXT-X-I-FRAMES-ONLY` tag contained in the playlist. + /// Returns the [`ExtXIFramesOnly`] tag contained in the playlist. pub const fn i_frames_only_tag(&self) -> Option { self.i_frames_only_tag } - /// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist. + /// Returns the [`ExtXIndependentSegments`] tag contained in the playlist. pub const fn independent_segments_tag(&self) -> Option { self.independent_segments_tag } - /// Returns the `EXT-X-START` tag contained in the playlist. + /// Returns the [`ExtXStart`] tag contained in the playlist. pub const fn start_tag(&self) -> Option { self.start_tag } - /// Returns the `EXT-X-ENDLIST` tag contained in the playlist. + /// Returns the [`ExtXEndList`] tag contained in the playlist. pub const fn end_list_tag(&self) -> Option { self.end_list_tag } - /// Returns the media segments contained in the playlist. - pub fn segments(&self) -> &[MediaSegment] { &self.segments } + /// Returns the [`MediaSegment`]s contained in the playlist. + pub const fn segments(&self) -> &Vec { &self.segments } } impl fmt::Display for MediaPlaylist { @@ -306,6 +298,8 @@ fn parse_media_playlist( let mut has_discontinuity_tag = false; let mut has_version = false; // m3u8 files without ExtXVersion tags are ProtocolVersion::V1 + let mut available_key_tags = vec![]; + for (i, line) in input.parse::()?.into_iter().enumerate() { match line { Line::Tag(tag) => { @@ -336,10 +330,29 @@ fn parse_media_playlist( } Tag::ExtXKey(t) => { has_partial_segment = true; - segment.push_key_tag(t); + if !available_key_tags.is_empty() { + available_key_tags.push(t); + } else { + // An ExtXKey applies to every MediaSegment and to every Media + // Initialization Section declared by an EXT-X-MAP tag, that appears + // between it and the next EXT-X-KEY tag in the Playlist file with the + // same KEYFORMAT attribute (or the end of the Playlist file). + available_key_tags = available_key_tags + .into_iter() + .map(|k| { + if t.key_format() == k.key_format() { + t.clone() + } else { + k + } + }) + .collect(); + } } - Tag::ExtXMap(t) => { + Tag::ExtXMap(mut t) => { has_partial_segment = true; + + t.set_keys(available_key_tags.clone()); segment.map_tag(t); } Tag::ExtXProgramDateTime(t) => { @@ -379,7 +392,7 @@ fn parse_media_playlist( | Tag::ExtXIFrameStreamInf(_) | Tag::ExtXSessionData(_) | Tag::ExtXSessionKey(_) => { - return Err(Error::custom(tag)); + return Err(Error::unexpected_tag(tag)); } Tag::ExtXIndependentSegments(t) => { builder.independent_segments_tag(t); @@ -395,15 +408,18 @@ fn parse_media_playlist( } Line::Uri(uri) => { segment.uri(uri); + segment.keys(available_key_tags.clone()); segments.push(segment.build().map_err(Error::builder_error)?); segment = MediaSegment::builder(); has_partial_segment = false; } } } + if has_partial_segment { return Err(Error::invalid_input()); } + if !has_version { builder.version(ProtocolVersion::V1); } diff --git a/src/media_segment.rs b/src/media_segment.rs index 8f85a3c..7e3722d 100644 --- a/src/media_segment.rs +++ b/src/media_segment.rs @@ -6,43 +6,44 @@ use derive_builder::Builder; use crate::tags::{ ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime, }; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; +use crate::{Encrypted, RequiredVersion}; -/// Media segment. #[derive(Debug, Clone, Builder)] #[builder(setter(into, strip_option))] +/// Media segment. pub struct MediaSegment { #[builder(default)] - /// Sets all [ExtXKey] tags. - key_tags: Vec, + /// Sets all [`ExtXKey`] tags. + keys: Vec, #[builder(default)] - /// Sets an [ExtXMap] tag. + /// Sets an [`ExtXMap`] tag. map_tag: Option, #[builder(default)] - /// Sets an [ExtXByteRange] tag. + /// Sets an [`ExtXByteRange`] tag. byte_range_tag: Option, #[builder(default)] - /// Sets an [ExtXDateRange] tag. + /// Sets an [`ExtXDateRange`] tag. date_range_tag: Option, #[builder(default)] - /// Sets an [ExtXDiscontinuity] tag. + /// Sets an [`ExtXDiscontinuity`] tag. discontinuity_tag: Option, #[builder(default)] - /// Sets an [ExtXProgramDateTime] tag. + /// Sets an [`ExtXProgramDateTime`] tag. program_date_time_tag: Option, - /// Sets an [ExtInf] tag. + /// Sets an [`ExtInf`] tag. inf_tag: ExtInf, - /// Sets an Uri. + /// Sets an `URI`. uri: String, } impl MediaSegmentBuilder { - /// Pushes an [ExtXKey] tag. + /// Pushes an [`ExtXKey`] tag. pub fn push_key_tag>(&mut self, value: VALUE) -> &mut Self { - if let Some(key_tags) = &mut self.key_tags { + if let Some(key_tags) = &mut self.keys { key_tags.push(value.into()); } else { - self.key_tags = Some(vec![value.into()]); + self.keys = Some(vec![value.into()]); } self } @@ -50,7 +51,7 @@ impl MediaSegmentBuilder { impl fmt::Display for MediaSegment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for value in &self.key_tags { + for value in &self.keys { writeln!(f, "{}", value)?; } if let Some(value) = &self.map_tag { @@ -75,41 +76,38 @@ impl fmt::Display for MediaSegment { } impl MediaSegment { - /// Creates a [MediaSegmentBuilder]. + /// Creates a [`MediaSegmentBuilder`]. pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() } - /// Returns the URI of the media segment. + /// Returns the `URI` of the media segment. pub const fn uri(&self) -> &String { &self.uri } - /// Returns the `EXT-X-INF` tag associated with the media segment. + /// Returns the [`ExtInf`] tag associated with the media segment. pub const fn inf_tag(&self) -> &ExtInf { &self.inf_tag } - /// Returns the `EXT-X-BYTERANGE` tag associated with the media segment. + /// Returns the [`ExtXByteRange`] tag associated with the media segment. pub const fn byte_range_tag(&self) -> Option { self.byte_range_tag } - /// Returns the `EXT-X-DATERANGE` tag associated with the media segment. - pub fn date_range_tag(&self) -> Option<&ExtXDateRange> { self.date_range_tag.as_ref() } + /// Returns the [`ExtXDateRange`] tag associated with the media segment. + pub const fn date_range_tag(&self) -> &Option { &self.date_range_tag } - /// Returns the `EXT-X-DISCONTINUITY` tag associated with the media segment. + /// Returns the [`ExtXDiscontinuity`] tag associated with the media segment. pub const fn discontinuity_tag(&self) -> Option { self.discontinuity_tag } - /// Returns the `EXT-X-PROGRAM-DATE-TIME` tag associated with the media + /// Returns the [`ExtXProgramDateTime`] tag associated with the media /// segment. - pub fn program_date_time_tag(&self) -> Option<&ExtXProgramDateTime> { - self.program_date_time_tag.as_ref() + pub const fn program_date_time_tag(&self) -> Option { + self.program_date_time_tag } - /// Returns the `EXT-X-MAP` tag associated with the media segment. - pub fn map_tag(&self) -> Option<&ExtXMap> { self.map_tag.as_ref() } - - /// Returns the `EXT-X-KEY` tags associated with the media segment. - pub fn key_tags(&self) -> &[ExtXKey] { &self.key_tags } + /// Returns the [`ExtXMap`] tag associated with the media segment. + pub const fn map_tag(&self) -> &Option { &self.map_tag } } impl RequiredVersion for MediaSegment { fn required_version(&self) -> ProtocolVersion { iter::empty() - .chain(self.key_tags.iter().map(|t| t.required_version())) + .chain(self.keys.iter().map(|t| t.required_version())) .chain(self.map_tag.iter().map(|t| t.required_version())) .chain(self.byte_range_tag.iter().map(|t| t.required_version())) .chain(self.date_range_tag.iter().map(|t| t.required_version())) @@ -121,6 +119,12 @@ impl RequiredVersion for MediaSegment { ) .chain(iter::once(self.inf_tag.required_version())) .max() - .unwrap_or(ProtocolVersion::V7) + .unwrap_or_else(ProtocolVersion::latest) } } + +impl Encrypted for MediaSegment { + fn keys(&self) -> &Vec { &self.keys } + + fn keys_mut(&mut self) -> &mut Vec { &mut self.keys } +} diff --git a/src/tags/basic/m3u.rs b/src/tags/basic/m3u.rs index 2fc1523..07077d3 100644 --- a/src/tags/basic/m3u.rs +++ b/src/tags/basic/m3u.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.1.1. EXTM3U] /// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]** diff --git a/src/tags/basic/version.rs b/src/tags/basic/version.rs index d473810..54e559f 100644 --- a/src/tags/basic/version.rs +++ b/src/tags/basic/version.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.1.2. EXT-X-VERSION] /// The [`ExtXVersion`] tag indicates the compatibility version of the diff --git a/src/tags/master_playlist/i_frame_stream_inf.rs b/src/tags/master_playlist/i_frame_stream_inf.rs index f886430..7b051c6 100644 --- a/src/tags/master_playlist/i_frame_stream_inf.rs +++ b/src/tags/master_playlist/i_frame_stream_inf.rs @@ -3,9 +3,9 @@ use std::ops::{Deref, DerefMut}; use std::str::FromStr; use crate::attribute::AttributePairs; -use crate::types::{ProtocolVersion, RequiredVersion, StreamInf}; +use crate::types::{ProtocolVersion, StreamInf}; use crate::utils::{quote, tag, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.5.3. EXT-X-I-FRAME-STREAM-INF] /// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file, diff --git a/src/tags/master_playlist/media.rs b/src/tags/master_playlist/media.rs index 8358609..3f990e9 100644 --- a/src/tags/master_playlist/media.rs +++ b/src/tags/master_playlist/media.rs @@ -4,9 +4,9 @@ use std::str::FromStr; use derive_builder::Builder; use crate::attribute::AttributePairs; -use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion, RequiredVersion}; +use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion}; use crate::utils::{parse_yes_or_no, quote, tag, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.5.1. EXT-X-MEDIA] /// The [`ExtXMedia`] tag is used to relate [`Media Playlist`]s, diff --git a/src/tags/master_playlist/session_data.rs b/src/tags/master_playlist/session_data.rs index a6fd617..674947b 100644 --- a/src/tags/master_playlist/session_data.rs +++ b/src/tags/master_playlist/session_data.rs @@ -4,9 +4,9 @@ use std::str::FromStr; use derive_builder::Builder; use crate::attribute::AttributePairs; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::{quote, tag, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; /// The data of an [ExtXSessionData] tag. #[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)] diff --git a/src/tags/master_playlist/session_key.rs b/src/tags/master_playlist/session_key.rs index c9776fc..bdd7fdb 100644 --- a/src/tags/master_playlist/session_key.rs +++ b/src/tags/master_playlist/session_key.rs @@ -2,9 +2,9 @@ use std::fmt; use std::ops::{Deref, DerefMut}; use std::str::FromStr; -use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion, RequiredVersion}; +use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion}; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.3.4.5. EXT-X-SESSION-KEY] /// The [`ExtXSessionKey`] tag allows encryption keys from [`Media Playlist`]s diff --git a/src/tags/master_playlist/stream_inf.rs b/src/tags/master_playlist/stream_inf.rs index 4772ae6..065ed01 100644 --- a/src/tags/master_playlist/stream_inf.rs +++ b/src/tags/master_playlist/stream_inf.rs @@ -3,11 +3,9 @@ use std::ops::{Deref, DerefMut}; use std::str::FromStr; use crate::attribute::AttributePairs; -use crate::types::{ - ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, RequiredVersion, StreamInf, -}; +use crate::types::{ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, StreamInf}; use crate::utils::{quote, tag, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; /// [4.3.4.2. EXT-X-STREAM-INF] /// diff --git a/src/tags/media_playlist/discontinuity_sequence.rs b/src/tags/media_playlist/discontinuity_sequence.rs index 1ca9d8e..fbfd851 100644 --- a/src/tags/media_playlist/discontinuity_sequence.rs +++ b/src/tags/media_playlist/discontinuity_sequence.rs @@ -1,8 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; +use crate::RequiredVersion; /// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE] /// diff --git a/src/tags/media_playlist/end_list.rs b/src/tags/media_playlist/end_list.rs index 119de43..4e62b30 100644 --- a/src/tags/media_playlist/end_list.rs +++ b/src/tags/media_playlist/end_list.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.3.4. EXT-X-ENDLIST] /// The [`ExtXEndList`] tag indicates, that no more [`Media Segment`]s will be diff --git a/src/tags/media_playlist/i_frames_only.rs b/src/tags/media_playlist/i_frames_only.rs index 7c52ba4..59aae4b 100644 --- a/src/tags/media_playlist/i_frames_only.rs +++ b/src/tags/media_playlist/i_frames_only.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.3.6. EXT-X-I-FRAMES-ONLY] /// The [`ExtXIFramesOnly`] tag indicates that each [`Media Segment`] in the diff --git a/src/tags/media_playlist/media_sequence.rs b/src/tags/media_playlist/media_sequence.rs index e80a570..3564955 100644 --- a/src/tags/media_playlist/media_sequence.rs +++ b/src/tags/media_playlist/media_sequence.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE] /// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of diff --git a/src/tags/media_playlist/playlist_type.rs b/src/tags/media_playlist/playlist_type.rs index 80bcee8..6ce50b4 100644 --- a/src/tags/media_playlist/playlist_type.rs +++ b/src/tags/media_playlist/playlist_type.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.3.5. EXT-X-PLAYLIST-TYPE] /// diff --git a/src/tags/media_playlist/target_duration.rs b/src/tags/media_playlist/target_duration.rs index c70ab24..83a6aa9 100644 --- a/src/tags/media_playlist/target_duration.rs +++ b/src/tags/media_playlist/target_duration.rs @@ -2,9 +2,9 @@ use std::fmt; use std::str::FromStr; use std::time::Duration; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.3.1. EXT-X-TARGETDURATION] /// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`] diff --git a/src/tags/media_segment/byte_range.rs b/src/tags/media_segment/byte_range.rs index a79769f..0705d9d 100644 --- a/src/tags/media_segment/byte_range.rs +++ b/src/tags/media_segment/byte_range.rs @@ -2,9 +2,9 @@ use std::fmt; use std::ops::{Deref, DerefMut}; use std::str::FromStr; -use crate::types::{ByteRange, ProtocolVersion, RequiredVersion}; +use crate::types::{ByteRange, ProtocolVersion}; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.2.2. EXT-X-BYTERANGE] /// diff --git a/src/tags/media_segment/date_range.rs b/src/tags/media_segment/date_range.rs index 67c1089..12f15db 100644 --- a/src/tags/media_segment/date_range.rs +++ b/src/tags/media_segment/date_range.rs @@ -6,9 +6,9 @@ use std::time::Duration; use chrono::{DateTime, FixedOffset}; use crate::attribute::AttributePairs; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::{quote, tag, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.3.2.7. EXT-X-DATERANGE] /// @@ -106,7 +106,8 @@ impl ExtXDateRange { /// # use hls_m3u8::tags::ExtXDateRange; /// use chrono::offset::TimeZone; /// use chrono::{DateTime, FixedOffset}; -/// use hls_m3u8::types::{ProtocolVersion, RequiredVersion}; +/// use hls_m3u8::types::ProtocolVersion; +/// use hls_m3u8::RequiredVersion; /// /// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds /// diff --git a/src/tags/media_segment/discontinuity.rs b/src/tags/media_segment/discontinuity.rs index ee0d607..250ed65 100644 --- a/src/tags/media_segment/discontinuity.rs +++ b/src/tags/media_segment/discontinuity.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.2.3. EXT-X-DISCONTINUITY] /// The [`ExtXDiscontinuity`] tag indicates a discontinuity between the diff --git a/src/tags/media_segment/inf.rs b/src/tags/media_segment/inf.rs index e13338f..337ed1f 100644 --- a/src/tags/media_segment/inf.rs +++ b/src/tags/media_segment/inf.rs @@ -2,9 +2,9 @@ use std::fmt; use std::str::FromStr; use std::time::Duration; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.4.2.1. EXTINF] /// diff --git a/src/tags/media_segment/map.rs b/src/tags/media_segment/map.rs index 05de349..dc6552f 100644 --- a/src/tags/media_segment/map.rs +++ b/src/tags/media_segment/map.rs @@ -2,9 +2,10 @@ use std::fmt; use std::str::FromStr; use crate::attribute::AttributePairs; -use crate::types::{ByteRange, ProtocolVersion, RequiredVersion}; +use crate::tags::ExtXKey; +use crate::types::{ByteRange, ProtocolVersion}; use crate::utils::{quote, tag, unquote}; -use crate::Error; +use crate::{Encrypted, Error, RequiredVersion}; /// # [4.4.2.5. EXT-X-MAP] /// The [`ExtXMap`] tag specifies how to obtain the Media Initialization @@ -22,6 +23,7 @@ use crate::Error; pub struct ExtXMap { uri: String, range: Option, + keys: Vec, } impl ExtXMap { @@ -29,17 +31,19 @@ impl ExtXMap { /// Makes a new [`ExtXMap`] tag. pub fn new(uri: T) -> Self { - ExtXMap { + Self { uri: uri.to_string(), range: None, + keys: vec![], } } /// Makes a new [`ExtXMap`] tag with the given range. pub fn with_range(uri: T, range: ByteRange) -> Self { - ExtXMap { + Self { uri: uri.to_string(), range: Some(range), + keys: vec![], } } @@ -51,6 +55,13 @@ impl ExtXMap { pub const fn range(&self) -> Option { self.range } } +impl Encrypted for ExtXMap { + fn keys(&self) -> &Vec { &self.keys } + + fn keys_mut(&mut self) -> &mut Vec { &mut self.keys } +} + +/// This tag requires [`ProtocolVersion::V6`]. impl RequiredVersion for ExtXMap { fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V6 } } @@ -59,9 +70,11 @@ impl fmt::Display for ExtXMap { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", Self::PREFIX)?; write!(f, "URI={}", quote(&self.uri))?; + if let Some(value) = &self.range { write!(f, ",BYTERANGE={}", quote(value))?; } + Ok(()) } } @@ -90,7 +103,11 @@ impl FromStr for ExtXMap { } let uri = uri.ok_or_else(|| Error::missing_value("EXT-X-URI"))?; - Ok(ExtXMap { uri, range }) + Ok(Self { + uri, + range, + keys: vec![], + }) } } diff --git a/src/tags/media_segment/program_date_time.rs b/src/tags/media_segment/program_date_time.rs index 4ce0aa2..9350761 100644 --- a/src/tags/media_segment/program_date_time.rs +++ b/src/tags/media_segment/program_date_time.rs @@ -4,9 +4,9 @@ use std::str::FromStr; use chrono::{DateTime, FixedOffset}; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME] /// The [`ExtXProgramDateTime`] tag associates the first sample of a diff --git a/src/tags/shared/independent_segments.rs b/src/tags/shared/independent_segments.rs index 9655e0f..a617a5f 100644 --- a/src/tags/shared/independent_segments.rs +++ b/src/tags/shared/independent_segments.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::tag; -use crate::Error; +use crate::{Error, RequiredVersion}; /// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS] /// diff --git a/src/tags/shared/start.rs b/src/tags/shared/start.rs index 045bfa4..a69d8b1 100644 --- a/src/tags/shared/start.rs +++ b/src/tags/shared/start.rs @@ -2,9 +2,9 @@ use std::fmt; use std::str::FromStr; use crate::attribute::AttributePairs; -use crate::types::{ProtocolVersion, RequiredVersion, SignedDecimalFloatingPoint}; +use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint}; use crate::utils::{parse_yes_or_no, tag}; -use crate::Error; +use crate::{Error, RequiredVersion}; /// [4.3.5.2. EXT-X-START] /// diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..fa1df6a --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,124 @@ +use crate::tags::ExtXKey; +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; +/// use hls_m3u8::Encrypted; +/// +/// struct ExampleTag { +/// keys: Vec, +/// } +/// +/// // Implementing the trait is very simple: +/// // Simply expose the internal buffer, that contains all the keys. +/// impl Encrypted for ExampleTag { +/// fn keys(&self) -> &Vec { &self.keys } +/// +/// fn keys_mut(&mut self) -> &mut Vec { &mut self.keys } +/// } +/// +/// let mut example_tag = ExampleTag { keys: vec![] }; +/// +/// // adding new keys: +/// example_tag.set_keys(vec![ExtXKey::empty()]); +/// example_tag.push_key(ExtXKey::new( +/// EncryptionMethod::Aes128, +/// "http://www.example.com/data.bin", +/// )); +/// +/// // getting the keys: +/// assert_eq!( +/// example_tag.keys(), +/// &vec![ +/// ExtXKey::empty(), +/// ExtXKey::new(EncryptionMethod::Aes128, "http://www.example.com/data.bin",) +/// ] +/// ); +/// +/// assert_eq!( +/// example_tag.keys_mut(), +/// &mut vec![ +/// ExtXKey::empty(), +/// ExtXKey::new(EncryptionMethod::Aes128, "http://www.example.com/data.bin",) +/// ] +/// ); +/// +/// assert!(example_tag.is_encrypted()); +/// assert!(!example_tag.is_not_encrypted()); +/// ``` +pub trait Encrypted { + /// Returns a shared reference to all keys, that can be used to decrypt this + /// tag. + fn keys(&self) -> &Vec; + + /// Returns an exclusive reference to all keys, that can be used to decrypt + /// this tag. + fn keys_mut(&mut self) -> &mut Vec; + + /// Sets all keys, that can be used to decrypt this tag. + fn set_keys(&mut self, value: Vec) -> &mut Self { + let keys = self.keys_mut(); + *keys = value; + self + } + + /// Add a single key to the list of keys, that can be used to decrypt this + /// tag. + fn push_key(&mut self, value: ExtXKey) -> &mut Self { + self.keys_mut().push(value); + self + } + + /// Returns `true`, if the tag is encrypted. + /// + /// # Note + /// This will return `true`, if any of the keys satisfies + /// ```text + /// key.method() != EncryptionMethod::None + /// ``` + fn is_encrypted(&self) -> bool { + if self.keys().is_empty() { + return false; + } + self.keys() + .iter() + .any(|k| k.method() != EncryptionMethod::None) + } + + /// Returns `false`, if the tag is not encrypted. + /// + /// # Note + /// This is the inverse of [`is_encrypted`]. + /// + /// [`is_encrypted`]: #method.is_encrypted + fn is_not_encrypted(&self) -> bool { !self.is_encrypted() } +} + +/// # Example +/// Implementing it: +/// ``` +/// # use hls_m3u8::RequiredVersion; +/// use hls_m3u8::types::ProtocolVersion; +/// +/// struct ExampleTag(u64); +/// +/// impl RequiredVersion for ExampleTag { +/// fn required_version(&self) -> ProtocolVersion { +/// if self.0 == 5 { +/// ProtocolVersion::V4 +/// } else { +/// ProtocolVersion::V1 +/// } +/// } +/// } +/// assert_eq!(ExampleTag(5).required_version(), ProtocolVersion::V4); +/// assert_eq!(ExampleTag(2).required_version(), ProtocolVersion::V1); +/// ``` +pub trait RequiredVersion { + /// Returns the protocol compatibility version that this tag requires. + fn required_version(&self) -> ProtocolVersion; +} diff --git a/src/types/decryption_key.rs b/src/types/decryption_key.rs index a6995cd..9476d2a 100644 --- a/src/types/decryption_key.rs +++ b/src/types/decryption_key.rs @@ -6,10 +6,9 @@ use derive_builder::Builder; use crate::attribute::AttributePairs; use crate::types::{ EncryptionMethod, InitializationVector, KeyFormat, KeyFormatVersions, ProtocolVersion, - RequiredVersion, }; use crate::utils::{quote, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; #[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)] #[builder(setter(into), build_fn(validate = "Self::validate"))] diff --git a/src/types/key_format.rs b/src/types/key_format.rs index 01510db..d1268a4 100644 --- a/src/types/key_format.rs +++ b/src/types/key_format.rs @@ -1,9 +1,9 @@ use std::fmt; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::{quote, tag, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] /// [`KeyFormat`] specifies, how the key is represented in the diff --git a/src/types/key_format_versions.rs b/src/types/key_format_versions.rs index 9a871cb..91bc413 100644 --- a/src/types/key_format_versions.rs +++ b/src/types/key_format_versions.rs @@ -2,9 +2,9 @@ use std::fmt; use std::ops::{Deref, DerefMut}; use std::str::FromStr; -use crate::types::{ProtocolVersion, RequiredVersion}; +use crate::types::ProtocolVersion; use crate::utils::{quote, unquote}; -use crate::Error; +use crate::{Error, RequiredVersion}; /// A list of [usize], that can be used to indicate which version(s) /// this instance complies with, if more than one version of a particular diff --git a/src/types/protocol_version.rs b/src/types/protocol_version.rs index c90c5a5..d81d0ed 100644 --- a/src/types/protocol_version.rs +++ b/src/types/protocol_version.rs @@ -3,30 +3,6 @@ use std::str::FromStr; use crate::Error; -/// # Example -/// Implementing it: -/// ``` -/// # use hls_m3u8::types::{ProtocolVersion, RequiredVersion}; -/// # -/// struct NewTag(u64); -/// -/// impl RequiredVersion for NewTag { -/// fn required_version(&self) -> ProtocolVersion { -/// if self.0 == 5 { -/// ProtocolVersion::V4 -/// } else { -/// ProtocolVersion::V1 -/// } -/// } -/// } -/// assert_eq!(NewTag(5).required_version(), ProtocolVersion::V4); -/// assert_eq!(NewTag(2).required_version(), ProtocolVersion::V1); -/// ``` -pub trait RequiredVersion { - /// Returns the protocol compatibility version that this tag requires. - fn required_version(&self) -> ProtocolVersion; -} - /// # [7. Protocol Version Compatibility] /// The [`ProtocolVersion`] specifies, which m3u8 revision is required, to parse /// a certain tag correctly.