app.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import tempfile
  2. import os
  3. import numpy as np
  4. import time
  5. import logging
  6. import os
  7. import sys
  8. from fastapi import FastAPI, UploadFile, File, WebSocket, WebSocketDisconnect
  9. from faster_whisper import WhisperModel
  10. def env_bool(name: str, default: bool = False) -> bool:
  11. return os.getenv(name, str(default)).lower() in ("1", "true", "yes", "on")
  12. LOG_LEVEL = os.getenv("WHISPER_LOG_LEVEL", "INFO").upper()
  13. VAD_ENABLED = env_bool("WHISPER_VAD", True)
  14. logging.basicConfig(
  15. level=getattr(logging, LOG_LEVEL, logging.INFO),
  16. format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
  17. stream=sys.stdout,
  18. force=True
  19. )
  20. logger = logging.getLogger("whisper-ws")
  21. logger.setLevel(getattr(logging, LOG_LEVEL, logging.INFO))
  22. logger.info(f"Log level = {LOG_LEVEL}")
  23. logger.info(f"VAD enabled = {VAD_ENABLED}")
  24. app = FastAPI()
  25. # =========================
  26. # Whisper model (shared)
  27. # =========================
  28. model = WhisperModel(
  29. "small",
  30. device="cuda",
  31. compute_type="float32" # GTX 1050 Ti compatible
  32. )
  33. # =========================
  34. # REST API (Swagger / tests)
  35. # =========================
  36. @app.post("/transcribe")
  37. async def transcribe(file: UploadFile = File(...)):
  38. with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
  39. tmp.write(await file.read())
  40. tmp_path = tmp.name
  41. segments, info = model.transcribe(
  42. tmp_path,
  43. language="fr",
  44. vad_filter=True
  45. )
  46. os.remove(tmp_path)
  47. text = " ".join(seg.text for seg in segments)
  48. return {
  49. "text": text,
  50. "language": info.language
  51. }
  52. # =========================
  53. # WebSocket STT (streaming)
  54. # =========================
  55. SAMPLE_RATE = 16000
  56. MIN_AUDIO_SEC = 0.5
  57. MAX_AUDIO_SEC = 10.0
  58. @app.websocket("/ws/transcribe")
  59. async def ws_transcribe(ws: WebSocket):
  60. await ws.accept()
  61. logger.info("WebSocket connecté !")
  62. audio_buffer = []
  63. start_time = time.time()
  64. try:
  65. while True:
  66. data = await ws.receive_bytes()
  67. logger.debug(f"Audio reçu: {len(data)} bytes")
  68. # PCM int16 → float32
  69. pcm = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0
  70. audio_buffer.append(pcm)
  71. audio_len = sum(len(chunk) for chunk in audio_buffer) / SAMPLE_RATE
  72. logger.debug(f"Buffer audio: {audio_len:.2f}s")
  73. # Sécurité : reset si trop long
  74. if audio_len > MAX_AUDIO_SEC:
  75. logger.warning("Buffer trop long, reset")
  76. audio_buffer.clear()
  77. start_time = time.time()
  78. continue
  79. # Transcription dès qu'on a assez d'audio
  80. if audio_len >= MIN_AUDIO_SEC:
  81. logger.info(f"Transcription ({audio_len:.2f}s)")
  82. audio = np.concatenate(audio_buffer)
  83. audio_buffer.clear()
  84. segments, _ = model.transcribe(
  85. audio,
  86. language="fr",
  87. vad_filter=VAD_ENABLED
  88. )
  89. text = " ".join(seg.text for seg in segments).strip()
  90. logger.info(f"Texte: '{text}'")
  91. if text:
  92. await ws.send_json({
  93. "type": "final",
  94. "text": text
  95. })
  96. logger.info("Résultat envoyé")
  97. except WebSocketDisconnect:
  98. logger.info("WebSocket déconnecté")
  99. except Exception as e:
  100. logger.exception("Erreur de WebSocket")
  101. await ws.send_json({"error": str(e)})
  102. await ws.close()