feat(smart-crop): overhaul with face detection, social presets, and 3 modes

Replace the confusing 2-mode smart crop with a clear 3-mode system:
- Subject Focus: Sharp attention/entropy saliency crop with social media presets
- Face Focus: MediaPipe face detection with headshot framing presets
- Auto Trim: Border removal with optional pad-to-square

Adds detectFaces() to AI package, face preset constants, backward
compatibility for old mode names, and comprehensive integration tests.
This commit is contained in:
Siddharth Kumar Sah
2026-04-13 00:47:53 +08:00
parent 29fafd0722
commit 92d4d2d9c6
9 changed files with 798 additions and 277 deletions
+40 -1
View File
@@ -1,4 +1,5 @@
import { readFile, writeFile } from "node:fs/promises";
import { readFile, unlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { type ProgressCallback, runPythonWithProgress } from "./bridge.js";
@@ -7,6 +8,10 @@ export interface BlurFacesOptions {
sensitivity?: number;
}
export interface DetectFacesOptions {
sensitivity?: number;
}
export interface FaceRegion {
x: number;
y: number;
@@ -20,6 +25,11 @@ export interface BlurFacesResult {
faces: FaceRegion[];
}
export interface DetectFacesResult {
facesDetected: number;
faces: FaceRegion[];
}
export async function blurFaces(
inputBuffer: Buffer,
outputDir: string,
@@ -48,3 +58,32 @@ export async function blurFaces(
faces: result.faces ?? [],
};
}
export async function detectFaces(
inputBuffer: Buffer,
options: DetectFacesOptions = {},
onProgress?: ProgressCallback,
): Promise<DetectFacesResult> {
const inputPath = join(tmpdir(), `detect_faces_${Date.now()}.png`);
try {
await writeFile(inputPath, inputBuffer);
const { stdout } = await runPythonWithProgress(
"detect_faces.py",
[inputPath, "unused", JSON.stringify({ ...options, detectOnly: true })],
{ onProgress },
);
const result = JSON.parse(stdout);
if (!result.success) {
throw new Error(result.error || "Face detection failed");
}
return {
facesDetected: result.facesDetected,
faces: result.faces ?? [],
};
} finally {
await unlink(inputPath).catch(() => {});
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
export { removeBackground } from "./background-removal.js";
export { isGpuAvailable, shutdownDispatcher } from "./bridge.js";
export { blurFaces } from "./face-detection.js";
export type { DetectFacesResult, FaceRegion } from "./face-detection.js";
export { blurFaces, detectFaces } from "./face-detection.js";
export { inpaint } from "./inpainting.js";
export { extractText } from "./ocr.js";
export { seamCarve } from "./seam-carving.js";