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_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
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
|
# Current position tracking
|
||||||
self.current_frame = 0
|
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):
|
def get_frame(self, frame_number):
|
||||||
"""Get frame at specific index - always accurate"""
|
"""Get frame at specific index - always accurate"""
|
||||||
# Clamp frame number to valid range
|
# Clamp frame number to valid range
|
||||||
frame_number = max(0, min(frame_number, self.total_frames - 1))
|
frame_number = max(0, min(frame_number, self.total_frames - 1))
|
||||||
|
|
||||||
# Check cache first
|
# Optimize for sequential reading (next frame)
|
||||||
cached_frame = self._get_from_cache(frame_number)
|
if frame_number == self.current_frame + 1:
|
||||||
if cached_frame is not None:
|
ret, frame = self.cap.read()
|
||||||
self.current_frame = frame_number
|
else:
|
||||||
return cached_frame
|
# Seek for non-sequential access
|
||||||
|
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
|
||||||
# Not in cache, seek to frame and read
|
ret, frame = self.cap.read()
|
||||||
self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
|
|
||||||
ret, frame = self.cap.read()
|
|
||||||
|
|
||||||
if ret:
|
if ret:
|
||||||
self._add_to_cache(frame_number, frame)
|
|
||||||
self.current_frame = frame_number
|
self.current_frame = frame_number
|
||||||
return frame
|
return frame
|
||||||
else:
|
else:
|
||||||
@@ -461,9 +431,9 @@ class ProjectView:
|
|||||||
|
|
||||||
class VideoEditor:
|
class VideoEditor:
|
||||||
# Configuration constants
|
# Configuration constants
|
||||||
BASE_FRAME_DELAY_MS = 16 # ~60 FPS
|
TARGET_FPS = 80 # Target FPS for speed calculations
|
||||||
SPEED_INCREMENT = 0.2
|
SPEED_INCREMENT = 0.1
|
||||||
MIN_PLAYBACK_SPEED = 0.1
|
MIN_PLAYBACK_SPEED = 0.05
|
||||||
MAX_PLAYBACK_SPEED = 10.0
|
MAX_PLAYBACK_SPEED = 10.0
|
||||||
|
|
||||||
# Seek multiplier configuration
|
# Seek multiplier configuration
|
||||||
@@ -986,8 +956,18 @@ class VideoEditor:
|
|||||||
|
|
||||||
def calculate_frame_delay(self) -> int:
|
def calculate_frame_delay(self) -> int:
|
||||||
"""Calculate frame delay in milliseconds based on playback speed"""
|
"""Calculate frame delay in milliseconds based on playback speed"""
|
||||||
delay_ms = int(self.BASE_FRAME_DELAY_MS / self.playback_speed)
|
# Round to 2 decimals to handle floating point precision issues
|
||||||
return max(1, delay_ms)
|
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):
|
def seek_video(self, frames_delta: int):
|
||||||
"""Seek video by specified number of frames"""
|
"""Seek video by specified number of frames"""
|
||||||
@@ -1169,9 +1149,8 @@ class VideoEditor:
|
|||||||
if not self.is_playing:
|
if not self.is_playing:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Calculate how many frames to advance based on speed
|
# Always advance by 1 frame - speed is controlled by delay timing
|
||||||
frames_to_advance = max(1, int(self.playback_speed))
|
new_frame = self.current_frame + 1
|
||||||
new_frame = self.current_frame + frames_to_advance
|
|
||||||
|
|
||||||
# Handle marker looping bounds
|
# 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:
|
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)
|
line_to_draw = ("prev_next", prev_result, next_result)
|
||||||
|
|
||||||
if line_to_draw:
|
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
|
# Draw lines between corresponding tracking points
|
||||||
for i, (px1, py1) in enumerate(pts1):
|
for i, (px1, py1) in enumerate(pts1):
|
||||||
@@ -2201,7 +2180,7 @@ class VideoEditor:
|
|||||||
best_snap_point = None
|
best_snap_point = None
|
||||||
|
|
||||||
# Check all tracking points from all frames for point snapping
|
# 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:
|
for (px, py) in points:
|
||||||
sxp, syp = self._map_rotated_to_screen(px, py)
|
sxp, syp = self._map_rotated_to_screen(px, py)
|
||||||
distance = ((sxp - x) ** 2 + (syp - y) ** 2) ** 0.5
|
distance = ((sxp - x) ** 2 + (syp - y) ** 2) ** 0.5
|
||||||
@@ -2227,7 +2206,7 @@ class VideoEditor:
|
|||||||
print(f"DEBUG: Checking prev->next line")
|
print(f"DEBUG: Checking prev->next line")
|
||||||
|
|
||||||
if line_to_check:
|
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
|
# Check each corresponding pair of points
|
||||||
for j in range(min(len(pts1), len(pts2))):
|
for j in range(min(len(pts1), len(pts2))):
|
||||||
@@ -3048,10 +3027,15 @@ class VideoEditor:
|
|||||||
# Use calculated frame delay for proper playback speed
|
# Use calculated frame delay for proper playback speed
|
||||||
delay_ms = self.calculate_frame_delay()
|
delay_ms = self.calculate_frame_delay()
|
||||||
else:
|
else:
|
||||||
# Use minimal delay when not playing for responsive UI
|
# Use non-blocking wait for immediate responsiveness when not playing
|
||||||
delay_ms = 1
|
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
|
# Key capture with appropriate delay
|
||||||
|
print(f"Delay ms: {delay_ms}")
|
||||||
key = cv2.waitKey(delay_ms) & 0xFF
|
key = cv2.waitKey(delay_ms) & 0xFF
|
||||||
|
|
||||||
# Route keys based on window focus
|
# Route keys based on window focus
|
||||||
@@ -3305,10 +3289,6 @@ class VideoEditor:
|
|||||||
print(f"Contracted crop from left by {self.crop_size_step}px")
|
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.save_state()
|
||||||
self.cleanup_render_thread()
|
self.cleanup_render_thread()
|
||||||
if hasattr(self, 'cap') and self.cap:
|
if hasattr(self, 'cap') and self.cap:
|
||||||
|
Reference in New Issue
Block a user