Compare commits

...

27 Commits

Author SHA1 Message Date
bd1824a7ca Remove spammy prints 2025-09-26 19:44:17 +02:00
4806c95095 Shift 1-2 to go to markers 2025-09-26 19:41:07 +02:00
16c841d14d Remove some retard 2025-09-26 19:39:59 +02:00
bfb9ed54d9 Bigger template aoe 2025-09-26 19:39:53 +02:00
3ac725c2aa Enhance template selection logic in VideoEditor to handle current frame matches
This commit updates the template selection logic in the VideoEditor to correctly identify and utilize templates that start at the current frame. The changes include additional checks to remove or use templates based on their start frames, improving the efficiency and accuracy of template management during video editing sessions.
2025-09-26 19:38:45 +02:00
b5a0811cbd Enhance template management in VideoEditor by including template images
This commit updates the template management system in the VideoEditor to store template images alongside their start frames and regions. The changes include modifications to the loading and saving processes, ensuring that template images are recreated when needed. Additionally, the logic for adding and retrieving templates has been refined to accommodate the new structure, improving the overall efficiency and clarity of template handling during video editing sessions.
2025-09-26 19:35:13 +02:00
1ac8cd04b3 Refactor template management in VideoEditor to simplify template structure
This commit updates the template management system in the VideoEditor by transitioning from a dictionary-based structure to a list of tuples. The new structure simplifies the handling of templates, focusing on start frames and regions without the need for template IDs or counters. Additionally, the loading and saving of templates have been streamlined, enhancing clarity and efficiency in template operations during video editing sessions.
2025-09-26 19:33:52 +02:00
203d036a92 Remove all templates on C 2025-09-26 19:14:00 +02:00
fa2ac22f9f Refactor video scaling logic in VideoEditor to improve display handling
This commit updates the video scaling logic to ensure that videos are scaled down to fit the screen bounds instead of resizing the window. The previous logic for window resizing has been removed, streamlining the display process and enhancing the user experience during video editing sessions. The changes also include adjustments to maintain the aspect ratio of the video while scaling.
2025-09-26 19:04:54 +02:00
2013ccf627 Remove logic for future template management in VideoEditor
This commit eliminates the code that removes templates starting after the current frame, streamlining the template handling process. The changes enhance clarity and efficiency in template management during video editing sessions. Debug messages related to template removal have also been removed to reflect this update.
2025-09-26 18:53:10 +02:00
e1d94f2b24 Refactor template management in VideoEditor to streamline template handling
This commit removes unused attributes and logic related to template matching, enhancing the clarity and efficiency of the template management system. The changes include the removal of the template matching enabled flag and associated state management, focusing on the current template ID and its associated data. Debug messages have been updated to reflect these changes, ensuring better tracking and feedback during video editing sessions.
2025-09-26 18:42:35 +02:00
9df6d73db8 Enhance template management in VideoEditor by removing future templates and ending current ones
This commit updates the template handling logic in the VideoEditor to remove any templates that start after the current frame and properly end templates that start at the current frame. This change improves the clarity and efficiency of template management during video editing sessions, ensuring that only relevant templates are active. Debug messages have been added to provide insights into the template removal and ending process.
2025-09-26 18:31:10 +02:00
01340a0a81 Refactor and streamline template matching logic in VideoEditor
This commit removes unused methods and simplifies the template matching process by eliminating multi-scale matching in favor of a single-scale approach. The changes enhance performance and clarity in the tracking logic, ensuring a more efficient template matching experience. Debug messages have been updated to reflect the current state of the template matching process.
2025-09-26 18:30:19 +02:00
44ed4220b9 Refactor template matching logic in VideoEditor to support full frame and cropped modes
This commit introduces a toggle for template matching modes in the VideoEditor, allowing users to choose between full frame and cropped region matching. The logic has been updated to handle both modes effectively, improving flexibility and performance during template tracking. Additionally, the multi-scale template matching feature has been removed, streamlining the template matching process. Debug messages and feedback have been enhanced to reflect the current mode, ensuring better user experience during video editing sessions.
2025-09-26 18:20:12 +02:00
151744d144 Update 2025-09-26 17:56:28 +02:00
e823a11929 Enhance template matching in VideoEditor by applying motion tracking offsets
This commit updates the template matching logic in the VideoEditor to apply motion tracking offsets to the current frame before performing template matching. The new method, _apply_motion_tracking_offset, creates an offset frame based on the base position, improving tracking accuracy. This change simplifies the process by removing the previous cropping logic and ensures that template matching works effectively on the adjusted frame, enhancing overall performance during video editing sessions.
2025-09-26 17:54:56 +02:00
c1c01e86ca Optimize template matching in VideoEditor to utilize cropped regions for improved performance
This commit modifies the template matching logic in the VideoEditor to use only the cropped region of the frame when available, significantly enhancing the speed of template tracking. If no crop is applied, the original frame is used for tracking. This change improves the efficiency of the template matching process while maintaining accuracy in locating objects during video editing sessions.
2025-09-26 17:52:09 +02:00
184aceeee3 Enhance template selection and tracking logic in VideoEditor
This commit introduces a mechanism to select the best template for the current frame if templates are available. Additionally, it updates the template matching process to apply tracking directly on the original frame, avoiding infinite recursion and improving tracking accuracy. These changes enhance the overall functionality and reliability of the template management system during video editing sessions.
2025-09-26 17:50:07 +02:00
db2aa57ce5 Refactor template matching logic in VideoEditor to utilize transformed frames
This commit updates the template matching process in the VideoEditor to apply tracking on the already transformed frame, which includes crop, zoom, and rotation adjustments. This change enhances tracking accuracy by ensuring that the template can locate the object in its modified position, while also improving code clarity and performance during video editing sessions.
2025-09-26 17:49:29 +02:00
92c2e62166 Refactor template matching logic in VideoEditor to use original frame for tracking
This commit updates the template matching process in the VideoEditor to apply tracking directly on the original frame instead of a cropped region. This change ensures that the template can accurately locate the object in its original position, improving tracking reliability. The code has been simplified by removing the previous cropping logic, enhancing overall clarity and performance during video editing sessions.
2025-09-26 17:45:19 +02:00
86c31a49d9 Refactor template management in VideoEditor to enhance tracking functionality
This commit removes the initial template selection logic from the tracking method and introduces a mechanism to disable template matching when no templates are available. Additionally, it updates the comments to clarify the enabling of template matching upon template creation, improving the overall management of templates during video editing sessions.
2025-09-26 17:42:30 +02:00
f5b8656bc2 Refactor template navigation methods in VideoEditor for improved functionality
This commit renames and updates the template navigation methods in the VideoEditor to enhance user experience. The methods now allow users to jump directly to the previous or next template markers, improving frame handling and feedback. Debug messages have been added to provide clearer insights during navigation, ensuring users are informed about the current actions and template positions.
2025-09-26 17:39:02 +02:00
b9c60ffc25 Refactor template dot rendering and right-click functionality in VideoEditor
This commit simplifies the rendering of template dots in the VideoEditor by standardizing their appearance to match motion tracking points. Additionally, it enhances the right-click functionality to allow for template removal when clicking near template centers, improving user interaction and feedback during video editing sessions.
2025-09-26 17:37:35 +02:00
b6c7863b77 Refactor template navigation in VideoEditor for improved frame handling and user feedback
This commit updates the navigation methods for templates in the VideoEditor, enhancing the logic to jump to the start frame of the selected template. The feedback messages have been improved to provide clearer information about the selected template and its corresponding frame. Additionally, visual indicators for active templates have been added, allowing users to easily identify which templates are currently in use during video editing sessions.
2025-09-26 17:34:27 +02:00
612d024161 Update VideoEditor to manage template lifecycle and improve frame handling
This commit enhances the VideoEditor by implementing logic to end the previous template at the current frame when a new template is added. It also updates the method for retrieving the best template for a given frame to prioritize the most recent template. Additionally, a new method is introduced to recreate templates from their data region, ensuring templates are correctly initialized when needed. These changes improve template management and user feedback during video editing sessions.
2025-09-26 17:31:34 +02:00
840440eb1a Add multiple templates management to VideoEditor
This commit introduces a system for managing multiple templates within the VideoEditor. Users can now add, remove, and navigate through templates, enhancing the flexibility of template tracking. The state management has been updated to save and load multiple templates, including their regions and frame ranges. Additionally, visual indicators for active templates have been implemented in the frame display, improving user feedback during editing sessions.
2025-09-26 17:23:04 +02:00
c3bf49f301 Implement fullscreen scaling for video display in VideoEditor
This commit adds functionality to scale down the video display when in fullscreen mode if the video dimensions exceed the available screen size. This enhancement ensures that videos are properly displayed within the window dimensions, improving user experience during video editing.
2025-09-26 15:22:57 +02:00

