feat(capcut-pipeline): 将 TTS 配音切换为 CosyVoice 并重构动画系统
- 将 TTS 引擎从 Qwen-TTS 切换为阿里云 CosyVoice(DashScope WebSocket) - 输出格式从 WAV(24kHz)改为 MP3 - 重构图片动画分拆逻辑,支持组合动画(如"渐显+放大") - 移除字幕关键词高亮相关字段 - 移除已删除的 `uploadAudioToOSS` 函数,统一使用 `uploadToOSS` - 更新文档和配置默认值以匹配新引擎
This commit is contained in:
@@ -218,8 +218,7 @@ async function assemble(args) {
|
||||
format = '9:16',
|
||||
apiKey = '',
|
||||
duration = '4',
|
||||
animation = '缩放',
|
||||
localAudio = 'true',
|
||||
animation = '渐显+放大',
|
||||
} = args
|
||||
|
||||
if (!input) throw new Error('缺少 --input 参数')
|
||||
@@ -352,12 +351,11 @@ async function assemble(args) {
|
||||
// Step 2: 上传(已调速的)视频到 OSS
|
||||
const missingUrl = items.filter(it => it.video && !it.videoUrl)
|
||||
if (missingUrl.length > 0) {
|
||||
const { uploadFile } = require('./oss-upload')
|
||||
console.log(` 上传 ${missingUrl.length} 个视频到 OSS...`)
|
||||
for (const item of missingUrl) {
|
||||
const videoPath = path.resolve(inputDir, item.video)
|
||||
try {
|
||||
const { url } = await uploadFile(videoPath)
|
||||
const url = await uploadToOSS(videoPath)
|
||||
item.videoUrl = url
|
||||
// 回写 manifest
|
||||
if (manifestFile) {
|
||||
@@ -492,17 +490,12 @@ async function addImages(draftUrl, items, imgUrls, timeline, width, height, anim
|
||||
}
|
||||
|
||||
if (animation) {
|
||||
const parts = animation.split('+')
|
||||
for (const part of parts) {
|
||||
const name = part.trim()
|
||||
// 组合动画(持续整段):缩放、三分割 等
|
||||
if (name === '缩放' || name === '缩放 II') {
|
||||
info.loop_animation = name
|
||||
} else {
|
||||
// 默认作为入场动画
|
||||
info.in_animation = name
|
||||
}
|
||||
}
|
||||
const parts = animation.split('+').map(p => p.trim()).filter(Boolean)
|
||||
const groupNames = ['缩放', '缩放 II']
|
||||
const groupAnims = parts.filter(p => groupNames.includes(p))
|
||||
const inAnims = parts.filter(p => !groupNames.includes(p))
|
||||
if (groupAnims.length > 0) info.loop_animation = groupAnims.join('|')
|
||||
if (inAnims.length > 0) info.in_animation = inAnims.join('|')
|
||||
}
|
||||
|
||||
return info
|
||||
@@ -637,19 +630,9 @@ async function addVideos(draftUrl, inputDir, items, timeline, width, height, tra
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 音频上传(本地文件 → OSS 公网 URL)
|
||||
// 音频批量上传(本地文件 → OSS 公网 URL)
|
||||
// ============================================================================
|
||||
|
||||
async function uploadAudioToOSS(filePath) {
|
||||
try {
|
||||
const oss = require(path.join(__dirname, 'oss-upload'))
|
||||
const { url } = await oss.uploadFile(filePath)
|
||||
return url
|
||||
} catch (err) {
|
||||
throw new Error(`音频上传 OSS 失败: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function batchUploadAudio(inputDir, items) {
|
||||
const urls = {}
|
||||
for (const item of items) {
|
||||
@@ -665,7 +648,7 @@ async function batchUploadAudio(inputDir, items) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
urls[seg.audio] = await uploadAudioToOSS(filePath)
|
||||
urls[seg.audio] = await uploadToOSS(filePath)
|
||||
console.log(` 上传: ${path.basename(filePath)} -> OK`)
|
||||
} catch (err) {
|
||||
console.error(` 上传失败: ${path.basename(filePath)} - ${err.message}`)
|
||||
@@ -686,7 +669,7 @@ async function batchUploadAudio(inputDir, items) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
urls[item.audio] = await uploadAudioToOSS(filePath)
|
||||
urls[item.audio] = await uploadToOSS(filePath)
|
||||
console.log(` 上传: ${path.basename(filePath)} -> OK`)
|
||||
} catch (err) {
|
||||
console.error(` 上传失败: ${path.basename(filePath)} - ${err.message}`)
|
||||
@@ -868,8 +851,6 @@ async function addSubtitles(draftUrl, items, timeline, style = {}, split = false
|
||||
start: currentTime,
|
||||
end: currentTime + duration,
|
||||
text: seg.text,
|
||||
keyword: '',
|
||||
keyword_color: '',
|
||||
}
|
||||
|
||||
if (inAnimation) cap.in_animation = inAnimation
|
||||
@@ -903,8 +884,6 @@ async function addSubtitles(draftUrl, items, timeline, style = {}, split = false
|
||||
start: currentTime,
|
||||
end: currentTime + duration,
|
||||
text: sentence,
|
||||
keyword: '',
|
||||
keyword_color: '',
|
||||
}
|
||||
|
||||
if (inAnimation) cap.in_animation = inAnimation
|
||||
@@ -918,16 +897,10 @@ async function addSubtitles(draftUrl, items, timeline, style = {}, split = false
|
||||
}
|
||||
} else {
|
||||
// 原始模式:一句字幕
|
||||
const keyword = ''
|
||||
const keywordColor = style.highlightColor || style.color || '#FFFFFF'
|
||||
|
||||
const cap = {
|
||||
start: tl.start,
|
||||
end: tl.end,
|
||||
text,
|
||||
keyword,
|
||||
keyword_color: keyword ? keywordColor : '',
|
||||
keyword_font_size: 18,
|
||||
}
|
||||
|
||||
if (inAnimation) cap.in_animation = inAnimation
|
||||
@@ -1040,7 +1013,7 @@ async function main() {
|
||||
console.log(' --duration 4 默认每段时长/秒(无TTS时的fallback,默认 4)')
|
||||
console.log(' --voiceover true|false 是否添加TTS配音轨道(默认 true)')
|
||||
console.log(' --subtitles true|false 是否添加字幕(默认 true)')
|
||||
console.log(' --split-captions true|false 分句字幕模式(默认 false,长句按标点切分)')
|
||||
console.log(' --split-captions true|false 分句字幕模式(默认 true,按标点切分)')
|
||||
console.log(' --bgm <url> 背景音乐 URL')
|
||||
console.log(' --effects "名称1,名称2" 特效名称(逗号分隔)')
|
||||
console.log(' --filter "名称:强度" 滤镜(强度 0-100)')
|
||||
|
||||
Reference in New Issue
Block a user