语音合成 - 用 Python 合成藏语三大方言语音

通过使用藏语语音合成技术,可以把一段藏文文字,快速变成可用的音频,用在短视频、朗读、课件或字幕配音里。本文介绍一套 Python 脚本,可以直接合成藏语三大方言的语音:

  • 卫藏方言:facebook/mms-tts-bod ([Hugging Face][1])
  • 康巴方言:facebook/mms-tts-khg ([Hugging Face][2])
  • 安多方言:facebook/mms-tts-adx ([Hugging Face][3])

它们都属于 Meta 的 MMS-TTS 系列,并且在 Transformers 4.33+ 就能直接调用。([Hugging Face][3])


语音合成技术可以用来干什么?

  1. 语言学习短视频配音

    每行一句藏文文字,批量合成音频;导入剪映等视频编辑工具,配字幕、配画面,快速出片。

  2. 课件/朗读材料配音

    把教材段落放进文本文件,生成"逐句音频 + 合并后的整段音频",课堂或自学都方便。

  3. 多方言对比

    同一段藏文,用卫藏/康巴/安多方言各合成一版,做对比学习或演示。


准备工作

1)安装依赖

在命令行里执行:

bash 复制代码
pip install -U transformers accelerate torch soundfile numpy

可选(推荐,但没有也能跑):

bash 复制代码
pip install -U scipy

2)准备一个文本文件(每行一句)

例如:sentences_tbt.txt

  • 每行写一条藏文句子
  • 空行会自动跳过
  • 也可以写代码来把文本分割为句子

最简单的用法:改 3 行就能跑

新建文件:tibetan_tts_easy.py,复制下面全部代码。你只需要改最上面的三行:方言、输入文件名、输出目录。

这份脚本会同时生成:

  • 逐句 wav:adx_0001.wavadx_0002.wav...
  • 合并后的整段 wav:adx_full.wav
    并且强制输出 PCM_16,避免"播放器能播但剪映无声"的问题。
python 复制代码
from transformers import VitsModel, AutoTokenizer
import torch
import soundfile as sf
import numpy as np
import os
from math import gcd

# ========== 你只需要改这 3 行 ==========
DIALECT = "adx"            # "bod"(卫藏方言) / "khg"(康巴方言) / "adx"(安多方言)
INPUT_FILE = "sentences_tbt.txt"
OUTPUT_DIR = "wavs_adx"
# ===================================

# 句子之间停顿(秒)
GAP_SECONDS = 0.2

# 为了剪映/剪辑软件兼容:强制写 PCM16
WAV_SUBTYPE = "PCM_16"

# 可选:统一采样率到 48000(更适合剪映/PR)
TARGET_SR = 48000  # 不想重采样可改成 None

DIALECT_MODELS = {
    "bod": "facebook/mms-tts-bod",  # :contentReference[oaicite:4]{index=4}
    "khg": "facebook/mms-tts-khg",  # :contentReference[oaicite:5]{index=5}
    "adx": "facebook/mms-tts-adx",  # :contentReference[oaicite:6]{index=6}
}

DEVICE = "cuda" if torch.cuda.is_available() else "cpu"

def read_sentences(path):
    out = []
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            t = line.strip()
            if t:
                out.append(t)
    return out

def normalize(audio, target_peak=0.95):
    audio = audio.astype(np.float32, copy=False)
    peak = float(np.max(np.abs(audio)) + 1e-12)
    if peak > 0:
        audio = np.clip(audio / peak * target_peak, -1.0, 1.0)
    return audio

def resample_if_needed(audio, orig_sr, target_sr):
    if target_sr is None or target_sr == orig_sr:
        return audio, orig_sr
    try:
        import scipy.signal
    except ImportError:
        print("[WARN] 未安装 scipy,跳过重采样,使用原采样率输出。")
        return audio, orig_sr

    g = gcd(orig_sr, target_sr)
    up = target_sr // g
    down = orig_sr // g
    audio2 = scipy.signal.resample_poly(audio, up, down).astype(np.float32)
    return audio2, target_sr

