1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-25 19:58:14 +00:00
This commit is contained in:
Luro02 2019-10-04 11:02:21 +02:00
parent ebddb7a0e2
commit 4b4cffc248
31 changed files with 354 additions and 207 deletions

View file

@ -76,6 +76,10 @@ pub enum ErrorKind {
/// An unexpected value.
UnexpectedAttribute(String),
#[fail(display = "Unexpected Tag: {:?}", _0)]
/// An unexpected tag.
UnexpectedTag(String),
/// Hints that destructuring should not be exhaustive.
///
/// This enum may grow additional variants, so this makes sure clients
@ -119,6 +123,10 @@ impl Error {
Self::from(ErrorKind::UnexpectedAttribute(value.to_string()))
}
pub(crate) fn unexpected_tag<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::UnexpectedTag(value.to_string()))
}
pub(crate) fn invalid_input() -> Self { Self::from(ErrorKind::InvalidInput) }
pub(crate) fn parse_int_error<T: ToString>(value: T) -> Self {

View file

@ -1,4 +1,5 @@
#![forbid(unsafe_code)]
#![feature(option_flattening)]
#![warn(
//clippy::pedantic,
clippy::nursery,
@ -49,6 +50,8 @@ mod line;
mod master_playlist;
mod media_playlist;
mod media_segment;
mod traits;
mod utils;
pub use error::Result;
pub use traits::*;

View file

@ -10,8 +10,8 @@ use crate::tags::{
ExtM3u, ExtXIFrameStreamInf, ExtXIndependentSegments, ExtXMedia, ExtXSessionData,
ExtXSessionKey, ExtXStart, ExtXStreamInf, ExtXVersion,
};
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion, RequiredVersion};
use crate::Error;
use crate::types::{ClosedCaptions, MediaType, ProtocolVersion};
use crate::{Error, RequiredVersion};
/// Master playlist.
#[derive(Debug, Clone, Builder)]

View file

