prepareSources applies every face, mark, and region protection before the compiler runs and fails closed when one cannot be honored: all face modes, tracking with interpolation and grown holds over lost detections, reframe_out crops with blur fallback, synthetic identity only through a licensed provider (blur fallback otherwise), inpainting with a quality floor that falls back to a cover, and person-bound marks re-found next to the face. The gate runs on the render output: classifier at the profile rate plus cuts, face tracking matched to people locally, pixel verification of every recorded protection, consent and age proof (always for paid video), text and technical checks. Auto-fix proposes only compliance repairs and stops after three rounds. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NLSCTnhge4umMnemGXWr1w
105 lines
3.8 KiB
TypeScript
105 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { encodeMaskPng } from "./media-emulator.js";
|
|
import type { LumaFrame } from "./media-privacy-pixels.js";
|
|
import {
|
|
controlAgreement,
|
|
decodePngLuma,
|
|
maskBounds,
|
|
residualVisibility,
|
|
temporalStability,
|
|
} from "./media-privacy-pixels.js";
|
|
|
|
/** A 64x64 checkerboard of 4 px squares: detail everywhere. */
|
|
function checker(width = 64, height = 64, shift = 0): LumaFrame {
|
|
const luma = new Uint8Array(width * height);
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
luma[y * width + x] = (Math.floor((x + shift) / 4) + Math.floor(y / 4)) % 2 ? 220 : 30;
|
|
}
|
|
}
|
|
return { width, height, luma };
|
|
}
|
|
|
|
function fill(
|
|
frame: LumaFrame,
|
|
box: { x: number; y: number; w: number; h: number },
|
|
value: number,
|
|
) {
|
|
const out = { ...frame, luma: frame.luma.slice() };
|
|
for (
|
|
let y = Math.floor(box.y * frame.height);
|
|
y < Math.ceil((box.y + box.h) * frame.height);
|
|
y++
|
|
) {
|
|
for (
|
|
let x = Math.floor(box.x * frame.width);
|
|
x < Math.ceil((box.x + box.w) * frame.width);
|
|
x++
|
|
) {
|
|
out.luma[y * frame.width + x] = value;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const region = { x: 0.25, y: 0.25, w: 0.5, h: 0.5 };
|
|
|
|
describe("PNG masks", () => {
|
|
it("decodes the masks the segmenter writes", () => {
|
|
const mask = decodePngLuma(encodeMaskPng(40, 20, { x: 0.5, y: 0.25, w: 0.25, h: 0.5 }));
|
|
expect(mask).toMatchObject({ width: 40, height: 20 });
|
|
expect(maskBounds(mask)).toEqual({ x: 0.5, y: 0.25, w: 0.25, h: 0.5 });
|
|
expect(maskBounds(decodePngLuma(encodeMaskPng(8, 8, { x: 0, y: 0, w: 0, h: 0 })))).toBeNull();
|
|
expect(() => decodePngLuma(Buffer.from("nope"))).toThrow("not a PNG");
|
|
});
|
|
});
|
|
|
|
describe("residual visibility", () => {
|
|
it("is 1 for untouched detail and 0 for a filled or flat region", () => {
|
|
const source = checker();
|
|
expect(residualVisibility({ source, treated: source, box: region })).toBe(1);
|
|
expect(residualVisibility({ source, treated: fill(source, region, 128), box: region })).toBe(0);
|
|
const flat = fill(source, region, 90);
|
|
expect(
|
|
residualVisibility({ source: flat, treated: flat, box: { x: 0.3, y: 0.3, w: 0.4, h: 0.4 } }),
|
|
).toBe(0);
|
|
});
|
|
|
|
it("counts only the part a crop still shows and only mask pixels", () => {
|
|
const source = checker();
|
|
// The treated frame is the right half of the source, untouched.
|
|
const half: LumaFrame = { width: 32, height: 64, luma: new Uint8Array(32 * 64) };
|
|
for (let y = 0; y < 64; y++)
|
|
half.luma.set(source.luma.subarray(y * 64 + 32, y * 64 + 64), y * 32);
|
|
const left = { x: 0, y: 0.25, w: 0.4, h: 0.5 };
|
|
expect(residualVisibility({ source, treated: half, box: left, offset: { x: 32, y: 0 } })).toBe(
|
|
0,
|
|
);
|
|
expect(
|
|
residualVisibility({ source, treated: half, box: region, offset: { x: 32, y: 0 } }),
|
|
).toBe(1);
|
|
const mask = decodePngLuma(encodeMaskPng(64, 64, { x: 0.5, y: 0.25, w: 0.25, h: 0.5 }));
|
|
const treated = fill(source, { x: 0.25, y: 0.25, w: 0.25, h: 0.5 }, 128);
|
|
expect(residualVisibility({ source, treated, box: region, mask })).toBe(1);
|
|
});
|
|
|
|
it("detects frames that are not the same moment", () => {
|
|
const source = checker();
|
|
const treated = fill(source, region, 128);
|
|
expect(controlAgreement({ source, treated, boxes: [region] })).toBe(1);
|
|
expect(controlAgreement({ source, treated: checker(64, 64, 2), boxes: [region] })).toBeLessThan(
|
|
0.8,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("temporal stability", () => {
|
|
it("drops when the treated region flickers more than the frame", () => {
|
|
const a = checker();
|
|
expect(temporalStability([a, a, a], region)).toBe(1);
|
|
const flicker = [fill(a, region, 0), fill(a, region, 255), fill(a, region, 0)];
|
|
expect(temporalStability(flicker, region)).toBe(0);
|
|
expect(temporalStability([a], region)).toBe(1);
|
|
});
|
|
});
|