# renderer.py import subprocess from pathlib import Path from config import ( TEMP_DIR, OUTPUT_DIR, ) # ===================================================== # BUILD CONCAT FILE # ===================================================== def build_concat_file( timeline, concat_file=None ): """ timeline: [ { "path": "clips/1_A.mp4", "duration": 7.1, ... } ] """ if concat_file is None: concat_file = ( TEMP_DIR / "concat.txt" ) concat_file = Path(concat_file) with open( concat_file, "w", encoding="utf-8" ) as f: for scene in timeline: clip_path = ( Path(scene["path"]) .resolve() .as_posix() ) f.write( f"file '{clip_path}'\n" ) return str(concat_file) # ===================================================== # ESTIMATE TIMELINE DURATION # ===================================================== def estimate_duration( timeline ): total = 0 for scene in timeline: total += scene["duration"] return round(total, 2) # ===================================================== # RENDER TIMELINE (FAST) # ===================================================== def render_timeline( timeline, output_file=None ): """ Fast concat mode. Semua clip HARUS: - 1280x720 - 30fps - h264 - yuv420p - no audio """ if not timeline: raise ValueError( "Timeline is empty" ) if output_file is None: output_file = ( OUTPUT_DIR / "video_only.mp4" ) output_file = Path(output_file) concat_file = build_concat_file( timeline ) cmd = [ "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", concat_file, "-c", "copy", str(output_file) ] subprocess.run( cmd, check=True, capture_output=True ) return str(output_file) # ===================================================== # SAFE RENDER (RE-ENCODE) # ===================================================== def render_timeline_safe( timeline, output_file=None ): """ Gunakan jika concat copy gagal. Lebih lambat, tetapi paling kompatibel. """ if not timeline: raise ValueError( "Timeline is empty" ) if output_file is None: output_file = ( OUTPUT_DIR / "video_only.mp4" ) output_file = Path(output_file) concat_file = build_concat_file( timeline ) cmd = [ "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", concat_file, "-c:v", "libx264", "-preset", "ultrafast", "-crf", "28", "-pix_fmt", "yuv420p", str(output_file) ] subprocess.run( cmd, check=True, capture_output=True ) return str(output_file) # ===================================================== # AUTO RENDER # ===================================================== def render( timeline, output_file=None ): """ Coba fast mode dulu. Jika gagal, fallback ke safe mode. """ try: return render_timeline( timeline, output_file ) except Exception as e: print( "[Renderer] Fast concat failed:" ) print(e) print( "[Renderer] Falling back to re-encode..." ) return render_timeline_safe( timeline, output_file ) # ===================================================== # TEST # ===================================================== if __name__ == "__main__": timeline = [ { "path": "clips/1_A.mp4", "duration": 7.0 }, { "path": "clips/2_A.mp4", "duration": 7.0 }, { "path": "clips/3_A.mp4", "duration": 7.0 }, ] print( "Estimated:", estimate_duration( timeline ) ) result = render( timeline ) print( "Output:", result )