Files
video-create/web/server/agent/tools/validate-storyboard.ts

71 lines
3.1 KiB
TypeScript
Raw Normal View History

import type { ToolDefinition } from './types';
export const validateStoryboard: ToolDefinition = {
name: 'validate_storyboard',
description: '校验分镜脚本质量TTS 估算 ≤ 6s、ratio 预检、script 拼接校验。返回校验结果和问题列表。',
input_schema: {
type: 'object',
properties: {
items: { type: 'string', description: '分镜 JSON 数组字符串,每个元素需包含 shotDesc、script 字段,可选 duration、directorRef' },
videoModelDuration: { type: 'number', description: '视频模型固定时长(秒),默认 6' },
},
required: ['items'],
},
execute: async (params) => {
const { items, videoModelDuration = 6 } = params as { items: string; videoModelDuration?: number };
let parsed: any[];
try { parsed = JSON.parse(items); } catch { return '错误: items 不是合法 JSON'; }
if (!Array.isArray(parsed) || parsed.length === 0) return '错误: items 必须是非空数组';
const errors: string[] = [];
const warnings: string[] = [];
const TTS_SPEED = 1.15;
const CHARS_PER_SEC = 5;
for (const item of parsed) {
const idx = item.id ?? parsed.indexOf(item) + 1;
const script: string = item.script || '';
const charCount = script.length;
const ttsEstimate = charCount / CHARS_PER_SEC;
const audioDur = ttsEstimate * TTS_SPEED;
const ratio = videoModelDuration / audioDur;
if (!item.shotDesc) errors.push(`Shot ${idx}: 缺少 shotDesc`);
if (!script) errors.push(`Shot ${idx}: 缺少 script`);
// TTS 估算检查
if (ttsEstimate > 6) {
errors.push(`Shot ${idx}: TTS 估算 ${ttsEstimate.toFixed(1)}s > 6s必须拆分 (script: ${script.slice(0, 30)}...)`);
}
// ratio 预检
if (ratio < 0.9) {
errors.push(`Shot ${idx}: ratio ${ratio.toFixed(2)} < 0.9,音频太长需拆分 (audio=${audioDur.toFixed(1)}s, video=${videoModelDuration}s)`);
}
if (!item.directorRef) {
warnings.push(`Shot ${idx}: 建议填写 directorRef`);
}
}
// script 拼接校验 - 返回统计而非原文比对(原文由用户提供)
const totalChars = parsed.reduce((sum: number, i: any) => sum + (i.script?.length || 0), 0);
const totalAudio = (totalChars / CHARS_PER_SEC) * TTS_SPEED;
const result = {
valid: errors.length === 0,
shotCount: parsed.length,
totalChars,
estimatedTotalAudio: `${totalAudio.toFixed(1)}s`,
errors,
warnings,
};
if (errors.length > 0) {
return `❌ 分镜校验未通过 (${errors.length} 个问题):\n\n${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}${warnings.length ? `\n\n⚠ 警告:\n${warnings.map((w, i) => `${i + 1}. ${w}`).join('\n')}` : ''}\n\n统计: ${parsed.length} 个镜头, 总字数 ${totalChars}, 音频估算 ${totalAudio.toFixed(1)}s`;
}
return `✅ 分镜校验通过\n\n统计: ${parsed.length} 个镜头, 总字数 ${totalChars}, 音频估算 ${totalAudio.toFixed(1)}s${warnings.length ? `\n\n⚠ 警告:\n${warnings.map((w, i) => `${i + 1}. ${w}`).join('\n')}` : ''}`;
},
};