CoolFace
Apppublic

shift0x/smart_turn_v3

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
app.py173 linesDownload Raw Back to root
1import io2import math3import os4import logging5from typing import List, Optional6 7import numpy as np8import soundfile as sf9from scipy.signal import resample_poly10from fastapi import FastAPI, UploadFile, File, HTTPException11from fastapi.middleware.cors import CORSMiddleware12from pydantic import BaseModel13 14# Import your provided inference module (must be in the same repo)15# It should contain: predict_endpoint(audio_array: np.ndarray) -> dict16# and load the ONNX model "smart-turn-v3.0.onnx" at import time.17try:18    import inference as inf19except Exception as e:20    raise RuntimeError(21        "Failed to import inference.py. Ensure inference.py and smart-turn-v3.0.onnx "22        "are present in the repository."23    ) from e24 25logger = logging.getLogger("uvicorn")26logger.setLevel(logging.INFO)27 28TARGET_SR = 1600029 30app = FastAPI(title="Smart Turn Endpoint Detector", version="1.0.0")31 32# Allow cross-origin requests (optional)33app.add_middleware(34    CORSMiddleware,35    allow_origins=["*"],36    allow_credentials=True,37    allow_methods=["*"],38    allow_headers=["*"],39)40 41 42class ArrayInput(BaseModel):43    # Raw 1-D samples; can be any sample rate (will resample to 16 kHz)44    samples: List[float]45    sample_rate: Optional[int] = TARGET_SR  # default 16k if not provided46 47 48def _to_mono_float32(y: np.ndarray) -> np.ndarray:49    if y.ndim == 2:50        y = y.mean(axis=1)51    return y.astype(np.float32, copy=False)52 53 54def _resample_to_target_sr(y: np.ndarray, orig_sr: int, target_sr: int = TARGET_SR) -> np.ndarray:55    if orig_sr == target_sr:56        return y.astype(np.float32, copy=False)57    g = math.gcd(orig_sr, target_sr)58    up = target_sr // g59    down = orig_sr // g60    y_resampled = resample_poly(y, up=up, down=down)61    return y_resampled.astype(np.float32, copy=False)62 63 64def _load_audio_from_upload(file_bytes: bytes) -> tuple[np.ndarray, int]:65    # Decode with soundfile (supports wav, flac, ogg; not mp3)66    try:67        with sf.SoundFile(io.BytesIO(file_bytes)) as f:68            y = f.read(dtype="float32", always_2d=False)69            sr = int(f.samplerate)70    except Exception as e:71        raise HTTPException(72            status_code=400,73            detail=f"Could not read audio file. Ensure it's WAV/FLAC/OGG and not corrupted. Error: {e}",74        )75    y = _to_mono_float32(np.asarray(y))76    return y, sr77 78 79@app.get("/")80def root():81    return {82        "name": "Smart Turn Endpoint Detector",83        "status": "ok",84        "model_file": getattr(inf, "ONNX_MODEL_PATH", "smart-turn-v3.0.onnx"),85        "target_sample_rate_hz": TARGET_SR,86        "endpoints": {87            "health": "/healthz",88            "predict_file": "/predict/file",89            "predict_array": "/predict/array",90        },91    }92 93 94@app.get("/healthz")95def healthz():96    try:97        _ = inf.session98        _ = inf.feature_extractor99    except Exception as e:100        raise HTTPException(status_code=500, detail=f"Model not ready: {e}")101    return {"status": "healthy"}102 103 104@app.post("/predict/file")105async def predict_file(audio_file: UploadFile = File(...)):106    """107    Accepts an uploaded audio file (WAV/FLAC/OGG). MP3 is not supported by libsndfile.108    The audio will be converted to mono and resampled to 16 kHz before inference.109    """110    file_bytes = await audio_file.read()111    if not file_bytes:112        raise HTTPException(status_code=400, detail="Empty file upload.")113 114    y, sr = _load_audio_from_upload(file_bytes)115    y16 = _resample_to_target_sr(y, sr, TARGET_SR)116 117    result = inf.predict_endpoint(y16)118 119    return {120        "prediction": int(result["prediction"]),121        "probability": float(result["probability"]),122        "input_info": {123            "filename": audio_file.filename,124            "original_sample_rate_hz": sr,125            "original_num_samples": int(len(y)),126            "original_duration_s": float(len(y) / max(sr, 1)),127        },128        "preproc_info": {129            "sample_rate_hz": TARGET_SR,130            "num_samples": int(len(y16)),131            "duration_s": float(len(y16) / TARGET_SR),132        },133    }134 135 136@app.post("/predict/array")137def predict_array(body: ArrayInput):138    """139    Accepts raw samples in JSON:140    {141      "samples": [ ... floats ... ],142      "sample_rate": 16000   // optional, defaults to 16000143    }144    """145    if not body.samples:146        raise HTTPException(status_code=400, detail="`samples` list is empty.")147 148    y = _to_mono_float32(np.asarray(body.samples, dtype=np.float32))149    sr = int(body.sample_rate) if body.sample_rate else TARGET_SR150    y16 = _resample_to_target_sr(y, sr, TARGET_SR)151 152    result = inf.predict_endpoint(y16)153 154    return {155        "prediction": int(result["prediction"]),156        "probability": float(result["probability"]),157        "input_info": {158            "original_sample_rate_hz": sr,159            "original_num_samples": int(len(y)),160            "original_duration_s": float(len(y) / max(sr, 1)),161        },162        "preproc_info": {163            "sample_rate_hz": TARGET_SR,164            "num_samples": int(len(y16)),165            "duration_s": float(len(y16) / TARGET_SR),166        },167    }168 169 170if __name__ == "__main__":171    import uvicorn172    port = int(os.environ.get("PORT", "7860"))173    uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False)