binaryMao commited on
Commit
2e70459
·
verified ·
1 Parent(s): dba9bff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -167
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # -*- coding: utf-8 -*-
2
- """RobotsMali_ASR_Demo.ipynb - Script FINAL
3
- Intégrant toutes les corrections, sans découpage, avec barre de progression Gradio (syntaxe corrigée) et post-correction.
4
  """
5
  import gradio as gr
6
  import time
@@ -10,13 +10,11 @@ import soundfile as sf
10
  import numpy as np
11
 
12
  # --- IMPORTS NEMO ---
13
- # Nécessite : pip install nemo_toolkit['asr'] nemo_toolkit['nlp']
14
  try:
15
  import nemo.collections.asr as nemo_asr
16
  import nemo.collections.nlp as nemo_nlp
17
  except ImportError:
18
- print("!!! AVERTISSEMENT : NeMo ASR ou NLP n'est pas installé. Les modèles ne fonctionneront pas.")
19
- # On définit des substituts pour permettre au script de s'exécuter jusqu'à l'interface
20
  class DummyASRModel:
21
  def from_pretrained(self, model_name): raise RuntimeError("NeMo ASR not installed.")
22
  class DummyNLPModel:
@@ -27,19 +25,18 @@ except ImportError:
27
  # ----------------------------------------------------------------------
28
  # CONSTANTES DE CONFIGURATION
29
  # ----------------------------------------------------------------------
30
- # Liste de modèles vérifiée (conforme à RobotsMali sur HF)
31
- ROBOTSMALI_MODELS = [
32
- "RobotsMali/soloba-ctc-0.6b-v0",
33
- "RobotsMali/soloni-114m-tdt-ctc-v1",
34
- "RobotsMali/soloni-114m-tdt-ctc-V0",
35
- "RobotsMali/stt-bm-quartznet15x5-V0",
36
- "RobotsMali/stt-bm-quartznet15x5-v1",
37
- "RobotsMali/soloba-ctc-0.6b-v1"
38
- ]
 
39
 
40
- SR_TARGET = 16000 # Taux d'échantillonnage cible pour NeMo ASR (16kHz)
41
-
42
- # Modèle de post-traitement pour restaurer la ponctuation et la casse
43
  PUNCT_MODEL_NAME = "nemo/nlp/punctuation_and_capitalization"
44
 
45
  # Caches
@@ -47,250 +44,215 @@ asr_pipelines = {}
47
  punct_pipeline = None
48
 
49
  # ----------------------------------------------------------------------
50
- # 1. FONCTIONS DE GESTION DES MODÈLES (CHARGEMENT & CACHE)
51
  # ----------------------------------------------------------------------
52
- def load_pipeline(model_name):
53
- """Charge un modèle ASR NeMo, le met en cache et effectue un warm-up."""
54
- if model_name not in asr_pipelines:
55
- print(f"-> Tentative de chargement du modèle NeMo: {model_name}...")
 
 
 
56
  temp_warmup_file = "dummy_warmup.wav"
57
-
58
  try:
59
  model_instance = nemo_asr.models.ASRModel.from_pretrained(model_name=model_name)
60
  model_instance.eval()
61
- asr_pipelines[model_name] = model_instance
62
- print(f"-> Modèle NeMo {model_name} chargé avec succès.")
63
 
64
- # WARM-UP
65
- print(f" [Warmup] Exécution d'une inférence à blanc...")
66
  dummy_audio = np.random.randn(SR_TARGET).astype(np.float32)
67
  sf.write(temp_warmup_file, dummy_audio, SR_TARGET)
68
  model_instance.transcribe([temp_warmup_file], batch_size=1)
69
- print(f" [Warmup] Terminé.")
70
-
71
  except Exception as e:
72
- if model_name in asr_pipelines: del asr_pipelines[model_name]
73
- print(f"!!! Erreur de chargement NeMo pour {model_name}: {e}")
74
- raise RuntimeError(f"Impossible de charger le modèle {model_name}. Détail: {e}")
75
 
76
  finally:
