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