import os import tkinter as tk from tkinter import filedialog, simpledialog, colorchooser, messagebox from moviepy import VideoFileClip from PIL import Image, ImageTk, ImageDraw, ImageFont # Enhanced Thumbnail Editor with Frame Slider + Default Emoji Pack + Text Adding def open_thumbnail_editor(video_path): try: editor = tk.Toplevel() editor.title("šŸ“ø Professional Thumbnail Editor") editor.geometry("1200x800") # Load video print(f"šŸ“¹ Loading video: {os.path.basename(video_path)}") clip = VideoFileClip(video_path) duration = int(clip.duration) # Default emoji pack folder stickers_folder = os.path.join(os.path.dirname(__file__), "stickers") os.makedirs(stickers_folder, exist_ok=True) # Create default stickers if folder is empty create_default_stickers(stickers_folder) # Main layout main_frame = tk.Frame(editor) main_frame.pack(fill="both", expand=True, padx=10, pady=10) # Canvas setup (left side) canvas_frame = tk.Frame(main_frame) canvas_frame.pack(side="left", fill="both", expand=True) tk.Label(canvas_frame, text="šŸŽ¬ Thumbnail Preview", font=("Arial", 12, "bold")).pack() canvas = tk.Canvas(canvas_frame, width=720, height=405, bg="black", relief="sunken", bd=2) canvas.pack(pady=10) # Track items for dragging drag_data = {"item": None, "x": 0, "y": 0} def capture_frame_at(time_sec): try: frame = clip.get_frame(max(0, min(time_sec, clip.duration - 0.1))) img = Image.fromarray(frame) # Maintain aspect ratio while fitting in canvas img.thumbnail((720, 405), Image.Resampling.LANCZOS) return img except Exception as e: print(f"āš ļø Error capturing frame: {e}") # Create a placeholder image img = Image.new('RGB', (720, 405), color='black') return img # Displayed image current_frame = capture_frame_at(duration // 2) tk_frame_img = ImageTk.PhotoImage(current_frame) image_item = canvas.create_image(360, 202, image=tk_frame_img) canvas.image = tk_frame_img # Items data sticker_items = [] text_items = [] def update_canvas_frame(val): nonlocal current_frame, tk_frame_img try: sec = float(val) current_frame = capture_frame_at(sec) tk_frame_img = ImageTk.PhotoImage(current_frame) canvas.itemconfig(image_item, image=tk_frame_img) canvas.image = tk_frame_img except Exception as e: print(f"āš ļø Error updating frame: {e}") # Frame controls controls_frame = tk.Frame(canvas_frame) controls_frame.pack(fill="x", pady=5) tk.Label(controls_frame, text="ā±ļø Frame Time (seconds):").pack() frame_slider = tk.Scale(controls_frame, from_=0, to=duration, orient="horizontal", command=update_canvas_frame, length=600, resolution=0.1) frame_slider.set(duration // 2) frame_slider.pack(fill="x", pady=5) # Tools panel (right side) tools_frame = tk.Frame(main_frame, width=300, relief="groove", bd=2) tools_frame.pack(side="right", fill="y", padx=(10, 0)) tools_frame.pack_propagate(False) tk.Label(tools_frame, text="šŸ› ļø Editing Tools", font=("Arial", 14, "bold")).pack(pady=10) # Stickers section stickers_label_frame = tk.LabelFrame(tools_frame, text="šŸŽ­ Stickers & Emojis", padx=10, pady=5) stickers_label_frame.pack(fill="x", padx=10, pady=5) # Create scrollable frame for stickers stickers_canvas = tk.Canvas(stickers_label_frame, height=200) stickers_scrollbar = tk.Scrollbar(stickers_label_frame, orient="vertical", command=stickers_canvas.yview) stickers_scrollable_frame = tk.Frame(stickers_canvas) stickers_scrollable_frame.bind( "", lambda e: stickers_canvas.configure(scrollregion=stickers_canvas.bbox("all")) ) stickers_canvas.create_window((0, 0), window=stickers_scrollable_frame, anchor="nw") stickers_canvas.configure(yscrollcommand=stickers_scrollbar.set) stickers_canvas.pack(side="left", fill="both", expand=True) stickers_scrollbar.pack(side="right", fill="y") def add_sticker(path): try: img = Image.open(path).convert("RGBA") img.thumbnail((60, 60), Image.Resampling.LANCZOS) tk_img = ImageTk.PhotoImage(img) item = canvas.create_image(360, 200, image=tk_img) # Keep reference to prevent garbage collection if not hasattr(canvas, 'images'): canvas.images = [] canvas.images.append(tk_img) sticker_items.append((item, img)) print(f"āœ… Added sticker: {os.path.basename(path)}") except Exception as e: print(f"āš ļø Failed to load sticker {path}: {e}") # Load default stickers sticker_count = 0 stickers_row_frame = None for file in os.listdir(stickers_folder): if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')): try: if sticker_count % 4 == 0: # 4 stickers per row stickers_row_frame = tk.Frame(stickers_scrollable_frame) stickers_row_frame.pack(fill="x", pady=2) btn_img = Image.open(os.path.join(stickers_folder, file)).convert("RGBA") btn_img.thumbnail((40, 40), Image.Resampling.LANCZOS) tk_btn_img = ImageTk.PhotoImage(btn_img) b = tk.Button(stickers_row_frame, image=tk_btn_img, command=lambda f=file: add_sticker(os.path.join(stickers_folder, f))) b.image = tk_btn_img b.pack(side="left", padx=2) sticker_count += 1 except Exception as e: print(f"āš ļø Failed to load sticker {file}: {e}") # Add custom sticker button tk.Button(stickers_label_frame, text="šŸ“ Add Custom Sticker", command=lambda: add_custom_sticker()).pack(pady=5) def add_custom_sticker(): file_path = filedialog.askopenfilename( title="Select Sticker Image", filetypes=[("Image files", "*.png *.jpg *.jpeg *.gif"), ("All files", "*.*")] ) if file_path: add_sticker(file_path) # Text section text_label_frame = tk.LabelFrame(tools_frame, text="šŸ“ Text Tools", padx=10, pady=5) text_label_frame.pack(fill="x", padx=10, pady=5) def add_text(): text_value = simpledialog.askstring("Add Text", "Enter text:") if not text_value: return color_result = colorchooser.askcolor(title="Choose text color") color = color_result[1] if color_result[1] else "white" size = simpledialog.askinteger("Font size", "Enter font size:", initialvalue=48, minvalue=8, maxvalue=200) if not size: size = 48 try: item = canvas.create_text(360, 200, text=text_value, fill=color, font=("Arial", size, "bold"), anchor="center") text_items.append((item, text_value, color, size)) print(f"āœ… Added text: '{text_value}'") except Exception as e: print(f"āš ļø Error adding text: {e}") tk.Button(text_label_frame, text="āž• Add Text", command=add_text, bg="#4CAF50", fg="white", font=("Arial", 10, "bold")).pack(pady=5, fill="x") # Clear all button def clear_all(): if messagebox.askyesno("Clear All", "Remove all stickers and text?"): for item_id, _ in sticker_items + text_items: canvas.delete(item_id) sticker_items.clear() text_items.clear() print("šŸ—‘ļø Cleared all items") tk.Button(text_label_frame, text="šŸ—‘ļø Clear All", command=clear_all, bg="#F44336", fg="white").pack(pady=5, fill="x") # Drag handling def on_drag_start(event): items = canvas.find_overlapping(event.x, event.y, event.x, event.y) items = [i for i in items if i != image_item] if not items: return item = items[-1] # topmost drag_data["item"] = item drag_data["x"] = event.x drag_data["y"] = event.y def on_drag_motion(event): if drag_data["item"] is None: return dx = event.x - drag_data["x"] dy = event.y - drag_data["y"] canvas.move(drag_data["item"], dx, dy) drag_data["x"] = event.x drag_data["y"] = event.y def on_drag_release(event): drag_data["item"] = None canvas.bind("", on_drag_start) canvas.bind("", on_drag_motion) canvas.bind("", on_drag_release) # Save section save_frame = tk.LabelFrame(tools_frame, text="šŸ’¾ Export Options", padx=10, pady=5) save_frame.pack(fill="x", padx=10, pady=5) def save_thumbnail(): try: save_path = filedialog.asksaveasfilename( defaultextension=".jpg", filetypes=[("JPEG", "*.jpg"), ("PNG", "*.png"), ("All files", "*.*")], title="Save Thumbnail As" ) if not save_path: return print("šŸ’¾ Generating high-quality thumbnail...") # Get the current frame at full resolution sec = float(frame_slider.get()) frame = Image.fromarray(clip.get_frame(sec)).convert("RGBA") # Calculate scaling factors canvas_w, canvas_h = 720, 405 scale_x = frame.width / canvas_w scale_y = frame.height / canvas_h # Add stickers for item_id, sticker_img in sticker_items: coords = canvas.coords(item_id) if not coords: continue x, y = coords[0], coords[1] # Convert canvas coordinates to frame coordinates px = int(x * scale_x) py = int(y * scale_y) # Scale sticker size target_w = int(sticker_img.width * scale_x) target_h = int(sticker_img.height * scale_y) if target_w > 0 and target_h > 0: sticker_resized = sticker_img.resize((target_w, target_h), Image.Resampling.LANCZOS) # Paste with alpha blending frame.paste(sticker_resized, (px - target_w//2, py - target_h//2), sticker_resized) # Add text draw = ImageDraw.Draw(frame) for item_id, text_value, color, font_size in text_items: coords = canvas.coords(item_id) if not coords: continue x, y = coords[0], coords[1] px = int(x * scale_x) py = int(y * scale_y) # Scale font size scaled_font_size = int(font_size * scale_x) try: font = ImageFont.truetype("arial.ttf", scaled_font_size) except: try: font = ImageFont.truetype("calibri.ttf", scaled_font_size) except: font = ImageFont.load_default() # Get text bounding box for centering bbox = draw.textbbox((0, 0), text_value, font=font) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] # Draw text with outline outline_w = max(2, scaled_font_size // 15) for dx in range(-outline_w, outline_w + 1): for dy in range(-outline_w, outline_w + 1): draw.text((px - text_w//2 + dx, py - text_h//2 + dy), text_value, font=font, fill="black") draw.text((px - text_w//2, py - text_h//2), text_value, font=font, fill=color) # Convert to RGB and save if save_path.lower().endswith('.png'): frame.save(save_path, "PNG", quality=95) else: background = Image.new("RGB", frame.size, (255, 255, 255)) background.paste(frame, mask=frame.split()[3] if frame.mode == 'RGBA' else None) background.save(save_path, "JPEG", quality=95) print(f"āœ… Thumbnail saved: {save_path}") messagebox.showinfo("Success", f"Thumbnail saved successfully!\n{save_path}") except Exception as e: print(f"āŒ Error saving thumbnail: {e}") messagebox.showerror("Error", f"Failed to save thumbnail:\n{str(e)}") tk.Button(save_frame, text="šŸ’¾ Save Thumbnail", command=save_thumbnail, bg="#2196F3", fg="white", font=("Arial", 12, "bold")).pack(pady=5, fill="x") # Info label info_text = f"šŸ“¹ Video: {os.path.basename(video_path)}\nā±ļø Duration: {duration}s\nšŸ“ Size: {clip.size[0]}x{clip.size[1]}" tk.Label(save_frame, text=info_text, font=("Arial", 8), justify="left").pack(pady=5) print(f"āœ… Thumbnail editor loaded successfully!") except Exception as e: print(f"āŒ Error opening thumbnail editor: {e}") messagebox.showerror("Error", f"Failed to open thumbnail editor:\n{str(e)}") def create_default_stickers(stickers_folder): """Create some default emoji stickers if folder is empty""" if os.listdir(stickers_folder): return # Already has stickers try: from PIL import Image, ImageDraw # Create simple emoji stickers emojis = [ ("šŸ˜€", (255, 255, 0)), # Happy face ("ā¤ļø", (255, 0, 0)), # Heart ("šŸ‘", (255, 220, 177)), # Thumbs up ("šŸ”„", (255, 100, 0)), # Fire ("⭐", (255, 215, 0)), # Star ("šŸ’Æ", (0, 255, 0)), # 100 ] for i, (emoji, color) in enumerate(emojis): # Create a simple colored circle as placeholder img = Image.new('RGBA', (80, 80), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) draw.ellipse([10, 10, 70, 70], fill=color) # Save as PNG img.save(os.path.join(stickers_folder, f"emoji_{i+1}.png")) print("āœ… Created default sticker pack") except Exception as e: print(f"āš ļø Could not create default stickers: {e}") # Main execution if __name__ == '__main__': root = tk.Tk() root.withdraw() video_path = filedialog.askopenfilename( title='Select a video file', filetypes=[('Video files', '*.mp4 *.mov *.avi *.mkv'), ('All files', '*.*')] ) if video_path: try: root.deiconify() # Show root window root.title("Thumbnail Editor") open_thumbnail_editor(video_path) root.mainloop() except Exception as e: print(f"āŒ Error: {e}") messagebox.showerror("Error", f"Failed to start thumbnail editor:\n{str(e)}") else: print('No video selected.') root.destroy()