|
from fastapi import FastAPI, HTTPException, Response |
|
from pydantic import BaseModel |
|
import requests |
|
import base64 |
|
import os |
|
|
|
app = FastAPI(title="OpenAI‑Compatible TTS Adapter") |
|
|
|
class OpenAITTSRequest(BaseModel): |
|
model: str |
|
input: str |
|
voice: str = "tara" |
|
format: str = "wav" |
|
|
|
@app.post( |
|
"/v1/audio/speech", |
|
summary="Generate speech", |
|
response_class=Response, |
|
) |
|
def tts_speech(req: OpenAITTSRequest): |
|
|
|
chutes_token = os.getenv("CHUTES_API_TOKEN") |
|
if not chutes_token: |
|
raise HTTPException(status_code=500, detail="Chutes API token not configured") |
|
|
|
|
|
resp = requests.post( |
|
"https://chutes-orpheus-tts.chutes.ai/speak", |
|
headers={ |
|
"Authorization": f"Bearer {chutes_token}", |
|
"Content-Type": "application/json" |
|
}, |
|
json={"voice": req.voice, "prompt": req.input}, |
|
timeout=30 |
|
) |
|
|
|
if resp.status_code != 200: |
|
raise HTTPException( |
|
status_code=502, |
|
detail=f"Chutes TTS error: {resp.status_code} {resp.text}" |
|
) |
|
|
|
|
|
content_type = f"audio/{req.format}" |
|
try: |
|
segments = resp.json() |
|
for seg in segments: |
|
if seg.get("type") == "audio" and seg.get("data"): |
|
|
|
prefix, b64 = seg["data"].split(",", 1) |
|
audio_bytes = base64.b64decode(b64) |
|
return Response(content=audio_bytes, media_type=content_type) |
|
except ValueError: |
|
|
|
return Response(content=resp.content, media_type=content_type) |
|
|
|
raise HTTPException(status_code=502, detail="No audio in Chutes response") |
|
|
|
@app.get("/healthz") |
|
def health_check(): |
|
return {"status": "ok"} |