가이드

내 컴퓨터에서 돌아가는 OpenAI 호환 TTS 서버

많은 앱이 OpenAI 음성 API로 말합니다. Open WebUI, SillyTavern, OpenAI SDK로 만든 모든 프로그램은 기본 URL을 바꿔 POST /v1/audio/speech를 다른 서버로 보낼 수 있습니다. 내 서버를 지정하면 목소리가 내 컴퓨터에서 무료로, 오프라인으로 돌아갑니다.

이 가이드는 무료 SuperClonica 엔진으로 약 70줄의 Python 서버를 만듭니다. 공식 OpenAI Python 클라이언트로 테스트했으며 MP3, WAV, 원시 PCM을 반환하고 선택적 키를 확인합니다.

설치

Python 3.10 이상이 필요합니다. 엔진, 웹 서버, 오디오 인코더를 설치하세요:

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 필드는 기본 목소리 10개(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를 호출하세요.