教程指南进阶四川话
四川话TTS批量生成教程:SRT字幕导出+情感控制
完整实操流程与API示例,覆盖批量合成、断句、字幕生成与情感参数。
乡音阁团队
2025/1/185 阅读时长
这是一篇可直接落地的实操教程:从准备稿件 → 批量合成四川话音频 → 自动生成字幕(SRT/VTT) → 打包导出。既可通过 XiangYinGe Web Studio 完成,也附上 API(Node/Python) 的批处理示例与常见问题排障。
适用人群 & 成果预览
谁适合读这篇文章
- 融媒体/文旅/政务:需要本地化四川话播报、统一音色风格与合规标识
- MCN/教学机构:批量产出短视频口播、一键导出字幕与工程包
- 开发者/工具党:希望用API自动化处理长文本、批量脚本
你将产出
- 统一音色/风格的四川话音频(WAV/MP3)
- 对齐良好的SRT/VTT字幕(带时码)
- 可复用的字典/情感预设/断句策略模板
一、准备数据(稿件模板、命名规范、发音词典)
1. 稿件模板(CSV/Excel)
建议用结构化表格管理脚本与参数,便于一次性导入Web Studio或API。
| id | title | speaker | style | speed | pause_ms | emotion | text |
|---|---|---|---|---|---|---|---|
| 0001 | 城市宣传片01 | sc_female_A | narr | 1.00 | 180 | calm | 今天的成都,烟火气里有温度,创新里有力量。 |
| 0002 | 城市宣传片02 | sc_female_A | narr | 0.95 | 160 | warm | 等你来摆龙门阵,喝盖碗茶,看一场川剧变脸。 |
| 0003 | 短视频口播01 | sc_male_B | promo | 1.10 | 120 | upbeat | 今朝走嘛!一起打卡网红小店,巴适得板! |
字段说明:
speaker:音色ID(示例值,具体以你平台音色库为准)style:叙述narr、广告promo、客服assistant等speed:语速系数(0.8~1.2常用)pause_ms:句间停顿(毫秒)emotion:calm/warm/upbeat/serious...(示例值)
2. 命名规范
- 项目目录:
{projectSlug}-{yyyymmdd},如chengdu-promo-20250818/ - 音频:
{id}_{speaker}_{style}.mp3→0001_sc_female_A_narr.mp3 - 字幕:同名
0001_sc_female_A_narr.srt - 清单:
manifest.json记录批量任务的参数与生成产物路径
3. 方言发音词典(可选但强烈建议)
为多音字、地名、人名、外来词建立词条,改善读音与重音。
示例(CSV):
| term | phoneme | note |
|---|---|---|
| 锦里 | /tɕin˨˩ li˨˩/ | 景点名 |
| 龙门阵 | /luŋ˧˥ mən˨˩ t͡ʂən˥˩/ | 四川方言:闲聊 |
| 巴适 | /pa˥ ʂɿ˥˩/ | 四川方言:舒适/惬意 |
| 火锅 | huo2 guo1 | 用普通话音素亦可,由系统做四川口音映射 |
lexicon 字段)。
二、批量合成(Web Studio与API两种方式)
方式A:Web Studio(零代码)
- 创建项目 → 选择「四川话」方言包 → 选基础音色(如
sc_female_A、sc_male_B) - 导入稿件:上传CSV/Excel → 字段映射(
text/speaker/style/speed/pause_ms/emotion) - 选择断句策略(默认即可,第四节详解):
- 标点断句
- 软上限18个汉字(超出则按停顿概率切分)
- 语气词与方言词优先与前句绑定
- 设置情感/韵律模板(可选):为广告稿套
promo-upbeat,为解说套narr-calm - 一键合成:可选择"并发4/8/16路"与"遇错重试"
- 校听与批量修订:右侧播放器逐条预听,词典修正自动回灌
- 导出:选择
MP3 320kbps+SRT+manifest.json,可打包ZIP
方式B:API(Node/Python示例)
Node.js(批量读CSV → 合成MP3与SRT)
// package.json 需安装: node-fetch, csv-parse, fs-extra
import fetch from 'node-fetch'
import { parse } from 'csv-parse/sync'
import fs from 'fs-extra'
import path from 'path'
const BASE_URL = 'https://api.xiangyinge.com/v1'
const API_KEY = process.env.XIANGYINGE_API_KEY
const outDir = 'chengdu-promo-20250818'
await fs.ensureDir(outDir)
const csv = fs.readFileSync('./scripts_sichuan.csv', 'utf8')
const rows = parse(csv, { columns: true, skip_empty_lines: true })
const manifest = []
for (const r of rows) {
const payload = {
dialect: 'sc', // 四川话
speaker: r.speaker || 'sc_female_A',
style: r.style || 'narr',
speed: Number(r.speed || 1.0),
pause_ms: Number(r.pause_ms || 150),
emotion: r.emotion || 'calm',
text: r.text,
// 可选:自定义词典,覆盖默认读音
lexicon: [{ term: '龙门阵', phoneme: 'luong2 men2 zhen4', note: '方言词' }],
// 请求字幕时间轴(强烈建议:后续一键导出SRT/VTT)
with_alignment: true,
}
const res = await fetch(`${BASE_URL}/tts:synthesize`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
if (!res.ok) {
const t = await res.text()
console.error(`Synth failed for id=${r.id}`, t)
continue
}
const data = await res.json()
// 假设响应返回 { audio_base64, alignment: [{start,end,text}], audio_format }
const audioPath = path.join(outDir, `${r.id}_${r.speaker}_${r.style}.mp3`)
const srtPath = audioPath.replace(/\.mp3$/, '.srt')
// 写音频
const buf = Buffer.from(data.audio_base64, 'base64')
await fs.writeFile(audioPath, buf)
// 写SRT
const srt = data.alignment
.map((seg, i) => {
const toSrtTime = (sec) => {
const ms = Math.floor(sec * 1000)
const h = String(Math.floor(ms / 3600000)).padStart(2, '0')
const m = String(Math.floor((ms % 3600000) / 60000)).padStart(2, '0')
const s = String(Math.floor((ms % 60000) / 1000)).padStart(2, '0')
const ms3 = String(ms % 1000).padStart(3, '0')
return `${h}:${m}:${s},${ms3}`
}
return `${i + 1}
${toSrtTime(seg.start)} --> ${toSrtTime(seg.end)}
${seg.text}
`
})
.join('\n')
await fs.writeFile(srtPath, srt, 'utf8')
manifest.push({
id: r.id,
title: r.title,
audio: path.basename(audioPath),
srt: path.basename(srtPath),
params: payload,
})
console.log('OK', r.id)
}
// 写清单
await fs.writeJson(path.join(outDir, 'manifest.json'), { items: manifest }, { spaces: 2 })
console.log('DONE', outDir)
Python(长文本自动断句 → 批量合成)
# 依赖: requests, pandas, tqdm
import base64, json, os
import pandas as pd
import requests
from tqdm import tqdm
BASE_URL = 'https://api.xiangyinge.com/v1'
API_KEY = os.environ['XIANGYINGE_API_KEY']
OUT_DIR = 'chengdu-promo-20250818'
os.makedirs(OUT_DIR, exist_ok=True)
def split_cn(text, max_len=18):
# 简易断句:按标点切,再对超长片段做二次切分
import re
parts = re.split(r'([。!?;…])', text)
merged = [''.join(parts[i:i+2]).strip() for i in range(0, len(parts), 2)]
chunks = []
for m in merged:
while len(m) > max_len:
chunks.append(m[:max_len])
m = m[max_len:]
if m:
chunks.append(m)
return chunks
def tts_one(text, speaker='sc_female_A', style='narr', speed=1.0, pause_ms=150, emotion='calm'):
payload = {
'dialect': 'sc',
'speaker': speaker, 'style': style,
'speed': speed, 'pause_ms': pause_ms, 'emotion': emotion,
'text': text, 'with_alignment': True
}
r = requests.post(f'{BASE_URL}/tts:synthesize',
headers={'Authorization': f'Bearer {API_KEY}'},
json=payload, timeout=60)
r.raise_for_status()
return r.json()
df = pd.read_csv('scripts_sichuan.csv')
manifest = []
for _, row in tqdm(df.iterrows(), total=len(df)):
text = row['text']
# 你也可以直接把整段交给服务端断句;这里演示本地断句
pieces = split_cn(text, max_len=18)
align_all, audio_all = [], b''
# 简化:逐句合成再拼接(生产建议使用服务端级联,避免边界拼接痕迹)
for p in pieces:
data = tts_one(p, row.get('speaker', 'sc_female_A'),
row.get('style', 'narr'),
float(row.get('speed', 1.0)),
int(row.get('pause_ms', 150)),
row.get('emotion', 'calm'))
audio_all += base64.b64decode(data['audio_base64'])
for seg in data['alignment']:
# 对齐追加并修正时间轴(此处略,生产建议用服务端统一对齐)
align_all.append(seg)
base = f"{row['id']}_{row.get('speaker','sc_female_A')}_{row.get('style','narr')}"
audio_path = os.path.join(OUT_DIR, base + '.mp3')
srt_path = os.path.join(OUT_DIR, base + '.srt')
with open(audio_path, 'wb') as f:
f.write(audio_all)
def to_srt_time(sec):
ms = int(sec * 1000)
h, ms = divmod(ms, 3600000)
m, ms = divmod(ms, 60000)
s, ms = divmod(ms, 1000)
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
with open(srt_path, 'w', encoding='utf-8') as f:
for i, seg in enumerate(align_all, 1):
f.write(f"{i}\n{to_srt_time(seg['start'])} --> {to_srt_time(seg['end'])}\n{seg['text']}\n\n")
manifest.append({'id': row['id'], 'title': row.get('title',''),
'audio': os.path.basename(audio_path),
'srt': os.path.basename(srt_path)})
with open(os.path.join(OUT_DIR, 'manifest.json'), 'w', encoding='utf-8') as f:
json.dump({'items': manifest}, f, ensure_ascii=False, indent=2)
print('DONE', OUT_DIR)
三、自动断句与字幕时间轴(Alignment)
为什么断句很重要?
- 提升自然度:在词组/语义边界停顿更自然
- 改善字幕可读性:每行12-18字最佳
- 便于后期:更稳定的切分与替换
推荐策略
- 标点优先:
,。!?;…直接切分;冒号/长句再按停顿概率二次切 - 语气词绑定:如"嘛/哟/哦/诶",尽量与前句合并
- 时长控制:每句1.0-6.0秒,过短合并、过长切分
- 方言词典加权:词典中标为"固定搭配"的词组不打断
SRT示例
1
00:00:00,000 --> 00:00:02,200
今天的成都,烟火气里有温度。
2
00:00:02,200 --> 00:00:04,900
创新里有力量。
3
00:00:04,900 --> 00:00:07,400
今朝走嘛,一起摆龙门阵!
四、导出与交付
常见导出组合
- 音频:
WAV 48kHz(后期用)或MP3 320kbps(成片用) - 字幕:
SRT(通用)与VTT(Web友好) - 工程包:
manifest.json(参数与语义切分记录)
交付建议
- 统一命名,便于NLE(Premiere/CapCut)自动关联
- 附带
README.md(音色、参数、生成日期、版本号) - 若对外发布,务必附上**"合成内容标识"**与授权声明
五、常见问题与排障(四川话场景)
1. 断句太碎或过长?
提高/降低"最大字数阈值",或把"语气词绑定"打开;长句加逗号优化标点。
2. 多音字/地名读错?
在词典中添加条目(优先级高于默认),排查是否被错误切分。
3. 情绪不稳、韵律忽快忽慢?
统一 style 与 emotion;长文本建议用段落级模板,避免句间风格漂移。
4. 数字/日期/英文缩写读法不合意?
在文本中显式写法("二〇二五年""美元一百二十"),或词典覆盖。
5. 四川方言词语未按习惯读法?
为"固定搭配"添加词条;必要时提供音素/IPA,让系统照读。
6. 拼接痕迹(本地断句合成后再拼)?
改用服务端级联对齐或提升"跨句平滑"选项;尽量避免本地拼接。
7. SRT时间轴与声音略有漂移?
打开"音素级对齐(phoneme-level)",或在导出时做整体回归修正。
8. 合规与标识
开启合成水印、输出元数据(生产时间、请求ID、音色ID),保留日志6个月以上。
六、质量自检清单(可打印)
- 语速/停顿与场景匹配(广告略快、解说略慢)
- 词典覆盖:地名、人名、品牌词、多音字
- 字幕每行12-18字,时长1-6秒,换行恰当
- 全片响度一致(LUFS标准:短视频-14~-16)
- 合成标识与授权声明已附
- 产物可复现:
manifest.json参数完整
七、下一步
相关资源
- XiangYinGe API文档(含沙箱Key与示例)
- 选择资源包(早鸟用户可获额度与上手支持)
- 政企/文旅/融媒体:支持私有化部署与定制音色,请邮件联系:
hello@xiangyinge.com
结语
到这里,你已经掌握了从脚本到四川话音频与字幕的完整流水线。无论你是融媒体、文旅项目还是短视频团队,都可以把这套流程标准化、模板化,持续复用。