binaryMao commited on
Commit
74445b0
·
verified ·
1 Parent(s): 173bdc2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -45
app.py CHANGED
@@ -5,9 +5,8 @@ from huggingface_hub import snapshot_download
5
  from nemo.collections import asr as nemo_asr
6
  import gradio as gr
7
 
8
- # 1. CONFIGURATION ET MODÈLES
9
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
10
-
11
  MODELS = {
12
  "Soloba V3 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v3", "ctc"),
13
  "Soloba V2 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v2", "ctc"),
@@ -23,7 +22,6 @@ MODELS = {
23
  "Traduction Soloni (ST)": ("RobotsMali/st-soloni-114m-tdt-ctc", "rnnt"),
24
  }
25
 
26
- # Détection de la vidéo d'exemple dans le dossier local
27
  def find_example_video():
28
  paths = ["examples/MARALINKE_FIXED.mp4", "examples/MARALINKE.mp4", "MARALINKE.mp4"]
29
  for p in paths:
@@ -33,64 +31,65 @@ def find_example_video():
33
  EXAMPLE_PATH = find_example_video()
34
  _cache = {}
35
 
36
- # 2. OPTIMISATION CHARGEMENT ET MÉMOIRE
37
- def clear_memory():
38
- _cache.clear()
39
- gc.collect()
40
- if torch.cuda.is_available():
41
- torch.cuda.empty_cache()
42
-
43
  def get_model(name):
44
  if name in _cache: return _cache[name]
45
- clear_memory()
46
- repo, _ = MODELS[name]
 
47
 
 
48
  folder = snapshot_download(repo, local_dir_use_symlinks=False)
49
  nemo_file = next((os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(".nemo")), None)
50
 
51
  from nemo.core.connectors.save_restore_connector import SaveRestoreConnector
52
  model = nemo_asr.models.ASRModel.restore_from(
53
- nemo_file,
54
- map_location=torch.device(DEVICE),
55
  save_restore_connector=SaveRestoreConnector()
56
  )
57
-
58
  model.to(DEVICE).eval()
59
- if DEVICE == "cuda":
60
- model.half()
61
-
62
  _cache[name] = model
63
  return model
64
 
65
- # 3. TRANSCRIPTION ET RENDU
66
  def format_srt_time(sec):
67
  td = time.gmtime(sec)
68
  ms = int((sec - int(sec)) * 1000)
69
  return f"{time.strftime('%H:%M:%S', td)},{ms:03}"
70
 
 
71
  def pipeline(video_in, model_name):
72
  tmp_dir = tempfile.mkdtemp()
73
  try:
74
- if not video_in: return "❌ Vidéo absente", None
75
-
76
- # FFmpeg optimisé (-threads 0)
77
- yield "🚀 Phase 1 : Extraction Audio...", None
 
 
78
  full_wav = os.path.join(tmp_dir, "full.wav")
79
  subprocess.run(f"ffmpeg -y -threads 0 -i {shlex.quote(video_in)} -vn -ac 1 -ar 16000 {full_wav}", shell=True, check=True)
80
-
81
- yield "🚀 Phase 2 : Segmentation...", None
 
82
  subprocess.run(f"ffmpeg -i {full_wav} -f segment -segment_time 20 -c copy {os.path.join(tmp_dir, 'seg_%03d.wav')}", shell=True, check=True)
83
  audio_segments = sorted(glob.glob(os.path.join(tmp_dir, "seg_*.wav")))
84
-
85
- # Inférence par BATCH (beaucoup plus rapide)
86
- yield f"🎙️ Phase 3 : Transcription Batch avec {model_name}...", None
87
  model = get_model(model_name)
88
 
89
- #
90
- batch_hypotheses = model.transcribe(audio_segments, batch_size=4, return_hypotheses=True)
 
 
 
 
91
 
92
  all_words_ts = []
93
  for idx, hyp in enumerate(batch_hypotheses):
 
 
94
  base_time = idx * 20
95
  if isinstance(hyp, list): hyp = hyp[0]
96
  text = hyp.text if hasattr(hyp, 'text') else str(hyp)
@@ -99,7 +98,8 @@ def pipeline(video_in, model_name):
99
  for i, w in enumerate(words):
100
  all_words_ts.append({"word": w, "start": base_time + (i * gap), "end": base_time + ((i+1) * gap)})
101
 
102
- # Génération SRT
 
103
  srt_path = os.path.join(tmp_dir, "final.srt")
104
  with open(srt_path, "w", encoding="utf-8") as f:
105
  for i in range(0, len(all_words_ts), 6):