77
  if os.path.exists(temp_warmup_file): os.remove(temp_warmup_file)
78
 
79
- return asr_pipelines.get(model_name)
80
 
81
  def load_punct_model():
82
  """Charge le modèle de ponctuation/casse et le met en cache."""
83
  global punct_pipeline
84
  if punct_pipeline is None:
85
- print(f"-> Tentative de chargement du modèle de ponctuation: {PUNCT_MODEL_NAME}...")
86
  try:
87
  punct_pipeline = nemo_nlp.models.PunctuationCapitalizationModel.from_pretrained(model_name=PUNCT_MODEL_NAME)
88
  punct_pipeline.eval()
89
- print("-> Modèle de ponctuation chargé avec succès.")
90
  except Exception as e:
91
- print(f"!!! AVERTISSEMENT: Échec du chargement du modèle de ponctuation {PUNCT_MODEL_NAME}. La sortie restera brute. Détail: {e}")
92
- punct_pipeline = False # Marquer comme tentative échouée
93
  return punct_pipeline
94
 
95
  # ----------------------------------------------------------------------
96
- # 2. FONCTION PRINCIPALE D'INFÉRENCE (TRAITEMENT COMPLET AVEC PROGRESSION)
97
  # ----------------------------------------------------------------------
98
- def transcribe_audio(model_name: str, audio_path: str):
99
- """
100
- Effectue la transcription ASR de l'audio complet avec une barre de progression stylée.
101
- """
102
- # CORRECTION DE L'ERREUR GRADIO : Initialisation correcte de gr.Progress
103
  progress = gr.Progress()
104
- progress(0, desc="Démarrage du traitement")
105
 
106
  if audio_path is None:
107
- yield "⚠️ Veuillez d'abord télécharger ou enregistrer un fichier audio."
108
- return
109
-
110
  start_time = time.time()
111
- model_short_name = model_name.split('/')[-1]
112
  temp_full_path = f"temp_nemo_input_{os.path.basename(audio_path)}.wav"
 
113
 
114
  try:
115
- # ----------------------------------------------------------------
116
- # ÉTAPE 1 : PRÉPARATION ET CHARGEMENT AUDIO
117
- # ----------------------------------------------------------------
118
- yield f"**[1/4] CHARGEMENT AUDIO...** Préparation du fichier original (Mono @ 16kHz). ⚙️"
119
 
120
- # NOTE : Utilisation de librosa pour garantir le 16kHz et mono
121
  full_audio_data, sr = librosa.load(audio_path, sr=SR_TARGET, mono=True)
122
  total_duration = len(full_audio_data) / SR_TARGET
123
-
124
- # Correction de la forme audio (squeeze)
125
  segment_data = full_audio_data.squeeze()
126
  sf.write(temp_full_path, segment_data, SR_TARGET)
127
 
128
- # ----------------------------------------------------------------
129
- # ÉTAPE 2 : CHARGEMENT/VÉRIFICATION DU MODÈLE
130
- # ----------------------------------------------------------------
131
- yield f"**[2/4] PRÉ-CALCUL...** Chargement du modèle. Durée de l'audio : {total_duration:.1f}s. 🧠"
132
 
133
- asr_model = load_pipeline(model_name)
134
-
135
- # ----------------------------------------------------------------
136
- # ÉTAPE 3 : TRANSCRIPTION COMPLÈTE (AVEC BARRE DE PROGRESSION)
137
- # ----------------------------------------------------------------
138
- yield f"**[3/4] TRANSCRIPTION EN COURS...** Démarrage de l'inférence. ⏳"
139
 
140
- # --- BARRE DE PROGRESSION SIMULÉE ---
141
  for progress_percent in range(0, 91, 10):
142
  time.sleep(0.3)
143
  progress(progress_percent / 100, desc=f"Progression ASR ({progress_percent}%)")
144
 
145
- yield f"**[3/4] FINALISATION...** Inférence en cours sur le GPU. 🚀"
146
- # ---------------------------------------------
147
-
148
- # 🚀 INFÉRENCE NEMO
149
  transcriptions = asr_model.transcribe([temp_full_path], batch_size=1)
