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.resizable(False, False) self.window.transient(parent) self.window.grab_set() # 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}") # Create progress widgets self.status_label = tk.Label(self.window, text="Initializing...", anchor="w", font=("Arial", 10)) self.status_label.pack(fill="x", padx=15, pady=(15,5)) self.time_label = tk.Label(self.window, text="Elapsed: 0.0s | Remaining: --s", anchor="w", font=("Arial", 9), fg="gray") self.time_label.pack(fill="x", padx=15, pady=(0,5)) # Main progress bar self.progress_var = tk.DoubleVar() self.progress_bar = ttk.Progressbar(self.window, variable=self.progress_var, maximum=100, length=370) self.progress_bar.pack(fill="x", padx=15, pady=(5,3)) # Detection progress bar (hidden by default) self.detection_label = tk.Label(self.window, text="", anchor="w", font=("Arial", 9), fg="blue") self.detection_progress_var = tk.DoubleVar() self.detection_progress_bar = ttk.Progressbar(self.window, variable=self.detection_progress_var, maximum=100, length=370) # Cancel button self.cancel_btn = tk.Button(self.window, text="Cancel", command=self.cancel_operation) self.cancel_btn.pack(pady=(5,15)) self.start_time = time.time() self.cancelled = False def show_detection_progress(self): """Show the detection progress bar - thread safe""" def _show(): self.detection_label.pack(fill="x", padx=15, pady=(3,0)) self.detection_progress_bar.pack(fill="x", padx=15, 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.pack_forget() self.detection_progress_bar.pack_forget() 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_operation(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 MainApplication: def __init__(self): self.root = tk.Tk() self.root.title("AI Shorts Generator - Main Controller") self.root.geometry("500x600") self.root.configure(bg="#f0f0f0") # Initialize the ShortsGeneratorGUI (but don't show its window) self.shorts_generator = None self.init_shorts_generator() self.setup_gui() 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""" # Title title_label = tk.Label(self.root, text="🎬 AI Shorts Generator", font=("Arial", 16, "bold"), bg="#f0f0f0", fg="#2c3e50") title_label.pack(pady=20) # File selection frame file_frame = tk.Frame(self.root, bg="#f0f0f0") file_frame.pack(pady=10, padx=20, fill="x") tk.Label(file_frame, text="Selected Video:", font=("Arial", 10, "bold"), bg="#f0f0f0").pack(anchor="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.pack(fill="x", pady=(5,10)) # 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.pack(pady=5) # Settings frame (simplified) settings_frame = tk.LabelFrame(self.root, text="Quick Settings", font=("Arial", 10, "bold"), bg="#f0f0f0", padx=10, pady=10) settings_frame.pack(pady=10, padx=20, fill="x") # Detection mode tk.Label(settings_frame, text="Detection Mode:", bg="#f0f0f0").pack(anchor="w") self.detection_var = tk.StringVar(value="loud") detection_frame = tk.Frame(settings_frame, bg="#f0f0f0") detection_frame.pack(fill="x", pady=5) modes = [("Loud Moments", "loud"), ("Scene Changes", "scene"), ("Motion", "motion"), ("Speech", "speech"), ("Audio Peaks", "peaks"), ("Combined", "combined")] for i, (text, value) in enumerate(modes): if i % 3 == 0: row_frame = tk.Frame(detection_frame, bg="#f0f0f0") row_frame.pack(fill="x") tk.Radiobutton(row_frame, text=text, variable=self.detection_var, value=value, 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 button_frame = tk.Frame(self.root, bg="#f0f0f0") button_frame.pack(pady=20, padx=20, fill="x") # 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.pack(fill="x", pady=5) # Generate Shorts Button self.generate_btn = tk.Button(button_frame, text="🎬 Generate Shorts", command=self.generate_shorts_threaded, bg="#4CAF50", fg="white", font=("Arial", 12, "bold"), pady=10) self.generate_btn.pack(fill="x", pady=5) # 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.pack(fill="x", pady=5) # 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.pack(fill="x", pady=5) # Status label self.status_label = tk.Label(self.root, text="Ready - Select a video to begin", font=("Arial", 9), fg="gray", bg="#f0f0f0") self.status_label.pack(pady=(20,10)) 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 = self.clips_var.get() # Use our own duration setting # Thread-safe progress callbacks def progress_callback(message, percent): self.root.after(0, lambda: progress_window.update_progress(message, percent)) def detection_callback(message, percent): self.root.after(0, lambda: progress_window.update_detection_progress(message, percent)) # 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) self.root.after(0, lambda: progress_callback("Analysis complete!", 90)) # Show results def show_results(): if moments: result_msg = f"Found {len(moments)} interesting moments:\n\n" 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() self.root.after(0, lambda: progress_callback("Preparing results...", 100)) self.root.after(500, show_results) 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 max_clips = self.clips_var.get() clip_duration = self.clips_var.get() # Using clips_var as duration for simplicity # Thread-safe progress callbacks def progress_callback(message, percent): if not progress_window.cancelled: self.root.after(0, lambda: progress_window.update_progress(message, percent)) def detection_callback(message, percent): if not progress_window.cancelled: self.root.after(0, lambda: progress_window.update_detection_progress(message, percent)) # Run the actual generation generate_shorts( video_path, max_clips=max_clips, 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", f"Successfully generated {max_clips} shorts!\n\nCheck the 'shorts' folder for your videos.") self.root.after(0, show_success) 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 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()) self.shorts_generator.clips_var.set(self.clips_var.get()) def open_editor(self): """Open the shorts editor""" if self.shorts_generator: try: self.shorts_generator.open_shorts_editor() except Exception as e: messagebox.showerror("Editor Error", f"Could not open editor: {e}") def open_thumbnails(self): """Open the thumbnail editor""" if self.shorts_generator: try: self.shorts_generator.open_thumbnail_editor() except Exception as e: messagebox.showerror("Thumbnail Error", f"Could not open thumbnail editor: {e}") def run(self): """Start the main application""" self.root.mainloop() if __name__ == "__main__": app = MainApplication() app.run()