binaryMao commited on
Commit
4fb6b44
·
verified ·
1 Parent(s): 555494f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +26 -25
app.py CHANGED
@@ -1,9 +1,9 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- ROBOTSMALI — Sous-titrage Bambara (VERSION 7.2 - FIX DURATION & STREAMING)
4
- - Correction Moov Atom (+faststart) pour affichage instantané
5
- - Case de résultat unique (Lecture + Téléchargement)
6
- - Suivi des phases de traitement (Audio, IA, Rendu)
7
  """
8
  import os
9
  import shlex
@@ -17,8 +17,6 @@ from pathlib import Path
17
 
18
  import numpy as np
19
  import torch
20
- import soundfile as sf
21
- import librosa
22
  from huggingface_hub import snapshot_download
23
  from nemo.collections import asr as nemo_asr
24
  import gradio as gr
@@ -35,7 +33,7 @@ MODELS = {
35
  "QuartzNet V0 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v0", "ctc_char"),
36
  }
37
 
38
- # Détection automatique de la vidéo d'exemple
39
  def get_example():
40
  paths = ["examples/MARALINKE.mp4", "MARALINKE.mp4"]
41
  for p in paths:
@@ -48,12 +46,14 @@ _cache = {}
48
  # ---------------------------- # MOTEUR DE TRAITEMENT # ----------------------------
49
 
50
  def run_cmd(cmd):
 
51
  res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
52
  if res.returncode != 0:
53
- raise RuntimeError(f"FFmpeg Error: {res.stdout}")
54
  return res.stdout
55
 
56
  def load_model(name):
 
57
  if name in _cache: return _cache[name]
58
  _cache.clear()
59
  if torch.cuda.is_available(): torch.cuda.empty_cache()
@@ -74,12 +74,12 @@ def load_model(name):
74
  _cache[name] = model
75
  return model
76
 
77
- def burn_subtitles(video_path, words, duration):
78
- # Création du nom de fichier unique
79
  out_name = f"robotsmali_final_{int(time.time())}.mp4"
80
  out_path = os.path.abspath(out_name)
81
 
82
- # Création du fichier de sous-titres (SRT)
83
  chunk_size = 7
84
  with tempfile.NamedTemporaryFile(suffix=".srt", mode="w", encoding="utf-8", delete=False) as tf:
85
  for i, idx in enumerate(range(0, len(words), chunk_size)):
@@ -93,8 +93,7 @@ def burn_subtitles(video_path, words, duration):
93
  tf.write(f"{i+1}\n{t_srt(start)} --> {t_srt(end)}\n{txt}\n\n")
94
  srt_name = tf.name
95
 
96
- # FFmpeg avec correction du Moov Atom (+faststart) et format Web standard
97
- # Cela permet au navigateur de connaître la durée dès le début du fichier.
98
  vf = f"subtitles={shlex.quote(srt_name)}:force_style='Fontsize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&'"
99
  cmd = (
100
  f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_path)} '
@@ -110,14 +109,13 @@ def burn_subtitles(video_path, words, duration):
110
  def pipeline(video_input, model_name):
111
  try:
112
  if not video_input:
113
- yield "### ❌ État\n*Aucune vidéo chargée.*", None
114
  return
115
 
116
- yield "### ⏳ État\n*Phase 1/3 : Analyse audio et extraction...*", None
117
  wav_path = os.path.abspath("temp_audio.wav")
118
  run_cmd(f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_input)} -vn -ac 1 -ar 16000 -f wav {shlex.quote(wav_path)}')
119
 
120
- # Récupération de la durée exacte pour synchroniser les sous-titres
121
  dur_out = subprocess.run(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(video_input)}',
122
  shell=True, stdout=subprocess.PIPE, text=True).stdout
123
  duration = float(dur_out.strip()) if dur_out.strip() else 10.0
@@ -132,17 +130,17 @@ def pipeline(video_input, model_name):
132
  yield "### ⚠️ État\n*Aucune parole détectée.*", None
133
  return
134
 
135
- yield "### ⏳ État\n*Phase 3/3 : Encodage vidéo et optimisation streaming...*", None
136
- final_v = burn_subtitles(video_input, words, duration)
137
 
138
  if os.path.exists(wav_path): os.remove(wav_path)
139
  yield "### ✅ État\n*Traitement terminé avec succès !*", final_v
140
 
141
  except Exception as e:
142
  traceback.print_exc()
143
- yield f"### ❌ État\n*Erreur : {str(e)}*", None
144
 
145
- # ---------------------------- # INTERFACE # ----------------------------
146
 
147
  custom_css = """
