Review round 2, items 1-10: - 1: §11.3 rewritten in order. The verifier (Rust and Python) requires the receipt's components to be the policy's by id, order and media (policy-component-mismatch, so a zero-component receipt is refused); runs pack-id and §3.6's capability rules under the effective policy; holds each index's timebase, media, string table, class records, every entry's role/capability/layer, and every piece's rules, moments and class plan to the policy (index-policy-mismatch). Seal and verify refuse a receipt with no component (receipt-index-count). - 2: verify reads objects by get(), skips the floor and catalogs once anything has failed; the catalog renderers return Result (a start past 2^53 - 1 is catalog-mismatch); the fragment time uses checked_mul; Python maps encoding, overflow and value errors to refusals. New index rule window-start: an index with no previous window starts at piece 0, entry 0 and time 0 (§4.5), in both. - 3: each floor piece opens on a shown key frame, and holds no other unless extra_keyframes; every sequence header is the first's bytes (both, §11.3). - 4: both readers refuse a member named twice before reading the version; the package's receipt file must be its canonical bytes; Python refuses -0. - 5: a refinement track depends on the tracks carrying what its entries refine (MSF's depends is all-of); never a track the catalog lacks. catalogs/ corpus. - 6: Rust binds a supplied floor's renderer, settings, framing header and target (floor-object-mismatch); SPEC §7.1 says a profile may fix those members. - 7: verify reads the directory strictly (dot-names, *.sha256 included). - 8: Python's manifest parse is the typed one; the object differential is 0. - 9: Python refuses two components of one id (receipt-component). - 10: v2 writes every member, energy_provenance null when unmetered, evidence exactly as its class carries; v1 keeps reading an absent class. Regression tests: tests/review.rs (the review's probes, re-sealed with the key, and a seeded run of random alterations that never panics); mutation corpus receipt cases. Co-Authored-By: Claude Opus 5.5 <[email protected]>
86 lines
3.3 KiB
Rust
86 lines
3.3 KiB
Rust
//! 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> {
|
|
[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);
|
|
}
|
|
}
|