150
 
151
- # --- GESTION DE L'OBJET HYPOTHESIS ---
152
- transcription_text_final = "[Transcription vide ou échec ASR]"
153
  if transcriptions and transcriptions[0]:
154
  hyp_object = transcriptions[0]
155
-
156
- # Tente d'extraire le texte de l'objet de retour de NeMo
157
  if hasattr(hyp_object, 'text'):
158
- transcription_text_final = hyp_object.text.strip()
159
  elif isinstance(hyp_object, str):
160
- transcription_text_final = hyp_object.strip()
161
  elif isinstance(hyp_object, list) and hasattr(hyp_object[0], 'text'):
162
- transcription_text_final = hyp_object[0].text.strip()
163
-
164
- if not transcription_text_final:
165
- transcription_text_final = "[Transcription vide ou échec ASR]"
166
-
167
 
168
- # ----------------------------------------------------
169
- # ÉTAPE 4 : POST-TRAITEMENT ET AFFICHAGE FINAL
170
- # ----------------------------------------------------
171
  end_time = time.time()
172
  duration = end_time - start_time
173
- processed_text = transcription_text_final
 
174
 
175
- # --- POST-TRAITEMENT (PONCTUATION & CASSE) ---
176
  punct_model = load_punct_model()
177
 
178
- if punct_model and transcription_text_final != "[Transcription vide ou échec ASR]":
179
- yield f"**[4/4] POST-TRAITEMENT...** Correction de la ponctuation et de la casse pour la lisibilité. ✨"
180
- # Termine la barre de progression
181
  progress(1.0, desc="Progression ASR (100%)")
182
 
183
  try:
184
- corrected_list = punct_model.add_punctuation_capitalization([transcription_text_final])
185
  if corrected_list:
186
  processed_text = corrected_list[0].strip()
 
187
  except Exception as pc_error:
188
- print(f"!!! Échec du post-traitement de ponctuation : {pc_error}")
189
- # Le texte brut (transcription_text_final) reste dans processed_text
190
- yield "⚠️ Échec de la correction de ponctuation. Affichage du texte brut."
191
 
192
- # 1. EN-TÊTE D'INFORMATION - LE NOM DU MODÈLE EST MAINTENANT DYNAMIQUE ICI
193
- output = f"**Modèle Utilisé :** `{model_short_name}` (NeMo)\n"
194
- output += f"**Durée de l'Audio :** {total_duration:.1f} secondes\n"
195
- output += f"**Temps de Traitement Total :** {duration:.2f} secondes\n"
196
- output += f"***\n"
197
 
198
- # 2. PRÉSENTATION LYRICS PROPRE (AMÉLIORÉE)
199
- output += "**RÉSULTAT DE LA TRANSCRIPTION (Lyrics) :**\n"
200
- output += "---\n" # Séparateur visuel pour la section lyrics
 
201
 
202
- # Nettoyage et normalisation de base
203
  clean_text = processed_text.replace('\n', ' ').strip()
204
-
205
- # Remplacer les séparateurs de phrases par un double saut de ligne pour simuler des paragraphes/strophes
206
  formatted_lyrics = clean_text.replace('. ', '.\n\n').replace('? ', '?\n\n').replace('! ', '!\n\n')
207
-
208
- # Ajouter le bloc de citation (>) au début de chaque ligne pour un rendu plus clair en Markdown
209
- final_lines = []
210
- for line in formatted_lyrics.split('\n'):
211
- if line.strip():
212
- final_lines.append('> ' + line.strip())
213
-
214
- output += '\n'.join(final_lines)
215
- output += "\n---\n"
216
 
217
- # 3. NOTE FINALE
218
- output += "\n\n*Traitement complet de l'audio sans découpage (chunking).* "
 
 
 
 
 
 
 
219
 
220
- yield output
 
221
 
222
  except RuntimeError as e:
223
- yield f"❌ Erreur critique lors du chargement ou de l'inférence : {str(e)}"
224
 
