scuffle_flv/video/body/enhanced/
metadata.rs

1//! Types and functions for working with metadata video packets.
2
3use core::fmt;
4
5use scuffle_amf0::{Amf0Object, Amf0Value};
6use scuffle_bytes_util::StringCow;
7use serde::de::{Error, VariantAccess};
8
9/// Color configuration metadata.
10///
11/// > `colorPrimaries`, `transferCharacteristics` and `matrixCoefficients` are defined
12/// > in ISO/IEC 23091-4/ITU-T H.273. The values are an index into
13/// > respective tables which are described in "Colour primaries",
14/// > "Transfer characteristics" and "Matrix coefficients" sections.
15/// > It is RECOMMENDED to provide these values.
16#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct MetadataColorInfoColorConfig {
19    /// Number of bits used to record the color channels for each pixel.
20    ///
21    /// SHOULD be 8, 10 or 12
22    #[serde(default)]
23    pub bit_depth: Option<f64>,
24    /// Indicates the chromaticity coordinates of the source color primaries.
25    ///
26    /// enumeration [0-255]
27    #[serde(default)]
28    pub color_primaries: Option<f64>,
29    /// Opto-electronic transfer characteristic function (e.g., PQ, HLG).
30    ///
31    /// enumeration [0-255]
32    #[serde(default)]
33    pub transfer_characteristics: Option<f64>,
34    /// Matrix coefficients used in deriving luma and chroma signals.
35    ///
36    /// enumeration [0-255]
37    #[serde(default)]
38    pub matrix_coefficients: Option<f64>,
39}
40
41/// HDR content light level metadata.
42#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct MetadataColorInfoHdrCll {
45    /// Maximum value of the frame average light level
46    /// (in 1 cd/m2) of the entire playback sequence.
47    ///
48    /// [0.0001-10000]
49    #[serde(default)]
50    pub max_fall: Option<f64>,
51    /// Maximum light level of any single pixel (in 1 cd/m2)
52    /// of the entire playback sequence.
53    ///
54    /// [0.0001-10000]
55    #[serde(default)]
56    pub max_cll: Option<f64>,
57}
58
59/// HDR mastering display color volume metadata.
60///
61/// > The hdrMdcv object defines mastering display (i.e., where
62/// > creative work is done during the mastering process) color volume (a.k.a., mdcv)
63/// > metadata which describes primaries, white point and min/max luminance. The
64/// > hdrMdcv object SHOULD be provided.
65/// >
66/// > Specification of the metadata along with its ranges adhere to the
67/// > ST 2086:2018 - SMPTE Standard (except for minLuminance see
68/// > comments below)
69///
70/// > Mastering display color volume (mdcv) xy Chromaticity Coordinates within CIE
71/// > 1931 color space.
72///
73/// > Values SHALL be specified with four decimal places. The x coordinate SHALL
74/// > be in the range [0.0001, 0.7400]. The y coordinate SHALL be
75/// > in the range [0.0001, 0.8400].
76#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct MetadataColorInfoHdrMdcv {
79    /// Red x coordinate.
80    #[serde(default)]
81    pub red_x: Option<f64>,
82    /// Red y coordinate.
83    #[serde(default)]
84    pub red_y: Option<f64>,
85    /// Green x coordinate.
86    #[serde(default)]
87    pub green_x: Option<f64>,
88    /// Green y coordinate.
89    #[serde(default)]
90    pub green_y: Option<f64>,
91    /// Blue x coordinate.
92    #[serde(default)]
93    pub blue_x: Option<f64>,
94    /// Blue y coordinate.
95    #[serde(default)]
96    pub blue_y: Option<f64>,
97    /// White point x coordinate.
98    #[serde(default)]
99    pub white_point_x: Option<f64>,
100    /// White point y coordinate.
101    #[serde(default)]
102    pub white_point_y: Option<f64>,
103    /// Max display luminance of the mastering display (in 1 cd/m2 ie. nits).
104    ///
105    /// > note: ST 2086:2018 - SMPTE Standard specifies minimum display mastering
106    /// > luminance in multiples of 0.0001 cd/m2.
107    ///
108    /// > For consistency we specify all values
109    /// > in 1 cd/m2. Given that a hypothetical perfect screen has a peak brightness
110    /// > of 10,000 nits and a black level of .0005 nits we do not need to
111    /// > switch units to 0.0001 cd/m2 to increase resolution on the lower end of the
112    /// > minLuminance property. The ranges (in nits) mentioned below suffice
113    /// > the theoretical limit for Mastering Reference Displays and adhere to the
114    /// > SMPTE ST 2084 standard (a.k.a., PQ) which is capable of representing full gamut
115    /// > of luminance level.
116    #[serde(default)]
117    pub max_luminance: Option<f64>,
118    /// Min display luminance of the mastering display (in 1 cd/m2 ie. nits).
119    ///
120    /// See [`max_luminance`](MetadataColorInfoHdrMdcv::max_luminance) for details.
121    #[serde(default)]
122    pub min_luminance: Option<f64>,
123}
124
125/// Color info metadata.
126///
127/// Defined by:
128/// - Enhanced RTMP spec, page 32-34, Metadata Frame
129#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct MetadataColorInfo {
132    /// Color configuration metadata.
133    #[serde(default)]
134    pub color_config: Option<MetadataColorInfoColorConfig>,
135    /// HDR content light level metadata.
136    #[serde(default)]
137    pub hdr_cll: Option<MetadataColorInfoHdrCll>,
138    /// HDR mastering display color volume metadata.
139    #[serde(default)]
140    pub hdr_mdcv: Option<MetadataColorInfoHdrMdcv>,
141}
142
143/// A single entry in a metadata video packet.
144// It will almost always be ColorInfo, so it's fine that it wastes space when it's the other variant
145#[allow(clippy::large_enum_variant)]
146#[derive(Debug, Clone, PartialEq)]
147pub enum VideoPacketMetadataEntry<'a> {
148    /// Color info metadata.
149    ColorInfo(MetadataColorInfo),
150    /// Any other metadata entry.
151    Other {
152        /// The key of the metadata entry.
153        key: StringCow<'a>,
154        /// The metadata object.
155        object: Amf0Object<'static>,
156    },
157}
158
159impl<'de> serde::Deserialize<'de> for VideoPacketMetadataEntry<'de> {
160    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
161    where
162        D: serde::Deserializer<'de>,
163    {
164        struct Visitor;
165
166        const VIDEO_PACKET_METADATA_ENTRY: &str = "VideoPacketMetadataEntry";
167        const COLOR_INFO: &str = "colorInfo";
168
169        impl<'de> serde::de::Visitor<'de> for Visitor {
170            type Value = VideoPacketMetadataEntry<'de>;
171
172            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
173                formatter.write_str(VIDEO_PACKET_METADATA_ENTRY)
174            }
175
176            fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
177            where
178                A: serde::de::EnumAccess<'de>,
179            {
180                let (key, content): (StringCow<'de>, A::Variant) = data.variant()?;
181                match key.as_ref() {
182                    COLOR_INFO => Ok(VideoPacketMetadataEntry::ColorInfo(content.newtype_variant()?)),
183                    _ => Ok(VideoPacketMetadataEntry::Other {
184                        key,
185                        object: match content.newtype_variant::<Amf0Value>()?.into_owned() {
186                            Amf0Value::Object(object) => object,
187                            _ => return Err(A::Error::custom(format!("expected {VIDEO_PACKET_METADATA_ENTRY} object"))),
188                        },
189                    }),
190                }
191            }
192        }
193
194        deserializer.deserialize_enum(VIDEO_PACKET_METADATA_ENTRY, &[COLOR_INFO], Visitor)
195    }
196}
197
198#[cfg(test)]
199#[cfg_attr(all(test, coverage_nightly), coverage(off))]
200mod tests {
201    use bytes::Bytes;
202    use scuffle_amf0::decoder::Amf0Decoder;
203    use scuffle_amf0::encoder::Amf0Encoder;
204    use scuffle_amf0::{Amf0Object, Amf0Value};
205    use serde::Deserialize;
206
207    use super::VideoPacketMetadataEntry;
208    use crate::video::body::enhanced::metadata::MetadataColorInfo;
209
210    #[test]
211    fn metadata_color_info() {
212        let object: Amf0Object = [
213            (
214                "colorConfig".into(),
215                Amf0Value::Object(
216                    [
217                        ("bitDepth".into(), 10.0.into()),
218                        ("colorPrimaries".into(), 1.0.into()),
219                        ("transferCharacteristics".into(), 1.0.into()),
220                        ("matrixCoefficients".into(), 1.0.into()),
221                    ]
222                    .into_iter()
223                    .collect(),
224                ),
225            ),
226            (
227                "hdrCll".into(),
228                Amf0Value::Object(
229                    [("maxFall".into(), 1000.0.into()), ("maxCll".into(), 1000.0.into())]
230                        .into_iter()
231                        .collect(),
232                ),
233            ),
234            (
235                "hdrMdcv".into(),
236                Amf0Value::Object(
237                    [
238                        ("redX".into(), 0.0.into()),
239                        ("redY".into(), 0.0.into()),
240                        ("greenX".into(), 0.0.into()),
241                        ("greenY".into(), 0.0.into()),
242                        ("blueX".into(), 0.0.into()),
243                        ("blueY".into(), 0.0.into()),
244                        ("whitePointX".into(), 0.0.into()),
245                        ("whitePointY".into(), 0.0.into()),
246                        ("maxLuminance".into(), 0.0.into()),
247                        ("minLuminance".into(), 0.0.into()),
248                    ]
249                    .into_iter()
250                    .collect(),
251                ),
252            ),
253        ]
254        .into_iter()
255        .collect();
256
257        let mut buf = Vec::new();
258        let mut encoder = Amf0Encoder::new(&mut buf);
259        encoder.encode_string("colorInfo").unwrap();
260        encoder.serialize(object).unwrap();
261
262        let mut deserializer = Amf0Decoder::from_buf(Bytes::from(buf));
263        let entry = VideoPacketMetadataEntry::deserialize(&mut deserializer).unwrap();
264
265        assert_eq!(
266            entry,
267            VideoPacketMetadataEntry::ColorInfo(MetadataColorInfo {
268                color_config: Some(super::MetadataColorInfoColorConfig {
269                    bit_depth: Some(10.0),
270                    color_primaries: Some(1.0),
271                    transfer_characteristics: Some(1.0),
272                    matrix_coefficients: Some(1.0),
273                }),
274                hdr_cll: Some(super::MetadataColorInfoHdrCll {
275                    max_fall: Some(1000.0),
276                    max_cll: Some(1000.0),
277                }),
278                hdr_mdcv: Some(super::MetadataColorInfoHdrMdcv {
279                    red_x: Some(0.0),
280                    red_y: Some(0.0),
281                    green_x: Some(0.0),
282                    green_y: Some(0.0),
283                    blue_x: Some(0.0),
284                    blue_y: Some(0.0),
285                    white_point_x: Some(0.0),
286                    white_point_y: Some(0.0),
287                    max_luminance: Some(0.0),
288                    min_luminance: Some(0.0),
289                }),
290            })
291        )
292    }
293}