指南

在自己电脑上运行的 OpenAI 兼容 TTS 服务器

请注意:Supertonic 3 目前不支持中文,文中的 SuperClonica 声音可朗读其支持的 31 种语言。许多应用通过 OpenAI 语音 API 说话:Open WebUI、SillyTavern 以及所有基于 OpenAI SDK 的程序,都允许修改基础 URL,把 POST /v1/audio/speech 发到其他服务器。指向你自己的服务器,声音就在你的电脑上免费、离线运行。

本指南用免费的 SuperClonica 引擎,以约 70 行 Python 搭建这样的服务器。我们用官方 OpenAI Python 客户端测试过:它返回 MP3、WAV 或原始 PCM,并可校验可选的密钥。

安装

需要 Python 3.10 或更高版本。安装引擎、Web 服务器和音频编码器:

pip install superclonica-cpu fastapi uvicorn av

首次运行时引擎会下载约 400 MB 的 Supertonic 3 模型;之后在普通处理器上离线运行,NVIDIA 显卡只是让它更快。

服务器

把以下代码保存为 openai_tts.py:

import io, os, wave
import av
import numpy as np
from fastapi import FastAPI, Header, HTTPException
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import Response
from pydantic import BaseModel
from superclonica import Superclonica

VOICES = {name: name for name in ["M1", "M2", "M3", "M4", "M5", "F1", "F2", "F3", "F4", "F5"]}
# a catalog voice: VOICES["narrator"] = "/path/to/voice-file-from-the-catalog"
LANGUAGE = os.environ.get("TTS_LANGUAGE", "en")
KEY = os.environ.get("TTS_KEY", "")  # leave empty on a private machine
app = FastAPI()
tts = Superclonica()

class Speech(BaseModel):
    input: str
    voice: str = "F1"
    model: str = "superclonica"
    response_format: str = "mp3"

def to_wav(pcm, rate):
    buffer = io.BytesIO()
    with wave.open(buffer, "wb") as file:
        file.setnchannels(1)
        file.setsampwidth(2)
        file.setframerate(rate)
        file.writeframes(pcm.tobytes())
    return buffer.getvalue()

def to_mp3(pcm, rate):
    buffer = io.BytesIO()
    with av.open(buffer, "w", format="mp3") as container:
        stream = container.add_stream("libmp3lame", rate=rate, layout="mono")
        frame = av.AudioFrame.from_ndarray(pcm.reshape(1, -1), format="s16", layout="mono")
        frame.rate = rate
        for packet in stream.encode(frame):
            container.mux(packet)
        for packet in stream.encode(None):
            container.mux(packet)
    return buffer.getvalue()

def speak(request):
    voice = VOICES.get(request.voice, "F1")
    audio, rate = tts.synthesize(request.input, voice=voice, lang=LANGUAGE)
    pcm = (np.clip(np.asarray(audio, dtype=np.float32).reshape(-1), -1, 1) * 32767).astype(np.int16)
    if request.response_format == "wav":
        return to_wav(pcm, rate), "audio/wav"
    if request.response_format == "pcm":
        return pcm.tobytes(), "audio/pcm"
    return to_mp3(pcm, rate), "audio/mpeg"

@app.post("/v1/audio/speech")
async def speech(request: Speech, authorization: str = Header("")):
    if KEY and authorization != "Bearer " + KEY:
        raise HTTPException(401)
    audio, media_type = await run_in_threadpool(speak, request)
    return Response(audio, media_type=media_type)

@app.get("/v1/models")
def models():
    return {"object": "list", "data": [{"id": "superclonica", "object": "model"}]}

@app.get("/v1/audio/voices")
def voices():
    return {"voices": list(VOICES)}

启动后它在 8880 端口响应:

uvicorn openai_tts:app --host 127.0.0.1 --port 8880

voice 字段接受十个标准声音之一:M1 至 M5 和 F1 至 F5;未知名称会回退到 F1,所以发送 alloy 的客户端也能得到回应。把 TTS_LANGUAGE 设为应用使用的语言:引擎支持 31 种语言,但暂不支持中文。如果除你之外还有人能访问该端口,请设置 TTS_KEY。

连接 Open WebUI

打开 Admin Panel > Settings > Audio。把 Text-to-Speech Engine 设为 OpenAI,API Base URL 设为 http://localhost:8880/v1,TTS Model 设为 superclonica,TTS Voice 设为某个标准声音。Open WebUI 要求 API 密钥非空:填入你的 TTS_KEY,若留空则填任意文字。如果 Open WebUI 运行在 Docker 中,请用 --host 0.0.0.0 启动服务器并使用 http://host.docker.internal:8880/v1;在 Linux 上容器需要 --add-host=host.docker.internal:host-gateway。Response Splitting 保持 punctuation,长回答会在第一句后就开始朗读。

连接 SillyTavern

打开 Extensions > TTS,选择 OpenAI Compatible 提供方。端点填 http://localhost:8880/v1/audio/speech,模型填 superclonica,填入密钥,并手动输入声音列表,因为该提供方不会自动获取:M1,M2,M3,M4,M5,F1,F2,F3,F4,F5。然后在声音映射中为每个角色和你的人设分配声音,点击 Apply。

角色声音与其他客户端

要使用 SuperClonica 目录中的声音,下载其声音文件并添加一行,例如 VOICES["narrator"] = "/path/to/the/file";之后 narrator 这个名字在所有客户端中都可用,包括 SillyTavern 的声音列表。任何基于 OpenAI SDK 的程序也可以:把 base_url 设为 http://localhost:8880/v1 创建客户端,照常调用 audio.speech.create。