Add project view functionality to VideoEditor: implement video browsing with thumbnails, progress tracking, and keyboard navigation. Toggle between project and editor modes, enhancing user experience.
This commit is contained in:
392
croppa/main.py
392
croppa/main.py
@@ -11,6 +11,289 @@ import json
|
|||||||
import threading
|
import threading
|
||||||
import queue
|
import queue
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
def get_active_window_title():
|
||||||
|
"""Get the title of the currently active window"""
|
||||||
|
try:
|
||||||
|
# Get handle to foreground window
|
||||||
|
hwnd = ctypes.windll.user32.GetForegroundWindow()
|
||||||
|
|
||||||
|
# Get window title length
|
||||||
|
length = ctypes.windll.user32.GetWindowTextLengthW(hwnd)
|
||||||
|
|
||||||
|
# Create buffer and get window title
|
||||||
|
buffer = ctypes.create_unicode_buffer(length + 1)
|
||||||
|
ctypes.windll.user32.GetWindowTextW(hwnd, buffer, length + 1)
|
||||||
|
|
||||||
|
return buffer.value
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
class ProjectView:
|
||||||
|
"""Project view that displays videos in current directory with progress bars"""
|
||||||
|
|
||||||
|
# Project view configuration
|
||||||
|
THUMBNAIL_SIZE = (200, 150) # Width, Height
|
||||||
|
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)
|
||||||
|
THUMBNAIL_BG_COLOR = (60, 60, 60)
|
||||||
|
PROGRESS_BG_COLOR = (80, 80, 80)
|
||||||
|
PROGRESS_FILL_COLOR = (0, 120, 255)
|
||||||
|
TEXT_COLOR = (255, 255, 255)
|
||||||
|
SELECTED_COLOR = (255, 165, 0)
|
||||||
|
|
||||||
|
def __init__(self, directory: Path, video_editor):
|
||||||
|
self.directory = directory
|
||||||
|
self.video_editor = video_editor
|
||||||
|
self.video_files = []
|
||||||
|
self.thumbnails = {}
|
||||||
|
self.progress_data = {}
|
||||||
|
self.selected_index = 0
|
||||||
|
self.scroll_offset = 0
|
||||||
|
self.items_per_row = 4
|
||||||
|
self.window_width = 1200
|
||||||
|
self.window_height = 800
|
||||||
|
|
||||||
|
self._load_video_files()
|
||||||
|
self._load_progress_data()
|
||||||
|
|
||||||
|
def _load_video_files(self):
|
||||||
|
"""Load all video files from directory"""
|
||||||
|
self.video_files = []
|
||||||
|
for file_path in self.directory.iterdir():
|
||||||
|
if (file_path.is_file() and
|
||||||
|
file_path.suffix.lower() in self.video_editor.VIDEO_EXTENSIONS):
|
||||||
|
self.video_files.append(file_path)
|
||||||
|
self.video_files.sort(key=lambda x: x.name)
|
||||||
|
|
||||||
|
def _load_progress_data(self):
|
||||||
|
"""Load progress data from JSON state files"""
|
||||||
|
self.progress_data = {}
|
||||||
|
for video_path in self.video_files:
|
||||||
|
state_file = video_path.with_suffix('.json')
|
||||||
|
if state_file.exists():
|
||||||
|
try:
|
||||||
|
with open(state_file, 'r') as f:
|
||||||
|
state = json.load(f)
|
||||||
|
current_frame = state.get('current_frame', 0)
|
||||||
|
|
||||||
|
# Get total frames from video
|
||||||
|
cap = cv2.VideoCapture(str(video_path))
|
||||||
|
if cap.isOpened():
|
||||||
|
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||||
|
cap.release()
|
||||||
|
|
||||||
|
if total_frames > 0:
|
||||||
|
progress = current_frame / (total_frames - 1)
|
||||||
|
self.progress_data[video_path] = {
|
||||||
|
'current_frame': current_frame,
|
||||||
|
'total_frames': total_frames,
|
||||||
|
'progress': progress
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading progress for {video_path.name}: {e}")
|
||||||
|
|
||||||
|
def get_progress_for_video(self, video_path: Path) -> float:
|
||||||
|
"""Get progress (0.0 to 1.0) for a video"""
|
||||||
|
if video_path in self.progress_data:
|
||||||
|
return self.progress_data[video_path]['progress']
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def get_thumbnail_for_video(self, video_path: Path) -> np.ndarray:
|
||||||
|
"""Get thumbnail for a video, generating it if needed"""
|
||||||
|
if video_path in self.thumbnails:
|
||||||
|
return self.thumbnails[video_path]
|
||||||
|
|
||||||
|
# Generate thumbnail on demand
|
||||||
|
try:
|
||||||
|
cap = cv2.VideoCapture(str(video_path))
|
||||||
|
if cap.isOpened():
|
||||||
|
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||||
|
if total_frames > 0:
|
||||||
|
middle_frame = total_frames // 2
|
||||||
|
cap.set(cv2.CAP_PROP_POS_FRAMES, middle_frame)
|
||||||
|
ret, frame = cap.read()
|
||||||
|
if ret:
|
||||||
|
thumbnail = cv2.resize(frame, self.THUMBNAIL_SIZE)
|
||||||
|
self.thumbnails[video_path] = thumbnail
|
||||||
|
cap.release()
|
||||||
|
return thumbnail
|
||||||
|
cap.release()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating thumbnail for {video_path.name}: {e}")
|
||||||
|
|
||||||
|
# Return a placeholder if thumbnail generation failed
|
||||||
|
placeholder = np.full((self.THUMBNAIL_SIZE[1], self.THUMBNAIL_SIZE[0], 3),
|
||||||
|
self.THUMBNAIL_BG_COLOR, dtype=np.uint8)
|
||||||
|
return placeholder
|
||||||
|
|
||||||
|
def draw(self) -> np.ndarray:
|
||||||
|
"""Draw the project view"""
|
||||||
|
canvas = np.full((self.window_height, self.window_width, 3), self.BG_COLOR, dtype=np.uint8)
|
||||||
|
|
||||||
|
if not self.video_files:
|
||||||
|
# No videos message
|
||||||
|
text = "No videos found in directory"
|
||||||
|
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||||
|
text_size = cv2.getTextSize(text, font, 1.0, 2)[0]
|
||||||
|
text_x = (self.window_width - text_size[0]) // 2
|
||||||
|
text_y = (self.window_height - text_size[1]) // 2
|
||||||
|
cv2.putText(canvas, text, (text_x, text_y), font, 1.0, self.TEXT_COLOR, 2)
|
||||||
|
return canvas
|
||||||
|
|
||||||
|
# Calculate layout
|
||||||
|
items_per_row = min(self.items_per_row, len(self.video_files))
|
||||||
|
item_width = (self.window_width - (items_per_row + 1) * self.THUMBNAIL_MARGIN) // items_per_row
|
||||||
|
thumbnail_width = min(item_width, self.THUMBNAIL_SIZE[0])
|
||||||
|
thumbnail_height = int(thumbnail_width * self.THUMBNAIL_SIZE[1] / self.THUMBNAIL_SIZE[0])
|
||||||
|
|
||||||
|
# Draw videos in grid
|
||||||
|
for i, video_path in enumerate(self.video_files):
|
||||||
|
row = i // items_per_row
|
||||||
|
col = i % items_per_row
|
||||||
|
|
||||||
|
# Skip if scrolled out of view
|
||||||
|
if row < self.scroll_offset:
|
||||||
|
continue
|
||||||
|
if row > self.scroll_offset + (self.window_height // self.ITEM_HEIGHT):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Calculate position
|
||||||
|
x = self.THUMBNAIL_MARGIN + col * (item_width + self.THUMBNAIL_MARGIN)
|
||||||
|
y = self.THUMBNAIL_MARGIN + (row - self.scroll_offset) * self.ITEM_HEIGHT
|
||||||
|
|
||||||
|
# Draw thumbnail background
|
||||||
|
cv2.rectangle(canvas,
|
||||||
|
(x, y),
|
||||||
|
(x + thumbnail_width, y + thumbnail_height),
|
||||||
|
self.THUMBNAIL_BG_COLOR, -1)
|
||||||
|
|
||||||
|
# Draw selection highlight
|
||||||
|
if i == self.selected_index:
|
||||||
|
cv2.rectangle(canvas,
|
||||||
|
(x - 2, y - 2),
|
||||||
|
(x + thumbnail_width + 2, y + thumbnail_height + 2),
|
||||||
|
self.SELECTED_COLOR, 3)
|
||||||
|
|
||||||
|
# Draw thumbnail
|
||||||
|
thumbnail = self.get_thumbnail_for_video(video_path)
|
||||||
|
# Resize thumbnail to fit
|
||||||
|
resized_thumbnail = cv2.resize(thumbnail, (thumbnail_width, thumbnail_height))
|
||||||
|
canvas[y:y+thumbnail_height, x:x+thumbnail_width] = resized_thumbnail
|
||||||
|
|
||||||
|
# Draw progress bar
|
||||||
|
progress_y = y + thumbnail_height + 5
|
||||||
|
progress_width = thumbnail_width
|
||||||
|
progress = self.get_progress_for_video(video_path)
|
||||||
|
|
||||||
|
# Progress background
|
||||||
|
cv2.rectangle(canvas,
|
||||||
|
(x, progress_y),
|
||||||
|
(x + progress_width, progress_y + self.PROGRESS_BAR_HEIGHT),
|
||||||
|
self.PROGRESS_BG_COLOR, -1)
|
||||||
|
|
||||||
|
# Progress fill
|
||||||
|
if progress > 0:
|
||||||
|
fill_width = int(progress_width * progress)
|
||||||
|
cv2.rectangle(canvas,
|
||||||
|
(x, progress_y),
|
||||||
|
(x + fill_width, progress_y + self.PROGRESS_BAR_HEIGHT),
|
||||||
|
self.PROGRESS_FILL_COLOR, -1)
|
||||||
|
|
||||||
|
# Draw filename
|
||||||
|
filename = video_path.name
|
||||||
|
# Truncate if too long
|
||||||
|
if len(filename) > 20:
|
||||||
|
filename = filename[:17] + "..."
|
||||||
|
|
||||||
|
text_y = progress_y + self.PROGRESS_BAR_HEIGHT + 20
|
||||||
|
cv2.putText(canvas, filename, (x, text_y),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, self.TEXT_COLOR, 1)
|
||||||
|
|
||||||
|
# Draw progress percentage
|
||||||
|
if video_path in self.progress_data:
|
||||||
|
progress_text = f"{progress * 100:.0f}%"
|
||||||
|
text_size = cv2.getTextSize(progress_text, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1)[0]
|
||||||
|
progress_text_x = x + progress_width - text_size[0]
|
||||||
|
cv2.putText(canvas, progress_text, (progress_text_x, text_y),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.4, self.TEXT_COLOR, 1)
|
||||||
|
|
||||||
|
# Draw instructions
|
||||||
|
instructions = [
|
||||||
|
"Project View - Videos in current directory",
|
||||||
|
"WASD: Navigate | E: Open video | ESC: Back to editor",
|
||||||
|
f"Showing {len(self.video_files)} videos"
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, instruction in enumerate(instructions):
|
||||||
|
y_pos = self.window_height - 60 + i * 20
|
||||||
|
cv2.putText(canvas, instruction, (10, y_pos),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, self.TEXT_COLOR, 1)
|
||||||
|
|
||||||
|
return canvas
|
||||||
|
|
||||||
|
def handle_key(self, key: int) -> str:
|
||||||
|
"""Handle keyboard input, returns action taken"""
|
||||||
|
if key == 27: # ESC
|
||||||
|
return "back_to_editor"
|
||||||
|
elif key == ord('e') or key == ord('E'): # E - Open video
|
||||||
|
if self.video_files and 0 <= self.selected_index < len(self.video_files):
|
||||||
|
return f"open_video:{self.video_files[self.selected_index]}"
|
||||||
|
elif key == ord('w') or key == ord('W'): # W - Up
|
||||||
|
if self.selected_index >= self.items_per_row:
|
||||||
|
self.selected_index -= self.items_per_row
|
||||||
|
else:
|
||||||
|
self.selected_index = 0
|
||||||
|
self._update_scroll()
|
||||||
|
elif key == ord('s') or key == ord('S'): # S - Down
|
||||||
|
if self.selected_index + self.items_per_row < len(self.video_files):
|
||||||
|
self.selected_index += self.items_per_row
|
||||||
|
else:
|
||||||
|
self.selected_index = len(self.video_files) - 1
|
||||||
|
self._update_scroll()
|
||||||
|
elif key == ord('a') or key == ord('A'): # A - Left
|
||||||
|
if self.selected_index > 0:
|
||||||
|
self.selected_index -= 1
|
||||||
|
self._update_scroll()
|
||||||
|
elif key == ord('d') or key == ord('D'): # D - Right
|
||||||
|
if self.selected_index < len(self.video_files) - 1:
|
||||||
|
self.selected_index += 1
|
||||||
|
self._update_scroll()
|
||||||
|
|
||||||
|
return "none"
|
||||||
|
|
||||||
|
def _update_scroll(self):
|
||||||
|
"""Update scroll offset based on selected item"""
|
||||||
|
if not self.video_files:
|
||||||
|
return
|
||||||
|
|
||||||
|
items_per_row = min(self.items_per_row, len(self.video_files))
|
||||||
|
selected_row = self.selected_index // items_per_row
|
||||||
|
visible_rows = max(1, self.window_height // self.ITEM_HEIGHT)
|
||||||
|
|
||||||
|
# Calculate how many rows we can actually show
|
||||||
|
total_rows = (len(self.video_files) + items_per_row - 1) // items_per_row
|
||||||
|
|
||||||
|
# If we can show all rows, no scrolling needed
|
||||||
|
if total_rows <= visible_rows:
|
||||||
|
self.scroll_offset = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update scroll to keep selected item visible
|
||||||
|
if selected_row < self.scroll_offset:
|
||||||
|
self.scroll_offset = selected_row
|
||||||
|
elif selected_row >= self.scroll_offset + visible_rows:
|
||||||
|
self.scroll_offset = selected_row - visible_rows + 1
|
||||||
|
|
||||||
|
# Ensure scroll offset doesn't go negative or beyond available content
|
||||||
|
self.scroll_offset = max(0, min(self.scroll_offset, total_rows - visible_rows))
|
||||||
|
|
||||||
class VideoEditor:
|
class VideoEditor:
|
||||||
# Configuration constants
|
# Configuration constants
|
||||||
@@ -154,6 +437,10 @@ class VideoEditor:
|
|||||||
self.cached_frame_number = None
|
self.cached_frame_number = None
|
||||||
self.cached_transform_hash = None
|
self.cached_transform_hash = None
|
||||||
|
|
||||||
|
# Project view mode
|
||||||
|
self.project_view_mode = False
|
||||||
|
self.project_view = None
|
||||||
|
|
||||||
# Initialize with first video
|
# Initialize with first video
|
||||||
self._load_video(self.video_files[0])
|
self._load_video(self.video_files[0])
|
||||||
|
|
||||||
@@ -824,6 +1111,66 @@ class VideoEditor:
|
|||||||
|
|
||||||
self.display_needs_update = True
|
self.display_needs_update = True
|
||||||
|
|
||||||
|
def toggle_project_view(self):
|
||||||
|
"""Toggle between editor and project view mode"""
|
||||||
|
if self.project_view_mode:
|
||||||
|
# Switch back to editor mode
|
||||||
|
self.project_view_mode = False
|
||||||
|
if self.project_view:
|
||||||
|
cv2.destroyWindow("Project View")
|
||||||
|
self.project_view = None
|
||||||
|
print("Switched to editor mode")
|
||||||
|
else:
|
||||||
|
# Switch to project view mode
|
||||||
|
self.project_view_mode = True
|
||||||
|
# Create project view for the current directory
|
||||||
|
if self.path.is_dir():
|
||||||
|
project_dir = self.path
|
||||||
|
else:
|
||||||
|
project_dir = self.path.parent
|
||||||
|
self.project_view = ProjectView(project_dir, self)
|
||||||
|
# Create separate window for project view
|
||||||
|
cv2.namedWindow("Project View", cv2.WINDOW_AUTOSIZE)
|
||||||
|
print("Switched to project view mode")
|
||||||
|
|
||||||
|
self.display_needs_update = True
|
||||||
|
|
||||||
|
def open_video_from_project_view(self, video_path: Path):
|
||||||
|
"""Open a video from project view in editor mode"""
|
||||||
|
print(f"Attempting to open video: {video_path}")
|
||||||
|
print(f"Video path exists: {video_path.exists()}")
|
||||||
|
|
||||||
|
# Save current state before switching
|
||||||
|
self.save_state()
|
||||||
|
|
||||||
|
# Switch back to editor mode first
|
||||||
|
self.project_view_mode = False
|
||||||
|
self.project_view = None
|
||||||
|
|
||||||
|
# Find the video in our video_files list
|
||||||
|
try:
|
||||||
|
video_index = self.video_files.index(video_path)
|
||||||
|
self.current_video_index = video_index
|
||||||
|
self._load_video(video_path)
|
||||||
|
self.load_current_frame()
|
||||||
|
print(f"Opened video: {video_path.name}")
|
||||||
|
except ValueError:
|
||||||
|
print(f"Video not found in current session: {video_path.name}")
|
||||||
|
# If video not in current session, reload the directory
|
||||||
|
self.path = video_path.parent
|
||||||
|
self.video_files = self._get_media_files_from_directory(self.path)
|
||||||
|
if video_path in self.video_files:
|
||||||
|
video_index = self.video_files.index(video_path)
|
||||||
|
self.current_video_index = video_index
|
||||||
|
self._load_video(video_path)
|
||||||
|
self.load_current_frame()
|
||||||
|
print(f"Opened video: {video_path.name}")
|
||||||
|
else:
|
||||||
|
print(f"Could not find video: {video_path.name}")
|
||||||
|
# Re-enable project view if we couldn't open the video
|
||||||
|
self.project_view_mode = True
|
||||||
|
self.project_view = ProjectView(video_path.parent, self)
|
||||||
|
|
||||||
def draw_feedback_message(self, frame):
|
def draw_feedback_message(self, frame):
|
||||||
"""Draw feedback message on frame if visible"""
|
"""Draw feedback message on frame if visible"""
|
||||||
if not self.feedback_message or not self.feedback_message_time:
|
if not self.feedback_message or not self.feedback_message_time:
|
||||||
@@ -1298,6 +1645,10 @@ class VideoEditor:
|
|||||||
|
|
||||||
def mouse_callback(self, event, x, y, flags, _):
|
def mouse_callback(self, event, x, y, flags, _):
|
||||||
"""Handle mouse events"""
|
"""Handle mouse events"""
|
||||||
|
# Handle project view mode - no mouse interaction needed
|
||||||
|
if self.project_view_mode and self.project_view:
|
||||||
|
return
|
||||||
|
|
||||||
# Handle timeline interaction (not for images)
|
# Handle timeline interaction (not for images)
|
||||||
if self.timeline_rect and not self.is_image_mode:
|
if self.timeline_rect and not self.is_image_mode:
|
||||||
bar_x_start, bar_y, bar_width, bar_height = self.timeline_rect
|
bar_x_start, bar_y, bar_width, bar_height = self.timeline_rect
|
||||||
@@ -2034,6 +2385,7 @@ class VideoEditor:
|
|||||||
print(" Ctrl+Scroll: Zoom in/out")
|
print(" Ctrl+Scroll: Zoom in/out")
|
||||||
print(" Shift+S: Save screenshot")
|
print(" Shift+S: Save screenshot")
|
||||||
print(" f: Toggle fullscreen")
|
print(" f: Toggle fullscreen")
|
||||||
|
print(" p: Toggle project view")
|
||||||
if len(self.video_files) > 1:
|
if len(self.video_files) > 1:
|
||||||
print(" N: Next file")
|
print(" N: Next file")
|
||||||
print(" n: Previous file")
|
print(" n: Previous file")
|
||||||
@@ -2064,6 +2416,7 @@ class VideoEditor:
|
|||||||
print(" Ctrl+Scroll: Zoom in/out")
|
print(" Ctrl+Scroll: Zoom in/out")
|
||||||
print(" Shift+S: Save screenshot")
|
print(" Shift+S: Save screenshot")
|
||||||
print(" f: Toggle fullscreen")
|
print(" f: Toggle fullscreen")
|
||||||
|
print(" p: Toggle project view")
|
||||||
print(" 1: Set cut start point")
|
print(" 1: Set cut start point")
|
||||||
print(" 2: Set cut end point")
|
print(" 2: Set cut end point")
|
||||||
print(" T: Toggle loop between markers")
|
print(" T: Toggle loop between markers")
|
||||||
@@ -2093,23 +2446,48 @@ class VideoEditor:
|
|||||||
# Update display
|
# Update display
|
||||||
self.display_current_frame()
|
self.display_current_frame()
|
||||||
|
|
||||||
delay = self.calculate_frame_delay() if self.is_playing else 1 # Very short delay for responsive key detection
|
# Handle project view window if it exists
|
||||||
key = cv2.waitKey(delay) & 0xFF
|
if self.project_view_mode and self.project_view:
|
||||||
|
# Draw project view in its own window
|
||||||
|
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
|
||||||
|
|
||||||
|
# Route keys based on window focus
|
||||||
|
if key != 255: # Key was pressed
|
||||||
|
active_window = get_active_window_title()
|
||||||
|
|
||||||
|
if "Project View" in active_window:
|
||||||
|
# Project view window has focus - handle project view keys
|
||||||
|
if self.project_view_mode and self.project_view:
|
||||||
|
action = self.project_view.handle_key(key)
|
||||||
|
if action == "back_to_editor":
|
||||||
|
self.toggle_project_view()
|
||||||
|
elif action.startswith("open_video:"):
|
||||||
|
video_path_str = action.split(":", 1)[1]
|
||||||
|
video_path = Path(video_path_str)
|
||||||
|
self.open_video_from_project_view(video_path)
|
||||||
|
continue # Skip main window key handling
|
||||||
|
|
||||||
|
elif "Video Editor" in active_window or "Image Editor" in active_window:
|
||||||
|
# Main window has focus - handle editor keys
|
||||||
|
pass # Continue to main window key handling below
|
||||||
|
else:
|
||||||
|
# Neither window has focus, ignore key
|
||||||
|
continue
|
||||||
|
|
||||||
# Handle auto-repeat - stop if no key is pressed
|
# Handle auto-repeat - stop if no key is pressed
|
||||||
if key == 255 and self.auto_repeat_active: # 255 means no key pressed
|
if key == 255 and self.auto_repeat_active: # 255 means no key pressed
|
||||||
self.stop_auto_repeat_seek()
|
self.stop_auto_repeat_seek()
|
||||||
|
|
||||||
# Get modifier key states
|
|
||||||
window_title = "Image Editor" if self.is_image_mode else "Video Editor"
|
|
||||||
# Note: OpenCV doesn't provide direct access to modifier keys in waitKey
|
|
||||||
# We'll handle this through special key combinations
|
|
||||||
|
|
||||||
if key == ord("q") or key == 27: # ESC
|
if key == ord("q") or key == 27: # ESC
|
||||||
self.stop_auto_repeat_seek()
|
self.stop_auto_repeat_seek()
|
||||||
self.save_state()
|
self.save_state()
|
||||||
break
|
break
|
||||||
|
elif key == ord("p"): # P - Toggle project view
|
||||||
|
self.toggle_project_view()
|
||||||
elif key == ord(" "):
|
elif key == ord(" "):
|
||||||
# Don't allow play/pause for images
|
# Don't allow play/pause for images
|
||||||
if not self.is_image_mode:
|
if not self.is_image_mode:
|
||||||
|
Reference in New Issue
Block a user