open_notebook/services/stt_server.py

64 lines
1.9 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""OpenAI-compatible STT wrapper around faster-whisper, for Open Notebook.
Implements exactly the endpoint esperanto's OpenAICompatibleSpeechToTextModel calls:
POST /audio/transcriptions (multipart: file, model, language?, prompt?) -> {"text": ...}
Start:
python3 stt_server.py
Env vars:
STT_HOST (default 0.0.0.0), STT_PORT (default 8902)
WHISPER_MODEL (default large-v3), WHISPER_COMPUTE_TYPE (default float16)
CUDA_VISIBLE_DEVICES should be set by the caller (e.g. "1,2") to keep GPU 0 free.
"""
from __future__ import annotations
import os
import tempfile
from pathlib import Path
from faster_whisper import WhisperModel
from fastapi import FastAPI, File, Form, UploadFile
app = FastAPI(title="faster-whisper STT (OpenAI-compatible)", version="1.0")
_MODEL_SIZE = os.environ.get("WHISPER_MODEL", "large-v3")
_COMPUTE_TYPE = os.environ.get("WHISPER_COMPUTE_TYPE", "float16")
_model = WhisperModel(_MODEL_SIZE, device="cuda", compute_type=_COMPUTE_TYPE)
@app.get("/health")
def health():
return {"status": "ok", "model": _MODEL_SIZE}
@app.post("/audio/transcriptions")
async def transcriptions(
file: UploadFile = File(...),
model: str = Form(default="whisper-1"),
language: str | None = Form(default=None),
prompt: str | None = Form(default=None),
):
suffix = Path(file.filename or "audio").suffix or ".wav"
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
tmp.write(await file.read())
tmp_path = tmp.name
try:
segments, _info = _model.transcribe(
tmp_path,
language=language,
initial_prompt=prompt,
)
text = "".join(segment.text for segment in segments).strip()
finally:
os.unlink(tmp_path)
return {"text": text}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host=os.environ.get("STT_HOST", "0.0.0.0"),
port=int(os.environ.get("STT_PORT", "8902")))