binaryMao commited on
Commit
34592a3
·
verified ·
1 Parent(s): 06d9459

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -40
app.py CHANGED
@@ -1,9 +1,9 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- ROBOTSMALI — Sous-titrage Bambara (V5.2 - Final Fix)
4
- - Correction Codec Webcam (VP8 -> H264)
5
- - Interface Artistique (Compatible Gradio)
6
- - Intégration Vidéo d'Exemple
7
  """
8
  import os
9
  import shlex
@@ -28,8 +28,6 @@ random.seed(1234)
28
  np.random.seed(1234)
29
  torch.manual_seed(1234)
30
 
31
- SEGMENT_DURATION = 10.0
32
-
33
  MODELS = {
34
  "Soloni V1 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v1", "rnnt"),
35
  "Soloni V0 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v0", "rnnt"),
@@ -39,8 +37,9 @@ MODELS = {
39
  "QuartzNet V0 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v0", "ctc_char"),
40
  }
41
 
 
42
  VIDEO_EXAMPLES = [
43
- ["examples/MARALINKE-Wii (Lève-toi) Black lives matter (Clip officiel) - MARALINKE (360p, H264).mp4", "Soloba V1 (CTC)"]
44
  ]
45
 
46
  _cache = {}
@@ -78,7 +77,7 @@ def load_model(name):
78
  def extract_audio(video_path, out_wav):
79
  tmp_fd, stabilized_mp4 = tempfile.mkstemp(suffix="_stabilized.mp4")
80
  os.close(tmp_fd)
81
- # Réencodage H.264 pour supporter le VP8 de la webcam
82
  run_cmd(f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_path)} -c:v libx264 -preset ultrafast -crf 23 -c:a aac {shlex.quote(stabilized_mp4)}')
83
  run_cmd(f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(stabilized_mp4)} -vn -ac 1 -ar 16000 -f wav {shlex.quote(out_wav)}')
84
  if os.path.exists(stabilized_mp4): os.remove(stabilized_mp4)
@@ -93,21 +92,14 @@ def clean_audio(wav_path):
93
  sf.write(clean_path, audio, 16000)
94
  return clean_path, audio, 16000
95
 
96
- # ---------------------------- # LOGIQUE MÉTIER # ----------------------------
97
-
98
- def transcribe(model, wav_path):
99
- out = model.transcribe([wav_path])
100
- if isinstance(out, list) and len(out) > 0:
101
- res = out[0]
102
- return res.text.strip() if hasattr(res, "text") else str(res).strip()
103
- return str(out).strip()
104
 
105
  def pipeline(video_input, model_name):
106
  try:
107
- video_path = video_input["tmp_path"] if isinstance(video_input, dict) else video_input
108
- if not video_path: return "❌ Aucune vidéo détectée", None
109
 
110
- yield "⏳ Phase 1 : Stabilisation et extraction...", None
111
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tf:
112
  wav_path = tf.name
113
 
@@ -115,25 +107,25 @@ def pipeline(video_input, model_name):
115
  clean_wav, audio, sr = clean_audio(wav_path)
116
  duration = ffprobe_duration(video_path) or (len(audio)/sr)
117
 
118
- yield f"⏳ Phase 2 : Analyse avec {model_name}...", None
119
  model = load_model(model_name)
120
- text = transcribe(model, clean_wav)
121
- words = [w for w in text.split() if any(c in w.lower() for c in ["ɛ","ɔ","ŋ"]) or len(w) > 2]
 
122
 
123
- if not words: return "⚠️ Pas de texte Bambara détecté.", None
124
 
125
- yield "⏳ Phase 3 : Création de la vidéo finale...", None
126
- # Heuristique simple pour les sous-titres
127
  subs = []
128
- chunk_size = 8
129
  for i in range(0, len(words), chunk_size):
130
  chunk = words[i:i+chunk_size]
131
  s = (i / len(words)) * duration
132
  e = (min(i + chunk_size, len(words)) / len(words)) * duration
133
  subs.append((s, e, "\n".join(textwrap.wrap(" ".join(chunk), 40))))
134
 
135
- out_v = burn(video_path, subs)
136
- yield "✅ Terminé !", out_v
137
  except Exception as e:
138
  traceback.print_exc()
139
  yield f"❌ Erreur: {str(e)}", None
@@ -152,40 +144,49 @@ def burn(video_path, subs):
152
  os.remove(srt_name)
153
  return out_path
154
 
155
- # ---------------------------- # INTERFACE GRADIO # ----------------------------
156
 
157
  custom_css = """
