feat(video-from-script): 添加 TTS 音色管理和解析功能
- 在 config.json 中添加 `ttsVoices` 音色库,支持音色名称到 ID 的映射 - 实现 `resolveVoice` 函数,将音色名称解析为实际 ID - 更新账号系统和批量管道,支持通过音色名称配置 TTS 语音 - Excel 导入和 CLI 参数新增音色字段,支持按行指定不同音色
This commit is contained in:
@@ -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 指定默认值)')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user