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:
129
croppa/main.py
129
croppa/main.py
@@ -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
|
||||||
|
Reference in New Issue
Block a user