将视频制作工作流拆分为独立子步骤:分镜 → 图片提示词 → 生图 → 视频提示词 → 生视频 → 成片,每步由子Agent独立执行。引入prompts/目录统一管理提示词模板(分镜.md、图片提示词.md、视频提示词.md),通过account.json的storyboardPrompt/imageStylePrompt/videoStylePrompt字段引用。 变更内容: - 新增confirmed机制和pipeline.js confirm命令,生图后必须人工确认才能继续 - manifest schema改用shotDesc/narration/duration/directorRef替代旧字段 - 文件命名规则从keyword改为slug(从shotDesc/narration派生) - 删除旧的storyboard-rules.md和prompt-rules.md - pipeline.js脚本拆分为lib/目录下的独立模块(cmd-init/cmd-confirm/cmd-validate/phase-*) - 新增cmd-create-account支持一键创建带prompts目录的账号 - capcut_assemble支持narration字段替代text作为字幕源 - 新增.gitclaude/settings.json权限配置
111 lines
4.3 KiB
JavaScript
111 lines
4.3 KiB
JavaScript
/**
|
||
* Command: validate — 校验 manifest.json 完整性
|
||
* Command: validate-account — 校验账号目录完整性
|
||
*/
|
||
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
const { loadManifest, ACCOUNTS_DIR } = require('./pipeline-utils')
|
||
|
||
function validateManifest(manifestPath) {
|
||
const issues = []
|
||
|
||
if (!fs.existsSync(manifestPath)) {
|
||
console.error(`错误: manifest 不存在: ${manifestPath}`)
|
||
process.exit(1)
|
||
}
|
||
|
||
let manifest
|
||
try {
|
||
manifest = loadManifest(manifestPath)
|
||
} catch (e) {
|
||
console.error(`错误: JSON 解析失败: ${e.message}`)
|
||
process.exit(1)
|
||
}
|
||
|
||
if (!manifest.account) issues.push('缺少顶层 account')
|
||
if (!manifest.imageModel) issues.push('缺少顶层 imageModel(可选: gemini, mj)')
|
||
if (!manifest.format) issues.push('缺少顶层 format(如 9:16)')
|
||
if (!manifest.items || !Array.isArray(manifest.items)) issues.push('缺少顶层 items 数组')
|
||
if (!manifest.mode) issues.push('缺少顶层 mode(single 或 framePair)')
|
||
|
||
if (manifest.items && Array.isArray(manifest.items)) {
|
||
manifest.items.forEach((item, i) => {
|
||
const prefix = `items[${i}]`
|
||
if (!item.narration && !item.text) issues.push(`${prefix} 缺少 narration 或 text(中文旁白)`)
|
||
if (!item.shotDesc) issues.push(`${prefix} 缺少 shotDesc(分镜描述)`)
|
||
if (!item.imagePrompt) issues.push(`${prefix} 缺少 imagePrompt`)
|
||
if (manifest.mode === 'framePair' && !item.lastFramePrompt) {
|
||
issues.push(`${prefix} 首尾帧模式缺少 lastFramePrompt(imagePrompt 作为第一帧)`)
|
||
}
|
||
if (item.status && !['pending', 'generating', 'done', 'failed'].includes(item.status)) {
|
||
issues.push(`${prefix} status 无效: ${item.status}`)
|
||
}
|
||
})
|
||
}
|
||
|
||
if (issues.length === 0) {
|
||
console.log(`✓ Manifest 校验通过: ${manifestPath}`)
|
||
console.log(` ${manifest.items?.length || 0} items, account=${manifest.account}, mode=${manifest.mode}`)
|
||
} else {
|
||
console.error(`✗ 发现 ${issues.length} 个问题:`)
|
||
issues.forEach(issue => console.error(` - ${issue}`))
|
||
process.exit(1)
|
||
}
|
||
}
|
||
|
||
function validateAccount(accountId) {
|
||
const issues = []
|
||
const accountDir = path.join(ACCOUNTS_DIR, accountId)
|
||
|
||
if (!fs.existsSync(accountDir)) { console.error(`错误: 账号不存在: ${accountDir}`); process.exit(1) }
|
||
|
||
const accountPath = path.join(accountDir, 'account.json')
|
||
if (!fs.existsSync(accountPath)) { console.error('错误: 缺少 account.json'); process.exit(1) }
|
||
|
||
let config
|
||
try { config = JSON.parse(fs.readFileSync(accountPath, 'utf-8')) }
|
||
catch (e) { console.error(`错误: JSON 解析失败: ${e.message}`); process.exit(1) }
|
||
|
||
if (config.id !== accountId) issues.push(`id 不匹配: json="${config.id}" vs 目录="${accountId}"`)
|
||
if (!config.name) issues.push('缺少 name')
|
||
if (!config.imageModel) issues.push('缺少 imageModel')
|
||
if (!config.defaultFormat) issues.push('缺少 defaultFormat')
|
||
|
||
const refDir = path.join(accountDir, 'references')
|
||
const styles = config.styles || {}
|
||
const hasStyleRefs = Object.values(styles).some(s => s.references && s.references.length > 0)
|
||
const localRefs = fs.existsSync(refDir)
|
||
? fs.readdirSync(refDir).filter(f => /\.(png|jpg|jpeg|webp)$/i.test(f))
|
||
: []
|
||
if (localRefs.length === 0 && !hasStyleRefs) {
|
||
issues.push('无参考图(建议至少 1 张)')
|
||
}
|
||
|
||
const stylesDir = path.join(accountDir, 'styles')
|
||
const styleFiles = fs.existsSync(stylesDir)
|
||
? fs.readdirSync(stylesDir).filter(f => f.endsWith('.md'))
|
||
: []
|
||
if (styleFiles.length === 0) {
|
||
issues.push('无风格文件(styles/ 下至少 1 个 .md)')
|
||
}
|
||
|
||
for (const [sName, sConf] of Object.entries(styles)) {
|
||
for (const ref of (sConf.references || [])) {
|
||
if (!ref.url) issues.push(`styles.${sName}: 参考图 ${ref.file} 缺少 url(未上传 OSS)`)
|
||
}
|
||
}
|
||
|
||
if (issues.length === 0) {
|
||
console.log(`✓ 账号校验通过: ${accountId}`)
|
||
console.log(` ${config.name}, 模型: ${config.imageModel}+${config.videoModel || '(未指定)'}`)
|
||
console.log(` 参考图: ${localRefs.length} 本地, 风格: ${styleFiles.length} 个`)
|
||
} else {
|
||
console.error(`✗ 发现 ${issues.length} 个问题:`)
|
||
issues.forEach(i => console.error(` - ${i}`))
|
||
process.exit(1)
|
||
}
|
||
}
|
||
|
||
module.exports = { validateManifest, validateAccount }
|