View File

@@ -4,7 +4,7 @@ import cv2
import argparse
import numpy as np
from pathlib import Path
from typing import List, Optional, Tuple, Dict, Any
from typing import List, Dict, Any
import time
import re
import threading
@@ -200,24 +200,6 @@ class FeatureTracker:
def get_tracking_position(self, frame_number: int) -> Optional[Tuple[float, float]]:
"""Get the average tracking position for a frame"""
if frame_number not in self.features:
return None
if not self.features[frame_number]['positions']:
return None
positions = self.features[frame_number]['positions']
if not positions:
return None
# Calculate average position
avg_x = sum(pos[0] for pos in positions) / len(positions)
avg_y = sum(pos[1] for pos in positions) / len(positions)
return (avg_x, avg_y)
def clear_features(self):
@@ -878,14 +860,17 @@ class VideoEditor:
self.previous_frame_for_flow = None
# Template matching tracking
self.template_matching_enabled = False
self.tracking_template = None
self.template_region = None
self.template_match_history = [] # Store recent match confidences for adaptive thresholding
self.multi_scale_template_matching = False # Disable multi-scale by default # (x, y, w, h) in rotated frame coordinates
# (x, y, w, h) in rotated frame coordinates
self.template_selection_start = None
self.template_selection_rect = None
# Simple template system - list of (start_frame, region, template_image) tuples sorted by start_frame
self.templates = [] # [(start_frame, region, template_image), ...] sorted by start_frame
# Template matching modes
self.template_matching_full_frame = False # Toggle for full frame vs cropped template matching
# Project view mode
self.project_view_mode = False
self.project_view = None
@@ -932,9 +917,11 @@ class VideoEditor:
'tracking_enabled': self.tracking_enabled,
'tracking_points': {str(k): v for k, v in self.tracking_points.items()},
'feature_tracker': self.feature_tracker.get_state_dict(),
'template_matching_enabled': self.template_matching_enabled,
'template_region': self.template_region,
'multi_scale_template_matching': self.multi_scale_template_matching
'template_matching_full_frame': self.template_matching_full_frame,
'templates': [{
'start_frame': start_frame,
'region': region
} for start_frame, region, template_image in self.templates]
}
with open(state_file, 'w') as f:
@@ -1023,14 +1010,23 @@ class VideoEditor:
print(f"Loaded feature tracker state")
# Load template matching state
if 'template_matching_enabled' in state:
self.template_matching_enabled = state['template_matching_enabled']
if 'template_region' in state and state['template_region'] is not None:
self.template_region = state['template_region']
# Recreate template from region when needed
self.tracking_template = None
if 'multi_scale_template_matching' in state:
self.multi_scale_template_matching = state['multi_scale_template_matching'] # Will be recreated on first use
if 'template_matching_full_frame' in state:
self.template_matching_full_frame = state['template_matching_full_frame']
# Load simple templates state
if 'templates' in state:
self.templates = []
for template_data in state['templates']:
start_frame = template_data['start_frame']
region = template_data['region']
# We'll recreate the template image when needed
self.templates.append((start_frame, region, None))
# Sort by start_frame
self.templates.sort(key=lambda x: x[0])
print(f"Loaded {len(self.templates)} templates")
# Recreate template images by seeking to capture frames
self._recreate_template_images()
# Validate cut markers against current video length
if self.cut_start_frame is not None and self.cut_start_frame >= self.total_frames:
@@ -1278,7 +1274,7 @@ class VideoEditor:
"""Calculate frame delay in milliseconds based on playback speed"""
# Round to 2 decimals to handle floating point precision issues
speed = round(self.playback_speed, 2)
print(f"Playback speed: {speed}")
# print(f"Playback speed: {speed}")
if speed >= 1.0:
# Speed >= 1: maximum FPS (no delay)
return 1
@@ -1376,6 +1372,10 @@ class VideoEditor:
else:
print(f"DEBUG: Frame {self.current_frame} has NO features")
# Select the best template for the new frame
if self.templates:
self._select_best_template_for_frame(self.current_frame)
# Auto-extract features if feature tracking is enabled and auto-tracking is on
print(f"DEBUG: seek_to_frame {frame_number}: is_image_mode={self.is_image_mode}, tracking_enabled={self.feature_tracker.tracking_enabled}, auto_tracking={self.feature_tracker.auto_tracking}, display_frame={self.current_display_frame is not None}")
@@ -1647,16 +1647,27 @@ class VideoEditor:
# Calculate offset from template matching if enabled
template_offset = None
if self.template_matching_enabled and self.tracking_template is not None:
if self.templates:
if self.current_display_frame is not None:
# Use only the cropped region for much faster template matching
if self.template_matching_full_frame:
# Full frame mode - use the entire original frame
result = self.track_template(self.current_display_frame)
if result:
center_x, center_y, confidence = result
# print(f"DEBUG: Template match found at ({center_x}, {center_y}) with confidence {confidence:.2f}")
template_offset = (center_x, center_y)
else:
# Cropped mode - use only the cropped region for faster template matching
if self.crop_rect:
crop_x, crop_y, crop_w, crop_h = self.crop_rect
# Extract only the cropped region from raw frame
cropped_frame = self.current_display_frame[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
if cropped_frame is not None and cropped_frame.size > 0:
# Track template in cropped frame (much faster!)
result = self.track_template(cropped_frame)
# Apply motion tracking offset to the cropped frame
offset_frame = self._apply_motion_tracking_offset(cropped_frame, base_pos)
if offset_frame is not None:
# Track template in cropped and offset frame (much faster!)
result = self.track_template(offset_frame)
if result:
center_x, center_y, confidence = result
print(f"DEBUG: Template match found at ({center_x}, {center_y}) with confidence {confidence:.2f}")
@@ -1665,18 +1676,14 @@ class VideoEditor:
# Add crop offset back
raw_x = center_x + crop_x
raw_y = center_y + crop_y
template_offset = (raw_x, raw_y)
else:
# No crop - use full frame
raw_frame = self.current_display_frame.copy()
if raw_frame is not None:
result = self.track_template(raw_frame)
# No crop - use full frame with offset
offset_frame = self._apply_motion_tracking_offset(self.current_display_frame, base_pos)
if offset_frame is not None:
result = self.track_template(offset_frame)
if result:
center_x, center_y, confidence = result
print(f"DEBUG: Template match found at ({center_x}, {center_y}) with confidence {confidence:.2f}")
# Template matching returns coordinates in raw frame space
template_offset = (center_x, center_y)
# Calculate offset from feature tracking if enabled
@@ -1712,7 +1719,7 @@ class VideoEditor:
# Add template matching position
if template_offset:
positions.append(template_offset)
print(f"DEBUG: Template matching: ({template_offset[0]:.1f}, {template_offset[1]:.1f})")
# print(f"DEBUG: Template matching: ({template_offset[0]:.1f}, {template_offset[1]:.1f})")
# Add feature tracking position
if feature_offset:
@@ -1723,7 +1730,7 @@ class VideoEditor:
if positions:
avg_x = sum(pos[0] for pos in positions) / len(positions)
avg_y = sum(pos[1] for pos in positions) / len(positions)
print(f"DEBUG: Average of {len(positions)} positions: ({avg_x:.1f}, {avg_y:.1f})")
# print(f"DEBUG: Average of {len(positions)} positions: ({avg_x:.1f}, {avg_y:.1f})")
return (avg_x, avg_y)
# Fall back to individual tracking methods if no base position
@@ -1765,20 +1772,76 @@ class VideoEditor:
return (x1 + t * (x2 - x1), y1 + t * (y2 - y1))
return None
def _apply_motion_tracking_offset(self, frame, base_pos):
"""Apply motion tracking offset to frame for template matching"""
if base_pos is None:
return frame
try:
# Get the motion tracking offset
offset_x, offset_y = base_pos
# Create offset frame by shifting the content
h, w = frame.shape[:2]
offset_frame = np.zeros_like(frame)
# Calculate the shift
shift_x = int(offset_x)
shift_y = int(offset_y)
# Apply the offset
if shift_x != 0 or shift_y != 0:
# Calculate source and destination regions
src_x1 = max(0, -shift_x)
src_y1 = max(0, -shift_y)
src_x2 = min(w, w - shift_x)
src_y2 = min(h, h - shift_y)
dst_x1 = max(0, shift_x)
dst_y1 = max(0, shift_y)
dst_x2 = min(w, w + shift_x)
dst_y2 = min(h, h + shift_y)
if src_x2 > src_x1 and src_y2 > src_y1 and dst_x2 > dst_x1 and dst_y2 > dst_y1:
offset_frame[dst_y1:dst_y2, dst_x1:dst_x2] = frame[src_y1:src_y2, src_x1:src_x2]
else:
offset_frame = frame.copy()
else:
offset_frame = frame.copy()
return offset_frame
except Exception as e:
print(f"Error applying motion tracking offset: {e}")
return frame
def _get_template_matching_position(self, frame_number):
"""Get template matching position and confidence for a frame"""
if not self.template_matching_enabled or self.tracking_template is None:
if not self.templates:
return None
if self.current_display_frame is not None:
# Use only the cropped region for much faster template matching
# Get base position for motion tracking offset
base_pos = self._get_manual_tracking_position(frame_number)
if self.template_matching_full_frame:
# Full frame mode - use the entire original frame
result = self.track_template(self.current_display_frame)
if result:
center_x, center_y, confidence = result
return (center_x, center_y, confidence)
else:
# Cropped mode - use only the cropped region for faster template matching
if self.crop_rect:
crop_x, crop_y, crop_w, crop_h = self.crop_rect
# Extract only the cropped region from raw frame
cropped_frame = self.current_display_frame[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
if cropped_frame is not None and cropped_frame.size > 0:
# Track template in cropped frame (much faster!)
result = self.track_template(cropped_frame)
# Apply motion tracking offset to the cropped frame
offset_frame = self._apply_motion_tracking_offset(cropped_frame, base_pos)
if offset_frame is not None:
# Track template in cropped and offset frame (much faster!)
result = self.track_template(offset_frame)
if result:
center_x, center_y, confidence = result
# Map from cropped frame coordinates to raw frame coordinates
@@ -1787,10 +1850,10 @@ class VideoEditor:
raw_y = center_y + crop_y
return (raw_x, raw_y, confidence)
else:
# No crop - use full frame
raw_frame = self.current_display_frame.copy()
if raw_frame is not None:
result = self.track_template(raw_frame)
# No crop - use full frame with offset
offset_frame = self._apply_motion_tracking_offset(self.current_display_frame, base_pos)
if offset_frame is not None:
result = self.track_template(offset_frame)
if result:
center_x, center_y, confidence = result
return (center_x, center_y, confidence)
@@ -1866,8 +1929,6 @@ class VideoEditor:
def _map_screen_to_rotated(self, sx, sy):
"""Map a point on canvas screen coords back to ROTATED frame coords (pre-crop)."""
frame_number = getattr(self, 'current_frame', 0)
angle = self.rotation_angle
# Use unified display params
params = self._get_display_params()
# Back to processed (zoomed+cropped) space
@@ -2208,60 +2269,26 @@ class VideoEditor:
return (interp_x, interp_y)
def set_tracking_template(self, frame, region):
"""Set a template region for tracking (much better than optical flow)"""
try:
x, y, w, h = region
self.tracking_template = frame[y:y+h, x:x+w].copy()
self.template_region = region
print(f"DEBUG: Set tracking template with region {region}")
return True
except Exception as e:
print(f"Error setting tracking template: {e}")
return False
def track_template(self, frame):
"""Track the template in the current frame"""
if self.tracking_template is None:
# Try to recreate template from saved region
if self.template_region is not None:
self._recreate_template_from_region(frame)
if self.tracking_template is None:
if not self.templates:
return None
# Get the template for current frame
template_index = self.get_template_for_frame(self.current_frame)
if template_index is None:
return None
start_frame, region, template_image = self.templates[template_index]
# Use the stored template image from when it was captured
tracking_template = template_image
try:
# Apply image preprocessing for better template matching
gray_frame, gray_template = self._improve_template_matching(frame, self.tracking_template)
gray_frame, gray_template = self._improve_template_matching(frame, tracking_template)
# Multi-scale template matching for better tracking (if enabled)
if self.multi_scale_template_matching:
scales = [0.8, 0.9, 1.0, 1.1, 1.2] # Different scales to try
best_match = None
best_confidence = 0.0
for scale in scales:
# Resize template
template_h, template_w = gray_template.shape
new_w = int(template_w * scale)
new_h = int(template_h * scale)
if new_w <= 0 or new_h <= 0 or new_w > gray_frame.shape[1] or new_h > gray_frame.shape[0]:
continue
scaled_template = cv2.resize(gray_template, (new_w, new_h))
# Perform template matching
result = cv2.matchTemplate(gray_frame, scaled_template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
# Check if this is the best match so far
if max_val > best_confidence:
best_confidence = max_val
# Get center of template
center_x = max_loc[0] + new_w // 2
center_y = max_loc[1] + new_h // 2
best_match = (center_x, center_y, max_val)
else:
# Single-scale template matching (faster)
result = cv2.matchTemplate(gray_frame, gray_template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)
@@ -2298,36 +2325,6 @@ class VideoEditor:
print(f"Error in template tracking: {e}")
return None
def _recreate_template_from_region(self, frame):
"""Recreate template from saved region coordinates"""
try:
if self.template_region is None:
return False
x, y, w, h = self.template_region
print(f"DEBUG: Recreating template from region ({x}, {y}, {w}, {h})")
# Ensure region is within frame bounds
if (x >= 0 and y >= 0 and
x + w <= frame.shape[1] and
y + h <= frame.shape[0]):
# Extract template from frame
template = frame[y:y+h, x:x+w]
if template.size > 0:
self.tracking_template = template.copy()
print(f"DEBUG: Template recreated with size {template.shape}")
return True
else:
print("DEBUG: Template region too small")
return False
else:
print("DEBUG: Template region outside frame bounds")
return False
except Exception as e:
print(f"Error recreating template: {e}")
return False
def _improve_template_matching(self, frame, template):
"""Apply image preprocessing to improve template matching"""
@@ -2383,15 +2380,138 @@ class VideoEditor:
# Extract template from raw frame
template = self.current_display_frame[raw_y:raw_y+raw_h, raw_x:raw_x+raw_w]
if template.size > 0:
self.tracking_template = template.copy()
self.template_region = (raw_x, raw_y, raw_w, raw_h)
self.show_feedback_message(f"Template set from region ({raw_w}x{raw_h})")
print(f"DEBUG: Template set with size {template.shape}")
# Add template to collection
template_id = self.add_template(template, (raw_x, raw_y, raw_w, raw_h))
self.show_feedback_message(f"Template {template_id} set from region ({raw_w}x{raw_h})")
print(f"DEBUG: Template {template_id} set with size {template.shape}")
else:
self.show_feedback_message("Template region too small")
else:
self.show_feedback_message("Template region outside frame bounds")
def add_template(self, template, region, start_frame=None):
"""Add a new template to the collection"""
if start_frame is None:
start_frame = self.current_frame
# Add template to list with the actual template image
self.templates.append((start_frame, region, template.copy()))
# Sort by start_frame
self.templates.sort(key=lambda x: x[0])
self.show_feedback_message(f"Template added at frame {start_frame}")
return len(self.templates) - 1
def remove_template(self, template_id):
"""Remove a template from the collection"""
if not self.templates:
return False
# Use the existing function to find the template to remove
template_to_remove = self.get_template_for_frame(self.current_frame)
if template_to_remove is not None:
removed_template = self.templates.pop(template_to_remove)
self.show_feedback_message(f"Template removed (was at frame {removed_template[0]})")
return True
else:
self.show_feedback_message("No template to remove")
return False
def get_template_for_frame(self, frame_number):
"""Get the template for the current frame"""
if not self.templates:
return None
# Find template with start_frame > current_frame
for i, (start_frame, region, template_image) in enumerate(self.templates):
if start_frame > frame_number:
# Found template with start_frame > current_frame
# Use the previous one (if it exists)
if i > 0:
return i - 1
else:
return None
elif start_frame == frame_number:
# Found template with start_frame == current_frame
# Use THIS template
return i
# If no template found with start_frame > current_frame, use the last one
return len(self.templates) - 1 if self.templates else None
def _select_best_template_for_frame(self, frame_number):
"""Select the best template for the current frame"""
template_index = self.get_template_for_frame(frame_number)
return template_index is not None
def _recreate_template_images(self):
"""Recreate template images by seeking to their capture frames"""
if not self.templates:
return
current_frame_backup = self.current_frame
for i, (start_frame, region, template_image) in enumerate(self.templates):
if template_image is None: # Only recreate if missing
try:
# Seek to the capture frame
self.seek_to_frame(start_frame)
# Extract template from current frame
x, y, w, h = region
if (y + h <= self.current_display_frame.shape[0] and
x + w <= self.current_display_frame.shape[1]):
template_image = self.current_display_frame[y:y+h, x:x+w].copy()
# Update the template in the list
self.templates[i] = (start_frame, region, template_image)
print(f"DEBUG: Recreated template {i} from frame {start_frame}")
else:
print(f"DEBUG: Failed to recreate template {i} - region outside frame bounds")
except Exception as e:
print(f"DEBUG: Failed to recreate template {i}: {e}")
# Restore original frame
self.seek_to_frame(current_frame_backup)
def jump_to_previous_template(self):
"""Jump to the previous template marker (frame where template was created)."""
if self.is_image_mode:
return
self.stop_auto_repeat_seek()
if not self.templates:
print("DEBUG: No template markers; prev jump ignored")
return
current = self.current_frame
candidates = [start_frame for start_frame, region, template_image in self.templates if start_frame < current]
if candidates:
target = candidates[-1]
print(f"DEBUG: Jump prev template from {current} -> {target}")
self.seek_to_frame(target)
else:
target = self.templates[0][0]
print(f"DEBUG: Jump prev template to first marker from {current} -> {target}")
self.seek_to_frame(target)
def jump_to_next_template(self):
"""Jump to the next template marker (frame where template was created)."""
if self.is_image_mode:
return
self.stop_auto_repeat_seek()
if not self.templates:
print("DEBUG: No template markers; next jump ignored")
return
current = self.current_frame
for start_frame, region, template_image in self.templates:
if start_frame > current:
print(f"DEBUG: Jump next template from {current} -> {start_frame}")
self.seek_to_frame(start_frame)
return
target = self.templates[-1][0]
print(f"DEBUG: Jump next template to last marker from {current} -> {target}")
self.seek_to_frame(target)
def apply_rotation(self, frame):
"""Apply rotation to frame"""
@@ -2847,6 +2967,37 @@ class VideoEditor:
1,
)
# Draw template markers
for start_frame, region, template_image in self.templates:
# Draw template start point
start_progress = start_frame / max(1, self.total_frames - 1)
start_x = bar_x_start + int(bar_width * start_progress)
# Template color (green for active, red for inactive)
template_index = self.get_template_for_frame(self.current_frame)
is_active = (template_index is not None and self.templates[template_index][0] == start_frame)
template_color = (0, 255, 0) if is_active else (255, 0, 0) # Green if active, red if inactive
# Draw template start marker
cv2.rectangle(
frame,
(start_x, bar_y + 2),
(start_x + 4, bar_y + self.TIMELINE_BAR_HEIGHT - 2),
template_color,
-1,
)
# Draw template number
cv2.putText(
frame,
str(start_frame),
(start_x + 2, bar_y + 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.3,
(255, 255, 255),
1,
)
def display_current_frame(self):
"""Display the current frame with all overlays"""
if self.current_display_frame is None:
@@ -2884,28 +3035,20 @@ class VideoEditor:
height, width = display_frame.shape[:2]
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
# Don't downscale - keep original video quality
# If video is larger than window, we'll handle it by resizing the window
# Scale video to fit screen bounds
scale = min(self.window_width / width, available_height / height)
if scale < 1.0:
# Resize window to fit video instead of downscaling video
new_window_width = int(width * 1.1) # Add 10% padding
new_window_height = int(height * 1.1) + (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
# Update window size
self.window_width = new_window_width
self.window_height = new_window_height
# Resize the OpenCV window
window_title = "Image Editor" if self.is_image_mode else "Video Editor"
cv2.resizeWindow(window_title, self.window_width, self.window_height)
# Scale down video to fit screen
new_width = int(width * scale)
new_height = int(height * scale)
display_frame = cv2.resize(display_frame, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
# Create canvas with timeline space
canvas = np.zeros((self.window_height, self.window_width, 3), dtype=np.uint8)
# Center the frame on canvas
frame_height, frame_width = display_frame.shape[:2]
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
start_y = (available_height - frame_height) // 2
start_x = (self.window_width - frame_width) // 2
@@ -2949,11 +3092,10 @@ class VideoEditor:
feature_text = f" | Features: {feature_count} pts"
if self.optical_flow_enabled:
feature_text += " (OPTICAL FLOW)"
template_text = (
f" | Template: {self.template_matching_enabled}" if self.template_matching_enabled else ""
)
if self.template_matching_enabled and self.multi_scale_template_matching:
template_text += " (MULTI-SCALE)"
template_text = ""
if self.templates:
mode = "Full Frame" if self.template_matching_full_frame else "Cropped"
template_text = f" | Template: {mode}"
autorepeat_text = (
f" | Loop: ON" if self.looping_between_markers else ""
)
@@ -3067,8 +3209,7 @@ class VideoEditor:
# Draw template matching point (blue circle with confidence)
if (not self.is_image_mode and
self.template_matching_enabled and
self.tracking_template is not None):
self.templates):
# Get template matching position for current frame
template_pos = self._get_template_matching_position(self.current_frame)
if template_pos:
@@ -3082,6 +3223,7 @@ class VideoEditor:
conf_text = f"{confidence:.2f}"
cv2.putText(canvas, conf_text, (sx + 10, sy - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
# Draw selection rectangles for feature extraction/deletion
if self.selective_feature_extraction_rect:
x, y, w, h = self.selective_feature_extraction_rect
@@ -3300,6 +3442,23 @@ class VideoEditor:
# Handle right-click for tracking points (no modifiers)
if event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)):
if not self.is_image_mode:
# First check for template removal (like motion tracking points)
if self.templates:
screen_x, screen_y = x, y
raw_x, raw_y = self._map_screen_to_rotated(screen_x, screen_y)
for i, (start_frame, region, template_image) in enumerate(self.templates):
tx, ty, tw, th = region
center_x = tx + tw // 2
center_y = ty + th // 2
# Check if click is within 10px of template center
distance = ((raw_x - center_x) ** 2 + (raw_y - center_y) ** 2) ** 0.5
if distance <= 40:
self.remove_template(i) # Pass index instead of ID
self.save_state()
return
# Store tracking points in ROTATED frame coordinates (pre-crop)
rx, ry = self._map_screen_to_rotated(x, y)
threshold = self.TRACKING_POINT_THRESHOLD
@@ -3529,6 +3688,9 @@ class VideoEditor:
# Reset feature tracking
self.feature_tracker.clear_features()
# Reset templates
self.templates.clear()
# Reset cut markers
self.cut_start_frame = None
self.cut_end_frame = None
@@ -3674,13 +3836,10 @@ class VideoEditor:
def _render_video_worker(self, output_path: str):
"""Worker method that runs in the render thread"""
render_cap = None
try:
if not output_path.endswith(".mp4"):
output_path += ".mp4"
start_time = time.time()
# Send progress update to main thread
self.render_progress_queue.put(("init", "Initializing render...", 0.0, 0.0))
@@ -4352,6 +4511,14 @@ class VideoEditor:
self.cut_end_frame = self.current_frame
print(f"Set cut end at frame {self.current_frame}")
self.save_state() # Save state when cut end is set
elif key == ord("!"): # Shift+1 - Jump to cut start marker
if not self.is_image_mode and self.cut_start_frame is not None:
self.seek_to_frame(self.cut_start_frame)
print(f"Jumped to cut start marker at frame {self.cut_start_frame}")
elif key == ord("\""): # Shift+2 - Jump to cut end marker
if not self.is_image_mode and self.cut_end_frame is not None:
self.seek_to_frame(self.cut_end_frame)
print(f"Jumped to cut end marker at frame {self.cut_end_frame}")
elif key == ord("N"):
if len(self.video_files) > 1:
self.previous_video()
@@ -4491,16 +4658,24 @@ class VideoEditor:
self.show_feedback_message(f"Optical flow {'ON' if self.optical_flow_enabled else 'OFF'}")
self.save_state()
elif key == ord("m"):
# Toggle template matching tracking
self.template_matching_enabled = not self.template_matching_enabled
print(f"DEBUG: Template matching toggled to {self.template_matching_enabled}")
self.show_feedback_message(f"Template matching {'ON' if self.template_matching_enabled else 'OFF'}")
# Clear all templates
if self.templates:
self.templates.clear()
print("DEBUG: All templates cleared")
self.show_feedback_message("All templates cleared")
else:
print("DEBUG: No templates to clear")
self.show_feedback_message("No templates to clear")
self.save_state()
elif key == ord("M"): # Shift+M - Toggle multi-scale template matching
self.multi_scale_template_matching = not self.multi_scale_template_matching
print(f"DEBUG: Multi-scale template matching toggled to {self.multi_scale_template_matching}")
self.show_feedback_message(f"Multi-scale template matching {'ON' if self.multi_scale_template_matching else 'OFF'}")
self.template_matching_full_frame = not self.template_matching_full_frame
print(f"DEBUG: Template matching full frame toggled to {self.template_matching_full_frame}")
self.show_feedback_message(f"Template matching: {'Full Frame' if self.template_matching_full_frame else 'Cropped'}")
self.save_state()
elif key == ord(";"): # Semicolon - Jump to previous template marker
self.jump_to_previous_template()
elif key == ord(":"): # Colon - Jump to next template marker
self.jump_to_next_template()
elif key == ord("t"):
# Marker looping only for videos
if not self.is_image_mode: