diff --git a/croppa/main.py b/croppa/main.py index 6503ec5..c8a4c56 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -758,7 +758,7 @@ class VideoEditor: # Crop adjustment settings CROP_SIZE_STEP = 5 # pixels to expand/contract crop CROP_MIN_SIZE = 10 # minimum crop width/height in pixels - CROP_BORDER_DETECTION_MAX_DISTANCE = 800 # pixels - maximum distance for border hit detection + CROP_BORDER_DETECTION_MAX_DISTANCE = 8000 # pixels - maximum distance for border hit detection # Motion tracking settings TRACKING_POINT_THRESHOLD = 10 # pixels for delete/snap radius @@ -768,6 +768,37 @@ class VideoEditor: SEEK_FRAMES_SHIFT = 10 # Shift modifier: 10 frames SEEK_FRAMES_DEFAULT = 1 # Default: 1 frame + # Brightness and contrast settings + MIN_BRIGHTNESS = -100 + MAX_BRIGHTNESS = 100 + MIN_CONTRAST = 0.1 + MAX_CONTRAST = 3.0 + + # Image/video quality settings + JPEG_QUALITY = 95 # JPEG quality for screenshots (0-100) + IMAGE_MODE_FPS = 30 # Dummy FPS for image mode + HIGH_FPS_THRESHOLD = 60 # FPS threshold for high FPS detection + + # Frame difference detection settings + FRAME_DIFFERENCE_THRESHOLD_DEFAULT = 10.0 # Percentage threshold for frame difference + FRAME_DIFFERENCE_GAP_DEFAULT = 10 # Number of frames between comparisons + FRAME_DIFFERENCE_PIXEL_THRESHOLD = 30 # Pixel threshold for binary thresholding + + # Template matching settings + TEMPLATE_MATCH_HISTORY_SIZE = 20 # Number of recent matches to keep + TEMPLATE_MATCH_AVERAGE_SIZE = 10 # Number of recent matches for average calculation + TEMPLATE_MATCH_MIN_THRESHOLD = 0.3 # Minimum confidence threshold + TEMPLATE_MATCH_AVERAGE_FACTOR = 0.8 # Factor for adaptive threshold (80% of average) + TEMPLATE_MATCH_DEFAULT_THRESHOLD = 0.5 # Default confidence threshold + + # Search/update intervals + INTERESTING_POINT_SEARCH_UPDATE_INTERVAL = 10 # Frames between progress updates + + # UI display settings + FONT_SCALE_SMALL = 0.5 # Small font scale for UI text + OVERLAY_ALPHA_LOW = 0.3 # Low alpha for transparent overlays + OVERLAY_ALPHA_HIGH = 0.7 # High alpha for semi-transparent overlays + def __init__(self, path: str): self.path = Path(path) @@ -894,8 +925,8 @@ class VideoEditor: self.template_matching_full_frame = False # Toggle for full frame vs cropped template matching # Frame difference for interesting point detection - self.frame_difference_threshold = 10.0 # Percentage threshold for frame difference (10% default) - self.frame_difference_gap = 10 # Number of frames between comparisons (default 10) + self.frame_difference_threshold = self.FRAME_DIFFERENCE_THRESHOLD_DEFAULT + self.frame_difference_gap = self.FRAME_DIFFERENCE_GAP_DEFAULT # Region selection for interesting point detection self.interesting_region = None # (x, y, width, height) or None for full frame @@ -1108,8 +1139,8 @@ class VideoEditor: # Validate and clamp values self.current_frame = max(0, min(self.current_frame, getattr(self, 'total_frames', 1) - 1)) self.zoom_factor = max(self.MIN_ZOOM, min(self.MAX_ZOOM, self.zoom_factor)) - self.brightness = max(-100, min(100, self.brightness)) - self.contrast = max(0.1, min(3.0, self.contrast)) + self.brightness = max(self.MIN_BRIGHTNESS, min(self.MAX_BRIGHTNESS, self.brightness)) + self.contrast = max(self.MIN_CONTRAST, min(self.MAX_CONTRAST, self.contrast)) self.playback_speed = max(self.MIN_PLAYBACK_SPEED, min(self.MAX_PLAYBACK_SPEED, self.playback_speed)) self.seek_multiplier = max(self.MIN_SEEK_MULTIPLIER, min(self.MAX_SEEK_MULTIPLIER, self.seek_multiplier)) @@ -1173,8 +1204,7 @@ class VideoEditor: if processed_frame is not None: # Save the processed frame with high quality settings - # Use JPEG quality 95 (0-100, where 100 is highest quality) - success = cv2.imwrite(str(screenshot_path), processed_frame, [cv2.IMWRITE_JPEG_QUALITY, 95]) + success = cv2.imwrite(str(screenshot_path), processed_frame, [cv2.IMWRITE_JPEG_QUALITY, self.JPEG_QUALITY]) if success: print(f"Screenshot saved: {screenshot_name}") self.show_feedback_message(f"Screenshot saved: {screenshot_name}") @@ -1236,7 +1266,7 @@ class VideoEditor: # Set up image properties to mimic video interface self.frame_height, self.frame_width = self.static_image.shape[:2] self.total_frames = 1 - self.fps = 30 # Dummy FPS for image mode + self.fps = self.IMAGE_MODE_FPS self.cap = None print(f"Loaded image: {self.video_path.name}") @@ -1285,7 +1315,7 @@ class VideoEditor: print(" Warning: Large H.264 video detected - seeking may be slow") if self.frame_width * self.frame_height > 1920 * 1080: print(" Warning: High resolution video - decoding may be slow") - if self.fps > 60: + if self.fps > self.HIGH_FPS_THRESHOLD: print(" Warning: High framerate video - may impact playback smoothness") # Set default values for video-specific properties @@ -1619,7 +1649,7 @@ class VideoEditor: 'base_frame': None, 'base_frame_num': None, 'search_cancelled': False, - 'update_interval': 10 + 'update_interval': self.INTERESTING_POINT_SEARCH_UPDATE_INTERVAL } # Enable search mode for OSD display @@ -1841,7 +1871,7 @@ class VideoEditor: # Calculate percentage of pixels that changed significantly # Use threshold to ignore minor noise - _, thresh_diff = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY) + _, thresh_diff = cv2.threshold(diff, self.FRAME_DIFFERENCE_PIXEL_THRESHOLD, 255, cv2.THRESH_BINARY) # Count changed pixels changed_pixels = cv2.countNonZero(thresh_diff) @@ -2639,16 +2669,16 @@ class VideoEditor: # Adaptive thresholding based on recent match history if len(self.template_match_history) > 0: # Use average of recent matches as baseline - avg_confidence = sum(self.template_match_history[-10:]) / len(self.template_match_history[-10:]) - threshold = max(0.3, avg_confidence * 0.8) # 80% of recent average, minimum 0.3 + avg_confidence = sum(self.template_match_history[-self.TEMPLATE_MATCH_AVERAGE_SIZE:]) / len(self.template_match_history[-self.TEMPLATE_MATCH_AVERAGE_SIZE:]) + threshold = max(self.TEMPLATE_MATCH_MIN_THRESHOLD, avg_confidence * self.TEMPLATE_MATCH_AVERAGE_FACTOR) else: - threshold = 0.5 # Default threshold + threshold = self.TEMPLATE_MATCH_DEFAULT_THRESHOLD # Only accept matches above adaptive threshold if best_confidence > threshold: # Store confidence for adaptive thresholding self.template_match_history.append(best_confidence) - if len(self.template_match_history) > 20: # Keep only last 20 matches + if len(self.template_match_history) > self.TEMPLATE_MATCH_HISTORY_SIZE: self.template_match_history.pop(0) return best_match else: @@ -2879,13 +2909,13 @@ class VideoEditor: def adjust_brightness(self, delta: int): """Adjust brightness by delta (-100 to 100)""" - self.brightness = max(-100, min(100, self.brightness + delta)) + self.brightness = max(self.MIN_BRIGHTNESS, min(self.MAX_BRIGHTNESS, self.brightness + delta)) self.clear_transformation_cache() self.display_needs_update = True def adjust_contrast(self, delta: float): """Adjust contrast by delta (0.1 to 3.0)""" - self.contrast = max(0.1, min(3.0, self.contrast + delta)) + self.contrast = max(self.MIN_CONTRAST, min(self.MAX_CONTRAST, self.contrast + delta)) self.clear_transformation_cache() self.display_needs_update = True @@ -3038,7 +3068,7 @@ class VideoEditor: # Semi-transparent background overlay = frame.copy() cv2.rectangle(overlay, (rect_x1, rect_y1), (rect_x2, rect_y2), (0, 0, 0), -1) - alpha = 0.7 + alpha = self.OVERLAY_ALPHA_HIGH cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame) # Draw text with shadow @@ -3117,12 +3147,12 @@ class VideoEditor: # Draw progress percentage on the left percentage_text = f"{self.progress_bar_progress * 100:.1f}%" text_color = tuple(int(255 * fade_alpha) for _ in range(3)) - cv2.putText( + cv2.putText( frame, percentage_text, (bar_x + 12, bar_y + 22), cv2.FONT_HERSHEY_SIMPLEX, - 0.5, + self.FONT_SCALE_SMALL, (0, 0, 0), 4, ) @@ -3131,7 +3161,7 @@ class VideoEditor: percentage_text, (bar_x + 10, bar_y + 20), cv2.FONT_HERSHEY_SIMPLEX, - 0.5, + self.FONT_SCALE_SMALL, text_color, 2, ) @@ -3139,7 +3169,7 @@ class VideoEditor: # Draw FPS on the right if available if self.progress_bar_fps > 0: fps_text = f"{self.progress_bar_fps:.1f} FPS" - fps_text_size = cv2.getTextSize(fps_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)[ + fps_text_size = cv2.getTextSize(fps_text, cv2.FONT_HERSHEY_SIMPLEX, self.FONT_SCALE_SMALL, 1)[ 0 ] fps_x = bar_x + bar_width - fps_text_size[0] - 10 @@ -3148,7 +3178,7 @@ class VideoEditor: fps_text, (fps_x + 2, bar_y + 22), cv2.FONT_HERSHEY_SIMPLEX, - 0.5, + self.FONT_SCALE_SMALL, (0, 0, 0), 4, ) @@ -3157,7 +3187,7 @@ class VideoEditor: fps_text, (fps_x, bar_y + 20), cv2.FONT_HERSHEY_SIMPLEX, - 0.5, + self.FONT_SCALE_SMALL, text_color, 2, ) @@ -3165,7 +3195,7 @@ class VideoEditor: # Draw main text in center if self.progress_bar_text: text_size = cv2.getTextSize( - self.progress_bar_text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1 + self.progress_bar_text, cv2.FONT_HERSHEY_SIMPLEX, self.FONT_SCALE_SMALL, 1 )[0] text_x = bar_x + (bar_width - text_size[0]) // 2 text_y = bar_y + 20 @@ -3570,7 +3600,7 @@ class VideoEditor: cv2.circle(canvas, (sx, sy), 8, (255, 255, 255), 2) # Draw confidence text conf_text = f"{confidence:.2f}" - cv2.putText(canvas, conf_text, (sx + 10, sy - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) + cv2.putText(canvas, conf_text, (sx + 10, sy - 10), cv2.FONT_HERSHEY_SIMPLEX, self.FONT_SCALE_SMALL, (255, 255, 255), 1) # Draw selection rectangles for feature extraction/deletion @@ -3628,7 +3658,7 @@ class VideoEditor: cv2.line(overlay, (sx2, sy2), (arrow_x1, arrow_y1), (255, 255, 0), 1) cv2.line(overlay, (sx2, sy2), (arrow_x2, arrow_y2), (255, 255, 0), 1) - cv2.addWeighted(overlay, 0.3, canvas, 0.7, 0, canvas) # Very transparent + cv2.addWeighted(overlay, self.OVERLAY_ALPHA_LOW, canvas, self.OVERLAY_ALPHA_HIGH, 0, canvas) # Previous tracking point (red) - from the most recent frame with tracking points before current if prev_result: @@ -3689,7 +3719,7 @@ class VideoEditor: # Semi-transparent background overlay = canvas.copy() cv2.rectangle(overlay, (bg_x, bg_y), (bg_x + bg_w, bg_y + bg_h), (0, 0, 0), -1) - cv2.addWeighted(overlay, 0.7, canvas, 0.3, 0, canvas) + cv2.addWeighted(overlay, self.OVERLAY_ALPHA_HIGH, canvas, self.OVERLAY_ALPHA_LOW, 0, canvas) # Border cv2.rectangle(canvas, (bg_x, bg_y), (bg_x + bg_w, bg_y + bg_h), (255, 255, 0), 2) @@ -3749,7 +3779,7 @@ class VideoEditor: # Draw selection info info_text = f"Region: {sel_w}x{sel_h}" - cv2.putText(canvas, info_text, (sel_x, sel_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) + cv2.putText(canvas, info_text, (sel_x, sel_y - 5), cv2.FONT_HERSHEY_SIMPLEX, self.FONT_SCALE_SMALL, (0, 255, 255), 1) window_title = "Image Editor" if self.is_image_mode else "Video Editor" cv2.imshow(window_title, canvas) @@ -4639,7 +4669,7 @@ class VideoEditor: if processed_image is not None: # Save the image with high quality settings - success = cv2.imwrite(output_path, processed_image, [cv2.IMWRITE_JPEG_QUALITY, 95]) + success = cv2.imwrite(output_path, processed_image, [cv2.IMWRITE_JPEG_QUALITY, self.JPEG_QUALITY]) if success: print(f"Image saved successfully to {output_path}") return True @@ -5193,7 +5223,7 @@ class VideoEditor: print(f"Frame difference threshold: {self.frame_difference_threshold:.1f}% (-10pp)") self.show_feedback_message(f"Threshold: {self.frame_difference_threshold:.1f}% (-10pp)") elif key == ord("="): # Shift+0 - Increase frame difference threshold by 10 percentage points - self.frame_difference_threshold = min(100.0, self.frame_difference_threshold + 10.0) + self.frame_difference_threshold = min(100.0, self.frame_difference_threshold + 10.0) # Max 100% print(f"Frame difference threshold: {self.frame_difference_threshold:.1f}% (+10pp)") self.show_feedback_message(f"Threshold: {self.frame_difference_threshold:.1f}% (+10pp)") elif key == ord("7"): # 7 - Decrease frame difference gap