@ -11,8 +11,8 @@ use crate::tags::{
ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly, ExtXIndependentSegments,
ExtXMediaSequence, ExtXPlaylistType, ExtXStart, ExtXTargetDuration, ExtXVersion,
};
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::Error;
use crate::types::ProtocolVersion;
use crate::{Encrypted, Error, RequiredVersion};
/// Media playlist.
#[derive(Debug, Clone, Builder)]
@ -28,30 +28,30 @@ pub struct MediaPlaylist {
/// The default is the maximum version among the tags in the playlist.
#[builder(setter(name = "version"))]
version_tag: ExtXVersion,
/// Sets the [ExtXTargetDuration] tag.
/// Sets the [`ExtXTargetDuration`] tag.
target_duration_tag: ExtXTargetDuration,
#[builder(default)]
/// Sets the [ExtXMediaSequence] tag.
/// Sets the [`ExtXMediaSequence`] tag.
media_sequence_tag: Option<ExtXMediaSequence>,
#[builder(default)]
/// Sets the [ExtXDiscontinuitySequence] tag.
/// Sets the [`ExtXDiscontinuitySequence`] tag.
discontinuity_sequence_tag: Option<ExtXDiscontinuitySequence>,
#[builder(default)]
/// Sets the [ExtXPlaylistType] tag.
/// Sets the [`ExtXPlaylistType`] tag.
playlist_type_tag: Option<ExtXPlaylistType>,
#[builder(default)]
/// Sets the [ExtXIFramesOnly] tag.
/// Sets the [`ExtXIFramesOnly`] tag.
i_frames_only_tag: Option<ExtXIFramesOnly>,
#[builder(default)]
/// Sets the [ExtXIndependentSegments] tag.
/// Sets the [`ExtXIndependentSegments`] tag.
independent_segments_tag: Option<ExtXIndependentSegments>,
#[builder(default)]
/// Sets the [ExtXStart] tag.
/// Sets the [`ExtXStart`] tag.
start_tag: Option<ExtXStart>,
#[builder(default)]
/// Sets the [ExtXEndList] tag.
/// Sets the [`ExtXEndList`] tag.
end_list_tag: Option<ExtXEndList>,
/// Sets all [MediaSegment]s.
/// Sets all [`MediaSegment`]s.
segments: Vec<MediaSegment>,
/// Sets the allowable excess duration of each media segment in the
/// associated playlist.
@ -69,10 +69,7 @@ pub struct MediaPlaylist {
impl MediaPlaylistBuilder {
fn validate(&self) -> Result<(), String> {
let required_version = self.required_version();
let specified_version = self
.version_tag
.unwrap_or_else(|| required_version.into())
.version();
let specified_version = self.version_tag.map_or(required_version, |p| p.version());
if required_version > specified_version {
return Err(Error::custom(format!(
@ -138,72 +135,6 @@ impl MediaPlaylistBuilder {
Ok(())
}
fn required_version(&self) -> ProtocolVersion {
iter::empty()
.chain(
self.target_duration_tag
.iter()
.map(|t| t.required_version()),
)
.chain(self.media_sequence_tag.iter().map(|t| {
if let Some(p) = t {
p.required_version()
} else {
ProtocolVersion::V1
}
}))
.chain(self.discontinuity_sequence_tag.iter().map(|t| {
if let Some(p) = t {
p.required_version()
} else {
ProtocolVersion::V1
}
}))
.chain(self.playlist_type_tag.iter().map(|t| {
if let Some(p) = t {
p.required_version()
} else {
ProtocolVersion::V1
}
}))
.chain(self.i_frames_only_tag.iter().map(|t| {
if let Some(p) = t {
p.required_version()
} else {
ProtocolVersion::V1
}
}))
.chain(self.independent_segments_tag.iter().map(|t| {
if let Some(p) = t {
p.required_version()
} else {
ProtocolVersion::V1
}
}))
.chain(self.start_tag.iter().map(|t| {
if let Some(p) = t {
p.required_version()
} else {
ProtocolVersion::V1
}
}))
.chain(self.end_list_tag.iter().map(|t| {
if let Some(p) = t {
p.required_version()
} else {
ProtocolVersion::V1
}
}))
.chain(self.segments.iter().map(|t| {
t.iter()
.map(|s| s.required_version())
.max()
.unwrap_or(ProtocolVersion::V1)
}))
.max()
.unwrap_or_else(ProtocolVersion::latest)
}
/// Adds a media segment to the resulting playlist.
pub fn push_segment<VALUE: Into<MediaSegment>>(&mut self, value: VALUE) -> &mut Self {
if let Some(segments) = &mut self.segments {
@ -214,50 +145,111 @@ impl MediaPlaylistBuilder {
self
}
/// Parse the rest of the [MediaPlaylist] from an m3u8 file.
/// Parse the rest of the [`MediaPlaylist`] from an m3u8 file.
pub fn parse(&mut self, input: &str) -> crate::Result<MediaPlaylist> {
parse_media_playlist(input, self)
}
}
impl RequiredVersion for MediaPlaylistBuilder {
fn required_version(&self) -> ProtocolVersion {
iter::empty()
.chain(
self.target_duration_tag
.iter()
.map(|p| p.required_version()),
)
.chain(
self.media_sequence_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.discontinuity_sequence_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.playlist_type_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.i_frames_only_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.independent_segments_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.start_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(
self.end_list_tag
.flatten()
.iter()
.map(|p| p.required_version()),
)
.chain(self.segments.iter().map(|t| {
t.iter()
.map(|p| p.required_version())
.max()
.unwrap_or(ProtocolVersion::V1)
}))
.max()
.unwrap_or_else(ProtocolVersion::latest)
}
}
impl MediaPlaylist {
/// Creates a [MediaPlaylistBuilder].
/// Returns a builder for [`MediaPlaylist`].
pub fn builder() -> MediaPlaylistBuilder { MediaPlaylistBuilder::default() }
/// Returns the `EXT-X-VERSION` tag contained in the playlist.
/// Returns the [`ExtXVersion`] tag contained in the playlist.
pub const fn version_tag(&self) -> ExtXVersion { self.version_tag }
/// Returns the `EXT-X-TARGETDURATION` tag contained in the playlist.
/// Returns the [`ExtXTargetDuration`] tag contained in the playlist.
pub const fn target_duration_tag(&self) -> ExtXTargetDuration { self.target_duration_tag }
/// Returns the `EXT-X-MEDIA-SEQUENCE` tag contained in the playlist.
pub const fn media_sequence_tag(&self) -> Option<ExtXMediaSequence> { self.media_sequence_tag }
/// Returns the `EXT-X-DISCONTINUITY-SEQUENCE` tag contained in the
/// Returns the [`ExtXDiscontinuitySequence`] tag contained in the
/// playlist.
pub const fn discontinuity_sequence_tag(&self) -> Option<ExtXDiscontinuitySequence> {
self.discontinuity_sequence_tag
}
/// Returns the `EXT-X-PLAYLIST-TYPE` tag contained in the playlist.
/// Returns the [`ExtXPlaylistType`] tag contained in the playlist.
pub const fn playlist_type_tag(&self) -> Option<ExtXPlaylistType> { self.playlist_type_tag }
/// Returns the `EXT-X-I-FRAMES-ONLY` tag contained in the playlist.
/// Returns the [`ExtXIFramesOnly`] tag contained in the playlist.
pub const fn i_frames_only_tag(&self) -> Option<ExtXIFramesOnly> { self.i_frames_only_tag }
/// Returns the `EXT-X-INDEPENDENT-SEGMENTS` tag contained in the playlist.
/// Returns the [`ExtXIndependentSegments`] tag contained in the playlist.
pub const fn independent_segments_tag(&self) -> Option<ExtXIndependentSegments> {
self.independent_segments_tag
}
/// Returns the `EXT-X-START` tag contained in the playlist.
/// Returns the [`ExtXStart`] tag contained in the playlist.
pub const fn start_tag(&self) -> Option<ExtXStart> { self.start_tag }
/// Returns the `EXT-X-ENDLIST` tag contained in the playlist.
/// Returns the [`ExtXEndList`] tag contained in the playlist.
pub const fn end_list_tag(&self) -> Option<ExtXEndList> { self.end_list_tag }
/// Returns the media segments contained in the playlist.
pub fn segments(&self) -> &[MediaSegment] { &self.segments }
/// Returns the [`MediaSegment`]s contained in the playlist.
pub const fn segments(&self) -> &Vec<MediaSegment> { &self.segments }
}
impl fmt::Display for MediaPlaylist {
@ -306,6 +298,8 @@ fn parse_media_playlist(
let mut has_discontinuity_tag = false;
let mut has_version = false; // m3u8 files without ExtXVersion tags are ProtocolVersion::V1
let mut available_key_tags = vec![];
for (i, line) in input.parse::<Lines>()?.into_iter().enumerate() {
match line {
Line::Tag(tag) => {
@ -336,10 +330,29 @@ fn parse_media_playlist(
}
Tag::ExtXKey(t) => {
has_partial_segment = true;
segment.push_key_tag(t);
if !available_key_tags.is_empty() {
available_key_tags.push(t);
} else {
// An ExtXKey applies to every MediaSegment and to every Media
// Initialization Section declared by an EXT-X-MAP tag, that appears
// between it and the next EXT-X-KEY tag in the Playlist file with the
// same KEYFORMAT attribute (or the end of the Playlist file).
available_key_tags = available_key_tags
.into_iter()
.map(|k| {
if t.key_format() == k.key_format() {
t.clone()
} else {
k
}
})
.collect();
}
}
Tag::ExtXMap(t) => {
Tag::ExtXMap(mut t) => {
has_partial_segment = true;
t.set_keys(available_key_tags.clone());
segment.map_tag(t);
}
Tag::ExtXProgramDateTime(t) => {
@ -379,7 +392,7 @@ fn parse_media_playlist(
| Tag::ExtXIFrameStreamInf(_)
| Tag::ExtXSessionData(_)
| Tag::ExtXSessionKey(_) => {
return Err(Error::custom(tag));
return Err(Error::unexpected_tag(tag));
}
Tag::ExtXIndependentSegments(t) => {
builder.independent_segments_tag(t);
@ -395,15 +408,18 @@ fn parse_media_playlist(
}
Line::Uri(uri) => {
segment.uri(uri);
segment.keys(available_key_tags.clone());
segments.push(segment.build().map_err(Error::builder_error)?);
segment = MediaSegment::builder();
has_partial_segment = false;
}
}
}
if has_partial_segment {
return Err(Error::invalid_input());
}
if !has_version {
builder.version(ProtocolVersion::V1);
}

View file

@ -6,43 +6,44 @@ use derive_builder::Builder;
use crate::tags::{
ExtInf, ExtXByteRange, ExtXDateRange, ExtXDiscontinuity, ExtXKey, ExtXMap, ExtXProgramDateTime,
};
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::{Encrypted, RequiredVersion};
/// Media segment.
#[derive(Debug, Clone, Builder)]
#[builder(setter(into, strip_option))]
/// Media segment.
pub struct MediaSegment {
#[builder(default)]
/// Sets all [ExtXKey] tags.
key_tags: Vec<ExtXKey>,
/// Sets all [`ExtXKey`] tags.
keys: Vec<ExtXKey>,
#[builder(default)]
/// Sets an [ExtXMap] tag.
/// Sets an [`ExtXMap`] tag.
map_tag: Option<ExtXMap>,
#[builder(default)]
/// Sets an [ExtXByteRange] tag.
/// Sets an [`ExtXByteRange`] tag.
byte_range_tag: Option<ExtXByteRange>,
#[builder(default)]
/// Sets an [ExtXDateRange] tag.
/// Sets an [`ExtXDateRange`] tag.
date_range_tag: Option<ExtXDateRange>,
#[builder(default)]
/// Sets an [ExtXDiscontinuity] tag.
/// Sets an [`ExtXDiscontinuity`] tag.
discontinuity_tag: Option<ExtXDiscontinuity>,
#[builder(default)]
/// Sets an [ExtXProgramDateTime] tag.
/// Sets an [`ExtXProgramDateTime`] tag.
program_date_time_tag: Option<ExtXProgramDateTime>,
/// Sets an [ExtInf] tag.
/// Sets an [`ExtInf`] tag.
inf_tag: ExtInf,
/// Sets an Uri.
/// Sets an `URI`.
uri: String,
}
impl MediaSegmentBuilder {
/// Pushes an [ExtXKey] tag.
/// Pushes an [`ExtXKey`] tag.
pub fn push_key_tag<VALUE: Into<ExtXKey>>(&mut self, value: VALUE) -> &mut Self {
if let Some(key_tags) = &mut self.key_tags {
if let Some(key_tags) = &mut self.keys {
key_tags.push(value.into());
} else {
self.key_tags = Some(vec![value.into()]);
self.keys = Some(vec![value.into()]);
}
self
}
@ -50,7 +51,7 @@ impl MediaSegmentBuilder {
impl fmt::Display for MediaSegment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for value in &self.key_tags {
for value in &self.keys {
writeln!(f, "{}", value)?;
}
if let Some(value) = &self.map_tag {
@ -75,41 +76,38 @@ impl fmt::Display for MediaSegment {
}
impl MediaSegment {
/// Creates a [MediaSegmentBuilder].
/// Creates a [`MediaSegmentBuilder`].
pub fn builder() -> MediaSegmentBuilder { MediaSegmentBuilder::default() }
/// Returns the URI of the media segment.
/// Returns the `URI` of the media segment.
pub const fn uri(&self) -> &String { &self.uri }
/// Returns the `EXT-X-INF` tag associated with the media segment.
/// Returns the [`ExtInf`] tag associated with the media segment.
pub const fn inf_tag(&self) -> &ExtInf { &self.inf_tag }
/// Returns the `EXT-X-BYTERANGE` tag associated with the media segment.
/// Returns the [`ExtXByteRange`] tag associated with the media segment.
pub const fn byte_range_tag(&self) -> Option<ExtXByteRange> { self.byte_range_tag }
/// Returns the `EXT-X-DATERANGE` tag associated with the media segment.
pub fn date_range_tag(&self) -> Option<&ExtXDateRange> { self.date_range_tag.as_ref() }
/// Returns the [`ExtXDateRange`] tag associated with the media segment.
pub const fn date_range_tag(&self) -> &Option<ExtXDateRange> { &self.date_range_tag }
/// Returns the `EXT-X-DISCONTINUITY` tag associated with the media segment.
/// Returns the [`ExtXDiscontinuity`] tag associated with the media segment.
pub const fn discontinuity_tag(&self) -> Option<ExtXDiscontinuity> { self.discontinuity_tag }
/// Returns the `EXT-X-PROGRAM-DATE-TIME` tag associated with the media
/// Returns the [`ExtXProgramDateTime`] tag associated with the media
/// segment.
pub fn program_date_time_tag(&self) -> Option<&ExtXProgramDateTime> {
self.program_date_time_tag.as_ref()
pub const fn program_date_time_tag(&self) -> Option<ExtXProgramDateTime> {
self.program_date_time_tag
}
/// Returns the `EXT-X-MAP` tag associated with the media segment.
pub fn map_tag(&self) -> Option<&ExtXMap> { self.map_tag.as_ref() }
/// Returns the `EXT-X-KEY` tags associated with the media segment.
pub fn key_tags(&self) -> &[ExtXKey] { &self.key_tags }
/// Returns the [`ExtXMap`] tag associated with the media segment.
pub const fn map_tag(&self) -> &Option<ExtXMap> { &self.map_tag }
}
impl RequiredVersion for MediaSegment {
fn required_version(&self) -> ProtocolVersion {
iter::empty()
.chain(self.key_tags.iter().map(|t| t.required_version()))
.chain(self.keys.iter().map(|t| t.required_version()))
.chain(self.map_tag.iter().map(|t| t.required_version()))
.chain(self.byte_range_tag.iter().map(|t| t.required_version()))
.chain(self.date_range_tag.iter().map(|t| t.required_version()))
@ -121,6 +119,12 @@ impl RequiredVersion for MediaSegment {
)
.chain(iter::once(self.inf_tag.required_version()))
.max()
.unwrap_or(ProtocolVersion::V7)
.unwrap_or_else(ProtocolVersion::latest)
}
}
impl Encrypted for MediaSegment {
fn keys(&self) -> &Vec<ExtXKey> { &self.keys }
fn keys_mut(&mut self) -> &mut Vec<ExtXKey> { &mut self.keys }
}

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.1.1. EXTM3U]
/// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]**

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.1.2. EXT-X-VERSION]
/// The [`ExtXVersion`] tag indicates the compatibility version of the

View file

@ -3,9 +3,9 @@ use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, RequiredVersion, StreamInf};
use crate::types::{ProtocolVersion, StreamInf};
use crate::utils::{quote, tag, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]
/// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file,

View file

@ -4,9 +4,9 @@ use std::str::FromStr;
use derive_builder::Builder;
use crate::attribute::AttributePairs;
use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion, RequiredVersion};
use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion};
use crate::utils::{parse_yes_or_no, quote, tag, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.5.1. EXT-X-MEDIA]
/// The [`ExtXMedia`] tag is used to relate [`Media Playlist`]s,

View file

@ -4,9 +4,9 @@ use std::str::FromStr;
use derive_builder::Builder;
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::{quote, tag, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
/// The data of an [ExtXSessionData] tag.
#[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]

View file

@ -2,9 +2,9 @@ use std::fmt;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion, RequiredVersion};
use crate::types::{DecryptionKey, EncryptionMethod, ProtocolVersion};
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.3.4.5. EXT-X-SESSION-KEY]
/// The [`ExtXSessionKey`] tag allows encryption keys from [`Media Playlist`]s

View file

@ -3,11 +3,9 @@ use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::attribute::AttributePairs;
use crate::types::{
ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, RequiredVersion, StreamInf,
};
use crate::types::{ClosedCaptions, DecimalFloatingPoint, ProtocolVersion, StreamInf};
use crate::utils::{quote, tag, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
/// [4.3.4.2. EXT-X-STREAM-INF]
///

View file

@ -1,8 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::RequiredVersion;
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
///

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.3.4. EXT-X-ENDLIST]
/// The [`ExtXEndList`] tag indicates, that no more [`Media Segment`]s will be

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.3.6. EXT-X-I-FRAMES-ONLY]
/// The [`ExtXIFramesOnly`] tag indicates that each [`Media Segment`] in the

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.3.2. EXT-X-MEDIA-SEQUENCE]
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.3.5. EXT-X-PLAYLIST-TYPE]
///

View file

@ -2,9 +2,9 @@ use std::fmt;
use std::str::FromStr;
use std::time::Duration;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.3.1. EXT-X-TARGETDURATION]
/// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`]

View file

@ -2,9 +2,9 @@ use std::fmt;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::types::{ByteRange, ProtocolVersion, RequiredVersion};
use crate::types::{ByteRange, ProtocolVersion};
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.2.2. EXT-X-BYTERANGE]
///

View file

@ -6,9 +6,9 @@ use std::time::Duration;
use chrono::{DateTime, FixedOffset};
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::{quote, tag, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.3.2.7. EXT-X-DATERANGE]
///
@ -106,7 +106,8 @@ impl ExtXDateRange {
/// # use hls_m3u8::tags::ExtXDateRange;
/// use chrono::offset::TimeZone;
/// use chrono::{DateTime, FixedOffset};
/// use hls_m3u8::types::{ProtocolVersion, RequiredVersion};
/// use hls_m3u8::types::ProtocolVersion;
/// use hls_m3u8::RequiredVersion;
///
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
///

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.2.3. EXT-X-DISCONTINUITY]
/// The [`ExtXDiscontinuity`] tag indicates a discontinuity between the

View file

@ -2,9 +2,9 @@ use std::fmt;
use std::str::FromStr;
use std::time::Duration;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.4.2.1. EXTINF]
///

View file

@ -2,9 +2,10 @@ use std::fmt;
use std::str::FromStr;
use crate::attribute::AttributePairs;
use crate::types::{ByteRange, ProtocolVersion, RequiredVersion};
use crate::tags::ExtXKey;
use crate::types::{ByteRange, ProtocolVersion};
use crate::utils::{quote, tag, unquote};
use crate::Error;
use crate::{Encrypted, Error, RequiredVersion};
/// # [4.4.2.5. EXT-X-MAP]
/// The [`ExtXMap`] tag specifies how to obtain the Media Initialization
@ -22,6 +23,7 @@ use crate::Error;
pub struct ExtXMap {
uri: String,
range: Option<ByteRange>,
keys: Vec<ExtXKey>,
}
impl ExtXMap {
@ -29,17 +31,19 @@ impl ExtXMap {
/// Makes a new [`ExtXMap`] tag.
pub fn new<T: ToString>(uri: T) -> Self {
ExtXMap {
Self {
uri: uri.to_string(),
range: None,
keys: vec![],
}
}
/// Makes a new [`ExtXMap`] tag with the given range.
pub fn with_range<T: ToString>(uri: T, range: ByteRange) -> Self {
ExtXMap {
Self {
uri: uri.to_string(),
range: Some(range),
keys: vec![],
}
}
@ -51,6 +55,13 @@ impl ExtXMap {
pub const fn range(&self) -> Option<ByteRange> { self.range }
}
impl Encrypted for ExtXMap {
fn keys(&self) -> &Vec<ExtXKey> { &self.keys }
fn keys_mut(&mut self) -> &mut Vec<ExtXKey> { &mut self.keys }
}
/// This tag requires [`ProtocolVersion::V6`].
impl RequiredVersion for ExtXMap {
fn required_version(&self) -> ProtocolVersion { ProtocolVersion::V6 }
}
@ -59,9 +70,11 @@ impl fmt::Display for ExtXMap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "URI={}", quote(&self.uri))?;
if let Some(value) = &self.range {
write!(f, ",BYTERANGE={}", quote(value))?;
}
Ok(())
}
}
@ -90,7 +103,11 @@ impl FromStr for ExtXMap {
}
let uri = uri.ok_or_else(|| Error::missing_value("EXT-X-URI"))?;
Ok(ExtXMap { uri, range })
Ok(Self {
uri,
range,
keys: vec![],
})
}
}

View file

@ -4,9 +4,9 @@ use std::str::FromStr;
use chrono::{DateTime, FixedOffset};
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// # [4.3.2.6. EXT-X-PROGRAM-DATE-TIME]
/// The [`ExtXProgramDateTime`] tag associates the first sample of a

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
use crate::{Error, RequiredVersion};
/// [4.3.5.1. EXT-X-INDEPENDENT-SEGMENTS]
///

View file

@ -2,9 +2,9 @@ use std::fmt;
use std::str::FromStr;
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, RequiredVersion, SignedDecimalFloatingPoint};
use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint};
use crate::utils::{parse_yes_or_no, tag};
use crate::Error;
use crate::{Error, RequiredVersion};
/// [4.3.5.2. EXT-X-START]
///

124
src/traits.rs Normal file
View file

@ -0,0 +1,124 @@
use crate::tags::ExtXKey;
use crate::types::{EncryptionMethod, ProtocolVersion};
/// A trait, that is implemented on all tags, that could be encrypted.
///
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod;
/// use hls_m3u8::Encrypted;
///
/// struct ExampleTag {
/// keys: Vec<ExtXKey>,
/// }
///
/// // Implementing the trait is very simple:
/// // Simply expose the internal buffer, that contains all the keys.
/// impl Encrypted for ExampleTag {
/// fn keys(&self) -> &Vec<ExtXKey> { &self.keys }
///
/// fn keys_mut(&mut self) -> &mut Vec<ExtXKey> { &mut self.keys }
/// }
///
/// let mut example_tag = ExampleTag { keys: vec![] };
///
/// // adding new keys:
/// example_tag.set_keys(vec![ExtXKey::empty()]);
/// example_tag.push_key(ExtXKey::new(
/// EncryptionMethod::Aes128,
/// "http://www.example.com/data.bin",
/// ));
///
/// // getting the keys:
/// assert_eq!(
/// example_tag.keys(),
/// &vec![
/// ExtXKey::empty(),
/// ExtXKey::new(EncryptionMethod::Aes128, "http://www.example.com/data.bin",)
/// ]
/// );
///
/// assert_eq!(
/// example_tag.keys_mut(),
/// &mut vec![
/// ExtXKey::empty(),
/// ExtXKey::new(EncryptionMethod::Aes128, "http://www.example.com/data.bin",)
/// ]
/// );
///
/// assert!(example_tag.is_encrypted());
/// assert!(!example_tag.is_not_encrypted());
/// ```
pub trait Encrypted {
/// Returns a shared reference to all keys, that can be used to decrypt this
/// tag.
fn keys(&self) -> &Vec<ExtXKey>;
/// Returns an exclusive reference to all keys, that can be used to decrypt
/// this tag.
fn keys_mut(&mut self) -> &mut Vec<ExtXKey>;
/// Sets all keys, that can be used to decrypt this tag.
fn set_keys(&mut self, value: Vec<ExtXKey>) -> &mut Self {
let keys = self.keys_mut();
*keys = value;
self
}
/// Add a single key to the list of keys, that can be used to decrypt this
/// tag.
fn push_key(&mut self, value: ExtXKey) -> &mut Self {
self.keys_mut().push(value);
self
}
/// Returns `true`, if the tag is encrypted.
///
/// # Note
/// This will return `true`, if any of the keys satisfies
/// ```text
/// key.method() != EncryptionMethod::None
/// ```
fn is_encrypted(&self) -> bool {
if self.keys().is_empty() {
return false;
}
self.keys()
.iter()
.any(|k| k.method() != EncryptionMethod::None)
}
/// Returns `false`, if the tag is not encrypted.
///
/// # Note
/// This is the inverse of [`is_encrypted`].
///
/// [`is_encrypted`]: #method.is_encrypted
fn is_not_encrypted(&self) -> bool { !self.is_encrypted() }
}
/// # Example
/// Implementing it:
/// ```
/// # use hls_m3u8::RequiredVersion;
/// use hls_m3u8::types::ProtocolVersion;
///
/// struct ExampleTag(u64);
///
/// impl RequiredVersion for ExampleTag {
/// fn required_version(&self) -> ProtocolVersion {
/// if self.0 == 5 {
/// ProtocolVersion::V4
/// } else {
/// ProtocolVersion::V1
/// }
/// }
/// }
/// assert_eq!(ExampleTag(5).required_version(), ProtocolVersion::V4);
/// assert_eq!(ExampleTag(2).required_version(), ProtocolVersion::V1);
/// ```
pub trait RequiredVersion {
/// Returns the protocol compatibility version that this tag requires.
fn required_version(&self) -> ProtocolVersion;
}

View file

@ -6,10 +6,9 @@ use derive_builder::Builder;
use crate::attribute::AttributePairs;
use crate::types::{
EncryptionMethod, InitializationVector, KeyFormat, KeyFormatVersions, ProtocolVersion,
RequiredVersion,
};
use crate::utils::{quote, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
#[builder(setter(into), build_fn(validate = "Self::validate"))]

View file

@ -1,9 +1,9 @@
use std::fmt;
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::{quote, tag, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
/// [`KeyFormat`] specifies, how the key is represented in the

View file

@ -2,9 +2,9 @@ use std::fmt;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::types::{ProtocolVersion, RequiredVersion};
use crate::types::ProtocolVersion;
use crate::utils::{quote, unquote};
use crate::Error;
use crate::{Error, RequiredVersion};
/// A list of [usize], that can be used to indicate which version(s)
/// this instance complies with, if more than one version of a particular

View file

@ -3,30 +3,6 @@ use std::str::FromStr;
use crate::Error;
/// # Example
/// Implementing it:
/// ```
/// # use hls_m3u8::types::{ProtocolVersion, RequiredVersion};
/// #
/// struct NewTag(u64);
///
/// impl RequiredVersion for NewTag {
/// fn required_version(&self) -> ProtocolVersion {
/// if self.0 == 5 {
/// ProtocolVersion::V4
/// } else {
/// ProtocolVersion::V1
/// }
/// }
/// }
/// assert_eq!(NewTag(5).required_version(), ProtocolVersion::V4);
/// assert_eq!(NewTag(2).required_version(), ProtocolVersion::V1);
/// ```
pub trait RequiredVersion {
/// Returns the protocol compatibility version that this tag requires.
fn required_version(&self) -> ProtocolVersion;
}
/// # [7. Protocol Version Compatibility]
/// The [`ProtocolVersion`] specifies, which m3u8 revision is required, to parse
/// a certain tag correctly.