Ronaldodev commited on
Commit
9e7a5e0
·
1 Parent(s): 46cff75

[UPDATE] use flask api insted gradio

Browse files
Files changed (2) hide show
  1. app.py +172 -160
  2. requirements.txt +3 -7
app.py CHANGED
@@ -1,84 +1,71 @@
1
- from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from pydantic import BaseModel
4
  import torch
5
  import torchaudio
6
  import librosa
7
  import os
8
  import base64
9
  import tempfile
10
- import io
11
- import uvicorn
12
  from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq
13
  from huggingface_hub import login
14
  import logging
15
- from contextlib import asynccontextmanager
16
- from typing import Optional
17
- import json
18
 
19
  # Configuration du logging
20
  logging.basicConfig(level=logging.INFO)
21
  logger = logging.getLogger(__name__)
22
 
 
 
23
  # Configuration du modèle
24
  MODEL_NAME = "Ronaldodev/speech-to-text-fongbe"
25
  HF_TOKEN = os.environ.get("HF_TOKEN")
26
 
27
- # Variables globales pour le modèle
28
  model = None
29
  processor = None
 
30
 
31
 
32
- # Modèles Pydantic pour les requêtes
33
- class AudioBase64Request(BaseModel):
34
- audio_base64: str
35
- remove_prefix: bool = True
36
-
37
-
38
- class TranscriptionResponse(BaseModel):
39
- success: bool
40
- transcription: Optional[str] = None
41
- error: Optional[str] = None
42
- model_name: str = MODEL_NAME
43
-
44
-
45
- class HealthResponse(BaseModel):
46
- status: str
47
- model_loaded: bool
48
- message: str
49
 
50
-
51
- # Fonction pour charger le modèle
52
- async def load_model():
53
- """Charge le modèle au démarrage de l'application"""
54
- global model, processor
55
 
56
  try:
57
  logger.info("🔄 Chargement du modèle STT Fongbé...")
58
 
59
- if not HF_TOKEN:
60
- raise ValueError("HF_TOKEN non configuré dans les variables d'environnement")
61
-
62
- login(token=HF_TOKEN)
63
- logger.info(" Authentification Hugging Face réussie")
64
 
65
- model = AutoModelForSpeechSeq2Seq.from_pretrained(MODEL_NAME)
66
  processor = AutoProcessor.from_pretrained(MODEL_NAME)
 
 
 
 
 
 
 
 
 
 
67
 
 
68
  logger.info("✅ Modèle STT Fongbé chargé avec succès!")
69
  return True
70
 
71
  except Exception as e:
72
- logger.error(f"❌ Erreur lors du chargement du modèle: {e}")
 
73
  return False
74
 
75
 
76
- # Fonction pour traiter l'audio
77
  def process_audio_data(audio_data, sample_rate=None):
78
  """Traite les données audio pour la transcription"""
79
- if model is None or processor is None:
80
- raise HTTPException(status_code=503, detail="Modèle non chargé")
81
-
82
  try:
83
  # Convertir en mono si nécessaire
84
  if len(audio_data.shape) > 1:
@@ -118,87 +105,74 @@ def process_audio_data(audio_data, sample_rate=None):
118
  return transcription.strip()
119
 
120
  except Exception as e:
121
- logger.error(f"❌ Erreur lors du traitement audio: {e}")
122
- raise HTTPException(status_code=500, detail=f"Erreur de transcription: {str(e)}")
123
-
124
-
125
- # Context manager pour le cycle de vie de l'app
126
- @asynccontextmanager
127
- async def lifespan(app: FastAPI):
128
- # Démarrage
129
- logger.info("🚀 Démarrage de l'API STT Fongbé")
130
- success = await load_model()
131
- if not success:
132
- logger.error("❌ Impossible de charger le modèle")
133
- yield
134
- # Arrêt
135
- logger.info("🔴 Arrêt de l'API STT Fongbé")
136
-
137
-
138
- # Création de l'application FastAPI
139
- app = FastAPI(
140
- title="🎤 API STT Fongbé",
141
- description="API de reconnaissance vocale pour la langue Fongbé",
142
- version="1.0.0",
143
- lifespan=lifespan
144
- )
145
-
146
- # Configuration CORS
147
- app.add_middleware(
148
- CORSMiddleware,
149
- allow_origins=["*"], # À restreindre en production
150
- allow_credentials=True,
151
- allow_methods=["*"],
152
- allow_headers=["*"],
153
- )
154
-
155
-
156
- # ENDPOINTS API
157
-
158
- @app.get("/", response_model=HealthResponse)
159
- async def root():
160
  """Point d'entrée principal de l'API"""
