binaryMao commited on
Commit
b7b07c0
·
verified ·
1 Parent(s): bd6b31b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -27
app.py CHANGED
@@ -1,6 +1,9 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- ROBOTSMALI — Sous-titrage Bambara (VERSION INTÉGRALE V6.1 - FIX FINAL OUTPUT)
 
 
 
4
  """
5
  import os
6
  import shlex
@@ -10,7 +13,6 @@ import traceback
10
  import random
11
  import textwrap
12
  import time
13
- import shutil
14
  from pathlib import Path
15
 
16
  import numpy as np
@@ -42,12 +44,12 @@ VIDEO_EXAMPLES = [
42
 
43
  _cache = {}
44
 
45
- # ---------------------------- # FONCTIONS TECHNIQUES # ----------------------------
46
 
47
  def run_cmd(cmd):
48
  res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
49
  if res.returncode != 0:
50
- raise RuntimeError(f"Erreur FFmpeg: {res.stdout}")
51
  return res.stdout
52
 
53
  def load_model(name):
@@ -72,8 +74,9 @@ def load_model(name):
72
  return model
73
 
74
  def burn_subtitles(video_path, words, duration):
75
- # Création d'un fichier de sortie dans un dossier temporaire Gradio
76
- out_path = os.path.join(tempfile.gettempdir(), f"final_output_{int(time.time())}.mp4")
 
77
 
78
  chunk_size = 7
79
  with tempfile.NamedTemporaryFile(suffix=".srt", mode="w", encoding="utf-8", delete=False) as tf:
@@ -88,77 +91,79 @@ def burn_subtitles(video_path, words, duration):
88
  tf.write(f"{i+1}\n{t_srt(start)} --> {t_srt(end)}\n{txt}\n\n")
89
  srt_name = tf.name
90
 
91
- # Commande d'encodage optimisée pour le Web (H.264 Baseline + Faststart)
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'-movflags +faststart -c:a copy {shlex.quote(out_path)}'
97
  )
98
  run_cmd(cmd)
99
  os.remove(srt_name)
100
  return out_path
101
 
 
 
102
  def pipeline(video_input, model_name):
103
  try:
104
- if not video_input: return "❌ Veuillez charger une vidéo", None
105
 
106
- yield "⏳ Phase 1/3 : Analyse Audio...", None
107
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tf:
108
  wav_path = tf.name
109
 
110
- # Extraction stable
111
  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)}')
112
 
113
- # Récupération durée
114
  dur_out = subprocess.run(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(video_input)}',
115
  shell=True, stdout=subprocess.PIPE, text=True).stdout
116
  duration = float(dur_out.strip()) if dur_out.strip() else 10.0
117
 
118
- yield f"⏳ Phase 2/3 : Transcription IA ({model_name})...", None
119
  model = load_model(model_name)
120
  res = model.transcribe([wav_path])[0]
121
  text = res.text if hasattr(res, 'text') else str(res)
122
  words = [w for w in text.split() if len(w) > 1]
123
 
124
- if not words: return "⚠️ Pas de parole détectée.", None
125
 
126
- yield "⏳ Phase 3/3 : Encodage vidéo final...", None
127
  final_v = burn_subtitles(video_input, words, duration)
128
 
129
  if os.path.exists(wav_path): os.remove(wav_path)
130
 
131
- # On force Gradio à renvoyer le chemin absolu
132
- yield "✅ Succès !", gr.update(value=final_v)
133
 
134
  except Exception as e:
135
  traceback.print_exc()
136
- yield f"❌ Erreur: {str(e)}", None
137
 
138
  # ---------------------------- # INTERFACE # ----------------------------
139
 
140
  custom_css = """
