binaryMao commited on
Commit
8a33271
·
verified ·
1 Parent(s): e4a7c99

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -47
app.py CHANGED
@@ -1,9 +1,9 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- ROBOTSMALI — Sous-titrage Bambara (VERSION COMPLÈTE V6.6)
4
- - Interface à deux colonnes (Entrée / Résultat unique)
5
- - Conservation du Statut détaillé (Phases de traitement)
6
- - Tous les modèles RobotsMali inclus
7
  """
8
  import os
9
  import shlex
@@ -23,40 +23,41 @@ from huggingface_hub import snapshot_download
23
  from nemo.collections import asr as nemo_asr
24
  import gradio as gr
25
 
26
- # ---------------------------- # CONFIGURATION # ----------------------------
27
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
28
- random.seed(1234)
29
- np.random.seed(1234)
30
- torch.manual_seed(1234)
31
 
32
  MODELS = {
33
- "Soloni V1 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v1", "rnnt"),
34
- "Soloni V0 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v0", "rnnt"),
35
  "Soloba V1 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v1", "ctc"),
36
- "Soloba V0 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v0", "ctc"),
37
  "QuartzNet V1 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v1", "ctc_char"),
 
 
38
  "QuartzNet V0 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v0", "ctc_char"),
39
  }
40
 
41
- VIDEO_EXAMPLES = [
42
- ["examples/MARALINKE.mp4", "Soloba V1 (CTC)"]
43
- ]
 
 
 
 
44
 
 
45
  _cache = {}
46
 
47
- # ---------------------------- # FONCTIONS TECHNIQUES # ----------------------------
48
 
49
  def run_cmd(cmd):
50
  res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
51
  if res.returncode != 0:
52
- raise RuntimeError(f"FFmpeg Error: {res.stdout}")
53
  return res.stdout
54
 
55
  def load_model(name):
56
  if name in _cache: return _cache[name]
57
- if len(_cache) > 0:
58
- _cache.clear()
59
- if torch.cuda.is_available(): torch.cuda.empty_cache()
60
 
61
  repo, mode = MODELS[name]
62
  folder = snapshot_download(repo, local_dir_use_symlinks=False)
@@ -69,14 +70,17 @@ def load_model(name):
69
  else:
70
  try: model = nemo_asr.models.EncDecCTCModelBPE.restore_from(nemo_file)
71
  except: model = nemo_asr.models.EncDecCTCModel.restore_from(nemo_file)
 
72
  model.to(DEVICE).eval()
73
  _cache[name] = model
74
  return model
75
 
76
  def burn_subtitles(video_path, words, duration):
77
- out_name = f"robotsmali_output_{int(time.time())}.mp4"
 
78
  out_path = os.path.abspath(out_name)
79
 
 
80
  chunk_size = 7
81
  with tempfile.NamedTemporaryFile(suffix=".srt", mode="w", encoding="utf-8", delete=False) as tf:
82
  for i, idx in enumerate(range(0, len(words), chunk_size)):
@@ -90,6 +94,7 @@ def burn_subtitles(video_path, words, duration):
90
  tf.write(f"{i+1}\n{t_srt(start)} --> {t_srt(end)}\n{txt}\n\n")
91
  srt_name = tf.name
92
 
 
93
  vf = f"subtitles={shlex.quote(srt_name)}:force_style='Fontsize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&'"
94
  cmd = (
95
  f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_path)} '
@@ -97,19 +102,18 @@ def burn_subtitles(video_path, words, duration):
97
  f'-c:a aac -b:a 128k -movflags +faststart {shlex.quote(out_path)}'
98
  )
99
  run_cmd(cmd)
100
- os.remove(srt_name)
101
  return out_path
102
 
103
- # ---------------------------- # PIPELINE # ----------------------------
104
 
105
  def pipeline(video_input, model_name):
106
  try:
107
  if not video_input:
108
- yield "❌ Erreur : Aucune vidéo chargée.", None
109
  return
110
 
111
- # Phase 1 : Audio
112
- yield "⏳ Phase 1/3 : Analyse et extraction de l'audio...", None
113
  wav_path = os.path.abspath("temp_audio.wav")
114
  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)}')
115
 
@@ -117,58 +121,58 @@ def pipeline(video_input, model_name):
117
  shell=True, stdout=subprocess.PIPE, text=True).stdout
118
  duration = float(dur_out.strip()) if dur_out.strip() else 10.0
119
 
120
- # Phase 2 : Transcription
121
- yield f"⏳ Phase 2/3 : Transcription IA ({model_name})...", None
122
  model = load_model(model_name)
123
  res = model.transcribe([wav_path])[0]
124
  text = res.text if hasattr(res, 'text') else str(res)
125
  words = [w for w in text.split() if len(w) > 1]
126
 
127
  if not words:
128
- yield "⚠️ Phase 2 : Aucune parole détectée dans l'audio.", None
129
  return
130
 
131
- # Phase 3 : Encodage
132
- yield "⏳ Phase 3/3 : Génération de la vidéo sous-titrée...", None
133
- final_v = burn_subtitles(video_input, words, duration)
134
 
135
  if os.path.exists(wav_path): os.remove(wav_path)
136
-
137
- yield "✅ Traitement terminé avec succès !", final_v
138
 
139
  except Exception as e:
140
  traceback.print_exc()
141
- yield f"❌ Erreur critique : {str(e)}", None
142
 
143
- # ---------------------------- # INTERFACE GRADIO # ----------------------------
144
 
145
  custom_css = """
146
  body { background-color: #0b0e14; }
147
- .gradio-container { background: rgba(17, 25, 40, 0.8) !important; border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.1); }
148
- #header { text-align: center; padding: 20px; }
149
  .gr-button-primary { background: linear-gradient(135deg, #059669, #10b981) !important; border: none !important; }
150
  """
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;'>🤖 ROBOTSMALI</h1><p style='color:#94a3b8;'>Sous-titrage Bambara Intégral</p>")
155
 
156
  with gr.Row():
157
  with gr.Column():
158
- gr.Markdown("### 📥 Source")
159
- v_in = gr.Video(label=None, mirror_webcam=False)
160
  m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle IA")
161
  btn = gr.Button("🚀 GÉNÉRER", variant="primary")
162
 
163
  with gr.Column():
164
- gr.Markdown("### 📤 Résultat")
165
- # Le Statut est conservé ici pour informer sur l'état des fichiers audio/vidéo
166
- status = gr.Markdown("### État\n*En attente de démarrage...*")
167
- v_out = gr.Video(label="Vidéo finale")
 
 
 
 
 
168
 
169
- gr.Examples(examples=VIDEO_EXAMPLES, inputs=[v_in, m_sel], cache_examples=False)
170
-
171
  btn.click(pipeline, [v_in, m_sel], [status, v_out])
172
 
173
  if __name__ == "__main__":
174
- demo.launch(share=True, debug=True)
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ROBOTSMALI — Sous-titrage Bambara (VERSION 7.0 - STABLE & ÉPURÉE)
4
+ - Case de résultat unique (Lecture + Téléchargement)
5
+ - Statut de traitement détaillé (Audio -> IA -> Vidéo)
6
+ - Correction automatique des chemins d'exemples
7
  """
8
  import os
9
  import shlex
 
23
  from nemo.collections import asr as nemo_asr
24
  import gradio as gr
25
 
26
+ # ---------------------------- # CONFIGURATION TECHNIQUE # ----------------------------
27
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
 
 
28
 
29
  MODELS = {
 
 
30
  "Soloba V1 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v1", "ctc"),
31
+ "Soloni V1 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v1", "rnnt"),
32
  "QuartzNet V1 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v1", "ctc_char"),
33
+ "Soloba V0 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v0", "ctc"),
34
+ "Soloni V0 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v0", "rnnt"),
35
  "QuartzNet V0 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v0", "ctc_char"),
36
  }
37
 
38
+ # Recherche intelligente du fichier exemple
39
+ def find_example():
40
+ paths = ["examples/MARALINKE.mp4", "MARALINKE.mp4", "examples/maralinke.mp4"]
41
+ for p in paths:
42
+ if os.path.exists(p):
43
+ return p
44
+ return None
45
 
46
+ EXAMPLE_PATH = find_example()
47
  _cache = {}
48
 
49
+ # ---------------------------- # MOTEUR DE TRAITEMENT # ----------------------------
50
 
51
  def run_cmd(cmd):
52
  res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
53
  if res.returncode != 0:
54
+ raise RuntimeError(f"Erreur système: {res.stdout}")
55
  return res.stdout
56
 
57
  def load_model(name):
58
  if name in _cache: return _cache[name]
59
+ _cache.clear()
60
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
 
61
 
62
  repo, mode = MODELS[name]
63
  folder = snapshot_download(repo, local_dir_use_symlinks=False)
 
70
  else:
71
  try: model = nemo_asr.models.EncDecCTCModelBPE.restore_from(nemo_file)
72
  except: model = nemo_asr.models.EncDecCTCModel.restore_from(nemo_file)
73
+
74
  model.to(DEVICE).eval()
75
  _cache[name] = model
76
  return model
77
 
78
  def burn_subtitles(video_path, words, duration):
79
+ # Sortie dans le dossier courant pour éviter les pertes de fichiers temporaires
80
+ out_name = f"robotsmali_final_{int(time.time())}.mp4"
81
  out_path = os.path.abspath(out_name)
82
 
83
+ # Génération du fichier SRT
84
  chunk_size = 7
85
  with tempfile.NamedTemporaryFile(suffix=".srt", mode="w", encoding="utf-8", delete=False) as tf:
86
  for i, idx in enumerate(range(0, len(words), chunk_size)):
 
94
  tf.write(f"{i+1}\n{t_srt(start)} --> {t_srt(end)}\n{txt}\n\n")
95
  srt_name = tf.name
96
 
97
+ # Encodage H.264 ultra-compatible (MP4 Progressif)
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)} '
 
102
  f'-c:a aac -b:a 128k -movflags +faststart {shlex.quote(out_path)}'
103
  )
104
  run_cmd(cmd)
105
+ if os.path.exists(srt_name): os.remove(srt_name)
106
  return out_path
107
 
108
+ # ---------------------------- # PIPELINE PRINCIPALE # ----------------------------
109
 
110
  def pipeline(video_input, model_name):
111
  try:
112
  if not video_input:
113
+ yield "### État\n*Veuillez charger une vidéo.*", None
114
  return
115
 
116
+ yield "### ⏳ État\n*Phase 1 : Extraction du signal audio...*", 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
 
 
121
  shell=True, stdout=subprocess.PIPE, text=True).stdout
122
  duration = float(dur_out.strip()) if dur_out.strip() else 10.0
123
 
124
+ yield f"### ⏳ État\n*Phase 2 : Transcription Bambara ({model_name})...*", None
 
125
  model = load_model(model_name)
126
  res = model.transcribe([wav_path])[0]
127
  text = res.text if hasattr(res, 'text') else str(res)
128
  words = [w for w in text.split() if len(w) > 1]
129
 
130
  if not words:
131
+ yield "### ⚠️ État\n*Aucune parole détectée dans la vidéo.*", None
132
  return
133
 
134
+ yield "### ⏳ État\n*Phase 3 : Incrustation et rendu final...*", None
135
+ final_video = burn_subtitles(video_input, words, duration)
 
136
 
137
  if os.path.exists(wav_path): os.remove(wav_path)
138
+ yield "### ✅ État\n*Traitement terminé !*", final_video
 
139
 
140
  except Exception as e:
141
  traceback.print_exc()
142
+ yield f"### État\n*Erreur technique : {str(e)}*", None
143
 
144
+ # ---------------------------- # INTERFACE UTILISATEUR # ----------------------------
145
 
146
  custom_css = """
147
  body { background-color: #0b0e14; }
148
+ .gradio-container { background: rgba(17, 25, 40, 0.9) !important; border-radius: 15px; border: 1px solid rgba(255, 255, 255, 0.1); }
149
+ #header { text-align: center; padding: 15px; }
150
  .gr-button-primary { background: linear-gradient(135deg, #059669, #10b981) !important; border: none !important; }
151
  """
152
 
153
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
154
  with gr.Column(elem_id="header"):
155
+ gr.HTML("<h1 style='color:#facc15; margin:0;'>🤖 ROBOTSMALI</h1><p style='color:#94a3b8;'>Intelligence Artificielle de Sous-titrage Bambara</p>")
156
 
157
  with gr.Row():
158
  with gr.Column():
159
+ gr.Markdown("### 📥 1. ENTRÉE")
160
+ v_in = gr.Video(label="Vidéo à traiter")
161
  m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle IA")
162
  btn = gr.Button("🚀 GÉNÉRER", variant="primary")
163
 
164
  with gr.Column():
165
+ gr.Markdown("### 📤 2. SORTIE")
166
+ status = gr.Markdown("### État\n*Prêt*")
167
+ v_out = gr.Video(label="Résultat final")
168
+
169
+ # Gestion des exemples
170
+ if EXAMPLE_PATH:
171
+ gr.Examples(examples=[[EXAMPLE_PATH, "Soloba V1 (CTC)"]], inputs=[v_in, m_sel], label="📺 Exemple")
172
+ else:
173
+ gr.Markdown("⚠️ *Note : Aucun fichier exemple détecté sur le serveur.*")
174
 
 
 
175
  btn.click(pipeline, [v_in, m_sel], [status, v_out])
176
 
177
  if __name__ == "__main__":
178
+ demo.launch(debug=True,share=True)