225
  except Exception as e:
226
- yield f"❌ Erreur générale lors de la transcription complète : {e}"
227
 
228
  finally:
229
- # Nettoyage
230
  if os.path.exists(temp_full_path):
231
  os.remove(temp_full_path)
232
 
233
 
234
  # ----------------------------------------------------------------------
235
- # 3. PRÉ-CHARGEMENT ET INTERFACE GRADIO
236
  # ----------------------------------------------------------------------
237
 
238
- # --- MODIFICATIONS APPLIQUÉES ICI ---
239
-
240
- # 1. On donne une description initiale GÉNÉRALE
241
- INITIAL_DESCRIPTION_BASE = (
242
- "Sélectionnez un modèle ASR de RobotsMali, puis enregistrez ou téléchargez un fichier audio pour obtenir la transcription. "
243
- "Attention : Le traitement se fait sur l'audio complet. Les longs fichiers peuvent planter la RAM."
244
- )
245
- INITIAL_DESCRIPTION = INITIAL_DESCRIPTION_BASE
246
-
247
- if ROBOTSMALI_MODELS:
248
- default_model = ROBOTSMALI_MODELS[0]
249
- default_model_short_name = default_model.split('/')[-1]
250
  try:
251
- # Tente de charger le modèle par défaut au démarrage
252
- load_pipeline(default_model)
253
- # 2. On ajoute seulement le statut de préchargement au message d'information initial
254
- INITIAL_DESCRIPTION = (
255
- f"✅ Modèle par défaut `{default_model_short_name}` **préchargé et réchauffé** avec succès. "
256
- f"{INITIAL_DESCRIPTION_BASE}"
257
- )
258
- except RuntimeError as e:
259
- INITIAL_DESCRIPTION = (
260
- f"❌ ERREUR CRITIQUE AU DÉMARRAGE : Impossible de charger le modèle `{default_model_short_name}`. "
261
- f"**Veuillez sélectionner un autre modèle dans la liste**. "
262
- f"Détails de l'erreur : {str(e)}"
263
- )
264
  except Exception as e:
265
- INITIAL_DESCRIPTION = (
266
- f"❌ ERREUR CRITIQUE : Problème de configuration (peut-être NeMo/CUDA). Détails : {str(e)}"
267
- )
268
- # -------------------------------------
269
-
270
 
 
271
  model_dropdown = gr.Dropdown(
272
- label="1. Sélectionner un Modèle RobotsMali",
273
- choices=ROBOTSMALI_MODELS,
274
- value=ROBOTSMALI_MODELS[0] if ROBOTSMALI_MODELS else None,
275
- interactive=True,
276
- allow_custom_value=False)
277
 
 
278
  audio_input = gr.Audio(
279
- label="2. Télécharger ou Enregistrer l'Audio",
280
  type="filepath",
281
- sources=["microphone", "upload"],
282
  format="mp3")
283
 
284
  text_output = gr.Markdown(
285
- label="3. Résultat de la Transcription ASR")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
- interface = gr.Interface(
288
- fn=transcribe_audio,
289
- inputs=[model_dropdown, audio_input],
290
- outputs=text_output,
291
- title="🤖 RobotsMali ASR Multi-Modèles (Traitement Complet)",
292
- description=INITIAL_DESCRIPTION,
293
- allow_flagging="never")
294
 
295
- print("Lancement de l'interface Gradio...")
296
- interface.launch(share=True)
 
1
  # -*- coding: utf-8 -*-
2
+ """RobotsMali_ASR_Demo.ipynb - Script FINAL MINIMALISTE (UPLOAD SEULEMENT)
3
+ Interface utilisateur simplifiée pour les tests utilisateurs, ne gardant que l'option de téléchargement de fichier audio.
4
  """
5
  import gradio as gr
6
  import time
 
10
  import numpy as np
11
 
12
  # --- IMPORTS NEMO ---
 
13
  try:
14
  import nemo.collections.asr as nemo_asr
15
  import nemo.collections.nlp as nemo_nlp
