CapsulesVideoPro_v2 / src /video_utils.py
omarbajouk's picture
Update src/video_utils.py
a1b31ab verified
import os, re, uuid, traceback
from moviepy.editor import VideoFileClip
import moviepy.video.fx.all as vfx
from .config import OUT_DIR, W, H
def safe_name(stem, ext=".mp4"):
stem = re.sub(r"[^\w\-]+", "_", stem)[:40]
return f"{stem}_{uuid.uuid4().hex[:6]}{ext}"
def prepare_video_presentateur(video_path, audio_duration, position, plein_ecran=False):
"""Prépare la vidéo du présentateur avec la bonne durée et position."""
try:
print(f"[Video] Chargement: {video_path}")
if not os.path.exists(video_path):
print(f"[Video] ❌ Fichier introuvable: {video_path}")
return None
v = VideoFileClip(video_path).without_audio()
print(f"[Video] Durée vidéo: {v.duration}s, Audio: {audio_duration}s")
if v.duration < audio_duration:
print(f"[Video] Bouclage nécessaire ({v.duration}s -> {audio_duration}s)")
v = v.fx(vfx.loop, duration=audio_duration)
elif v.duration > audio_duration:
print(f"[Video] Découpage nécessaire ({v.duration}s -> {audio_duration}s)")
v = v.subclip(0, audio_duration)
if plein_ecran:
print(f"[Video] Mode plein écran")
v = v.resize(newsize=(W, H)).set_position(("center", "center"))
else:
print(f"[Video] Mode incrustation, position: {position}")
v = v.resize(width=520)
pos_map = {
"bottom-right": ("right", "bottom"),
"bottom-left": ("left", "bottom"),
"top-right": ("right", "top"),
"top-left": ("left", "top"),
"center": ("center", "center"),
}
v = v.set_position(pos_map.get(position, ("right", "bottom")))
print(f"[Video] ✅ Vidéo préparée avec succès")
return v
except Exception as e:
print(f"[Video] ❌ Erreur préparation: {e}")
print(f"[Video] Traceback: {traceback.format_exc()}")
return None
def write_srt(text, duration, base_name=None):
"""
Crée un fichier SRT synchronisé avec la durée audio.
Si base_name est fourni, le fichier SRT porte ce nom fixe.
"""
parts = re.split(r'(?<=[\.!?])\s+', text.strip())
parts = [p for p in parts if p]
total = len("".join(parts)) or 1
cur = 0.0
srt = []
for i, p in enumerate(parts, 1):
prop = len(p) / total
start = cur
end = min(duration, cur + duration * prop)
cur = end
def ts(t):
m, s = divmod(t, 60)
h, m = divmod(m, 60)
return f"{int(h):02}:{int(m):02}:{int(s):02},000"
srt += [f"{i}", f"{ts(start)} --> {ts(end)}", p, ""]
# Nom du fichier SRT
if base_name:
safe_base = re.sub(r'[^\w\-]+', '_', base_name)[:40]
path = os.path.join(OUT_DIR, f"{safe_base}.srt")
else:
path = os.path.join(OUT_DIR, f"srt_default.srt")
# Écriture du fichier
with open(path, "w", encoding="utf-8") as f:
f.write("\n".join(srt))
print(f"[SRT] ✅ Fichier SRT généré : {path}")
return path
def write_video_with_fallback(final_clip, out_path_base, fps=25):
attempts = [
{"ext": ".mp4", "codec": "libx264", "audio_codec": "aac"},
{"ext": ".mp4", "codec": "mpeg4", "audio_codec": "aac"},
{"ext": ".mp4", "codec": "libx264", "audio_codec": "libmp3lame"},
]
ffmpeg_params = ["-pix_fmt", "yuv420p", "-movflags", "+faststart", "-threads", "1", "-shortest"]
last_err = None
for opt in attempts:
out = out_path_base if out_path_base.endswith(opt["ext"]) else out_path_base + opt["ext"]
try:
final_clip.write_videofile(
out,
fps=fps,
codec=opt["codec"],
audio_codec=opt["audio_codec"],
audio=True,
ffmpeg_params=ffmpeg_params,
logger=None,
threads=os.cpu_count(), # ✅ multi-threading activé
)
if os.path.exists(out) and os.path.getsize(out) > 150000:
return out
except Exception as e:
last_err = f"{type(e).__name__}: {e}"
raise RuntimeError(last_err or "FFmpeg a échoué")