feat(video-from-script): 添加 TTS 音色管理和解析功能

- 在 config.json 中添加 `ttsVoices` 音色库,支持音色名称到 ID 的映射
- 实现 `resolveVoice` 函数,将音色名称解析为实际 ID
- 更新账号系统和批量管道,支持通过音色名称配置 TTS 语音
- Excel 导入和 CLI 参数新增音色字段,支持按行指定不同音色
This commit is contained in:
2026-05-08 23:53:37 +08:00
parent 4a15e38169
commit 18fce1b5a1
12 changed files with 66 additions and 25 deletions

View File

@@ -15,7 +15,7 @@
const fs = require('fs')
const path = require('path')
const { SKILLS_DIR, ACCOUNTS_DIR } = require('./lib/pipeline-utils')
const { SKILLS_DIR, ACCOUNTS_DIR, loadConfig, resolveVoice } = require('./lib/pipeline-utils')
// output/ 在项目根的父级(美图/output/
const OUTPUT_BASE = path.join(SKILLS_DIR, '..', '..', '..', 'output')
@@ -30,6 +30,7 @@ function parseArgs(argv) {
if (argv[i] === '--file' && argv[i + 1]) args.file = argv[++i]
else if (argv[i] === '--account' && argv[i + 1]) args.account = argv[++i]
else if (argv[i] === '--mode' && argv[i + 1]) args.mode = argv[++i]
else if (argv[i] === '--voice' && argv[i + 1]) args.voice = argv[++i]
else if (argv[i] === '--row' && argv[i + 1]) args.row = parseInt(argv[++i])
else if (argv[i] === '--status' && argv[i + 1]) args.status = argv[++i]
else if (argv[i] === '--manifest-path' && argv[i + 1]) args.manifestPath = argv[++i]
@@ -80,6 +81,7 @@ function cmdInit(args) {
const defaultAccount = args.account || ''
const defaultMode = args.mode || 'single'
const defaultVoice = args.voice || ''
// 构建 items + 提取脚本
const items = []
@@ -89,6 +91,7 @@ function cmdInit(args) {
const title = extractField(row, ['选题', '标题', 'title', 'name']) || `视频${i + 1}`
const account = extractField(row, ['账号', 'account']) || defaultAccount
const mode = extractField(row, ['模式', 'mode']) || defaultMode
const voiceName = extractField(row, ['音色', 'voice']) || defaultVoice
if (!script || !script.trim()) {
console.warn(` ⚠ 第 ${i + 2} 行(${title})脚本为空,跳过`)
@@ -98,11 +101,15 @@ function cmdInit(args) {
const scriptFile = path.join(scriptsDir, `row_${String(i + 1).padStart(3, '0')}.txt`)
fs.writeFileSync(scriptFile, script.trim(), 'utf-8')
// 解析音色名称 → ID
const resolvedVoice = voiceName ? resolveVoice(voiceName) : ''
items.push({
row: i + 1,
title,
account: account || defaultAccount,
mode: mode || defaultMode,
voice: resolvedVoice,
scriptFile: `scripts/row_${String(i + 1).padStart(3, '0')}.txt`,
status: 'pending',
manifestPath: null,
@@ -122,7 +129,7 @@ function cmdInit(args) {
const batchManifest = {
source: path.basename(filePath),
createdAt: new Date().toISOString(),
defaults: { account: defaultAccount, mode: defaultMode },
defaults: { account: defaultAccount, mode: defaultMode, voice: defaultVoice ? resolveVoice(defaultVoice) : '' },
stats: calcStats(items),
items,
}
@@ -135,6 +142,7 @@ function cmdInit(args) {
console.log(` 总数: ${items.length}`)
console.log(` 默认账号: ${defaultAccount || '(未指定,需每行填写)'}`)
console.log(` 默认模式: ${defaultMode}`)
console.log(` 默认音色: ${defaultVoice || '(用账号配置)'}`)
console.log(` 脚本目录: ${scriptsDir}/`)
console.log()
}
@@ -183,14 +191,14 @@ function cmdStatus(args) {
if (grouped.pending.length > 0) {
console.log(` ⏳ 待处理 (${grouped.pending.length}):`)
for (const it of grouped.pending) {
console.log(` #${it.row} ${it.title} (账号: ${it.account || '未指定'}, 模式: ${it.mode})`)
console.log(` #${it.row} ${it.title} (账号: ${it.account || '未指定'}, 模式: ${it.mode}, 音色: ${it.voice || '账号默认'})`)
}
}
// 输出下一个待处理的行号(方便 AI agent 消费)
const next = batch.items.find(it => it.status === 'pending')
if (next) {
console.log(`\n ▶ 下一条: #${next.row} (账号: ${next.account}, 模式: ${next.mode})`)
console.log(`\n ▶ 下一条: #${next.row} (账号: ${next.account}, 模式: ${next.mode}, 音色: ${next.voice || '账号默认'})`)
console.log(` 脚本文件: ${path.resolve(batchDir, next.scriptFile)}`)
}
@@ -271,6 +279,7 @@ function cmdNext(args) {
title: item.title,
account: item.account,
mode: item.mode,
voice: item.voice || '',
scriptFile: path.resolve(batchDir, item.scriptFile),
}))
}
@@ -431,7 +440,7 @@ function main() {
console.log('批量视频生产编排器')
console.log('')
console.log('用法:')
console.log(' batch-pipeline.js init --file <xlsx/csv> [--account <账号>] [--mode <single|framePair>]')
console.log(' batch-pipeline.js init --file <xlsx/csv> [--account <账号>] [--mode <single|framePair>] [--voice <音色>]')
console.log(' batch-pipeline.js status --file <batch-manifest.json>')
console.log(' batch-pipeline.js next --file <batch-manifest.json>')
console.log(' batch-pipeline.js mark --file <...> --row <N> --status <pending|processing|completed|failed> [--manifest-path <path>] [--error <msg>]')
@@ -443,6 +452,7 @@ function main() {
console.log(' 脚本/文案/旁白 — 口播文案(必填)')
console.log(' 账号/account — 账号ID可选可由 --account 指定默认值)')
console.log(' 模式/mode — single|framePair可选可由 --mode 指定默认值)')
console.log(' 音色/voice — 音色名称或ID可选可由 --voice 指定默认值)')
}
}