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};
8
9#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct MetadataColorInfoColorConfig {
19 #[serde(default)]
23 pub bit_depth: Option<f64>,
24 #[serde(default)]
28 pub color_primaries: Option<f64>,
29 #[serde(default)]
33 pub transfer_characteristics: Option<f64>,
34 #[serde(default)]
38 pub matrix_coefficients: Option<f64>,
39}
40
41#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct MetadataColorInfoHdrCll {
45 #[serde(default)]
50 pub max_fall: Option<f64>,
51 #[serde(default)]
56 pub max_cll: Option<f64>,
57}
58
59#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct MetadataColorInfoHdrMdcv {
79 #[serde(default)]
81 pub red_x: Option<f64>,
82 #[serde(default)]
84 pub red_y: Option<f64>,
85 #[serde(default)]
87 pub green_x: Option<f64>,
88 #[serde(default)]
90 pub green_y: Option<f64>,
91 #[serde(default)]
93 pub blue_x: Option<f64>,
94 #[serde(default)]
96 pub blue_y: Option<f64>,
97 #[serde(default)]
99 pub white_point_x: Option<f64>,
100 #[serde(default)]
102 pub white_point_y: Option<f64>,
103 #[serde(default)]
117 pub max_luminance: Option<f64>,
118 #[serde(default)]
122 pub min_luminance: Option<f64>,
123}
124
125#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct MetadataColorInfo {
132 #[serde(default)]
134 pub color_config: Option<MetadataColorInfoColorConfig>,
135 #[serde(default)]
137 pub hdr_cll: Option<MetadataColorInfoHdrCll>,
138 #[serde(default)]
140 pub hdr_mdcv: Option<MetadataColorInfoHdrMdcv>,
141}
142
143#[allow(clippy::large_enum_variant)]
146#[derive(Debug, Clone, PartialEq)]
147pub enum VideoPacketMetadataEntry<'a> {
148 ColorInfo(MetadataColorInfo),
150 Other {
152 key: StringCow<'a>,
154 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}