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.

This commit is contained in:
2025-09-16 13:29:09 +02:00
parent f0d540be27
commit c7c092d3f3

View File

@@ -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)
@@ -393,6 +391,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
TIMELINE_MARGIN = 20
@@ -520,6 +521,10 @@ class VideoEditor:
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
self.project_view = None
@@ -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