def main():
    model_name = DIALECT_MODELS[DIALECT]
    print(f"Loading {model_name} on {DEVICE} ...")
    model = VitsModel.from_pretrained(model_name).to(DEVICE)
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    os.makedirs(OUTPUT_DIR, exist_ok=True)

    sents = read_sentences(INPUT_FILE)
    print(f"Loaded {len(sents)} lines from {INPUT_FILE}")
    if not sents:
        return

    full_path = os.path.join(OUTPUT_DIR, f"{DIALECT}_full.wav")
    full_writer = None
    full_sr = None

    for i, text in enumerate(sents, start=1):
        inputs = tokenizer(text, return_tensors="pt").to(DEVICE)
        with torch.no_grad():
            wav = model(**inputs).waveform.squeeze().cpu().numpy()

        wav = wav.reshape(-1)  # 保证一维
        wav = normalize(wav)

        sr = int(model.config.sampling_rate)
        wav, sr = resample_if_needed(wav, sr, TARGET_SR)

        if full_writer is None:
            full_sr = sr
            gap = np.zeros(int(full_sr * GAP_SECONDS), dtype=np.float32)
            full_writer = sf.SoundFile(
                full_path, mode="w", samplerate=full_sr, channels=1,
                subtype=WAV_SUBTYPE, format="WAV"
            )
            print("FULL:", full_path)

        seg_path = os.path.join(OUTPUT_DIR, f"{DIALECT}_{i:04d}.wav")
        sf.write(seg_path, wav, full_sr, subtype=WAV_SUBTYPE)

        full_writer.write(wav)
        if GAP_SECONDS > 0:
            full_writer.write(gap)

        print(f"[{i}/{len(sents)}] {seg_path}")

    full_writer.close()
    print("Done.")

if __name__ == "__main__":
    main()

运行:

bash 复制代码
python tibetan_tts_easy.py

导入剪映的最稳设置(避免"无声")

你只要用上面脚本输出的 WAV(PCM_16),一般直接导入就会有波形和声音。原因是模型页本身给的是波形输出示例,但很多人保存时会写成 float WAV,剪辑软件兼容性不稳定;而 MMS 模型在 Transformers 4.33+ 可直接推理,这点在模型页也写得很清楚。([Hugging Face][3])


常见问题

1)能不能选"男声/女声/不同音色"?

就这三个 MMS 藏语方言 checkpoint 来说,你可以理解为:每个方言模型基本就是一个固定声音风格。它们主要提供"能把文本读出来"的能力,而不是"内置很多音色可选"的商业 TTS 音色库。([Hugging Face][3])

2)能不能控制语速?

对非技术用户,最稳的方式是:合成完再变速(不会影响能不能合成)。例如把音频加速 1.15 倍:

bash 复制代码
ffmpeg -y -i input.wav -filter:a "atempo=1.15" output.wav

3)同一句话每次合成略有差别,正常吗?

正常。VITS 合成本身有非确定性;如果你希望"每次一样",可以固定随机种子(Transformers 的 VITS 文档也专门提醒了这点)。([Hugging Face][4])


使用许可提醒(建议写在文末,避免踩坑)

这几个模型页面都标注了许可证为 CC BY-NC 4.0(非商业)。如果你用于商业用途(付费课程、付费 App、广告变现等),务必先确认许可范围。([Hugging Face][3])

相关推荐
mikejahn3 小时前
爬取CECS网站征求意见栏目的最新信息
python
占疏3 小时前
dify API访问工作流/聊天
开发语言·数据库·python
似水এ᭄往昔3 小时前
【C++】--AVL树的认识和实现
开发语言·数据结构·c++·算法·stl
aningxiaoxixi4 小时前
TTS 之 PYTHON库 pyttsx3
开发语言·python·语音识别
深蓝海拓4 小时前
PySide6从0开始学习的笔记(三) 布局管理器与尺寸策略
笔记·python·qt·学习·pyqt
有点。4 小时前
C++ ⼀级 2023 年06 ⽉
开发语言·c++
Mr.Jessy4 小时前
JavaScript高级:深入对象与内置构造函数
开发语言·前端·javascript·ecmascript
charlie1145141914 小时前
编写INI Parser 测试完整指南 - 从零开始
开发语言·c++·笔记·学习·算法·单元测试·测试
数据科学项目实践4 小时前
建模步骤 3 :数据探索(EDA) — 1、初步了解数据:常用函数
人工智能·python·机器学习·数据挖掘·数据分析·pandas·数据可视化