Files
video-create/.claude/skills/video-from-script/scripts/lib/phase-tts.js
sion123 c878abc39b feat(video-pipeline): 用 ffprobe 实际测量音视频时长并统一字段名为 script
将项目中的 `narration` 字段统一重命名为 `script`,并新增 `getAudioDurationSec` 函数通过 `ffprobe` 实际测量音频和视频文件的时长,替代 Manifest 中的估计值,提高时间线组装的准确性。同时优化字幕逻辑,仅在有 TTS 音频时调整视频速度。
2026-05-01 01:52:02 +08:00

47 lines
1.5 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: tts — 语音合成
*
* 使用通义千问 TTS 生成旁白音频
*/
const path = require('path')
const { saveManifest, ensureDir, log, getManifestDir } = require('./pipeline-utils')
async function phaseTts(manifest, manifestPath, options = {}) {
const dir = getManifestDir(manifestPath)
const audioDir = path.join(dir, 'audio')
ensureDir(audioDir)
const { synthesize } = require('../qwen-tts')
const items = manifest.items.filter(it =>
it.status === 'done' && (it.script || it.text) && !it.audio
)
if (items.length === 0) { log('tts', '无待处理 item跳过'); return }
log('tts', `${items.length}`)
for (let i = 0; i < items.length; i++) {
const item = items[i]
const idx = i + 1
try {
const { filePath, duration } = await synthesize(item.script || item.text, {
outputDir: audioDir,
id: item.id || idx,
voice: manifest.ttsVoice || undefined,
instruction: manifest.ttsInstruction || undefined,
})
item.audio = path.relative(dir, filePath).replace(/\\/g, '/')
item.audioDuration = Math.round(duration * 1000) / 1000
log('tts', `[${idx}/${items.length}] ${duration.toFixed(1)}s: ${(item.script || item.text).substring(0, 30)}...`)
} catch (err) {
item.status = 'failed'
item.error = `TTS失败: ${err.message}`
log('tts', `[${idx}/${items.length}] 失败: ${err.message}`)
}
saveManifest(manifestPath, manifest)
}
}
module.exports = { phaseTts }