use super::H264Error;
const MB_MODE_BINS: usize = 32;
const CBP_BINS: usize = 48;
const QP_DELTA_BINS: usize = 52;
const NZC_BINS: usize = 17;
const MV_MAG_BINS: usize = 12;
#[derive(Debug, Clone, PartialEq)]
pub struct Fingerprint {
pub mb_mode_hist: [u32; MB_MODE_BINS],
pub cbp_hist: [u32; CBP_BINS],
pub qp_delta_hist: [u32; QP_DELTA_BINS],
pub nzc_hist: [u32; NZC_BINS],
pub mv_mag_hist: [u32; MV_MAG_BINS],
pub mv_zero_ratio: f32,
pub slice_flags: u16,
pub sps_constraints: u8,
pub entropy_mode: u8,
pub gop_interval: u32,
pub ref_list_len: u8,
pub total_mbs: u32,
pub total_slices: u32,
}
impl Default for Fingerprint {
fn default() -> Self {
Self {
mb_mode_hist: [0; MB_MODE_BINS],
cbp_hist: [0; CBP_BINS],
qp_delta_hist: [0; QP_DELTA_BINS],
nzc_hist: [0; NZC_BINS],
mv_mag_hist: [0; MV_MAG_BINS],
mv_zero_ratio: 0.0,
slice_flags: 0,
sps_constraints: 0,
entropy_mode: 0,
gop_interval: 0,
ref_list_len: 0,
total_mbs: 0,
total_slices: 0,
}
}
}
impl Fingerprint {
pub fn to_vec(&self) -> Vec<f32> {
let n = self.total_mbs.max(1) as f32;
let mut v = Vec::with_capacity(
MB_MODE_BINS + CBP_BINS + QP_DELTA_BINS + NZC_BINS + MV_MAG_BINS + 8,
);
v.extend(self.mb_mode_hist.iter().map(|&x| x as f32 / n));
v.extend(self.cbp_hist.iter().map(|&x| x as f32 / n));
v.extend(self.qp_delta_hist.iter().map(|&x| x as f32 / n));
v.extend(self.nzc_hist.iter().map(|&x| x as f32 / n));
v.extend(self.mv_mag_hist.iter().map(|&x| x as f32 / n));
v.push(self.mv_zero_ratio);
v.push(self.slice_flags as f32);
v.push(self.sps_constraints as f32);
v.push(self.entropy_mode as f32);
v.push(self.gop_interval as f32);
v.push(self.ref_list_len as f32);
v.push(self.total_mbs as f32);
v.push(self.total_slices as f32);
v
}
}
pub fn extract(_mp4_bytes: &[u8]) -> Result<Fingerprint, H264Error> {
Err(H264Error::Unsupported(
"fingerprint::extract — Phase 6.0c scaffold, populated by Phase 6A/6B sub-phases"
.into(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fingerprint_to_vec_length_stable() {
let fp = Fingerprint::default();
let v = fp.to_vec();
assert_eq!(
v.len(),
MB_MODE_BINS + CBP_BINS + QP_DELTA_BINS + NZC_BINS + MV_MAG_BINS + 8,
);
}
#[test]
fn fingerprint_default_vec_is_finite() {
let fp = Fingerprint::default();
assert!(fp.to_vec().iter().all(|x| x.is_finite()));
}
#[test]
fn extract_returns_not_yet_impl() {
let result = extract(b"not a real mp4");
assert!(matches!(result, Err(H264Error::Unsupported(_))));
}
}