mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-25 08:31:00 +00:00
use shorthand #24
This commit is contained in:
parent
048f09bd14
commit
27d94faec4
39 changed files with 820 additions and 2481 deletions
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<ExtXIndependentSegments>,
|
||||
#[builder(default)]
|
||||
/// Sets the [`ExtXStart`] tag.
|
||||
/// The [`ExtXStart`] tag of the playlist.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This tag is optional.
|
||||
#[builder(default)]
|
||||
start_tag: Option<ExtXStart>,
|
||||
#[builder(default)]
|
||||
/// Sets the [`ExtXMedia`] tag.
|
||||
/// The [`ExtXMedia`] tags of the playlist.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This tag is optional.
|
||||
#[builder(default)]
|
||||
media_tags: Vec<ExtXMedia>,
|
||||
#[builder(default)]
|
||||
/// Sets all [`ExtXStreamInf`] tags.
|
||||
/// The [`ExtXStreamInf`] tags of the playlist.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This tag is optional.
|
||||
#[builder(default)]
|
||||
stream_inf_tags: Vec<ExtXStreamInf>,
|
||||
#[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<ExtXIFrameStreamInf>,
|
||||
#[builder(default)]
|
||||
/// Sets all [`ExtXSessionData`] tags.
|
||||
/// The [`ExtXSessionData`] tags of the playlist.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This tag is optional.
|
||||
#[builder(default)]
|
||||
session_data_tags: Vec<ExtXSessionData>,
|
||||
#[builder(default)]
|
||||
/// Sets all [`ExtXSessionKey`] tags.
|
||||
/// The [`ExtXSessionKey`] tags of the playlist.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This tag is optional.
|
||||
#[builder(default)]
|
||||
session_key_tags: Vec<ExtXSessionKey>,
|
||||
}
|
||||
|
||||
impl MasterPlaylist {
|
||||
/// Returns a Builder for a [`MasterPlaylist`].
|
||||
/// Returns a builder for a [`MasterPlaylist`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -76,124 +85,6 @@ impl MasterPlaylist {
|
|||
/// # Ok::<(), Box<dyn ::std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn builder() -> MasterPlaylistBuilder { MasterPlaylistBuilder::default() }
|
||||
|
||||
/// Returns the [`ExtXIndependentSegments`] tag contained in the playlist.
|
||||
pub const fn independent_segments(&self) -> Option<ExtXIndependentSegments> {
|
||||
self.independent_segments_tag
|
||||
}
|
||||
|
||||
/// Sets the [`ExtXIndependentSegments`] tag contained in the playlist.
|
||||
pub fn set_independent_segments<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXIndependentSegments>,
|
||||
{
|
||||
self.independent_segments_tag = value.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the [`ExtXStart`] tag contained in the playlist.
|
||||
pub const fn start(&self) -> Option<ExtXStart> { self.start_tag }
|
||||
|
||||
/// Sets the [`ExtXStart`] tag contained in the playlist.
|
||||
pub fn set_start<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXStart>,
|
||||
{
|
||||
self.start_tag = value.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the [`ExtXMedia`] tags contained in the playlist.
|
||||
pub const fn media_tags(&self) -> &Vec<ExtXMedia> { &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<T>(&mut self, value: Vec<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXMedia>,
|
||||
{
|
||||
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<ExtXStreamInf> { &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<T>(&mut self, value: Vec<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXStreamInf>,
|
||||
{
|
||||
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<ExtXIFrameStreamInf> {
|
||||
&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<T>(&mut self, value: Vec<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXIFrameStreamInf>,
|
||||
{
|
||||
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<ExtXSessionData> { &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<T>(&mut self, value: Vec<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXSessionData>,
|
||||
{
|
||||
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<ExtXSessionKey> { &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<T>(&mut self, value: Vec<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXSessionKey>,
|
||||
{
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ExtXMediaSequence>,
|
||||
#[builder(default)]
|
||||
/// Sets the [`ExtXDiscontinuitySequence`] tag.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(default)]
|
||||
discontinuity_sequence_tag: Option<ExtXDiscontinuitySequence>,
|
||||
#[builder(default)]
|
||||
/// Sets the [`ExtXPlaylistType`] tag.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(default)]
|
||||
playlist_type_tag: Option<ExtXPlaylistType>,
|
||||
#[builder(default)]
|
||||
/// Sets the [`ExtXIFramesOnly`] tag.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(default)]
|
||||
i_frames_only_tag: Option<ExtXIFramesOnly>,
|
||||
#[builder(default)]
|
||||
/// Sets the [`ExtXIndependentSegments`] tag.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(default)]
|
||||
independent_segments_tag: Option<ExtXIndependentSegments>,
|
||||
#[builder(default)]
|
||||
/// Sets the [`ExtXStart`] tag.
|
||||
start_tag: Option<ExtXStart>,
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(default)]
|
||||
start_tag: Option<ExtXStart>,
|
||||
/// Sets the [`ExtXEndList`] tag.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This field is optional.
|
||||
#[builder(default)]
|
||||
end_list_tag: Option<ExtXEndList>,
|
||||
/// Sets all [`MediaSegment`]s.
|
||||
/// A list of all [`MediaSegment`]s.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This field is required.
|
||||
segments: Vec<MediaSegment>,
|
||||
/// 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<ExtXMediaSequence> { self.media_sequence_tag }
|
||||
|
||||
/// Returns the [`ExtXDiscontinuitySequence`] tag contained in the
|
||||
/// playlist.
|
||||
pub const fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
|
||||
self.discontinuity_sequence_tag
|
||||
}
|
||||
|
||||
/// Returns the [`ExtXPlaylistType`] tag contained in the playlist.
|
||||
pub const fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> { self.playlist_type_tag }
|
||||
|
||||
/// Returns the [`ExtXIFramesOnly`] tag contained in the playlist.
|
||||
pub const fn i_frames_only_tag(&self) -> Option<ExtXIFramesOnly> { self.i_frames_only_tag }
|
||||
|
||||
/// Returns the [`ExtXIndependentSegments`] tag contained in the playlist.
|
||||
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
|
||||
self.independent_segments_tag
|
||||
}
|
||||
|
||||
/// Returns the [`ExtXStart`] tag contained in the playlist.
|
||||
pub const fn start_tag(&self) -> Option<ExtXStart> { self.start_tag }
|
||||
|
||||
/// Returns the [`ExtXEndList`] tag contained in the playlist.
|
||||
pub const fn end_list_tag(&self) -> Option<ExtXEndList> { self.end_list_tag }
|
||||
|
||||
/// Returns the [`MediaSegment`]s contained in the playlist.
|
||||
pub const fn segments(&self) -> &Vec<MediaSegment> { &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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ExtXKey>,
|
||||
/// The [`ExtXMap`] tag associated with the media segment.
|
||||
#[builder(default)]
|
||||
/// Sets an [`ExtXMap`] tag.
|
||||
map_tag: Option<ExtXMap>,
|
||||
/// The [`ExtXByteRange`] tag associated with the [`MediaSegment`].
|
||||
#[builder(default)]
|
||||
/// Sets an [`ExtXByteRange`] tag.
|
||||
byte_range_tag: Option<ExtXByteRange>,
|
||||
/// The [`ExtXDateRange`] tag associated with the media segment.
|
||||
#[builder(default)]
|
||||
/// Sets an [`ExtXDateRange`] tag.
|
||||
date_range_tag: Option<ExtXDateRange>,
|
||||
/// The [`ExtXDiscontinuity`] tag associated with the media segment.
|
||||
#[builder(default)]
|
||||
/// Sets an [`ExtXDiscontinuity`] tag.
|
||||
discontinuity_tag: Option<ExtXDiscontinuity>,
|
||||
/// The [`ExtXProgramDateTime`] tag associated with the media
|
||||
/// segment.
|
||||
#[builder(default)]
|
||||
/// Sets an [`ExtXProgramDateTime`] tag.
|
||||
program_date_time_tag: Option<ExtXProgramDateTime>,
|
||||
/// 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<T>(&mut self, value: T) -> &mut Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
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<T>(&mut self, value: T) -> &mut Self
|
||||
where
|
||||
T: Into<ExtInf>,
|
||||
{
|
||||
self.inf_tag = value.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the [`ExtXByteRange`] tag associated with the media segment.
|
||||
pub const fn byte_range_tag(&self) -> Option<ExtXByteRange> { self.byte_range_tag }
|
||||
|
||||
/// Sets the [`ExtXByteRange`] tag associated with the media segment.
|
||||
pub fn set_byte_range_tag<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXByteRange>,
|
||||
{
|
||||
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<ExtXDateRange> { &self.date_range_tag }
|
||||
|
||||
/// Sets the [`ExtXDateRange`] tag associated with the media segment.
|
||||
pub fn set_date_range_tag<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXDateRange>,
|
||||
{
|
||||
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<ExtXDiscontinuity> { self.discontinuity_tag }
|
||||
|
||||
/// Sets the [`ExtXDiscontinuity`] tag associated with the media segment.
|
||||
pub fn set_discontinuity_tag<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXDiscontinuity>,
|
||||
{
|
||||
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<ExtXProgramDateTime> {
|
||||
self.program_date_time_tag
|
||||
}
|
||||
|
||||
/// Sets the [`ExtXProgramDateTime`] tag associated with the media
|
||||
/// segment.
|
||||
pub fn set_program_date_time_tag<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXProgramDateTime>,
|
||||
{
|
||||
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<ExtXMap> { &self.map_tag }
|
||||
|
||||
/// Sets the [`ExtXMap`] tag associated with the media segment.
|
||||
pub fn set_map_tag<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<ExtXMap>,
|
||||
{
|
||||
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(())
|
||||
|
|
|
@ -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"));
|
||||
|
||||
|
|
|
@ -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<String>,
|
||||
/// 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<String>,
|
||||
/// 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<String>,
|
||||
/// 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<InStreamId>,
|
||||
/// 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<String>,
|
||||
/// Sets the parameters of the rendition.
|
||||
/// The [`Channels`].
|
||||
#[builder(setter(strip_option), default)]
|
||||
channels: Option<Channels>,
|
||||
}
|
||||
|
@ -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<T: Into<String>>(&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<T: Into<String>>(&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<String> { &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<T: Into<String>>(&mut self, value: Option<T>) -> &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<String> { &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<T: Into<String>>(&mut self, value: Option<T>) -> &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<String> { &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<T: Into<String>>(&mut self, value: Option<T>) -> &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<InStreamId> { 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<InStreamId>) -> &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<String> { &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<T: Into<String>>(&mut self, value: Option<T>) -> &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<Channels> { &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<T: Into<Channels>>(&mut self, value: Option<T>) -> &mut Self {
|
||||
self.channels = value.map(Into::into);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RequiredVersion for ExtXMedia {
|
||||
|
|
|
@ -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<String>,
|
||||
}
|
||||
|
@ -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<String> { &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<T: ToString>(&mut self, value: Option<T>) -> &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<T: ToString>(&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 }
|
||||
}
|
||||
|
|
|
@ -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:<attribute-list>
|
||||
/// ```
|
||||
///
|
||||
/// [`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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<DecimalFloatingPoint>,
|
||||
/// The group identifier for the audio in the variant stream.
|
||||
audio: Option<String>,
|
||||
/// The group identifier for the subtitles in the variant stream.
|
||||
subtitles: Option<String>,
|
||||
/// The value of the [`ClosedCaptions`] attribute.
|
||||
closed_captions: Option<ClosedCaptions>,
|
||||
#[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<f64> { 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<T: ToString>(&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<f64>) -> &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<f64> { 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<String> { &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<T: Into<String>>(&mut self, value: Option<T>) -> &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<String> { &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<T: Into<String>>(&mut self, value: Option<T>) -> &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<ClosedCaptions> { &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<ClosedCaptions>) -> &mut Self {
|
||||
self.closed_captions = value;
|
||||
/// This function panics, if the float is infinite or negative.
|
||||
pub fn set_frame_rate<VALUE: Into<f64>>(&mut self, value_0: Option<VALUE>) -> &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(())
|
||||
}
|
||||
|
|
|
@ -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:<number>
|
||||
/// ```
|
||||
/// 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 }
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:<n>[@<o>]
|
||||
/// ```
|
||||
///
|
||||
/// 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 }
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
/// The date at which the [`ExtXDateRange`] begins.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This attribute is required.
|
||||
start_date: DateTime<FixedOffset>,
|
||||
#[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<DateTime<FixedOffset>>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
end_date: Option<DateTime<FixedOffset>>,
|
||||
/// 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<Duration>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
duration: Option<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.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This attribute is optional.
|
||||
#[builder(setter(strip_option), default)]
|
||||
planned_duration: Option<Duration>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1
|
||||
/// You can read about this attribute here
|
||||
/// <https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1>
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This attribute is optional.
|
||||
#[builder(setter(strip_option), default)]
|
||||
scte35_cmd: Option<String>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1
|
||||
/// You can read about this attribute here
|
||||
/// <https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1>
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This attribute is optional.
|
||||
#[builder(setter(strip_option), default)]
|
||||
scte35_out: Option<String>,
|
||||
#[builder(setter(strip_option), default)]
|
||||
/// https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1
|
||||
/// You can read about this attribute here
|
||||
/// <https://tools.ietf.org/html/rfc8216#section-4.3.2.7.1>
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This attribute is optional.
|
||||
#[builder(setter(strip_option), default)]
|
||||
scte35_in: Option<String>,
|
||||
#[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<String, Value>,
|
||||
}
|
||||
|
||||
|
@ -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<T: ToString>(&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<String> { &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<T: ToString>(&mut self, value: Option<T>) -> &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<FixedOffset> { 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<T>(&mut self, value: T) -> &mut Self
|
||||
where
|
||||
T: Into<DateTime<FixedOffset>>,
|
||||
{
|
||||
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<DateTime<FixedOffset>> { 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<T>(&mut self, value: Option<T>) -> &mut Self
|
||||
where
|
||||
T: Into<DateTime<FixedOffset>>,
|
||||
{
|
||||
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<Duration> { 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<Duration>) -> &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<Duration> { 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<Duration>) -> &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<String> { &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<String> { &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<String> { &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<String, Value> { &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<String, Value> {
|
||||
&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<String, Value>) -> &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))?;
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
|
|
|
@ -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<ByteRange>,
|
||||
#[shorthand(enable(skip))]
|
||||
keys: Vec<ExtXKey>,
|
||||
}
|
||||
|
||||
|
@ -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<T: ToString>(&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<ByteRange> { 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<ByteRange>) -> &mut Self {
|
||||
self.range = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Encrypted for ExtXMap {
|
||||
|
|
|
@ -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<FixedOffset>);
|
||||
|
||||
|
@ -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};
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<usize>,
|
||||
}
|
||||
|
||||
|
@ -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<usize>) -> 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<usize> { 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<usize>) -> &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<Self, Self::Err> {
|
||||
let tokens = s.splitn(2, '@').collect::<Vec<_>>();
|
||||
|
||||
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]
|
||||
|
|
|
@ -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<Self, Self::Err> {
|
||||
let parameters = input.split('/').collect::<Vec<_>>();
|
||||
|
||||
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(","))?;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ impl Deref for DecimalFloatingPoint {
|
|||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl From<f64> for DecimalFloatingPoint {
|
||||
fn from(value: f64) -> Self {
|
||||
let mut result = value;
|
||||
|
@ -64,6 +65,7 @@ impl From<f64> for DecimalFloatingPoint {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl From<f32> 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::<DecimalFloatingPoint>().unwrap()
|
||||
);
|
||||
|
||||
let decimal_floating_point = DecimalFloatingPoint::new(4.1).unwrap();
|
||||
assert_eq!(
|
||||
decimal_floating_point,
|
||||
DecimalFloatingPoint::new(4.1).unwrap(),
|
||||
"4.1".parse::<DecimalFloatingPoint>().unwrap()
|
||||
);
|
||||
|
||||
|
|
|
@ -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<Self, Self::Err> {
|
||||
let tokens = input.splitn(2, 'x').collect::<Vec<_>>();
|
||||
|
||||
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::<DecimalResolution>().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
DecimalResolution::new(1280, 720),
|
||||
"1280x720".parse::<DecimalResolution>().unwrap()
|
||||
);
|
||||
|
||||
assert!("1280".parse::<DecimalResolution>().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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<String>,
|
||||
/// 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<InitializationVector>,
|
||||
#[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<KeyFormat>,
|
||||
///
|
||||
/// # 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<KeyFormat>,
|
||||
/// 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<KeyFormatVersions>,
|
||||
}
|
||||
|
||||
|
@ -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<String> { &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<T: ToString>(&mut self, value: Option<T>) -> &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<T>(&mut self, value: Option<T>) -> &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<KeyFormat> { 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<T: Into<KeyFormat>>(&mut self, value: Option<T>) -> &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<KeyFormatVersions> {
|
||||
&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<T: Into<KeyFormatVersions>>(
|
||||
&mut self,
|
||||
value: Option<T>,
|
||||
) -> &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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Self, Self::Err> {
|
||||
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::<InitializationVector>()
|
||||
.unwrap(),
|
||||
InitializationVector([
|
||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"0X10ef8f758ca555115584bb5b3c687f52"
|
||||
.parse::<InitializationVector>()
|
||||
.unwrap(),
|
||||
InitializationVector([
|
||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"0X10EF8F758CA555115584BB5B3C687F52"
|
||||
.parse::<InitializationVector>()
|
||||
.unwrap(),
|
||||
InitializationVector([
|
||||
16, 239, 143, 117, 140, 165, 85, 17, 85, 132, 187, 91, 60, 104, 127, 82
|
||||
])
|
||||
);
|
||||
|
||||
assert!("garbage".parse::<InitializationVector>().is_err());
|
||||
assert!("0xgarbage".parse::<InitializationVector>().is_err());
|
||||
assert!("0x12".parse::<InitializationVector>().is_err());
|
||||
assert!("0X10EF8F758CA555115584BB5B3C687F5Z"
|
||||
.parse::<InitializationVector>()
|
||||
.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]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
104
src/types/resolution.rs
Normal file
104
src/types/resolution.rs
Normal file
|
@ -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<Self, Self::Err> {
|
||||
let tokens = input.splitn(2, 'x').collect::<Vec<_>>();
|
||||
|
||||
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::<Resolution>().unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Resolution::new(1280, 720),
|
||||
"1280x720".parse::<Resolution>().unwrap()
|
||||
);
|
||||
|
||||
assert!("1280".parse::<Resolution>().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));
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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<u64>,
|
||||
/// 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<String>,
|
||||
#[builder(default)]
|
||||
/// The resolution of the stream.
|
||||
resolution: Option<DecimalResolution>,
|
||||
///
|
||||
/// # 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<Resolution>,
|
||||
/// 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<HdcpLevel>,
|
||||
#[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<String>,
|
||||
}
|
||||
|
||||
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<String> { &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<T: ToString>(&mut self, value: Option<T>) -> &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<u64> { 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<u64>) -> &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<String> { &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<T: ToString>(&mut self, value: Option<T>) -> &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<HdcpLevel> { 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<T: Into<HdcpLevel>>(&mut self, value: Option<T>) -> &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"));
|
||||
|
||||
|
|
23
src/utils.rs
23
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<T: AsRef<str>>(s: T) -> crate::Result<bool> {
|
||||
match s.as_ref() {
|
||||
"YES" => Ok(true),
|
||||
|
|
Loading…
Reference in a new issue