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