Spaces:
Running
on
Zero
Running
on
Zero
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() | |