YoVoice 本地跑多语言音频,先确认语音包和输出格式

文章导读
在本地把 YoVoice 跑起来处理多语言文本,最先要排除的不是音质问题,而是两件更容易被忽略的准备工作:目标语言在你的机器上到底有没有对应语音包,以及导出音频的容器格式和采样率是否被剪辑端或发布端接受。语言读不出来,多半是语音包缺失、语言码写错,或一段文本里混了多种语言导致切分失败;文件导入不了,则先看扩展名和实际编码参数是否一致。这两项都能用命令和日志直接验证,不需要靠反复试听猜原因。
📋 目录
  1. A 在语音列表里核对目标语言的覆盖情况
  2. B 检查运行环境里的语言相关依赖是否齐全
  3. C 用一条短文本跑最小合成
  4. D 对比不同输出格式与采样率的导出结果
  5. E 把多语言文本拆成单语文件再批量送入
A A

在本地把 YoVoice 跑起来处理多语言文本,最先要排除的不是音质问题,而是两件更容易被忽略的准备工作:目标语言在你的机器上到底有没有对应语音包,以及导出音频的容器格式和采样率是否被剪辑端或发布端接受。语言读不出来,多半是语音包缺失、语言码写错,或一段文本里混了多种语言导致切分失败;文件导入不了,则先看扩展名和实际编码参数是否一致。这两项都能用命令和日志直接验证,不需要靠反复试听猜原因。

本地做多语言合成,建议先把语言覆盖清单和输出格式定下来,再调音色和语速。具体动作是:列出已安装语音包并对照目标语言码标出缺失项,检查字体与编码环境,用一句短文本跑通单语言链路,再按交付端要求确定格式、采样率和声道三项。验证方式是看退出码、输出文件能否被 file 命令识别、日志里有没有语言或编码报错。任何一项没通过,就不建议进入批量合成,否则错误会被放大成一堆难以定位的坏文件。

在语音列表里核对目标语言的覆盖情况

先做一张对照表,把“要用的语言”和“实际安装的语音包”摆在一起。语音包目录名通常带语言码和音色标识,语言码写法各版本可能不同,常见是 zh-CN、en-US、ja-JP 这种“语言-地区”形式,也有只写 zh、ja 的。具体子命令名以本机 `--help` 输出为准,不要照抄别人博客里的写法。

# 列出可用语音(子命令以本机 `--help` 为准)
yovoice `--list-voices`

# 或直接看语音包目录
ls -1 ./voices | sed 's/\..*$//' | sort -u

把目标语言写成清单文件,与实际目录做差集,缺失项就会直接打印出来,比肉眼比对目录可靠。

cat > wanted.txt <<'EOF'
zh-CN
en-US
ja-JP
EOF

ls -1 ./voices | sort -u > have.txt
comm -23 <(sort wanted.txt) <(sort have.txt)

对照表建议按下面四列记录,缺失的行不要删掉,留着提醒后续补包:

目标语言语言码语音包目录名状态
中文zh-CNzh-CN-female-01已安装
英语en-USen-US-male-01已安装
日语ja-JP—缺失,需补包

要注意一种常见情况:语音包存在,但配置里写的语言码大小写或连字符与目录名不完全一致,加载时仍然会报找不到语音。建议配置值直接从目录名复制,别手打。

检查运行环境里的语言相关依赖是否齐全

语音包齐全但合成出来是方框、空白或直接报编码错误,通常指向字体和 locale。Linux 下先看字体覆盖:

YoVoice 本地跑多语言音频,先确认语音包和输出格式
fc-list :lang=zh | head -n 5
fc-list :lang=ja | head -n 5
locale
echo $LANG

期望结果是 fc-list 能列出若干条字体记录,而不是空输出;LANG 一般以 .UTF-8 结尾,例如 zh_CN.UTF-8 或 en_US.UTF-8。如果 locale 显示 C 或 POSIX,文本读入阶段就可能出问题,可以先临时改成 UTF-8 再复测。再确认解释器侧的默认编码:

python3 -c "import sys, locale; print(sys.getdefaultencoding(), locale.getpreferredencoding(False))"
iconv -f UTF-8 -t UTF-8 ./probe.txt > /dev/null && echo encoding-ok

期望输出里两项都是 utf-8 与 UTF-8;iconv 成功会打印 encoding-ok,失败说明文本文件本身不是 UTF-8,需要先转码再喂给合成。日志里出现 missing glyph、fallback font、unsupported encoding 这类字样,基本可以确认是字体或编码问题,而不是语音包问题。字体装上以后要重启进程,缓存不一定自动刷新。

