44 lines
2.5 KiB
TypeScript
44 lines
2.5 KiB
TypeScript
import { z } from "zod";
|
||
import type { EditCandidate } from "./contract";
|
||
|
||
export const MIN_SEQUENCE_MATCH_SCORE = 60;
|
||
export class HighlightValidationError extends Error {
|
||
constructor(message: string) { super(message); this.name = "HighlightValidationError"; }
|
||
}
|
||
|
||
const highlightResultSchema = z.object({
|
||
selections: z.array(z.object({
|
||
id: z.string(),
|
||
startSeconds: z.number().finite().min(0),
|
||
transition: z.enum(["cut", "fade"]),
|
||
reason: z.string().trim().min(1).max(300),
|
||
matchScore: z.number().finite().min(0).max(100),
|
||
visualIssues: z.array(z.string().trim().min(1).max(160)).max(10).default([]),
|
||
})).min(1).max(12),
|
||
summary: z.string().max(1000).default(""),
|
||
});
|
||
export type SequenceHighlight = z.infer<typeof highlightResultSchema>["selections"][number];
|
||
|
||
export function validateSequenceHighlights(value: unknown, candidates: EditCandidate[], shotDurations: number[]) {
|
||
const parsed = highlightResultSchema.safeParse(value);
|
||
if (!parsed.success) throw new HighlightValidationError("视频高光分析格式无效,未提交成片合成");
|
||
const result = parsed.data;
|
||
if (shotDurations.length !== candidates.length || result.selections.length !== candidates.length ||
|
||
new Set(result.selections.map(item => item.id)).size !== candidates.length ||
|
||
result.selections.some((item, index) => item.id !== candidates[index].id))
|
||
throw new HighlightValidationError("视频高光分析遗漏、重复或更改了分镜顺序,未提交成片合成");
|
||
for (const [index, item] of result.selections.entries()) {
|
||
const available = (candidates[index].end - candidates[index].start) / 1_000_000;
|
||
const wanted = shotDurations[index];
|
||
if (!Number.isFinite(wanted) || wanted <= 0 || item.startSeconds + wanted > available + 0.001)
|
||
throw new HighlightValidationError(`镜头 ${index + 1} 的高光区间超出真实视频长度,请重新选片`);
|
||
if (index === 0 && item.transition !== "cut")
|
||
throw new HighlightValidationError("首镜头的转场分析无效,请重新选片");
|
||
if (item.visualIssues.length)
|
||
throw new HighlightValidationError(`镜头 ${index + 1} 存在画面违规:${item.visualIssues.join(";")}。请替换或重新生成该镜头后再合成`);
|
||
if (item.matchScore < MIN_SEQUENCE_MATCH_SCORE)
|
||
throw new HighlightValidationError(`镜头 ${index + 1} 未达到需求匹配门槛(${item.matchScore}/${MIN_SEQUENCE_MATCH_SCORE}):${item.reason}。请替换或重新生成该镜头后再合成`);
|
||
}
|
||
return result;
|
||
}
|