1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-29 02:00:59 +00:00

use required_version! macro

This commit is contained in:
Luro02 2019-10-05 16:24:48 +02:00
parent 32876e1371
commit b18e6ea4fb
6 changed files with 155 additions and 167 deletions

View file

@ -165,17 +165,6 @@ impl Error {
pub(crate) fn io<T: ToString>(value: T) -> Self { Self::from(ErrorKind::Io(value.to_string())) } pub(crate) fn io<T: ToString>(value: T) -> Self { Self::from(ErrorKind::Io(value.to_string())) }
pub(crate) fn required_version<T, U>(required_version: T, specified_version: U) -> Self
where
T: ToString,
U: ToString,
{
Self::from(ErrorKind::VersionError(
required_version.to_string(),
specified_version.to_string(),
))
}
pub(crate) fn builder_error<T: ToString>(value: T) -> Self { pub(crate) fn builder_error<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::BuilderError(value.to_string())) Self::from(ErrorKind::BuilderError(value.to_string()))
} }

View file

@ -44,6 +44,8 @@ pub use media_segment::{MediaSegment, MediaSegmentBuilder};
pub mod tags; pub mod tags;
pub mod types; pub mod types;
#[macro_use]
mod utils;
mod attribute; mod attribute;
mod error; mod error;
mod line; mod line;
@ -51,7 +53,6 @@ mod master_playlist;
mod media_playlist; mod media_playlist;
mod media_segment; mod media_segment;
mod traits; mod traits;
mod utils;
pub use error::Result; pub use error::Result;
pub use traits::*; pub use traits::*;

View file

