feat(video-pipeline): 优化子 Agent 模板交互并新增模板路径工具
- 重构 SKILL.md,要求子 Agent 直接读取模板文件而非由主 Agent 摘要传送 - 新增 get-template-path.js 脚本,支持按账号和类型获取模板文件绝对路径 - 移除 capcut_assemble.js 中的关键字氛围词功能及相关依赖
This commit is contained in:
@@ -70,7 +70,21 @@ B 模式又分两种:**单图模式**(1 图 → 1 段视频)/ **首尾帧
|
||||
|
||||
### Step 1: 分镜脚本(子 Agent 执行)
|
||||
|
||||
主 Agent 将**用户文案 + 分镜模板路径**交给子 Agent → 子 Agent 按模板输出分镜表 JSON:
|
||||
主 Agent 获取账号专属模板路径 → 将**模板文件绝对路径 + 用户文案**传给子 Agent → 子 Agent **自行 Read 模板文件全文** → 按模板规则输出分镜表 JSON:
|
||||
|
||||
**模板路径获取方式**:
|
||||
```bash
|
||||
node .claude/skills/video-from-script/scripts/get-template-path.js --account <账号ID> --type storyboard
|
||||
```
|
||||
输出示例:`accounts\军事账号\prompts\分镜.md`
|
||||
|
||||
**子 Agent prompt 必须包含**:
|
||||
1. `模板文件绝对路径:{get-template-path.js 输出的路径,转为绝对路径}`,并指示子 Agent "先 Read 此文件全文,严格按模板规则执行"
|
||||
2. 用户完整口播文案
|
||||
3. 成片模式(图文/视频)
|
||||
4. 输出格式要求(JSON 数组)
|
||||
|
||||
**禁止**:主 Agent 不得摘要模板内容传给子 Agent,必须让子 Agent 直接读文件。
|
||||
|
||||
```json
|
||||
[{"id":1,"shotDesc":"英文画面描述","script":"中文口播文案","duration":5,"directorRef":"tarantino","keyword":"权力"}]
|
||||
@@ -94,10 +108,16 @@ node scripts/pipeline.js init --account <id> --mode <single|framePair> \
|
||||
|
||||
### Step 2-A: 图片提示词(子 Agent 执行)
|
||||
|
||||
- 主 Agent 传**manifest 路径 + 图片提示词模板路径**给子 Agent
|
||||
- 子 Agent 读 manifest.items,为每个 shot 追加 `imagePrompt` 字段后回写 manifest
|
||||
- 主 Agent 获取账号专属图片模板路径:`node .../get-template-path.js --account <账号ID> --type image`
|
||||
- 将**模板文件绝对路径 + manifest 绝对路径**传给子 Agent
|
||||
- 子 Agent **先 Read 模板文件全文**,再 Read manifest.json 的 items,为每个 shot 追加 `imagePrompt` 字段后回写 manifest
|
||||
- **硬约束:输出 shot 数量 == 输入 shot 数量**
|
||||
|
||||
**子 Agent prompt 必须包含**:
|
||||
1. `模板文件绝对路径:{get-template-path.js 输出的路径,转为绝对路径}`,并指示 "先 Read 此文件全文,严格按模板规则执行"
|
||||
2. `manifest 绝对路径`,指示 "Read manifest.json 的 items 数组,为每个 item 生成 imagePrompt 后回写"
|
||||
3. **禁止**:主 Agent 不得摘要模板内容传给子 Agent
|
||||
|
||||
**主 Agent 审查**:① 数量对得上?② shotDesc 内容完整保留?③ 光影策略对应 directorRef?
|
||||
|
||||
### Step 2-B: 生图
|
||||
@@ -113,10 +133,16 @@ node scripts/pipeline.js run --manifest <path> --phase images
|
||||
|
||||
### Step 3-A: 视频提示词(B 模式专属,子 Agent 执行)
|
||||
|
||||
- 主 Agent 传**manifest 路径 + 视频提示词模板路径**给子 Agent
|
||||
- 子 Agent 读 manifest.items(含已确认分镜图路径),为每个 shot 生成 `videoPrompt` 后回写 manifest
|
||||
- 主 Agent 获取账号专属视频模板路径:`node .../get-template-path.js --account <账号ID> --type video`
|
||||
- 将**模板文件绝对路径 + manifest 绝对路径**传给子 Agent
|
||||
- 子 Agent **先 Read 模板文件全文**,再 Read manifest.items(含已确认分镜图路径),为每个 shot 生成 `videoPrompt` 后回写 manifest
|
||||
- **硬约束:输出数量 == 分镜表 shot 数量**
|
||||
|
||||
**子 Agent prompt 必须包含**:
|
||||
1. `模板文件绝对路径:{get-template-path.js 输出的路径,转为绝对路径}`,并指示 "先 Read 此文件全文,严格按模板规则执行"
|
||||
2. `manifest 绝对路径`,指示 "Read manifest.json 的 items 数组,为每个 item 生成 videoPrompt 后回写"
|
||||
3. **禁止**:主 Agent 不得摘要模板内容传给子 Agent
|
||||
|
||||
**主 Agent 审查**:① 数量对得上?② 描述运动而非内容?③ 字数 ≤ 50?
|
||||
|
||||
### Step 3-B: 生视频(B 模式专属)
|
||||
|
||||
@@ -20,11 +20,11 @@ const fs = require('fs')
|
||||
const { US, parseArgs, getResolution, getAudioDurationSec } = require('./lib/capcut-api')
|
||||
const { buildTimeline, adjustVideoSpeed } = require('./lib/capcut-timeline')
|
||||
const {
|
||||
loadAccountConfig, loadSubtitleStyle, loadKeywordStyle,
|
||||
loadAccountConfig, loadSubtitleStyle,
|
||||
loadKenBurns, loadTransitions,
|
||||
addImages, addVideos, addKenBurns,
|
||||
addVoiceover, addBGM,
|
||||
addSubtitles, addKeywordOverlays,
|
||||
addSubtitles,
|
||||
addEffects, addFilter,
|
||||
} = require('./lib/capcut-tracks')
|
||||
const { saveManifest } = require('./lib/pipeline-utils')
|
||||
@@ -239,7 +239,7 @@ async function assemble(args) {
|
||||
|
||||
const steps = []
|
||||
if (mode === 'images') steps.push('upload')
|
||||
steps.push('draft', 'materials', 'kenburns', 'audio_oss', 'voiceover', 'audio', 'subtitles', 'keywords', 'effects', 'filter', 'save', 'sync')
|
||||
steps.push('draft', 'materials', 'kenburns', 'audio_oss', 'voiceover', 'audio', 'subtitles', 'effects', 'filter', 'save', 'sync')
|
||||
const totalSteps = steps.length
|
||||
let step = 0
|
||||
|
||||
@@ -387,16 +387,7 @@ async function assemble(args) {
|
||||
console.log(' 跳过')
|
||||
}
|
||||
|
||||
// -- 关键字氛围词 --
|
||||
step++; console.log(`[${step}/${totalSteps}] 添加关键字氛围词...`)
|
||||
const keywordStyle = loadKeywordStyle(manifest)
|
||||
if (Object.keys(keywordStyle).length > 0 && items.some(i => i.keyword)) {
|
||||
await addKeywordOverlays(draftUrl, items, timeline, keywordStyle)
|
||||
} else {
|
||||
console.log(' 跳过(无关键字或未配置 keywordStyle)')
|
||||
}
|
||||
|
||||
// -- 特效 --
|
||||
// -- 特效 -- // -- 特效 --
|
||||
step++; console.log(`[${step}/${totalSteps}] 添加特效...`)
|
||||
if (finalEffects) {
|
||||
try {
|
||||
|
||||
103
.claude/skills/video-from-script/scripts/get-template-path.js
Normal file
103
.claude/skills/video-from-script/scripts/get-template-path.js
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 获取账号专属模板文件的完整路径
|
||||
*
|
||||
* 用法:
|
||||
* node get-template-path.js --account 军事账号 --type storyboard
|
||||
* node get-template-path.js --account 军事账号 --type image
|
||||
* node get-template-path.js --account 军事账号 --type video
|
||||
*
|
||||
* 输出:
|
||||
* 完整的绝对路径,可直接传给 Read 工具或 fs.readFileSync
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// 路径常量
|
||||
const SCRIPTS_DIR = path.join(__dirname, '..')
|
||||
const SKILLS_DIR = path.join(SCRIPTS_DIR, '..')
|
||||
const PROJECT_ROOT = path.join(SKILLS_DIR, '..', '..')
|
||||
const ACCOUNTS_DIR = path.join(PROJECT_ROOT, 'accounts')
|
||||
|
||||
function getTemplatePath(accountId, templateType) {
|
||||
// 1. 读取账号配置
|
||||
const accountPath = path.join(ACCOUNTS_DIR, accountId, 'account.json')
|
||||
if (!fs.existsSync(accountPath)) {
|
||||
throw new Error(`账号不存在: ${accountPath}`)
|
||||
}
|
||||
|
||||
const account = JSON.parse(fs.readFileSync(accountPath, 'utf-8'))
|
||||
|
||||
// 2. 映射模板类型到字段名
|
||||
const fieldMap = {
|
||||
storyboard: 'storyboardPrompt',
|
||||
image: 'imageStylePrompt',
|
||||
video: 'videoStylePrompt'
|
||||
}
|
||||
|
||||
const fieldName = fieldMap[templateType]
|
||||
if (!fieldName) {
|
||||
throw new Error(`未知模板类型: ${templateType},支持: storyboard, image, video`)
|
||||
}
|
||||
|
||||
// 3. 获取相对路径
|
||||
const relativePath = account[fieldName]
|
||||
if (!relativePath) {
|
||||
throw new Error(`账号配置缺少字段: ${fieldName}`)
|
||||
}
|
||||
|
||||
// 4. 判断是否为绝对路径
|
||||
let absolutePath
|
||||
if (path.isAbsolute(relativePath)) {
|
||||
// 已经是绝对路径,直接使用
|
||||
absolutePath = relativePath
|
||||
} else {
|
||||
// 相对路径,拼接账号目录(相对于账号目录)
|
||||
absolutePath = path.join(ACCOUNTS_DIR, accountId, relativePath)
|
||||
}
|
||||
|
||||
// 5. 检查文件是否存在
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
throw new Error(`模板文件不存在: ${absolutePath}`)
|
||||
}
|
||||
|
||||
// 6. 转换为相对于项目根目录的相对路径(子 Agent 友好)
|
||||
const relativeToProject = path.relative(PROJECT_ROOT, absolutePath)
|
||||
|
||||
return relativeToProject
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CLI 入口
|
||||
// ============================================================================
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
const accountIndex = args.indexOf('--account')
|
||||
const typeIndex = args.indexOf('--type')
|
||||
|
||||
if (accountIndex === -1 || typeIndex === -1) {
|
||||
console.error('用法:node get-template-path.js --account <账号ID> --type <storyboard|image|video>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const accountId = args[accountIndex + 1]
|
||||
const templateType = args[typeIndex + 1]
|
||||
|
||||
try {
|
||||
const fullPath = getTemplatePath(accountId, templateType)
|
||||
console.log(fullPath)
|
||||
} catch (err) {
|
||||
console.error(`错误: ${err.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main()
|
||||
}
|
||||
|
||||
module.exports = { getTemplatePath }
|
||||
Reference in New Issue
Block a user