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:
@@ -132,7 +132,7 @@ function buildTimeline(items, defaultDurationUs) {
|
||||
// 音频为主轴,视频调速适配(≤2x 加速,>2x 截断)
|
||||
let offset = 0
|
||||
return items.map(item => {
|
||||
const audioDur = (item.duration != null) ? item.duration * US : 0
|
||||
const audioDur = (item.audioDuration != null) ? item.audioDuration * US : (item.duration != null) ? item.duration * US : 0
|
||||
const videoDur = (item.videoDuration != null) ? item.videoDuration * US : 0
|
||||
// 无 TTS:用视频时长或固定时长
|
||||
if (audioDur <= 0) {
|
||||
@@ -196,7 +196,7 @@ async function assemble(args) {
|
||||
// 统一时间线:由 duration 驱动(TTS 音频时长)或 fallback 到固定时长
|
||||
const timeline = buildTimeline(items, defaultDurationUs)
|
||||
const totalDurationUs = timeline.length > 0 ? timeline[timeline.length - 1].end : 0
|
||||
const hasTTS = items.some(item => item.audio && item.duration != null)
|
||||
const hasTTS = items.some(item => item.audio && (item.audioDuration != null || item.duration != null))
|
||||
|
||||
console.log(`\nCapCut 成片组装`)
|
||||
console.log(` 模式: ${mode} 画幅: ${format} (${width}x${height})`)
|
||||
@@ -280,7 +280,7 @@ async function assemble(args) {
|
||||
if (manifestFile) {
|
||||
try {
|
||||
const m = JSON.parse(fs.readFileSync(manifestFile, 'utf-8'))
|
||||
const mi = m.items.find(i => i.text === item.text)
|
||||
const mi = m.items.find(i => i.id === item.id || i.narration === (item.narration || item.text) || i.text === (item.narration || item.text))
|
||||
if (mi) { mi.videoUrl = url; fs.writeFileSync(manifestFile, JSON.stringify(m, null, 2)) }
|
||||
} catch (_) {}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ async function assemble(args) {
|
||||
|
||||
// -- 添加字幕 --
|
||||
step++; console.log(`[${step}/${totalSteps}] 添加字幕...`)
|
||||
if (subtitles === 'true' && items.some(i => i.text)) {
|
||||
if (subtitles === 'true' && items.some(i => i.narration || i.text)) {
|
||||
await addSubtitles(draftUrl, items, timeline, subtitleStyle)
|
||||
} else {
|
||||
console.log(' 跳过')
|
||||
@@ -667,12 +667,12 @@ async function addSubtitles(draftUrl, items, timeline, style = {}) {
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
const text = item.text || item.caption || ''
|
||||
const text = item.narration || item.text || item.caption || ''
|
||||
if (!text) continue
|
||||
|
||||
const tl = timeline[i]
|
||||
const keyword = item.keyword || ''
|
||||
const keywordColor = style.highlightColor || item.keywordColor || style.color || '#FFFFFF'
|
||||
const keyword = ''
|
||||
const keywordColor = style.highlightColor || style.color || '#FFFFFF'
|
||||
|
||||
const cap = {
|
||||
start: tl.start,
|
||||
|
||||
Reference in New Issue
Block a user