1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-25 11:48:10 +00:00

more tests #25 + better docs #31

This commit is contained in:
Luro02 2019-10-03 16:23:27 +02:00
parent 6b717f97c2
commit 93283f61f1
33 changed files with 666 additions and 266 deletions

View file

@ -90,11 +90,11 @@ fn split(value: &str, terminator: char) -> Vec<String> {
temp_string.push(c);
}
k if (k == terminator) => {
if !inside_quotes {
if inside_quotes {
temp_string.push(c);
} else {
result.push(temp_string);
temp_string = String::new();
} else {
temp_string.push(c);
}
}
_ => {

View file

@ -5,7 +5,7 @@ use failure::{Backtrace, Context, Fail};
/// This crate specific `Result` type.
pub type Result<T> = std::result::Result<T, Error>;
/// The ErrorKind.
/// The [`ErrorKind`].
#[derive(Debug, Fail, Clone, PartialEq, Eq)]
pub enum ErrorKind {
#[fail(display = "ChronoParseError: {}", _0)]
@ -72,6 +72,10 @@ pub enum ErrorKind {
/// An attribute is missing.
MissingAttribute(String),
#[fail(display = "Unexpected Attribute: {:?}", _0)]
/// An unexpected value.
UnexpectedAttribute(String),
/// Hints that destructuring should not be exhaustive.
///
/// This enum may grow additional variants, so this makes sure clients
@ -121,6 +125,10 @@ impl Error {
Self::from(ErrorKind::MissingValue(value.to_string()))
}
pub(crate) fn unexpected_attribute<T: ToString>(value: T) -> Self {
Self::from(ErrorKind::UnexpectedAttribute(value.to_string()))
}
pub(crate) fn invalid_input() -> Self {
Self::from(ErrorKind::InvalidInput)
}

View file

@ -154,53 +154,53 @@ impl fmt::Display for Tag {
impl FromStr for Tag {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with(tags::ExtM3u::PREFIX) {
s.parse().map(Tag::ExtM3u)
} else if s.starts_with(tags::ExtXVersion::PREFIX) {
s.parse().map(Tag::ExtXVersion)
} else if s.starts_with(tags::ExtInf::PREFIX) {
s.parse().map(Tag::ExtInf)
} else if s.starts_with(tags::ExtXByteRange::PREFIX) {
s.parse().map(Tag::ExtXByteRange)
} else if s.starts_with(tags::ExtXDiscontinuity::PREFIX) {
s.parse().map(Tag::ExtXDiscontinuity)
} else if s.starts_with(tags::ExtXKey::PREFIX) {
s.parse().map(Tag::ExtXKey)
} else if s.starts_with(tags::ExtXMap::PREFIX) {
s.parse().map(Tag::ExtXMap)
} else if s.starts_with(tags::ExtXProgramDateTime::PREFIX) {
s.parse().map(Tag::ExtXProgramDateTime)
} else if s.starts_with(tags::ExtXTargetDuration::PREFIX) {
s.parse().map(Tag::ExtXTargetDuration)
} else if s.starts_with(tags::ExtXDateRange::PREFIX) {
s.parse().map(Tag::ExtXDateRange)
} else if s.starts_with(tags::ExtXMediaSequence::PREFIX) {
s.parse().map(Tag::ExtXMediaSequence)
} else if s.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) {
s.parse().map(Tag::ExtXDiscontinuitySequence)
} else if s.starts_with(tags::ExtXEndList::PREFIX) {
s.parse().map(Tag::ExtXEndList)
} else if s.starts_with(tags::ExtXPlaylistType::PREFIX) {
s.parse().map(Tag::ExtXPlaylistType)
} else if s.starts_with(tags::ExtXIFramesOnly::PREFIX) {
s.parse().map(Tag::ExtXIFramesOnly)
} else if s.starts_with(tags::ExtXMedia::PREFIX) {
s.parse().map(Tag::ExtXMedia)
} else if s.starts_with(tags::ExtXStreamInf::PREFIX) {
s.parse().map(Tag::ExtXStreamInf)
} else if s.starts_with(tags::ExtXIFrameStreamInf::PREFIX) {
s.parse().map(Tag::ExtXIFrameStreamInf)
} else if s.starts_with(tags::ExtXSessionData::PREFIX) {
s.parse().map(Tag::ExtXSessionData)
} else if s.starts_with(tags::ExtXSessionKey::PREFIX) {
s.parse().map(Tag::ExtXSessionKey)
} else if s.starts_with(tags::ExtXIndependentSegments::PREFIX) {
s.parse().map(Tag::ExtXIndependentSegments)
} else if s.starts_with(tags::ExtXStart::PREFIX) {
s.parse().map(Tag::ExtXStart)
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.starts_with(tags::ExtM3u::PREFIX) {
input.parse().map(Tag::ExtM3u)
} else if input.starts_with(tags::ExtXVersion::PREFIX) {
input.parse().map(Tag::ExtXVersion)
} else if input.starts_with(tags::ExtInf::PREFIX) {
input.parse().map(Tag::ExtInf)
} else if input.starts_with(tags::ExtXByteRange::PREFIX) {
input.parse().map(Tag::ExtXByteRange)
} else if input.starts_with(tags::ExtXDiscontinuity::PREFIX) {
input.parse().map(Tag::ExtXDiscontinuity)
} else if input.starts_with(tags::ExtXKey::PREFIX) {
input.parse().map(Tag::ExtXKey)
} else if input.starts_with(tags::ExtXMap::PREFIX) {
input.parse().map(Tag::ExtXMap)
} else if input.starts_with(tags::ExtXProgramDateTime::PREFIX) {
input.parse().map(Tag::ExtXProgramDateTime)
} else if input.starts_with(tags::ExtXTargetDuration::PREFIX) {
input.parse().map(Tag::ExtXTargetDuration)
} else if input.starts_with(tags::ExtXDateRange::PREFIX) {
input.parse().map(Tag::ExtXDateRange)
} else if input.starts_with(tags::ExtXMediaSequence::PREFIX) {
input.parse().map(Tag::ExtXMediaSequence)
} else if input.starts_with(tags::ExtXDiscontinuitySequence::PREFIX) {
input.parse().map(Tag::ExtXDiscontinuitySequence)
} else if input.starts_with(tags::ExtXEndList::PREFIX) {
input.parse().map(Tag::ExtXEndList)
} else if input.starts_with(tags::ExtXPlaylistType::PREFIX) {
input.parse().map(Tag::ExtXPlaylistType)
} else if input.starts_with(tags::ExtXIFramesOnly::PREFIX) {
input.parse().map(Tag::ExtXIFramesOnly)
} else if input.starts_with(tags::ExtXMedia::PREFIX) {
input.parse().map(Tag::ExtXMedia).map_err(Error::custom)
} else if input.starts_with(tags::ExtXStreamInf::PREFIX) {
input.parse().map(Tag::ExtXStreamInf)
} else if input.starts_with(tags::ExtXIFrameStreamInf::PREFIX) {
input.parse().map(Tag::ExtXIFrameStreamInf)
} else if input.starts_with(tags::ExtXSessionData::PREFIX) {
input.parse().map(Tag::ExtXSessionData)
} else if input.starts_with(tags::ExtXSessionKey::PREFIX) {
input.parse().map(Tag::ExtXSessionKey)
} else if input.starts_with(tags::ExtXIndependentSegments::PREFIX) {
input.parse().map(Tag::ExtXIndependentSegments)
} else if input.starts_with(tags::ExtXStart::PREFIX) {
input.parse().map(Tag::ExtXStart)
} else {
Ok(Tag::Unknown(s.to_string()))
Ok(Tag::Unknown(input.to_string()))
}
}
}

View file

@ -5,17 +5,35 @@ use crate::types::{ProtocolVersion, RequiredVersion};
use crate::utils::tag;
use crate::Error;
/// # [4.3.1.1. EXTM3U]
/// The [ExtM3u] tag indicates that the file is an Extended [M3U]
/// # [4.4.1.1. EXTM3U]
/// The [`ExtM3u`] tag indicates that the file is an **Ext**ended **[`M3U`]**
/// Playlist file.
/// It is the at the start of every [`Media Playlist`] and [`Master Playlist`].
///
/// Its format is:
/// ```text
/// #EXTM3U
/// # Examples
/// Parsing from a [`str`]:
/// ```
/// # use failure::Error;
/// # use hls_m3u8::tags::ExtM3u;
/// #
/// # fn main() -> Result<(), Error> {
/// assert_eq!("#EXTM3U".parse::<ExtM3u>()?, ExtM3u);
/// #
/// # Ok(())
/// # }
/// ```
/// Converting to a [`str`]:
/// ```
/// # use hls_m3u8::tags::ExtM3u;
/// #
/// assert_eq!("#EXTM3U".to_string(), ExtM3u.to_string());
/// ```
///
/// [M3U]: https://en.wikipedia.org/wiki/M3U
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`Master Playlist`]: crate::MasterPlaylist
/// [`M3U`]: https://en.wikipedia.org/wiki/M3U
/// [4.4.1.1. EXTM3U]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.1.1
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ExtM3u;
@ -23,6 +41,7 @@ impl ExtM3u {
pub(crate) const PREFIX: &'static str = "#EXTM3U";
}
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtM3u {
fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
@ -31,7 +50,7 @@ impl RequiredVersion for ExtM3u {
impl fmt::Display for ExtM3u {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Self::PREFIX.fmt(f)
write!(f, "{}", Self::PREFIX)
}
}
@ -40,7 +59,7 @@ impl FromStr for ExtM3u {
fn from_str(input: &str) -> Result<Self, Self::Err> {
tag(input, Self::PREFIX)?;
Ok(ExtM3u)
Ok(Self)
}
}

View file

@ -5,40 +5,64 @@ use crate::types::{ProtocolVersion, RequiredVersion};
use crate::utils::tag;
use crate::Error;
/// # [4.3.1.2. EXT-X-VERSION]
/// The [ExtXVersion] tag indicates the compatibility version of the
/// Playlist file, its associated media, and its server.
/// # [4.4.1.2. EXT-X-VERSION]
/// The [`ExtXVersion`] tag indicates the compatibility version of the
/// [`Master Playlist`] or [`Media Playlist`] file.
/// It applies to the entire Playlist.
///
/// The [ExtXVersion] tag applies to the entire Playlist file. Its
/// format is:
///
/// ```text
/// #EXT-X-VERSION:<n>
/// # Examples
/// Parsing from a [`str`]:
/// ```
/// where `n` is an integer indicating the protocol compatibility version
/// number.
/// # use failure::Error;
/// # use hls_m3u8::tags::ExtXVersion;
/// #
/// # fn main() -> Result<(), Error> {
/// use hls_m3u8::types::ProtocolVersion;
///
/// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
/// assert_eq!(
/// "#EXT-X-VERSION:5".parse::<ExtXVersion>()?,
/// ExtXVersion::new(ProtocolVersion::V5)
/// );
/// #
/// # Ok(())
/// # }
/// ```
/// Converting to a [`str`]:
/// ```
/// # use hls_m3u8::tags::ExtXVersion;
/// #
/// use hls_m3u8::types::ProtocolVersion;
///
/// assert_eq!(
/// "#EXT-X-VERSION:5".to_string(),
/// ExtXVersion::new(ProtocolVersion::V5).to_string()
/// );
/// ```
///
/// [`Media Playlist`]: crate::MediaPlaylist
/// [`Master Playlist`]: crate::MasterPlaylist
/// [4.4.1.2. EXT-X-VERSION]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.1.2
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct ExtXVersion(ProtocolVersion);
impl ExtXVersion {
pub(crate) const PREFIX: &'static str = "#EXT-X-VERSION:";
/// Makes a new [ExtXVersion] tag.
/// Makes a new [`ExtXVersion`] tag.
///
/// # Example
/// ```
/// # use hls_m3u8::tags::ExtXVersion;
/// use hls_m3u8::types::ProtocolVersion;
///
/// let version_tag = ExtXVersion::new(ProtocolVersion::V2);
/// let version = ExtXVersion::new(ProtocolVersion::V2);
/// ```
pub const fn new(version: ProtocolVersion) -> Self {
Self(version)
}
/// Returns the protocol compatibility version of the playlist, containing this tag.
/// Returns the [`ProtocolVersion`] of the playlist, containing this tag.
///
/// # Example
/// ```
@ -55,6 +79,7 @@ impl ExtXVersion {
}
}
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXVersion {
fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
@ -84,7 +109,7 @@ impl FromStr for ExtXVersion {
fn from_str(input: &str) -> Result<Self, Self::Err> {
let version = tag(input, Self::PREFIX)?.parse()?;
Ok(ExtXVersion::new(version))
Ok(Self::new(version))
}
}
@ -115,4 +140,12 @@ mod test {
ProtocolVersion::V1
);
}
#[test]
fn test_default_and_from() {
assert_eq!(
ExtXVersion::default(),
ExtXVersion::from(ProtocolVersion::V1)
);
}
}

View file

@ -7,20 +7,17 @@ use crate::types::{ProtocolVersion, RequiredVersion, StreamInf};
use crate::utils::{quote, tag, unquote};
use crate::Error;
/// # [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
/// The [ExtXIFrameStreamInf] tag identifies a [Media Playlist] file
/// containing the I-frames of a multimedia presentation. It stands
/// alone, in that it does not apply to a particular `URI` in the [Master Playlist].
/// # [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]
/// The [`ExtXIFrameStreamInf`] tag identifies a [`Media Playlist`] file,
/// containing the I-frames of a multimedia presentation.
///
/// Its format is:
/// I-frames are encoded video frames, whose decoding
/// does not depend on any other frame.
///
/// ```text
/// #EXT-X-I-FRAME-STREAM-INF:<attribute-list>
/// ```
///
/// [Master Playlist]: crate::MasterPlaylist
/// [Media Playlist]: crate::MediaPlaylist
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
/// [`Master Playlist`]: crate::MasterPlaylist
/// [`Media Playlist`]: crate::MediaPlaylist
/// [4.4.5.3. EXT-X-I-FRAME-STREAM-INF]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.5.3
#[derive(PartialOrd, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXIFrameStreamInf {
uri: String,
@ -30,7 +27,7 @@ pub struct ExtXIFrameStreamInf {
impl ExtXIFrameStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-I-FRAME-STREAM-INF:";
/// Makes a new [ExtXIFrameStreamInf] tag.
/// Makes a new [`ExtXIFrameStreamInf`] tag.
///
/// # Example
/// ```
@ -44,7 +41,7 @@ impl ExtXIFrameStreamInf {
}
}
/// Returns the `URI`, that identifies the associated media playlist.
/// Returns the `URI`, that identifies the associated [`media playlist`].
///
/// # Example
/// ```
@ -52,11 +49,13 @@ impl ExtXIFrameStreamInf {
/// let stream = ExtXIFrameStreamInf::new("https://www.example.com", 20);
/// assert_eq!(stream.uri(), &"https://www.example.com".to_string());
/// ```
///
/// [`media playlist`]: crate::MediaPlaylist
pub const fn uri(&self) -> &String {
&self.uri
}
/// Sets the `URI`, that identifies the associated media playlist.
/// Sets the `URI`, that identifies the associated [`media playlist`].
///
/// # Example
/// ```
@ -67,12 +66,15 @@ impl ExtXIFrameStreamInf {
/// stream.set_uri("../new/uri");
/// assert_eq!(stream.uri(), &"../new/uri".to_string());
/// ```
///
/// [`media playlist`]: crate::MediaPlaylist
pub fn set_uri<T: ToString>(&mut self, value: T) -> &mut Self {
self.uri = value.to_string();
self
}
}
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXIFrameStreamInf {
fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1

View file

@ -4,83 +4,120 @@ use std::str::FromStr;
use derive_builder::Builder;
use crate::attribute::AttributePairs;
use crate::types::{InStreamId, MediaType, ProtocolVersion, RequiredVersion};
use crate::types::{Channels, InStreamId, MediaType, ProtocolVersion, RequiredVersion};
use crate::utils::{parse_yes_or_no, quote, tag, unquote};
use crate::Error;
/// # [4.4.4.1. EXT-X-MEDIA]
/// The [ExtXMedia] tag is used to relate [Media Playlist]s that contain
/// alternative Renditions of the same content. For
/// example, three [ExtXMedia] tags can be used to identify audio-only
/// [Media Playlist]s, that contain English, French, and Spanish Renditions
/// of the same presentation. Or, two [ExtXMedia] tags can be used to
/// identify video-only [Media Playlist]s that show two different camera
/// # [4.4.5.1. EXT-X-MEDIA]
/// The [`ExtXMedia`] tag is used to relate [`Media Playlist`]s,
/// that contain alternative Renditions of the same content.
///
/// For
/// example, three [`ExtXMedia`] tags can be used to identify audio-only
/// [`Media Playlist`]s, that contain English, French, and Spanish Renditions
/// of the same presentation. Or, two [`ExtXMedia`] tags can be used to
/// identify video-only [`Media Playlist`]s that show two different camera
/// angles.
///
/// Its format is:
/// ```text
/// #EXT-X-MEDIA:<attribute-list>
/// ```
///
/// [Media Playlist]: crate::MediaPlaylist
/// [4.4.4.1. EXT-X-MEDIA]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.4.1
/// [`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)]
#[builder(setter(into))]
#[builder(build_fn(validate = "Self::validate"))]
pub struct ExtXMedia {
/// Sets the media type of the rendition.
/// Sets the [`MediaType`] of the rendition.
///
/// # Note
/// This attribute is **required**.
media_type: MediaType,
#[builder(setter(strip_option, into), default)]
/// Sets the URI that identifies the media playlist.
/// 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`].
///
/// [`Media Playlist`]: crate::MediaPlaylist
uri: Option<String>,
/// Sets the identifier that specifies the group to which the rendition belongs.
/// Sets the identifier, that specifies the group to which the rendition belongs.
///
/// # Note
/// This attribute is **required**.
group_id: String,
#[builder(setter(strip_option, into), default)]
/// Sets the name of the primary language used in the rendition.
#[builder(setter(strip_option, into), default)]
/// The value has to conform to [`RFC5646`].
///
/// # Note
/// This attribute is **optional**.
///
/// [`RFC5646`]: https://tools.ietf.org/html/rfc5646
language: Option<String>,
/// Sets the name of a language associated with the rendition.
#[builder(setter(strip_option, into), default)]
/// Sets the name of a language associated with the rendition.
///
/// # Note
/// This attribute is **optional**.
///
/// [`language`]: #method.language
assoc_language: Option<String>,
/// Sets a human-readable description of the rendition.
///
/// # Note
/// This attribute is **required**.
///
/// If the [`language`] attribute is present, this attribute should be in that language.
///
/// [`language`]: #method.language
name: String,
#[builder(default)]
/// Sets the value of the `default` flag.
#[builder(default)]
///
/// # Note
/// This attribute is **optional**, its absence indicates an implicit value of `false`.
is_default: bool,
#[builder(default)]
/// Sets the value of the `autoselect` flag.
#[builder(default)]
///
/// # Note
/// This attribute is **optional**, its absence indicates an implicit value of `false`.
is_autoselect: bool,
/// Sets the value of the `forced` flag.
#[builder(default)]
/// Sets the value of the `forced` flag.
is_forced: bool,
#[builder(setter(strip_option, into), default)]
/// Sets the identifier that specifies a rendition within the segments in the media playlist.
#[builder(setter(strip_option, into), default)]
instream_id: Option<InStreamId>,
#[builder(setter(strip_option, into), default)]
/// Sets the string that represents uniform type identifiers (UTI).
#[builder(setter(strip_option, into), default)]
characteristics: Option<String>,
/// Sets the string that represents the parameters of the rendition.
#[builder(setter(strip_option, into), default)]
channels: Option<String>,
/// Sets the parameters of the rendition.
channels: Option<Channels>,
}
impl ExtXMediaBuilder {
fn validate(&self) -> Result<(), String> {
// A MediaType is always required!
let media_type = self
.media_type
.ok_or_else(|| Error::missing_attribute("MEDIA-TYPE").to_string())?;
if MediaType::ClosedCaptions == media_type {
if media_type == MediaType::Subtitles && self.uri.is_none() {
return Err(Error::missing_attribute("URI").to_string());
}
if media_type == MediaType::ClosedCaptions {
if self.uri.is_some() {
return Err(Error::custom(
"Unexpected attribute: \"URL\" for MediaType::ClosedCaptions!",
)
.to_string());
return Err(Error::unexpected_attribute("URI").to_string());
}
if self.instream_id.is_none() {
return Err(Error::missing_attribute("INSTREAM-ID").to_string());
}
self.instream_id
.ok_or_else(|| Error::missing_attribute("INSTREAM-ID").to_string())?;
} else if self.instream_id.is_some() {
return Err(Error::custom("Unexpected attribute: \"INSTREAM-ID\"!").to_string());
return Err(Error::unexpected_attribute("INSTREAM-ID").to_string());
}
if self.is_default.unwrap_or(false) && !self.is_autoselect.unwrap_or(false) {
@ -89,7 +126,7 @@ impl ExtXMediaBuilder {
);
}
if MediaType::Subtitles != media_type && self.is_forced.is_some() {
if media_type != MediaType::Subtitles && self.is_forced.is_some() {
return Err(Error::invalid_input().to_string());
}
@ -100,7 +137,7 @@ impl ExtXMediaBuilder {
impl ExtXMedia {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
/// Makes a new [ExtXMedia] tag.
/// Makes a new [`ExtXMedia`] tag.
pub fn new<T: ToString>(media_type: MediaType, group_id: T, name: T) -> Self {
Self {
media_type,
@ -118,7 +155,7 @@ impl ExtXMedia {
}
}
/// Returns a builder for [ExtXMedia].
/// Returns a builder for [`ExtXMedia`].
pub fn builder() -> ExtXMediaBuilder {
ExtXMediaBuilder::default()
}
@ -215,6 +252,9 @@ impl ExtXMedia {
/// 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;
@ -229,12 +269,14 @@ impl ExtXMedia {
/// &"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 [MediaPlaylist].
/// Returns the `URI`, that identifies the [`Media Playlist`].
///
/// # Example
/// ```
@ -251,11 +293,18 @@ impl ExtXMedia {
/// &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 [MediaPlaylist].
/// 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
/// ```
@ -272,6 +321,8 @@ impl ExtXMedia {
/// &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(|v| v.into());
self
@ -299,6 +350,7 @@ impl ExtXMedia {
}
/// Sets the name of the primary language used in the rendition.
/// The value has to conform to [`RFC5646`].
///
/// # Example
/// ```
@ -315,6 +367,8 @@ impl ExtXMedia {
/// &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(|v| v.into());
self
@ -342,6 +396,9 @@ impl ExtXMedia {
}
/// 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
/// ```
@ -358,12 +415,14 @@ impl ExtXMedia {
/// &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(|v| v.into());
self
}
/// Returns whether this is the default rendition.
/// Returns whether this is the `default` rendition.
///
/// # Example
/// ```
@ -385,6 +444,9 @@ impl ExtXMedia {
}
/// 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
/// ```
@ -479,7 +541,7 @@ impl ExtXMedia {
}
/// Returns the identifier that specifies a rendition within the segments in the
/// [MediaPlaylist].
/// [`Media Playlist`].
///
/// # Example
/// ```
@ -493,11 +555,14 @@ impl ExtXMedia {
///
/// 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].
/// Sets the [`InStreamId`], that specifies a rendition within the
/// segments in the [`Media Playlist`].
///
/// # Example
/// ```
@ -536,7 +601,20 @@ impl ExtXMedia {
&self.characteristics
}
/// Sets the 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
/// ```
@ -550,26 +628,29 @@ impl ExtXMedia {
///
/// 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(|v| v.into());
self
}
/// Returns a [String] that represents the parameters of the rendition.
/// Returns the channels.
///
/// # Example
/// ```
/// # use hls_m3u8::tags::ExtXMedia;
/// use hls_m3u8::types::MediaType;
/// use hls_m3u8::types::{Channels, MediaType};
///
/// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name");
/// # assert_eq!(media.channels(), &None);
///
/// media.set_channels(Some("channel"));
/// media.set_channels(Some(Channels::new(6)));
///
/// assert_eq!(media.channels(), &Some("channel".into()));
/// assert_eq!(media.channels(), &Some(Channels::new(6)));
/// ```
pub const fn channels(&self) -> &Option<String> {
pub const fn channels(&self) -> &Option<Channels> {
&self.channels
}
@ -578,16 +659,16 @@ impl ExtXMedia {
/// # Example
/// ```
/// # use hls_m3u8::tags::ExtXMedia;
/// use hls_m3u8::types::MediaType;
/// use hls_m3u8::types::{Channels, MediaType};
///
/// let mut media = ExtXMedia::new(MediaType::Audio, "audio", "name");
/// # assert_eq!(media.characteristics(), &None);
/// # assert_eq!(media.channels(), &None);
///
/// media.set_characteristics(Some("characteristic"));
/// media.set_channels(Some(Channels::new(6)));
///
/// assert_eq!(media.characteristics(), &Some("characteristic".into()));
/// assert_eq!(media.channels(), &Some(Channels::new(6)));
/// ```
pub fn set_channels<T: Into<String>>(&mut self, value: Option<T>) -> &mut Self {
pub fn set_channels<T: Into<Channels>>(&mut self, value: Option<T>) -> &mut Self {
self.channels = value.map(|v| v.into());
self
}
@ -687,7 +768,7 @@ impl FromStr for ExtXMedia {
builder.characteristics(unquote(value));
}
"CHANNELS" => {
builder.channels(unquote(value));
builder.channels(unquote(value).parse::<Channels>()?);
}
_ => {
// [6.3.1. General Client Responsibilities]
@ -695,6 +776,7 @@ impl FromStr for ExtXMedia {
}
}
}
builder.build().map_err(Error::builder_error)
}
}
@ -915,7 +997,7 @@ mod test {
.name("English")
.is_autoselect(true)
.is_default(true)
.channels("2")
.channels(Channels::new(2))
.build()
.unwrap()
.to_string(),
@ -1196,7 +1278,7 @@ mod test {
.name("English")
.is_autoselect(true)
.is_default(true)
.channels("2")
.channels(Channels::new(2))
.build()
.unwrap(),
"#EXT-X-MEDIA:\
@ -1262,7 +1344,30 @@ mod test {
"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"foo\",NAME=\"bar\""
.parse()
.unwrap()
)
);
}
#[test]
fn test_parser_error() {
assert!("".parse::<ExtXMedia>().is_err());
assert!("garbage".parse::<ExtXMedia>().is_err());
assert!(
"#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,URI=\"http://www.example.com\""
.parse::<ExtXMedia>()
.is_err()
);
assert!("#EXT-X-MEDIA:TYPE=AUDIO,INSTREAM-ID=CC1"
.parse::<ExtXMedia>()
.is_err());
assert!("#EXT-X-MEDIA:TYPE=AUDIO,DEFAULT=YES,AUTOSELECT=NO"
.parse::<ExtXMedia>()
.is_err());
assert!("#EXT-X-MEDIA:TYPE=AUDIO,FORCED=YES"
.parse::<ExtXMedia>()
.is_err());
}
#[test]

View file

@ -11,37 +11,41 @@ use crate::Error;
/// The data of an [ExtXSessionData] tag.
#[derive(Hash, Eq, Ord, Debug, PartialEq, Clone, PartialOrd)]
pub enum SessionData {
/// A String, that contains the data identified by [data_id](ExtXSessionData::data_id).
/// If a [language](ExtXSessionData::language) is specified, the value should
/// A String, that contains the data identified by [`data_id`](ExtXSessionData::data_id).
/// If a [`language`](ExtXSessionData::language) is specified, the value should
/// contain a human-readable string written in the specified language.
Value(String),
/// An [uri], which points to a [json].
/// An [`uri`], which points to a [`json`].
///
/// [json]: https://tools.ietf.org/html/rfc8259
/// [uri]: https://tools.ietf.org/html/rfc3986
/// [`json`]: https://tools.ietf.org/html/rfc8259
/// [`uri`]: https://tools.ietf.org/html/rfc3986
Uri(String),
}
/// # [4.3.4.4. EXT-X-SESSION-DATA]
///
/// The [ExtXSessionData] tag allows arbitrary session data to be
/// carried in a [Master Playlist].
/// The [`ExtXSessionData`] tag allows arbitrary session data to be
/// carried in a [`Master Playlist`].
///
/// [Master Playlist]: crate::MasterPlaylist
/// [`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)]
#[builder(setter(into))]
pub struct ExtXSessionData {
/// The identifier of the data. For more information look [here](ExtXSessionData::set_data_id).
/// The identifier of the data.
/// For more information look [`here`](ExtXSessionData::set_data_id).
///
/// # Note
/// This field is required.
data_id: String,
/// The data associated with the [data_id](ExtXSessionDataBuilder::data_id).
/// For more information look [here](SessionData).
/// The data associated with the
/// [`data_id`](ExtXSessionDataBuilder::data_id).
/// For more information look [`here`](SessionData).
///
/// # Note
/// This field is required.
data: SessionData,
/// The language of the [data](ExtXSessionDataBuilder::data).
/// The language of the [`data`](ExtXSessionDataBuilder::data).
#[builder(setter(into, strip_option), default)]
language: Option<String>,
}
@ -49,7 +53,7 @@ pub struct ExtXSessionData {
impl ExtXSessionData {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-DATA:";
/// Makes a new [ExtXSessionData] tag.
/// Makes a new [`ExtXSessionData`] tag.
///
/// # Example
/// ```
@ -68,7 +72,7 @@ impl ExtXSessionData {
}
}
/// Returns a new Builder for [ExtXSessionData].
/// Returns a new Builder for [`ExtXSessionData`].
///
/// # Example
/// ```
@ -94,7 +98,7 @@ impl ExtXSessionData {
ExtXSessionDataBuilder::default()
}
/// Makes a new [ExtXSessionData] tag, with the given language.
/// Makes a new [`ExtXSessionData`] tag, with the given language.
///
/// # Example
/// ```
@ -154,7 +158,7 @@ impl ExtXSessionData {
&self.data
}
/// Returns the `language` tag, that identifies the language of [SessionData].
/// Returns the `language` tag, that identifies the language of [`SessionData`].
///
/// # Example
/// ```
@ -175,7 +179,7 @@ impl ExtXSessionData {
&self.language
}
/// Sets the `language` attribute, that identifies the language of [SessionData].
/// Sets the `language` attribute, that identifies the language of [`SessionData`].
/// See [rfc5646](https://tools.ietf.org/html/rfc5646).
///
/// # Example
@ -224,7 +228,7 @@ impl ExtXSessionData {
self
}
/// Sets the [data](ExtXSessionData::data) of this tag.
/// Sets the [`data`](ExtXSessionData::data) of this tag.
///
/// # Example
/// ```

View file

@ -7,9 +7,9 @@ use crate::utils::tag;
use crate::Error;
/// # [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
/// 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:
@ -17,8 +17,8 @@ use crate::Error;
/// #EXT-X-SESSION-KEY:<attribute-list>
/// ```
///
/// [Media Playlist]: crate::MediaPlaylist
/// [Master Playlist]: crate::MasterPlaylist
/// [`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
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXSessionKey(DecryptionKey);
@ -26,11 +26,13 @@ pub struct ExtXSessionKey(DecryptionKey);
impl ExtXSessionKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-SESSION-KEY:";
/// Makes a new [ExtXSessionKey] tag.
/// 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].
/// 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
/// ```

View file

@ -25,7 +25,7 @@ pub struct ExtXStreamInf {
impl ExtXStreamInf {
pub(crate) const PREFIX: &'static str = "#EXT-X-STREAM-INF:";
/// Creates a new [ExtXStreamInf] tag.
/// Creates a new [`ExtXStreamInf`] tag.
///
/// # Example
/// ```
@ -164,7 +164,7 @@ impl ExtXStreamInf {
self
}
/// Returns the value of [ClosedCaptions] attribute.
/// Returns the value of [`ClosedCaptions`] attribute.
///
/// # Example
/// ```
@ -181,7 +181,7 @@ impl ExtXStreamInf {
&self.closed_captions
}
/// Returns the value of [ClosedCaptions] attribute.
/// Returns the value of [`ClosedCaptions`] attribute.
///
/// # Example
/// ```

View file

@ -6,9 +6,9 @@ use crate::utils::tag;
/// # [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
///
/// The [ExtXDiscontinuitySequence] tag allows synchronization between
/// The [`ExtXDiscontinuitySequence`] tag allows synchronization between
/// different Renditions of the same Variant Stream or different Variant
/// Streams that have [ExtXDiscontinuity] tags in their [Media Playlist]s.
/// Streams that have [`ExtXDiscontinuity`] tags in their [`Media Playlist`]s.
///
/// Its format is:
/// ```text
@ -16,8 +16,8 @@ use crate::utils::tag;
/// ```
/// where `number` is a [u64].
///
/// [ExtXDiscontinuity]: crate::tags::ExtXDiscontinuity
/// [Media Playlist]: crate::MediaPlaylist
/// [`ExtXDiscontinuity`]: crate::tags::ExtXDiscontinuity
/// [`Media Playlist`]: crate::MediaPlaylist
/// [4.4.3.3. EXT-X-DISCONTINUITY-SEQUENCE]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.3
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]

View file

@ -6,16 +6,16 @@ use crate::utils::tag;
use crate::Error;
/// # [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.
/// 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
/// [`Media Segment`]: crate::MediaSegment
/// [`Media Playlist`]: crate::MediaPlaylist
/// [4.4.3.4. EXT-X-ENDLIST]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.4
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View file

@ -6,7 +6,7 @@ use crate::utils::tag;
use crate::Error;
/// # [4.4.3.6. EXT-X-I-FRAMES-ONLY]
/// The [ExtXIFramesOnly] tag indicates that each [Media Segment] in the
/// 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
@ -17,7 +17,7 @@ use crate::Error;
/// #EXT-X-I-FRAMES-ONLY
/// ```
///
/// [Media Segment]: crate::MediaSegment
/// [`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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View file

@ -6,14 +6,14 @@ use crate::utils::tag;
use crate::Error;
/// # [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.
/// The [`ExtXMediaSequence`] tag indicates the Media Sequence Number of
/// the first [`Media Segment`] that appears in a Playlist file.
///
/// Its format is:
/// ```text
/// #EXT-X-MEDIA-SEQUENCE:<number>
/// ```
/// where `number` is a [u64].
/// where `number` is a [`u64`].
///
/// [Media Segment]: crate::MediaSegment
/// [4.4.3.2. EXT-X-MEDIA-SEQUENCE]:
@ -24,7 +24,7 @@ pub struct ExtXMediaSequence(u64);
impl ExtXMediaSequence {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA-SEQUENCE:";
/// Makes a new [ExtXMediaSequence] tag.
/// Makes a new [`ExtXMediaSequence`] tag.
///
/// # Example
/// ```

View file

@ -7,8 +7,8 @@ use crate::Error;
/// # [4.4.3.5. EXT-X-PLAYLIST-TYPE]
///
/// The [ExtXPlaylistType] tag provides mutability information about the
/// [Media Playlist]. It applies to the entire [Media Playlist].
/// The [`ExtXPlaylistType`] tag provides mutability information about the
/// [`Media Playlist`]. It applies to the entire [`Media Playlist`].
///
/// Its format is:
/// ```text
@ -20,10 +20,10 @@ use crate::Error;
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.5
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExtXPlaylistType {
/// If the [ExtXPlaylistType] is Event, Media Segments can only be added to
/// the end of the Media Playlist.
/// If the [`ExtXPlaylistType`] is Event, Media Segments
/// can only be added to the end of the Media Playlist.
Event,
/// If the [ExtXPlaylistType] is Video On Demand (Vod),
/// If the [`ExtXPlaylistType`] is Video On Demand (Vod),
/// the Media Playlist cannot change.
Vod,
}

View file

@ -7,39 +7,52 @@ use crate::utils::tag;
use crate::Error;
/// # [4.4.3.1. EXT-X-TARGETDURATION]
/// The [ExtXTargetDuration] tag specifies the maximum [Media Segment]
/// The [`ExtXTargetDuration`] tag specifies the maximum [`Media Segment`]
/// duration.
///
/// Its format is:
/// ```text
/// #EXT-X-TARGETDURATION:<s>
/// ```
/// where `s` is the target [Duration] in seconds.
///
/// [Media Segment]: crate::MediaSegment
/// [`Media Segment`]: crate::MediaSegment
/// [4.4.3.1. EXT-X-TARGETDURATION]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.3.1
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.4.3.1
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ExtXTargetDuration(Duration);
impl ExtXTargetDuration {
pub(crate) const PREFIX: &'static str = "#EXT-X-TARGETDURATION:";
/// Makes a new [ExtXTargetduration] tag.
/// Makes a new [`ExtXTargetDuration`] tag.
///
/// # Example
/// ```
/// # use hls_m3u8::tags::ExtXTargetDuration;
/// use std::time::Duration;
///
/// let target_duration = ExtXTargetDuration::new(Duration::from_secs(20));
/// ```
///
/// # Note
/// The nanoseconds part of the [Duration] will be discarded.
/// The nanoseconds part of the [`Duration`] will be discarded.
pub const fn new(duration: Duration) -> Self {
// TOOD: round instead of discarding?
Self(Duration::from_secs(duration.as_secs()))
}
/// Returns the maximum media segment duration in the associated playlist.
/// Returns the maximum media segment duration.
///
/// # Example
/// ```
/// # use hls_m3u8::tags::ExtXTargetDuration;
/// use std::time::Duration;
///
/// let target_duration = ExtXTargetDuration::new(Duration::from_nanos(2_000_000_000));
///
/// assert_eq!(target_duration.duration(), Duration::from_secs(2));
/// ```
pub const fn duration(&self) -> Duration {
self.0
}
}
/// This tag requires [`ProtocolVersion::V1`].
impl RequiredVersion for ExtXTargetDuration {
fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1

View file

@ -8,7 +8,7 @@ use crate::Error;
/// # [4.4.2.2. EXT-X-BYTERANGE]
///
/// The [ExtXByteRange] tag indicates that a [Media Segment] is a sub-range
/// The [`ExtXByteRange`] tag indicates that a [`Media Segment`] is a sub-range
/// of the resource identified by its `URI`.
///
/// Its format is:
@ -20,7 +20,7 @@ use crate::Error;
/// 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
/// [`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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -29,7 +29,7 @@ pub struct ExtXByteRange(ByteRange);
impl ExtXByteRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
/// Makes a new [ExtXByteRange] tag.
/// Makes a new [`ExtXByteRange`] tag.
///
/// # Example
/// ```
@ -40,7 +40,7 @@ impl ExtXByteRange {
Self(ByteRange::new(length, start))
}
/// Converts the [ExtXByteRange] to a [ByteRange].
/// Converts the [`ExtXByteRange`] to a [`ByteRange`].
///
/// # Example
/// ```

View file

@ -10,7 +10,7 @@ use crate::types::{ProtocolVersion, RequiredVersion};
use crate::utils::{quote, tag, unquote};
use crate::Error;
/// [4.3.2.7. EXT-X-DATERANGE]
/// # [4.3.2.7. EXT-X-DATERANGE]
///
/// [4.3.2.7. EXT-X-DATERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.7
///
@ -18,7 +18,7 @@ use crate::Error;
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXDateRange {
/// A string that uniquely identifies a [ExtXDateRange] in the Playlist.
/// A string that uniquely identifies a [`ExtXDateRange`] in the Playlist.
/// This attribute is required.
id: String,
/// A client-defined string that specifies some set of attributes and their associated value
@ -64,7 +64,7 @@ pub struct ExtXDateRange {
impl ExtXDateRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-DATERANGE:";
/// Makes a new [ExtXDateRange] tag.
/// Makes a new [`ExtXDateRange`] tag.
///
/// # Example
/// ```
@ -74,7 +74,7 @@ impl ExtXDateRange {
///
/// const HOURS_IN_SECS: i32 = 3600; // 1 hour = 3600 seconds
///
/// ExtXDateRange::new("id", FixedOffset::east(8 * HOURS_IN_SECS)
/// let date_range = ExtXDateRange::new("id", FixedOffset::east(8 * HOURS_IN_SECS)
/// .ymd(2010, 2, 19)
/// .and_hms_milli(14, 54, 23, 31));
/// ```
@ -95,6 +95,22 @@ impl ExtXDateRange {
}
}
/// This tag requires [`ProtocolVersion::V1`].
///
/// # Example
/// ```
/// # use hls_m3u8::tags::ExtXDateRange;
/// use hls_m3u8::types::{RequiredVersion, ProtocolVersion};
/// use chrono::{DateTime, FixedOffset};
/// use chrono::offset::TimeZone;
///
/// 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.required_version(), ProtocolVersion::V1);
/// ```
impl RequiredVersion for ExtXDateRange {
fn required_version(&self) -> ProtocolVersion {
ProtocolVersion::V1

View file

@ -6,15 +6,15 @@ use crate::utils::tag;
use crate::Error;
/// # [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.
/// 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
/// [`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
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View file

@ -8,8 +8,8 @@ use crate::Error;
/// # [4.4.2.1. EXTINF]
///
/// The [ExtInf] tag specifies the duration of a [Media Segment]. It applies
/// only to the next [Media Segment].
/// The [`ExtInf`] tag specifies the duration of a [`Media Segment`]. It applies
/// only to the next [`Media Segment`].
///
/// Its format is:
/// ```text
@ -17,7 +17,7 @@ use crate::Error;
/// ```
/// The title is an optional informative title about the [Media Segment].
///
/// [Media Segment]: crate::media_segment::MediaSegment
/// [`Media Segment`]: crate::media_segment::MediaSegment
/// [4.4.2.1. EXTINF]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.1
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
@ -29,7 +29,7 @@ pub struct ExtInf {
impl ExtInf {
pub(crate) const PREFIX: &'static str = "#EXTINF:";
/// Makes a new [ExtInf] tag.
/// Makes a new [`ExtInf`] tag.
///
/// # Example
/// ```
@ -45,7 +45,7 @@ impl ExtInf {
}
}
/// Makes a new [ExtInf] tag with the given title.
/// Makes a new [`ExtInf`] tag with the given title.
///
/// # Example
/// ```

View file

@ -7,11 +7,11 @@ use crate::utils::tag;
use crate::Error;
/// # [4.4.2.4. EXT-X-KEY]
/// [Media Segment]s may be encrypted. The [ExtXKey] tag specifies how to
/// decrypt them. It applies to every [Media Segment] and to every Media
/// Initialization Section declared by an [ExtXMap] tag, that appears
/// between it and the next [ExtXKey] tag in the Playlist file with the
/// same [KeyFormat] attribute (or the end of the Playlist file).
/// [`Media Segment`]s may be encrypted. The [`ExtXKey`] tag specifies how to
/// decrypt them. It applies to every [`Media Segment`] and to every Media
/// Initialization Section declared by an [`ExtXMap`] tag, that appears
/// between it and the next [`ExtXKey`] tag in the Playlist file with the
/// same [`KeyFormat`] attribute (or the end of the Playlist file).
///
/// The format is:
/// ```text
@ -19,11 +19,12 @@ use crate::Error;
/// ```
///
/// # Note
/// In case of an empty key (`EncryptionMethod::None`), all attributes will be ignored.
/// In case of an empty key (`EncryptionMethod::None`),
/// all attributes will be ignored.
///
/// [KeyFormat]: crate::types::KeyFormat
/// [ExtXMap]: crate::tags::ExtXMap
/// [Media Segment]: crate::MediaSegment
/// [`KeyFormat`]: crate::types::KeyFormat
/// [`ExtXMap`]: crate::tags::ExtXMap
/// [`Media Segment`]: crate::MediaSegment
/// [4.4.2.4. EXT-X-KEY]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-04#section-4.4.2.4
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -32,8 +33,9 @@ pub struct ExtXKey(DecryptionKey);
impl ExtXKey {
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
/// Makes a new `ExtXKey` tag.
/// # Examples
/// Makes a new [`ExtXKey`] tag.
///
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod;
@ -52,8 +54,9 @@ impl ExtXKey {
Self(DecryptionKey::new(method, uri))
}
/// Makes a new `ExtXKey` tag without a decryption key.
/// # Examples
/// Makes a new [`ExtXKey`] tag without a decryption key.
///
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXKey;
///
@ -74,8 +77,10 @@ impl ExtXKey {
})
}
/// Returns whether the [EncryptionMethod] is [None](EncryptionMethod::None).
/// # Examples
/// Returns whether the [`EncryptionMethod`] is
/// [`None`](EncryptionMethod::None).
///
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXKey;
/// use hls_m3u8::types::EncryptionMethod;

View file

@ -7,7 +7,7 @@ use crate::utils::{quote, tag, unquote};
use crate::Error;
/// # [4.4.2.5. EXT-X-MAP]
/// The [ExtXMap] tag specifies how to obtain the Media Initialization
/// The [`ExtXMap`] tag specifies how to obtain the Media Initialization
/// Section, required to parse the applicable [Media Segment]s.
///
/// Its format is:
@ -27,7 +27,7 @@ pub struct ExtXMap {
impl ExtXMap {
pub(crate) const PREFIX: &'static str = "#EXT-X-MAP:";
/// Makes a new `ExtXMap` tag.
/// Makes a new [`ExtXMap`] tag.
pub fn new<T: ToString>(uri: T) -> Self {
ExtXMap {
uri: uri.to_string(),
@ -35,7 +35,7 @@ impl ExtXMap {
}
}
/// Makes a new `ExtXMap` tag with the given range.
/// Makes a new [`ExtXMap`] tag with the given range.
pub fn with_range<T: ToString>(uri: T, range: ByteRange) -> Self {
ExtXMap {
uri: uri.to_string(),
@ -43,7 +43,8 @@ impl ExtXMap {
}
}
/// Returns the URI that identifies a resource that contains the media initialization section.
/// Returns the `URI` that identifies a resource,
/// that contains the media initialization section.
pub const fn uri(&self) -> &String {
&self.uri
}

View file

@ -9,10 +9,10 @@ use crate::utils::tag;
use crate::Error;
/// # [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.
/// The [`ExtXProgramDateTime`] tag associates the first sample of a
/// [`Media Segment`] with an absolute date and/or time.
///
/// [Media Segment]: crate::MediaSegment
/// [`Media Segment`]: crate::MediaSegment
/// [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>);
@ -20,7 +20,7 @@ pub struct ExtXProgramDateTime(DateTime<FixedOffset>);
impl ExtXProgramDateTime {
pub(crate) const PREFIX: &'static str = "#EXT-X-PROGRAM-DATE-TIME:";
/// Makes a new `ExtXProgramDateTime` tag.
/// Makes a new [`ExtXProgramDateTime`] tag.
///
/// # Example
/// ```

View file

@ -18,7 +18,7 @@ pub struct ExtXStart {
impl ExtXStart {
pub(crate) const PREFIX: &'static str = "#EXT-X-START:";
/// Makes a new [ExtXStart] tag.
/// Makes a new [`ExtXStart`] tag.
///
/// # Panic
/// Panics if the time_offset value is infinite.
@ -35,7 +35,7 @@ impl ExtXStart {
}
}
/// Makes a new [ExtXStart] tag with the given `precise` flag.
/// Makes a new [`ExtXStart`] tag with the given `precise` flag.
///
/// # Panic
/// Panics if the time_offset value is infinite.

View file

@ -15,7 +15,7 @@ pub struct ByteRange {
}
impl ByteRange {
/// Creates a new [ByteRange].
/// Creates a new [`ByteRange`].
///
/// # Example
/// ```

188
src/types/channels.rs Normal file
View file

@ -0,0 +1,188 @@
use core::fmt;
use core::str::FromStr;
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.
///
/// The second parameter identifies the encoding of object-based audio used by the
/// rendition. This parameter is a comma-separated list of Audio
/// Object Coding Identifiers. It is optional. An Audio Object
/// Coding Identifier is a string containing characters from the set
/// `[A..Z]`, `[0..9]`, and `'-'`. They are codec-specific. A parameter
/// value of consisting solely of the dash character (`'-'`) indicates
/// that the audio is not object-based.
///
/// # Example
/// Creating a `CHANNELS="6"` attribute
/// ```
/// # use hls_m3u8::types::Channels;
/// let mut channels = Channels::new(6);
///
/// assert_eq!(format!("CHANNELS=\"{}\"", channels), "CHANNELS=\"6\"".to_string());
/// ```
///
/// # Note
/// Currently there are no example playlists in the documentation,
/// or in popular m3u8 libraries, showing a usage for the second parameter
/// of [`Channels`], so if you have one please open an issue on github!
///
/// [`MediaSegment`]: crate::MediaSegment
#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Channels {
first_parameter: u64,
second_parameter: Option<Vec<String>>,
}
impl Channels {
/// Makes a new [`Channels`] struct.
///
/// # Example
/// ```
/// # use hls_m3u8::types::Channels;
/// let mut channels = Channels::new(6);
/// ```
pub const fn new(value: u64) -> Self {
Self {
first_parameter: value,
second_parameter: None,
}
}
/// Returns the first parameter.
///
/// # Example
/// ```
/// # use hls_m3u8::types::Channels;
/// let mut channels = Channels::new(6);
///
/// assert_eq!(channels.first_parameter(), 6);
/// ```
pub const fn first_parameter(&self) -> u64 {
self.first_parameter
}
/// Sets the first parameter.
///
/// # Example
/// ```
/// # use hls_m3u8::types::Channels;
/// let mut channels = Channels::new(3);
///
/// channels.set_first_parameter(6);
/// assert_eq!(channels.first_parameter(), 6)
/// ```
pub fn set_first_parameter(&mut self, value: u64) -> &mut Self {
self.first_parameter = value;
self
}
/// Returns the second parameter, if there is any!
///
/// # Example
/// ```
/// # use hls_m3u8::types::Channels;
/// let mut channels = Channels::new(3);
/// # assert_eq!(channels.second_parameter(), &None);
///
/// channels.set_second_parameter(Some(vec!["AAC","MP3"]));
/// assert_eq!(channels.second_parameter(), &Some(vec!["AAC".to_string(),"MP3".to_string()]))
/// ```
///
/// # Note
/// Currently there is no use for this parameter.
pub const fn second_parameter(&self) -> &Option<Vec<String>> {
&self.second_parameter
}
/// Sets the second parameter.
///
/// # Example
/// ```
/// # use hls_m3u8::types::Channels;
/// let mut channels = Channels::new(3);
/// # assert_eq!(channels.second_parameter(), &None);
///
/// channels.set_second_parameter(Some(vec!["AAC","MP3"]));
/// assert_eq!(channels.second_parameter(), &Some(vec!["AAC".to_string(),"MP3".to_string()]))
/// ```
///
/// # Note
/// Currently there is no use for this parameter.
pub fn set_second_parameter<T: ToString>(&mut self, value: Option<Vec<T>>) -> &mut Self {
self.second_parameter = value.map(|v| v.into_iter().map(|s| s.to_string()).collect());
self
}
}
impl FromStr for Channels {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let parameters = input.split('/').collect::<Vec<_>>();
let first_parameter = parameters
.first()
.ok_or_else(|| Error::missing_attribute("First parameter of channels!"))?
.parse()?;
let second_parameter = parameters
.get(1)
.map(|v| v.split(',').map(|v| v.to_string()).collect());
Ok(Self {
first_parameter,
second_parameter,
})
}
}
impl fmt::Display for Channels {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.first_parameter)?;
if let Some(second) = &self.second_parameter {
write!(f, "/{}", second.join(","))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display() {
let mut channels = Channels::new(6);
assert_eq!(channels.to_string(), "6".to_string());
channels.set_first_parameter(7);
assert_eq!(channels.to_string(), "7".to_string());
assert_eq!(
"6/P,K,J".to_string(),
Channels::new(6)
.set_second_parameter(Some(vec!["P", "K", "J"]))
.to_string()
);
}
#[test]
fn test_parser() {
assert_eq!("6".parse::<Channels>().unwrap(), Channels::new(6));
let mut result = Channels::new(6);
result.set_second_parameter(Some(vec!["P", "K", "J"]));
assert_eq!("6/P,K,J".parse::<Channels>().unwrap(), result);
assert!("garbage".parse::<Channels>().is_err());
assert!("".parse::<Channels>().is_err());
}
}

View file

@ -15,7 +15,7 @@ use crate::Error;
pub(crate) struct DecimalFloatingPoint(f64);
impl DecimalFloatingPoint {
/// Makes a new [DecimalFloatingPoint] instance.
/// Makes a new [`DecimalFloatingPoint`] instance.
///
/// # Errors
///
@ -32,7 +32,7 @@ impl DecimalFloatingPoint {
Self(value)
}
/// Converts [DecimalFloatingPoint] to [f64].
/// Converts [`DecimalFloatingPoint`] to [`f64`].
pub const fn as_f64(self) -> f64 {
self.0
}
@ -40,7 +40,7 @@ impl DecimalFloatingPoint {
impl Eq for DecimalFloatingPoint {}
// this trait is implemented manually, so it doesn't construct a [DecimalFloatingPoint],
// this trait is implemented manually, so it doesn't construct a [`DecimalFloatingPoint`],
// with a negative value.
impl FromStr for DecimalFloatingPoint {
type Err = Error;

View file

@ -17,7 +17,7 @@ pub(crate) struct DecimalResolution {
}
impl DecimalResolution {
/// Creates a new DecimalResolution.
/// Creates a new [`DecimalResolution`].
pub const fn new(width: usize, height: usize) -> Self {
Self { width, height }
}
@ -45,7 +45,7 @@ impl DecimalResolution {
}
}
/// [DecimalResolution] can be constructed from a tuple; `(width, height)`.
/// [`DecimalResolution`] can be constructed from a tuple; `(width, height)`.
impl From<(usize, usize)> for DecimalResolution {
fn from(value: (usize, usize)) -> Self {
DecimalResolution::new(value.0, value.1)

View file

@ -13,10 +13,10 @@ use crate::Error;
#[derive(Builder, Debug, Clone, PartialEq, Eq, Hash)]
#[builder(setter(into), build_fn(validate = "Self::validate"))]
/// [DecryptionKey] contains data, that is shared between [ExtXSessionKey] and [ExtXKey].
/// [`DecryptionKey`] contains data, that is shared between [`ExtXSessionKey`] and [`ExtXKey`].
///
/// [ExtXSessionKey]: crate::tags::ExtXSessionKey
/// [ExtXKey]: crate::tags::ExtXKey
/// [`ExtXSessionKey`]: crate::tags::ExtXSessionKey
/// [`ExtXKey`]: crate::tags::ExtXKey
pub struct DecryptionKey {
/// The [EncryptionMethod].
pub(crate) method: EncryptionMethod,

View file

@ -6,7 +6,8 @@ use crate::utils::{quote, tag, unquote};
use crate::Error;
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
/// KeyFormat specifies, how the key is represented in the resource identified by the URI
/// [`KeyFormat`] specifies, how the key is represented in the
/// resource identified by the `URI`.
pub enum KeyFormat {
/// The key is a single packed array of 16 octets in binary format.
Identity,

View file

@ -8,9 +8,9 @@ use crate::Error;
/// 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
/// [KeyFormat] is defined.
/// [`KeyFormat`] is defined.
///
/// [KeyFormat]: crate::types::KeyFormat
/// [`KeyFormat`]: crate::types::KeyFormat
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct KeyFormatVersions(Vec<usize>);
@ -21,12 +21,12 @@ impl Default for KeyFormatVersions {
}
impl KeyFormatVersions {
/// Makes a new [KeyFormatVersions].
/// Makes a new [`KeyFormatVersions`].
pub fn new() -> Self {
Self::default()
}
/// Add a value to the [KeyFormatVersions].
/// Add a value to the [`KeyFormatVersions`].
pub fn push(&mut self, value: usize) {
if self.is_default() {
self.0 = vec![value];
@ -35,7 +35,7 @@ impl KeyFormatVersions {
}
}
/// Returns `true`, if [KeyFormatVersions] has the default value of `vec![1]`.
/// Returns `true`, if [`KeyFormatVersions`] has the default value of `vec![1]`.
pub fn is_default(&self) -> bool {
self.0 == vec![1] && self.0.len() == 1 || self.0.is_empty()
}

View file

@ -1,5 +1,6 @@
//! Miscellaneous types.
mod byte_range;
mod channels;
mod closed_captions;
mod decimal_floating_point;
mod decimal_resolution;
@ -16,6 +17,7 @@ mod signed_decimal_floating_point;
mod stream_inf;
pub use byte_range::*;
pub use channels::*;
pub use closed_captions::*;
pub(crate) use decimal_floating_point::*;
pub(crate) use decimal_resolution::*;

View file

@ -28,7 +28,7 @@ pub trait RequiredVersion {
}
/// # [7. Protocol Version Compatibility]
/// The [ProtocolVersion] specifies, which m3u8 revision is required, to parse
/// The [`ProtocolVersion`] specifies, which m3u8 revision is required, to parse
/// a certain tag correctly.
///
/// [7. Protocol Version Compatibility]:
@ -46,7 +46,8 @@ pub enum ProtocolVersion {
}
impl ProtocolVersion {
/// Returns the newest [ProtocolVersion], that is supported by this library.
/// Returns the newest [`ProtocolVersion`], that is supported by
/// this library.
///
/// # Example
/// ```