158
  body { background-color: #0b0e14; }
159
  .gradio-container { background: rgba(17, 25, 40, 0.8) !important; backdrop-filter: blur(12px); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.1); }
160
- #title-header { text-align: center; padding: 20px; }
161
  .gr-button-primary { background: linear-gradient(135deg, #059669, #10b981) !important; border: none !important; }
162
  """
163
 
164
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
165
- # Utilisation de gr.Column au lieu de gr.Div pour éviter l'erreur AttributeError
166
- with gr.Column(elem_id="title-header"):
167
  gr.HTML("""
168
  <h1 style='color:#facc15; font-size: 2.5rem; margin:0;'>🤖 ROBOTSMALI</h1>
169
- <p style='color:#94a3b8; font-style:italic;'>Système Expert de Sous-titrage Bambara</p>
170
  <div style="height: 3px; width: 60px; background: #facc15; margin: 15px auto;"></div>
171
  """)
172
 
173
  with gr.Row():
174
  with gr.Column():
175
- gr.Markdown("### 📥 Source")
176
  v_in = gr.Video(label=None, mirror_webcam=False)
177
- m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle IA")
178
  btn = gr.Button("🚀 GÉNÉRER", variant="primary")
179
 
180
  with gr.Column():
181
- gr.Markdown("### 📤 Sortie")
182
- status = gr.Markdown("*En attente...*")
183
  v_out = gr.Video(label=None)
184
 
185
- gr.Examples(examples=VIDEO_EXAMPLES, inputs=[v_in, m_sel], label="📺 Exemples")
186
- gr.HTML("<div style='text-align: center; color: #475569; padding-top: 20px;'>© 2025 RobotsMali</div>")
 
 
 
 
 
 
187
 
188
  btn.click(pipeline, [v_in, m_sel], [status, v_out])
189
 
190
  if __name__ == "__main__":
191
- demo.launch( share=True, debug=True)
 
 
 
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ROBOTSMALI — Sous-titrage Bambara (V5.3 - Production)
4
+ - Vidéo d'exemple : examples/MARALINKE.mp4
5
+ - Correction AttributeError: Gradio Div -> Column/HTML
6
+ - Correction Codec Webcam : VP8 -> H.264
7
  """
8
  import os
9
  import shlex
 
28
  np.random.seed(1234)
29
  torch.manual_seed(1234)
30
 
 
 
31
  MODELS = {
32
  "Soloni V1 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v1", "rnnt"),
33
  "Soloni V0 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v0", "rnnt"),
 
37
  "QuartzNet V0 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v0", "ctc_char"),
38
  }
39
 
40
+ # Mise à jour avec le nom simplifié
41
  VIDEO_EXAMPLES = [
42
+ ["examples/MARALINKE.mp4", "Soloba V1 (CTC)"]
43
  ]
44
 
45
  _cache = {}
 
77
  def extract_audio(video_path, out_wav):
78
  tmp_fd, stabilized_mp4 = tempfile.mkstemp(suffix="_stabilized.mp4")
79
  os.close(tmp_fd)
80
+ # Réencodage H.264 pour supporter le flux webcam
81
  run_cmd(f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_path)} -c:v libx264 -preset ultrafast -crf 23 -c:a aac {shlex.quote(stabilized_mp4)}')
82
  run_cmd(f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(stabilized_mp4)} -vn -ac 1 -ar 16000 -f wav {shlex.quote(out_wav)}')
83
  if os.path.exists(stabilized_mp4): os.remove(stabilized_mp4)
 
92
  sf.write(clean_path, audio, 16000)
93
  return clean_path, audio, 16000
94
 
