Skip to content

tts — 语音合成 ​

接云端 TTS 或本地合成引擎。当前引擎互斥。支持音色表 / 克隆音色 / 流式 / 语速。


声明 ​

json
{
  "id": "myteam.tts.my_voice",
  "name": "My Voice TTS",
  "version": "1.0.0",
  "target_app_version": "0.2",
  "type": "tts",
  "entrypoint": "my_tts.py:MyTTSProvider",
  "tags": ["cloud"],
  "requires_hf": false,
  "capabilities": {
    "voices": true,
    "voice_clone": true,
    "voice_remove": true,
    "speed": false
  },
  "config_schema": [
    { "key": "api_key", "label": "API Key", "type": "password", "is_secret": true }
  ]
}

capabilities 的含义与「声明式 vs 类属性」两种等价写法见 能力声明与只读契约。


最小实现 ​

python
import numpy as np
from transbox.plugins.base import BaseTTSProvider


class CustomTTSProvider(BaseTTSProvider):
    supports_voice_clone = True   # True → 设置页出现「注册克隆音色」面板(含软件内录制)
    supports_speed = False        # False → 宿主统一做保音高变速,你什么都不用做

    @property
    def sample_rate(self) -> int:
        return 24000

    @property
    def is_streaming_capable(self) -> bool:
        return True

    def synthesize(self, text, voice_id="", voice=None, language="zh"):
        # 返回 (float32 单声道 PCM, 采样率)
        return np.zeros(24000, dtype=np.float32), self.sample_rate

    def stream_tts(self, text, voice_id="", voice=None, language="zh"):
        """可选:按块 yield(宿主按预缓冲逐块播,起播更早)。
        未实现时宿主退化为整段 synthesize。"""
        yield self.synthesize(text, voice_id, voice, language)[0]

参数约定:

  • voice 优先于 voice_id(宿主可能只传其中一个)。
  • 返回 (float32 单声道 PCM, 采样率)。
  • 音色配置键:宿主把用户选中的音色写进插件配置的 voice 键 (分语种时还有 voice_en / voice_other,缺省回落 voice)。 实现里读 self._config.get("voice") 即可。

音色表 ​

python
def list_voices(self) -> list[dict]:
    return [
        {"id": "vivian", "name": "薇薇安", "lang": "zh", "kind": "speaker"},
        {"id": "my-clone", "name": "我的音色", "lang": "zh", "kind": "registered"},
    ]
  • kind="speaker" = 模型自带音色;kind="registered" = 用户注册的克隆音色。
  • 只有 kind="registered" 的音色才会显示「删除音色」。
  • 只返回真实可用的音色(例:base 变体没有内置 speaker,就只返回已注册的克隆音色)。
  • 只读方法:服务未启动时返回已缓存/本地保存的音色表或空表, 绝不为了列音色而拉起服务(见 能力声明)。

克隆音色 ​

python
def register_voice(self, voice_name, audio_samples, reference_text="", sample_rate=24000) -> bool:
    """audio_samples: float32 单声道 numpy,1~30 秒。"""
    ...

def remove_voice(self, voice_name: str) -> bool:
    ...
  • 参考音频由宿主提供:用户选文件,或者在设置页内直接录制 (宿主负责麦克风采集、写 WAV、读回 float32)。 插件不需要碰麦克风、文件对话框或音频格式转换。
  • 你只负责转码 / 上传 / 落盘,返回 True/False。

克隆音色的持久化(强制) ​

若你的音色只注册在服务端内存里(进程重启即丢失),必须自己持久化:

  1. 把参考音频与 ref_text 存到插件私有数据目录(self.get_data_dir());
  2. 在服务就绪后自动补注册:对比服务端 GET /v1/audio/voices,缺哪条补哪条; 同一服务实例只跑一次(服务换实例 / 重启 / 换设备 / 换端口后重跑);
  3. remove_voice() 同步删本地;
  4. list_voices() 在服务未启动时返回本地保存的音色(kind="registered"),不得拉起服务。

否则用户每次重启应用都得重新注册音色。实测参考实现: extensions/community_qwen3_tts(_persist_voice / _restore_voices / _read_voice_index)。


语速 ​

情况宿主行为你要做什么
supports_speed = False(默认)合成后统一做保音高时间伸缩(宿主自带 DSP)什么都不用做
supports_speed = True把 speed=(1.0 = 原速,范围 0.5~2.0)传给你自己在模型内实现

流式 × 语速:speed != 1.0 且插件未声明 supports_speed 时,宿主不会调用 stream_tts(整段才好做时间伸缩)。


多能力模型(变体) ​

同一模型仓库里有多个互斥变体时(例:base 克隆音色 / customvoice 内置音色 / voicedesign 文字描述音色),把它们声明成多个 model_artifacts,并在 model_artifacts() 里把当前生效的那份排到第一位——宿主模型卡只展示/下载第一个同 category 的工件, 用户不会被逼把每个 ~1.1 GB 的变体都下下来,也不会被误报「未就绪」。

python
def model_artifacts(self) -> list[dict]:
    arts = [...]                       # 你的全部变体
    active = self._resolve_variant()   # 按配置 / 已下载情况选出生效的
    arts.sort(key=lambda a: 0 if a["id"] == active else 1)
    return arts

见 模型工件 § 多能力模型。


本地服务型 TTS(推荐:audio.cpp 与 BaseLocalTTSProvider) ​

自己起本地服务或使用 GGUF 语音模型的,推荐结合宿主运行库与标准基类开发:

  1. 声明依赖宿主运行库(mod.json):
    json
    {
      "requires_runtime": [
        { "id": "audiocpp_runtime", "reason": "提供 audio.cpp 本地推理运行时(CUDA 13 / Vulkan / CPU)" }
      ]
    }

    接 Qwen3-TTS 用另一份内置运行库:tts_server_runtime(tts-server.exe, OpenAI 兼容 /v1/audio/speech + 克隆音色注册表), 启动参数与 HTTP 契约见 本地 TTS 服务。

  2. 继承 BaseLocalTTSProvider:
    • 绝不在 initialize() 启动服务,ensure_loaded() 或合成入口才按需启动;
    • 自动获得 GPU/CPU 设备热切(CUDA 13 / Vulkan / CPU)、端口冲突清理与 Windows JobObject 进程/显存彻底回收;
    • 完整参考代码见 本地服务型引擎强制契约 § 示例 2。
  3. 轻量纯 CPU 推理方案:
    • 若模型结构为标准 VITS/ONNX(如 MeloTTS),可通过 sherpa-onnx 在进程内加载运行,无需拉起外部服务进程。

参考实现 ​

目录类型
extensions/community_cloud_mimo_tts/云端 API
extensions/workshop_openai_tts/OpenAI 兼容(kokoro-fastapi / GPT-SoVITS / fish-speech…)
extensions/community_qwen3_tts/本地服务型 + 克隆音色 + 变体(完整范例)

相关 ​

TransBox - 赋能全人类无界限跨语言交流