feat: Add ClipSelectionWindow for user clip selection and enhance progress handling
This commit is contained in:
parent
0b25f6fef5
commit
8cc0d02473
462
Main.py
462
Main.py
@ -48,7 +48,7 @@ class ProgressWindow:
|
|||||||
maximum=100, length=370)
|
maximum=100, length=370)
|
||||||
|
|
||||||
# Cancel button
|
# Cancel button
|
||||||
self.cancel_btn = tk.Button(self.window, text="Cancel", command=self.cancel_operation)
|
self.cancel_btn = tk.Button(self.window, text="Cancel", command=self.cancel)
|
||||||
self.cancel_btn.pack(pady=(5,15))
|
self.cancel_btn.pack(pady=(5,15))
|
||||||
|
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
@ -96,7 +96,7 @@ class ProgressWindow:
|
|||||||
# Schedule the update on the main thread
|
# Schedule the update on the main thread
|
||||||
self.window.after(0, _update)
|
self.window.after(0, _update)
|
||||||
|
|
||||||
def cancel_operation(self):
|
def cancel(self):
|
||||||
"""Cancel the current operation"""
|
"""Cancel the current operation"""
|
||||||
self.cancelled = True
|
self.cancelled = True
|
||||||
self.window.destroy()
|
self.window.destroy()
|
||||||
@ -106,6 +106,137 @@ class ProgressWindow:
|
|||||||
if not self.cancelled:
|
if not self.cancelled:
|
||||||
self.window.destroy()
|
self.window.destroy()
|
||||||
|
|
||||||
|
class ClipSelectionWindow:
|
||||||
|
def __init__(self, parent, clips, video_path, detection_mode):
|
||||||
|
self.parent = parent
|
||||||
|
self.clips = clips
|
||||||
|
self.video_path = video_path
|
||||||
|
self.detection_mode = detection_mode
|
||||||
|
self.selected_clips = []
|
||||||
|
|
||||||
|
# Create window with parent's root
|
||||||
|
self.window = tk.Toplevel(parent.root)
|
||||||
|
self.window.title("Select Clips to Generate")
|
||||||
|
self.window.geometry("600x500")
|
||||||
|
self.window.resizable(True, True)
|
||||||
|
self.window.transient(parent.root)
|
||||||
|
self.window.grab_set()
|
||||||
|
|
||||||
|
# Center the window
|
||||||
|
self.window.update_idletasks()
|
||||||
|
x = (self.window.winfo_screenwidth() // 2) - (600 // 2)
|
||||||
|
y = (self.window.winfo_screenheight() // 2) - (500 // 2)
|
||||||
|
self.window.geometry(f"600x500+{x}+{y}")
|
||||||
|
|
||||||
|
self.setup_gui()
|
||||||
|
|
||||||
|
def setup_gui(self):
|
||||||
|
# Title
|
||||||
|
title_label = tk.Label(self.window, text=f"Found {len(self.clips)} clips using {self.detection_mode} detection",
|
||||||
|
font=("Arial", 12, "bold"))
|
||||||
|
title_label.pack(pady=10)
|
||||||
|
|
||||||
|
# Instructions
|
||||||
|
instruction_label = tk.Label(self.window,
|
||||||
|
text="Select the clips you want to generate (check the boxes):",
|
||||||
|
font=("Arial", 10))
|
||||||
|
instruction_label.pack(pady=(0,10))
|
||||||
|
|
||||||
|
# Clips list frame with scrollbar
|
||||||
|
list_frame = tk.Frame(self.window)
|
||||||
|
list_frame.pack(fill="both", expand=True, padx=20, pady=10)
|
||||||
|
|
||||||
|
# Scrollable frame
|
||||||
|
canvas = tk.Canvas(list_frame)
|
||||||
|
scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=canvas.yview)
|
||||||
|
scrollable_frame = tk.Frame(canvas)
|
||||||
|
|
||||||
|
scrollable_frame.bind(
|
||||||
|
"<Configure>",
|
||||||
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
||||||
|
canvas.configure(yscrollcommand=scrollbar.set)
|
||||||
|
|
||||||
|
# Clip checkboxes
|
||||||
|
self.clip_vars = []
|
||||||
|
for i, (start, end) in enumerate(self.clips):
|
||||||
|
var = tk.BooleanVar(value=True) # All selected by default
|
||||||
|
self.clip_vars.append(var)
|
||||||
|
|
||||||
|
duration = end - start
|
||||||
|
clip_frame = tk.Frame(scrollable_frame, relief="ridge", bd=1)
|
||||||
|
clip_frame.pack(fill="x", pady=2, padx=5)
|
||||||
|
|
||||||
|
checkbox = tk.Checkbutton(clip_frame, variable=var, text="", width=2)
|
||||||
|
checkbox.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
info_label = tk.Label(clip_frame,
|
||||||
|
text=f"Clip {i+1}: {start:.1f}s - {end:.1f}s (Duration: {duration:.1f}s)",
|
||||||
|
font=("Arial", 10), anchor="w")
|
||||||
|
info_label.pack(side="left", fill="x", expand=True, padx=5)
|
||||||
|
|
||||||
|
canvas.pack(side="left", fill="both", expand=True)
|
||||||
|
scrollbar.pack(side="right", fill="y")
|
||||||
|
|
||||||
|
# Selection buttons
|
||||||
|
button_frame = tk.Frame(self.window)
|
||||||
|
button_frame.pack(fill="x", padx=20, pady=10)
|
||||||
|
|
||||||
|
select_all_btn = tk.Button(button_frame, text="Select All", command=self.select_all)
|
||||||
|
select_all_btn.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
select_none_btn = tk.Button(button_frame, text="Select None", command=self.select_none)
|
||||||
|
select_none_btn.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# Generate button
|
||||||
|
action_frame = tk.Frame(self.window)
|
||||||
|
action_frame.pack(fill="x", padx=20, pady=10)
|
||||||
|
|
||||||
|
cancel_btn = tk.Button(action_frame, text="Cancel", command=self.cancel, bg="#f44336", fg="white")
|
||||||
|
cancel_btn.pack(side="right", padx=5)
|
||||||
|
|
||||||
|
generate_selected_btn = tk.Button(action_frame, text="Generate Selected Clips",
|
||||||
|
command=self.generate_selected, bg="#4CAF50", fg="white",
|
||||||
|
font=("Arial", 10, "bold"))
|
||||||
|
generate_selected_btn.pack(side="right", padx=5)
|
||||||
|
|
||||||
|
def select_all(self):
|
||||||
|
"""Select all clips"""
|
||||||
|
for var in self.clip_vars:
|
||||||
|
var.set(True)
|
||||||
|
|
||||||
|
def select_none(self):
|
||||||
|
"""Deselect all clips"""
|
||||||
|
for var in self.clip_vars:
|
||||||
|
var.set(False)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
"""Cancel selection"""
|
||||||
|
self.window.destroy()
|
||||||
|
|
||||||
|
def generate_selected(self):
|
||||||
|
"""Generate the selected clips"""
|
||||||
|
# Get selected clips
|
||||||
|
self.selected_clips = []
|
||||||
|
for i, var in enumerate(self.clip_vars):
|
||||||
|
if var.get():
|
||||||
|
self.selected_clips.append(self.clips[i])
|
||||||
|
|
||||||
|
if not self.selected_clips:
|
||||||
|
messagebox.showwarning("No Selection", "Please select at least one clip to generate.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Close selection window
|
||||||
|
self.window.destroy()
|
||||||
|
|
||||||
|
# Trigger generation with selected clips
|
||||||
|
if hasattr(self.parent, 'generate_selected_clips'):
|
||||||
|
self.parent.generate_selected_clips(self.selected_clips)
|
||||||
|
else:
|
||||||
|
messagebox.showerror("Error", "Generation method not available.")
|
||||||
|
|
||||||
class MainApplication:
|
class MainApplication:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.root = tk.Tk()
|
self.root = tk.Tk()
|
||||||
@ -181,14 +312,6 @@ class MainApplication:
|
|||||||
tk.Radiobutton(row_frame, text=text, variable=self.detection_var, value=value,
|
tk.Radiobutton(row_frame, text=text, variable=self.detection_var, value=value,
|
||||||
bg="#f0f0f0").pack(side="left", padx=10)
|
bg="#f0f0f0").pack(side="left", padx=10)
|
||||||
|
|
||||||
# Number of clips
|
|
||||||
clips_frame = tk.Frame(settings_frame, bg="#f0f0f0")
|
|
||||||
clips_frame.pack(fill="x", pady=5)
|
|
||||||
tk.Label(clips_frame, text="Max Clips:", bg="#f0f0f0").pack(side="left")
|
|
||||||
self.clips_var = tk.IntVar(value=3)
|
|
||||||
clips_spinbox = tk.Spinbox(clips_frame, from_=1, to=10, textvariable=self.clips_var, width=5)
|
|
||||||
clips_spinbox.pack(side="left", padx=10)
|
|
||||||
|
|
||||||
# Main action buttons
|
# Main action buttons
|
||||||
button_frame = tk.Frame(self.root, bg="#f0f0f0")
|
button_frame = tk.Frame(self.root, bg="#f0f0f0")
|
||||||
button_frame.pack(pady=20, padx=20, fill="x")
|
button_frame.pack(pady=20, padx=20, fill="x")
|
||||||
@ -200,11 +323,16 @@ class MainApplication:
|
|||||||
self.preview_btn.pack(fill="x", pady=5)
|
self.preview_btn.pack(fill="x", pady=5)
|
||||||
|
|
||||||
# Generate Shorts Button
|
# Generate Shorts Button
|
||||||
self.generate_btn = tk.Button(button_frame, text="🎬 Generate Shorts",
|
self.generate_btn = tk.Button(button_frame, text="🎬 Generate All Detected Clips",
|
||||||
command=self.generate_shorts_threaded, bg="#4CAF50", fg="white",
|
command=self.generate_shorts_threaded, bg="#4CAF50", fg="white",
|
||||||
font=("Arial", 12, "bold"), pady=10)
|
font=("Arial", 12, "bold"), pady=10)
|
||||||
self.generate_btn.pack(fill="x", pady=5)
|
self.generate_btn.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
# Info label
|
||||||
|
info_label = tk.Label(button_frame, text="💡 Tip: Use 'Preview Clips' to select specific clips for faster processing",
|
||||||
|
font=("Arial", 9), fg="gray", bg="#f0f0f0")
|
||||||
|
info_label.pack(pady=(5,10))
|
||||||
|
|
||||||
# Edit Generated Shorts Button
|
# Edit Generated Shorts Button
|
||||||
self.edit_btn = tk.Button(button_frame, text="✏️ Edit Generated Shorts",
|
self.edit_btn = tk.Button(button_frame, text="✏️ Edit Generated Shorts",
|
||||||
command=self.open_editor, bg="#FF9800", fg="white",
|
command=self.open_editor, bg="#FF9800", fg="white",
|
||||||
@ -222,6 +350,9 @@ class MainApplication:
|
|||||||
font=("Arial", 9), fg="gray", bg="#f0f0f0")
|
font=("Arial", 9), fg="gray", bg="#f0f0f0")
|
||||||
self.status_label.pack(pady=(20,10))
|
self.status_label.pack(pady=(20,10))
|
||||||
|
|
||||||
|
# Store detected clips for selection
|
||||||
|
self.detected_clips = []
|
||||||
|
|
||||||
def select_video_file(self):
|
def select_video_file(self):
|
||||||
"""Select video file"""
|
"""Select video file"""
|
||||||
filetypes = [
|
filetypes = [
|
||||||
@ -271,15 +402,24 @@ class MainApplication:
|
|||||||
validate_video)
|
validate_video)
|
||||||
|
|
||||||
video_path = self.shorts_generator.video_path
|
video_path = self.shorts_generator.video_path
|
||||||
clip_duration = self.clips_var.get() # Use our own duration setting
|
clip_duration = 5 # Fixed clip duration since we removed the setting
|
||||||
|
detection_mode = self.detection_var.get()
|
||||||
|
|
||||||
# Thread-safe progress callbacks
|
# Thread-safe progress callbacks with cancellation checks
|
||||||
def progress_callback(message, percent):
|
def progress_callback(message, percent):
|
||||||
|
if progress_window.cancelled:
|
||||||
|
raise InterruptedError("Preview cancelled by user")
|
||||||
self.root.after(0, lambda: progress_window.update_progress(message, percent))
|
self.root.after(0, lambda: progress_window.update_progress(message, percent))
|
||||||
|
|
||||||
def detection_callback(message, percent):
|
def detection_callback(message, percent):
|
||||||
|
if progress_window.cancelled:
|
||||||
|
raise InterruptedError("Preview cancelled by user")
|
||||||
self.root.after(0, lambda: progress_window.update_detection_progress(message, percent))
|
self.root.after(0, lambda: progress_window.update_detection_progress(message, percent))
|
||||||
|
|
||||||
|
# Check for cancellation before starting
|
||||||
|
if progress_window.cancelled:
|
||||||
|
return
|
||||||
|
|
||||||
# Validate video first
|
# Validate video first
|
||||||
self.root.after(0, lambda: progress_callback("Validating video...", 5))
|
self.root.after(0, lambda: progress_callback("Validating video...", 5))
|
||||||
validate_video(video_path, min_duration=clip_duration * 2)
|
validate_video(video_path, min_duration=clip_duration * 2)
|
||||||
@ -307,25 +447,36 @@ class MainApplication:
|
|||||||
else:
|
else:
|
||||||
moments = detect_loud_moments(video_path, chunk_duration=clip_duration, threshold_db=-30)
|
moments = detect_loud_moments(video_path, chunk_duration=clip_duration, threshold_db=-30)
|
||||||
|
|
||||||
self.root.after(0, lambda: progress_callback("Analysis complete!", 90))
|
# Final progress updates without cancellation checks (process is nearly complete)
|
||||||
|
if not progress_window.cancelled:
|
||||||
|
self.root.after(0, lambda: progress_window.update_progress("Analysis complete!", 90))
|
||||||
|
|
||||||
# Show results
|
# Show results with selection window
|
||||||
def show_results():
|
def show_results():
|
||||||
|
if progress_window.cancelled:
|
||||||
|
return
|
||||||
if moments:
|
if moments:
|
||||||
result_msg = f"Found {len(moments)} interesting moments:\n\n"
|
self.detected_clips = moments # Store for potential generation
|
||||||
for i, (start, end) in enumerate(moments[:10], 1): # Show first 10
|
|
||||||
result_msg += f"{i}. {start:.1f}s - {end:.1f}s ({end-start:.1f}s)\n"
|
|
||||||
if len(moments) > 10:
|
|
||||||
result_msg += f"\n... and {len(moments)-10} more moments"
|
|
||||||
messagebox.showinfo("Preview Results", result_msg)
|
|
||||||
else:
|
|
||||||
messagebox.showwarning("No Results", "No interesting moments found with current settings.")
|
|
||||||
|
|
||||||
progress_window.close()
|
progress_window.close()
|
||||||
|
|
||||||
self.root.after(0, lambda: progress_callback("Preparing results...", 100))
|
# Show clip selection window
|
||||||
|
ClipSelectionWindow(self, moments, video_path, detection_mode)
|
||||||
|
else:
|
||||||
|
progress_window.close()
|
||||||
|
messagebox.showwarning("No Results", "No interesting moments found with current settings.")
|
||||||
|
|
||||||
|
if not progress_window.cancelled:
|
||||||
|
self.root.after(0, lambda: progress_window.update_progress("Preparing results...", 100))
|
||||||
self.root.after(500, show_results)
|
self.root.after(500, show_results)
|
||||||
|
|
||||||
|
except InterruptedError:
|
||||||
|
# Preview was cancelled by user
|
||||||
|
def show_cancelled():
|
||||||
|
progress_window.close()
|
||||||
|
# Don't show an error message for user cancellation
|
||||||
|
|
||||||
|
self.root.after(0, show_cancelled)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
def show_error():
|
def show_error():
|
||||||
@ -359,22 +510,28 @@ class MainApplication:
|
|||||||
from shorts_generator2 import generate_shorts
|
from shorts_generator2 import generate_shorts
|
||||||
|
|
||||||
video_path = self.shorts_generator.video_path
|
video_path = self.shorts_generator.video_path
|
||||||
max_clips = self.clips_var.get()
|
detection_mode = self.detection_var.get()
|
||||||
clip_duration = self.clips_var.get() # Using clips_var as duration for simplicity
|
clip_duration = 5 # Default duration
|
||||||
|
|
||||||
# Thread-safe progress callbacks
|
# Thread-safe progress callbacks with cancellation checks
|
||||||
def progress_callback(message, percent):
|
def progress_callback(message, percent):
|
||||||
if not progress_window.cancelled:
|
if progress_window.cancelled:
|
||||||
|
raise InterruptedError("Generation cancelled by user")
|
||||||
self.root.after(0, lambda: progress_window.update_progress(message, percent))
|
self.root.after(0, lambda: progress_window.update_progress(message, percent))
|
||||||
|
|
||||||
def detection_callback(message, percent):
|
def detection_callback(message, percent):
|
||||||
if not progress_window.cancelled:
|
if progress_window.cancelled:
|
||||||
|
raise InterruptedError("Generation cancelled by user")
|
||||||
self.root.after(0, lambda: progress_window.update_detection_progress(message, percent))
|
self.root.after(0, lambda: progress_window.update_detection_progress(message, percent))
|
||||||
|
|
||||||
# Run the actual generation
|
# Check for cancellation before starting
|
||||||
|
if progress_window.cancelled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Run the actual generation (no max clips limit - user will select from preview)
|
||||||
generate_shorts(
|
generate_shorts(
|
||||||
video_path,
|
video_path,
|
||||||
max_clips=max_clips,
|
max_clips=50, # Generate a reasonable number for preview/selection
|
||||||
output_folder="shorts",
|
output_folder="shorts",
|
||||||
progress_callback=progress_callback,
|
progress_callback=progress_callback,
|
||||||
detection_progress_callback=detection_callback,
|
detection_progress_callback=detection_callback,
|
||||||
@ -385,10 +542,18 @@ class MainApplication:
|
|||||||
|
|
||||||
def show_success():
|
def show_success():
|
||||||
progress_window.close()
|
progress_window.close()
|
||||||
messagebox.showinfo("Success", f"Successfully generated {max_clips} shorts!\n\nCheck the 'shorts' folder for your videos.")
|
messagebox.showinfo("Success", "Successfully generated shorts!\n\nCheck the 'shorts' folder for your videos.")
|
||||||
|
|
||||||
self.root.after(0, show_success)
|
self.root.after(0, show_success)
|
||||||
|
|
||||||
|
except InterruptedError:
|
||||||
|
# Generation was cancelled by user
|
||||||
|
def show_cancelled():
|
||||||
|
progress_window.close()
|
||||||
|
# Don't show an error message for user cancellation
|
||||||
|
|
||||||
|
self.root.after(0, show_cancelled)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
def show_error():
|
def show_error():
|
||||||
@ -400,27 +565,252 @@ class MainApplication:
|
|||||||
# Start the generation in a background thread
|
# Start the generation in a background thread
|
||||||
threading.Thread(target=run_generation, daemon=True).start()
|
threading.Thread(target=run_generation, daemon=True).start()
|
||||||
|
|
||||||
|
def generate_selected_clips(self, selected_clips):
|
||||||
|
"""Generate only the selected clips"""
|
||||||
|
if not self.shorts_generator or not self.shorts_generator.video_path:
|
||||||
|
messagebox.showerror("Error", "No video selected.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create progress window
|
||||||
|
progress_window = ProgressWindow(self.root, "Generating Selected Clips")
|
||||||
|
|
||||||
|
def run_selected_generation():
|
||||||
|
try:
|
||||||
|
video_path = self.shorts_generator.video_path
|
||||||
|
|
||||||
|
# Thread-safe progress callback with cancellation checks
|
||||||
|
def progress_callback(message, percent):
|
||||||
|
if progress_window.cancelled:
|
||||||
|
raise InterruptedError("Generation cancelled by user")
|
||||||
|
self.root.after(0, lambda: progress_window.update_progress(message, percent))
|
||||||
|
|
||||||
|
# Check for cancellation before starting
|
||||||
|
if progress_window.cancelled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Use a custom generation function for selected clips
|
||||||
|
self.generate_clips_from_moments(
|
||||||
|
video_path,
|
||||||
|
selected_clips,
|
||||||
|
progress_callback,
|
||||||
|
progress_window
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_success():
|
||||||
|
progress_window.close()
|
||||||
|
messagebox.showinfo("Success", f"Successfully generated {len(selected_clips)} selected clips!\n\nCheck the 'shorts' folder for your videos.")
|
||||||
|
|
||||||
|
self.root.after(0, show_success)
|
||||||
|
|
||||||
|
except InterruptedError:
|
||||||
|
# Generation was cancelled by user
|
||||||
|
def show_cancelled():
|
||||||
|
progress_window.close()
|
||||||
|
# Don't show an error message for user cancellation
|
||||||
|
|
||||||
|
self.root.after(0, show_cancelled)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
def show_error():
|
||||||
|
progress_window.close()
|
||||||
|
messagebox.showerror("Generation Error", f"An error occurred during generation:\n{error_msg}")
|
||||||
|
|
||||||
|
self.root.after(0, show_error)
|
||||||
|
|
||||||
|
# Start the generation in a background thread
|
||||||
|
threading.Thread(target=run_selected_generation, daemon=True).start()
|
||||||
|
|
||||||
|
def generate_clips_from_moments(self, video_path, moments, progress_callback, progress_window):
|
||||||
|
"""Generate video clips from specific moments"""
|
||||||
|
try:
|
||||||
|
import os
|
||||||
|
from moviepy.editor import VideoFileClip
|
||||||
|
|
||||||
|
os.makedirs("shorts", exist_ok=True)
|
||||||
|
|
||||||
|
total_clips = len(moments)
|
||||||
|
for i, (start, end) in enumerate(moments):
|
||||||
|
if progress_window.cancelled:
|
||||||
|
break
|
||||||
|
|
||||||
|
progress_callback(f"Processing clip {i+1}/{total_clips}...", (i/total_clips) * 90)
|
||||||
|
|
||||||
|
# Create clip
|
||||||
|
with VideoFileClip(video_path) as video:
|
||||||
|
clip = video.subclipped(start, end)
|
||||||
|
output_path = os.path.join("shorts", f"short_{i+1}.mp4")
|
||||||
|
clip.write_videofile(output_path, verbose=False, logger=None)
|
||||||
|
clip.close()
|
||||||
|
|
||||||
|
progress_callback("All clips generated successfully!", 100)
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
# MoviePy not available, use alternative approach
|
||||||
|
progress_callback("Using alternative generation method...", 10)
|
||||||
|
|
||||||
|
# Check for cancellation before starting
|
||||||
|
if progress_window.cancelled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Import and use the existing generate_shorts function
|
||||||
|
from shorts_generator2 import generate_shorts
|
||||||
|
|
||||||
|
# Create a temporary callback that updates our progress window and checks for cancellation
|
||||||
|
def alt_progress_callback(message, percent):
|
||||||
|
if progress_window.cancelled:
|
||||||
|
# Try to indicate cancellation to the calling function
|
||||||
|
raise InterruptedError("Generation cancelled by user")
|
||||||
|
progress_callback(message, percent)
|
||||||
|
|
||||||
|
def alt_detection_callback(message, percent):
|
||||||
|
if progress_window.cancelled:
|
||||||
|
raise InterruptedError("Generation cancelled by user")
|
||||||
|
progress_callback(f"Detection: {message}", percent)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use generate_shorts with the exact number of selected clips
|
||||||
|
# This ensures we generate exactly what the user selected
|
||||||
|
generate_shorts(
|
||||||
|
video_path,
|
||||||
|
max_clips=len(moments), # Generate exactly the number of selected clips
|
||||||
|
output_folder="shorts",
|
||||||
|
progress_callback=alt_progress_callback,
|
||||||
|
detection_progress_callback=alt_detection_callback,
|
||||||
|
threshold_db=-30,
|
||||||
|
clip_duration=5,
|
||||||
|
detection_mode="loud" # Default detection mode
|
||||||
|
)
|
||||||
|
except InterruptedError:
|
||||||
|
# Generation was cancelled
|
||||||
|
progress_callback("Generation cancelled by user", 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
progress_callback(f"Error: {str(e)}", 100)
|
||||||
|
raise
|
||||||
|
|
||||||
def update_shorts_generator_settings(self):
|
def update_shorts_generator_settings(self):
|
||||||
"""Update the shorts generator with current settings"""
|
"""Update the shorts generator with current settings"""
|
||||||
if self.shorts_generator:
|
if self.shorts_generator:
|
||||||
self.shorts_generator.detection_mode_var.set(self.detection_var.get())
|
self.shorts_generator.detection_mode_var.set(self.detection_var.get())
|
||||||
self.shorts_generator.clips_var.set(self.clips_var.get())
|
|
||||||
|
|
||||||
def open_editor(self):
|
def open_editor(self):
|
||||||
"""Open the shorts editor"""
|
"""Open the shorts editor"""
|
||||||
|
print("DEBUG: open_editor called")
|
||||||
if self.shorts_generator:
|
if self.shorts_generator:
|
||||||
|
print("DEBUG: shorts_generator exists")
|
||||||
try:
|
try:
|
||||||
self.shorts_generator.open_shorts_editor()
|
print("DEBUG: Attempting to call open_shorts_editor()")
|
||||||
|
if hasattr(self.shorts_generator, 'open_shorts_editor'):
|
||||||
|
print("DEBUG: open_shorts_editor method exists")
|
||||||
|
print(f"DEBUG: shorts_generator root: {self.shorts_generator.root}")
|
||||||
|
print(f"DEBUG: shorts_generator output_folder: {getattr(self.shorts_generator, 'output_folder', 'NOT SET')}")
|
||||||
|
|
||||||
|
# Create the editor with the main window as parent instead of hidden root
|
||||||
|
from shorts_generator2 import ShortsEditorGUI
|
||||||
|
editor = ShortsEditorGUI(self.root, self.shorts_generator.output_folder)
|
||||||
|
editor.open_editor()
|
||||||
|
|
||||||
|
print("DEBUG: Editor opened successfully")
|
||||||
|
else:
|
||||||
|
print("DEBUG: open_shorts_editor method does NOT exist")
|
||||||
|
messagebox.showerror("Editor Error", "The open_shorts_editor method is not available in ShortsGeneratorGUI")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(f"DEBUG: Exception in open_editor: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
messagebox.showerror("Editor Error", f"Could not open editor: {e}")
|
messagebox.showerror("Editor Error", f"Could not open editor: {e}")
|
||||||
|
else:
|
||||||
|
print("DEBUG: shorts_generator is None")
|
||||||
|
messagebox.showerror("Editor Error", "ShortsGeneratorGUI is not initialized")
|
||||||
|
|
||||||
def open_thumbnails(self):
|
def open_thumbnails(self):
|
||||||
"""Open the thumbnail editor"""
|
"""Open the thumbnail editor"""
|
||||||
|
print("DEBUG: open_thumbnails called")
|
||||||
if self.shorts_generator:
|
if self.shorts_generator:
|
||||||
|
print("DEBUG: shorts_generator exists")
|
||||||
try:
|
try:
|
||||||
self.shorts_generator.open_thumbnail_editor()
|
print("DEBUG: Attempting to call open_thumbnail_editor()")
|
||||||
|
if hasattr(self.shorts_generator, 'open_thumbnail_editor'):
|
||||||
|
print("DEBUG: open_thumbnail_editor method exists")
|
||||||
|
|
||||||
|
# Call the method directly but handle the parent window issue
|
||||||
|
# Let's import and call the thumbnail editor function directly
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
# Check if there are any video files to work with
|
||||||
|
video_files = []
|
||||||
|
|
||||||
|
# Check for original video
|
||||||
|
if self.shorts_generator.video_path:
|
||||||
|
video_files.append(("Original Video", self.shorts_generator.video_path))
|
||||||
|
|
||||||
|
# Check for generated shorts
|
||||||
|
if os.path.exists(self.shorts_generator.output_folder):
|
||||||
|
shorts = glob.glob(os.path.join(self.shorts_generator.output_folder, "*.mp4"))
|
||||||
|
for short in shorts:
|
||||||
|
video_files.append((os.path.basename(short), short))
|
||||||
|
|
||||||
|
if not video_files:
|
||||||
|
messagebox.showinfo("No Videos Found",
|
||||||
|
"Please select a video or generate some shorts first!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# If only one video, open it directly
|
||||||
|
if len(video_files) == 1:
|
||||||
|
selected_video = video_files[0][1]
|
||||||
|
else:
|
||||||
|
# Let user choose which video to edit
|
||||||
|
choice_window = tk.Toplevel(self.root)
|
||||||
|
choice_window.title("Select Video for Thumbnail")
|
||||||
|
choice_window.geometry("400x300")
|
||||||
|
choice_window.transient(self.root)
|
||||||
|
choice_window.grab_set()
|
||||||
|
|
||||||
|
tk.Label(choice_window, text="📸 Select Video for Thumbnail Creation",
|
||||||
|
font=("Arial", 12, "bold")).pack(pady=10)
|
||||||
|
|
||||||
|
selected_video = None
|
||||||
|
|
||||||
|
def on_video_select(video_path):
|
||||||
|
nonlocal selected_video
|
||||||
|
selected_video = video_path
|
||||||
|
choice_window.destroy()
|
||||||
|
|
||||||
|
# Create list of videos
|
||||||
|
for display_name, video_path in video_files:
|
||||||
|
btn = tk.Button(choice_window, text=f"📹 {display_name}",
|
||||||
|
command=lambda vp=video_path: on_video_select(vp),
|
||||||
|
font=("Arial", 10), pady=5, width=40)
|
||||||
|
btn.pack(pady=2, padx=20, fill="x")
|
||||||
|
|
||||||
|
tk.Button(choice_window, text="Cancel",
|
||||||
|
command=choice_window.destroy).pack(pady=10)
|
||||||
|
|
||||||
|
# Wait for selection
|
||||||
|
choice_window.wait_window()
|
||||||
|
|
||||||
|
if not selected_video:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Import and open thumbnail editor
|
||||||
|
from thumbnail_editor import open_thumbnail_editor
|
||||||
|
open_thumbnail_editor(selected_video)
|
||||||
|
|
||||||
|
print("DEBUG: Thumbnail editor opened successfully")
|
||||||
|
else:
|
||||||
|
print("DEBUG: open_thumbnail_editor method does NOT exist")
|
||||||
|
messagebox.showerror("Thumbnail Error", "The open_thumbnail_editor method is not available in ShortsGeneratorGUI")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(f"DEBUG: Exception in open_thumbnails: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
messagebox.showerror("Thumbnail Error", f"Could not open thumbnail editor: {e}")
|
messagebox.showerror("Thumbnail Error", f"Could not open thumbnail editor: {e}")
|
||||||
|
else:
|
||||||
|
print("DEBUG: shorts_generator is None")
|
||||||
|
messagebox.showerror("Thumbnail Error", "ShortsGeneratorGUI is not initialized")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Start the main application"""
|
"""Start the main application"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user