use core::fmt;
use scuffle_amf0::{Amf0Object, Amf0Value};
use scuffle_bytes_util::StringCow;
use serde::de::{Error, VariantAccess};
use serde_derive::Deserialize;
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataColorInfoColorConfig {
#[serde(default)]
pub bit_depth: Option<f64>,
#[serde(default)]
pub color_primaries: Option<f64>,
#[serde(default)]
pub transfer_characteristics: Option<f64>,
#[serde(default)]
pub matrix_coefficients: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataColorInfoHdrCll {
#[serde(default)]
pub max_fall: Option<f64>,
#[serde(default)]
pub max_cll: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataColorInfoHdrMdcv {
#[serde(default)]
pub red_x: Option<f64>,
#[serde(default)]
pub red_y: Option<f64>,
#[serde(default)]
pub green_x: Option<f64>,
#[serde(default)]
pub green_y: Option<f64>,
#[serde(default)]
pub blue_x: Option<f64>,
#[serde(default)]
pub blue_y: Option<f64>,
#[serde(default)]
pub white_point_x: Option<f64>,
#[serde(default)]
pub white_point_y: Option<f64>,
#[serde(default)]
pub max_luminance: Option<f64>,
#[serde(default)]
pub min_luminance: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataColorInfo {
#[serde(default)]
pub color_config: Option<MetadataColorInfoColorConfig>,
#[serde(default)]
pub hdr_cll: Option<MetadataColorInfoHdrCll>,
#[serde(default)]
pub hdr_mdcv: Option<MetadataColorInfoHdrMdcv>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq)]
pub enum VideoPacketMetadataEntry<'a> {
ColorInfo(MetadataColorInfo),
Other {
key: StringCow<'a>,
object: Amf0Object<'static>,
},
}
impl<'de> serde::Deserialize<'de> for VideoPacketMetadataEntry<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
const VIDEO_PACKET_METADATA_ENTRY: &str = "VideoPacketMetadataEntry";
const COLOR_INFO: &str = "colorInfo";
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = VideoPacketMetadataEntry<'de>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(VIDEO_PACKET_METADATA_ENTRY)
}
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: serde::de::EnumAccess<'de>,
{
let (key, content): (StringCow<'de>, A::Variant) = data.variant()?;
match key.as_ref() {
COLOR_INFO => Ok(VideoPacketMetadataEntry::ColorInfo(content.newtype_variant()?)),
_ => Ok(VideoPacketMetadataEntry::Other {
key,
object: match content.newtype_variant::<Amf0Value>()?.into_owned() {
Amf0Value::Object(object) => object,
_ => return Err(A::Error::custom(format!("expected {VIDEO_PACKET_METADATA_ENTRY} object"))),
},
}),
}
}
}
deserializer.deserialize_enum(VIDEO_PACKET_METADATA_ENTRY, &[COLOR_INFO], Visitor)
}
}
#[cfg(test)]
#[cfg_attr(all(test, coverage_nightly), coverage(off))]
mod tests {
use bytes::Bytes;
use scuffle_amf0::decoder::Amf0Decoder;
use scuffle_amf0::encoder::Amf0Encoder;
use scuffle_amf0::{Amf0Object, Amf0Value};
use serde::Deserialize;
use super::VideoPacketMetadataEntry;
use crate::video::body::enhanced::metadata::MetadataColorInfo;
#[test]
fn metadata_color_info() {
let object: Amf0Object = [
(
"colorConfig".into(),
Amf0Value::Object(
[
("bitDepth".into(), 10.0.into()),
("colorPrimaries".into(), 1.0.into()),
("transferCharacteristics".into(), 1.0.into()),
("matrixCoefficients".into(), 1.0.into()),
]
.into_iter()
.collect(),
),
),
(
"hdrCll".into(),
Amf0Value::Object(
[("maxFall".into(), 1000.0.into()), ("maxCll".into(), 1000.0.into())]
.into_iter()
.collect(),
),
),
(
"hdrMdcv".into(),
Amf0Value::Object(
[
("redX".into(), 0.0.into()),
("redY".into(), 0.0.into()),
("greenX".into(), 0.0.into()),
("greenY".into(), 0.0.into()),
("blueX".into(), 0.0.into()),
("blueY".into(), 0.0.into()),
("whitePointX".into(), 0.0.into()),
("whitePointY".into(), 0.0.into()),
("maxLuminance".into(), 0.0.into()),
("minLuminance".into(), 0.0.into()),
]
.into_iter()
.collect(),
),
),
]
.into_iter()
.collect();
let mut buf = Vec::new();
let mut encoder = Amf0Encoder::new(&mut buf);
encoder.encode_string("colorInfo").unwrap();
encoder.serialize(object).unwrap();
let mut deserializer = Amf0Decoder::from_buf(Bytes::from(buf));
let entry = VideoPacketMetadataEntry::deserialize(&mut deserializer).unwrap();
assert_eq!(
entry,
VideoPacketMetadataEntry::ColorInfo(MetadataColorInfo {
color_config: Some(super::MetadataColorInfoColorConfig {
bit_depth: Some(10.0),
color_primaries: Some(1.0),
transfer_characteristics: Some(1.0),
matrix_coefficients: Some(1.0),
}),
hdr_cll: Some(super::MetadataColorInfoHdrCll {
max_fall: Some(1000.0),
max_cll: Some(1000.0),
}),
hdr_mdcv: Some(super::MetadataColorInfoHdrMdcv {
red_x: Some(0.0),
red_y: Some(0.0),
green_x: Some(0.0),
green_y: Some(0.0),
blue_x: Some(0.0),
blue_y: Some(0.0),
white_point_x: Some(0.0),
white_point_y: Some(0.0),
max_luminance: Some(0.0),
min_luminance: Some(0.0),
}),
})
)
}
}