From b8899004f38750e7bc3e7c731eebb16e600baeb8 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Tue, 16 Sep 2025 10:02:53 +0200 Subject: [PATCH] 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. --- croppa/main.py | 129 +++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 73 deletions(-) diff --git a/croppa/main.py b/croppa/main.py index 2f80b9e..771045d 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -57,13 +57,21 @@ class ProjectView: self.progress_data = {} self.selected_index = 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_height = 800 self._load_video_files() 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): """Load all video files from directory""" self.video_files = [] @@ -106,10 +114,15 @@ class ProjectView: return self.progress_data[video_path]['progress'] 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""" - if video_path in self.thumbnails: - return self.thumbnails[video_path] + if size is None: + 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 try: @@ -121,8 +134,8 @@ class ProjectView: 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 + thumbnail = cv2.resize(frame, size) + self.thumbnails[cache_key] = thumbnail cap.release() return thumbnail cap.release() @@ -130,7 +143,7 @@ class ProjectView: 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), + placeholder = np.full((size[1], size[0], 3), self.THUMBNAIL_BG_COLOR, dtype=np.uint8) return placeholder @@ -164,15 +177,16 @@ class ProjectView: cv2.putText(canvas, text, (text_x, text_y), font, 1.0, self.TEXT_COLOR, 2) return canvas - # Calculate layout - dynamically determine items per row based on thumbnail size - # Calculate how many thumbnails can fit in the window width - 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)) - items_per_row = min(items_per_row, len(self.video_files)) # Don't exceed number of videos + # Calculate layout - use fixed items_per_row and calculate thumbnail size to fit + items_per_row = min(self.items_per_row, len(self.video_files)) # Don't exceed number of videos + + # Calculate thumbnail size to fit the desired number of items per row + 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 - 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): @@ -182,12 +196,12 @@ class ProjectView: # Skip if scrolled out of view if row < self.scroll_offset: continue - if row > self.scroll_offset + (actual_height // self.ITEM_HEIGHT): + if row > self.scroll_offset + (actual_height // 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 + y = self.THUMBNAIL_MARGIN + (row - self.scroll_offset) * item_height # Draw thumbnail background cv2.rectangle(canvas, @@ -203,9 +217,9 @@ class ProjectView: 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)) + thumbnail = self.get_thumbnail_for_video(video_path, (thumbnail_width, thumbnail_height)) + # Thumbnail is already the correct size, no need to resize + resized_thumbnail = thumbnail # Ensure thumbnail doesn't exceed canvas bounds 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.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 instructions = [ "Project View - Videos in current directory", - "WASD: Navigate | E: Open video | Q: Larger thumbnails | Y: Smaller thumbnails | 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]}" + "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 | {items_per_row} per row | Thumbnail: {thumbnail_width}x{thumbnail_height}" ] 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): return f"open_video:{self.video_files[self.selected_index]}" elif key == ord('w') or key == ord('W'): # W - Up - # Calculate current items per row dynamically - 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)) - + current_items_per_row = min(self.items_per_row, len(self.video_files)) if self.selected_index >= current_items_per_row: self.selected_index -= current_items_per_row else: self.selected_index = 0 self._update_scroll() elif key == ord('s') or key == ord('S'): # S - Down - # Calculate current items per row dynamically - 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)) - + current_items_per_row = min(self.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 else: @@ -331,20 +316,14 @@ class ProjectView: if self.selected_index < len(self.video_files) - 1: self.selected_index += 1 self._update_scroll() - elif key == ord('Q'): # uppercase Q - Make thumbnails LARGER - # Increase thumbnail size (no maximum limit) - new_width = 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}") - elif key == ord('y') or key == ord('Y'): # Y - Make thumbnails SMALLER - # 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}") + elif key == ord('Q'): # uppercase Q - More items per row (smaller thumbnails) + if self.items_per_row < len(self.video_files): + self.items_per_row += 1 + print(f"Items per row: {self.items_per_row}") + elif key == ord('y') or key == ord('Y'): # Y - Fewer items per row (larger thumbnails) + if self.items_per_row > 1: + self.items_per_row -= 1 + print(f"Items per row: {self.items_per_row}") return "none" @@ -353,24 +332,28 @@ class ProjectView: if not self.video_files: 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: window_rect = cv2.getWindowImageRect("Project View") 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] else: - available_width = self.window_width - self.THUMBNAIL_MARGIN + window_width = self.window_width window_height = self.window_height except: - available_width = self.window_width - self.THUMBNAIL_MARGIN + window_width = self.window_width window_height = self.window_height - items_per_row = max(1, (available_width + self.THUMBNAIL_MARGIN) // (self.THUMBNAIL_SIZE[0] + self.THUMBNAIL_MARGIN)) - items_per_row = min(items_per_row, len(self.video_files)) + # Calculate thumbnail size and item height dynamically + 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 - 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 total_rows = (len(self.video_files) + items_per_row - 1) // items_per_row