@@ -107,38 +107,39 @@ def pipeline(video_in, model_name):
107
  f.write(f"{(i//6)+1}\n{format_srt_time(chunk[0]['start'])} --> {format_srt_time(chunk[-1]['end'])}\n")
108
  f.write(" ".join([c['word'] for c in chunk]) + "\n\n")
109
 
110
- # Rendu Final FFmpeg accéléré
111
- yield "🚀 Phase 4 : Encodage Vidéo...", None
112
- out_path = os.path.abspath(f"robotsmali_final_{int(time.time())}.mp4")
113
  safe_srt = srt_path.replace("\\", "/").replace(":", "\\:")
114
 
115
- cmd = f"ffmpeg -y -threads 0 -i {shlex.quote(video_in)} -vf \"subtitles='{safe_srt}':force_style='Alignment=2,FontSize=18,PrimaryColour=&H00FFFF'\" -c:v libx264 -preset superfast -c:a copy {out_path}"
 
116
  subprocess.run(cmd, shell=True, check=True)
117
 
118
- yield "✅ Succès !", out_path
119
 
120
  except Exception as e:
121
- yield f"❌ Erreur : {str(e)}", None
122
  finally:
123
  if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir)
124
 
125
- # 4. INTERFACE GRADIO
126
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
127
- gr.HTML("<div style='text-align:center;'><h1>🤖 RobotsMali Speech Lab</h1></div>")
128
 
129
  with gr.Row():
130
  with gr.Column():
131
- v_input = gr.Video(label="Source")
132
- m_input = gr.Dropdown(choices=list(MODELS.keys()), value="Soloba V3 (CTC)", label="Modèle")
133
- run_btn = gr.Button("🚀 LANCER LA TRANSCRIPTION", variant="primary")
134
 
135
  if EXAMPLE_PATH:
136
  gr.Examples(examples=[[EXAMPLE_PATH, "Soloba V3 (CTC)"]], inputs=[v_input, m_input])
137
 
138
  with gr.Column():
139
- status = gr.Markdown("Prêt.")
140
- v_output = gr.Video(label="Résultat")
141
 
 
142
  run_btn.click(pipeline, [v_input, m_input], [status, v_output])
143
 
144
- demo.launch()
 
 
5
  from nemo.collections import asr as nemo_asr
6
  import gradio as gr
7
 
8
+ # 1. CONFIGURATION
9
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
10
  MODELS = {
11
  "Soloba V3 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v3", "ctc"),
12
  "Soloba V2 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v2", "ctc"),
 
22
  "Traduction Soloni (ST)": ("RobotsMali/st-soloni-114m-tdt-ctc", "rnnt"),
23
  }
24
 
 
25
  def find_example_video():
26
  paths = ["examples/MARALINKE_FIXED.mp4", "examples/MARALINKE.mp4", "MARALINKE.mp4"]
27
  for p in paths:
 
31
  EXAMPLE_PATH = find_example_video()
32
  _cache = {}
33
 
34
+ # 2. GESTION MÉMOIRE
 
 
 
 
 
 
35
  def get_model(name):
36
  if name in _cache: return _cache[name]
37
+ _cache.clear()
38
+ gc.collect()
39
+ if torch.cuda.is_available(): torch.cuda.empty_cache()
40
 
41
+ repo, _ = MODELS[name]
42
  folder = snapshot_download(repo, local_dir_use_symlinks=False)
43
  nemo_file = next((os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(".nemo")), None)
44
 
45
  from nemo.core.connectors.save_restore_connector import SaveRestoreConnector
46
  model = nemo_asr.models.ASRModel.restore_from(
47
+ nemo_file, map_location=torch.device(DEVICE),
 
48
  save_restore_connector=SaveRestoreConnector()
49
  )
 
50
  model.to(DEVICE).eval()
51
+ if DEVICE == "cuda": model.half()
 
 
52
  _cache[name] = model
53
  return model
54
 
 
55
  def format_srt_time(sec):
56
  td = time.gmtime(sec)
57
  ms = int((sec - int(sec)) * 1000)
58
  return f"{time.strftime('%H:%M:%S', td)},{ms:03}"
59
 
60
+ # 3. PIPELINE AVEC GÉNÉRATEUR (POUR ÉVITER LE TIMEOUT)
61
  def pipeline(video_in, model_name):
62
  tmp_dir = tempfile.mkdtemp()
63
  try:
64
+ if not video_in:
65
+ yield "❌ Erreur : Aucune vidéo détectée.", None
66
+ return
67
+
68
+ # Phase 1 : Extraction Audio
69
+ yield "⏳ Phase 1/4 : Extraction de l'audio (FFmpeg)...", None
70
  full_wav = os.path.join(tmp_dir, "full.wav")
