РУКОВОДСТВО

OpenAI-совместимый сервер озвучки на вашем компьютере

Многие программы говорят через API озвучки OpenAI: Open WebUI, SillyTavern и всё, что написано на OpenAI SDK, позволяют сменить базовый адрес и отправлять POST /v1/audio/speech на другой сервер. Укажите им свой сервер — и голоса будут работать на вашем компьютере, бесплатно и офлайн.

В этом руководстве такой сервер собирается примерно в 70 строк Python на бесплатном движке SuperClonica. Мы проверили его официальным Python-клиентом OpenAI: он отдаёт MP3, WAV или сырой PCM и проверяет необязательный ключ.

Установка

Нужен Python 3.10 или новее. Установите движок, веб-сервер и аудиокодер:

pip install superclonica-cpu fastapi uvicorn av

При первом запуске движок скачивает модель Supertonic 3, около 400 МБ; дальше он работает офлайн на обычном процессоре, а видеокарта 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 как endpoint, 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 как обычно.