from pathlib import Path from typing import Dict import cv2 import numpy as np class ProjectView: """Project view that displays videos in current directory with progress bars""" THUMBNAIL_SIZE = (200, 150) THUMBNAIL_MARGIN = 20 PROGRESS_BAR_HEIGHT = 8 TEXT_HEIGHT = 30 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: Dict[Path, np.ndarray] = {} self.progress_data = {} self.selected_index = 0 self.scroll_offset = 0 self.items_per_row = 2 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: 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) thumbnail_height = int(thumbnail_width * self.THUMBNAIL_SIZE[1] / self.THUMBNAIL_SIZE[0]) return (thumbnail_width, thumbnail_height) def _load_video_files(self): 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): 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: import json state = json.load(f) current_frame = state.get('current_frame', 0) 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: # noqa: BLE001 - preserve original behavior print(f"Error loading progress for {video_path.name}: {e}") def refresh_progress_data(self): self._load_progress_data() def get_progress_for_video(self, video_path: Path) -> float: 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, size: tuple = None) -> np.ndarray: if size is None: size = self.THUMBNAIL_SIZE if video_path in self.thumbnails: original_thumbnail = self.thumbnails[video_path] return cv2.resize(original_thumbnail, size) 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: original_thumbnail = cv2.resize(frame, self.THUMBNAIL_SIZE) self.thumbnails[video_path] = original_thumbnail cap.release() return cv2.resize(original_thumbnail, size) cap.release() except Exception as e: # noqa: BLE001 - preserve original behavior print(f"Error generating thumbnail for {video_path.name}: {e}") placeholder = np.full((size[1], size[0], 3), self.THUMBNAIL_BG_COLOR, dtype=np.uint8) return placeholder # draw() and input handling remain in main editor for now to minimize churn