141
  body { background-color: #0b0e14; }
142
- .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); }
143
- #title-header { text-align: center; padding: 20px; }
144
- .gr-button-primary { background: linear-gradient(135deg, #059669, #10b981) !important; border: none !important; }
145
  """
146
 
147
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
148
- with gr.Column(elem_id="title-header"):
149
- gr.HTML("<h1 style='color:#facc15;'>🤖 ROBOTSMALI</h1><p style='color:#94a3b8;'>Sous-titrage Bambara Professionnel</p>")
150
 
151
  with gr.Row():
152
  with gr.Column():
153
- v_in = gr.Video(label="Entrée", mirror_webcam=False)
154
- m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle")
155
  btn = gr.Button("🚀 GÉNÉRER", variant="primary")
156
  with gr.Column():
157
  status = gr.Markdown("*Prêt*")
158
- v_out = gr.Video(label="Résultat Final")
 
159
 
160
  gr.Examples(examples=VIDEO_EXAMPLES, inputs=[v_in, m_sel], cache_examples=False)
161
- btn.click(pipeline, [v_in, m_sel], [status, v_out])
 
 
162
 
163
  if __name__ == "__main__":
164
  demo.launch(share=True, debug=True)
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ROBOTSMALI — Sous-titrage Bambara (VERSION COMPLÈTE V6.3)
4
+ - Tous les modèles inclus
5
+ - Correction du clic sur exemple
6
+ - Correction de l'affichage vidéo (Output Fix)
7
  """
8
  import os
9
  import shlex
 
13
  import random
14
  import textwrap
15
  import time
 
16
  from pathlib import Path
17
 
18
  import numpy as np
 
44
 
45
  _cache = {}
46
 
47
+ # ---------------------------- # FONCTIONS IA & FFmpeg # ----------------------------
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: {res.stdout}")
53
  return res.stdout
54
 
55
  def load_model(name):
 
74
  return model
75
 
76
  def burn_subtitles(video_path, words, duration):
77
+ # On crée le fichier dans le dossier courant pour éviter les erreurs de permissions
78
+ out_name = f"resultat_robotsmali_{int(time.time())}.mp4"
79
+ out_path = os.path.abspath(out_name)
80
 
81
  chunk_size = 7
82
  with tempfile.NamedTemporaryFile(suffix=".srt", mode="w", encoding="utf-8", delete=False) as tf:
 
91
  tf.write(f"{i+1}\n{t_srt(start)} --> {t_srt(end)}\n{txt}\n\n")
92
  srt_name = tf.name
93
 
94
+ # Commande ultra-compatible pour navigateur (H.264, YUV420p)
95
  vf = f"subtitles={shlex.quote(srt_name)}:force_style='Fontsize=22,PrimaryColour=&HFFFFFF&,OutlineColour=&H000000&'"
96
  cmd = (
97
  f'ffmpeg -hide_banner -loglevel error -y -i {shlex.quote(video_path)} '
98
  f'-vf {shlex.quote(vf)} -c:v libx264 -pix_fmt yuv420p -preset ultrafast -crf 28 '
99
+ f'-c:a aac -b:a 128k {shlex.quote(out_path)}'
100
  )
101
  run_cmd(cmd)
102
  os.remove(srt_name)
103
  return out_path
104
 
105
+ # ---------------------------- # PIPELINE # ----------------------------
106
+
107
  def pipeline(video_input, model_name):
108
  try:
109
+ if not video_input: return "❌ Vidéo introuvable", None, None
110
 
111
+ yield "⏳ Phase 1/3 : Analyse Audio...", None, None
112
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tf:
113
  wav_path = tf.name
114
 
 
115
  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)}')
116
 
 
117
  dur_out = subprocess.run(f'ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {shlex.quote(video_input)}',
118
  shell=True, stdout=subprocess.PIPE, text=True).stdout
119
  duration = float(dur_out.strip()) if dur_out.strip() else 10.0
120
 
121
+ yield f"⏳ Phase 2/3 : Transcription {model_name}...", None, 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: return "⚠️ Pas de parole détectée", None, None
128
 
129
+ yield "⏳ Phase 3/3 : Encodage de la vidéo...", None, None
130
  final_v = burn_subtitles(video_input, words, duration)
131
 
132
  if os.path.exists(wav_path): os.remove(wav_path)
133
 
134
+ yield "✅ Succès !", final_v, final_v
 
135
 
136
  except Exception as e:
137
  traceback.print_exc()
138
+ yield f"❌ Erreur: {str(e)}", None, None
139
 
140
  # ---------------------------- # INTERFACE # ----------------------------
141
 
142
  custom_css = """
143
  body { background-color: #0b0e14; }
144
+ .gradio-container { background: rgba(17, 25, 40, 0.8) !important; border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.1); }
145
+ #header { text-align: center; padding: 20px; }
146
+ .gr-button-primary { background: linear-gradient(135deg, #059669, #10b981) !important; }
147
  """
148
 
149
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
150
+ with gr.Column(elem_id="header"):
151
+ gr.HTML("<h1 style='color:#facc15;'>🤖 ROBOTSMALI</h1><p style='color:#94a3b8;'>Sous-titrage Bambara Intégral</p>")
152
 
153
  with gr.Row():
154
  with gr.Column():
155
+ v_in = gr.Video(label="Source", mirror_webcam=False)
156
+ m_sel = gr.Dropdown(list(MODELS.keys()), value="Soloba V1 (CTC)", label="Modèle IA")
157
  btn = gr.Button("🚀 GÉNÉRER", variant="primary")
158
  with gr.Column():
159
  status = gr.Markdown("*Prêt*")
160
+ v_out = gr.Video(label="Vidéo Sous-titrée")
161
+ f_out = gr.File(label="Lien de secours (Téléchargement)")
162
 
163
  gr.Examples(examples=VIDEO_EXAMPLES, inputs=[v_in, m_sel], cache_examples=False)
164
+
165
+ # On renvoie vers status, le lecteur vidéo ET le composant fichier
166
+ btn.click(pipeline, [v_in, m_sel], [status, v_out, f_out])
167
 
168
  if __name__ == "__main__":
169
  demo.launch(share=True, debug=True)