rtsp_types/headers/
media_properties.rs

1// Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
2//
3// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4
5use super::UtcTime;
6use super::*;
7use std::fmt;
8
9/// `Media-Properties` header ([RFC 7826 section 18.29](https://tools.ietf.org/html/rfc7826#section-18.29)).
10#[derive(Debug, Clone)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct MediaProperties(Vec<MediaProperty>);
13
14/// Media properties.
15#[derive(Debug, Clone, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum MediaProperty {
18    /// Random access access is possible in given duration.
19    RandomAccess(Option<f64>),
20    /// Random access is only possible in the beginning.
21    BeginningOnly,
22    /// Seeking is not possible.
23    NoSeeking,
24    /// Content will not be changed during the lifetime of the RTSP session.
25    Immutable,
26    /// Content might be changed.
27    Dynamic,
28    /// Accessible media range progresses with wallclock.
29    TimeProgressing,
30    /// Content will be available for the whole lifetime of the RTSP session.
31    Unlimited,
32    /// Content will be available at least until the specific wallclock time.
33    TimeLimited(UtcTime),
34    /// Content will be available for the specific duration.
35    TimeDuration(f64),
36    /// Supported scales.
37    Scales(Vec<ScaleRange>),
38    /// Extension.
39    Extension(String, Option<String>),
40}
41
42impl fmt::Display for MediaProperty {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        use fmt::Write;
45
46        match self {
47            MediaProperty::RandomAccess(Some(dur)) => write!(f, "Random-Access={dur}"),
48            MediaProperty::RandomAccess(None) => f.write_str("Random-Access"),
49            MediaProperty::BeginningOnly => f.write_str("Beginning-Only"),
50            MediaProperty::NoSeeking => f.write_str("No-Seeking"),
51            MediaProperty::Immutable => f.write_str("Immutable"),
52            MediaProperty::Dynamic => f.write_str("Dynamic"),
53            MediaProperty::TimeProgressing => f.write_str("Time-Progressing"),
54            MediaProperty::Unlimited => f.write_str("Unlimited"),
55            MediaProperty::TimeLimited(time) => write!(f, "Time-Limited={time}"),
56            MediaProperty::TimeDuration(dur) => write!(f, "Time-Duration={dur}"),
57            MediaProperty::Scales(scales) => {
58                let mut s = String::new();
59                for scale in scales {
60                    if !s.is_empty() {
61                        s.push_str(", ");
62                    }
63                    write!(&mut s, "{scale}").unwrap();
64                }
65                write!(f, "Scales=\"{s}\"")
66            }
67            MediaProperty::Extension(key, Some(value)) => write!(f, "{key}={value}"),
68            MediaProperty::Extension(key, None) => f.write_str(key),
69        }
70    }
71}
72
73/// Scale range.
74#[derive(Debug, Clone, PartialEq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub enum ScaleRange {
77    Scale(f64),
78    Range(f64, f64),
79}
80
81impl fmt::Display for ScaleRange {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            ScaleRange::Scale(scale) => write!(f, "{scale}"),
85            ScaleRange::Range(a, b) => write!(f, "{a}:{b}"),
86        }
87    }
88}
89
90impl std::ops::Deref for MediaProperties {
91    type Target = Vec<MediaProperty>;
92
93    fn deref(&self) -> &Self::Target {
94        &self.0
95    }
96}
97
98impl std::ops::DerefMut for MediaProperties {
99    fn deref_mut(&mut self) -> &mut Self::Target {
100        &mut self.0
101    }
102}
103
104impl AsRef<Vec<MediaProperty>> for MediaProperties {
105    fn as_ref(&self) -> &Vec<MediaProperty> {
106        &self.0
107    }
108}
109
110impl AsMut<Vec<MediaProperty>> for MediaProperties {
111    fn as_mut(&mut self) -> &mut Vec<MediaProperty> {
112        &mut self.0
113    }
114}
115
116impl From<Vec<MediaProperty>> for MediaProperties {
117    fn from(v: Vec<MediaProperty>) -> Self {
118        MediaProperties(v)
119    }
120}
121
122impl<'a> From<&'a [MediaProperty]> for MediaProperties {
123    fn from(v: &'a [MediaProperty]) -> Self {
124        MediaProperties(v.to_vec())
125    }
126}
127
128impl MediaProperties {
129    /// Creates a new `Media-Properties` header builder.
130    pub fn builder() -> MediaPropertiesBuilder {
131        MediaPropertiesBuilder(Vec::new())
132    }
133}
134
135/// Builder for the 'Media-Properties' header.
136#[derive(Debug, Clone)]
137pub struct MediaPropertiesBuilder(Vec<MediaProperty>);
138
139impl MediaPropertiesBuilder {
140    /// Add the provided media property to the `Media-Properties` header.
141    pub fn property(mut self, property: MediaProperty) -> Self {
142        self.0.push(property);
143        self
144    }
145
146    /// Build the `Media-Properties` header.
147    pub fn build(self) -> MediaProperties {
148        MediaProperties(self.0)
149    }
150}
151
152pub(super) mod parser {
153    use super::*;
154
155    use super::parser_helpers::{
156        cond_parser, quoted_string, rtsp_unreserved, split_once, token, trim,
157    };
158    use nom::branch::alt;
159    use nom::bytes::complete::tag;
160    use nom::combinator::{all_consuming, map_res};
161    use nom::multi::separated_list1;
162    use nom::sequence::tuple;
163    use nom::{Err, IResult};
164    use std::str;
165
166    fn param(input: &[u8]) -> IResult<&[u8], (&str, Option<&str>)> {
167        if input.is_empty() {
168            return Err(Err::Error(nom::error::Error::new(
169                input,
170                nom::error::ErrorKind::Eof,
171            )));
172        }
173
174        tuple((
175            trim(map_res(token, str::from_utf8)),
176            cond_parser(
177                tag(b"="),
178                trim(map_res(
179                    alt((quoted_string, rtsp_unreserved)),
180                    str::from_utf8,
181                )),
182            ),
183        ))(input)
184    }
185
186    fn media_property(input: &[u8]) -> IResult<&[u8], MediaProperty> {
187        map_res(param, |p| -> Result<_, HeaderParseError> {
188            dbg!(&p);
189            match p {
190                ("Random-Access", None) => Ok(MediaProperty::RandomAccess(None)),
191                ("Random-Access", Some(dur)) => {
192                    let dur = dur.parse().map_err(|_| HeaderParseError)?;
193                    Ok(MediaProperty::RandomAccess(Some(dur)))
194                }
195                ("Beginning-Only", None) => Ok(MediaProperty::BeginningOnly),
196                ("No-Seeking", None) => Ok(MediaProperty::NoSeeking),
197                ("Immutable", None) => Ok(MediaProperty::Immutable),
198                ("Dynamic", None) => Ok(MediaProperty::Dynamic),
199                ("Time-Progressing", None) => Ok(MediaProperty::TimeProgressing),
200                ("Unlimited", None) => Ok(MediaProperty::Unlimited),
201                ("Time-Limited", Some(time)) => {
202                    let time = time.parse().map_err(|_| HeaderParseError)?;
203                    Ok(MediaProperty::TimeLimited(time))
204                }
205                ("Time-Duration", Some(dur)) => {
206                    let dur = dur.parse().map_err(|_| HeaderParseError)?;
207                    Ok(MediaProperty::TimeDuration(dur))
208                }
209                ("Scales", Some(scales)) => {
210                    if !scales.starts_with('"') || !scales.ends_with('"') {
211                        return Err(HeaderParseError);
212                    }
213
214                    let mut s = Vec::new();
215                    for scale in scales[1..(scales.len() - 1)].split(',') {
216                        let scale = scale.trim();
217                        if let Some((a, b)) = split_once(scale, ':') {
218                            let a = a.parse().map_err(|_| HeaderParseError)?;
219                            let b = b.parse().map_err(|_| HeaderParseError)?;
220                            s.push(ScaleRange::Range(a, b));
221                        } else {
222                            let a = scale.parse().map_err(|_| HeaderParseError)?;
223                            s.push(ScaleRange::Scale(a));
224                        }
225                    }
226
227                    Ok(MediaProperty::Scales(s))
228                }
229                (key, value) => Ok(MediaProperty::Extension(
230                    key.into(),
231                    value.map(String::from),
232                )),
233            }
234        })(input)
235    }
236
237    pub(crate) fn media_properties(input: &[u8]) -> IResult<&[u8], Vec<MediaProperty>> {
238        all_consuming(separated_list1(tag(","), media_property))(input)
239    }
240}
241
242impl super::TypedHeader for MediaProperties {
243    fn from_headers(headers: impl AsRef<Headers>) -> Result<Option<Self>, HeaderParseError> {
244        let headers = headers.as_ref();
245
246        let header = match headers.get(&MEDIA_PROPERTIES) {
247            None => return Ok(None),
248            Some(header) => header,
249        };
250
251        let (_rem, properties) =
252            parser::media_properties(header.as_str().as_bytes()).map_err(|_| HeaderParseError)?;
253
254        Ok(Some(properties.into()))
255    }
256
257    fn insert_into(&self, mut headers: impl AsMut<Headers>) {
258        let headers = headers.as_mut();
259
260        let mut properties = String::new();
261        for property in &self.0 {
262            if !properties.is_empty() {
263                properties.push_str(", ");
264            }
265
266            properties.push_str(&property.to_string());
267        }
268
269        headers.insert(MEDIA_PROPERTIES, properties);
270    }
271}
272
273impl super::TypedAppendableHeader for MediaProperties {
274    fn append_to(&self, mut headers: impl AsMut<Headers>) {
275        let headers = headers.as_mut();
276
277        let mut properties = String::new();
278        for property in &self.0 {
279            if !properties.is_empty() {
280                properties.push_str(", ");
281            }
282
283            properties.push_str(&property.to_string());
284        }
285
286        headers.append(MEDIA_PROPERTIES, properties);
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[test]
295    fn test_media_properties() {
296        let header = "Random-Access=2.5, Unlimited, Immutable, Scales=\"-20, -10, -4, 0.5:1.5, 4, 8, 10, 15, 20\"";
297        let response = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
298            .header(crate::headers::MEDIA_PROPERTIES, header)
299            .empty();
300
301        let props = response
302            .typed_header::<super::MediaProperties>()
303            .unwrap()
304            .unwrap();
305
306        assert_eq!(
307            &*props,
308            &[
309                MediaProperty::RandomAccess(Some(2.5)),
310                MediaProperty::Unlimited,
311                MediaProperty::Immutable,
312                MediaProperty::Scales(vec![
313                    ScaleRange::Scale(-20.0),
314                    ScaleRange::Scale(-10.0),
315                    ScaleRange::Scale(-4.0),
316                    ScaleRange::Range(0.5, 1.5),
317                    ScaleRange::Scale(4.0),
318                    ScaleRange::Scale(8.0),
319                    ScaleRange::Scale(10.0),
320                    ScaleRange::Scale(15.0),
321                    ScaleRange::Scale(20.0),
322                ]),
323            ]
324        );
325
326        let response2 = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
327            .typed_header(&props)
328            .empty();
329        assert_eq!(response, response2);
330    }
331}