From fa945b9c3ebf883174b0c13d4dae0cb3edc75725 Mon Sep 17 00:00:00 2001 From: Scott Register Date: Sat, 26 Jul 2025 16:29:59 -0700 Subject: [PATCH] fix concat --- vr180_matting/video_processor.py | 102 +++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 19 deletions(-) diff --git a/vr180_matting/video_processor.py b/vr180_matting/video_processor.py index e2a2d5b..f21aa42 100644 --- a/vr180_matting/video_processor.py +++ b/vr180_matting/video_processor.py @@ -401,15 +401,19 @@ class VideoProcessor: if not chunk_files: raise ValueError("No chunk files to merge") - print(f"🎬 Streaming merge: {len(chunk_files)} chunks → {output_path}") + print(f"🎬 TRUE Streaming merge: {len(chunk_files)} chunks → {output_path}") - # Use simple concatenation approach - load and merge all frames sequentially - all_frames = [] + # Create temporary directory for frame images + import tempfile + temp_frames_dir = Path(tempfile.mkdtemp(prefix="merge_frames_")) + frame_counter = 0 try: - # Process each chunk without accumulation + print(f"📁 Using temp frames dir: {temp_frames_dir}") + + # Process each chunk and save frames directly to disk for i, chunk_file in enumerate(chunk_files): - print(f"📼 Loading chunk {i+1}/{len(chunk_files)}: {chunk_file.name}") + print(f"📼 Processing chunk {i+1}/{len(chunk_files)}: {chunk_file.name}") # Load chunk (this is the only copy in memory) chunk_data = np.load(str(chunk_file)) @@ -422,9 +426,16 @@ class VideoProcessor: frames = frames[overlap_frames:] print(f" ✂️ Skipped {overlap_frames} overlapping frames") - # Add frames to final sequence - all_frames.extend(frames) - print(f" ✅ Added {len(frames)} frames (total: {len(all_frames)})") + # Save frames directly to disk (no accumulation in memory) + for frame in frames: + frame_path = temp_frames_dir / f"frame_{frame_counter:06d}.jpg" + # Use high quality JPEG to minimize compression artifacts + success = cv2.imwrite(str(frame_path), frame, [cv2.IMWRITE_JPEG_QUALITY, 95]) + if not success: + raise RuntimeError(f"Failed to save frame {frame_counter}") + frame_counter += 1 + + print(f" ✅ Saved {len(frames)} frames to disk (total: {frame_counter})") # Immediately free chunk memory del frames, chunk_data @@ -439,9 +450,9 @@ class VideoProcessor: # Aggressive cleanup every chunk self._aggressive_memory_cleanup(f"After processing chunk {i}") - # Save final video using existing method - print(f"📹 Saving final video with {len(all_frames)} frames...") - self.save_video(all_frames, output_path) + # Create final video directly from frame images using ffmpeg + print(f"📹 Creating final video from {frame_counter} frames...") + self._create_video_from_frames(temp_frames_dir, Path(output_path), frame_counter) # Add audio if provided if audio_source: @@ -449,19 +460,72 @@ class VideoProcessor: except Exception as e: print(f"❌ Streaming merge failed: {e}") - # Cleanup - if 'all_frames' in locals(): - del all_frames - gc.collect() raise finally: - # Cleanup - if 'all_frames' in locals(): - del all_frames + # Cleanup temporary frames directory + try: + if temp_frames_dir.exists(): + import shutil + shutil.rmtree(temp_frames_dir) + print(f"🗑️ Cleaned up temp frames dir: {temp_frames_dir}") + except Exception as e: + print(f"⚠️ Could not cleanup temp frames dir: {e}") + + # Memory cleanup gc.collect() - print(f"✅ Streaming merge complete: {output_path}") + print(f"✅ TRUE Streaming merge complete: {output_path}") + + def _create_video_from_frames(self, frames_dir: Path, output_path: Path, frame_count: int): + """Create video directly from frame images using ffmpeg (memory efficient)""" + import subprocess + + frame_pattern = str(frames_dir / "frame_%06d.jpg") + fps = self.video_info['fps'] if hasattr(self, 'video_info') and self.video_info else 30.0 + + print(f"🎬 Creating video with ffmpeg: {frame_count} frames at {fps} fps") + + # Use GPU encoding if available, fallback to CPU + gpu_cmd = [ + 'ffmpeg', '-y', # -y to overwrite output file + '-framerate', str(fps), + '-i', frame_pattern, + '-c:v', 'h264_nvenc', # NVIDIA GPU encoder + '-preset', 'fast', + '-cq', '18', # Quality for GPU encoding + '-pix_fmt', 'yuv420p', + str(output_path) + ] + + cpu_cmd = [ + 'ffmpeg', '-y', # -y to overwrite output file + '-framerate', str(fps), + '-i', frame_pattern, + '-c:v', 'libx264', # CPU encoder + '-preset', 'medium', + '-crf', '18', # Quality for CPU encoding + '-pix_fmt', 'yuv420p', + str(output_path) + ] + + # Try GPU first + print(f"🚀 Trying GPU encoding...") + result = subprocess.run(gpu_cmd, capture_output=True, text=True) + + if result.returncode != 0: + print("⚠️ GPU encoding failed, using CPU...") + print(f"🔄 CPU encoding...") + result = subprocess.run(cpu_cmd, capture_output=True, text=True) + else: + print("✅ GPU encoding successful!") + + if result.returncode != 0: + print(f"❌ FFmpeg stdout: {result.stdout}") + print(f"❌ FFmpeg stderr: {result.stderr}") + raise RuntimeError(f"FFmpeg failed with return code {result.returncode}") + + print(f"✅ Video created successfully: {output_path}") def _add_audio_to_video(self, video_path: str, audio_source: str): """Add audio to video using ffmpeg"""