fix concat
This commit is contained in:
@@ -401,15 +401,19 @@ class VideoProcessor:
|
|||||||
if not chunk_files:
|
if not chunk_files:
|
||||||
raise ValueError("No chunk files to merge")
|
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
|
# Create temporary directory for frame images
|
||||||
all_frames = []
|
import tempfile
|
||||||
|
temp_frames_dir = Path(tempfile.mkdtemp(prefix="merge_frames_"))
|
||||||
|
frame_counter = 0
|
||||||
|
|
||||||
try:
|
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):
|
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)
|
# Load chunk (this is the only copy in memory)
|
||||||
chunk_data = np.load(str(chunk_file))
|
chunk_data = np.load(str(chunk_file))
|
||||||
@@ -422,9 +426,16 @@ class VideoProcessor:
|
|||||||
frames = frames[overlap_frames:]
|
frames = frames[overlap_frames:]
|
||||||
print(f" ✂️ Skipped {overlap_frames} overlapping frames")
|
print(f" ✂️ Skipped {overlap_frames} overlapping frames")
|
||||||
|
|
||||||
# Add frames to final sequence
|
# Save frames directly to disk (no accumulation in memory)
|
||||||
all_frames.extend(frames)
|
for frame in frames:
|
||||||
print(f" ✅ Added {len(frames)} frames (total: {len(all_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
|
# Immediately free chunk memory
|
||||||
del frames, chunk_data
|
del frames, chunk_data
|
||||||
@@ -439,9 +450,9 @@ class VideoProcessor:
|
|||||||
# Aggressive cleanup every chunk
|
# Aggressive cleanup every chunk
|
||||||
self._aggressive_memory_cleanup(f"After processing chunk {i}")
|
self._aggressive_memory_cleanup(f"After processing chunk {i}")
|
||||||
|
|
||||||
# Save final video using existing method
|
# Create final video directly from frame images using ffmpeg
|
||||||
print(f"📹 Saving final video with {len(all_frames)} frames...")
|
print(f"📹 Creating final video from {frame_counter} frames...")
|
||||||
self.save_video(all_frames, output_path)
|
self._create_video_from_frames(temp_frames_dir, Path(output_path), frame_counter)
|
||||||
|
|
||||||
# Add audio if provided
|
# Add audio if provided
|
||||||
if audio_source:
|
if audio_source:
|
||||||
@@ -449,19 +460,72 @@ class VideoProcessor:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Streaming merge failed: {e}")
|
print(f"❌ Streaming merge failed: {e}")
|
||||||
# Cleanup
|
|
||||||
if 'all_frames' in locals():
|
|
||||||
del all_frames
|
|
||||||
gc.collect()
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Cleanup
|
# Cleanup temporary frames directory
|
||||||
if 'all_frames' in locals():
|
try:
|
||||||
del all_frames
|
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()
|
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):
|
def _add_audio_to_video(self, video_path: str, audio_source: str):
|
||||||
"""Add audio to video using ffmpeg"""
|
"""Add audio to video using ffmpeg"""
|
||||||
|
|||||||
Reference in New Issue
Block a user