Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import json
- import shutil
- import tkinter as tk
- from tkinter import filedialog, messagebox, ttk
- from pathlib import Path
- from PIL import Image
- MAX_STICKER_SIZE = 100 * 1024 # 100 KB
- TARGET_DIM = 512 # Target dimension for the sticker canvas
- EXACT_HEIGHT = 512 # Exact required height for WhatsApp stickers
- INITIAL_QUALITY = 80
- MIN_QUALITY = 20
- QUALITY_STEP = 10
- def select_source_dir():
- path = filedialog.askdirectory(title="Select folder with your .webp files")
- source_var.set(path)
- def select_assets_dir():
- path = filedialog.askdirectory(title="Select Android app's assets folder (e.g. app/src/main/assets)")
- assets_var.set(path)
- def select_tray_icon():
- path = filedialog.askopenfilename(
- title="Select your 96×96px tray‑icon PNG",
- filetypes=[("PNG files", "*.png")]
- )
- tray_var.set(path)
- def normalize_and_compress(src_path, dst_path):
- """Resize, normalize, and compress a WebP to be under MAX_STICKER_SIZE, returns final size."""
- img = Image.open(src_path).convert("RGBA")
- w, h = img.size
- # Always create a 512×512 canvas
- # WhatsApp requires EXACTLY 512×512 dimension
- canvas = Image.new("RGBA", (TARGET_DIM, TARGET_DIM), (0, 0, 0, 0))
- # If image has different dimensions, resize while preserving aspect ratio
- if w != TARGET_DIM or h != TARGET_DIM:
- # Calculate scale to fit within canvas while maintaining aspect ratio
- scale = min(TARGET_DIM / w, TARGET_DIM / h)
- new_w = int(w * scale)
- new_h = int(h * scale)
- # Resize the image
- img = img.resize((new_w, new_h), Image.LANCZOS)
- # Center on canvas
- offset_x = (TARGET_DIM - new_w) // 2
- offset_y = (TARGET_DIM - new_h) // 2
- canvas.paste(img, (offset_x, offset_y))
- else:
- # Already the right size, just paste directly
- canvas.paste(img, (0, 0))
- # Try to save with progressively lower quality until size is acceptable
- quality = INITIAL_QUALITY
- while quality >= MIN_QUALITY:
- canvas.save(dst_path, "WEBP", quality=quality, method=6)
- size = dst_path.stat().st_size
- if size <= MAX_STICKER_SIZE:
- return size
- quality -= QUALITY_STEP
- # Return final size even if still over the limit
- return dst_path.stat().st_size
- def run():
- src = Path(source_var.get())
- assets = Path(assets_var.get())
- tray = Path(tray_var.get())
- prefix = prefix_var.get().strip()
- publisher = publisher_var.get().strip()
- # Validations
- if not src.is_dir():
- messagebox.showerror("Error", "Invalid .webp source folder.")
- return
- if not assets.is_dir():
- messagebox.showerror("Error", "Invalid Android assets folder.")
- return
- if not (tray.is_file() and tray.suffix.lower() == ".png"):
- messagebox.showerror("Error", "Invalid tray‑icon PNG.")
- return
- if not prefix or not publisher:
- messagebox.showerror("Error", "Pack name prefix and publisher name are required.")
- return
- webps = sorted(src.glob("*.webp"))
- if len(webps) < 3:
- messagebox.showerror("Error", "At least 3 .webp files are required.")
- return
- tray_name = tray.name
- # Split into packs of max 30, merge last if <3
- chunks = [webps[i:i+30] for i in range(0, len(webps), 30)]
- if len(chunks) > 1 and len(chunks[-1]) < 3:
- chunks[-2].extend(chunks[-1])
- chunks.pop()
- # Setup progress tracking
- progress_window = tk.Toplevel(root)
- progress_window.title("Processing Stickers")
- progress_var = tk.DoubleVar()
- progress_label = tk.Label(progress_window, text="Processing stickers...")
- progress_label.pack(padx=10, pady=5)
- progress_bar = ttk.Progressbar(progress_window, variable=progress_var, maximum=len(webps))
- progress_bar.pack(padx=10, pady=10, fill=tk.X)
- sticker_packs = []
- processed = 0
- for idx, chunk in enumerate(chunks, start=1):
- pack_id = str(idx)
- pack_name = f"{prefix} {idx}"
- pack_dir = assets / pack_id
- pack_dir.mkdir(parents=True, exist_ok=True)
- # Copy tray icon
- shutil.copy(tray, pack_dir / tray_name)
- stickers = []
- for f in chunk:
- dst = pack_dir / f.name
- size = normalize_and_compress(f, dst)
- if size > MAX_STICKER_SIZE:
- messagebox.showwarning(
- "Size Warning",
- f"{f.name} is still {size//1024}KB (>100KB) after compression and resizing."
- )
- # Verify dimensions are exactly 512×512
- verify_img = Image.open(dst)
- w, h = verify_img.size
- if w != TARGET_DIM or h != TARGET_DIM:
- messagebox.showerror(
- "Dimension Error",
- f"{f.name} dimensions are {w}×{h}px. WhatsApp requires exactly {TARGET_DIM}×{TARGET_DIM}px!"
- )
- stickers.append({
- "image_file": f.name,
- "emojis": ["🔡"],
- "accessibility_text": f.stem # Use filename as accessibility text
- })
- # Update progress
- processed += 1
- progress_var.set(processed)
- progress_window.update()
- sticker_packs.append({
- "identifier": pack_id,
- "name": pack_name,
- "publisher": publisher,
- "tray_image_file": tray_name,
- "image_data_version": "1",
- "stickers": stickers
- })
- # Write contents.json
- out_data = {
- "android_play_store_link": "",
- "ios_app_store_link": "",
- "sticker_packs": sticker_packs
- }
- with open(assets / "contents.json", "w", encoding="utf-8") as fp:
- json.dump(out_data, fp, indent=2, ensure_ascii=False)
- progress_window.destroy()
- messagebox.showinfo("Success",
- f"Generated {len(sticker_packs)} pack(s) under:\n{assets}\n(contents.json updated)")
- # GUI setup
- root = tk.Tk()
- root.title("WhatsApp Sticker-Pack Generator (512×512px Enforced)")
- # Add variables
- prefix_var = tk.StringVar(value="My Stickers")
- publisher_var = tk.StringVar(value="Your Name")
- source_var = tk.StringVar()
- assets_var = tk.StringVar()
- tray_var = tk.StringVar()
- # Create a consistent padding
- padx = 5
- pady = 5
- # Create a frame for better organization
- frame = ttk.Frame(root, padding="10")
- frame.pack(fill=tk.BOTH, expand=True)
- # Row 0 - Pack Name Prefix
- ttk.Label(frame, text="Pack Name Prefix:").grid(row=0, column=0, sticky="e", padx=padx, pady=pady)
- ttk.Entry(frame, textvariable=prefix_var, width=40).grid(row=0, column=1, columnspan=2, sticky="ew", padx=padx)
- # Row 1 - Publisher
- ttk.Label(frame, text="Publisher Name:").grid(row=1, column=0, sticky="e", padx=padx, pady=pady)
- ttk.Entry(frame, textvariable=publisher_var, width=40).grid(row=1, column=1, columnspan=2, sticky="ew", padx=padx)
- # Row 2 - Source Directory
- ttk.Label(frame, text="Source .webp folder:").grid(row=2, column=0, sticky="e", padx=padx, pady=pady)
- ttk.Entry(frame, textvariable=source_var, width=40).grid(row=2, column=1, sticky="ew", padx=padx)
- ttk.Button(frame, text="Browse…", command=select_source_dir).grid(row=2, column=2, padx=padx)
- # Row 3 - Assets Directory
- ttk.Label(frame, text="Android assets folder:").grid(row=3, column=0, sticky="e", padx=padx, pady=pady)
- ttk.Entry(frame, textvariable=assets_var, width=40).grid(row=3, column=1, sticky="ew", padx=padx)
- ttk.Button(frame, text="Browse…", command=select_assets_dir).grid(row=3, column=2, padx=padx)
- # Row 4 - Tray Icon
- ttk.Label(frame, text="Tray‑icon .png:").grid(row=4, column=0, sticky="e", padx=padx, pady=pady)
- ttk.Entry(frame, textvariable=tray_var, width=40).grid(row=4, column=1, sticky="ew", padx=padx)
- ttk.Button(frame, text="Browse…", command=select_tray_icon).grid(row=4, column=2, padx=padx)
- # Row 5 - Dimensions info
- info_text = f"Required dimensions: {TARGET_DIM}×{TARGET_DIM} pixels, Max size: {MAX_STICKER_SIZE//1024}KB"
- ttk.Label(frame, text=info_text, foreground="gray").grid(row=5, column=0, columnspan=3, pady=pady)
- # Row 6 - Generate Button
- ttk.Button(frame, text="Generate Packs", command=run).grid(row=6, column=0, columnspan=3, pady=10)
- # Configure grid column weights
- frame.columnconfigure(1, weight=1)
- # Start the application
- root.mainloop()
Add Comment
Please, Sign In to add comment