from pathlib import Path from typing import Optional, List, Dict from moviepy import ( VideoFileClip, AudioFileClip, CompositeAudioClip, CompositeVideoClip ) from scripts.generate_subtitles import create_animated_subtitle_clip def edit_video( video_path: str, audio_path: str, music_path: Optional[str], output_path: str, *, music_volume: float = 0.10, subtitles: Optional[List[Dict]] = None, # ← nouveau ): """ Encodage final : ajoute voix, (optionnel) musique et sous-titres en UNE seule passe. """ vid_clip = VideoFileClip(video_path) voice_clip = AudioFileClip(audio_path) # ── piste audio composite ───────────────────────────────── tracks = [voice_clip] if music_path and Path(music_path).is_file(): try: music_clip = ( AudioFileClip(music_path) .with_volume_scaled(music_volume) .with_duration(vid_clip.duration) ) tracks.insert(0, music_clip) except Exception as err: print(f"⚠️ Music ignored: {err}") final_audio = CompositeAudioClip(tracks).with_duration(vid_clip.duration) # ── couche(s) vidéo / sous-titres ───────────────────────── layers = [vid_clip] if subtitles: w, h = vid_clip.size for sub in subtitles: layers.append( create_animated_subtitle_clip( sub["text"], sub["start"], sub["end"], w, h ) ) final_clip = ( CompositeVideoClip(layers, size=vid_clip.size) .with_duration(vid_clip.duration) .with_audio(final_audio) ) # ── export ──────────────────────────────────────────────── Path(output_path).parent.mkdir(parents=True, exist_ok=True) final_clip.write_videofile( output_path, codec="libx264", audio_codec="aac", fps=30, threads=os.cpu_count(), preset="medium", ffmpeg_params=["-pix_fmt", "yuv420p"] ) print(f"✅ Video written → {output_path}") # ── nettoyage ───────────────────────────────────────────── voice_clip.close() if "music_clip" in locals(): music_clip.close() final_audio.close() final_clip.close() vid_clip.close()