返回文章列表
教程指南进阶四川话

四川话TTS批量生成教程:SRT字幕导出+情感控制

完整实操流程与API示例,覆盖批量合成、断句、字幕生成与情感参数。

乡音阁团队

乡音阁团队

2025/1/185 阅读时长

这是一篇可直接落地的实操教程:从准备稿件 → 批量合成四川话音频 → 自动生成字幕(SRT/VTT) → 打包导出。既可通过 XiangYinGe Web Studio 完成,也附上 API(Node/Python) 的批处理示例与常见问题排障。

适用人群 & 成果预览

谁适合读这篇文章

  • 融媒体/文旅/政务:需要本地化四川话播报、统一音色风格与合规标识
  • MCN/教学机构:批量产出短视频口播、一键导出字幕与工程包
  • 开发者/工具党:希望用API自动化处理长文本、批量脚本

你将产出

  • 统一音色/风格的四川话音频(WAV/MP3)
  • 对齐良好的SRT/VTT字幕(带时码)
  • 可复用的字典/情感预设/断句策略模板

若你还没有示例,建议先用下文的"示例脚本"快速生成一段Demo。

一、准备数据(稿件模板、命名规范、发音词典)

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:句间停顿(毫秒)
  • emotioncalm/warm/upbeat/serious...(示例值)

2. 命名规范

  • 项目目录{projectSlug}-{yyyymmdd},如 chengdu-promo-20250818/
  • 音频{id}_{speaker}_{style}.mp30001_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 用普通话音素亦可,由系统做四川口音映射

词典可在Web中上传,或通过API随请求附带(见下文 lexicon 字段)。

二、批量合成(Web Studio与API两种方式)

方式A:Web Studio(零代码)

  1. 创建项目 → 选择「四川话」方言包 → 选基础音色(如 sc_female_Asc_male_B
  2. 导入稿件:上传CSV/Excel → 字段映射(text/speaker/style/speed/pause_ms/emotion
  3. 选择断句策略(默认即可,第四节详解):
    • 标点断句
    • 软上限18个汉字(超出则按停顿概率切分)
    • 语气词与方言词优先与前句绑定
  4. 设置情感/韵律模板(可选):为广告稿套 promo-upbeat,为解说套 narr-calm
  5. 一键合成:可选择"并发4/8/16路"与"遇错重试"
  6. 校听与批量修订:右侧播放器逐条预听,词典修正自动回灌
  7. 导出:选择 MP3 320kbps + SRT + manifest.json,可打包ZIP

小技巧:打开"相同文本去重缓存",同一句话在多处复用时不重复计费

方式B:API(Node/Python示例)

**注意**:以下为示例接口,`BASE_URL`、字段名以你的实际文档为准。你可以先用**沙箱key**跑通流程,再替换为正式key。

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. 标点优先,。!?;… 直接切分;冒号/长句再按停顿概率二次切
  2. 语气词绑定:如"嘛/哟/哦/诶",尽量与前句合并
  3. 时长控制:每句1.0-6.0秒,过短合并、过长切分
  4. 方言词典加权:词典中标为"固定搭配"的词组不打断

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. 情绪不稳、韵律忽快忽慢?

统一 styleemotion;长文本建议用段落级模板,避免句间风格漂移。

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

结语

到这里,你已经掌握了从脚本到四川话音频与字幕的完整流水线。无论你是融媒体、文旅项目还是短视频团队,都可以把这套流程标准化模板化,持续复用。

如果你愿意,我可以把本文示例的**CSV模板、Node/Python脚本与示例词典**打包成一个ZIP,供你直接下载使用。

延伸阅读:工程化与质量优化