scuffle_flv/video/body/enhanced/
metadata.rs1use core::fmt;
4
5use scuffle_amf0::{Amf0Object, Amf0Value};
6use scuffle_bytes_util::StringCow;
7use serde::de::{Error, VariantAccess};
8use serde_derive::Deserialize;
9
10#[derive(Debug, Clone, PartialEq, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct MetadataColorInfoColorConfig {
20 #[serde(default)]
24 pub bit_depth: Option<f64>,
25 #[serde(default)]
29 pub color_primaries: Option<f64>,
30 #[serde(default)]
34 pub transfer_characteristics: Option<f64>,
35 #[serde(default)]
39 pub matrix_coefficients: Option<f64>,
40}
41
42#[derive(Debug, Clone, PartialEq, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub struct MetadataColorInfoHdrCll {
46 #[serde(default)]
51 pub max_fall: Option<f64>,
52 #[serde(default)]
57 pub max_cll: Option<f64>,
58}
59
60#[derive(Debug, Clone, PartialEq, Deserialize)]
78#[serde(rename_all = "camelCase")]
79pub struct MetadataColorInfoHdrMdcv {
80 #[serde(default)]
82 pub red_x: Option<f64>,
83 #[serde(default)]
85 pub red_y: Option<f64>,
86 #[serde(default)]
88 pub green_x: Option<f64>,
89 #[serde(default)]
91 pub green_y: Option<f64>,
92 #[serde(default)]
94 pub blue_x: Option<f64>,
95 #[serde(default)]
97 pub blue_y: Option<f64>,
98 #[serde(default)]
100 pub white_point_x: Option<f64>,
101 #[serde(default)]
103 pub white_point_y: Option<f64>,
104 #[serde(default)]
118 pub max_luminance: Option<f64>,
119 #[serde(default)]
123 pub min_luminance: Option<f64>,
124}
125
126#[derive(Debug, Clone, PartialEq, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct MetadataColorInfo {
133 #[serde(default)]
135 pub color_config: Option<MetadataColorInfoColorConfig>,
136 #[serde(default)]
138 pub hdr_cll: Option<MetadataColorInfoHdrCll>,
139 #[serde(default)]
141 pub hdr_mdcv: Option<MetadataColorInfoHdrMdcv>,
142}
143
144#[allow(clippy::large_enum_variant)]
147#[derive(Debug, Clone, PartialEq)]
148pub enum VideoPacketMetadataEntry<'a> {
149 ColorInfo(MetadataColorInfo),
151 Other {
153 key: StringCow<'a>,
155 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}