- 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
139 lines
4.6 KiB
Python
139 lines
4.6 KiB
Python
# 🎙️ 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")
|