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é")