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