16
  except ImportError:
17
+ # Simuler les imports si NeMo n'est pas disponible (pour le test initial)
 
18
  class DummyASRModel:
19
  def from_pretrained(self, model_name): raise RuntimeError("NeMo ASR not installed.")
20
  class DummyNLPModel:
 
25
  # ----------------------------------------------------------------------
26
  # CONSTANTES DE CONFIGURATION
27
  # ----------------------------------------------------------------------
28
+ # Dictionnaire des modèles : {Nom Lisible: Nom Complet (pour NeMo)}
29
+ ROBOTSMALI_MODELS_MAP = {
30
+ "Soloba CTC 0.6B v0": "RobotsMali/soloba-ctc-0.6b-v0",
31
+ "Soloni 114M TDT v1": "RobotsMali/soloni-114m-tdt-ctc-v1",
32
+ "Soloni 114M TDT v0": "RobotsMali/soloni-114m-tdt-ctc-V0",
33
+ "QuartzNet 15x5 V0": "RobotsMali/stt-bm-quartznet15x5-V0",
34
+ "QuartzNet 15x5 v1": "RobotsMali/stt-bm-quartznet15x5-v1",
35
+ "Soloba CTC 0.6B v1": "RobotsMali/soloba-ctc-0.6b-v1"
36
+ }
37
+ ROBOTSMALI_SHORT_NAMES = list(ROBOTSMALI_MODELS_MAP.keys())
38
 
39
+ SR_TARGET = 16000
 
 
40
  PUNCT_MODEL_NAME = "nemo/nlp/punctuation_and_capitalization"
41
 
42
  # Caches
 
44
  punct_pipeline = None
45
 
46
  # ----------------------------------------------------------------------
47
+ # 1. FONCTIONS DE GESTION DES MODÈLES (Pas de changement logique)
48
  # ----------------------------------------------------------------------
49
+ def load_pipeline(short_name):
50
+ """Charge un modèle ASR NeMo en utilisant son nom court."""
51
+ model_name = ROBOTSMALI_MODELS_MAP.get(short_name)
52
+ if not model_name:
53
+ raise ValueError(f"Nom de modèle inconnu: {short_name}")
54
+
55
+ if short_name not in asr_pipelines:
56
  temp_warmup_file = "dummy_warmup.wav"
 
57
  try:
58
  model_instance = nemo_asr.models.ASRModel.from_pretrained(model_name=model_name)
59
  model_instance.eval()
60
+ asr_pipelines[short_name] = model_instance
 
61
 
62
+ # WARM-UP (silencieux pour l'UX)
 
63
  dummy_audio = np.random.randn(SR_TARGET).astype(np.float32)
64
  sf.write(temp_warmup_file, dummy_audio, SR_TARGET)
65
  model_instance.transcribe([temp_warmup_file], batch_size=1)
66
+
 
67
  except Exception as e:
68
+ if short_name in asr_pipelines: del asr_pipelines[short_name]
69
+ raise RuntimeError(f"Impossible de charger le modèle {short_name}. Détail: {e}")
 
70
 
71
  finally:
72
  if os.path.exists(temp_warmup_file): os.remove(temp_warmup_file)
73
 
74
+ return asr_pipelines.get(short_name)
75
 
76
  def load_punct_model():
77
  """Charge le modèle de ponctuation/casse et le met en cache."""
78
  global punct_pipeline
79
  if punct_pipeline is None:
 
80
  try:
81
  punct_pipeline = nemo_nlp.models.PunctuationCapitalizationModel.from_pretrained(model_name=PUNCT_MODEL_NAME)
82
  punct_pipeline.eval()
 
83
  except Exception as e:
84
+ punct_pipeline = False
 
85
  return punct_pipeline
86
 
87
  # ----------------------------------------------------------------------
88
+ # 2. FONCTION PRINCIPALE D'INFÉRENCE
89
  # ----------------------------------------------------------------------
90
+ def transcribe_audio(model_short_name: str, audio_path: str):
91
+ """Effectue la transcription ASR de l'audio complet."""
 
 
 
