Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,9 +9,9 @@ from huggingface_hub import snapshot_download
|
|
| 9 |
from nemo.collections import asr as nemo_asr
|
| 10 |
import gradio as gr
|
| 11 |
|
| 12 |
-
# 1. CONFIGURATION ET MODÈLES (
|
| 13 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 14 |
-
SEGMENT_DURATION = 5.0
|
| 15 |
|
| 16 |
MODELS = {
|
| 17 |
"Soloba V3 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v3", "ctc"),
|
|
@@ -38,14 +38,13 @@ def find_example_video():
|
|
| 38 |
try:
|
| 39 |
subprocess.run(f"wget {example_url} -O {target_path}", shell=True, check=True)
|
| 40 |
return target_path
|
| 41 |
-
except Exception
|
| 42 |
-
print(f"⚠️ Impossible de télécharger l'exemple : {e}")
|
| 43 |
return None
|
| 44 |
|
| 45 |
EXAMPLE_PATH = find_example_video()
|
| 46 |
_cache = {}
|
| 47 |
|
| 48 |
-
# 2. GESTION MÉMOIRE ET CHARGEMENT SÉCURISÉ
|
| 49 |
def clear_memory():
|
| 50 |
_cache.clear()
|
| 51 |
gc.collect()
|
|
@@ -61,17 +60,17 @@ def get_model(name):
|
|
| 61 |
nemo_file = next((os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(".nemo")), None)
|
| 62 |
if not nemo_file: raise FileNotFoundError("Fichier .nemo introuvable.")
|
| 63 |
|
| 64 |
-
#
|
| 65 |
from nemo.core.connectors.save_restore_connector import SaveRestoreConnector
|
| 66 |
-
|
| 67 |
-
|
| 68 |
model = nemo_asr.models.ASRModel.restore_from(
|
| 69 |
nemo_file,
|
| 70 |
map_location=torch.device(DEVICE),
|
| 71 |
-
save_restore_connector=
|
| 72 |
)
|
| 73 |
|
| 74 |
-
model.
|
| 75 |
if DEVICE == "cuda":
|
| 76 |
model = model.half()
|
| 77 |
|
|
@@ -92,22 +91,21 @@ def pipeline(video_in, model_name):
|
|
| 92 |
yield "❌ Aucune vidéo sélectionnée.", None
|
| 93 |
return
|
| 94 |
|
| 95 |
-
yield "⏳
|
| 96 |
full_wav = os.path.join(tmp_dir, "full.wav")
|
| 97 |
subprocess.run(f"ffmpeg -y -i {shlex.quote(video_in)} -vn -ac 1 -ar 16000 {full_wav}", shell=True, check=True)
|
| 98 |
|
| 99 |
-
yield f"⏳
|
| 100 |
subprocess.run(f"ffmpeg -i {full_wav} -f segment -segment_time {SEGMENT_DURATION} -c copy {os.path.join(tmp_dir, 'seg_%03d.wav')}", shell=True, check=True)
|
| 101 |
|
| 102 |
files = sorted(glob.glob(os.path.join(tmp_dir, "seg_*.wav")))
|
| 103 |
-
# Sécurité : Ignorer les fichiers vides (<1.5 Ko)
|
| 104 |
valid_segments = [f for f in files if os.path.getsize(f) > 1500]
|
| 105 |
|
| 106 |
if not valid_segments:
|
| 107 |
-
yield "❌ Erreur : Audio invalide.", None
|
| 108 |
return
|
| 109 |
|
| 110 |
-
yield f"⏳
|
| 111 |
model = get_model(model_name)
|
| 112 |
|
| 113 |
yield f"🎙️ Transcription de {len(valid_segments)} segments...", None
|
|
@@ -131,7 +129,7 @@ def pipeline(video_in, model_name):
|
|
| 131 |
"end": base_time + ((i+1) * gap)
|
| 132 |
})
|
| 133 |
|
| 134 |
-
yield "⏳
|
| 135 |
srt_path = os.path.join(tmp_dir, "final.srt")
|
| 136 |
with open(srt_path, "w", encoding="utf-8") as f:
|
| 137 |
for i in range(0, len(all_words_ts), 6):
|
|
@@ -142,7 +140,7 @@ def pipeline(video_in, model_name):
|
|
| 142 |
out_path = os.path.abspath(f"resultat_{int(time.time())}.mp4")
|
| 143 |
safe_srt = srt_path.replace("\\", "/").replace(":", "\\:")
|
| 144 |
|
| 145 |
-
cmd = f"ffmpeg -y -i {shlex.quote(video_in)} -vf \"subtitles='{safe_srt}':force_style='Alignment=2,FontSize=18,PrimaryColour=&H00FFFF'\" -c:v libx264 -preset ultrafast -c:a copy {out_path}"
|
| 146 |
subprocess.run(cmd, shell=True, check=True)
|
| 147 |
|
| 148 |
yield "✅ Terminé !", out_path
|
|
@@ -163,13 +161,12 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 163 |
m_input = gr.Dropdown(choices=list(MODELS.keys()), value="Soloni V3 (TDT-CTC)", label="Modèle")
|
| 164 |
run_btn = gr.Button("🚀 GÉNÉRER", variant="primary")
|
| 165 |
|
| 166 |
-
# Rétabli : Affichage des exemples si trouvés
|
| 167 |
if EXAMPLE_PATH:
|
| 168 |
gr.Examples(examples=[[EXAMPLE_PATH, "Soloni V3 (TDT-CTC)"]], inputs=[v_input, m_input])
|
| 169 |
|
| 170 |
with gr.Column():
|
| 171 |
status = gr.Markdown("### État\nPrêt.")
|
| 172 |
-
v_output = gr.Video(label="
|
| 173 |
|
| 174 |
run_btn.click(pipeline, [v_input, m_input], [status, v_output])
|
| 175 |
|
|
|
|
| 9 |
from nemo.collections import asr as nemo_asr
|
| 10 |
import gradio as gr
|
| 11 |
|
| 12 |
+
# 1. CONFIGURATION ET MODÈLES (INTÉGRALITÉ CONSERVÉE)
|
| 13 |
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 14 |
+
SEGMENT_DURATION = 5.0
|
| 15 |
|
| 16 |
MODELS = {
|
| 17 |
"Soloba V3 (CTC)": ("RobotsMali/soloba-ctc-0.6b-v3", "ctc"),
|
|
|
|
| 38 |
try:
|
| 39 |
subprocess.run(f"wget {example_url} -O {target_path}", shell=True, check=True)
|
| 40 |
return target_path
|
| 41 |
+
except Exception:
|
|
|
|
| 42 |
return None
|
| 43 |
|
| 44 |
EXAMPLE_PATH = find_example_video()
|
| 45 |
_cache = {}
|
| 46 |
|
| 47 |
+
# 2. GESTION MÉMOIRE ET CHARGEMENT SÉCURISÉ
|
| 48 |
def clear_memory():
|
| 49 |
_cache.clear()
|
| 50 |
gc.collect()
|
|
|
|
| 60 |
nemo_file = next((os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(".nemo")), None)
|
| 61 |
if not nemo_file: raise FileNotFoundError("Fichier .nemo introuvable.")
|
| 62 |
|
| 63 |
+
# FIX CRITIQUE : Importation et instanciation explicite du connecteur
|
| 64 |
from nemo.core.connectors.save_restore_connector import SaveRestoreConnector
|
| 65 |
+
|
| 66 |
+
# On force NeMo à utiliser cette instance précise pour éviter le bug __init__()
|
| 67 |
model = nemo_asr.models.ASRModel.restore_from(
|
| 68 |
nemo_file,
|
| 69 |
map_location=torch.device(DEVICE),
|
| 70 |
+
save_restore_connector=SaveRestoreConnector()
|
| 71 |
)
|
| 72 |
|
| 73 |
+
model.eval()
|
| 74 |
if DEVICE == "cuda":
|
| 75 |
model = model.half()
|
| 76 |
|
|
|
|
| 91 |
yield "❌ Aucune vidéo sélectionnée.", None
|
| 92 |
return
|
| 93 |
|
| 94 |
+
yield "⏳ Extraction audio...", None
|
| 95 |
full_wav = os.path.join(tmp_dir, "full.wav")
|
| 96 |
subprocess.run(f"ffmpeg -y -i {shlex.quote(video_in)} -vn -ac 1 -ar 16000 {full_wav}", shell=True, check=True)
|
| 97 |
|
| 98 |
+
yield f"⏳ Segmentation ({SEGMENT_DURATION}s)...", None
|
| 99 |
subprocess.run(f"ffmpeg -i {full_wav} -f segment -segment_time {SEGMENT_DURATION} -c copy {os.path.join(tmp_dir, 'seg_%03d.wav')}", shell=True, check=True)
|
| 100 |
|
| 101 |
files = sorted(glob.glob(os.path.join(tmp_dir, "seg_*.wav")))
|
|
|
|
| 102 |
valid_segments = [f for f in files if os.path.getsize(f) > 1500]
|
| 103 |
|
| 104 |
if not valid_segments:
|
| 105 |
+
yield "❌ Erreur : Audio trop court ou invalide.", None
|
| 106 |
return
|
| 107 |
|
| 108 |
+
yield f"⏳ Chargement de {model_name}...", None
|
| 109 |
model = get_model(model_name)
|
| 110 |
|
| 111 |
yield f"🎙️ Transcription de {len(valid_segments)} segments...", None
|
|
|
|
| 129 |
"end": base_time + ((i+1) * gap)
|
| 130 |
})
|
| 131 |
|
| 132 |
+
yield "⏳ Encodage vidéo...", None
|
| 133 |
srt_path = os.path.join(tmp_dir, "final.srt")
|
| 134 |
with open(srt_path, "w", encoding="utf-8") as f:
|
| 135 |
for i in range(0, len(all_words_ts), 6):
|
|
|
|
| 140 |
out_path = os.path.abspath(f"resultat_{int(time.time())}.mp4")
|
| 141 |
safe_srt = srt_path.replace("\\", "/").replace(":", "\\:")
|
| 142 |
|
| 143 |
+
cmd = f"ffmpeg -y -i {shlex.quote(video_in)} -vf \"subtitles='{safe_srt}':force_style='Alignment=2,FontSize=18,PrimaryColour=&H00FFFF,Outline=1'\" -c:v libx264 -preset ultrafast -c:a copy {out_path}"
|
| 144 |
subprocess.run(cmd, shell=True, check=True)
|
| 145 |
|
| 146 |
yield "✅ Terminé !", out_path
|
|
|
|
| 161 |
m_input = gr.Dropdown(choices=list(MODELS.keys()), value="Soloni V3 (TDT-CTC)", label="Modèle")
|
| 162 |
run_btn = gr.Button("🚀 GÉNÉRER", variant="primary")
|
| 163 |
|
|
|
|
| 164 |
if EXAMPLE_PATH:
|
| 165 |
gr.Examples(examples=[[EXAMPLE_PATH, "Soloni V3 (TDT-CTC)"]], inputs=[v_input, m_input])
|
| 166 |
|
| 167 |
with gr.Column():
|
| 168 |
status = gr.Markdown("### État\nPrêt.")
|
| 169 |
+
v_output = gr.Video(label="Résultat")
|
| 170 |
|
| 171 |
run_btn.click(pipeline, [v_input, m_input], [status, v_output])
|
| 172 |
|