use alpha2_media::ColorMetadata;
use alpha2_time::FrameRate;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct MediaFingerprint {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_sha256: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub byte_length: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub modified_ms: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub streams: Option<StreamTopology>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duration_ticks: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<ColorMetadata>,
}
impl MediaFingerprint {
#[must_use]
pub fn is_valid(&self) -> bool {
self.malformed_part().is_none()
}
#[must_use]
pub fn malformed_part(&self) -> Option<&'static str> {
if self
.content_sha256
.as_deref()
.is_some_and(|hash| !is_sha256_hex(hash))
{
Some("content_sha256")
} else if self.duration_ticks.is_some_and(|ticks| ticks < 0) {
Some("duration_ticks")
} else if self
.streams
.as_ref()
.is_some_and(|streams| !streams.is_valid())
{
Some("streams")
} else if self.color.is_some_and(|color| !color.is_consistent()) {
Some("color")
} else {
None
}
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct StreamTopology {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video: Option<VideoStreamShape>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audio: Option<AudioStreamShape>,
}
impl StreamTopology {
fn is_valid(&self) -> bool {
self.video
.as_ref()
.is_none_or(|video| video.width > 0 && video.height > 0)
&& self
.audio
.as_ref()
.is_none_or(|audio| audio.channels > 0 && audio.sample_rate > 0)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct VideoStreamShape {
pub width: u32,
pub height: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub frame_rate: Option<FrameRate>,
}
impl VideoStreamShape {
#[must_use]
pub const fn new(width: u32, height: u32, frame_rate: Option<FrameRate>) -> Self {
Self {
width,
height,
frame_rate,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct AudioStreamShape {
pub channels: u32,
pub sample_rate: u32,
}
impl AudioStreamShape {
#[must_use]
pub const fn new(channels: u32, sample_rate: u32) -> Self {
Self {
channels,
sample_rate,
}
}
}
fn is_sha256_hex(hash: &str) -> bool {
hash.len() == 64
&& hash
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}