161
- return HealthResponse(
162
- status="running",
163
- model_loaded=model is not None and processor is not None,
164
- message="API STT Fongbé opérationnelle"
165
- )
 
 
166
 
167
 
168
- @app.get("/health", response_model=HealthResponse)
169
- async def health_check():
170
  """Vérification de l'état de santé de l'API"""
171
- model_loaded = model is not None and processor is not None
172
-
173
- return HealthResponse(
174
- status="healthy" if model_loaded else "unhealthy",
175
- model_loaded=model_loaded,
176
- message="Modèle chargé et prêt" if model_loaded else "Modèle non chargé"
177
- )
178
 
179
 
180
- @app.post("/transcribe/base64", response_model=TranscriptionResponse)
181
- async def transcribe_base64(request: AudioBase64Request):
182
  """
183
  Transcription audio à partir de données base64
184
-
185
- - **audio_base64**: Données audio encodées en base64 (avec ou sans préfixe data:audio/...)
186
- - **remove_prefix**: Supprime automatiquement le préfixe data:audio/... (défaut: True)
 
187
  """
188
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  logger.info("🎵 Transcription via base64...")
190
 
191
- audio_base64 = request.audio_base64.strip()
 
192
 
193
  # Supprimer le préfixe data:audio/... si présent
194
- if request.remove_prefix and audio_base64.startswith('data:'):
195
  audio_base64 = audio_base64.split(',')[1]
196
 
197
  # Décoder le base64
198
  try:
199
  audio_bytes = base64.b64decode(audio_base64)
200
  except Exception as e:
201
- raise HTTPException(status_code=400, detail=f"Données base64 invalides: {str(e)}")
 
 
 
202
 
203
  # Créer un fichier temporaire
204
  with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
@@ -215,42 +189,57 @@ async def transcribe_base64(request: AudioBase64Request):
215
 
216
  logger.info(f"✅ Transcription réussie: '{transcription}'")
217
 
218
- return TranscriptionResponse(
219
- success=True,
220
- transcription=transcription
221
- )
 
222
 
223
  finally:
224
  # Nettoyer le fichier temporaire
225
  if os.path.exists(temp_path):
226
  os.unlink(temp_path)
227
 
228
- except HTTPException:
229
- raise
230
  except Exception as e:
231
  logger.error(f"❌ Erreur transcription base64: {e}")
232
- return TranscriptionResponse(
233
- success=False,
234
- error=str(e)
235
- )
236
 
237
 
238
- @app.post("/transcribe/file", response_model=TranscriptionResponse)
239
- async def transcribe_file(audio_file: UploadFile = File(...)):
240
  """
241
  Transcription audio à partir d'un fichier uploadé
242
-
243
- - **audio_file**: Fichier audio (WAV, MP3, M4A, etc.)
244
  """
245
  try:
