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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -36
app.py CHANGED
@@ -1,9 +1,9 @@
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,7 +23,7 @@ from huggingface_hub import snapshot_download
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 = {
@@ -35,15 +35,14 @@ MODELS = {
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 # ----------------------------
@@ -51,7 +50,7 @@ _cache = {}
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):
@@ -76,11 +75,11 @@ def load_model(name):
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,7 +93,8 @@ def burn_subtitles(video_path, words, duration):
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)} '
@@ -105,74 +105,72 @@ def burn_subtitles(video_path, words, duration):
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
 
 
120
  dur_out = subprocess.run(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(video_input)}',
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)
 
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
 
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
 
29
  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:
42
+ if os.path.exists(p): return p
 
43
  return None
44
 
45
+ EXAMPLE_PATH = get_example()
46
  _cache = {}
47
 
48
  # ---------------------------- # MOTEUR DE TRAITEMENT # ----------------------------
 
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):
 
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
  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)} '
 
105
  if os.path.exists(srt_name): os.remove(srt_name)
106
  return out_path
107
 
108
+ # ---------------------------- # PIPELINE # ----------------------------
109
 
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
124
 
125
+ yield f"### ⏳ État\n*Phase 2/3 : Transcription IA ({model_name})...*", None
126
  model = load_model(model_name)
127
  res = model.transcribe([wav_path])[0]
128
  text = res.text if hasattr(res, 'text') else str(res)
129
  words = [w for w in text.split() if len(w) > 1]
130
 
131
  if not words:
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; }
149
+ .gradio-container { background: rgba(17, 25, 40, 0.9) !important; border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.1); }
150
+ #header { text-align: center; padding: 20px; }
151
  .gr-button-primary { background: linear-gradient(135deg, #059669, #10b981) !important; border: none !important; }
152
  """
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():
160
+ gr.Markdown("### 📥 1. CHARGEMENT")
161
+ v_in = gr.Video(label="Vidéo source", mirror_webcam=False)
162
  m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle IA")
163
  btn = gr.Button("🚀 GÉNÉRER", variant="primary")
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)