feat(video-from-script): 重构工作流为子Agent分步执行并新增提示词模板系统
将视频制作工作流拆分为独立子步骤:分镜 → 图片提示词 → 生图 → 视频提示词 → 生视频 → 成片,每步由子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权限配置
This commit is contained in:
110
.claude/skills/video-from-script/scripts/lib/cmd-validate.js
Normal file
110
.claude/skills/video-from-script/scripts/lib/cmd-validate.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 }
|
||||
Reference in New Issue
Block a user