用一条短文本跑最小合成

链路验证不要拿长文和多语言混排去试。准备一个 probe.txt,内容只放一句目标语言的短句,十到二十字,不要标点混排、不要夹杂英文单词。然后写一个最小配置:

{
  'voice_pack': './voices/zh-CN-female-01',
  'language': 'zh-CN',
  'text_file': './probe.txt',
  'output': './probe.wav',
  'format': 'wav',
  'sample_rate': 24000,
  'channels': 1
}

配置文件字段名以本机版本为准,上面是可替换的骨架;如果工具支持命令行参数,等价写法是 yovoice synth `--config` probe.json 这类形式。成功判据建议同时满足四条:

  • 退出码为 0,执行后立刻 echo $? 确认;
  • 输出文件存在且大小不为 0,用 ls -l probe.wav 看;
  • 容器信息与配置一致,file probe.wav 应显示 WAVE audio、对应采样率与单声道;
  • 日志里没有 missing voice、unsupported language、encoding 等报错。

这一条通了,再换第二种语言重复一次,每种语言都单独跑一遍,不要图省事把多语言塞进同一次合成。

对比不同输出格式与采样率的导出结果

格式决定交给谁用,采样率决定后处理会不会二次重采样。设置顺序建议是:先确认接收端能读什么格式,再定采样率,再定声道,最后才回头调语音包的合成参数。改后缀名不是换格式,必须走编码参数。

YoVoice 本地跑多语言音频,先确认语音包和输出格式
格式采样率声道适用场景
WAV24000单声道剪辑中间产物,体积大但兼容性好
WAV44100 或 48000单声道需要与视频工程采样率对齐时
MP344100 或 48000单声道发布端交付,注意低采样率兼容性
FLAC44100 或 48000单声道需要无损留档
OGG / Opus48000单声道对体积敏感的分发场景

几个容易踩的点:语音合成常见的 16k、22.05k、24k 采样率,导入某些剪辑软件后会被自动重采样,听感变化不大但文件时长可能微调;MP3 编码器对低采样率支持不一,导出时如果报参数不支持,就先把采样率提到 44100 再试。人声基本都是单声道,强行导立体声只是把同样的数据复制到两个声道,不会更好听。建议固定一组“中间格式 + 交付格式”的组合,别每个文件都换一套参数。

把多语言文本拆成单语文件再批量送入

混语文本常触发错误切分:语言识别在句内来回切换,合成结果会中途换音色甚至直接失败。更稳的做法是在源文本里给每行加语言码标记,先拆成单语文件,再逐文件合成。下面是一个可替换的脚本骨架:

import re, pathlib

src = pathlib.Path('all.txt').read_text(encoding='utf-8')
blocks, cur = {}, None
for line in src.splitlines():
    m = re.match(r'^\[([a-zA-Z]{2}(?:-[A-Z]{2})?)\]\s*(.*)$', line)
    if m:
        cur = m.group(1)
        blocks.setdefault(cur, []).append(m.group(2))
    elif cur and line.strip():
        blocks[cur].append(line)

for lang, lines in blocks.items():
    p = pathlib.Path('split') / f'{lang}.txt'
    p.parent.mkdir(exist_ok=True)
    p.write_text('\n'.join(lines) + '\n', encoding='utf-8')
    print(lang, len(lines))

拆分之后做三项校验:每个文件行数之和等于原文有效行数,避免漏行;每个语言码都出现在前面的 wanted.txt 覆盖清单里,避免拆出没有语音包的语言;每个文件首尾各抽一句试听确认发音正确。校验命令可以直接跑:

wc -l split/*.txt
for f in split/*.txt; do lang=$(basename "$f" .txt); grep -qx "$lang" wanted.txt || echo "未覆盖: $lang"; done

批量合成时建议逐文件调用、失败不中断,把结果统一写进日志,便于回查是哪个语言、哪个文件出的问题:

for f in split/*.txt; do
  lang=$(basename "$f" .txt)
  yovoice synth `--voice-pack` "./voices/$lang" \
    `--text-file` "$f" `--output` "out/$lang.wav" >> synth.log 2>&1 \
    || echo "fail: $f" >> synth.log
done

其中 `--voice-pack` 的路径拼接方式要按实际目录名调整,很多语音包目录名带音色后缀,直接拼语言码会找不到。批量跑完先抽查几个输出文件,用 file 确认格式,再交给后续流程。