ai-chatbot/lib/video-canvas/director-scene.ts

135 lines
3.6 KiB
TypeScript

import {
defaultDirectorMotion,
readDirectorMotion,
validDirectorPose,
type DirectorMotion,
} from "./director-motion";
export type DirectorVector = [number, number, number];
export type DirectorObject = {
id: string;
name: string;
kind: "actor" | "box" | "sphere" | "model";
url?: string;
position: DirectorVector;
rotation: DirectorVector;
scale: DirectorVector;
};
export type DirectorCamera = {
id: string;
name: string;
position: DirectorVector;
target: DirectorVector;
fov: number;
};
export type DirectorSnapshot = {
id: string;
title: string;
url: string;
width: number;
height: number;
cameraId: string;
};
export type DirectorScene = {
objects: DirectorObject[];
cameras: DirectorCamera[];
activeCameraId: string;
ratio: string;
snapshots: DirectorSnapshot[];
motion: DirectorMotion;
};
export const directorRatios = ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"];
export function directorImageSize(ratio: string) {
const [w, h] = (directorRatios.includes(ratio) ? ratio : "16:9")
.split(":")
.map(Number);
return w >= h
? { width: 1280, height: Math.round((1280 * h) / w) }
: { width: Math.round((1280 * w) / h), height: 1280 };
}
export function defaultDirectorScene(): DirectorScene {
return {
objects: [
{
id: "actor-a",
name: "基础人偶",
kind: "actor",
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
],
cameras: [
{
id: "camera-1",
name: "机位1",
position: [4, 3, 7],
target: [0, 1, 0],
fov: 50,
},
],
activeCameraId: "camera-1",
ratio: "16:9",
snapshots: [],
motion: defaultDirectorMotion(),
};
}
export function readDirectorScene(value: unknown): DirectorScene {
const fallback = defaultDirectorScene();
if (!value || typeof value !== "object") return fallback;
const v = value as DirectorScene;
const vector = (a: unknown): a is DirectorVector =>
Array.isArray(a) &&
a.length === 3 &&
a.every((n) => typeof n === "number" && Number.isFinite(n));
const cameras = Array.isArray(v.cameras)
? v.cameras.filter(
(c) =>
c &&
typeof c.id === "string" &&
typeof c.name === "string" &&
vector(c.position) &&
vector(c.target) &&
Number.isFinite(c.fov) &&
c.fov >= 15 &&
c.fov <= 90 &&
validDirectorPose(c),
)
: [];
const objects = Array.isArray(v.objects)
? v.objects.filter(
(o) =>
o &&
typeof o.id === "string" &&
typeof o.name === "string" &&
["actor", "box", "sphere", "model"].includes(o.kind) &&
vector(o.position) &&
vector(o.rotation) &&
vector(o.scale) &&
o.scale.every((n) => n > 0) &&
(o.kind !== "model" || /^https?:\/\//i.test(o.url || "")),
)
: fallback.objects;
const snapshots = Array.isArray(v.snapshots)
? v.snapshots.filter(
(s) =>
s &&
typeof s.id === "string" &&
typeof s.title === "string" &&
/^https?:\/\//i.test(s.url) &&
s.width > 0 &&
s.height > 0,
)
: [];
const validCameras = cameras.length ? cameras : fallback.cameras;
return {
objects,
cameras: validCameras,
activeCameraId: validCameras.some((c) => c.id === v.activeCameraId)
? v.activeCameraId
: validCameras[0].id,
ratio: directorRatios.includes(v.ratio) ? v.ratio : "16:9",
snapshots,
motion: readDirectorMotion(v.motion),
};
}