evOo-local/packages/adapters/src/media-privacy.ts
Claude 8d07ab17d4
feat(media): anonymization, marks, consent, and the render gate
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
2026-10-03 18:57:04 +00:00

40 lines
1.7 KiB
TypeScript

import { rm } from "node:fs/promises";
import path from "node:path";
import { prepareSources } from "./media-anonymize.js";
import type { MediaPrivacyService, MediaServiceDeps } from "./media-context.js";
import { runGate } from "./media-gate.js";
import { createMark, updateMark } from "./media-marks.js";
import type { PrivacyToolOptions } from "./media-privacy-io.js";
import { resolveStored } from "./media-privacy-io.js";
/*
* MediaPrivacyService: anonymization before rendering, the gate after it,
* identifying marks, and deleting a person with their biometric data. Face
* data never leaves this host: matching, tracking, and clustering run against
* the local inference provider and database only.
*/
export function createMediaPrivacyService(
deps: MediaServiceDeps,
tools: PrivacyToolOptions = {},
): MediaPrivacyService {
const withTools = { ...deps, ...tools };
return {
prepareSources: (input) => prepareSources(withTools, input),
gate: (input) => runGate(withTools, input),
createMark: (actor, input) => createMark(withTools, actor, input),
updateMark: (actor, input) => updateMark(withTools, actor, input),
async removePerson(actor, personId) {
const removed = await deps.repos.removePersonCompletely(actor.spaceId, personId);
// Rows are gone; now the files: track keyframes and each mark's directory
// (masks plus previews). A path outside the media root throws untouched.
const markDirs = removed.maskDirs.map((dir) =>
path.basename(dir) === "masks" ? path.dirname(dir) : dir,
);
for (const stored of [...removed.keyframesPaths, ...markDirs]) {
await rm(await resolveStored(deps.mediaRoot, stored), { recursive: true, force: true });
}
},
};
}