92
  progress = gr.Progress()
 
93
 
94
  if audio_path is None:
95
+ return "⚠️ **Erreur :** Veuillez **télécharger** un fichier audio pour commencer."
96
+
 
97
  start_time = time.time()
 
98
  temp_full_path = f"temp_nemo_input_{os.path.basename(audio_path)}.wav"
99
+ raw_transcription = "[Transcription vide ou échec ASR]"
100
 
101
  try:
102
+ # Affichage de la première étape
103
+ yield f"**Statut :** 🔄 Préparation et chargement du modèle `{model_short_name}`..."
 
 
104
 
 
105
  full_audio_data, sr = librosa.load(audio_path, sr=SR_TARGET, mono=True)
106
  total_duration = len(full_audio_data) / SR_TARGET
 
 
107
  segment_data = full_audio_data.squeeze()
108
  sf.write(temp_full_path, segment_data, SR_TARGET)
109
 
110
+ asr_model = load_pipeline(model_short_name)
 
 
 
111
 
112
+ # Affichage de la deuxième étape avec progression simulée
113
+ yield f"**Statut :** ⏳ Transcription en cours (Durée audio: {total_duration:.1f}s)..."
 
 
 
 
114
 
 
115
  for progress_percent in range(0, 91, 10):
116
  time.sleep(0.3)
117
  progress(progress_percent / 100, desc=f"Progression ASR ({progress_percent}%)")
118
 
119
+ # Inférence
 
 
 
120
  transcriptions = asr_model.transcribe([temp_full_path], batch_size=1)
121
 
 
 
122
  if transcriptions and transcriptions[0]:
123
  hyp_object = transcriptions[0]
 
 
124
  if hasattr(hyp_object, 'text'):
125
+ raw_transcription = hyp_object.text.strip()
126
  elif isinstance(hyp_object, str):
127
+ raw_transcription = hyp_object.strip()
128
  elif isinstance(hyp_object, list) and hasattr(hyp_object[0], 'text'):
129
+ raw_transcription = hyp_object[0].text.strip()
 
 
 
 
130
 
131
+ # Post-traitement (Ponctuation et Casse)
 
 
132
  end_time = time.time()
133
  duration = end_time - start_time
134
+ processed_text = raw_transcription
135
+ punct_status = ""
136
 
 
137
  punct_model = load_punct_model()
138
 
139
+ if punct_model and raw_transcription != "[Transcription vide ou échec ASR]":
140
+ yield f"**Statut :** Finalisation et correction de la ponctuation..."
 
141
  progress(1.0, desc="Progression ASR (100%)")
142
 
143
  try:
144
+ corrected_list = punct_model.add_punctuation_capitalization([raw_transcription])
145
  if corrected_list:
146
  processed_text = corrected_list[0].strip()
147
+ punct_status = " (Correction Ponctuation OK)"
148
  except Exception as pc_error:
149
+ punct_status = " (Correction Ponctuation ÉCHOUÉE)"
 
 
150
 
151
+ # ------------------------------------------------
152
+ # BLOC DE RÉSULTAT FINAL MINIMALISTE
153
+ # ------------------------------------------------
 
 
154
 
155
+ # 1. En-tête (une seule ligne)
156
+ header = f"### Transcription Terminée "
157
+ header += f"*(Modèle: **{model_short_name}** | Temps: {duration:.2f}s)*{punct_status}\n\n"
158
+ header += "--- \n"
159
 
160
+ # 2. Lyrics très clairs
161
  clean_text = processed_text.replace('\n', ' ').strip()
162
+ # Remplacer les séparateurs de phrases par un double saut de ligne
 
163
  formatted_lyrics = clean_text.replace('. ', '.\n\n').replace('? ', '?\n\n').replace('! ', '!\n\n')
 
 
 
 
 
 
 
 
 
164
 
