Compare commits

..

9 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

View File

@@ -865,10 +865,8 @@ class VideoEditor:
self.template_selection_start = None
self.template_selection_rect = None
# Multiple templates system
self.templates = {} # {template_id: {'template': image, 'region': (x,y,w,h), 'start_frame': int, 'end_frame': int}}
self.current_template_id = None
self.template_id_counter = 0
# 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
@@ -920,14 +918,10 @@ class VideoEditor:
'tracking_points': {str(k): v for k, v in self.tracking_points.items()},
'feature_tracker': self.feature_tracker.get_state_dict(),
'template_matching_full_frame': self.template_matching_full_frame,
'templates': {str(k): {
'template': None, # Don't save template images (too large)
'region': v['region'],
'start_frame': v['start_frame'],
'end_frame': v['end_frame']
} for k, v in self.templates.items()},
'current_template_id': self.current_template_id,
'template_id_counter': self.template_id_counter
'templates': [{
'start_frame': start_frame,
'region': region
} for start_frame, region, template_image in self.templates]
}
with open(state_file, 'w') as f:
@@ -1019,22 +1013,20 @@ class VideoEditor:
if 'template_matching_full_frame' in state:
self.template_matching_full_frame = state['template_matching_full_frame']
# Load multiple templates state
# Load simple templates state
if 'templates' in state:
self.templates = {}
for template_id_str, template_data in state['templates'].items():
template_id = int(template_id_str)
self.templates[template_id] = {
'template': None, # Will be recreated when needed
'region': template_data['region'],
'start_frame': template_data['start_frame'],
'end_frame': template_data['end_frame']
}
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")
if 'current_template_id' in state:
self.current_template_id = state['current_template_id']
if 'template_id_counter' in state:
self.template_id_counter = state['template_id_counter']
# 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:
@@ -1282,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
@@ -1655,14 +1647,14 @@ class VideoEditor:
# Calculate offset from template matching if enabled
template_offset = None
if self.templates and self.current_template_id is not None:
if self.templates:
if self.current_display_frame is not None:
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}")
# 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
@@ -1727,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:
@@ -1738,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
@@ -1825,7 +1817,7 @@ class VideoEditor:
def _get_template_matching_position(self, frame_number):
"""Get template matching position and confidence for a frame"""
if not self.templates or self.current_template_id is None:
if not self.templates:
return None
if self.current_display_frame is not None:
@@ -2280,14 +2272,18 @@ class VideoEditor:
def track_template(self, frame):
"""Track the template in the current frame"""
if self.current_template_id is None or self.current_template_id not in self.templates:
if not self.templates:
return None
template_data = self.templates[self.current_template_id]
tracking_template = template_data['template']
if tracking_template is 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
@@ -2393,137 +2389,110 @@ class VideoEditor:
else:
self.show_feedback_message("Template region outside frame bounds")
def add_template(self, template, region, start_frame=None, end_frame=None):
def add_template(self, template, region, start_frame=None):
"""Add a new template to the collection"""
template_id = self.template_id_counter
self.template_id_counter += 1
if start_frame is None:
start_frame = self.current_frame
if end_frame is None:
end_frame = self.total_frames - 1
# Only end the current template if it exists and starts at or before the current frame
if self.current_template_id is not None and self.current_template_id in self.templates:
current_template = self.templates[self.current_template_id]
if current_template['start_frame'] <= self.current_frame:
# End the current template at the previous frame
self.templates[self.current_template_id]['end_frame'] = self.current_frame - 1
print(f"DEBUG: Ended current template {self.current_template_id} at frame {self.current_frame - 1}")
self.templates[template_id] = {
'template': template.copy(),
'region': region,
'start_frame': start_frame,
'end_frame': end_frame
}
# Add template to list with the actual template image
self.templates.append((start_frame, region, template.copy()))
# Set as current template
self.current_template_id = template_id
# Sort by start_frame
self.templates.sort(key=lambda x: x[0])
self.show_feedback_message(f"Template {template_id} added (frames {start_frame}-{end_frame})")
return template_id
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 template_id in self.templates:
del self.templates[template_id]
if not self.templates:
return False
# If we removed the current template, find a new one
if self.current_template_id == template_id:
self._select_best_template_for_frame(self.current_frame)
# If no templates left, clear current template
if not self.templates:
self.current_template_id = None
self.show_feedback_message(f"Template {template_id} removed")
return True
return False
def get_template_for_frame(self, frame_number):
"""Get the most recent template that covers this frame"""
# Find the most recent template (highest ID) that covers this frame
best_template_id = None
# Use the existing function to find the template to remove
template_to_remove = self.get_template_for_frame(self.current_frame)
for template_id, template_data in self.templates.items():
start_frame = template_data['start_frame']
end_frame = template_data['end_frame']
# Check if template covers this frame
if start_frame <= frame_number <= end_frame:
# Use the template with the highest ID (most recent)
if best_template_id is None or template_id > best_template_id:
best_template_id = template_id
return best_template_id
def _select_best_template_for_frame(self, frame_number):
"""Select the most recent template for the current frame"""
best_template_id = self.get_template_for_frame(frame_number)
if best_template_id is not None:
self.current_template_id = best_template_id
template_data = self.templates[best_template_id]
# Recreate template if it's None (loaded from state)
if template_data['template'] is None:
if self.current_display_frame is not None:
success = self._recreate_template_from_template_data(best_template_id, self.current_display_frame)
if not success:
return False
else:
return False
# Template is already stored in the templates dict
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.current_template_id = None
self.show_feedback_message("No template to remove")
return False
def _recreate_template_from_template_data(self, template_id, frame):
"""Recreate template from template data region"""
try:
template_data = self.templates[template_id]
x, y, w, h = template_data['region']
def get_template_for_frame(self, frame_number):
"""Get the template for the current frame"""
if not self.templates:
return None
# 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.templates[template_id]['template'] = template.copy()
return True
# 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 False
else:
return False
except Exception as e:
print(f"Error recreating template {template_id}: {e}")
return False
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()
template_frames = sorted([data['start_frame'] for data in self.templates.values()])
if not template_frames:
if not self.templates:
print("DEBUG: No template markers; prev jump ignored")
return
current = self.current_frame
candidates = [f for f in template_frames if f < current]
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}; template_frames={template_frames}")
print(f"DEBUG: Jump prev template from {current} -> {target}")
self.seek_to_frame(target)
else:
target = template_frames[0]
print(f"DEBUG: Jump prev template to first marker from {current} -> {target}; template_frames={template_frames}")
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):
@@ -2531,18 +2500,17 @@ class VideoEditor:
if self.is_image_mode:
return
self.stop_auto_repeat_seek()
template_frames = sorted([data['start_frame'] for data in self.templates.values()])
if not template_frames:
if not self.templates:
print("DEBUG: No template markers; next jump ignored")
return
current = self.current_frame
for f in template_frames:
if f > current:
print(f"DEBUG: Jump next template from {current} -> {f}; template_frames={template_frames}")
self.seek_to_frame(f)
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 = template_frames[-1]
print(f"DEBUG: Jump next template to last marker from {current} -> {target}; template_frames={template_frames}")
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):
@@ -3000,33 +2968,29 @@ class VideoEditor:
)
# Draw template markers
for template_id, template_data in self.templates.items():
start_frame = template_data['start_frame']
end_frame = template_data['end_frame']
# Draw template range
for start_frame, region, template_image in self.templates:
# Draw template start point
start_progress = start_frame / max(1, self.total_frames - 1)
end_progress = end_frame / max(1, self.total_frames - 1)
start_x = bar_x_start + int(bar_width * start_progress)
end_x = bar_x_start + int(bar_width * end_progress)
# Template range color (green for active, blue for inactive)
is_active = (self.current_template_id == template_id)
# 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 range bar
# Draw template start marker
cv2.rectangle(
frame,
(start_x, bar_y + 2),
(end_x, bar_y + self.TIMELINE_BAR_HEIGHT - 2),
(start_x + 4, bar_y + self.TIMELINE_BAR_HEIGHT - 2),
template_color,
-1,
)
# Draw template ID number
# Draw template number
cv2.putText(
frame,
str(template_id),
str(start_frame),
(start_x + 2, bar_y + 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.3,
@@ -3071,39 +3035,19 @@ 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)
# Simple fullscreen fix: scale down if video is too large for screen
if self.is_fullscreen and (frame_height > available_height or frame_width > self.window_width):
scale_x = self.window_width / frame_width
scale_y = available_height / frame_height
scale = min(scale_x, scale_y)
if scale < 1.0:
new_width = int(frame_width * scale)
new_height = int(frame_height * scale)
display_frame = cv2.resize(display_frame, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
frame_height, frame_width = display_frame.shape[:2]
start_y = (available_height - frame_height) // 2
start_x = (self.window_width - frame_width) // 2
@@ -3265,8 +3209,7 @@ class VideoEditor:
# Draw template matching point (blue circle with confidence)
if (not self.is_image_mode and
self.templates and
self.current_template_id 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:
@@ -3504,19 +3447,17 @@ class VideoEditor:
screen_x, screen_y = x, y
raw_x, raw_y = self._map_screen_to_rotated(screen_x, screen_y)
for template_id, template_data in self.templates.items():
# Only check templates that cover current frame
if (template_data['start_frame'] <= self.current_frame <= template_data['end_frame']):
tx, ty, tw, th = template_data['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 <= 10:
self.remove_template(template_id)
self.save_state()
return
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)
@@ -3747,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
@@ -4567,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()
@@ -4709,7 +4661,6 @@ class VideoEditor:
# Clear all templates
if self.templates:
self.templates.clear()
self.current_template_id = None
print("DEBUG: All templates cleared")
self.show_feedback_message("All templates cleared")
else: