feat: 完成短剧项目空间与结构化资产创作闭环 (#38)
* feat(canvas): 短剧项目空间 - 建立项目章节与画布关联 MVP * feat(canvas): 短剧项目空间 - 完善项目资产、镜头与流程闭环 Refs #30 * fix(deploy): 本地部署 - 修复前端构建并忽略本地 PostgreSQL 配置 * feat(*): 短剧项目空间 - 完成项目、章节、资产、角色与画布创作闭环 Refs #30 --------- Co-authored-by: ddCat <ddcat666@88.com>
This commit is contained in:
1 parent
431b9e3294
commit
d2fbc413b3
208 files changed
+22655
-4234
No files matched your search
@@ -0,0 +1,79 @@
|
||||
import type { CanvasFaceBox } from "./canvas-emotion";
|
||||
|
||||
type DetectFaceResponse = {
|
||||
id: number;
|
||||
faces?: CanvasFaceBox[];
|
||||
imageWidth?: number;
|
||||
imageHeight?: number;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
type PendingRequest = {
|
||||
resolve: (result: CanvasFaceDetectionResult) => void;
|
||||
reject: (error: Error) => void;
|
||||
cleanup: () => void;
|
||||
};
|
||||
|
||||
export type CanvasFaceDetectionResult = {
|
||||
faces: CanvasFaceBox[];
|
||||
imageWidth: number;
|
||||
imageHeight: number;
|
||||
};
|
||||
|
||||
let detectorWorker: Worker | null = null;
|
||||
let requestSequence = 0;
|
||||
const pendingRequests = new Map<number, PendingRequest>();
|
||||
|
||||
export async function detectCanvasFaces(dataUrl: string, signal?: AbortSignal): Promise<CanvasFaceDetectionResult> {
|
||||
if (signal?.aborted) throw new DOMException("人脸识别已取消", "AbortError");
|
||||
const response = await fetch(dataUrl, { signal });
|
||||
if (!response.ok) throw new Error("无法读取源图片,请重新上传后再试");
|
||||
const image = await createImageBitmap(await response.blob());
|
||||
const worker = getDetectorWorker();
|
||||
const id = ++requestSequence;
|
||||
return new Promise((resolve, reject) => {
|
||||
const abort = () => {
|
||||
const request = pendingRequests.get(id);
|
||||
if (!request) return;
|
||||
pendingRequests.delete(id);
|
||||
request.cleanup();
|
||||
reject(new DOMException("人脸识别已取消", "AbortError"));
|
||||
};
|
||||
const cleanup = () => signal?.removeEventListener("abort", abort);
|
||||
pendingRequests.set(id, { resolve, reject, cleanup });
|
||||
signal?.addEventListener("abort", abort, { once: true });
|
||||
worker.postMessage({ id, image }, [image]);
|
||||
});
|
||||
}
|
||||
|
||||
function getDetectorWorker() {
|
||||
if (detectorWorker) return detectorWorker;
|
||||
const worker = new Worker(new URL("./canvas-face-detector.worker.ts", import.meta.url), { type: "module" });
|
||||
worker.onmessage = (event: MessageEvent<DetectFaceResponse>) => {
|
||||
const request = pendingRequests.get(event.data.id);
|
||||
if (!request) return;
|
||||
pendingRequests.delete(event.data.id);
|
||||
request.cleanup();
|
||||
if (event.data.error) {
|
||||
request.reject(new Error(event.data.error));
|
||||
return;
|
||||
}
|
||||
request.resolve({
|
||||
faces: event.data.faces || [],
|
||||
imageWidth: event.data.imageWidth || 0,
|
||||
imageHeight: event.data.imageHeight || 0,
|
||||
});
|
||||
};
|
||||
worker.onerror = (event) => {
|
||||
const error = new Error(event.message || "人脸识别服务初始化失败");
|
||||
pendingRequests.forEach((request) => {
|
||||
request.cleanup();
|
||||
request.reject(error);
|
||||
});
|
||||
pendingRequests.clear();
|
||||
worker.terminate();
|
||||
detectorWorker = null;
|
||||
};
|
||||
detectorWorker = worker;
|
||||
return worker;
|
||||
}
|
||||
Reference in new issue
Block a user