# src/rendering/video_assembler.py import re from pathlib import Path from datetime import datetime from moviepy import VideoFileClip, concatenate_videoclips from src.utils.helpers import get_hash from src.utils.smart_logger import logger def assemble_final_video(project_config: dict, output_dir: Path, temp_dir): """Workflow Step 5: Combine all rendered scenes into a final video.""" logger.start_operation("assemble_video", "Assembling final video") code_file_path = next((s["code_file"] for s in project_config["scenes"].values() if s.get("code_file")), None) if not code_file_path: logger.end_operation("assemble_video", "Can't find code file for scene order") return code_content = Path(code_file_path).read_text() match = re.search(r"SCENE_ORDER\s*=\s*(\[.*?\])", code_content, re.DOTALL) if not match: logger.end_operation("assemble_video", "SCENE_ORDER not found in code") return try: scene_order = eval(match.group(1)) logger.quick_log("info", f"Scene order: {scene_order}") except: logger.end_operation("assemble_video", "Could not parse SCENE_ORDER") return video_clips = [] for scene_name in scene_order: scene_data = project_config["scenes"].get(scene_name, {}) video_path = scene_data.get("final_video_path") if video_path and Path(video_path).exists(): clip = VideoFileClip(video_path) video_clips.append(clip) else: logger.quick_log("warn", f"Skipping {scene_name}: video not found") if not video_clips: logger.end_operation("assemble_video", "No video clips found to assemble") return try: final_video = concatenate_videoclips(video_clips) prompt_hash = get_hash(project_config.get("prompt", "no_prompt")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_file = output_dir / f"{prompt_hash}_{timestamp}.mp4" final_video.write_videofile(str(output_file), codec='libx264', audio_codec='aac', logger=None) for clip in video_clips: clip.close() file_size = output_file.stat().st_size / (1024*1024) logger.end_operation("assemble_video", f"Final video created: {output_file.name}", f"{file_size:.1f}MB") except Exception as e: logger.error_with_context("Final video creation failed", str(e)) logger.end_operation("assemble_video", "Assembly failed") # Clean up temporary directory temp_dir.cleanup() logger.quick_log("info", "Temporary files cleaned up")