@ -17,8 +17,6 @@ use crate::{Error, RequiredVersion};
#[builder(setter(into, strip_option))] #[builder(setter(into, strip_option))]
/// Master playlist. /// Master playlist.
pub struct MasterPlaylist { pub struct MasterPlaylist {
#[builder(default, setter(skip))]
version_tag: ExtXVersion,
#[builder(default)] #[builder(default)]
/// Sets the [`ExtXIndependentSegments`] tag. /// Sets the [`ExtXIndependentSegments`] tag.
/// ///
@ -199,17 +197,6 @@ impl MasterPlaylist {
} }
} }
macro_rules! required_version {
( $( $tag:expr ),* ) => {
::core::iter::empty()
$(
.chain(::core::iter::once($tag.required_version()))
)*
.max()
.unwrap_or_default()
}
}
impl RequiredVersion for MasterPlaylist { impl RequiredVersion for MasterPlaylist {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
required_version![ required_version![
@ -392,8 +379,6 @@ impl FromStr for MasterPlaylist {
// This tag can be ignored, because the // This tag can be ignored, because the
// MasterPlaylist will automatically set the // MasterPlaylist will automatically set the
// ExtXVersion tag to correct version! // ExtXVersion tag to correct version!
// builder.version(t.version());
} }
Tag::ExtInf(_) Tag::ExtInf(_)
| Tag::ExtXByteRange(_) | Tag::ExtXByteRange(_)

View file

@ -1,5 +1,4 @@
use std::fmt; use std::fmt;
use std::iter;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
@ -19,15 +18,6 @@ use crate::{Encrypted, Error, RequiredVersion};
#[builder(build_fn(validate = "Self::validate"))] #[builder(build_fn(validate = "Self::validate"))]
#[builder(setter(into, strip_option))] #[builder(setter(into, strip_option))]
pub struct MediaPlaylist { pub struct MediaPlaylist {
/// Sets the protocol compatibility version of the resulting playlist.
///
/// If the resulting playlist has tags which requires a compatibility
/// version greater than `version`,
/// `build()` method will fail with an `ErrorKind::InvalidInput` error.
///
/// The default is the maximum version among the tags in the playlist.
#[builder(setter(name = "version"))]
version_tag: ExtXVersion,
/// Sets the [`ExtXTargetDuration`] tag. /// Sets the [`ExtXTargetDuration`] tag.
target_duration_tag: ExtXTargetDuration, target_duration_tag: ExtXTargetDuration,
#[builder(default)] #[builder(default)]
@ -68,13 +58,6 @@ pub struct MediaPlaylist {
impl MediaPlaylistBuilder { impl MediaPlaylistBuilder {
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
let required_version = self.required_version();
let specified_version = self.version_tag.map_or(required_version, |p| p.version());
if required_version > specified_version {
return Err(Error::required_version(required_version, specified_version).to_string());
}
if let Some(target_duration) = &self.target_duration_tag { if let Some(target_duration) = &self.target_duration_tag {
self.validate_media_segments(target_duration.duration()) self.validate_media_segments(target_duration.duration())
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
@ -89,10 +72,12 @@ impl MediaPlaylistBuilder {
for s in segments { for s in segments {
// CHECK: `#EXT-X-TARGETDURATION` // CHECK: `#EXT-X-TARGETDURATION`
let segment_duration = s.inf_tag().duration(); let segment_duration = s.inf_tag().duration();
let rounded_segment_duration = if segment_duration.subsec_nanos() < 500_000_000 { let rounded_segment_duration = {
Duration::from_secs(segment_duration.as_secs()) if segment_duration.subsec_nanos() < 500_000_000 {
} else { Duration::from_secs(segment_duration.as_secs())
Duration::from_secs(segment_duration.as_secs() + 1) } else {
Duration::from_secs(segment_duration.as_secs() + 1)
}
}; };
let max_segment_duration = { let max_segment_duration = {
@ -149,62 +134,17 @@ impl MediaPlaylistBuilder {
impl RequiredVersion for MediaPlaylistBuilder { impl RequiredVersion for MediaPlaylistBuilder {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
iter::empty() required_version![
.chain( self.target_duration_tag,
self.target_duration_tag self.media_sequence_tag,
.iter() self.discontinuity_sequence_tag,
.map(|p| p.required_version()), self.playlist_type_tag,
) self.i_frames_only_tag,
.chain( self.independent_segments_tag,
self.media_sequence_tag self.start_tag,
.flatten() self.end_list_tag,
.iter() self.segments
.map(|p| p.required_version()), ]
)
.chain(
self.discontinuity_sequence_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.playlist_type_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.i_frames_only_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.independent_segments_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.start_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.end_list_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(self.segments.iter().map(|t| {
t.iter()
.map(|p| p.required_version())
.max()
.unwrap_or(ProtocolVersion::V1)
}))
.max()
.unwrap_or_else(ProtocolVersion::latest)
} }
} }
@ -212,9 +152,6 @@ impl MediaPlaylist {
/// Returns a builder for [`MediaPlaylist`]. /// Returns a builder for [`MediaPlaylist`].
pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() } pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() }
/// Returns the [`ExtXVersion`] tag contained in the playlist.
pub const fn version_tag(&self) -> ExtXVersion { self.version_tag }
/// Returns the [`ExtXTargetDuration`] tag contained in the playlist. /// Returns the [`ExtXTargetDuration`] tag contained in the playlist.
pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag } pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag }
@ -248,11 +185,27 @@ impl MediaPlaylist {
pub const fn segments(&self) -> &Vec<MediaSegment> { &self.segments } pub const fn segments(&self) -> &Vec<MediaSegment> { &self.segments }
} }
impl RequiredVersion for MediaPlaylist {
fn required_version(&self) -> ProtocolVersion {
required_version![
self.target_duration_tag,
self.media_sequence_tag,
self.discontinuity_sequence_tag,
self.playlist_type_tag,
self.i_frames_only_tag,
self.independent_segments_tag,
self.start_tag,
self.end_list_tag,
self.segments
]
}
}
impl fmt::Display for MediaPlaylist { impl fmt::Display for MediaPlaylist {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "{}", ExtM3u)?; writeln!(f, "{}", ExtM3u)?;
if self.version_tag.version() != ProtocolVersion::V1 { if self.required_version() != ProtocolVersion::V1 {
writeln!(f, "{}", self.version_tag)?; writeln!(f, "{}", ExtXVersion::new(self.required_version()))?;
} }
writeln!(f, "{}", self.target_duration_tag)?; writeln!(f, "{}", self.target_duration_tag)?;
if let Some(value) = &self.media_sequence_tag { if let Some(value) = &self.media_sequence_tag {
@ -292,9 +245,8 @@ fn parse_media_playlist(
let mut has_partial_segment = false; let mut has_partial_segment = false;
let mut has_discontinuity_tag = false; let mut has_discontinuity_tag = false;
let mut has_version = false; // m3u8 files without ExtXVersion tags are ProtocolVersion::V1
let mut available_key_tags = vec![]; let mut available_key_tags: Vec<crate::tags::ExtXKey> = vec![];
for (i, line) in input.parse::<Lines>()?.into_iter().enumerate() { for (i, line) in input.parse::<Lines>()?.into_iter().enumerate() {
match line { match line {
@ -307,10 +259,6 @@ fn parse_media_playlist(
} }
match tag { match tag {
Tag::ExtM3u(_) => return Err(Error::invalid_input()), Tag::ExtM3u(_) => return Err(Error::invalid_input()),
Tag::ExtXVersion(t) => {
builder.version(t.version());
has_version = true;
}
Tag::ExtInf(t) => { Tag::ExtInf(t) => {
has_partial_segment = true; has_partial_segment = true;
segment.inf_tag(t); segment.inf_tag(t);
@ -326,9 +274,7 @@ fn parse_media_playlist(
} }
Tag::ExtXKey(t) => { Tag::ExtXKey(t) => {
has_partial_segment = true; has_partial_segment = true;
if !available_key_tags.is_empty() { if available_key_tags.is_empty() {
available_key_tags.push(t);
} else {
// An ExtXKey applies to every MediaSegment and to every Media // An ExtXKey applies to every MediaSegment and to every Media
// Initialization Section declared by an EXT-X-MAP tag, that appears // Initialization Section declared by an EXT-X-MAP tag, that appears
// between it and the next EXT-X-KEY tag in the Playlist file with the // between it and the next EXT-X-KEY tag in the Playlist file with the
@ -343,6 +289,8 @@ fn parse_media_playlist(
} }
}) })
.collect(); .collect();
} else {
available_key_tags.push(t);
} }
} }
Tag::ExtXMap(mut t) => { Tag::ExtXMap(mut t) => {
@ -396,7 +344,7 @@ fn parse_media_playlist(
Tag::ExtXStart(t) => { Tag::ExtXStart(t) => {
builder.start_tag(t); builder.start_tag(t);
} }
Tag::Unknown(_) => { Tag::Unknown(_) | Tag::ExtXVersion(_) => {
// [6.3.1. General Client Responsibilities] // [6.3.1. General Client Responsibilities]
// > ignore any unrecognized tags. // > ignore any unrecognized tags.
} }
@ -416,10 +364,6 @@ fn parse_media_playlist(
return Err(Error::invalid_input()); return Err(Error::invalid_input());
} }
if !has_version {
builder.version(ProtocolVersion::V1);
}
builder.segments(segments); builder.segments(segments);
builder.build().map_err(Error::builder_error) builder.build().map_err(Error::builder_error)
} }

View file

@ -1,5 +1,4 @@
use std::fmt; use std::fmt;
use std::iter;
use derive_builder::Builder; use derive_builder::Builder;
@ -37,6 +36,99 @@ pub struct MediaSegment {
uri: String, uri: String,
} }
impl MediaSegment {
/// Returns a Builder for a [`MasterPlaylist`].
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
/// Returns the `URI` of the media segment.
pub const fn uri(&self) -> &String { &self.uri }
/// Sets the `URI` of the media segment.
pub fn set_uri<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 { impl MediaSegmentBuilder {
/// Pushes an [`ExtXKey`] tag. /// Pushes an [`ExtXKey`] tag.
pub fn push_key_tag<VALUE: Into<ExtXKey>>(&mut self, value: VALUE) -> &mut Self { pub fn push_key_tag<VALUE: Into<ExtXKey>>(&mut self, value: VALUE) -> &mut Self {
@ -75,51 +167,17 @@ impl fmt::Display for MediaSegment {
} }
} }
impl MediaSegment {
/// Creates a [`MediaSegmentBuilder`].
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
/// Returns the `URI` of the media segment.
pub const fn uri(&self) -> &String { &self.uri }
/// Returns the [`ExtInf`] tag associated with the media segment.
pub const fn inf_tag(&self) -> &ExtInf { &self.inf_tag }
/// Returns the [`ExtXByteRange`] tag associated with the media segment.
pub const fn byte_range_tag(&self) -> Option<ExtXByteRange> { self.byte_range_tag }
/// Returns the [`ExtXDateRange`] tag associated with the media segment.
pub const fn date_range_tag(&self) -> &Option<ExtXDateRange> { &self.date_range_tag }
/// Returns the [`ExtXDiscontinuity`] tag associated with the media segment.
pub const fn discontinuity_tag(&self) -> Option<ExtXDiscontinuity> { self.discontinuity_tag }
/// Returns the [`ExtXProgramDateTime`] tag associated with the media
/// segment.
pub const fn program_date_time_tag(&self) -> Option<ExtXProgramDateTime> {
self.program_date_time_tag
}
/// Returns the [`ExtXMap`] tag associated with the media segment.
pub const fn map_tag(&self) -> &Option<ExtXMap> { &self.map_tag }
}
impl RequiredVersion for MediaSegment { impl RequiredVersion for MediaSegment {
fn required_version(&self) -> ProtocolVersion { fn required_version(&self) -> ProtocolVersion {
iter::empty() required_version![
.chain(self.keys.iter().map(|t| t.required_version())) self.keys,
.chain(self.map_tag.iter().map(|t| t.required_version())) self.map_tag,
.chain(self.byte_range_tag.iter().map(|t| t.required_version())) self.byte_range_tag,
.chain(self.date_range_tag.iter().map(|t| t.required_version())) self.date_range_tag,
.chain(self.discontinuity_tag.iter().map(|t| t.required_version())) self.discontinuity_tag,
.chain( self.program_date_time_tag,
self.program_date_time_tag self.inf_tag
.iter() ]
.map(|t| t.required_version()),
)
.chain(iter::once(self.inf_tag.required_version()))
.max()
.unwrap_or_else(ProtocolVersion::latest)
} }
} }

View file

@ -1,5 +1,16 @@
use crate::Error; use crate::Error;
macro_rules! required_version {
( $( $tag:expr ),* ) => {
::core::iter::empty()
$(
.chain(::core::iter::once($tag.required_version()))
)*
.max()
.unwrap_or_default()
}
}
macro_rules! impl_from { macro_rules! impl_from {
( $($( $type:tt ),* => $target:path ),* ) => { ( $($( $type:tt ),* => $target:path ),* ) => {
use ::core::convert::From; use ::core::convert::From;