From c7c092d3f303838d62e24bf8b54c3c1f83529019 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Tue, 16 Sep 2025 13:29:09 +0200 Subject: [PATCH] Implement frame caching in VideoEditor: add methods for managing frame cache with LRU eviction, improving playback performance by reducing frame retrieval time. Clear cache when switching videos to optimize memory usage. --- croppa/main.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/croppa/main.py b/croppa/main.py index c83abb5..4fa3222 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -12,7 +12,6 @@ import threading import queue import subprocess import ctypes -from ctypes import wintypes def get_active_window_title(): """Get the title of the currently active window""" @@ -39,7 +38,6 @@ class ProjectView: THUMBNAIL_MARGIN = 20 PROGRESS_BAR_HEIGHT = 8 TEXT_HEIGHT = 30 - ITEM_HEIGHT = THUMBNAIL_SIZE[1] + PROGRESS_BAR_HEIGHT + TEXT_HEIGHT + THUMBNAIL_MARGIN # Colors BG_COLOR = (40, 40, 40) @@ -392,6 +390,9 @@ class VideoEditor: # Auto-repeat seeking configuration AUTO_REPEAT_DISPLAY_RATE = 1.0 + + # Frame cache configuration + MAX_CACHE_FRAMES = 3000 # Timeline configuration TIMELINE_HEIGHT = 60 @@ -519,6 +520,10 @@ class VideoEditor: self.cached_transformed_frame = None self.cached_frame_number = None self.cached_transform_hash = None + + # Frame cache for video playback + self.frame_cache = {} # frame_number -> frame_data + self.cache_access_order = [] # Track access order for LRU eviction # Project view mode self.project_view_mode = False @@ -772,6 +777,10 @@ class VideoEditor: if hasattr(self, "cap") and self.cap: self.cap.release() + # Clear frame cache when switching videos + self.frame_cache.clear() + self.cache_access_order.clear() + self.video_path = media_path self.is_image_mode = self._is_image_file(media_path) @@ -879,11 +888,19 @@ class VideoEditor: self.current_display_frame = self.static_image.copy() return True else: - # For videos, use OpenCV for reliable seeking + # Check cache first + cached_frame = self._get_frame_from_cache(self.current_frame) + if cached_frame is not None: + self.current_display_frame = cached_frame + return True + + # Not in cache, read from capture self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame) ret, frame = self.cap.read() if ret: self.current_display_frame = frame + # Add to cache + self._add_frame_to_cache(self.current_frame, frame) return True return False @@ -1102,6 +1119,36 @@ class VideoEditor: self.cached_frame_number = None self.cached_transform_hash = None + def _manage_frame_cache(self): + """Manage frame cache size using LRU eviction""" + while len(self.frame_cache) > self.MAX_CACHE_FRAMES: + # Remove least recently used frame + oldest_frame = self.cache_access_order.pop(0) + if oldest_frame in self.frame_cache: + del self.frame_cache[oldest_frame] + + def _add_frame_to_cache(self, frame_number: int, frame_data): + """Add frame to cache with LRU management""" + self.frame_cache[frame_number] = frame_data.copy() + + # Update access order + if frame_number in self.cache_access_order: + self.cache_access_order.remove(frame_number) + self.cache_access_order.append(frame_number) + + # Manage cache size + self._manage_frame_cache() + + def _get_frame_from_cache(self, frame_number: int): + """Get frame from cache and update access order""" + if frame_number in self.frame_cache: + # Update access order for LRU + if frame_number in self.cache_access_order: + self.cache_access_order.remove(frame_number) + self.cache_access_order.append(frame_number) + return self.frame_cache[frame_number].copy() + return None + def apply_rotation(self, frame): """Apply rotation to frame""" if self.rotation_angle == 0: @@ -2537,8 +2584,16 @@ class VideoEditor: project_canvas = self.project_view.draw() cv2.imshow("Project View", project_canvas) - # Key capture with NO DELAY - keys should be instant - key = cv2.waitKey(1) & 0xFF + # Calculate appropriate delay based on playback state + if self.is_playing and not self.is_image_mode: + # Use calculated frame delay for proper playback speed + delay_ms = self.calculate_frame_delay() + else: + # Use minimal delay when not playing for responsive UI + delay_ms = 1 + + # Key capture with appropriate delay + key = cv2.waitKey(delay_ms) & 0xFF # Route keys based on window focus if key != 255: # Key was pressed