#!/usr/bin/env python3 """OpenAI-compatible shim in front of sglang-omni s2-pro (port 8001). Keeps the public contract: model s2pro-egy, named voices, 24 kHz output. POST /v1/audio/speech {model, input, voice, response_format wav|pcm, stream, sample_rate=24000, temperature, top_p} GET /health /v1/models /v1/voices """ import argparse import io import json import struct import time import urllib.request from pathlib import Path import numpy as np import soundfile as sf import uvicorn from fastapi import FastAPI, HTTPException, Response from fastapi.responses import StreamingResponse from loguru import logger from pydantic import BaseModel MODEL_ID = "s2pro-egy" VOICES_DIR = Path("/opt/work/voices") UPSTREAM = "http://localhost:8001/v1/audio/speech" UPSTREAM_MODEL = "/opt/work/checkpoints/s2pro-egy-merged" SRC_RATE = 44100 app = FastAPI() VOICES = {} def load_voices(): VOICES.clear() for wav in sorted(VOICES_DIR.glob("*.wav")): txt = wav.with_suffix(".txt") if txt.exists(): VOICES[wav.stem] = { "audio_path": str(wav), "text": txt.read_text(encoding="utf-8").strip(), } logger.info(f"voices: {list(VOICES)}") def resample(audio: np.ndarray, src: int, dst: int) -> np.ndarray: if src == dst: return audio import torch import torchaudio.functional as AF t = torch.from_numpy(np.ascontiguousarray(audio, dtype=np.float32)) return AF.resample(t, src, dst).numpy() def wav_stream_header(sample_rate: int, channels: int = 1, bits: int = 16) -> bytes: byte_rate = sample_rate * channels * bits // 8 block_align = channels * bits // 8 return b"".join([ b"RIFF", struct.pack(" {UPSTREAM}") uvicorn.run(app, host="0.0.0.0", port=args.port, log_level="warning") if __name__ == "__main__": main()