# 🎙️ 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")