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:
@@ -12,7 +12,6 @@ import threading
|
|||||||
import queue
|
import queue
|
||||||
import subprocess
|
import subprocess
|
||||||
import ctypes
|
import ctypes
|
||||||
from ctypes import wintypes
|
|
||||||
|
|
||||||
def get_active_window_title():
|
def get_active_window_title():
|
||||||
"""Get the title of the currently active window"""
|
"""Get the title of the currently active window"""
|
||||||
@@ -39,7 +38,6 @@ class ProjectView:
|
|||||||
THUMBNAIL_MARGIN = 20
|
THUMBNAIL_MARGIN = 20
|
||||||
PROGRESS_BAR_HEIGHT = 8
|
PROGRESS_BAR_HEIGHT = 8
|
||||||
TEXT_HEIGHT = 30
|
TEXT_HEIGHT = 30
|
||||||
ITEM_HEIGHT = THUMBNAIL_SIZE[1] + PROGRESS_BAR_HEIGHT + TEXT_HEIGHT + THUMBNAIL_MARGIN
|
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
BG_COLOR = (40, 40, 40)
|
BG_COLOR = (40, 40, 40)
|
||||||
@@ -392,6 +390,9 @@ class VideoEditor:
|
|||||||
|
|
||||||
# Auto-repeat seeking configuration
|
# Auto-repeat seeking configuration
|
||||||
AUTO_REPEAT_DISPLAY_RATE = 1.0
|
AUTO_REPEAT_DISPLAY_RATE = 1.0
|
||||||
|
|
||||||
|
# Frame cache configuration
|
||||||
|
MAX_CACHE_FRAMES = 3000
|
||||||
|
|
||||||
# Timeline configuration
|
# Timeline configuration
|
||||||
TIMELINE_HEIGHT = 60
|
TIMELINE_HEIGHT = 60
|
||||||
@@ -519,6 +520,10 @@ class VideoEditor:
|
|||||||
self.cached_transformed_frame = None
|
self.cached_transformed_frame = None
|
||||||
self.cached_frame_number = None
|
self.cached_frame_number = None
|
||||||
self.cached_transform_hash = 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
|
# Project view mode
|
||||||
self.project_view_mode = False
|
self.project_view_mode = False
|
||||||
@@ -772,6 +777,10 @@ class VideoEditor:
|
|||||||
if hasattr(self, "cap") and self.cap:
|
if hasattr(self, "cap") and self.cap:
|
||||||
self.cap.release()
|
self.cap.release()
|
||||||
|
|
||||||
|
# Clear frame cache when switching videos
|
||||||
|
self.frame_cache.clear()
|
||||||
|
self.cache_access_order.clear()
|
||||||
|
|
||||||
self.video_path = media_path
|
self.video_path = media_path
|
||||||
self.is_image_mode = self._is_image_file(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()
|
self.current_display_frame = self.static_image.copy()
|
||||||
return True
|
return True
|
||||||
else:
|
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)
|
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame)
|
||||||
ret, frame = self.cap.read()
|
ret, frame = self.cap.read()
|
||||||
if ret:
|
if ret:
|
||||||
self.current_display_frame = frame
|
self.current_display_frame = frame
|
||||||
|
# Add to cache
|
||||||
|
self._add_frame_to_cache(self.current_frame, frame)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1102,6 +1119,36 @@ class VideoEditor:
|
|||||||
self.cached_frame_number = None
|
self.cached_frame_number = None
|
||||||
self.cached_transform_hash = 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):
|
def apply_rotation(self, frame):
|
||||||
"""Apply rotation to frame"""
|
"""Apply rotation to frame"""
|
||||||
if self.rotation_angle == 0:
|
if self.rotation_angle == 0:
|
||||||
@@ -2537,8 +2584,16 @@ class VideoEditor:
|
|||||||
project_canvas = self.project_view.draw()
|
project_canvas = self.project_view.draw()
|
||||||
cv2.imshow("Project View", project_canvas)
|
cv2.imshow("Project View", project_canvas)
|
||||||
|
|
||||||
# Key capture with NO DELAY - keys should be instant
|
# Calculate appropriate delay based on playback state
|
||||||
key = cv2.waitKey(1) & 0xFF
|
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
|
# Route keys based on window focus
|
||||||
if key != 255: # Key was pressed
|
if key != 255: # Key was pressed
|
||||||
|
Reference in New Issue
Block a user