165
+ lyrics_output = f"""
166
+ **Transcription Finale :**
167
+ ```text
168
+ {formatted_lyrics}
169
+ ```
170
+ """
171
+
172
+ # 3. Message final de nettoyage
173
+ footer = f"\n\n*Traitement basé sur le lien `{ROBOTSMALI_MODELS_MAP.get(model_short_name)}`.*"
174
 
175
+ final_markdown = header + lyrics_output + footer
176
+ yield final_markdown
177
 
178
  except RuntimeError as e:
179
+ yield f"❌ **Erreur Critique :** Impossible de procéder. Détails : {str(e)}"
180
 
181
  except Exception as e:
182
+ yield f"❌ **Erreur Générale :** Une erreur inattendue est survenue : {e}"
183
 
184
  finally:
 
185
  if os.path.exists(temp_full_path):
186
  os.remove(temp_full_path)
187
 
188
 
189
  # ----------------------------------------------------------------------
190
+ # 3. INTERFACE GRADIO (Minimaliste)
191
  # ----------------------------------------------------------------------
192
 
193
+ # Statut initial de l'application
194
+ APP_STATUS = "Chargement en cours..."
195
+ if ROBOTSMALI_SHORT_NAMES:
196
+ default_short_name = ROBOTSMALI_SHORT_NAMES[0]
 
 
 
 
 
 
 
 
197
  try:
198
+ load_pipeline(default_short_name)
199
+ APP_STATUS = f"✅ **Prêt :** Modèle de base `{default_short_name}` chargé."
 
 
 
 
 
 
 
 
 
 
 
200
  except Exception as e:
201
+ APP_STATUS = f"❌ **Échec au Démarrage :** Vérifiez la configuration NeMo/CUDA."
 
 
 
 
202
 
203
+ # Composants
204
  model_dropdown = gr.Dropdown(
205
+ label="Étape 1 : Choisir le Modèle ASR",
206
+ choices=ROBOTSMALI_SHORT_NAMES,
207
+ value=ROBOTSMALI_SHORT_NAMES[0] if ROBOTSMALI_SHORT_NAMES else None,
208
+ interactive=True)
 
209
 
210
+ # MODIFICATION ICI : Suppression de 'microphone'
211
  audio_input = gr.Audio(
212
+ label="Étape 2 : Télécharger l'Audio (MP3, WAV, etc.)",
213
  type="filepath",
214
+ sources=["upload"], # SEULEMENT UPLOAD
215
  format="mp3")
216
 
217
  text_output = gr.Markdown(
218
+ label="Résultat",
219
+ value="Commencez par choisir un modèle et télécharger votre audio. Le résultat s'affichera ici. 💡")
220
+
221
+ # Mise en page Blocks (Deux colonnes simples et bien définies)
222
+ with gr.Blocks(theme=gr.themes.Soft(), title="RobotsMali ASR") as demo:
223
+ gr.Markdown(
224
+ f"""
225
+ # 🤖 RobotsMali ASR Demo
226
+ ### **Transcription Vocale pour les langues maliennes. Minimaliste et rapide.**
227
+ {APP_STATUS}
228
+ ---
229
+ """
230
+ )
231
+
232
+ with gr.Row():
233
+ # Colonne de GAUCHE: INPUTS
234
+ with gr.Column(scale=1, min_width=300):
235
+ model_dropdown.render()
236
+ audio_input.render()
237
+
238
+ submit_btn = gr.Button("▶️ ÉTAPE 3 : LANCER LA TRANSCRIPTION", variant="primary")
239
+
240
+ gr.Markdown(
241
+ """
242
+ *Rappel : L'audio doit être court (moins de 5 minutes) pour éviter une erreur de mémoire.*
243
+ """
244
+ )
245
+
246
+ # Colonne de DROITE: OUTPUT
247
+ with gr.Column(scale=2, min_width=500):
248
+ text_output.render()
249
 
250
+ # Définition des actions
251
+ submit_btn.click(
252
+ fn=transcribe_audio,
253
+ inputs=[model_dropdown, audio_input],
254
+ outputs=text_output
255
+ )
 
256
 
257
+ print("Lancement de l'interface Gradio Blocks...")
258
+ demo.launch(share=True)