import tkinter as tk from tkinter import ttk, filedialog, messagebox import threading import time import os import sys # Import the ShortsGeneratorGUI from shorts_generator2.py try: from shorts_generator2 import ShortsGeneratorGUI except ImportError: messagebox.showerror("Error", "Could not import ShortsGeneratorGUI from shorts_generator2.py") sys.exit(1) class ProgressWindow: def __init__(self, parent, title="Processing"): self.parent = parent self.window = tk.Toplevel(parent) self.window.title(title) self.window.geometry("400x160") self.window.minsize(350, 140) # Set minimum size self.window.resizable(True, False) # Allow horizontal resize only self.window.transient(parent) self.window.grab_set() # Make window responsive self.window.columnconfigure(0, weight=1) # Center the window self.window.update_idletasks() x = (self.window.winfo_screenwidth() // 2) - (400 // 2) y = (self.window.winfo_screenheight() // 2) - (160 // 2) self.window.geometry(f"400x160+{x}+{y}") # Bind resize event self.window.bind('', self.on_window_resize) # Create progress widgets with responsive layout main_frame = tk.Frame(self.window) main_frame.pack(fill="both", expand=True, padx=15, pady=15) main_frame.columnconfigure(0, weight=1) self.status_label = tk.Label(main_frame, text="Initializing...", anchor="w", font=("Arial", 10)) self.status_label.grid(row=0, column=0, sticky="ew", pady=(0, 5)) self.time_label = tk.Label(main_frame, text="Elapsed: 0.0s | Remaining: --s", anchor="w", font=("Arial", 9), fg="gray") self.time_label.grid(row=1, column=0, sticky="ew", pady=(0, 5)) # Main progress bar self.progress_var = tk.DoubleVar() self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100) self.progress_bar.grid(row=2, column=0, sticky="ew", pady=(5, 3)) # Detection progress bar (hidden by default) self.detection_label = tk.Label(main_frame, text="", anchor="w", font=("Arial", 9), fg="blue") self.detection_progress_var = tk.DoubleVar() self.detection_progress_bar = ttk.Progressbar(main_frame, variable=self.detection_progress_var, maximum=100) # Cancel button self.cancel_btn = tk.Button(main_frame, text="Cancel", command=self.cancel) self.cancel_btn.grid(row=5, column=0, pady=(5, 0)) self.start_time = time.time() self.cancelled = False def on_window_resize(self, event): """Handle window resize events""" if event.widget == self.window: # Update progress bar length based on window width width = self.window.winfo_width() progress_length = max(250, width - 80) # Minimum 250px, with padding try: self.progress_bar.config(length=progress_length) self.detection_progress_bar.config(length=progress_length) except: pass def show_detection_progress(self): """Show the detection progress bar - thread safe""" def _show(): self.detection_label.grid(row=3, column=0, sticky="ew", pady=(3, 0)) self.detection_progress_bar.grid(row=4, column=0, sticky="ew", pady=(3, 5)) self.window.after(0, _show) def hide_detection_progress(self): """Hide the detection progress bar - thread safe""" def _hide(): self.detection_label.grid_remove() self.detection_progress_bar.grid_remove() self.window.after(0, _hide) def update_progress(self, message, percent): """Update main progress bar - thread safe""" def _update(): if self.cancelled: return self.status_label.config(text=message) self.progress_var.set(percent) elapsed = time.time() - self.start_time if percent > 0: remaining = (elapsed / percent) * (100 - percent) self.time_label.config(text=f"Elapsed: {elapsed:.1f}s | Remaining: {remaining:.1f}s") # Schedule the update on the main thread self.window.after(0, _update) def update_detection_progress(self, message, percent): """Update detection progress bar - thread safe""" def _update(): if self.cancelled: return self.detection_label.config(text=message) self.detection_progress_var.set(percent) # Schedule the update on the main thread self.window.after(0, _update) def cancel(self): """Cancel the current operation""" self.cancelled = True self.window.destroy() def close(self): """Close the progress window""" if not self.cancelled: 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.minsize(400, 350) # Set minimum size self.window.resizable(True, True) self.window.transient(parent.root) self.window.grab_set() # Make window responsive self.window.rowconfigure(2, weight=1) # Clips list expandable self.window.columnconfigure(0, weight=1) # 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}") # Bind resize event self.window.bind('', self.on_window_resize) self.setup_gui() def setup_gui(self): # Create main container main_container = tk.Frame(self.window) main_container.pack(fill="both", expand=True, padx=20, pady=10) # Make container responsive main_container.rowconfigure(2, weight=1) # List area expandable main_container.columnconfigure(0, weight=1) # Title title_label = tk.Label(main_container, text=f"Found {len(self.clips)} clips using {self.detection_mode} detection", font=("Arial", 12, "bold")) title_label.grid(row=0, column=0, pady=(0, 10), sticky="ew") # Instructions self.instruction_label = tk.Label(main_container, text="Select the clips you want to generate (check the boxes):", font=("Arial", 10), wraplength=400) self.instruction_label.grid(row=1, column=0, pady=(0, 10), sticky="ew") # Clips list frame with scrollbar list_frame = tk.Frame(main_container) list_frame.grid(row=2, column=0, sticky="nsew") list_frame.rowconfigure(0, weight=1) list_frame.columnconfigure(0, weight=1) # 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( "", 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) clip_frame.columnconfigure(1, weight=1) checkbox = tk.Checkbutton(clip_frame, variable=var, text="", width=2) checkbox.grid(row=0, column=0, padx=5, sticky="w") 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.grid(row=0, column=1, padx=5, sticky="ew") canvas.grid(row=0, column=0, sticky="nsew") scrollbar.grid(row=0, column=1, sticky="ns") # Selection buttons button_frame = tk.Frame(main_container) button_frame.grid(row=3, column=0, pady=10, sticky="ew") button_frame.columnconfigure(0, weight=1) button_frame.columnconfigure(1, weight=1) select_all_btn = tk.Button(button_frame, text="Select All", command=self.select_all) select_all_btn.grid(row=0, column=0, padx=(0, 5), sticky="ew") select_none_btn = tk.Button(button_frame, text="Select None", command=self.select_none) select_none_btn.grid(row=0, column=1, padx=(5, 0), sticky="ew") # Generate button action_frame = tk.Frame(main_container) action_frame.grid(row=4, column=0, pady=10, sticky="ew") action_frame.columnconfigure(0, weight=1) action_frame.columnconfigure(1, weight=1) cancel_btn = tk.Button(action_frame, text="Cancel", command=self.cancel, bg="#f44336", fg="white") cancel_btn.grid(row=0, column=1, padx=(5, 0), sticky="ew") 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.grid(row=0, column=0, padx=(0, 5), sticky="ew") def on_window_resize(self, event): """Handle window resize events""" if event.widget == self.window: # Get current window size width = self.window.winfo_width() # Adjust wrap length for instruction text wrap_length = max(300, width - 100) try: self.instruction_label.config(wraplength=wrap_length) except: pass 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: def __init__(self): self.root = tk.Tk() self.root.title("AI Shorts Generator - Main Controller") self.root.geometry("500x600") self.root.minsize(400, 500) # Set minimum size self.root.configure(bg="#f0f0f0") # Make window responsive self.root.rowconfigure(0, weight=1) self.root.columnconfigure(0, weight=1) # Initialize the ShortsGeneratorGUI (but don't show its window) self.shorts_generator = None self.init_shorts_generator() self.setup_gui() # Bind resize event for responsive updates self.root.bind('', self.on_window_resize) def init_shorts_generator(self): """Initialize the ShortsGeneratorGUI without showing its window""" try: # Create a hidden root for ShortsGeneratorGUI hidden_root = tk.Tk() hidden_root.withdraw() # Hide the window # Create ShortsGeneratorGUI instance self.shorts_generator = ShortsGeneratorGUI(hidden_root) # Don't show the original window hidden_root.withdraw() except Exception as e: messagebox.showerror("Initialization Error", f"Failed to initialize ShortsGeneratorGUI: {e}") self.shorts_generator = None def setup_gui(self): """Setup the main GUI with responsive design""" # Create main container that fills the window main_container = tk.Frame(self.root, bg="#f0f0f0") main_container.pack(fill="both", expand=True, padx=10, pady=10) # Make main container responsive main_container.rowconfigure(1, weight=1) # File frame expandable main_container.rowconfigure(2, weight=1) # Settings frame expandable main_container.rowconfigure(3, weight=2) # Button frame gets more space main_container.columnconfigure(0, weight=1) # Title title_label = tk.Label(main_container, text="🎬 AI Shorts Generator", font=("Arial", 16, "bold"), bg="#f0f0f0", fg="#2c3e50") title_label.grid(row=0, column=0, pady=(0, 20), sticky="ew") # File selection frame file_frame = tk.Frame(main_container, bg="#f0f0f0") file_frame.grid(row=1, column=0, pady=10, sticky="ew") file_frame.columnconfigure(0, weight=1) # Make expandable tk.Label(file_frame, text="Selected Video:", font=("Arial", 10, "bold"), bg="#f0f0f0").grid(row=0, column=0, sticky="w") self.file_label = tk.Label(file_frame, text="No video selected", font=("Arial", 9), bg="white", relief="sunken", anchor="w", pady=5, padx=10) self.file_label.grid(row=1, column=0, pady=(5,10), sticky="ew") # File selection button select_btn = tk.Button(file_frame, text="📁 Select Video File", command=self.select_video_file, bg="#3498db", fg="white", font=("Arial", 10, "bold"), pady=5) select_btn.grid(row=2, column=0, pady=5, sticky="ew") # Settings frame (simplified) settings_frame = tk.LabelFrame(main_container, text="Quick Settings", font=("Arial", 10, "bold"), bg="#f0f0f0", padx=10, pady=10) settings_frame.grid(row=2, column=0, pady=10, sticky="ew") settings_frame.columnconfigure(0, weight=1) # Make expandable # Detection mode tk.Label(settings_frame, text="Detection Mode:", bg="#f0f0f0").grid(row=0, column=0, sticky="w") self.detection_var = tk.StringVar(value="loud") detection_container = tk.Frame(settings_frame, bg="#f0f0f0") detection_container.grid(row=1, column=0, pady=5, sticky="ew") detection_container.columnconfigure(0, weight=1) detection_container.columnconfigure(1, weight=1) detection_container.columnconfigure(2, weight=1) modes = [("Loud Moments", "loud"), ("Scene Changes", "scene"), ("Motion", "motion"), ("Speech", "speech"), ("Audio Peaks", "peaks"), ("Combined", "combined")] # Create responsive grid for radio buttons for i, (text, value) in enumerate(modes): row = i // 3 col = i % 3 if row >= detection_container.grid_size()[1]: detection_container.rowconfigure(row, weight=1) tk.Radiobutton(detection_container, text=text, variable=self.detection_var, value=value, bg="#f0f0f0").grid(row=row, column=col, padx=5, pady=2, sticky="w") # Main action buttons with responsive design button_frame = tk.Frame(main_container, bg="#f0f0f0") button_frame.grid(row=3, column=0, pady=20, sticky="ew") button_frame.columnconfigure(0, weight=1) # Make expandable # Preview Clips Button self.preview_btn = tk.Button(button_frame, text="🔍 Preview Clips", command=self.preview_clips_threaded, bg="#2196F3", fg="white", font=("Arial", 11, "bold"), pady=8) self.preview_btn.grid(row=0, column=0, pady=5, sticky="ew") # Generate Shorts Button self.generate_btn = tk.Button(button_frame, text="🎬 Generate All Detected Clips", command=self.generate_shorts_threaded, bg="#4CAF50", fg="white", font=("Arial", 12, "bold"), pady=10) self.generate_btn.grid(row=1, column=0, pady=5, sticky="ew") # 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", wraplength=350) info_label.grid(row=2, column=0, pady=(5,10), sticky="ew") # Edit Generated Shorts Button self.edit_btn = tk.Button(button_frame, text="✏️ Edit Generated Shorts", command=self.open_editor, bg="#FF9800", fg="white", font=("Arial", 11, "bold"), pady=8) self.edit_btn.grid(row=3, column=0, pady=5, sticky="ew") # Create Thumbnails Button self.thumbnail_btn = tk.Button(button_frame, text="📸 Create Thumbnails", command=self.open_thumbnails, bg="#9C27B0", fg="white", font=("Arial", 11, "bold"), pady=8) self.thumbnail_btn.grid(row=4, column=0, pady=5, sticky="ew") # Status label self.status_label = tk.Label(main_container, text="Ready - Select a video to begin", font=("Arial", 9), fg="gray", bg="#f0f0f0", wraplength=400) self.status_label.grid(row=4, column=0, pady=(20,0), sticky="ew") # Store detected clips for selection self.detected_clips = [] def on_window_resize(self, event): """Handle window resize events for responsive layout""" if event.widget == self.root: # Get current window size width = self.root.winfo_width() height = self.root.winfo_height() # Adjust font sizes based on window width if width < 450: title_font_size = 14 button_font_size = 10 info_wrap_length = 300 elif width < 550: title_font_size = 15 button_font_size = 11 info_wrap_length = 350 else: title_font_size = 16 button_font_size = 12 info_wrap_length = 400 # Update wraplength for text elements try: # Find and update info label for widget in self.root.winfo_children(): if isinstance(widget, tk.Frame): for subwidget in widget.winfo_children(): if isinstance(subwidget, tk.Frame): for subsubwidget in subwidget.winfo_children(): if isinstance(subsubwidget, tk.Label) and "Tip:" in str(subsubwidget.cget("text")): subsubwidget.config(wraplength=info_wrap_length) elif isinstance(subsubwidget, tk.Label) and "Ready" in str(subsubwidget.cget("text")): subsubwidget.config(wraplength=info_wrap_length) except: pass # Ignore any errors during dynamic updates def select_video_file(self): """Select video file""" filetypes = [ ("Video files", "*.mp4 *.avi *.mov *.mkv *.wmv *.flv *.webm"), ("All files", "*.*") ] file_path = filedialog.askopenfilename( title="Select Video File", filetypes=filetypes ) if file_path: # Update display filename = os.path.basename(file_path) self.file_label.config(text=filename) # Update shorts generator if self.shorts_generator: self.shorts_generator.video_path = file_path self.shorts_generator.video_label.config(text=os.path.basename(file_path)) self.status_label.config(text=f"Video loaded: {filename}") def preview_clips_threaded(self): """Run preview clips with progress window""" if not self.shorts_generator or not self.shorts_generator.video_path: messagebox.showwarning("No Video", "Please select a video file first.") return # Update settings in shorts generator self.update_shorts_generator_settings() # Create progress window progress_window = ProgressWindow(self.root, "Previewing Clips") # Show detection progress for heavy modes detection_mode = self.detection_var.get() if detection_mode in ["scene", "motion", "speech", "peaks", "combined"]: progress_window.show_detection_progress() def run_preview(): try: from shorts_generator2 import (detect_loud_moments, detect_scene_changes_with_progress, detect_motion_intensity_with_progress, detect_speech_emotion_with_progress, detect_audio_peaks_with_progress, detect_combined_intensity_with_progress, validate_video) video_path = self.shorts_generator.video_path clip_duration = 5 # Fixed clip duration since we removed the setting detection_mode = self.detection_var.get() # Thread-safe progress callbacks with cancellation checks 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)) 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)) # Check for cancellation before starting if progress_window.cancelled: return # Validate video first self.root.after(0, lambda: progress_callback("Validating video...", 5)) validate_video(video_path, min_duration=clip_duration * 2) # Run detection based on mode self.root.after(0, lambda: progress_callback(f"Analyzing {detection_mode} moments...", 10)) if detection_mode == "loud": moments = detect_loud_moments(video_path, chunk_duration=clip_duration, threshold_db=-30) elif detection_mode == "scene": moments = detect_scene_changes_with_progress(video_path, chunk_duration=clip_duration, progress_callback=detection_callback) elif detection_mode == "motion": moments = detect_motion_intensity_with_progress(video_path, chunk_duration=clip_duration, progress_callback=detection_callback) elif detection_mode == "speech": moments = detect_speech_emotion_with_progress(video_path, chunk_duration=clip_duration, progress_callback=detection_callback) elif detection_mode == "peaks": moments = detect_audio_peaks_with_progress(video_path, chunk_duration=clip_duration, progress_callback=detection_callback) elif detection_mode == "combined": moments = detect_combined_intensity_with_progress(video_path, chunk_duration=clip_duration, progress_callback=detection_callback) else: moments = detect_loud_moments(video_path, chunk_duration=clip_duration, threshold_db=-30) # 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 with selection window def show_results(): if progress_window.cancelled: return if moments: self.detected_clips = moments # Store for potential generation progress_window.close() # 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) 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: error_msg = str(e) def show_error(): progress_window.close() messagebox.showerror("Preview Error", f"An error occurred during preview:\n{error_msg}") self.root.after(0, show_error) # Start the preview in a background thread threading.Thread(target=run_preview, daemon=True).start() def generate_shorts_threaded(self): """Run generate shorts with progress window""" if not self.shorts_generator or not self.shorts_generator.video_path: messagebox.showwarning("No Video", "Please select a video file first.") return # Update settings self.update_shorts_generator_settings() # Create progress window progress_window = ProgressWindow(self.root, "Generating Shorts") # Show detection progress for heavy modes detection_mode = self.detection_var.get() if detection_mode in ["scene", "motion", "speech", "peaks", "combined"]: progress_window.show_detection_progress() def run_generation(): try: from shorts_generator2 import generate_shorts video_path = self.shorts_generator.video_path detection_mode = self.detection_var.get() clip_duration = 5 # Default duration # Thread-safe progress callbacks 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)) def detection_callback(message, percent): if progress_window.cancelled: raise InterruptedError("Generation cancelled by user") self.root.after(0, lambda: progress_window.update_detection_progress(message, percent)) # 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( video_path, max_clips=50, # Generate a reasonable number for preview/selection output_folder="shorts", progress_callback=progress_callback, detection_progress_callback=detection_callback, threshold_db=-30, clip_duration=5, detection_mode=detection_mode ) def show_success(): progress_window.close() messagebox.showinfo("Success", "Successfully generated shorts!\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_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): """Update the shorts generator with current settings""" if self.shorts_generator: self.shorts_generator.detection_mode_var.set(self.detection_var.get()) def open_editor(self): """Open the shorts editor""" print("DEBUG: open_editor called") if self.shorts_generator: print("DEBUG: shorts_generator exists") try: 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: print(f"DEBUG: Exception in open_editor: {e}") import traceback traceback.print_exc() 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): """Open the thumbnail editor""" print("DEBUG: open_thumbnails called") if self.shorts_generator: print("DEBUG: shorts_generator exists") try: 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: print(f"DEBUG: Exception in open_thumbnails: {e}") import traceback traceback.print_exc() 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): """Start the main application""" self.root.mainloop() if __name__ == "__main__": app = MainApplication() app.run()