Refactor ProjectView to improve thumbnail layout and item display: set default items per row to 2, implement dynamic thumbnail size calculation, and update keyboard shortcuts for adjusting items per row. Enhance thumbnail caching mechanism to optimize performance and maintain aspect ratio during resizing.

This commit is contained in:
2025-09-16 10:02:53 +02:00
parent 8c4663c4ef
commit b8899004f3

View File

@@ -57,13 +57,21 @@ class ProjectView:
self.progress_data = {} self.progress_data = {}
self.selected_index = 0 self.selected_index = 0
self.scroll_offset = 0 self.scroll_offset = 0
self.items_per_row = 4 self.items_per_row = 2 # Default to 2 items per row
self.window_width = 1200 self.window_width = 1200
self.window_height = 800 self.window_height = 800
self._load_video_files() self._load_video_files()
self._load_progress_data() self._load_progress_data()
def _calculate_thumbnail_size(self, window_width: int) -> tuple:
"""Calculate thumbnail size based on items per row and window width"""
available_width = window_width - self.THUMBNAIL_MARGIN
item_width = (available_width - (self.items_per_row - 1) * self.THUMBNAIL_MARGIN) // self.items_per_row
thumbnail_width = max(50, item_width) # Minimum 50px width
thumbnail_height = int(thumbnail_width * self.THUMBNAIL_SIZE[1] / self.THUMBNAIL_SIZE[0]) # Maintain aspect ratio
return (thumbnail_width, thumbnail_height)
def _load_video_files(self): def _load_video_files(self):
"""Load all video files from directory""" """Load all video files from directory"""
self.video_files = [] self.video_files = []
@@ -106,10 +114,15 @@ class ProjectView:
return self.progress_data[video_path]['progress'] return self.progress_data[video_path]['progress']
return 0.0 return 0.0
def get_thumbnail_for_video(self, video_path: Path) -> np.ndarray: def get_thumbnail_for_video(self, video_path: Path, size: tuple = None) -> np.ndarray:
"""Get thumbnail for a video, generating it if needed""" """Get thumbnail for a video, generating it if needed"""
if video_path in self.thumbnails: if size is None:
return self.thumbnails[video_path] size = self.THUMBNAIL_SIZE
# Use a cache key that includes the size
cache_key = (video_path, size)
if cache_key in self.thumbnails:
return self.thumbnails[cache_key]
# Generate thumbnail on demand # Generate thumbnail on demand
try: try:
@@ -121,8 +134,8 @@ class ProjectView:
cap.set(cv2.CAP_PROP_POS_FRAMES, middle_frame) cap.set(cv2.CAP_PROP_POS_FRAMES, middle_frame)
ret, frame = cap.read() ret, frame = cap.read()
if ret: if ret:
thumbnail = cv2.resize(frame, self.THUMBNAIL_SIZE) thumbnail = cv2.resize(frame, size)
self.thumbnails[video_path] = thumbnail self.thumbnails[cache_key] = thumbnail
cap.release() cap.release()
return thumbnail return thumbnail
cap.release() cap.release()
@@ -130,7 +143,7 @@ class ProjectView:
print(f"Error generating thumbnail for {video_path.name}: {e}") print(f"Error generating thumbnail for {video_path.name}: {e}")
# Return a placeholder if thumbnail generation failed # Return a placeholder if thumbnail generation failed
placeholder = np.full((self.THUMBNAIL_SIZE[1], self.THUMBNAIL_SIZE[0], 3), placeholder = np.full((size[1], size[0], 3),
self.THUMBNAIL_BG_COLOR, dtype=np.uint8) self.THUMBNAIL_BG_COLOR, dtype=np.uint8)
return placeholder return placeholder
@@ -164,15 +177,16 @@ class ProjectView:
cv2.putText(canvas, text, (text_x, text_y), font, 1.0, self.TEXT_COLOR, 2) cv2.putText(canvas, text, (text_x, text_y), font, 1.0, self.TEXT_COLOR, 2)
return canvas return canvas
# Calculate layout - dynamically determine items per row based on thumbnail size # Calculate layout - use fixed items_per_row and calculate thumbnail size to fit
# Calculate how many thumbnails can fit in the window width items_per_row = min(self.items_per_row, len(self.video_files)) # Don't exceed number of videos
available_width = actual_width - self.THUMBNAIL_MARGIN # Account for left margin
items_per_row = max(1, (available_width + self.THUMBNAIL_MARGIN) // (self.THUMBNAIL_SIZE[0] + self.THUMBNAIL_MARGIN)) # Calculate thumbnail size to fit the desired number of items per row
items_per_row = min(items_per_row, len(self.video_files)) # Don't exceed number of videos thumbnail_width, thumbnail_height = self._calculate_thumbnail_size(actual_width)
# Calculate item height dynamically based on thumbnail size
item_height = thumbnail_height + self.PROGRESS_BAR_HEIGHT + self.TEXT_HEIGHT + self.THUMBNAIL_MARGIN
item_width = (actual_width - (items_per_row + 1) * self.THUMBNAIL_MARGIN) // items_per_row item_width = (actual_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 # Draw videos in grid
for i, video_path in enumerate(self.video_files): for i, video_path in enumerate(self.video_files):
@@ -182,12 +196,12 @@ class ProjectView:
# Skip if scrolled out of view # Skip if scrolled out of view
if row < self.scroll_offset: if row < self.scroll_offset:
continue continue
if row > self.scroll_offset + (actual_height // self.ITEM_HEIGHT): if row > self.scroll_offset + (actual_height // item_height):
break break
# Calculate position # Calculate position
x = self.THUMBNAIL_MARGIN + col * (item_width + self.THUMBNAIL_MARGIN) x = self.THUMBNAIL_MARGIN + col * (item_width + self.THUMBNAIL_MARGIN)
y = self.THUMBNAIL_MARGIN + (row - self.scroll_offset) * self.ITEM_HEIGHT y = self.THUMBNAIL_MARGIN + (row - self.scroll_offset) * item_height
# Draw thumbnail background # Draw thumbnail background
cv2.rectangle(canvas, cv2.rectangle(canvas,
@@ -203,9 +217,9 @@ class ProjectView:
self.SELECTED_COLOR, 3) self.SELECTED_COLOR, 3)
# Draw thumbnail # Draw thumbnail
thumbnail = self.get_thumbnail_for_video(video_path) thumbnail = self.get_thumbnail_for_video(video_path, (thumbnail_width, thumbnail_height))
# Resize thumbnail to fit # Thumbnail is already the correct size, no need to resize
resized_thumbnail = cv2.resize(thumbnail, (thumbnail_width, thumbnail_height)) resized_thumbnail = thumbnail
# Ensure thumbnail doesn't exceed canvas bounds # Ensure thumbnail doesn't exceed canvas bounds
end_y = min(y + thumbnail_height, actual_height) end_y = min(y + thumbnail_height, actual_height)
@@ -257,16 +271,11 @@ class ProjectView:
cv2.putText(canvas, progress_text, (progress_text_x, text_y), cv2.putText(canvas, progress_text, (progress_text_x, text_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, self.TEXT_COLOR, 1) cv2.FONT_HERSHEY_SIMPLEX, 0.4, self.TEXT_COLOR, 1)
# Calculate current items per row for display
available_width = actual_width - self.THUMBNAIL_MARGIN
current_items_per_row = max(1, (available_width + self.THUMBNAIL_MARGIN) // (self.THUMBNAIL_SIZE[0] + self.THUMBNAIL_MARGIN))
current_items_per_row = min(current_items_per_row, len(self.video_files))
# Draw instructions # Draw instructions
instructions = [ instructions = [
"Project View - Videos in current directory", "Project View - Videos in current directory",
"WASD: Navigate | E: Open video | Q: Larger thumbnails | Y: Smaller thumbnails | q: Quit | ESC: Back to editor", "WASD: Navigate | E: Open video | Q: More items per row | Y: Fewer items per row | q: Quit | ESC: Back to editor",
f"Showing {len(self.video_files)} videos | {current_items_per_row} per row | Thumbnail: {self.THUMBNAIL_SIZE[0]}x{self.THUMBNAIL_SIZE[1]}" f"Showing {len(self.video_files)} videos | {items_per_row} per row | Thumbnail: {thumbnail_width}x{thumbnail_height}"
] ]
for i, instruction in enumerate(instructions): for i, instruction in enumerate(instructions):
@@ -286,38 +295,14 @@ class ProjectView:
if self.video_files and 0 <= self.selected_index < len(self.video_files): if self.video_files and 0 <= self.selected_index < len(self.video_files):
return f"open_video:{self.video_files[self.selected_index]}" return f"open_video:{self.video_files[self.selected_index]}"
elif key == ord('w') or key == ord('W'): # W - Up elif key == ord('w') or key == ord('W'): # W - Up
# Calculate current items per row dynamically current_items_per_row = min(self.items_per_row, len(self.video_files))
try:
window_rect = cv2.getWindowImageRect("Project View")
if window_rect[2] > 0:
available_width = window_rect[2] - self.THUMBNAIL_MARGIN
else:
available_width = self.window_width - self.THUMBNAIL_MARGIN
except:
available_width = self.window_width - self.THUMBNAIL_MARGIN
current_items_per_row = max(1, (available_width + self.THUMBNAIL_MARGIN) // (self.THUMBNAIL_SIZE[0] + self.THUMBNAIL_MARGIN))
current_items_per_row = min(current_items_per_row, len(self.video_files))
if self.selected_index >= current_items_per_row: if self.selected_index >= current_items_per_row:
self.selected_index -= current_items_per_row self.selected_index -= current_items_per_row
else: else:
self.selected_index = 0 self.selected_index = 0
self._update_scroll() self._update_scroll()
elif key == ord('s') or key == ord('S'): # S - Down elif key == ord('s') or key == ord('S'): # S - Down
# Calculate current items per row dynamically current_items_per_row = min(self.items_per_row, len(self.video_files))
try:
window_rect = cv2.getWindowImageRect("Project View")
if window_rect[2] > 0:
available_width = window_rect[2] - self.THUMBNAIL_MARGIN
else:
available_width = self.window_width - self.THUMBNAIL_MARGIN
except:
available_width = self.window_width - self.THUMBNAIL_MARGIN
current_items_per_row = max(1, (available_width + self.THUMBNAIL_MARGIN) // (self.THUMBNAIL_SIZE[0] + self.THUMBNAIL_MARGIN))
current_items_per_row = min(current_items_per_row, len(self.video_files))
if self.selected_index + current_items_per_row < len(self.video_files): if self.selected_index + current_items_per_row < len(self.video_files):
self.selected_index += current_items_per_row self.selected_index += current_items_per_row
else: else:
@@ -331,20 +316,14 @@ class ProjectView:
if self.selected_index < len(self.video_files) - 1: if self.selected_index < len(self.video_files) - 1:
self.selected_index += 1 self.selected_index += 1
self._update_scroll() self._update_scroll()
elif key == ord('Q'): # uppercase Q - Make thumbnails LARGER elif key == ord('Q'): # uppercase Q - More items per row (smaller thumbnails)
# Increase thumbnail size (no maximum limit) if self.items_per_row < len(self.video_files):
new_width = self.THUMBNAIL_SIZE[0] + 20 self.items_per_row += 1
new_height = int(new_width * self.THUMBNAIL_SIZE[1] / self.THUMBNAIL_SIZE[0]) print(f"Items per row: {self.items_per_row}")
self.THUMBNAIL_SIZE = (new_width, new_height) elif key == ord('y') or key == ord('Y'): # Y - Fewer items per row (larger thumbnails)
self.ITEM_HEIGHT = self.THUMBNAIL_SIZE[1] + self.PROGRESS_BAR_HEIGHT + self.TEXT_HEIGHT + self.THUMBNAIL_MARGIN if self.items_per_row > 1:
print(f"Thumbnail size: {self.THUMBNAIL_SIZE}") self.items_per_row -= 1
elif key == ord('y') or key == ord('Y'): # Y - Make thumbnails SMALLER print(f"Items per row: {self.items_per_row}")
# Decrease thumbnail size (minimum 20x15 to prevent division by zero)
new_width = max(20, self.THUMBNAIL_SIZE[0] - 20)
new_height = int(new_width * self.THUMBNAIL_SIZE[1] / self.THUMBNAIL_SIZE[0])
self.THUMBNAIL_SIZE = (new_width, new_height)
self.ITEM_HEIGHT = self.THUMBNAIL_SIZE[1] + self.PROGRESS_BAR_HEIGHT + self.TEXT_HEIGHT + self.THUMBNAIL_MARGIN
print(f"Thumbnail size: {self.THUMBNAIL_SIZE}")
return "none" return "none"
@@ -353,24 +332,28 @@ class ProjectView:
if not self.video_files: if not self.video_files:
return return
# Calculate current items per row dynamically # Use fixed items per row
items_per_row = min(self.items_per_row, len(self.video_files))
# Get window dimensions for calculations
try: try:
window_rect = cv2.getWindowImageRect("Project View") window_rect = cv2.getWindowImageRect("Project View")
if window_rect[2] > 0 and window_rect[3] > 0: if window_rect[2] > 0 and window_rect[3] > 0:
available_width = window_rect[2] - self.THUMBNAIL_MARGIN window_width = window_rect[2]
window_height = window_rect[3] window_height = window_rect[3]
else: else:
available_width = self.window_width - self.THUMBNAIL_MARGIN window_width = self.window_width
window_height = self.window_height window_height = self.window_height
except: except:
available_width = self.window_width - self.THUMBNAIL_MARGIN window_width = self.window_width
window_height = self.window_height window_height = self.window_height
items_per_row = max(1, (available_width + self.THUMBNAIL_MARGIN) // (self.THUMBNAIL_SIZE[0] + self.THUMBNAIL_MARGIN)) # Calculate thumbnail size and item height dynamically
items_per_row = min(items_per_row, len(self.video_files)) thumbnail_width, thumbnail_height = self._calculate_thumbnail_size(window_width)
item_height = thumbnail_height + self.PROGRESS_BAR_HEIGHT + self.TEXT_HEIGHT + self.THUMBNAIL_MARGIN
selected_row = self.selected_index // items_per_row selected_row = self.selected_index // items_per_row
visible_rows = max(1, window_height // self.ITEM_HEIGHT) visible_rows = max(1, window_height // item_height)
# Calculate how many rows we can actually show # Calculate how many rows we can actually show
total_rows = (len(self.video_files) + items_per_row - 1) // items_per_row total_rows = (len(self.video_files) + items_per_row - 1) // items_per_row