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_thread = None
|
||||||
self.render_cancelled = False
|
self.render_cancelled = False
|
||||||
self.render_progress_queue = queue.Queue()
|
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:
|
def _get_state_file_path(self) -> Path:
|
||||||
"""Get the state file path for the current media file"""
|
"""Get the state file path for the current media file"""
|
||||||
@@ -507,6 +511,7 @@ class VideoEditor:
|
|||||||
)
|
)
|
||||||
self.current_frame = target_frame
|
self.current_frame = target_frame
|
||||||
self.load_current_frame()
|
self.load_current_frame()
|
||||||
|
self.display_needs_update = True
|
||||||
|
|
||||||
|
|
||||||
def seek_video_with_modifier(
|
def seek_video_with_modifier(
|
||||||
@@ -561,9 +566,6 @@ class VideoEditor:
|
|||||||
)
|
)
|
||||||
self.last_display_update = current_time
|
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):
|
def seek_to_frame(self, frame_number: int):
|
||||||
"""Seek to specific frame"""
|
"""Seek to specific frame"""
|
||||||
@@ -640,7 +642,8 @@ class VideoEditor:
|
|||||||
if frame is None:
|
if frame is None:
|
||||||
return 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)
|
# Apply brightness/contrast first (to original frame for best quality)
|
||||||
processed_frame = self.apply_brightness_contrast(processed_frame)
|
processed_frame = self.apply_brightness_contrast(processed_frame)
|
||||||
@@ -715,10 +718,12 @@ class VideoEditor:
|
|||||||
def adjust_brightness(self, delta: int):
|
def adjust_brightness(self, delta: int):
|
||||||
"""Adjust brightness by delta (-100 to 100)"""
|
"""Adjust brightness by delta (-100 to 100)"""
|
||||||
self.brightness = max(-100, min(100, self.brightness + delta))
|
self.brightness = max(-100, min(100, self.brightness + delta))
|
||||||
|
self.display_needs_update = True
|
||||||
|
|
||||||
def adjust_contrast(self, delta: float):
|
def adjust_contrast(self, delta: float):
|
||||||
"""Adjust contrast by delta (0.1 to 3.0)"""
|
"""Adjust contrast by delta (0.1 to 3.0)"""
|
||||||
self.contrast = max(0.1, min(3.0, self.contrast + delta))
|
self.contrast = max(0.1, min(3.0, self.contrast + delta))
|
||||||
|
self.display_needs_update = True
|
||||||
|
|
||||||
def show_progress_bar(self, text: str = "Processing..."):
|
def show_progress_bar(self, text: str = "Processing..."):
|
||||||
"""Show progress bar with given text"""
|
"""Show progress bar with given text"""
|
||||||
@@ -727,6 +732,7 @@ class VideoEditor:
|
|||||||
self.progress_bar_complete = False
|
self.progress_bar_complete = False
|
||||||
self.progress_bar_complete_time = None
|
self.progress_bar_complete_time = None
|
||||||
self.progress_bar_text = text
|
self.progress_bar_text = text
|
||||||
|
self.display_needs_update = True
|
||||||
|
|
||||||
def update_progress_bar(self, progress: float, text: str = None, fps: float = None):
|
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"""
|
"""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"""
|
"""Show a feedback message on screen for a few seconds"""
|
||||||
self.feedback_message = message
|
self.feedback_message = message
|
||||||
self.feedback_message_time = time.time()
|
self.feedback_message_time = time.time()
|
||||||
|
self.display_needs_update = True
|
||||||
|
|
||||||
def draw_feedback_message(self, frame):
|
def draw_feedback_message(self, frame):
|
||||||
"""Draw feedback message on frame if visible"""
|
"""Draw feedback message on frame if visible"""
|
||||||
@@ -1055,9 +1062,28 @@ class VideoEditor:
|
|||||||
if self.current_display_frame is None:
|
if self.current_display_frame is None:
|
||||||
return
|
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
|
# Apply crop, zoom, and rotation transformations for preview
|
||||||
display_frame = self.apply_crop_zoom_and_rotation(
|
display_frame = self.apply_crop_zoom_and_rotation(
|
||||||
self.current_display_frame.copy()
|
self.current_display_frame
|
||||||
)
|
)
|
||||||
|
|
||||||
if display_frame is None:
|
if display_frame is None:
|
||||||
@@ -1577,11 +1603,37 @@ class VideoEditor:
|
|||||||
# 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 video writer...", 0.1, 0.0))
|
||||||
|
|
||||||
# Use mp4v codec (most compatible with MP4)
|
# Try hardware-accelerated codecs first, fallback to mp4v
|
||||||
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
codecs_to_try = [
|
||||||
out = cv2.VideoWriter(
|
("h264_nvenc", "H264"), # NVIDIA hardware acceleration
|
||||||
output_path, fourcc, self.fps, (output_width, output_height)
|
("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():
|
if not out.isOpened():
|
||||||
self.render_progress_queue.put(("error", "Could not open video writer!", 1.0, 0.0))
|
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:
|
if self.rotation_angle != 0:
|
||||||
frame = self.apply_rotation(frame)
|
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:
|
if self.zoom_factor != 1.0:
|
||||||
height, width = frame.shape[:2]
|
height, width = frame.shape[:2]
|
||||||
intermediate_width = int(width * self.zoom_factor)
|
# Calculate what the zoomed dimensions would be
|
||||||
intermediate_height = int(height * self.zoom_factor)
|
zoomed_width = int(width * self.zoom_factor)
|
||||||
|
zoomed_height = int(height * self.zoom_factor)
|
||||||
# If zoom results in different dimensions than output, resize directly to output
|
|
||||||
if (
|
# If zoomed dimensions match output, use them; otherwise resize directly to output
|
||||||
intermediate_width != output_width
|
if zoomed_width == output_width and zoomed_height == output_height:
|
||||||
or intermediate_height != output_height
|
|
||||||
):
|
|
||||||
frame = cv2.resize(
|
frame = cv2.resize(
|
||||||
frame,
|
frame, (zoomed_width, zoomed_height), interpolation=cv2.INTER_LINEAR
|
||||||
(output_width, output_height),
|
|
||||||
interpolation=cv2.INTER_LINEAR,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# Resize directly to final output dimensions
|
||||||
frame = cv2.resize(
|
frame = cv2.resize(
|
||||||
frame,
|
frame, (output_width, output_height), interpolation=cv2.INTER_LINEAR
|
||||||
(intermediate_width, intermediate_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
|
return frame
|
||||||
|
|
||||||
@@ -1913,9 +1960,8 @@ class VideoEditor:
|
|||||||
# Update render progress from background thread
|
# Update render progress from background thread
|
||||||
self.update_render_progress()
|
self.update_render_progress()
|
||||||
|
|
||||||
# Only update display if needed and throttled
|
# Update display
|
||||||
if self.should_update_display():
|
self.display_current_frame()
|
||||||
self.display_current_frame()
|
|
||||||
|
|
||||||
delay = self.calculate_frame_delay() if self.is_playing else 1 # Very short delay for responsive key detection
|
delay = self.calculate_frame_delay() if self.is_playing else 1 # Very short delay for responsive key detection
|
||||||
key = cv2.waitKey(delay) & 0xFF
|
key = cv2.waitKey(delay) & 0xFF
|
||||||
|
|||||||
Reference in New Issue
Block a user