71
  subprocess.run(f"ffmpeg -y -threads 0 -i {shlex.quote(video_in)} -vn -ac 1 -ar 16000 {full_wav}", shell=True, check=True)
72
+
73
+ # Phase 2 : Découpage
74
+ yield "⏳ Phase 2/4 : Analyse et découpage des segments...", None
75
  subprocess.run(f"ffmpeg -i {full_wav} -f segment -segment_time 20 -c copy {os.path.join(tmp_dir, 'seg_%03d.wav')}", shell=True, check=True)
76
  audio_segments = sorted(glob.glob(os.path.join(tmp_dir, "seg_*.wav")))
77
+
78
+ # Phase 3 : Transcription (C'est ici que ça coupe d'habitude)
79
+ yield f"⏳ Phase 3/4 : Chargement de l'IA {model_name}...", None
80
  model = get_model(model_name)
81
 
82
+ yield f"🎙️ Transcription de {len(audio_segments)} segments en cours...", None
83
+
84
+ # Inférence Batch pour gagner du temps
85
+ # On réduit le batch_size si on est sur CPU pour éviter de saturer la RAM
86
+ b_size = 2 if DEVICE == "cpu" else 4
87
+ batch_hypotheses = model.transcribe(audio_segments, batch_size=b_size, return_hypotheses=True)
88
 
89
  all_words_ts = []
90
  for idx, hyp in enumerate(batch_hypotheses):
91
+ # On envoie un signal Gradio à chaque segment traité pour garder la connexion vivante
92
+ yield f"📝 Traitement du texte : {idx+1}/{len(audio_segments)} segments finis...", None
93
  base_time = idx * 20
94
  if isinstance(hyp, list): hyp = hyp[0]
95
  text = hyp.text if hasattr(hyp, 'text') else str(hyp)
 
98
  for i, w in enumerate(words):
99
  all_words_ts.append({"word": w, "start": base_time + (i * gap), "end": base_time + ((i+1) * gap)})
100
 
101
+ # Phase 4 : Rendu Final
102
+ yield "⏳ Phase 4/4 : Incrustation des sous-titres dans la vidéo...", None
103
  srt_path = os.path.join(tmp_dir, "final.srt")
104
  with open(srt_path, "w", encoding="utf-8") as f:
105
  for i in range(0, len(all_words_ts), 6):
 
107
  f.write(f"{(i//6)+1}\n{format_srt_time(chunk[0]['start'])} --> {format_srt_time(chunk[-1]['end'])}\n")
108
  f.write(" ".join([c['word'] for c in chunk]) + "\n\n")
109
 
110
+ out_path = os.path.abspath(f"resultat_{int(time.time())}.mp4")
 
 
111
  safe_srt = srt_path.replace("\\", "/").replace(":", "\\:")
112
 
113
+ # Encodage ultra-rapide
114
+ cmd = f"ffmpeg -y -threads 0 -i {shlex.quote(video_in)} -vf \"subtitles='{safe_srt}':force_style='Alignment=2,FontSize=18'\" -c:v libx264 -preset ultrafast -c:a copy {out_path}"
115
  subprocess.run(cmd, shell=True, check=True)
116
 
117
+ yield "✅ Succès ! La vidéo est prête ci-dessous.", out_path
118
 
119
  except Exception as e:
120
+ yield f"❌ Erreur critique : {str(e)}", None
121
  finally:
122
  if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir)
123
 
124
+ # 4. INTERFACE GRADIO AVEC TIMEOUT ÉTENDU
125
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
126
+ gr.HTML("<h1 style='text-align:center;'>🤖 RobotsMali Transcription Pro</h1>")
127
 
128
  with gr.Row():
129
  with gr.Column():
130
+ v_input = gr.Video(label="Vidéo (MP4)")
131
+ m_input = gr.Dropdown(choices=list(MODELS.keys()), value="Soloba V3 (CTC)", label="Modèle IA")
132
+ run_btn = gr.Button("🚀 LANCER", variant="primary")
133
 
134
  if EXAMPLE_PATH:
135
  gr.Examples(examples=[[EXAMPLE_PATH, "Soloba V3 (CTC)"]], inputs=[v_input, m_input])
136
 
137
  with gr.Column():
138
+ status = gr.Markdown("### État\nPrêt.")
139
+ v_output = gr.Video(label="Vidéo finale")
140
 
141
+ # Important : on utilise queue() pour gérer les longues attentes
142
  run_btn.click(pipeline, [v_input, m_input], [status, v_output])
143
 
144
+ # Configuration de la file d'attente (Queue) pour éviter les coupures HTTP
145
+ demo.queue(max_size=10).launch(show_error=True)