Files
video-create/.claude/skills/video-from-script/scripts/lib/phase-videos.js
sion123 86b9b7948d 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权限配置
2026-04-30 21:18:31 +08:00

105 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Phase: videos — 视频生成VEO / Grok / Kling
*
* 图生视频,批量提交,生成后自动上传 OSS
*/
const path = require('path')
const { saveManifest, ensureDir, log, getManifestDir } = require('./pipeline-utils')
async function phaseVideos(manifest, manifestPath, options) {
const dir = getManifestDir(manifestPath)
const videosDir = path.join(dir, 'videos')
ensureDir(videosDir)
const accountConfig = options.accountConfig || {}
const videoModel = manifest.videoModel || accountConfig.videoModel || 'veo3-fast-frames'
const items = manifest.items.filter(it =>
it.status === 'done' && it.confirmed !== false && it.url && it.videoPrompt && !it.video
)
if (items.length === 0) { log('videos', '无待处理 item跳过'); return }
// 选择生成器
let generator
const modelLower = videoModel.toLowerCase()
if (modelLower.includes('grok')) {
generator = require('../grok-video-generator')
} else if (modelLower.includes('kling')) {
generator = require('../kling-video-generator')
} else {
generator = require('../veo-video-generator')
}
log('videos', `${items.length} 个, 模型: ${videoModel}`)
const tasks = items.map((item, i) => {
const task = {
id: item.id || i + 1,
prompt: item.videoPrompt,
image: item.url,
outputDir: videosDir,
}
if (item.lastFrameUrl) {
task.images = [item.url, item.lastFrameUrl]
task.lastFrameUrl = item.lastFrameUrl
} else {
task.images = [item.url]
}
return task
})
try {
const results = await generator.batchGenerate(tasks, {
videoModel,
aspectRatio: manifest.format || '9:16',
outputDir: videosDir,
skipManifestWrite: true,
})
for (let i = 0; i < results.length; i++) {
const result = results[i]
const item = items[i]
if (!item) continue
if (result.success && result.file) {
item.video = path.relative(dir, result.file).replace(/\\/g, '/')
item.videoDuration = result.duration
} else {
item.status = 'failed'
item.error = result.error || '视频生成失败'
log('videos', ` item ${(item.id || '?')} 失败: ${item.error}`)
}
}
} catch (err) {
log('videos', `批量生成失败: ${err.message}`)
for (const item of items) {
if (!item.video) {
item.status = 'failed'
item.error = `批量生成异常: ${err.message}`
}
}
}
// 上传视频到 OSS
const { uploadFile } = require('../oss-upload')
const videoItems = manifest.items.filter(it => it.video && !it.videoUrl)
if (videoItems.length > 0) {
log('videos', `上传 ${videoItems.length} 个视频到 OSS...`)
for (const item of videoItems) {
const videoPath = path.resolve(dir, item.video)
try {
const { url } = await uploadFile(videoPath)
item.videoUrl = url
log('videos', ` ${item.video} → OK`)
} catch (err) {
log('videos', ` ${item.video} 上传失败: ${err.message}`)
}
saveManifest(manifestPath, manifest)
}
}
saveManifest(manifestPath, manifest)
}
module.exports = { phaseVideos }