246
- logger.info(f"🎵 Transcription du fichier: {audio_file.filename}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
- # Vérifier le type de fichier
249
- if not audio_file.content_type or not audio_file.content_type.startswith('audio/'):
250
- logger.warning(f"⚠️ Type de fichier suspect: {audio_file.content_type}")
251
 
252
  # Lire le fichier
253
- audio_bytes = await audio_file.read()
254
 
255
  # Créer un fichier temporaire
256
  with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
@@ -267,38 +256,52 @@ async def transcribe_file(audio_file: UploadFile = File(...)):
267
 
268
  logger.info(f"✅ Transcription réussie: '{transcription}'")
269
 
270
- return TranscriptionResponse(
271
- success=True,
272
- transcription=transcription
273
- )
 
 
274
 
275
  finally:
276
  # Nettoyer le fichier temporaire
277
  if os.path.exists(temp_path):
278
  os.unlink(temp_path)
279
 
280
- except HTTPException:
281
- raise
282
  except Exception as e:
283
  logger.error(f"❌ Erreur transcription fichier: {e}")
284
- return TranscriptionResponse(
285
- success=False,
286
- error=str(e)
287
- )
288
 
289
 
290
- @app.post("/transcribe/url")
291
- async def transcribe_url(url: str):
292
  """
293
  Transcription audio à partir d'une URL
294
-
295
- - **url**: URL vers un fichier audio accessible publiquement
296
  """
297
  try:
298
- import requests
299
-
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  logger.info(f"🌐 Téléchargement depuis URL: {url}")
301
 
 
 
302
  # Télécharger le fichier
303
  response = requests.get(url, timeout=30)
304
  response.raise_for_status()
@@ -318,10 +321,12 @@ async def transcribe_url(url: str):
318
 
319
  logger.info(f"✅ Transcription réussie: '{transcription}'")
320
 
321
- return TranscriptionResponse(
322
- success=True,
323
- transcription=transcription
324
- )
 
 
325
 
326
  finally:
327
  # Nettoyer le fichier temporaire
@@ -330,27 +335,34 @@ async def transcribe_url(url: str):
330
 
331
  except Exception as e:
332
  logger.error(f"❌ Erreur transcription URL: {e}")
333
- return TranscriptionResponse(
334
- success=False,
335
- error=str(e)
336
- )
337
 
338
 
339
- if __name__ == "__main__":
340
- # Configuration pour le développement
341
- port = int(os.environ.get("PORT", 8000))
 
 
 
 
 
 
342
 
343
- print("🚀 DÉMARRAGE API STT FONGBÉ - FASTAPI")
 
 
344
  print("=" * 50)
345
- print(f"🌐 http://localhost:{port}")
346
- print(f"📖 Documentation: http://localhost:{port}/docs")
347
- print(f"🔧 Redoc: http://localhost:{port}/redoc")
 
 
 
 
 
348
  print("=" * 50)
349
 
350
- uvicorn.run(
351
- "app:app", # Remplacez "app" par le nom de votre fichier si différent
352
- host="0.0.0.0",
353
- port=port,
354
- reload=False, # À désactiver en production
355
- log_level="info"
356
- )
 
1
+ from flask import Flask, request, jsonify
 
 
2
  import torch
3
  import torchaudio
4
  import librosa
5
  import os
6
  import base64
7
  import tempfile
 
 
8
  from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq
9
  from huggingface_hub import login
10
  import logging
 
 
 
11
 
12
  # Configuration du logging
13
  logging.basicConfig(level=logging.INFO)
14
  logger = logging.getLogger(__name__)
15
 
16
+ app = Flask(__name__)
17
+
18
  # Configuration du modèle
19
  MODEL_NAME = "Ronaldodev/speech-to-text-fongbe"
20
  HF_TOKEN = os.environ.get("HF_TOKEN")
21
 
22
+ # Variables globales pour le modèle (chargement à la demande)
23
  model = None
24
  processor = None
25
+ model_loaded = False
26
 
27
 
28
+ def load_model_if_needed():
29
+ """Charge le modèle seulement quand nécessaire"""
30
+ global model, processor, model_loaded
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ if model_loaded and model is not None and processor is not None:
33
+ return True
 
 
 
34
 
35
  try:
36
  logger.info("🔄 Chargement du modèle STT Fongbé...")
37
 
38
+ if HF_TOKEN:
39
+ login(token=HF_TOKEN)
40
+ logger.info("✅ Authentification Hugging Face réussie")
41
+ else:
42
+ logger.info("⚠️ Pas de token HF - tentative sans authentification")
43
 
44
+ # Chargement optimisé
45
  processor = AutoProcessor.from_pretrained(MODEL_NAME)
46
+ model = AutoModelForSpeechSeq2Seq.from_pretrained(
47
+ MODEL_NAME,
48
+ torch_dtype=torch.float16,
49
+ low_cpu_mem_usage=True,
50
+ use_safetensors=True
51
+ )
52
+
53
+ model.eval()
54
+ if hasattr(model, 'half'):
55
+ model = model.half()
56
 
57
+ model_loaded = True
58
  logger.info("✅ Modèle STT Fongbé chargé avec succès!")
59
  return True
60
 
61
  except Exception as e:
62
+ logger.error(f"❌ Erreur chargement modèle: {e}")
63
+ model_loaded = False
64
  return False
65
 
66
 
 
67
  def process_audio_data(audio_data, sample_rate=None):
68
  """Traite les données audio pour la transcription"""
 
 
 
69
  try:
70
  # Convertir en mono si nécessaire
71
  if len(audio_data.shape) > 1:
 
105
  return transcription.strip()
106
 
107
  except Exception as e:
108
+ logger.error(f"❌ Erreur traitement audio: {e}")
109
+ raise e
110
+
111
+
112
+ @app.route("/", methods=["GET"])
113
+ def health_check():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  """Point d'entrée principal de l'API"""
115
+ return jsonify({
116
+ "status": "OK",
117
+ "message": "🎤 API STT Fongbé - Reconnaissance vocale pour la langue Fongbé",
118
+ "model_name": MODEL_NAME,
119
+ "model_loaded": model_loaded,
120
+ "version": "1.0.0"
121
+ })
122
 
123
 
124
+ @app.route("/health", methods=["GET"])
125
+ def health():
126
  """Vérification de l'état de santé de l'API"""
127
+ return jsonify({
128
+ "status": "healthy" if model_loaded else "model_not_loaded",
129
+ "model_loaded": model_loaded,
130
+ "model_name": MODEL_NAME,
131
+ "message": "Modèle chargé et prêt" if model_loaded else "Modèle sera chargé à la première utilisation"
132
+ })
 
133
 
134
 
135
+ @app.route("/transcribe/base64", methods=["POST"])
136
+ def transcribe_base64():
137
  """
138
  Transcription audio à partir de données base64
139
+ Body JSON: {
140
+ "audio_base64": "data:audio/wav;base64,..." ou "UklGR...",
141
+ "remove_prefix": true (optionnel, défaut: true)
142
+ }
143
  """
144
  try:
145
+ data = request.get_json()
146
+ if not data or "audio_base64" not in data:
147
+ return jsonify({
148
+ "success": False,
149
+ "error": "Paramètre 'audio_base64' requis"
150
+ }), 400
151
+
152
+ # Charger le modèle si nécessaire
153
+ if not load_model_if_needed():
154
+ return jsonify({
155
+ "success": False,
156
+ "error": "Impossible de charger le modèle"
157
+ }), 503
158
+
159
  logger.info("🎵 Transcription via base64...")
160
 
161
+ audio_base64 = data["audio_base64"].strip()
162
+ remove_prefix = data.get("remove_prefix", True)
163
 
164
  # Supprimer le préfixe data:audio/... si présent
165
+ if remove_prefix and audio_base64.startswith('data:'):
166
  audio_base64 = audio_base64.split(',')[1]
167
 
168
  # Décoder le base64
169
  try:
170
  audio_bytes = base64.b64decode(audio_base64)
171
  except Exception as e:
172
+ return jsonify({
173
+ "success": False,
174
+ "error": f"Données base64 invalides: {str(e)}"
175
+ }), 400
176
 
177
  # Créer un fichier temporaire
178
  with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
 
189
 
190
  logger.info(f"✅ Transcription réussie: '{transcription}'")
191
 
192
+ return jsonify({
193
+ "success": True,
194
+ "transcription": transcription,
195
+ "model_name": MODEL_NAME
196
+ })
197
 
198
  finally:
199
  # Nettoyer le fichier temporaire
200
  if os.path.exists(temp_path):
201
  os.unlink(temp_path)
202
 
 
 
203
  except Exception as e:
204
  logger.error(f"❌ Erreur transcription base64: {e}")
205
+ return jsonify({
206
+ "success": False,
207
+ "error": str(e)
208
+ }), 500
209
 
210
 
211
+ @app.route("/transcribe/file", methods=["POST"])
212
+ def transcribe_file():
213
  """
214
  Transcription audio à partir d'un fichier uploadé
215
+ Form-data avec 'audio_file' contenant le fichier audio
 
216
  """
217
  try:
218
+ # Vérifier qu'un fichier est présent
219
+ if 'audio_file' not in request.files:
220
+ return jsonify({
221
+ "success": False,
222
+ "error": "Aucun fichier 'audio_file' fourni"
223
+ }), 400
224
+
225
+ audio_file = request.files['audio_file']
226
+ if audio_file.filename == '':
227
+ return jsonify({
228
+ "success": False,
229
+ "error": "Aucun fichier sélectionné"
230
+ }), 400
231
+
232
+ # Charger le modèle si nécessaire
233
+ if not load_model_if_needed():
234
+ return jsonify({
235
+ "success": False,
236
+ "error": "Impossible de charger le modèle"
237
+ }), 503
238
 
239
+ logger.info(f"🎵 Transcription du fichier: {audio_file.filename}")
 
 
240
 
241
  # Lire le fichier
242
+ audio_bytes = audio_file.read()
243
 
244
  # Créer un fichier temporaire
245
  with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
 
256
 
257
  logger.info(f"✅ Transcription réussie: '{transcription}'")
258
 
259
+ return jsonify({
260
+ "success": True,
261
+ "transcription": transcription,
262
+ "model_name": MODEL_NAME,
263
+ "filename": audio_file.filename
264
+ })
265
 
266
  finally:
267
  # Nettoyer le fichier temporaire
268
  if os.path.exists(temp_path):
269
  os.unlink(temp_path)
270
 
 
 
271
  except Exception as e:
272
  logger.error(f"❌ Erreur transcription fichier: {e}")
273
+ return jsonify({
274
+ "success": False,
275
+ "error": str(e)
276
+ }), 500
277
 
278
 
279
+ @app.route("/transcribe/url", methods=["POST"])
280
+ def transcribe_url():
281
  """
282
  Transcription audio à partir d'une URL
283
+ Body JSON: {"url": "https://example.com/audio.wav"}
 
284
  """
285
  try:
286
+ data = request.get_json()
287
+ if not data or "url" not in data:
288
+ return jsonify({
289
+ "success": False,
290
+ "error": "Paramètre 'url' requis"
291
+ }), 400
292
+
293
+ # Charger le modèle si nécessaire
294
+ if not load_model_if_needed():
295
+ return jsonify({
296
+ "success": False,
297
+ "error": "Impossible de charger le modèle"
298
+ }), 503
299
+
300
+ url = data["url"]
301
  logger.info(f"🌐 Téléchargement depuis URL: {url}")
302
 
303
+ import requests
304
+
305
  # Télécharger le fichier
306
  response = requests.get(url, timeout=30)
307
  response.raise_for_status()
 
321
 
322
  logger.info(f"✅ Transcription réussie: '{transcription}'")
323
 
324
+ return jsonify({
325
+ "success": True,
326
+ "transcription": transcription,
327
+ "model_name": MODEL_NAME,
328
+ "url": url
329
+ })
330
 
331
  finally:
332
  # Nettoyer le fichier temporaire
 
335
 
336
  except Exception as e:
337
  logger.error(f"❌ Erreur transcription URL: {e}")
338
+ return jsonify({
339
+ "success": False,
340
+ "error": str(e)
341
+ }), 500
342
 
343
 
344
+ @app.route("/test", methods=["GET"])
345
+ def test():
346
+ """Endpoint de test simple"""
347
+ return jsonify({
348
+ "status": "API fonctionnelle",
349
+ "message": "Test réussi ✅",
350
+ "model_loaded": model_loaded,
351
+ "timestamp": "2025-01-04"
352
+ })
353
 
354
+
355
+ if __name__ == "__main__":
356
+ print("🚀 DÉMARRAGE API STT FONGBÉ - FLASK")
357
  print("=" * 50)
358
+ print("🌐 Port: 7860")
359
+ print("📖 Endpoints disponibles:")
360
+ print(" GET / - Statut de l'API")
361
+ print(" GET /health - Santé de l'API")
362
+ print(" GET /test - Test simple")
363
+ print(" POST /transcribe/base64 - Transcription base64")
364
+ print(" POST /transcribe/file - Transcription fichier")
365
+ print(" POST /transcribe/url - Transcription URL")
366
  print("=" * 50)
367
 
368
+ app.run(host="0.0.0.0", port=7860, debug=True)
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,12 +1,8 @@
1
- fastapi==0.104.1
2
- uvicorn[standard]==0.24.0
3
- python-multipart==0.0.6
4
- torch
5
- torchaudio
6
  librosa==0.10.1
7
  transformers>=4.35.0
8
  huggingface_hub>=0.17.0
9
- pydantic==2.4.2
10
- python-jose[cryptography]==3.3.0
11
  requests==2.31.0
12
  numpy>=1.24.0
 
1
+ flask==2.3.3
2
+ torch>=2.0.0
3
+ torchaudio>=2.0.0
 
 
4
  librosa==0.10.1
5
  transformers>=4.35.0
6
  huggingface_hub>=0.17.0
 
 
7
  requests==2.31.0
8
  numpy>=1.24.0