//! Piece classification ([packaging] §4.6), from the statistics the segmenter //! gathers. Precedence: static, then synthetic, then high motion, then natural. //! //! - `mad_milli = abs_diff · 1000 / compared`, 0 for a one-frame piece: the mean //! absolute luma change between consecutive frames, in thousandths. //! - `changed_permille = changed · 1000 / compared`, 0 for a one-frame piece. //! - static: `mad_milli ≤ static_mad_milli` and `changed_permille ≤ static_permille`; //! - synthetic: the first frame's histogram has at most `synthetic_bins` non-empty bins; //! - high motion: `mad_milli ≥ motion_high_milli`. //! //! [packaging]: ../../../extensions/packaging.md use crate::policy::ClassifyParams; use crate::scenecut::PieceStats; /// A piece's kind. The discriminant is the stage index's `kind` code. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PieceKind { Natural = 0, Static = 1, Synthetic = 2, MotionHigh = 3, } impl PieceKind { /// The kind a stage index's `kind` code names, of the four a video piece has. pub fn from_code(c: u8) -> Option { [PieceKind::Natural, PieceKind::Static, PieceKind::Synthetic, PieceKind::MotionHigh].get(c as usize).copied() } /// The policy's spelling (`rule.when.piece_kind`). pub fn label(self) -> &'static str { match self { PieceKind::Natural => "natural", PieceKind::Static => "static", PieceKind::Synthetic => "synthetic", PieceKind::MotionHigh => "motion_high", } } } /// `mad_milli` of a piece. pub fn mad_milli(s: &PieceStats) -> u64 { (s.abs_diff * 1000).checked_div(s.compared).unwrap_or(0) } /// `changed_permille` of a piece. pub fn changed_permille(s: &PieceStats) -> u64 { (s.changed * 1000).checked_div(s.compared).unwrap_or(0) } /// The kind of a piece. pub fn classify(s: &PieceStats, p: &ClassifyParams) -> PieceKind { let mad = mad_milli(s); if mad <= p.static_mad_milli && changed_permille(s) <= p.static_permille { PieceKind::Static } else if s.occupancy as u64 <= p.synthetic_bins { PieceKind::Synthetic } else if mad >= p.motion_high_milli { PieceKind::MotionHigh } else { PieceKind::Natural } } #[cfg(test)] mod tests { use super::*; const P: ClassifyParams = ClassifyParams { static_permille: 5, static_mad_milli: 300, synthetic_bins: 12, motion_high_milli: 9000 }; #[test] fn precedence_and_thresholds() { let st = |abs_diff, changed, occupancy| PieceStats { abs_diff, changed, compared: 1000, occupancy }; assert_eq!(classify(&st(300, 5, 40), &P), PieceKind::Static); // Static and synthetic both hold: static comes first. assert_eq!(classify(&st(0, 0, 3), &P), PieceKind::Static); assert_eq!(classify(&st(301, 5, 40), &P), PieceKind::Natural); assert_eq!(classify(&st(300, 6, 12), &P), PieceKind::Synthetic); assert_eq!(classify(&st(9000, 900, 12), &P), PieceKind::Synthetic); assert_eq!(classify(&st(9000, 900, 13), &P), PieceKind::MotionHigh); assert_eq!(classify(&st(8999, 900, 13), &P), PieceKind::Natural); // A one-frame piece compares nothing: static. assert_eq!(classify(&PieceStats { occupancy: 40, ..PieceStats::default() }, &P), PieceKind::Static); } }