Fix the rendering errors
This commit is contained in:
389
croppa/main.py
389
croppa/main.py
@@ -144,6 +144,7 @@ class VideoEditor:
|
|||||||
self.render_thread = None
|
self.render_thread = None
|
||||||
self.render_cancelled = False
|
self.render_cancelled = False
|
||||||
self.render_progress_queue = queue.Queue()
|
self.render_progress_queue = queue.Queue()
|
||||||
|
self.ffmpeg_process = None # Track FFmpeg process for cancellation
|
||||||
|
|
||||||
# Display optimization - track when redraw is needed
|
# Display optimization - track when redraw is needed
|
||||||
self.display_needs_update = True
|
self.display_needs_update = True
|
||||||
@@ -1121,8 +1122,9 @@ class VideoEditor:
|
|||||||
self.feedback_message
|
self.feedback_message
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.display_needs_update and current_state == self.last_display_state:
|
# Always update display when paused to ensure UI elements are visible
|
||||||
return # Skip redraw if nothing changed
|
if not self.display_needs_update and current_state == self.last_display_state and self.is_playing:
|
||||||
|
return # Skip redraw if nothing changed and playing
|
||||||
|
|
||||||
self.last_display_state = current_state
|
self.last_display_state = current_state
|
||||||
self.display_needs_update = False
|
self.display_needs_update = False
|
||||||
@@ -1647,193 +1649,11 @@ class VideoEditor:
|
|||||||
output_height = int(crop_height * self.zoom_factor)
|
output_height = int(crop_height * self.zoom_factor)
|
||||||
|
|
||||||
# Send progress update
|
# Send progress update
|
||||||
self.render_progress_queue.put(("progress", "Setting up video writer...", 0.1, 0.0))
|
self.render_progress_queue.put(("progress", "Setting up FFmpeg encoder...", 0.1, 0.0))
|
||||||
|
|
||||||
# Check for AMD GPU
|
# Skip all the OpenCV codec bullshit and go straight to FFmpeg
|
||||||
try:
|
print("Using FFmpeg for encoding with OpenCV transformations...")
|
||||||
import subprocess
|
return self._render_with_ffmpeg_pipe(output_path, start_frame, end_frame, output_width, output_height)
|
||||||
result = subprocess.run(['wmic', 'path', 'win32_VideoController', 'get', 'name'],
|
|
||||||
capture_output=True, text=True, timeout=5)
|
|
||||||
if 'AMD' in result.stdout or 'Radeon' in result.stdout:
|
|
||||||
print("AMD GPU detected - attempting hardware acceleration")
|
|
||||||
else:
|
|
||||||
print(" AMD GPU not detected in system info")
|
|
||||||
except:
|
|
||||||
print(" Could not detect GPU info, proceeding with codec tests")
|
|
||||||
|
|
||||||
# Use codecs that are actually available in your OpenCV installation
|
|
||||||
# Based on the codec list, these should work
|
|
||||||
codecs_to_try = [
|
|
||||||
("mjpg", "MJPG"), # Motion JPEG - very reliable
|
|
||||||
("xvid", "XVID"), # Xvid codec
|
|
||||||
("divx", "DIVX"), # DivX codec
|
|
||||||
("mp4v", "mp4v"), # MPEG-4 Part 2 (lowercase)
|
|
||||||
("mp4v", "MP4V"), # MPEG-4 Part 2 (uppercase)
|
|
||||||
("h264", "avc1"), # H.264 with avc1 fourcc (from the list)
|
|
||||||
]
|
|
||||||
|
|
||||||
out = None
|
|
||||||
successful_codec = None
|
|
||||||
|
|
||||||
for codec_name, fourcc_code in codecs_to_try:
|
|
||||||
try:
|
|
||||||
print(f"Trying {codec_name} codec with {fourcc_code} fourcc...")
|
|
||||||
fourcc = cv2.VideoWriter_fourcc(*fourcc_code)
|
|
||||||
test_writer = cv2.VideoWriter(
|
|
||||||
output_path, fourcc, self.fps, (output_width, output_height)
|
|
||||||
)
|
|
||||||
|
|
||||||
if test_writer.isOpened():
|
|
||||||
# Test if the writer actually works by trying to write a test frame
|
|
||||||
import numpy as np
|
|
||||||
test_frame = np.zeros((output_height, output_width, 3), dtype=np.uint8)
|
|
||||||
test_success = test_writer.write(test_frame)
|
|
||||||
|
|
||||||
if test_success:
|
|
||||||
out = test_writer
|
|
||||||
successful_codec = f"{codec_name} ({fourcc_code})"
|
|
||||||
print(f"✅ Successfully initialized {codec_name} codec with {fourcc_code} fourcc")
|
|
||||||
if "amf" in codec_name.lower():
|
|
||||||
print("🚀 Using AMD hardware acceleration!")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
test_writer.release()
|
|
||||||
print(f"❌ {codec_name} codec opened but test write failed")
|
|
||||||
else:
|
|
||||||
test_writer.release()
|
|
||||||
print(f"❌ Failed to open {codec_name} codec")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error with {codec_name} codec: {e}")
|
|
||||||
if 'test_writer' in locals():
|
|
||||||
test_writer.release()
|
|
||||||
continue
|
|
||||||
|
|
||||||
if out is None:
|
|
||||||
# Try AVI format as last resort
|
|
||||||
print("MP4 format failed, trying AVI format...")
|
|
||||||
avi_path = output_path.replace('.mp4', '.avi')
|
|
||||||
|
|
||||||
# Try basic codecs with AVI
|
|
||||||
avi_codecs = [("mjpg", "MJPG"), ("xvid", "XVID"), ("mp4v", "MP4V")]
|
|
||||||
|
|
||||||
for codec_name, fourcc_code in avi_codecs:
|
|
||||||
try:
|
|
||||||
print(f"Trying {codec_name} codec with AVI format...")
|
|
||||||
fourcc = cv2.VideoWriter_fourcc(*fourcc_code)
|
|
||||||
test_writer = cv2.VideoWriter(
|
|
||||||
avi_path, fourcc, self.fps, (output_width, output_height)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test write
|
|
||||||
import numpy as np
|
|
||||||
test_frame = np.zeros((output_height, output_width, 3), dtype=np.uint8)
|
|
||||||
test_success = test_writer.write(test_frame)
|
|
||||||
|
|
||||||
if test_writer.isOpened() and test_success:
|
|
||||||
out = test_writer
|
|
||||||
successful_codec = f"{codec_name} (AVI)"
|
|
||||||
output_path = avi_path # Update output path
|
|
||||||
print(f"Successfully initialized {codec_name} codec with AVI format")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
test_writer.release()
|
|
||||||
print(f"Failed to initialize {codec_name} codec with AVI")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error with {codec_name} AVI codec: {e}")
|
|
||||||
if 'test_writer' in locals():
|
|
||||||
test_writer.release()
|
|
||||||
continue
|
|
||||||
|
|
||||||
if out is None:
|
|
||||||
# Last resort: try the most basic approach without specifying codec
|
|
||||||
print("All codecs failed, trying basic VideoWriter without codec specification...")
|
|
||||||
try:
|
|
||||||
# Try without specifying fourcc - let OpenCV choose
|
|
||||||
out = cv2.VideoWriter(
|
|
||||||
avi_path, -1, self.fps, (output_width, output_height)
|
|
||||||
)
|
|
||||||
if out.isOpened():
|
|
||||||
successful_codec = "auto (AVI)"
|
|
||||||
output_path = avi_path
|
|
||||||
print("Successfully initialized auto codec with AVI format")
|
|
||||||
else:
|
|
||||||
out.release()
|
|
||||||
out = None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Auto codec failed: {e}")
|
|
||||||
out = None
|
|
||||||
|
|
||||||
if out is None:
|
|
||||||
# Use FFmpeg instead of OpenCV for rendering
|
|
||||||
print("OpenCV codecs failed. Using FFmpeg for rendering...")
|
|
||||||
return self._render_with_ffmpeg(output_path, start_frame, end_frame, output_width, output_height)
|
|
||||||
|
|
||||||
if not out.isOpened():
|
|
||||||
self.render_progress_queue.put(("error", "Could not open video writer!", 1.0, 0.0))
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Optimized sequential processing - seek once, then read sequentially
|
|
||||||
total_output_frames = end_frame - start_frame + 1
|
|
||||||
last_progress_update = 0
|
|
||||||
|
|
||||||
# Seek once to the start frame
|
|
||||||
render_cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
|
||||||
|
|
||||||
for frame_idx in range(start_frame, end_frame + 1):
|
|
||||||
# Check for cancellation
|
|
||||||
if self.render_cancelled:
|
|
||||||
out.release()
|
|
||||||
self.render_progress_queue.put(("cancelled", "Render cancelled", 0.0, 0.0))
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Read frame sequentially
|
|
||||||
ret, frame = render_cap.read()
|
|
||||||
|
|
||||||
if not ret:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Process and write frame directly (minimize memory copies)
|
|
||||||
processed_frame = self._process_frame_for_render(
|
|
||||||
frame, output_width, output_height
|
|
||||||
)
|
|
||||||
|
|
||||||
if processed_frame is not None:
|
|
||||||
out.write(processed_frame)
|
|
||||||
|
|
||||||
frames_written = frame_idx - start_frame + 1
|
|
||||||
current_time = time.time()
|
|
||||||
|
|
||||||
# Update progress bar (10% to 95% of progress reserved for frame processing)
|
|
||||||
progress = 0.1 + (0.85 * (frames_written / total_output_frames))
|
|
||||||
|
|
||||||
# Throttled progress update
|
|
||||||
if current_time - last_progress_update > 0.5:
|
|
||||||
elapsed = current_time - start_time
|
|
||||||
fps_rate = frames_written / elapsed
|
|
||||||
eta = (elapsed / frames_written) * (
|
|
||||||
total_output_frames - frames_written
|
|
||||||
)
|
|
||||||
|
|
||||||
progress_text = f"Rendering {frames_written}/{total_output_frames} frames (ETA: {eta:.1f}s)"
|
|
||||||
self.render_progress_queue.put(("progress", progress_text, progress, fps_rate))
|
|
||||||
last_progress_update = current_time
|
|
||||||
|
|
||||||
out.release()
|
|
||||||
|
|
||||||
# Ensure the video writer is completely closed and file handles are freed
|
|
||||||
del out
|
|
||||||
time.sleep(0.1) # Small delay to ensure file is unlocked
|
|
||||||
|
|
||||||
total_time = time.time() - start_time
|
|
||||||
total_frames_written = end_frame - start_frame + 1
|
|
||||||
avg_fps = total_frames_written / total_time if total_time > 0 else 0
|
|
||||||
|
|
||||||
# Complete the progress bar
|
|
||||||
self.render_progress_queue.put(("complete", f"Complete! Rendered {total_frames_written} frames in {total_time:.1f}s", 1.0, avg_fps))
|
|
||||||
|
|
||||||
print(f"\nVideo rendered successfully to {output_path}")
|
|
||||||
print(f"Rendered {total_frames_written} frames in {total_time:.2f}s (avg {avg_fps:.1f} FPS)")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
@@ -1931,6 +1751,17 @@ class VideoEditor:
|
|||||||
"""Clean up render thread resources"""
|
"""Clean up render thread resources"""
|
||||||
if self.render_thread and self.render_thread.is_alive():
|
if self.render_thread and self.render_thread.is_alive():
|
||||||
self.render_cancelled = True
|
self.render_cancelled = True
|
||||||
|
# Terminate FFmpeg process if running
|
||||||
|
if self.ffmpeg_process:
|
||||||
|
try:
|
||||||
|
self.ffmpeg_process.terminate()
|
||||||
|
self.ffmpeg_process.wait(timeout=1.0)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
self.ffmpeg_process.kill()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.ffmpeg_process = None
|
||||||
# Wait a bit for the thread to finish gracefully
|
# Wait a bit for the thread to finish gracefully
|
||||||
self.render_thread.join(timeout=2.0)
|
self.render_thread.join(timeout=2.0)
|
||||||
if self.render_thread.is_alive():
|
if self.render_thread.is_alive():
|
||||||
@@ -2020,129 +1851,111 @@ class VideoEditor:
|
|||||||
print(f"Error processing frame: {e}")
|
print(f"Error processing frame: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _render_with_ffmpeg(self, output_path: str, start_frame: int, end_frame: int, output_width: int, output_height: int):
|
def _render_with_ffmpeg_pipe(self, output_path: str, start_frame: int, end_frame: int, output_width: int, output_height: int):
|
||||||
"""Use FFmpeg directly for rendering - much faster than OpenCV"""
|
"""Hybrid approach: OpenCV transformations + FFmpeg encoding via pipe"""
|
||||||
try:
|
try:
|
||||||
# Calculate time range
|
self.render_progress_queue.put(("progress", "Starting FFmpeg encoder...", 0.0, 0.0))
|
||||||
start_time = start_frame / self.fps
|
|
||||||
duration = (end_frame - start_frame + 1) / self.fps
|
|
||||||
|
|
||||||
# Create temporary directory for processed frames
|
# Start FFmpeg process to receive frames via pipe
|
||||||
temp_dir = tempfile.mkdtemp()
|
# Use Windows-friendly approach with explicit binary mode
|
||||||
frame_pattern = os.path.join(temp_dir, "frame_%06d.png")
|
ffmpeg_cmd = [
|
||||||
|
|
||||||
self.render_progress_queue.put(("progress", "Extracting frames with FFmpeg...", 0.1, 0.0))
|
|
||||||
|
|
||||||
# Extract frames using FFmpeg
|
|
||||||
extract_cmd = [
|
|
||||||
'ffmpeg', '-y', '-v', 'quiet',
|
'ffmpeg', '-y', '-v', 'quiet',
|
||||||
'-ss', str(start_time),
|
'-f', 'rawvideo',
|
||||||
'-i', str(self.video_path),
|
'-vcodec', 'rawvideo',
|
||||||
'-t', str(duration),
|
'-s', f'{output_width}x{output_height}',
|
||||||
'-vf', f'scale={output_width}:{output_height}',
|
'-pix_fmt', 'bgr24',
|
||||||
frame_pattern
|
'-r', str(self.fps),
|
||||||
]
|
'-i', '-', # Read from stdin
|
||||||
|
'-c:v', 'libx264',
|
||||||
result = subprocess.run(extract_cmd, capture_output=True, text=True)
|
'-preset', 'fast',
|
||||||
if result.returncode != 0:
|
'-crf', '18',
|
||||||
self.render_progress_queue.put(("error", f"FFmpeg extraction failed: {result.stderr}", 1.0, 0.0))
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Count extracted frames
|
|
||||||
frame_files = sorted([f for f in os.listdir(temp_dir) if f.startswith('frame_')])
|
|
||||||
total_frames = len(frame_files)
|
|
||||||
|
|
||||||
if total_frames == 0:
|
|
||||||
self.render_progress_queue.put(("error", "No frames extracted", 1.0, 0.0))
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.render_progress_queue.put(("progress", f"Processing {total_frames} frames...", 0.2, 0.0))
|
|
||||||
|
|
||||||
# Process frames with OpenCV (for transformations)
|
|
||||||
processed_frames = []
|
|
||||||
for i, frame_file in enumerate(frame_files):
|
|
||||||
if self.render_cancelled:
|
|
||||||
self.render_progress_queue.put(("cancelled", "Render cancelled", 0.0, 0.0))
|
|
||||||
return False
|
|
||||||
|
|
||||||
frame_path = os.path.join(temp_dir, frame_file)
|
|
||||||
frame = cv2.imread(frame_path)
|
|
||||||
|
|
||||||
if frame is not None:
|
|
||||||
# Apply transformations
|
|
||||||
processed_frame = self._process_frame_for_render(frame, output_width, output_height)
|
|
||||||
if processed_frame is not None:
|
|
||||||
processed_frames.append(processed_frame)
|
|
||||||
|
|
||||||
# Update progress
|
|
||||||
if i % 10 == 0:
|
|
||||||
progress = 0.2 + (0.6 * (i / total_frames))
|
|
||||||
self.render_progress_queue.put(("progress", f"Processing frame {i}/{total_frames}", progress, 0.0))
|
|
||||||
|
|
||||||
self.render_progress_queue.put(("progress", "Encoding with FFmpeg...", 0.8, 0.0))
|
|
||||||
|
|
||||||
# Save processed frames and encode with FFmpeg
|
|
||||||
processed_dir = os.path.join(temp_dir, "processed")
|
|
||||||
os.makedirs(processed_dir, exist_ok=True)
|
|
||||||
|
|
||||||
for i, frame in enumerate(processed_frames):
|
|
||||||
cv2.imwrite(os.path.join(processed_dir, f"frame_{i:06d}.png"), frame)
|
|
||||||
|
|
||||||
# Encode final video with FFmpeg
|
|
||||||
processed_pattern = os.path.join(processed_dir, "frame_%06d.png")
|
|
||||||
encode_cmd = [
|
|
||||||
'ffmpeg', '-y', '-v', 'info', # Changed from quiet to info for progress
|
|
||||||
'-framerate', str(self.fps),
|
|
||||||
'-i', processed_pattern,
|
|
||||||
'-c:v', 'libx264', # Use x264 encoder
|
|
||||||
'-preset', 'fast', # Fast encoding
|
|
||||||
'-crf', '18', # Good quality
|
|
||||||
'-pix_fmt', 'yuv420p',
|
'-pix_fmt', 'yuv420p',
|
||||||
'-progress', 'pipe:1', # Output progress to stdout
|
|
||||||
output_path
|
output_path
|
||||||
]
|
]
|
||||||
|
|
||||||
# Run FFmpeg with progress monitoring
|
# Start FFmpeg process with Windows-friendly settings
|
||||||
process = subprocess.Popen(encode_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
self.ffmpeg_process = subprocess.Popen(
|
||||||
|
ffmpeg_cmd,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
bufsize=0, # Unbuffered for better pipe performance
|
||||||
|
universal_newlines=False # Binary mode for Windows
|
||||||
|
)
|
||||||
|
|
||||||
# Monitor progress
|
# OpenCV for frame reading and transformations
|
||||||
while True:
|
render_cap = cv2.VideoCapture(str(self.video_path))
|
||||||
|
render_cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
||||||
|
|
||||||
|
total_frames = end_frame - start_frame + 1
|
||||||
|
frames_written = 0
|
||||||
|
start_time = time.time()
|
||||||
|
last_progress_update = 0
|
||||||
|
|
||||||
|
self.render_progress_queue.put(("progress", f"Processing {total_frames} frames...", 0.1, 0.0))
|
||||||
|
|
||||||
|
for i in range(total_frames):
|
||||||
if self.render_cancelled:
|
if self.render_cancelled:
|
||||||
process.terminate()
|
self.ffmpeg_process.stdin.close()
|
||||||
|
self.ffmpeg_process.terminate()
|
||||||
|
self.ffmpeg_process.wait()
|
||||||
|
render_cap.release()
|
||||||
|
self.ffmpeg_process = None
|
||||||
self.render_progress_queue.put(("cancelled", "Render cancelled", 0.0, 0.0))
|
self.render_progress_queue.put(("cancelled", "Render cancelled", 0.0, 0.0))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
output = process.stdout.readline()
|
ret, frame = render_cap.read()
|
||||||
if output == '' and process.poll() is not None:
|
if not ret:
|
||||||
break
|
break
|
||||||
|
|
||||||
if 'out_time_ms=' in output:
|
# Apply transformations with OpenCV
|
||||||
# Parse progress from FFmpeg output
|
processed_frame = self._process_frame_for_render(frame, output_width, output_height)
|
||||||
|
if processed_frame is not None:
|
||||||
|
# Write frame to FFmpeg via pipe
|
||||||
try:
|
try:
|
||||||
time_ms = int(output.split('out_time_ms=')[1].split()[0])
|
self.ffmpeg_process.stdin.write(processed_frame.tobytes())
|
||||||
total_time_ms = len(processed_frames) * (1000 / self.fps)
|
frames_written += 1
|
||||||
progress = min(0.95, 0.8 + (0.15 * (time_ms / total_time_ms)))
|
except BrokenPipeError:
|
||||||
self.render_progress_queue.put(("progress", f"Encoding: {progress*100:.1f}%", progress, 0.0))
|
# FFmpeg process died
|
||||||
except:
|
break
|
||||||
pass
|
|
||||||
|
|
||||||
stdout, stderr = process.communicate()
|
# Update progress with FPS calculation
|
||||||
result = type('Result', (), {'returncode': process.returncode, 'stderr': stderr})()
|
current_time = time.time()
|
||||||
|
progress = 0.1 + (0.8 * (i + 1) / total_frames)
|
||||||
|
|
||||||
# Cleanup
|
# Calculate FPS and update progress (throttled)
|
||||||
import shutil
|
if current_time - last_progress_update > 0.5:
|
||||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
elapsed = current_time - start_time
|
||||||
|
fps_rate = frames_written / elapsed if elapsed > 0 else 0
|
||||||
|
self.render_progress_queue.put(("progress", f"Processed {i+1}/{total_frames} frames", progress, fps_rate))
|
||||||
|
last_progress_update = current_time
|
||||||
|
|
||||||
if result.returncode == 0:
|
# Close FFmpeg input and wait for completion
|
||||||
self.render_progress_queue.put(("complete", f"Rendered {len(processed_frames)} frames with FFmpeg", 1.0, 0.0))
|
self.ffmpeg_process.stdin.close()
|
||||||
print(f"Successfully rendered {len(processed_frames)} frames using FFmpeg")
|
stderr = self.ffmpeg_process.communicate()[1]
|
||||||
|
return_code = self.ffmpeg_process.returncode
|
||||||
|
self.ffmpeg_process = None
|
||||||
|
|
||||||
|
render_cap.release()
|
||||||
|
|
||||||
|
if return_code == 0:
|
||||||
|
total_time = time.time() - start_time
|
||||||
|
avg_fps = frames_written / total_time if total_time > 0 else 0
|
||||||
|
self.render_progress_queue.put(("complete", f"Rendered {frames_written} frames with FFmpeg", 1.0, avg_fps))
|
||||||
|
print(f"Successfully rendered {frames_written} frames using FFmpeg pipe (avg {avg_fps:.1f} FPS)")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.render_progress_queue.put(("error", f"FFmpeg encoding failed: {result.stderr}", 1.0, 0.0))
|
self.render_progress_queue.put(("error", f"FFmpeg encoding failed: {stderr.decode()}", 1.0, 0.0))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.render_progress_queue.put(("error", f"FFmpeg rendering failed: {e}", 1.0, 0.0))
|
error_msg = str(e)
|
||||||
|
# Handle specific Windows pipe errors
|
||||||
|
if "Errno 22" in error_msg or "invalid argument" in error_msg.lower():
|
||||||
|
error_msg = "Windows pipe error - try using a different output path or restart the application"
|
||||||
|
elif "BrokenPipeError" in error_msg:
|
||||||
|
error_msg = "FFmpeg process terminated unexpectedly - check if FFmpeg is installed correctly"
|
||||||
|
|
||||||
|
self.render_progress_queue.put(("error", f"FFmpeg pipe rendering failed: {error_msg}", 1.0, 0.0))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _render_as_frames(self, output_path: str, start_frame: int, end_frame: int, output_width: int, output_height: int):
|
def _render_as_frames(self, output_path: str, start_frame: int, end_frame: int, output_width: int, output_height: int):
|
||||||
|
Reference in New Issue
Block a user