feat: Create Effi-STT - custom STT solution for OpenWebUI

- FastAPI server with OpenAI-compatible API
- Integration with faster-whisper (large-v3)
- Full Docker support with docker-compose
- Support for multiple languages (FR/EN/Auto)
- Clean, maintainable codebase (~100 lines)
- Health endpoint and error handling
- Volume caching for models
This commit is contained in:
Folzo
2026-04-05 22:20:18 +02:00
parent eb2c365220
commit 7c0a0a39ba
5 changed files with 359 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
# 🎙️ Effi-STT - Speech-to-Text pour Effi
# API OpenAI-compatible avec faster-whisper
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.responses import JSONResponse
import uvicorn
from faster_whisper import WhisperModel
import tempfile
import os
from typing import Optional
import logging
# Configuration du logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(
title="Effi-STT",
description="Speech-to-Text API compatible OpenAI pour Effi",
version="1.0.0"
)
# Configuration
MODEL_SIZE = os.getenv("MODEL_SIZE", "large-v3")
DEVICE = os.getenv("DEVICE", "cpu")
COMPUTE_TYPE = os.getenv("COMPUTE_TYPE", "int8")
LANGUAGE = os.getenv("LANGUAGE", "fr")
logger.info(f"🎙️ Chargement du modèle Whisper: {MODEL_SIZE}")
logger.info(f" Device: {DEVICE}, Compute type: {COMPUTE_TYPE}")
# Chargement du modèle au démarrage
model = WhisperModel(
MODEL_SIZE,
device=DEVICE,
compute_type=COMPUTE_TYPE,
cpu_threads=int(os.getenv("CPU_THREADS", "4"))
)
logger.info("✅ Modèle chargé avec succès!")
@app.get("/health")
async def health():
"""Endpoint de vérification de santé"""
return {"status": "healthy", "model": MODEL_SIZE}
@app.post("/v1/audio/transcriptions")
async def transcribe(
file: UploadFile = File(..., description="Fichier audio à transcrire"),
model_param: Optional[str] = Form(None, alias="model"),
language: Optional[str] = Form(LANGUAGE, description="Code langue (fr, en, auto)"),
prompt: Optional[str] = Form(None, description="Prompt optionnel"),
response_format: Optional[str] = Form("json", description="Format de réponse")
):
"""
Endpoint de transcription compatible API OpenAI
"""
try:
logger.info(f"📝 Transcription demandée: lang={language}, file={file.filename}")
# Sauvegarder le fichier temporairement
suffix = os.path.splitext(file.filename)[1] or ".mp3"
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
content = await file.read()
tmp.write(content)
tmp_path = tmp.name
logger.info(f" Fichier sauvegardé: {tmp_path} ({len(content)} bytes)")
try:
# Transcription avec faster-whisper
segments, info = model.transcribe(
tmp_path,
language=language if language != "auto" else None,
task="transcribe",
beam_size=5,
best_of=5,
condition_on_previous_text=True
)
# Concaténer tous les segments
text_parts = []
for segment in segments:
text_parts.append(segment.text)
full_text = " ".join(text_parts).strip()
logger.info(f"✅ Transcription réussie: '{full_text[:50]}...'")
logger.info(f" Langue détectée: {info.language}, Probabilité: {info.language_probability:.2f}")
# Format OpenAI standard
if response_format == "text":
return full_text
elif response_format == "srt":
# TODO: Implémenter format SRT
return full_text
elif response_format == "vtt":
# TODO: Implémenter format VTT
return full_text
else:
# Format JSON (défaut)
return JSONResponse(content={"text": full_text})
finally:
# Nettoyage
os.unlink(tmp_path)
except Exception as e:
logger.error(f"❌ Erreur transcription: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/v1/models")
async def list_models():
"""Liste les modèles disponibles (compatibilité OpenAI)"""
return {
"data": [
{
"id": "whisper-1",
"object": "model",
"created": 1677610602,
"owned_by": "openai",
"permission": [],
"root": "whisper-1",
"parent": None
}
],
"object": "list"
}
if __name__ == "__main__":
port = int(os.getenv("PORT", "8200"))
host = os.getenv("HOST", "0.0.0.0")
logger.info(f"🚀 Démarrage du serveur sur http://{host}:{port}")
logger.info(f" Endpoints:")
logger.info(f" - GET /health")
logger.info(f" - POST /v1/audio/transcriptions")
logger.info(f" - GET /v1/models")
uvicorn.run(app, host=host, port=port, log_level="info")