2026-05-10 03:06:54 +07:00
|
|
|
export type FaceBox = {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
gender?: "male" | "female";
|
|
|
|
|
genderScore?: number;
|
|
|
|
|
score?: number;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
export type FaceDetectionResult = {
|
|
|
|
|
faces: FaceBox[];
|
|
|
|
|
naturalWidth: number;
|
|
|
|
|
naturalHeight: number;
|
|
|
|
|
};
|
2026-05-10 03:06:54 +07:00
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
type FaceDetectionWorkerRequest = {
|
|
|
|
|
id: number;
|
|
|
|
|
sourceUri: string;
|
|
|
|
|
};
|
2026-05-10 03:06:54 +07:00
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
type FaceDetectionWorkerResponse = {
|
|
|
|
|
id: number;
|
|
|
|
|
result?: FaceDetectionResult;
|
|
|
|
|
error?: string;
|
|
|
|
|
};
|
2026-05-10 03:06:54 +07:00
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
let faceDetectionWorker: Worker | null = null;
|
|
|
|
|
let faceDetectionWorkerDisabled = false;
|
|
|
|
|
let workerRequestId = 0;
|
|
|
|
|
const workerRequests = new Map<
|
|
|
|
|
number,
|
|
|
|
|
{
|
|
|
|
|
resolve: (result: FaceDetectionResult) => void;
|
|
|
|
|
reject: (error: Error) => void;
|
|
|
|
|
}
|
|
|
|
|
>();
|
2026-05-10 03:06:54 +07:00
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
export async function detectFaceBoxes(
|
|
|
|
|
sourceUri: string,
|
|
|
|
|
): Promise<FaceDetectionResult> {
|
|
|
|
|
if (typeof Worker === "undefined" || faceDetectionWorkerDisabled) {
|
|
|
|
|
throw new Error("Face detection worker is unavailable");
|
2026-05-10 03:06:54 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
try {
|
|
|
|
|
return await detectFaceBoxesInWorker(sourceUri);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
faceDetectionWorkerDisabled = true;
|
|
|
|
|
faceDetectionWorker?.terminate();
|
|
|
|
|
faceDetectionWorker = null;
|
|
|
|
|
console.warn("[face-detection] worker failed:", err);
|
|
|
|
|
throw err;
|
2026-05-10 03:06:54 +07:00
|
|
|
}
|
2026-05-16 11:51:44 +07:00
|
|
|
}
|
2026-05-10 03:06:54 +07:00
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
function detectFaceBoxesInWorker(
|
|
|
|
|
sourceUri: string,
|
|
|
|
|
): Promise<FaceDetectionResult> {
|
|
|
|
|
const worker = getFaceDetectionWorker();
|
|
|
|
|
const id = ++workerRequestId;
|
2026-05-10 03:06:54 +07:00
|
|
|
|
2026-05-16 11:51:44 +07:00
|
|
|
return new Promise<FaceDetectionResult>((resolve, reject) => {
|
|
|
|
|
workerRequests.set(id, { resolve, reject });
|
|
|
|
|
worker.postMessage({ id, sourceUri } satisfies FaceDetectionWorkerRequest);
|
2026-05-10 03:06:54 +07:00
|
|
|
});
|
2026-05-10 12:11:58 +07:00
|
|
|
}
|
2026-05-16 11:51:44 +07:00
|
|
|
|
|
|
|
|
function getFaceDetectionWorker(): Worker {
|
|
|
|
|
if (faceDetectionWorker) return faceDetectionWorker;
|
|
|
|
|
|
|
|
|
|
faceDetectionWorker = new Worker(
|
|
|
|
|
new URL("./face-detection-worker.ts", import.meta.url),
|
|
|
|
|
{ type: "module" },
|
|
|
|
|
);
|
|
|
|
|
faceDetectionWorker.onmessage = (
|
|
|
|
|
event: MessageEvent<FaceDetectionWorkerResponse>,
|
|
|
|
|
) => {
|
|
|
|
|
const request = workerRequests.get(event.data.id);
|
|
|
|
|
if (!request) return;
|
|
|
|
|
workerRequests.delete(event.data.id);
|
|
|
|
|
if (event.data.error) {
|
|
|
|
|
faceDetectionWorkerDisabled = true;
|
|
|
|
|
faceDetectionWorker?.terminate();
|
|
|
|
|
faceDetectionWorker = null;
|
|
|
|
|
request.reject(new Error(event.data.error));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!event.data.result) {
|
|
|
|
|
request.reject(new Error("Face detection worker returned no result"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
request.resolve(event.data.result);
|
|
|
|
|
};
|
|
|
|
|
faceDetectionWorker.onerror = (event) => {
|
|
|
|
|
const error = new Error(event.message || "Face detection worker failed");
|
|
|
|
|
faceDetectionWorkerDisabled = true;
|
|
|
|
|
workerRequests.forEach((request) => request.reject(error));
|
|
|
|
|
workerRequests.clear();
|
|
|
|
|
faceDetectionWorker?.terminate();
|
|
|
|
|
faceDetectionWorker = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return faceDetectionWorker;
|
|
|
|
|
}
|