95
+ # ---------------------------- # PIPELINE # ----------------------------
 
 
 
 
 
 
 
96
 
97
  def pipeline(video_input, model_name):
98
  try:
99
+ if not video_input: return " Vidéo introuvable", None
100
+ video_path = video_input
101
 
102
+ yield "⏳ Phase 1 : Extraction audio...", None
103
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tf:
104
  wav_path = tf.name
105
 
 
107
  clean_wav, audio, sr = clean_audio(wav_path)
108
  duration = ffprobe_duration(video_path) or (len(audio)/sr)
109
 
110
+ yield f"⏳ Phase 2 : Transcription IA ({model_name})...", None
111
  model = load_model(model_name)
112
+ text = model.transcribe([clean_wav])[0]
113
+ text_str = text.text if hasattr(text, 'text') else str(text)
114
+ words = [w for w in text_str.split() if len(w) > 1]
115
 
116
+ if not words: return "⚠️ Pas de parole détectée", None
117
 
118
+ yield "⏳ Phase 3 : Incrustation des sous-titres...", None
 
119
  subs = []
120
+ chunk_size = 7
121
  for i in range(0, len(words), chunk_size):
122
  chunk = words[i:i+chunk_size]
123
  s = (i / len(words)) * duration
124
  e = (min(i + chunk_size, len(words)) / len(words)) * duration
125
  subs.append((s, e, "\n".join(textwrap.wrap(" ".join(chunk), 40))))
126
 
127
+ res_v = burn(video_path, subs)
128
+ yield "✅ Succès !", res_v
129
  except Exception as e:
130
  traceback.print_exc()
131
  yield f"❌ Erreur: {str(e)}", None
 
144
  os.remove(srt_name)
145
  return out_path
146
 
147
+ # ---------------------------- # INTERFACE ARTISTIQUE # ----------------------------
148
 
149
  custom_css = """
150
  body { background-color: #0b0e14; }
151
  .gradio-container { background: rgba(17, 25, 40, 0.8) !important; backdrop-filter: blur(12px); border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.1); }
152
+ #title-container { text-align: center; padding: 20px; }
153
  .gr-button-primary { background: linear-gradient(135deg, #059669, #10b981) !important; border: none !important; }
154
  """
155
 
156
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
157
+ # Remplacement de gr.Div par gr.Column (Fix AttributeError)
158
+ with gr.Column(elem_id="title-container"):
159
  gr.HTML("""
160
  <h1 style='color:#facc15; font-size: 2.5rem; margin:0;'>🤖 ROBOTSMALI</h1>
161
+ <p style='color:#94a3b8;'>Intelligence Artificielle pour le Bambara</p>
162
  <div style="height: 3px; width: 60px; background: #facc15; margin: 15px auto;"></div>
163
  """)
164
 
165
  with gr.Row():
166
  with gr.Column():
167
+ gr.Markdown("### 📥 Entrée")
168
  v_in = gr.Video(label=None, mirror_webcam=False)
169
+ m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle")
170
  btn = gr.Button("🚀 GÉNÉRER", variant="primary")
171
 
172
  with gr.Column():
173
+ gr.Markdown("### 📤 Résultat")
174
+ status = gr.Markdown("*Prêt...*")
175
  v_out = gr.Video(label=None)
176
 
177
+ # Section des exemples (Utilise maintenant MARALINKE.mp4)
178
+ gr.Examples(
179
+ examples=VIDEO_EXAMPLES,
180
+ inputs=[v_in, m_sel],
181
+ label="📺 Exemples de Clips"
182
+ )
183
+
184
+ gr.HTML("<div style='text-align: center; color: #475569; margin-top: 30px;'>© 2025 RobotsMali - Bamako</div>")
185
 
186
  btn.click(pipeline, [v_in, m_sel], [status, v_out])
187
 
188
  if __name__ == "__main__":
189
+ # Petit check de debug pour le dossier examples
190
+ if not os.path.exists("examples/MARALINKE.MP4"):
191
+ print("⚠️ ATTENTION : examples/MARALINKE.mp4 est introuvable sur le serveur.")
192
+ demo.launch(share=True, debug=True)