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 # e.g. "tts-1" input: str # the text prompt voice: str = "tara" # optional voice parameter format: str = "wav" # "wav" or "mp3" @app.post( "/v1/audio/speech", summary="Generate speech", response_class=Response, # raw bytes ) def tts_speech(req: OpenAITTSRequest): # Load your Chutes token chutes_token = os.getenv("CHUTES_API_TOKEN") if not chutes_token: raise HTTPException(status_code=500, detail="Chutes API token not configured") # Call Chutes TTS 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}" ) # If Chutes returns JSON-wrapped base64, extract and decode content_type = f"audio/{req.format}" try: segments = resp.json() for seg in segments: if seg.get("type") == "audio" and seg.get("data"): # data:audio/wav;base64,AAAA... prefix, b64 = seg["data"].split(",", 1) audio_bytes = base64.b64decode(b64) return Response(content=audio_bytes, media_type=content_type) except ValueError: # Not JSON — assume raw bytes 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"}