feat(main.py): track display updates and optimize video rendering process
This commit is contained in:
114
croppa/main.py
114
croppa/main.py
@@ -142,6 +142,10 @@ class VideoEditor:
|
||||
self.render_thread = None
|
||||
self.render_cancelled = False
|
||||
self.render_progress_queue = queue.Queue()
|
||||
|
||||
# Display optimization - track when redraw is needed
|
||||
self.display_needs_update = True
|
||||
self.last_display_state = None
|
||||
|
||||
def _get_state_file_path(self) -> Path:
|
||||
"""Get the state file path for the current media file"""
|
||||
@@ -507,6 +511,7 @@ class VideoEditor:
|
||||
)
|
||||
self.current_frame = target_frame
|
||||
self.load_current_frame()
|
||||
self.display_needs_update = True
|
||||
|
||||
|
||||
def seek_video_with_modifier(
|
||||
@@ -561,9 +566,6 @@ class VideoEditor:
|
||||
)
|
||||
self.last_display_update = current_time
|
||||
|
||||
def should_update_display(self) -> bool:
|
||||
"""Check if display should be updated"""
|
||||
return True
|
||||
|
||||
def seek_to_frame(self, frame_number: int):
|
||||
"""Seek to specific frame"""
|
||||
@@ -640,7 +642,8 @@ class VideoEditor:
|
||||
if frame is None:
|
||||
return None
|
||||
|
||||
processed_frame = frame.copy()
|
||||
# Work in-place when possible to avoid unnecessary copying
|
||||
processed_frame = frame
|
||||
|
||||
# Apply brightness/contrast first (to original frame for best quality)
|
||||
processed_frame = self.apply_brightness_contrast(processed_frame)
|
||||
@@ -715,10 +718,12 @@ class VideoEditor:
|
||||
def adjust_brightness(self, delta: int):
|
||||
"""Adjust brightness by delta (-100 to 100)"""
|
||||
self.brightness = max(-100, min(100, self.brightness + delta))
|
||||
self.display_needs_update = True
|
||||
|
||||
def adjust_contrast(self, delta: float):
|
||||
"""Adjust contrast by delta (0.1 to 3.0)"""
|
||||
self.contrast = max(0.1, min(3.0, self.contrast + delta))
|
||||
self.display_needs_update = True
|
||||
|
||||
def show_progress_bar(self, text: str = "Processing..."):
|
||||
"""Show progress bar with given text"""
|
||||
@@ -727,6 +732,7 @@ class VideoEditor:
|
||||
self.progress_bar_complete = False
|
||||
self.progress_bar_complete_time = None
|
||||
self.progress_bar_text = text
|
||||
self.display_needs_update = True
|
||||
|
||||
def update_progress_bar(self, progress: float, text: str = None, fps: float = None):
|
||||
"""Update progress bar progress (0.0 to 1.0) and optionally text and FPS"""
|
||||
@@ -753,6 +759,7 @@ class VideoEditor:
|
||||
"""Show a feedback message on screen for a few seconds"""
|
||||
self.feedback_message = message
|
||||
self.feedback_message_time = time.time()
|
||||
self.display_needs_update = True
|
||||
|
||||
def draw_feedback_message(self, frame):
|
||||
"""Draw feedback message on frame if visible"""
|
||||
@@ -1055,9 +1062,28 @@ class VideoEditor:
|
||||
if self.current_display_frame is None:
|
||||
return
|
||||
|
||||
# Check if display needs update (optimization)
|
||||
current_state = (
|
||||
self.current_frame,
|
||||
self.crop_rect,
|
||||
self.zoom_factor,
|
||||
self.rotation_angle,
|
||||
self.brightness,
|
||||
self.contrast,
|
||||
self.display_offset,
|
||||
self.progress_bar_visible,
|
||||
self.feedback_message
|
||||
)
|
||||
|
||||
if not self.display_needs_update and current_state == self.last_display_state:
|
||||
return # Skip redraw if nothing changed
|
||||
|
||||
self.last_display_state = current_state
|
||||
self.display_needs_update = False
|
||||
|
||||
# Apply crop, zoom, and rotation transformations for preview
|
||||
display_frame = self.apply_crop_zoom_and_rotation(
|
||||
self.current_display_frame.copy()
|
||||
self.current_display_frame
|
||||
)
|
||||
|
||||
if display_frame is None:
|
||||
@@ -1577,11 +1603,37 @@ class VideoEditor:
|
||||
# Send progress update
|
||||
self.render_progress_queue.put(("progress", "Setting up video writer...", 0.1, 0.0))
|
||||
|
||||
# Use mp4v codec (most compatible with MP4)
|
||||
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
||||
out = cv2.VideoWriter(
|
||||
output_path, fourcc, self.fps, (output_width, output_height)
|
||||
)
|
||||
# Try hardware-accelerated codecs first, fallback to mp4v
|
||||
codecs_to_try = [
|
||||
("h264_nvenc", "H264"), # NVIDIA hardware acceleration
|
||||
("h264_qsv", "H264"), # Intel Quick Sync
|
||||
("h264", "H264"), # Software H.264
|
||||
("mp4v", "MP4V") # Fallback
|
||||
]
|
||||
|
||||
out = None
|
||||
for codec_name, fourcc_code in codecs_to_try:
|
||||
try:
|
||||
fourcc = cv2.VideoWriter_fourcc(*fourcc_code)
|
||||
out = cv2.VideoWriter(
|
||||
output_path, fourcc, self.fps, (output_width, output_height)
|
||||
)
|
||||
if out.isOpened():
|
||||
print(f"Using {codec_name} codec for rendering")
|
||||
break
|
||||
else:
|
||||
out.release()
|
||||
out = None
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if out is None:
|
||||
# Final fallback to mp4v
|
||||
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
||||
out = cv2.VideoWriter(
|
||||
output_path, fourcc, self.fps, (output_width, output_height)
|
||||
)
|
||||
print("Using mp4v codec (fallback)")
|
||||
|
||||
if not out.isOpened():
|
||||
self.render_progress_queue.put(("error", "Could not open video writer!", 1.0, 0.0))
|
||||
@@ -1805,34 +1857,29 @@ class VideoEditor:
|
||||
if self.rotation_angle != 0:
|
||||
frame = self.apply_rotation(frame)
|
||||
|
||||
# Apply zoom and resize in one step for efficiency
|
||||
# Apply zoom and resize directly to final output dimensions
|
||||
if self.zoom_factor != 1.0:
|
||||
height, width = frame.shape[:2]
|
||||
intermediate_width = int(width * self.zoom_factor)
|
||||
intermediate_height = int(height * self.zoom_factor)
|
||||
|
||||
# If zoom results in different dimensions than output, resize directly to output
|
||||
if (
|
||||
intermediate_width != output_width
|
||||
or intermediate_height != output_height
|
||||
):
|
||||
# Calculate what the zoomed dimensions would be
|
||||
zoomed_width = int(width * self.zoom_factor)
|
||||
zoomed_height = int(height * self.zoom_factor)
|
||||
|
||||
# If zoomed dimensions match output, use them; otherwise resize directly to output
|
||||
if zoomed_width == output_width and zoomed_height == output_height:
|
||||
frame = cv2.resize(
|
||||
frame,
|
||||
(output_width, output_height),
|
||||
interpolation=cv2.INTER_LINEAR,
|
||||
frame, (zoomed_width, zoomed_height), interpolation=cv2.INTER_LINEAR
|
||||
)
|
||||
else:
|
||||
# Resize directly to final output dimensions
|
||||
frame = cv2.resize(
|
||||
frame,
|
||||
(intermediate_width, intermediate_height),
|
||||
interpolation=cv2.INTER_LINEAR,
|
||||
frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR
|
||||
)
|
||||
else:
|
||||
# No zoom, just resize to output dimensions if needed
|
||||
if frame.shape[1] != output_width or frame.shape[0] != output_height:
|
||||
frame = cv2.resize(
|
||||
frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR
|
||||
)
|
||||
|
||||
# Final size check and resize if needed
|
||||
if frame.shape[1] != output_width or frame.shape[0] != output_height:
|
||||
frame = cv2.resize(
|
||||
frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR
|
||||
)
|
||||
|
||||
return frame
|
||||
|
||||
@@ -1913,9 +1960,8 @@ class VideoEditor:
|
||||
# Update render progress from background thread
|
||||
self.update_render_progress()
|
||||
|
||||
# Only update display if needed and throttled
|
||||
if self.should_update_display():
|
||||
self.display_current_frame()
|
||||
# Update display
|
||||
self.display_current_frame()
|
||||
|
||||
delay = self.calculate_frame_delay() if self.is_playing else 1 # Very short delay for responsive key detection
|
||||
key = cv2.waitKey(delay) & 0xFF
|
||||
|
Reference in New Issue
Block a user