Spaces:
Sleeping
Sleeping
Delete src/video_utils.py
Browse files- src/video_utils.py +0 -125
src/video_utils.py
DELETED
|
@@ -1,125 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import re
|
| 3 |
-
import uuid
|
| 4 |
-
import traceback
|
| 5 |
-
from moviepy.editor import VideoFileClip, concatenate_videoclips
|
| 6 |
-
import moviepy.video.fx.all as vfx
|
| 7 |
-
from .config import OUT_DIR, W, H
|
| 8 |
-
|
| 9 |
-
# ----------------------------------------------------------------------
|
| 10 |
-
# 🔤 Fonctions utilitaires
|
| 11 |
-
# ----------------------------------------------------------------------
|
| 12 |
-
|
| 13 |
-
def safe_name(base):
|
| 14 |
-
"""Génère un nom de fichier sûr (sans caractères spéciaux)."""
|
| 15 |
-
return re.sub(r'[^a-zA-Z0-9_-]+', '_', str(base)).strip('_')
|
| 16 |
-
|
| 17 |
-
# ----------------------------------------------------------------------
|
| 18 |
-
# 📝 Génération du fichier SRT (corrigée)
|
| 19 |
-
# ----------------------------------------------------------------------
|
| 20 |
-
|
| 21 |
-
def write_srt(text, duration, base_name=None):
|
| 22 |
-
"""
|
| 23 |
-
Crée un fichier .srt à partir du texte et de la durée audio.
|
| 24 |
-
Utilise un nom stable lié à la capsule plutôt qu'un UUID aléatoire.
|
| 25 |
-
"""
|
| 26 |
-
# Nom de fichier SRT stable
|
| 27 |
-
if base_name:
|
| 28 |
-
srt_path = os.path.join(OUT_DIR, f"{safe_name(base_name)}.srt")
|
| 29 |
-
else:
|
| 30 |
-
srt_path = os.path.join(OUT_DIR, "subtitles.srt")
|
| 31 |
-
|
| 32 |
-
# Contenu basique : 1 seul bloc pour tout le texte
|
| 33 |
-
start_time = "00:00:00,000"
|
| 34 |
-
total_seconds = int(duration)
|
| 35 |
-
end_time = f"00:00:{total_seconds:02d},000"
|
| 36 |
-
content = f"1\n{start_time} --> {end_time}\n{text.strip()}\n"
|
| 37 |
-
|
| 38 |
-
# Écrire (en remplaçant si existe)
|
| 39 |
-
with open(srt_path, "w", encoding="utf-8") as f:
|
| 40 |
-
f.write(content)
|
| 41 |
-
|
| 42 |
-
print(f"[SRT] ✅ Fichier SRT généré : {srt_path}")
|
| 43 |
-
return srt_path
|
| 44 |
-
|
| 45 |
-
# ----------------------------------------------------------------------
|
| 46 |
-
# 🎬 Préparation de la vidéo présentateur
|
| 47 |
-
# ----------------------------------------------------------------------
|
| 48 |
-
|
| 49 |
-
def prepare_video_presentateur(video_path, duration, position, plein):
|
| 50 |
-
"""
|
| 51 |
-
Charge la vidéo du présentateur, ajuste la durée et la position.
|
| 52 |
-
"""
|
| 53 |
-
try:
|
| 54 |
-
clip = VideoFileClip(video_path)
|
| 55 |
-
print(f"[Video] Chargement: {video_path}")
|
| 56 |
-
print(f"[Video] Durée vidéo: {clip.duration:.2f}s, Audio: {duration:.2f}s")
|
| 57 |
-
|
| 58 |
-
# Bouclage si la vidéo est plus courte que l'audio
|
| 59 |
-
if clip.duration < duration:
|
| 60 |
-
loop_count = int(duration // clip.duration) + 1
|
| 61 |
-
clip = concatenate_videoclips([clip] * loop_count)
|
| 62 |
-
clip = clip.subclip(0, duration)
|
| 63 |
-
print(f"[Video] Bouclage nécessaire ({clip.duration:.2f}s -> {duration:.2f}s)")
|
| 64 |
-
else:
|
| 65 |
-
clip = clip.subclip(0, duration)
|
| 66 |
-
|
| 67 |
-
# Redimensionner si "plein" est activé
|
| 68 |
-
if plein:
|
| 69 |
-
clip = clip.resize(height=H)
|
| 70 |
-
else:
|
| 71 |
-
clip = clip.resize(height=int(H / 2))
|
| 72 |
-
|
| 73 |
-
# Position
|
| 74 |
-
pos_map = {
|
| 75 |
-
"bottom-right": (W - clip.w - 50, H - clip.h - 50),
|
| 76 |
-
"bottom-left": (50, H - clip.h - 50),
|
| 77 |
-
"top-right": (W - clip.w - 50, 50),
|
| 78 |
-
"top-left": (50, 50),
|
| 79 |
-
"center": ("center", "center"),
|
| 80 |
-
}
|
| 81 |
-
clip = clip.set_position(pos_map.get(position, ("center", "center")))
|
| 82 |
-
|
| 83 |
-
print(f"[Video] ✅ Vidéo préparée avec succès")
|
| 84 |
-
return clip
|
| 85 |
-
|
| 86 |
-
except Exception as e:
|
| 87 |
-
print(f"[Video] ❌ Erreur préparation vidéo : {e}")
|
| 88 |
-
traceback.print_exc()
|
| 89 |
-
return None
|
| 90 |
-
|
| 91 |
-
# ----------------------------------------------------------------------
|
| 92 |
-
# 💾 Écriture vidéo finale avec fallback
|
| 93 |
-
# ----------------------------------------------------------------------
|
| 94 |
-
|
| 95 |
-
def write_video_with_fallback(final_clip, out_path_base, fps=25):
|
| 96 |
-
"""
|
| 97 |
-
Tente d'écrire la vidéo avec MoviePy puis avec FFMPEG si échec.
|
| 98 |
-
"""
|
| 99 |
-
out_mp4 = f"{out_path_base}.mp4"
|
| 100 |
-
try:
|
| 101 |
-
print(f"[Export] Rendu vidéo en cours vers : {out_mp4}")
|
| 102 |
-
final_clip.write_videofile(
|
| 103 |
-
out_mp4,
|
| 104 |
-
codec="libx264",
|
| 105 |
-
audio_codec="aac",
|
| 106 |
-
fps=fps,
|
| 107 |
-
threads=4,
|
| 108 |
-
verbose=False,
|
| 109 |
-
logger=None
|
| 110 |
-
)
|
| 111 |
-
print(f"[Export] ✅ Vidéo exportée : {out_mp4}")
|
| 112 |
-
except Exception as e:
|
| 113 |
-
print(f"[Export] ❌ Échec MoviePy ({e}), tentative FFMPEG brute…")
|
| 114 |
-
try:
|
| 115 |
-
final_clip.write_videofile(
|
| 116 |
-
out_mp4,
|
| 117 |
-
codec="libx264",
|
| 118 |
-
audio_codec="aac",
|
| 119 |
-
fps=fps,
|
| 120 |
-
ffmpeg_params=["-preset", "ultrafast"]
|
| 121 |
-
)
|
| 122 |
-
print(f"[Export] ✅ Vidéo exportée avec fallback : {out_mp4}")
|
| 123 |
-
except Exception as e2:
|
| 124 |
-
print(f"[Export] ❌ FFMPEG a également échoué : {e2}")
|
| 125 |
-
return out_mp4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|