mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2025-01-11 07:05:27 +00:00
Merge pull request #45 from vagetman/patch-2
A fix for `CLOSED-CAPTIONS=NONE` case and a few minor fixes
This commit is contained in:
commit
7173c26015
2 changed files with 134 additions and 70 deletions
103
src/parser.rs
103
src/parser.rs
|
@ -12,6 +12,7 @@ use crate::playlist::*;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::f32;
|
use std::f32;
|
||||||
|
use std::fmt;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -542,13 +543,16 @@ fn extmap(i: &[u8]) -> IResult<&[u8], Map> {
|
||||||
let uri = attrs.get("URI").cloned().unwrap_or_default();
|
let uri = attrs.get("URI").cloned().unwrap_or_default();
|
||||||
let byte_range = attrs
|
let byte_range = attrs
|
||||||
.get("BYTERANGE")
|
.get("BYTERANGE")
|
||||||
.map(|range| match byte_range_val(range.as_bytes()) {
|
.map(|range| match byte_range_val(range.to_string().as_bytes()) {
|
||||||
IResult::Ok((_, range)) => Ok(range),
|
IResult::Ok((_, range)) => Ok(range),
|
||||||
IResult::Err(_) => Err("invalid byte range"),
|
IResult::Err(_) => Err("invalid byte range"),
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
Ok(Map { uri, byte_range })
|
Ok(Map {
|
||||||
|
uri: uri.to_string(),
|
||||||
|
byte_range,
|
||||||
|
})
|
||||||
})(i)
|
})(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,7 +605,7 @@ fn comment_tag(i: &[u8]) -> IResult<&[u8], String> {
|
||||||
// Util
|
// Util
|
||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
fn key_value_pairs(i: &[u8]) -> IResult<&[u8], HashMap<String, String>> {
|
fn key_value_pairs(i: &[u8]) -> IResult<&[u8], HashMap<String, QuotedOrUnquoted>> {
|
||||||
fold_many0(
|
fold_many0(
|
||||||
preceded(space0, key_value_pair),
|
preceded(space0, key_value_pair),
|
||||||
HashMap::new,
|
HashMap::new,
|
||||||
|
@ -612,7 +616,41 @@ fn key_value_pairs(i: &[u8]) -> IResult<&[u8], HashMap<String, String>> {
|
||||||
)(i)
|
)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_value_pair(i: &[u8]) -> IResult<&[u8], (String, String)> {
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum QuotedOrUnquoted {
|
||||||
|
Unquoted(String),
|
||||||
|
Quoted(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for QuotedOrUnquoted {
|
||||||
|
fn default() -> Self {
|
||||||
|
QuotedOrUnquoted::Quoted(String::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for QuotedOrUnquoted {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
if s.starts_with('"') && s.ends_with('"') {
|
||||||
|
return QuotedOrUnquoted::Quoted(s.trim_matches('"').to_string());
|
||||||
|
}
|
||||||
|
QuotedOrUnquoted::Unquoted(s.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for QuotedOrUnquoted {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
QuotedOrUnquoted::Unquoted(s) => s,
|
||||||
|
QuotedOrUnquoted::Quoted(u) => u,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_value_pair(i: &[u8]) -> IResult<&[u8], (String, QuotedOrUnquoted)> {
|
||||||
map(
|
map(
|
||||||
tuple((
|
tuple((
|
||||||
peek(none_of("\r\n")),
|
peek(none_of("\r\n")),
|
||||||
|
@ -625,16 +663,16 @@ fn key_value_pair(i: &[u8]) -> IResult<&[u8], (String, String)> {
|
||||||
)(i)
|
)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quoted(i: &[u8]) -> IResult<&[u8], String> {
|
fn quoted(i: &[u8]) -> IResult<&[u8], QuotedOrUnquoted> {
|
||||||
delimited(
|
delimited(
|
||||||
char('\"'),
|
char('\"'),
|
||||||
map_res(is_not("\""), from_utf8_slice),
|
map_res(is_not("\""), quoted_from_utf8_slice),
|
||||||
char('\"'),
|
char('\"'),
|
||||||
)(i)
|
)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unquoted(i: &[u8]) -> IResult<&[u8], String> {
|
fn unquoted(i: &[u8]) -> IResult<&[u8], QuotedOrUnquoted> {
|
||||||
map_res(is_not(",\r\n"), from_utf8_slice)(i)
|
map_res(is_not(",\r\n"), unquoted_from_utf8_slice)(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_line(i: &[u8]) -> IResult<&[u8], String> {
|
fn consume_line(i: &[u8]) -> IResult<&[u8], String> {
|
||||||
|
@ -687,6 +725,20 @@ fn from_utf8_slice(s: &[u8]) -> Result<String, string::FromUtf8Error> {
|
||||||
String::from_utf8(s.to_vec())
|
String::from_utf8(s.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn quoted_from_utf8_slice(s: &[u8]) -> Result<QuotedOrUnquoted, string::FromUtf8Error> {
|
||||||
|
match String::from_utf8(s.to_vec()) {
|
||||||
|
Ok(q) => Ok(QuotedOrUnquoted::Quoted(q)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unquoted_from_utf8_slice(s: &[u8]) -> Result<QuotedOrUnquoted, string::FromUtf8Error> {
|
||||||
|
match String::from_utf8(s.to_vec()) {
|
||||||
|
Ok(q) => Ok(QuotedOrUnquoted::Unquoted(q)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -729,9 +781,12 @@ mod tests {
|
||||||
key_value_pairs(b"BANDWIDTH=395000,CODECS=\"avc1.4d001f,mp4a.40.2\"\r\nrest="),
|
key_value_pairs(b"BANDWIDTH=395000,CODECS=\"avc1.4d001f,mp4a.40.2\"\r\nrest="),
|
||||||
Result::Ok((
|
Result::Ok((
|
||||||
"\r\nrest=".as_bytes(),
|
"\r\nrest=".as_bytes(),
|
||||||
vec![("BANDWIDTH", "395000"), ("CODECS", "avc1.4d001f,mp4a.40.2")]
|
vec![
|
||||||
|
("BANDWIDTH", "395000"),
|
||||||
|
("CODECS", "\"avc1.4d001f,mp4a.40.2\"")
|
||||||
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (String::from(k), String::from(v)))
|
.map(|(k, v)| (String::from(k), v.into()))
|
||||||
.collect::<HashMap<_, _>>(),
|
.collect::<HashMap<_, _>>(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
@ -745,13 +800,13 @@ mod tests {
|
||||||
"\nrest".as_bytes(),
|
"\nrest".as_bytes(),
|
||||||
vec![
|
vec![
|
||||||
("BANDWIDTH", "86000"),
|
("BANDWIDTH", "86000"),
|
||||||
("URI", "low/iframe.m3u8"),
|
("URI", "\"low/iframe.m3u8\""),
|
||||||
("PROGRAM-ID", "1"),
|
("PROGRAM-ID", "1"),
|
||||||
("RESOLUTION", "1x1"),
|
("RESOLUTION", "\"1x1\""),
|
||||||
("VIDEO", "1")
|
("VIDEO", "1")
|
||||||
].into_iter()
|
].into_iter()
|
||||||
.map(|(k, v)| (String::from(k), String::from(v)))
|
.map(|(k, v)| (String::from(k), v.into()))
|
||||||
.collect::<HashMap<String,String>>()
|
.collect::<HashMap<_,_>>()
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -762,10 +817,13 @@ mod tests {
|
||||||
key_value_pairs(b"BANDWIDTH=300000,CODECS=\"avc1.42c015,mp4a.40.2\"\r\nrest"),
|
key_value_pairs(b"BANDWIDTH=300000,CODECS=\"avc1.42c015,mp4a.40.2\"\r\nrest"),
|
||||||
Result::Ok((
|
Result::Ok((
|
||||||
"\r\nrest".as_bytes(),
|
"\r\nrest".as_bytes(),
|
||||||
vec![("BANDWIDTH", "300000"), ("CODECS", "avc1.42c015,mp4a.40.2")]
|
vec![
|
||||||
|
("BANDWIDTH", "300000"),
|
||||||
|
("CODECS", "\"avc1.42c015,mp4a.40.2\"")
|
||||||
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (String::from(k), String::from(v)))
|
.map(|(k, v)| (String::from(k), v.into()))
|
||||||
.collect::<HashMap<String, String>>()
|
.collect::<HashMap<_, _>>()
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -782,8 +840,8 @@ mod tests {
|
||||||
("VIDEO", "1")
|
("VIDEO", "1")
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (String::from(k), String::from(v)))
|
.map(|(k, v)| (String::from(k), v.into()))
|
||||||
.collect::<HashMap<String, String>>()
|
.collect::<HashMap<_, _>>()
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -792,10 +850,7 @@ mod tests {
|
||||||
fn test_key_value_pair() {
|
fn test_key_value_pair() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
key_value_pair(b"PROGRAM-ID=1,rest"),
|
key_value_pair(b"PROGRAM-ID=1,rest"),
|
||||||
Result::Ok((
|
Result::Ok(("rest".as_bytes(), ("PROGRAM-ID".to_string(), "1".into())))
|
||||||
"rest".as_bytes(),
|
|
||||||
("PROGRAM-ID".to_string(), "1".to_string())
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -839,7 +894,7 @@ mod tests {
|
||||||
fn quotes() {
|
fn quotes() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
quoted(b"\"value\"rest"),
|
quoted(b"\"value\"rest"),
|
||||||
Result::Ok(("rest".as_bytes(), "value".to_string()))
|
Result::Ok(("rest".as_bytes(), "\"value\"".into()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! The main type here is the `Playlist` enum.
|
//! The main type here is the `Playlist` enum.
|
||||||
//! Which is either a `MasterPlaylist` or a `MediaPlaylist`.
|
//! Which is either a `MasterPlaylist` or a `MediaPlaylist`.
|
||||||
|
|
||||||
|
use crate::QuotedOrUnquoted;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::f32;
|
use std::f32;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -140,28 +141,31 @@ pub struct VariantStream {
|
||||||
pub codecs: Option<String>,
|
pub codecs: Option<String>,
|
||||||
pub resolution: Option<String>,
|
pub resolution: Option<String>,
|
||||||
pub frame_rate: Option<String>,
|
pub frame_rate: Option<String>,
|
||||||
pub hdcp_level: Option<String>,
|
pub hdcp_level: Option<QuotedOrUnquoted>,
|
||||||
pub audio: Option<String>,
|
pub audio: Option<String>,
|
||||||
pub video: Option<String>,
|
pub video: Option<String>,
|
||||||
pub subtitles: Option<String>,
|
pub subtitles: Option<String>,
|
||||||
pub closed_captions: Option<String>,
|
pub closed_captions: Option<QuotedOrUnquoted>,
|
||||||
// PROGRAM-ID tag was removed in protocol version 6
|
// PROGRAM-ID tag was removed in protocol version 6
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VariantStream {
|
impl VariantStream {
|
||||||
pub fn from_hashmap(mut attrs: HashMap<String, String>, is_i_frame: bool) -> VariantStream {
|
pub fn from_hashmap(
|
||||||
|
mut attrs: HashMap<String, QuotedOrUnquoted>,
|
||||||
|
is_i_frame: bool,
|
||||||
|
) -> VariantStream {
|
||||||
VariantStream {
|
VariantStream {
|
||||||
is_i_frame,
|
is_i_frame,
|
||||||
uri: attrs.remove("URI").unwrap_or_else(String::new),
|
uri: attrs.remove("URI").unwrap_or_default().to_string(),
|
||||||
bandwidth: attrs.remove("BANDWIDTH").unwrap_or_else(String::new),
|
bandwidth: attrs.remove("BANDWIDTH").unwrap_or_default().to_string(),
|
||||||
average_bandwidth: attrs.remove("AVERAGE-BANDWIDTH"),
|
average_bandwidth: attrs.remove("AVERAGE-BANDWIDTH").map(|a| a.to_string()),
|
||||||
codecs: attrs.remove("CODECS"),
|
codecs: attrs.remove("CODECS").map(|c| c.to_string()),
|
||||||
resolution: attrs.remove("RESOLUTION"),
|
resolution: attrs.remove("RESOLUTION").map(|r| r.to_string()),
|
||||||
frame_rate: attrs.remove("FRAME-RATE"),
|
frame_rate: attrs.remove("FRAME-RATE").map(|f| f.to_string()),
|
||||||
hdcp_level: attrs.remove("HDCP-LEVEL"),
|
hdcp_level: attrs.remove("HDCP-LEVEL"),
|
||||||
audio: attrs.remove("AUDIO"),
|
audio: attrs.remove("AUDIO").map(|a| a.to_string()),
|
||||||
video: attrs.remove("VIDEO"),
|
video: attrs.remove("VIDEO").map(|v| v.to_string()),
|
||||||
subtitles: attrs.remove("SUBTITLES"),
|
subtitles: attrs.remove("SUBTITLES").map(|s| s.to_string()),
|
||||||
closed_captions: attrs.remove("CLOSED-CAPTIONS"),
|
closed_captions: attrs.remove("CLOSED-CAPTIONS"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,7 +180,7 @@ impl VariantStream {
|
||||||
self.write_stream_inf_common_attributes(w)?;
|
self.write_stream_inf_common_attributes(w)?;
|
||||||
write_some_attribute_quoted!(w, ",AUDIO", &self.audio)?;
|
write_some_attribute_quoted!(w, ",AUDIO", &self.audio)?;
|
||||||
write_some_attribute_quoted!(w, ",SUBTITLES", &self.subtitles)?;
|
write_some_attribute_quoted!(w, ",SUBTITLES", &self.subtitles)?;
|
||||||
write_some_attribute_quoted!(w, ",CLOSED-CAPTIONS", &self.closed_captions)?;
|
write_some_attribute!(w, ",CLOSED-CAPTIONS", &self.closed_captions)?;
|
||||||
writeln!(w)?;
|
writeln!(w)?;
|
||||||
writeln!(w, "{}", self.uri)
|
writeln!(w, "{}", self.uri)
|
||||||
}
|
}
|
||||||
|
@ -219,23 +223,23 @@ pub struct AlternativeMedia {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlternativeMedia {
|
impl AlternativeMedia {
|
||||||
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> AlternativeMedia {
|
pub fn from_hashmap(mut attrs: HashMap<String, QuotedOrUnquoted>) -> AlternativeMedia {
|
||||||
AlternativeMedia {
|
AlternativeMedia {
|
||||||
media_type: attrs
|
media_type: attrs
|
||||||
.get("TYPE")
|
.get("TYPE")
|
||||||
.and_then(|s| AlternativeMediaType::from_str(s).ok())
|
.and_then(|s| AlternativeMediaType::from_str(s.to_string().as_str()).ok())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
uri: attrs.remove("URI"),
|
uri: attrs.remove("URI").map(|u| u.to_string()),
|
||||||
group_id: attrs.remove("GROUP-ID").unwrap_or_else(String::new),
|
group_id: attrs.remove("GROUP-ID").unwrap_or_default().to_string(),
|
||||||
language: attrs.remove("LANGUAGE"),
|
language: attrs.remove("LANGUAGE").map(|l| l.to_string()),
|
||||||
assoc_language: attrs.remove("ASSOC-LANGUAGE"),
|
assoc_language: attrs.remove("ASSOC-LANGUAGE").map(|a| a.to_string()),
|
||||||
name: attrs.remove("NAME").unwrap_or_default(),
|
name: attrs.remove("NAME").unwrap_or_default().to_string(),
|
||||||
default: bool_default_false!(attrs.remove("DEFAULT")),
|
default: bool_default_false!(attrs.remove("DEFAULT").map(|s| s.to_string())),
|
||||||
autoselect: bool_default_false!(attrs.remove("AUTOSELECT")),
|
autoselect: bool_default_false!(attrs.remove("AUTOSELECT").map(|s| s.to_string())),
|
||||||
forced: bool_default_false!(attrs.remove("FORCED")),
|
forced: bool_default_false!(attrs.remove("FORCED").map(|f| f.to_string())),
|
||||||
instream_id: attrs.remove("INSTREAM-ID"),
|
instream_id: attrs.remove("INSTREAM-ID").map(|i| i.to_string()),
|
||||||
characteristics: attrs.remove("CHARACTERISTICS"),
|
characteristics: attrs.remove("CHARACTERISTICS").map(|c| c.to_string()),
|
||||||
channels: attrs.remove("CHANNELS"),
|
channels: attrs.remove("CHANNELS").map(|c| c.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,14 +345,16 @@ pub struct SessionData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionData {
|
impl SessionData {
|
||||||
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Result<SessionData, String> {
|
pub fn from_hashmap(
|
||||||
|
mut attrs: HashMap<String, QuotedOrUnquoted>,
|
||||||
|
) -> Result<SessionData, String> {
|
||||||
let data_id = match attrs.remove("DATA-ID") {
|
let data_id = match attrs.remove("DATA-ID") {
|
||||||
Some(data_id) => data_id,
|
Some(data_id) => data_id,
|
||||||
None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string()),
|
None => return Err("EXT-X-SESSION-DATA field without DATA-ID".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let value = attrs.remove("VALUE");
|
let value = attrs.remove("VALUE").map(|v| v.to_string());
|
||||||
let uri = attrs.remove("URI");
|
let uri = attrs.remove("URI").map(|u| u.to_string());
|
||||||
|
|
||||||
// SessionData must contain either a VALUE or a URI,
|
// SessionData must contain either a VALUE or a URI,
|
||||||
// but not both https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
// but not both https://tools.ietf.org/html/rfc8216#section-4.3.4.4
|
||||||
|
@ -370,9 +376,9 @@ impl SessionData {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(SessionData {
|
Ok(SessionData {
|
||||||
data_id,
|
data_id: data_id.to_string(),
|
||||||
field,
|
field,
|
||||||
language: attrs.remove("LANGUAGE"),
|
language: attrs.remove("LANGUAGE").map(|s| s.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,9 +438,6 @@ impl MediaPlaylist {
|
||||||
self.discontinuity_sequence
|
self.discontinuity_sequence
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if self.end_list {
|
|
||||||
writeln!(w, "#EXT-X-ENDLIST")?;
|
|
||||||
}
|
|
||||||
if let Some(ref v) = self.playlist_type {
|
if let Some(ref v) = self.playlist_type {
|
||||||
writeln!(w, "#EXT-X-PLAYLIST-TYPE:{}", v)?;
|
writeln!(w, "#EXT-X-PLAYLIST-TYPE:{}", v)?;
|
||||||
}
|
}
|
||||||
|
@ -450,6 +453,9 @@ impl MediaPlaylist {
|
||||||
for segment in &self.segments {
|
for segment in &self.segments {
|
||||||
segment.write_to(w)?;
|
segment.write_to(w)?;
|
||||||
}
|
}
|
||||||
|
if self.end_list {
|
||||||
|
writeln!(w, "#EXT-X-ENDLIST")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -586,13 +592,13 @@ pub struct Key {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Key {
|
impl Key {
|
||||||
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Key {
|
pub fn from_hashmap(mut attrs: HashMap<String, QuotedOrUnquoted>) -> Key {
|
||||||
Key {
|
Key {
|
||||||
method: attrs.remove("METHOD").unwrap_or_else(String::new),
|
method: attrs.remove("METHOD").unwrap_or_default().to_string(),
|
||||||
uri: attrs.remove("URI"),
|
uri: attrs.remove("URI").map(|u| u.to_string()),
|
||||||
iv: attrs.remove("IV"),
|
iv: attrs.remove("IV").map(|i| i.to_string()),
|
||||||
keyformat: attrs.remove("KEYFORMAT"),
|
keyformat: attrs.remove("KEYFORMAT").map(|k| k.to_string()),
|
||||||
keyformatversions: attrs.remove("KEYFORMATVERSIONS"),
|
keyformatversions: attrs.remove("KEYFORMATVERSIONS").map(|k| k.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,10 +690,13 @@ pub struct Start {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Start {
|
impl Start {
|
||||||
pub fn from_hashmap(mut attrs: HashMap<String, String>) -> Start {
|
pub fn from_hashmap(mut attrs: HashMap<String, QuotedOrUnquoted>) -> Start {
|
||||||
Start {
|
Start {
|
||||||
time_offset: attrs.remove("TIME-OFFSET").unwrap_or_else(String::new),
|
time_offset: attrs.remove("TIME-OFFSET").unwrap_or_default().to_string(),
|
||||||
precise: attrs.remove("PRECISE").or_else(|| Some("NO".to_string())),
|
precise: attrs
|
||||||
|
.remove("PRECISE")
|
||||||
|
.map(|a| a.to_string())
|
||||||
|
.or_else(|| Some("NO".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue