binaryMao commited on
Commit
33e6f73
·
verified ·
1 Parent(s): 4fb6b44

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -58
app.py CHANGED
@@ -1,29 +1,26 @@
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
10
  import subprocess
11
  import tempfile
12
  import traceback
13
- import random
14
  import textwrap
15
  import time
16
  from pathlib import Path
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
23
 
24
- # ---------------------------- # CONFIGURATION # ----------------------------
25
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
26
 
 
27
  MODELS = {
28
  "Soloba V1 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v1", "ctc"),
29
  "Soloni V1 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v1", "rnnt"),
@@ -33,27 +30,24 @@ MODELS = {
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:
40
  if os.path.exists(p): return p
41
  return None
42
 
43
- EXAMPLE_PATH = get_example()
44
  _cache = {}
45
 
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()
@@ -67,19 +61,20 @@ def load_model(name):
67
  elif mode == "ctc_char":
68
  model = nemo_asr.models.EncDecCTCModel.restore_from(nemo_file)
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
 
73
  model.to(DEVICE).eval()
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,14 +88,14 @@ def process_video(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
- # 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)} '
100
  f'-vf {shlex.quote(vf)} -c:v libx264 -pix_fmt yuv420p -preset ultrafast -crf 28 '
101
  f'-c:a aac -b:a 128k -movflags +faststart {shlex.quote(out_path)}'
102
  )
103
- run_cmd(cmd)
104
  if os.path.exists(srt_name): os.remove(srt_name)
105
  return out_path
106
 
@@ -109,69 +104,67 @@ def process_video(video_path, words, duration):
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
122
 
123
- yield f"### ⏳ État\n*Phase 2/3 : Transcription IA ({model_name})...*", None
124
  model = load_model(model_name)
125
  res = model.transcribe([wav_path])[0]
126
- text = res.text if hasattr(res, 'text') else str(res)
127
- words = [w for w in text.split() if len(w) > 1]
128
 
129
  if not words:
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; }
147
- .gradio-container { background: rgba(17, 25, 40, 0.9) !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; margin:0;'>🤖 ROBOTSMALI</h1><p style='color:#94a3b8;'>Intelligence Artificielle pour le Bambara</p>")
155
 
156
  with gr.Row():
157
  with gr.Column():
158
- gr.Markdown("### 📥 1. CHARGEMENT")
159
- v_in = gr.Video(label="Vidéo source", 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("### 📤 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)
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ROBOTSMALI — Sous-titrage Bambara (VERSION 7.7 - INTÉGRALE)
4
+ -=
 
 
5
  """
6
  import os
7
  import shlex
8
  import subprocess
9
  import tempfile
10
  import traceback
 
11
  import textwrap
12
  import time
13
  from pathlib import Path
14
 
 
15
  import torch
16
  from huggingface_hub import snapshot_download
17
  from nemo.collections import asr as nemo_asr
18
  import gradio as gr
19
 
20
+ # ---------------------------- # CONFIGURATION DES MODÈLES # ----------------------------
21
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
22
 
23
+ # La liste complète et correcte des modèles RobotsMali
24
  MODELS = {
25
  "Soloba V1 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v1", "ctc"),
26
  "Soloni V1 (RNNT)": ("RobotsMali/soloni-114m-tdt-ctc-v1", "rnnt"),
 
30
  "QuartzNet V0 (CTC-char)": ("RobotsMali/stt-bm-quartznet15x5-v0", "ctc_char"),
31
  }
32
 
33
+ # Détection du chemin absolu pour la vidéo d'exemple
34
+ def get_absolute_example():
35
+ paths = [
36
+ os.path.abspath("MARALINKE.mp4"),
37
+ os.path.abspath("examples/MARALINKE.mp4"),
38
+ "/home/user/app/MARALINKE.mp4",
39
+ "/home/user/app/examples/MARALINKE.mp4"
40
+ ]
41
  for p in paths:
42
  if os.path.exists(p): return p
43
  return None
44
 
45
+ EXAMPLE_PATH = get_absolute_example()
46
  _cache = {}
47
 
48
+ # ---------------------------- # MOTEUR IA & VIDÉO # ----------------------------
 
 
 
 
 
 
 
49
 
50
  def load_model(name):
 
51
  if name in _cache: return _cache[name]
52
  _cache.clear()
53
  if torch.cuda.is_available(): torch.cuda.empty_cache()
 
61
  elif mode == "ctc_char":
62
  model = nemo_asr.models.EncDecCTCModel.restore_from(nemo_file)
63
  else:
64
+ try:
65
+ model = nemo_asr.models.EncDecCTCModelBPE.restore_from(nemo_file)
66
+ except:
67
+ model = nemo_asr.models.EncDecCTCModel.restore_from(nemo_file)
68
 
69
  model.to(DEVICE).eval()
70
  _cache[name] = model
71
  return model
72
 
73
+ def burn_subtitles(video_path, words, duration):
74
+ out_name = f"robotsmali_output_{int(time.time())}.mp4"
 
75
  out_path = os.path.abspath(out_name)
76
 
77
+ # Génération du SRT
78
  chunk_size = 7
79
  with tempfile.NamedTemporaryFile(suffix=".srt", mode="w", encoding="utf-8", delete=False) as tf:
80
  for i, idx in enumerate(range(0, len(words), chunk_size)):
 
88
  tf.write(f"{i+1}\n{t_srt(start)} --> {t_srt(end)}\n{txt}\n\n")
89
  srt_name = tf.name
90
 
91
+ # Rendu FFmpeg avec optimisation FastStart pour corriger la durée web
92
  vf = f"subtitles={shlex.quote(srt_name)}:force_style='Fontsize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&'"
93
  cmd = (
94
  f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_path)} '
95
  f'-vf {shlex.quote(vf)} -c:v libx264 -pix_fmt yuv420p -preset ultrafast -crf 28 '
96
  f'-c:a aac -b:a 128k -movflags +faststart {shlex.quote(out_path)}'
97
  )
98
+ subprocess.run(cmd, shell=True, check=True)
99
  if os.path.exists(srt_name): os.remove(srt_name)
100
  return out_path
101
 
 
104
  def pipeline(video_input, model_name):
105
  try:
106
  if not video_input:
107
+ yield "### ❌ Erreur : Vidéo manquante.", None
108
  return
109
 
110
+ yield "### ⏳ Étape 1/3 : Extraction Audio...", None
111
  wav_path = os.path.abspath("temp_audio.wav")
112
+ subprocess.run(f"ffmpeg -y -i {shlex.quote(video_input)} -vn -ac 1 -ar 16000 {wav_path}", shell=True, check=True)
113
 
114
+ # Détection précise de la durée
115
  dur_out = subprocess.run(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(video_input)}',
116
  shell=True, stdout=subprocess.PIPE, text=True).stdout
117
  duration = float(dur_out.strip()) if dur_out.strip() else 10.0
118
 
119
+ yield f"### ⏳ Étape 2/3 : Transcription avec {model_name}...", None
120
  model = load_model(model_name)
121
  res = model.transcribe([wav_path])[0]
122
+ words = (res.text if hasattr(res, 'text') else str(res)).split()
 
123
 
124
  if not words:
125
+ yield "### ⚠️ Aucune parole détectée.", None
126
  return
127
 
128
+ yield "### ⏳ Étape 3/3 : Finalisation Vidéo...", None
129
+ final_video = burn_subtitles(video_input, words, duration)
130
 
131
  if os.path.exists(wav_path): os.remove(wav_path)
132
+ yield "### ✅ Succès !", final_video
133
 
134
  except Exception as e:
135
  traceback.print_exc()
136
+ yield f"### ❌ Erreur : {str(e)}", None
137
 
138
+ def force_load_demo():
139
+ return EXAMPLE_PATH
140
 
141
+ # ---------------------------- # INTERFACE # ----------------------------
 
 
 
 
 
142
 
143
+ with gr.Blocks(theme=gr.themes.Soft(), css="body { background-color: #0b0e14; }") as demo:
144
+ gr.HTML("<h1 style='text-align:center; color:#facc15;'>🤖 ROBOTSMALI V7.7</h1>")
 
145
 
146
  with gr.Row():
147
  with gr.Column():
148
+ gr.Markdown("### 📥 CHARGEMENT")
149
+ v_in = gr.Video(label="Vidéo source", interactive=True)
150
+
151
+ if EXAMPLE_PATH:
152
+ btn_demo = gr.Button("📂 CHARGER LA DÉMO (MARALINKE)", variant="secondary")
153
+
154
  m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle IA")
155
+ btn_run = gr.Button("🚀 GÉNÉRER", variant="primary")
156
 
157
  with gr.Column():
158
+ gr.Markdown("### 📤 RÉSULTAT")
159
+ status = gr.Markdown("### État\nPrêt")
160
  v_out = gr.Video(label="Vidéo finale")
161
 
162
+ # Actions
163
  if EXAMPLE_PATH:
164
+ btn_demo.click(fn=force_load_demo, outputs=v_in)
165
  gr.Examples(examples=[[EXAMPLE_PATH, "Soloba V1 (CTC)"]], inputs=[v_in, m_sel], cache_examples=False)
166
 
167
+ btn_run.click(pipeline, [v_in, m_sel], [status, v_out])
168
 
169
  if __name__ == "__main__":
 
 
170
  demo.launch(share=True, debug=True)