148
  body { background-color: #0b0e14; }
@@ -153,7 +151,7 @@ body { background-color: #0b0e14; }
153
 
154
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
155
  with gr.Column(elem_id="header"):
156
- gr.HTML("<h1 style='color:#facc15; margin:0;'>🤖 ROBOTSMALI</h1><p style='color:#94a3b8;'>Sous-titrage Automatique Bambara</p>")
157
 
158
  with gr.Row():
159
  with gr.Column():
@@ -164,13 +162,16 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
164
 
165
  with gr.Column():
166
  gr.Markdown("### 📤 2. RÉSULTAT")
167
- status = gr.Markdown("### État\n*En attente...*")
168
- v_out = gr.Video(label="Vidéo finale (Synchronisée)")
169
 
 
170
  if EXAMPLE_PATH:
171
- gr.Examples(examples=[[EXAMPLE_PATH, "Soloba V1 (CTC)"]], inputs=[v_in, m_sel], label="📺 Exemple")
172
 
173
  btn.click(pipeline, [v_in, m_sel], [status, v_out])
174
 
175
  if __name__ == "__main__":
176
- demo.launch(debug=True, share=True)
 
 
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ROBOTSMALI — Sous-titrage Bambara (VERSION 7.4 - DEBUG & SHARE)
4
+ - Correction Moov Atom pour le streaming (FastStart)
5
+ - Interface stable avec partage public activé
6
+ - Suivi détaillé des phases de transcription
7
  """
8
  import os
9
  import shlex
 
17
 
18
  import numpy as np
19
  import torch
 
 
20
  from huggingface_hub import snapshot_download
21
  from nemo.collections import asr as nemo_asr
22
  import gradio as gr
 
33
  "QuartzNet V0 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v0", "ctc_char"),
34
  }
35
 
36
+ # Détection automatique du fichier d'exemple
37
  def get_example():
38
  paths = ["examples/MARALINKE.mp4", "MARALINKE.mp4"]
39
  for p in paths:
 
46
  # ---------------------------- # MOTEUR DE TRAITEMENT # ----------------------------
47
 
48
  def run_cmd(cmd):
49
+ """Exécute une commande système et capture les erreurs."""
50
  res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
51
  if res.returncode != 0:
52
+ raise RuntimeError(f"Erreur système : {res.stdout}")
53
  return res.stdout
54
 
55
  def load_model(name):
56
+ """Charge le modèle IA en mémoire avec mise en cache."""
57
  if name in _cache: return _cache[name]
58
  _cache.clear()
59
  if torch.cuda.is_available(): torch.cuda.empty_cache()
 
74
  _cache[name] = model
75
  return model
76
 
77
+ def process_video(video_path, words, duration):
78
+ """Génère la vidéo finale avec sous-titres et optimisation streaming."""
79
  out_name = f"robotsmali_final_{int(time.time())}.mp4"
80
  out_path = os.path.abspath(out_name)
81
 
82
+ # Génération du fichier SRT temporaire
83
  chunk_size = 7
84
  with tempfile.NamedTemporaryFile(suffix=".srt", mode="w", encoding="utf-8", delete=False) as tf:
85
  for i, idx in enumerate(range(0, len(words), chunk_size)):
 
93
  tf.write(f"{i+1}\n{t_srt(start)} --> {t_srt(end)}\n{txt}\n\n")
94
  srt_name = tf.name
95
 
96
+ # Encodage FFmpeg avec le flag crucial +faststart
 
97
  vf = f"subtitles={shlex.quote(srt_name)}:force_style='Fontsize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&'"
98
  cmd = (
99
  f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_path)} '
 
109
  def pipeline(video_input, model_name):
110
  try:
111
  if not video_input:
112
+ yield "### ❌ État\n*Aucune vidéo détectée.*", None
113
  return
114
 
115
+ yield "### ⏳ État\n*Phase 1/3 : Analyse audio en cours...*", None
116
  wav_path = os.path.abspath("temp_audio.wav")
117
  run_cmd(f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_input)} -vn -ac 1 -ar 16000 -f wav {shlex.quote(wav_path)}')
118
 
 
119
  dur_out = subprocess.run(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(video_input)}',
120
  shell=True, stdout=subprocess.PIPE, text=True).stdout
121
  duration = float(dur_out.strip()) if dur_out.strip() else 10.0
 
130
  yield "### ⚠️ État\n*Aucune parole détectée.*", None
131
  return
132
 
133
+ yield "### ⏳ État\n*Phase 3/3 : Finalisation de la vidéo...*", None
134
+ final_v = process_video(video_input, words, duration)
135
 
136
  if os.path.exists(wav_path): os.remove(wav_path)
137
  yield "### ✅ État\n*Traitement terminé avec succès !*", final_v
138
 
139
  except Exception as e:
140
  traceback.print_exc()
141
+ yield f"### ❌ État\n*Erreur critique : {str(e)}*", None
142
 
143
+ # ---------------------------- # INTERFACE GRADIO # ----------------------------
144
 
145
  custom_css = """
146
  body { background-color: #0b0e14; }
 
151
 
152
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
153
  with gr.Column(elem_id="header"):
154
+ gr.HTML("<h1 style='color:#facc15; margin:0;'>🤖 ROBOTSMALI</h1><p style='color:#94a3b8;'>Intelligence Artificielle pour le Bambara</p>")
155
 
156
  with gr.Row():
157
  with gr.Column():
 
162
 
163
  with gr.Column():
164
  gr.Markdown("### 📤 2. RÉSULTAT")
165
+ status = gr.Markdown("### État\n*Prêt*")
166
+ v_out = gr.Video(label="Vidéo finale")
167
 
168
+ # Désactivation du cache pour éviter le gel de l'interface sur Hugging Face
169
  if EXAMPLE_PATH:
170
+ gr.Examples(examples=[[EXAMPLE_PATH, "Soloba V1 (CTC)"]], inputs=[v_in, m_sel], cache_examples=False)
171
 
172
  btn.click(pipeline, [v_in, m_sel], [status, v_out])
173
 
174
  if __name__ == "__main__":
175
+ # share=True : Crée un lien public .gradio.live
176
+ # debug=True : Affiche les erreurs détaillées dans la console
177
+ demo.launch(share=True, debug=True)