mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-12-23 12:30:29 +00:00
Rewrote AttributePairs
This commit is contained in:
parent
c8f3df1228
commit
3721106795
11 changed files with 168 additions and 129 deletions
196
src/attribute.rs
196
src/attribute.rs
|
@ -1,94 +1,136 @@
|
|||
use crate::{Error, Result};
|
||||
use std::collections::HashSet;
|
||||
use std::str;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AttributePairs<'a> {
|
||||
input: &'a str,
|
||||
visited_keys: HashSet<&'a str>,
|
||||
}
|
||||
impl<'a> AttributePairs<'a> {
|
||||
pub fn parse(input: &'a str) -> Self {
|
||||
AttributePairs {
|
||||
input,
|
||||
visited_keys: HashSet::new(),
|
||||
}
|
||||
}
|
||||
use crate::Error;
|
||||
|
||||
fn parse_name(&mut self) -> Result<&'a str> {
|
||||
for i in 0..self.input.len() {
|
||||
match self.input.as_bytes()[i] {
|
||||
b'=' => {
|
||||
let (key, _) = self.input.split_at(i);
|
||||
let (_, rest) = self.input.split_at(i + 1);
|
||||
self.input = rest;
|
||||
return Ok(key);
|
||||
}
|
||||
b'A'..=b'Z' | b'0'..=b'9' | b'-' => {}
|
||||
_ => {
|
||||
return Err(Error::invalid_attribute(self.input.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct AttributePairs(HashMap<String, String>);
|
||||
|
||||
Err(Error::missing_value(self.input.to_string()))
|
||||
}
|
||||
|
||||
fn parse_raw_value(&mut self) -> &'a str {
|
||||
let mut in_quote = false;
|
||||
let mut value_end = self.input.len();
|
||||
let mut next = self.input.len();
|
||||
for (i, c) in self.input.bytes().enumerate() {
|
||||
match c {
|
||||
b'"' => {
|
||||
in_quote = !in_quote;
|
||||
}
|
||||
b',' if !in_quote => {
|
||||
value_end = i;
|
||||
next = i + 1;
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let (value, _) = self.input.split_at(value_end);
|
||||
let (_, rest) = self.input.split_at(next);
|
||||
self.input = rest;
|
||||
value
|
||||
impl AttributePairs {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for AttributePairs<'a> {
|
||||
type Item = Result<(&'a str, &'a str)>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.input.is_empty() {
|
||||
return None;
|
||||
}
|
||||
impl Deref for AttributePairs {
|
||||
type Target = HashMap<String, String>;
|
||||
|
||||
let result = || -> Result<(&'a str, &'a str)> {
|
||||
let key = self.parse_name()?;
|
||||
self.visited_keys.insert(key);
|
||||
|
||||
let value = self.parse_raw_value();
|
||||
Ok((key, value))
|
||||
}();
|
||||
Some(result)
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for AttributePairs {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for AttributePairs {
|
||||
type Item = (String, String);
|
||||
type IntoIter = ::std::collections::hash_map::IntoIter<String, String>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a AttributePairs {
|
||||
type Item = (&'a String, &'a String);
|
||||
type IntoIter = ::std::collections::hash_map::Iter<'a, String, String>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AttributePairs {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let mut result = AttributePairs::new();
|
||||
|
||||
for line in split(input) {
|
||||
let pair = line.trim().split("=").collect::<Vec<_>>();
|
||||
|
||||
if pair.len() < 2 {
|
||||
return Err(Error::invalid_input());
|
||||
}
|
||||
|
||||
let key = pair[0].to_uppercase();
|
||||
let value = pair[1].to_string();
|
||||
|
||||
result.insert(key.to_string(), value.to_string());
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn split(value: &str) -> Vec<String> {
|
||||
let mut result = vec![];
|
||||
|
||||
let mut inside_quotes = false;
|
||||
let mut temp_string = String::new();
|
||||
|
||||
for c in value.chars() {
|
||||
match c {
|
||||
'"' => {
|
||||
if inside_quotes {
|
||||
inside_quotes = false;
|
||||
} else {
|
||||
inside_quotes = true;
|
||||
}
|
||||
temp_string.push(c);
|
||||
}
|
||||
',' => {
|
||||
if !inside_quotes {
|
||||
result.push(temp_string);
|
||||
temp_string = String::new();
|
||||
} else {
|
||||
temp_string.push(c);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
temp_string.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push(temp_string);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut pairs = AttributePairs::parse("FOO=BAR,BAR=\"baz,qux\",ABC=12.3");
|
||||
assert_eq!(pairs.next().map(|x| x.ok()), Some(Some(("FOO", "BAR"))));
|
||||
assert_eq!(
|
||||
pairs.next().map(|x| x.ok()),
|
||||
Some(Some(("BAR", "\"baz,qux\"")))
|
||||
);
|
||||
assert_eq!(pairs.next().map(|x| x.ok()), Some(Some(("ABC", "12.3"))));
|
||||
assert_eq!(pairs.next().map(|x| x.ok()), None)
|
||||
fn test_parser() {
|
||||
let pairs = ("FOO=BAR,BAR=\"baz,qux\",ABC=12.3")
|
||||
.parse::<AttributePairs>()
|
||||
.unwrap();
|
||||
|
||||
let mut iterator = pairs.iter();
|
||||
assert!(iterator.any(|(k, v)| k == "FOO" && "BAR" == v));
|
||||
|
||||
let mut iterator = pairs.iter();
|
||||
assert!(iterator.any(|(k, v)| k == "BAR" && v == "\"baz,qux\""));
|
||||
|
||||
let mut iterator = pairs.iter();
|
||||
assert!(iterator.any(|(k, v)| k == "ABC" && v == "12.3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterator() {
|
||||
let mut attrs = AttributePairs::new();
|
||||
attrs.insert("key_01".to_string(), "value_01".to_string());
|
||||
attrs.insert("key_02".to_string(), "value_02".to_string());
|
||||
|
||||
let mut iterator = attrs.iter();
|
||||
assert!(iterator.any(|(k, v)| k == "key_01" && v == "value_01"));
|
||||
|
||||
let mut iterator = attrs.iter();
|
||||
assert!(iterator.any(|(k, v)| k == "key_02" && v == "value_02"));
|
||||
}
|
||||
}
|
||||
|
|
13
src/error.rs
13
src/error.rs
|
@ -46,6 +46,9 @@ pub enum ErrorKind {
|
|||
#[fail(display = "Unknown Protocol version: {:?}", _0)]
|
||||
UnknownProtocolVersion(String),
|
||||
|
||||
#[fail(display = "IoError: {}", _0)]
|
||||
Io(String),
|
||||
|
||||
/// Hints that destructuring should not be exhaustive.
|
||||
///
|
||||
/// This enum may grow additional variants, so this makes sure clients
|
||||
|
@ -164,6 +167,10 @@ impl Error {
|
|||
pub(crate) fn unknown_protocol_version<T: ToString>(value: T) -> Self {
|
||||
Self::from(ErrorKind::UnknownProtocolVersion(value.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) fn io<T: ToString>(value: T) -> Self {
|
||||
Self::from(ErrorKind::Io(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::ParseIntError> for Error {
|
||||
|
@ -177,3 +184,9 @@ impl From<std::num::ParseFloatError> for Error {
|
|||
Error::parse_float_error(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(value: ::std::io::Error) -> Self {
|
||||
Error::io(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,10 +94,8 @@ impl FromStr for ExtXIFrameStreamInf {
|
|||
let mut hdcp_level = None;
|
||||
let mut video = None;
|
||||
|
||||
let attrs = AttributePairs::parse(input);
|
||||
for attr in attrs {
|
||||
let (key, value) = attr?;
|
||||
match key {
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"URI" => uri = Some(unquote(value)),
|
||||
"BANDWIDTH" => bandwidth = Some(parse_u64(value)?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some(parse_u64(value)?),
|
||||
|
|
|
@ -331,10 +331,9 @@ impl FromStr for ExtXMedia {
|
|||
let input = tag(input, Self::PREFIX)?;
|
||||
|
||||
let mut builder = ExtXMediaBuilder::new();
|
||||
let attrs = AttributePairs::parse(input);
|
||||
for attr in attrs {
|
||||
let (key, value) = attr?;
|
||||
match key {
|
||||
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"TYPE" => {
|
||||
builder.media_type(value.parse()?);
|
||||
}
|
||||
|
|
|
@ -77,10 +77,8 @@ impl FromStr for ExtXSessionData {
|
|||
let mut uri = None;
|
||||
let mut language = None;
|
||||
|
||||
let attrs = AttributePairs::parse(input);
|
||||
for attr in attrs {
|
||||
let (key, value) = attr?;
|
||||
match key {
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"DATA-ID" => data_id = Some(unquote(value)),
|
||||
"VALUE" => session_value = Some(unquote(value)),
|
||||
"URI" => uri = Some(unquote(value)),
|
||||
|
@ -92,7 +90,7 @@ impl FromStr for ExtXSessionData {
|
|||
}
|
||||
}
|
||||
|
||||
let data_id = data_id.ok_or(Error::missing_value("DATA-ID"))?;
|
||||
let data_id = data_id.ok_or(Error::missing_value("EXT-X-DATA-ID"))?;
|
||||
let data = {
|
||||
if let Some(value) = session_value {
|
||||
if uri.is_some() {
|
||||
|
|
|
@ -149,10 +149,10 @@ impl FromStr for ExtXStreamInf {
|
|||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let mut lines = input.lines();
|
||||
let first_line = lines.next().ok_or(Error::invalid_input())?; // TODO!
|
||||
let second_line = lines.next().ok_or(Error::invalid_input())?; // TODO!
|
||||
let first_line = lines.next().ok_or(Error::missing_value("first_line"))?;
|
||||
let second_line = lines.next().ok_or(Error::missing_value("second_line"))?;
|
||||
|
||||
tag(first_line, Self::PREFIX)?;
|
||||
let first_line = tag(first_line, Self::PREFIX)?;
|
||||
|
||||
let uri = SingleLineString::new(second_line)?;
|
||||
|
||||
|
@ -167,10 +167,8 @@ impl FromStr for ExtXStreamInf {
|
|||
let mut subtitles = None;
|
||||
let mut closed_captions = None;
|
||||
|
||||
let attrs = AttributePairs::parse(first_line.split_at(Self::PREFIX.len()).1);
|
||||
for attr in attrs {
|
||||
let (key, value) = (attr)?;
|
||||
match key {
|
||||
for (key, value) in first_line.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"BANDWIDTH" => bandwidth = Some((parse_u64(value))?),
|
||||
"AVERAGE-BANDWIDTH" => average_bandwidth = Some((parse_u64(value))?),
|
||||
"CODECS" => codecs = Some(unquote(value)),
|
||||
|
@ -188,7 +186,7 @@ impl FromStr for ExtXStreamInf {
|
|||
}
|
||||
}
|
||||
|
||||
let bandwidth = bandwidth.ok_or(Error::missing_value("BANDWIDTH"))?;
|
||||
let bandwidth = bandwidth.ok_or(Error::missing_value("EXT-X-BANDWIDTH"))?;
|
||||
|
||||
Ok(ExtXStreamInf {
|
||||
uri,
|
||||
|
|
|
@ -96,11 +96,9 @@ impl FromStr for ExtXDateRange {
|
|||
let mut end_on_next = false;
|
||||
|
||||
let mut client_attributes = BTreeMap::new();
|
||||
let attrs = AttributePairs::parse(input);
|
||||
|
||||
for attr in attrs {
|
||||
let (key, value) = attr?;
|
||||
match key {
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"ID" => id = Some(unquote(value)),
|
||||
"CLASS" => class = Some(unquote(value)),
|
||||
"START-DATE" => start_date = Some(unquote(value)),
|
||||
|
|
|
@ -10,33 +10,31 @@ use crate::Error;
|
|||
///
|
||||
/// [4.3.2.4. EXT-X-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.4
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ExtXKey {
|
||||
key: Option<DecryptionKey>,
|
||||
}
|
||||
pub struct ExtXKey(Option<DecryptionKey>);
|
||||
|
||||
impl ExtXKey {
|
||||
pub(crate) const PREFIX: &'static str = "#EXT-X-KEY:";
|
||||
|
||||
/// Makes a new `ExtXKey` tag.
|
||||
pub const fn new(key: DecryptionKey) -> Self {
|
||||
ExtXKey { key: Some(key) }
|
||||
Self(Some(key))
|
||||
}
|
||||
|
||||
/// Makes a new `ExtXKey` tag without a decryption key.
|
||||
///
|
||||
/// This tag has the `METHDO=NONE` attribute.
|
||||
pub const fn new_without_key() -> Self {
|
||||
ExtXKey { key: None }
|
||||
Self(None)
|
||||
}
|
||||
|
||||
/// Returns the decryption key for the following media segments and media initialization sections.
|
||||
pub fn key(&self) -> Option<&DecryptionKey> {
|
||||
self.key.as_ref()
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the protocol compatibility version that this tag requires.
|
||||
pub fn requires_version(&self) -> ProtocolVersion {
|
||||
self.key
|
||||
self.0
|
||||
.as_ref()
|
||||
.map_or(ProtocolVersion::V1, |k| k.requires_version())
|
||||
}
|
||||
|
@ -45,7 +43,7 @@ impl ExtXKey {
|
|||
impl fmt::Display for ExtXKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", Self::PREFIX)?;
|
||||
if let Some(ref key) = self.key {
|
||||
if let Some(ref key) = self.0 {
|
||||
write!(f, "{}", key)?;
|
||||
} else {
|
||||
write!(f, "METHOD=NONE")?;
|
||||
|
@ -60,17 +58,18 @@ impl FromStr for ExtXKey {
|
|||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let input = tag(input, Self::PREFIX)?;
|
||||
|
||||
if AttributePairs::parse(input).any(|a| a.as_ref().ok() == Some(&("METHOD", "NONE"))) {
|
||||
for attr in AttributePairs::parse(input) {
|
||||
let (key, _) = attr?;
|
||||
let pairs = input.parse::<AttributePairs>()?;
|
||||
|
||||
if pairs.iter().any(|(k, v)| k == "METHOD" && v == "NONE") {
|
||||
for (key, _) in pairs {
|
||||
if key == "URI" || key == "IV" || key == "KEYFORMAT" || key == "KEYFORMATVERSIONS" {
|
||||
return Err(Error::invalid_input());
|
||||
}
|
||||
}
|
||||
Ok(ExtXKey { key: None })
|
||||
|
||||
Ok(Self(None))
|
||||
} else {
|
||||
let key = input.parse()?;
|
||||
Ok(ExtXKey { key: Some(key) })
|
||||
Ok(Self(Some(input.parse()?)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,10 +69,9 @@ impl FromStr for ExtXMap {
|
|||
|
||||
let mut uri = None;
|
||||
let mut range = None;
|
||||
let attrs = AttributePairs::parse(input);
|
||||
for attr in attrs {
|
||||
let (key, value) = (attr)?;
|
||||
match key {
|
||||
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"URI" => uri = Some(unquote(value)),
|
||||
"BYTERANGE" => {
|
||||
range = Some((unquote(value).parse())?);
|
||||
|
|
|
@ -71,11 +71,8 @@ impl FromStr for ExtXStart {
|
|||
let mut time_offset = None;
|
||||
let mut precise = false;
|
||||
|
||||
let attrs = AttributePairs::parse(input);
|
||||
|
||||
for attr in attrs {
|
||||
let (key, value) = (attr)?;
|
||||
match key {
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"TIME-OFFSET" => time_offset = Some((value.parse())?),
|
||||
"PRECISE" => precise = (parse_yes_or_no(value))?,
|
||||
_ => {
|
||||
|
|
|
@ -60,10 +60,8 @@ impl FromStr for DecryptionKey {
|
|||
let mut key_format = None;
|
||||
let mut key_format_versions = None;
|
||||
|
||||
let attrs = AttributePairs::parse(input);
|
||||
for attr in attrs {
|
||||
let (key, value) = (attr)?;
|
||||
match key {
|
||||
for (key, value) in input.parse::<AttributePairs>()? {
|
||||
match key.as_str() {
|
||||
"METHOD" => method = Some((value.parse())?),
|
||||
"URI" => uri = Some(unquote(value)),
|
||||
"IV" => iv = Some((value.parse())?),
|
||||
|
|
Loading…
Reference in a new issue