1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-31 22:58:16 +00:00
hls_m3u8/src/tags/master_playlist/media.rs

379 lines
12 KiB
Rust
Raw Normal View History

2019-09-06 10:55:00 +00:00
use crate::attribute::AttributePairs;
2019-09-08 09:30:52 +00:00
use crate::types::{InStreamId, MediaType, ProtocolVersion};
use crate::utils::{parse_yes_or_no, quote, unquote};
2019-09-06 10:55:00 +00:00
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
/// `ExtXMedia` builder.
#[derive(Debug, Clone)]
pub struct ExtXMediaBuilder {
media_type: Option<MediaType>,
2019-09-08 09:30:52 +00:00
uri: Option<String>,
group_id: Option<String>,
language: Option<String>,
assoc_language: Option<String>,
name: Option<String>,
2019-09-06 10:55:00 +00:00
default: bool,
autoselect: Option<bool>,
forced: Option<bool>,
instream_id: Option<InStreamId>,
2019-09-08 09:30:52 +00:00
characteristics: Option<String>,
channels: Option<String>,
2019-09-06 10:55:00 +00:00
}
impl ExtXMediaBuilder {
/// Makes a `ExtXMediaBuilder` instance.
2019-09-08 10:23:33 +00:00
pub const fn new() -> Self {
2019-09-06 10:55:00 +00:00
ExtXMediaBuilder {
media_type: None,
uri: None,
group_id: None,
language: None,
assoc_language: None,
name: None,
default: false,
autoselect: None,
forced: None,
instream_id: None,
characteristics: None,
channels: None,
}
}
/// Sets the media type of the rendition.
pub fn media_type(&mut self, media_type: MediaType) -> &mut Self {
self.media_type = Some(media_type);
self
}
/// Sets the identifier that specifies the group to which the rendition belongs.
2019-09-08 09:30:52 +00:00
pub fn group_id<T: ToString>(&mut self, group_id: T) -> &mut Self {
self.group_id = Some(group_id.to_string());
2019-09-06 10:55:00 +00:00
self
}
/// Sets a human-readable description of the rendition.
2019-09-08 09:30:52 +00:00
pub fn name<T: ToString>(&mut self, name: T) -> &mut Self {
self.name = Some(name.to_string());
2019-09-06 10:55:00 +00:00
self
}
/// Sets the URI that identifies the media playlist.
2019-09-08 09:30:52 +00:00
pub fn uri<T: ToString>(&mut self, uri: T) -> &mut Self {
self.uri = Some(uri.to_string());
2019-09-06 10:55:00 +00:00
self
}
/// Sets the name of the primary language used in the rendition.
2019-09-08 09:30:52 +00:00
pub fn language<T: ToString>(&mut self, language: T) -> &mut Self {
self.language = Some(language.to_string());
2019-09-06 10:55:00 +00:00
self
}
/// Sets the name of a language associated with the rendition.
2019-09-08 09:30:52 +00:00
pub fn assoc_language<T: ToString>(&mut self, language: T) -> &mut Self {
self.assoc_language = Some(language.to_string());
2019-09-06 10:55:00 +00:00
self
}
/// Sets the value of the `default` flag.
pub fn default(&mut self, b: bool) -> &mut Self {
self.default = b;
self
}
/// Sets the value of the `autoselect` flag.
pub fn autoselect(&mut self, b: bool) -> &mut Self {
self.autoselect = Some(b);
self
}
/// Sets the value of the `forced` flag.
pub fn forced(&mut self, b: bool) -> &mut Self {
self.forced = Some(b);
self
}
/// Sets the identifier that specifies a rendition within the segments in the media playlist.
pub fn instream_id(&mut self, id: InStreamId) -> &mut Self {
self.instream_id = Some(id);
self
}
/// Sets the string that represents uniform type identifiers (UTI).
2019-09-08 09:30:52 +00:00
pub fn characteristics<T: ToString>(&mut self, characteristics: T) -> &mut Self {
self.characteristics = Some(characteristics.to_string());
2019-09-06 10:55:00 +00:00
self
}
/// Sets the string that represents the parameters of the rendition.
2019-09-08 09:30:52 +00:00
pub fn channels<T: ToString>(&mut self, channels: T) -> &mut Self {
self.channels = Some(channels.to_string());
2019-09-06 10:55:00 +00:00
self
}
/// Builds a `ExtXMedia` instance.
pub fn finish(self) -> Result<ExtXMedia> {
let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput);
let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput);
let name = track_assert_some!(self.name, ErrorKind::InvalidInput);
if MediaType::ClosedCaptions == media_type {
track_assert_ne!(self.uri, None, ErrorKind::InvalidInput);
track_assert!(self.instream_id.is_some(), ErrorKind::InvalidInput);
} else {
track_assert!(self.instream_id.is_none(), ErrorKind::InvalidInput);
}
if self.default && self.autoselect.is_some() {
track_assert_eq!(self.autoselect, Some(true), ErrorKind::InvalidInput);
}
if MediaType::Subtitles != media_type {
track_assert_eq!(self.forced, None, ErrorKind::InvalidInput);
}
Ok(ExtXMedia {
media_type,
uri: self.uri,
group_id,
language: self.language,
assoc_language: self.assoc_language,
name,
default: self.default,
autoselect: self.autoselect.unwrap_or(false),
forced: self.forced.unwrap_or(false),
instream_id: self.instream_id,
characteristics: self.characteristics,
channels: self.channels,
})
}
}
impl Default for ExtXMediaBuilder {
fn default() -> Self {
Self::new()
}
}
/// [4.3.4.1. EXT-X-MEDIA]
///
/// [4.3.4.1. EXT-X-MEDIA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.1
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtXMedia {
media_type: MediaType,
2019-09-08 09:30:52 +00:00
uri: Option<String>,
group_id: String,
language: Option<String>,
assoc_language: Option<String>,
name: String,
2019-09-06 10:55:00 +00:00
default: bool,
autoselect: bool,
forced: bool,
instream_id: Option<InStreamId>,
2019-09-08 09:30:52 +00:00
characteristics: Option<String>,
channels: Option<String>,
2019-09-06 10:55:00 +00:00
}
impl ExtXMedia {
pub(crate) const PREFIX: &'static str = "#EXT-X-MEDIA:";
/// Makes a new `ExtXMedia` tag.
2019-09-08 09:30:52 +00:00
pub fn new<T: ToString>(media_type: MediaType, group_id: T, name: T) -> Self {
2019-09-06 10:55:00 +00:00
ExtXMedia {
media_type,
uri: None,
2019-09-08 09:30:52 +00:00
group_id: group_id.to_string(),
2019-09-06 10:55:00 +00:00
language: None,
assoc_language: None,
2019-09-08 09:30:52 +00:00
name: name.to_string(),
2019-09-06 10:55:00 +00:00
default: false,
autoselect: false,
forced: false,
instream_id: None,
characteristics: None,
channels: None,
}
}
/// Returns the type of the media associated with this tag.
2019-09-08 10:23:33 +00:00
pub const fn media_type(&self) -> MediaType {
2019-09-06 10:55:00 +00:00
self.media_type
}
/// Returns the identifier that specifies the group to which the rendition belongs.
2019-09-08 10:23:33 +00:00
pub const fn group_id(&self) -> &String {
2019-09-06 10:55:00 +00:00
&self.group_id
}
/// Returns a human-readable description of the rendition.
2019-09-08 10:23:33 +00:00
pub const fn name(&self) -> &String {
2019-09-06 10:55:00 +00:00
&self.name
}
/// Returns the URI that identifies the media playlist.
2019-09-08 09:30:52 +00:00
pub fn uri(&self) -> Option<&String> {
2019-09-06 10:55:00 +00:00
self.uri.as_ref()
}
/// Returns the name of the primary language used in the rendition.
2019-09-08 09:30:52 +00:00
pub fn language(&self) -> Option<&String> {
2019-09-06 10:55:00 +00:00
self.language.as_ref()
}
/// Returns the name of a language associated with the rendition.
2019-09-08 09:30:52 +00:00
pub fn assoc_language(&self) -> Option<&String> {
2019-09-06 10:55:00 +00:00
self.assoc_language.as_ref()
}
/// Returns whether this is the default rendition.
2019-09-08 10:23:33 +00:00
pub const fn default(&self) -> bool {
2019-09-06 10:55:00 +00:00
self.default
}
/// Returns whether the client may choose to
/// play this rendition in the absence of explicit user preference.
2019-09-08 10:23:33 +00:00
pub const fn autoselect(&self) -> bool {
2019-09-06 10:55:00 +00:00
self.autoselect
}
/// Returns whether the rendition contains content that is considered essential to play.
2019-09-08 10:23:33 +00:00
pub const fn forced(&self) -> bool {
2019-09-06 10:55:00 +00:00
self.forced
}
/// Returns the identifier that specifies a rendition within the segments in the media playlist.
2019-09-08 10:23:33 +00:00
pub const fn instream_id(&self) -> Option<InStreamId> {
2019-09-06 10:55:00 +00:00
self.instream_id
}
/// Returns a string that represents uniform type identifiers (UTI).
///
/// Each UTI indicates an individual characteristic of the rendition.
2019-09-08 09:30:52 +00:00
pub fn characteristics(&self) -> Option<&String> {
2019-09-06 10:55:00 +00:00
self.characteristics.as_ref()
}
/// Returns a string that represents the parameters of the rendition.
2019-09-08 09:30:52 +00:00
pub fn channels(&self) -> Option<&String> {
2019-09-06 10:55:00 +00:00
self.channels.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub fn requires_version(&self) -> ProtocolVersion {
match self.instream_id {
None
| Some(InStreamId::Cc1)
| Some(InStreamId::Cc2)
| Some(InStreamId::Cc3)
| Some(InStreamId::Cc4) => ProtocolVersion::V1,
_ => ProtocolVersion::V7,
}
}
}
impl fmt::Display for ExtXMedia {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "TYPE={}", self.media_type)?;
if let Some(ref x) = self.uri {
2019-09-08 09:30:52 +00:00
write!(f, ",URI={}", quote(x))?;
2019-09-06 10:55:00 +00:00
}
2019-09-08 09:30:52 +00:00
write!(f, ",GROUP-ID={}", quote(&self.group_id))?;
2019-09-06 10:55:00 +00:00
if let Some(ref x) = self.language {
2019-09-08 09:30:52 +00:00
write!(f, ",LANGUAGE={}", quote(x))?;
2019-09-06 10:55:00 +00:00
}
if let Some(ref x) = self.assoc_language {
2019-09-08 09:30:52 +00:00
write!(f, ",ASSOC-LANGUAGE={}", quote(x))?;
2019-09-06 10:55:00 +00:00
}
2019-09-08 09:30:52 +00:00
write!(f, ",NAME={}", quote(&self.name))?;
2019-09-06 10:55:00 +00:00
if self.default {
write!(f, ",DEFAULT=YES")?;
}
if self.autoselect {
write!(f, ",AUTOSELECT=YES")?;
}
if self.forced {
write!(f, ",FORCED=YES")?;
}
if let Some(ref x) = self.instream_id {
2019-09-08 09:30:52 +00:00
write!(f, ",INSTREAM-ID={}", quote(x))?;
2019-09-06 10:55:00 +00:00
}
if let Some(ref x) = self.characteristics {
2019-09-08 09:30:52 +00:00
write!(f, ",CHARACTERISTICS={}", quote(x))?;
2019-09-06 10:55:00 +00:00
}
if let Some(ref x) = self.channels {
2019-09-08 09:30:52 +00:00
write!(f, ",CHANNELS={}", quote(x))?;
2019-09-06 10:55:00 +00:00
}
Ok(())
}
}
impl FromStr for ExtXMedia {
type Err = Error;
2019-09-08 10:23:33 +00:00
2019-09-06 10:55:00 +00:00
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let mut builder = ExtXMediaBuilder::new();
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
"TYPE" => {
builder.media_type(track!(value.parse())?);
}
"URI" => {
2019-09-08 09:30:52 +00:00
builder.uri(unquote(value));
2019-09-06 10:55:00 +00:00
}
"GROUP-ID" => {
2019-09-08 09:30:52 +00:00
builder.group_id(unquote(value));
2019-09-06 10:55:00 +00:00
}
"LANGUAGE" => {
2019-09-08 09:30:52 +00:00
builder.language(unquote(value));
2019-09-06 10:55:00 +00:00
}
"ASSOC-LANGUAGE" => {
2019-09-08 09:30:52 +00:00
builder.assoc_language(unquote(value));
2019-09-06 10:55:00 +00:00
}
"NAME" => {
2019-09-08 09:30:52 +00:00
builder.name(unquote(value));
2019-09-06 10:55:00 +00:00
}
"DEFAULT" => {
builder.default(track!(parse_yes_or_no(value))?);
}
"AUTOSELECT" => {
builder.autoselect(track!(parse_yes_or_no(value))?);
}
"FORCED" => {
builder.forced(track!(parse_yes_or_no(value))?);
}
"INSTREAM-ID" => {
2019-09-08 09:30:52 +00:00
builder.instream_id(unquote(value).parse()?);
2019-09-06 10:55:00 +00:00
}
"CHARACTERISTICS" => {
2019-09-08 09:30:52 +00:00
builder.characteristics(unquote(value));
2019-09-06 10:55:00 +00:00
}
"CHANNELS" => {
2019-09-08 09:30:52 +00:00
builder.channels(unquote(value));
2019-09-06 10:55:00 +00:00
}
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized AttributeName.
}
}
}
track!(builder.finish())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_x_media() {
2019-09-08 09:30:52 +00:00
let tag = ExtXMedia::new(MediaType::Audio, "foo", "bar");
2019-09-06 10:55:00 +00:00
let text = r#"#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="foo",NAME="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}