Compare commits
3 Commits
83ef71934b
...
04e391551e
Author | SHA1 | Date | |
---|---|---|---|
04e391551e | |||
f9f442a2d0 | |||
0fd108bc9a |
@@ -28,55 +28,25 @@ class Cv2BufferedCap:
|
||||
self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
|
||||
# Frame cache
|
||||
self.frame_cache = {}
|
||||
self.cache_access_order = []
|
||||
self.MAX_CACHE_FRAMES = 3000
|
||||
|
||||
# Current position tracking
|
||||
self.current_frame = 0
|
||||
|
||||
def _manage_cache(self):
|
||||
"""Manage cache size using LRU eviction"""
|
||||
while len(self.frame_cache) > self.MAX_CACHE_FRAMES:
|
||||
oldest_frame = self.cache_access_order.pop(0)
|
||||
if oldest_frame in self.frame_cache:
|
||||
del self.frame_cache[oldest_frame]
|
||||
|
||||
def _add_to_cache(self, frame_number, frame):
|
||||
"""Add frame to cache"""
|
||||
self.frame_cache[frame_number] = frame.copy()
|
||||
if frame_number in self.cache_access_order:
|
||||
self.cache_access_order.remove(frame_number)
|
||||
self.cache_access_order.append(frame_number)
|
||||
self._manage_cache()
|
||||
|
||||
def _get_from_cache(self, frame_number):
|
||||
"""Get frame from cache and update LRU"""
|
||||
if frame_number in self.frame_cache:
|
||||
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 get_frame(self, frame_number):
|
||||
"""Get frame at specific index - always accurate"""
|
||||
# Clamp frame number to valid range
|
||||
frame_number = max(0, min(frame_number, self.total_frames - 1))
|
||||
|
||||
# Check cache first
|
||||
cached_frame = self._get_from_cache(frame_number)
|
||||
if cached_frame is not None:
|
||||
self.current_frame = frame_number
|
||||
return cached_frame
|
||||
|
||||
# Not in cache, seek to frame and read
|
||||
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
|
||||
ret, frame = self.cap.read()
|
||||
# Optimize for sequential reading (next frame)
|
||||
if frame_number == self.current_frame + 1:
|
||||
ret, frame = self.cap.read()
|
||||
else:
|
||||
# Seek for non-sequential access
|
||||
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
|
||||
ret, frame = self.cap.read()
|
||||
|
||||
if ret:
|
||||
self._add_to_cache(frame_number, frame)
|
||||
self.current_frame = frame_number
|
||||
return frame
|
||||
else:
|
||||
@@ -461,9 +431,9 @@ class ProjectView:
|
||||
|
||||
class VideoEditor:
|
||||
# Configuration constants
|
||||
BASE_FRAME_DELAY_MS = 16 # ~60 FPS
|
||||
SPEED_INCREMENT = 0.2
|
||||
MIN_PLAYBACK_SPEED = 0.1
|
||||
TARGET_FPS = 80 # Target FPS for speed calculations
|
||||
SPEED_INCREMENT = 0.1
|
||||
MIN_PLAYBACK_SPEED = 0.05
|
||||
MAX_PLAYBACK_SPEED = 10.0
|
||||
|
||||
# Seek multiplier configuration
|
||||
@@ -986,8 +956,18 @@ class VideoEditor:
|
||||
|
||||
def calculate_frame_delay(self) -> int:
|
||||
"""Calculate frame delay in milliseconds based on playback speed"""
|
||||
delay_ms = int(self.BASE_FRAME_DELAY_MS / self.playback_speed)
|
||||
return max(1, delay_ms)
|
||||
# Round to 2 decimals to handle floating point precision issues
|
||||
speed = round(self.playback_speed, 2)
|
||||
print(f"Playback speed: {speed}")
|
||||
if speed >= 1.0:
|
||||
# Speed >= 1: maximum FPS (no delay)
|
||||
return 1
|
||||
else:
|
||||
# Speed < 1: scale FPS based on speed
|
||||
# Formula: fps = TARGET_FPS * speed, so delay = 1000 / fps
|
||||
target_fps = self.TARGET_FPS * speed
|
||||
delay_ms = int(1000 / target_fps)
|
||||
return max(1, delay_ms)
|
||||
|
||||
def seek_video(self, frames_delta: int):
|
||||
"""Seek video by specified number of frames"""
|
||||
@@ -1169,9 +1149,8 @@ class VideoEditor:
|
||||
if not self.is_playing:
|
||||
return True
|
||||
|
||||
# Calculate how many frames to advance based on speed
|
||||
frames_to_advance = max(1, int(self.playback_speed))
|
||||
new_frame = self.current_frame + frames_to_advance
|
||||
# Always advance by 1 frame - speed is controlled by delay timing
|
||||
new_frame = self.current_frame + 1
|
||||
|
||||
# Handle marker looping bounds
|
||||
if self.looping_between_markers and self.cut_start_frame is not None and self.cut_end_frame is not None:
|
||||
@@ -2049,7 +2028,7 @@ class VideoEditor:
|
||||
line_to_draw = ("prev_next", prev_result, next_result)
|
||||
|
||||
if line_to_draw:
|
||||
line_type, (frame1, pts1), (frame2, pts2) = line_to_draw
|
||||
line_type, (_, pts1), (_, pts2) = line_to_draw
|
||||
|
||||
# Draw lines between corresponding tracking points
|
||||
for i, (px1, py1) in enumerate(pts1):
|
||||
@@ -2201,7 +2180,7 @@ class VideoEditor:
|
||||
best_snap_point = None
|
||||
|
||||
# Check all tracking points from all frames for point snapping
|
||||
for frame_num, points in self.tracking_points.items():
|
||||
for _, points in self.tracking_points.items():
|
||||
for (px, py) in points:
|
||||
sxp, syp = self._map_rotated_to_screen(px, py)
|
||||
distance = ((sxp - x) ** 2 + (syp - y) ** 2) ** 0.5
|
||||
@@ -2227,7 +2206,7 @@ class VideoEditor:
|
||||
print(f"DEBUG: Checking prev->next line")
|
||||
|
||||
if line_to_check:
|
||||
line_type, (frame1, pts1), (frame2, pts2) = line_to_check
|
||||
line_type, (_, pts1), (_, pts2) = line_to_check
|
||||
|
||||
# Check each corresponding pair of points
|
||||
for j in range(min(len(pts1), len(pts2))):
|
||||
@@ -3048,10 +3027,15 @@ class VideoEditor:
|
||||
# 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
|
||||
# Use non-blocking wait for immediate responsiveness when not playing
|
||||
delay_ms = 0
|
||||
|
||||
# Auto advance frame when playing (videos only)
|
||||
if self.is_playing and not self.is_image_mode:
|
||||
self.advance_frame()
|
||||
|
||||
# Key capture with appropriate delay
|
||||
print(f"Delay ms: {delay_ms}")
|
||||
key = cv2.waitKey(delay_ms) & 0xFF
|
||||
|
||||
# Route keys based on window focus
|
||||
@@ -3305,10 +3289,6 @@ class VideoEditor:
|
||||
print(f"Contracted crop from left by {self.crop_size_step}px")
|
||||
|
||||
|
||||
# Auto advance frame when playing (videos only)
|
||||
if self.is_playing and not self.is_image_mode:
|
||||
self.advance_frame()
|
||||
|
||||
self.save_state()
|
||||
self.cleanup_render_thread()
|
||||
if hasattr(self, 'cap') and self.cap:
|
||||
|
Reference in New Issue
Block a user