/** * Phase: assemble — CapCut 成片组装 * * 图片/视频 + TTS → 剪映草稿 */ const { log, getManifestDir } = require('./pipeline-utils') async function phaseAssemble(manifest, manifestPath, options) { const dir = getManifestDir(manifestPath) const accountConfig = options.accountConfig || {} const capcutConfig = accountConfig.capcut || {} const videoItems = manifest.items.filter(it => it.video && it.status === 'done') const hasVideos = videoItems.length > 0 const mode = hasVideos ? 'videos' : 'images' // 前置校验:图片模式下检查 file 字段 if (mode === 'images') { const missingFile = manifest.items.filter(it => !it.file) if (missingFile.length > 0) { throw new Error(`${missingFile.length} 个 item 缺少 file 字段(id: ${missingFile.map(it => it.id).join(', ')}),请先运行 images 阶段生成图片`) } } // 检测是否有配音和字幕 const hasAudio = manifest.items.some(it => it.audio) const hasSubtitles = manifest.items.some(it => it.script && it.script.trim() && it.script !== '[无配音]') const assembleArgs = { input: dir, manifest: manifestPath, mode, format: manifest.format || accountConfig.defaultFormat || '9:16', subtitles: hasSubtitles ? 'true' : 'false', voiceover: hasAudio ? 'true' : 'false', animation: capcutConfig.animation || '渐显+放大', } if (capcutConfig.defaultBGM) assembleArgs.bgm = capcutConfig.defaultBGM if (capcutConfig.effects) assembleArgs.effects = capcutConfig.effects.join(',') if (capcutConfig.filter) assembleArgs.filter = capcutConfig.filter log('assemble', `模式: ${mode}, 字幕: ${assembleArgs.subtitles}, 配音: ${assembleArgs.voiceover}, 动画: ${assembleArgs.animation}`) try { const { assemble } = require('../capcut_assemble') await assemble(assembleArgs) log('assemble', '成片完成') } catch (err) { log('assemble', `成片失败: ${err.message}`) throw err } } module.exports = { phaseAssemble }