Guest User

WhatsApp sticker recovery

a guest
Apr 17th, 2025
786
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.46 KB | Fixit | 0 0
  1. import json
  2. import shutil
  3. import tkinter as tk
  4. from tkinter import filedialog, messagebox, ttk
  5. from pathlib import Path
  6. from PIL import Image
  7.  
  8. MAX_STICKER_SIZE = 100 * 1024  # 100 KB
  9. TARGET_DIM = 512  # Target dimension for the sticker canvas
  10. EXACT_HEIGHT = 512  # Exact required height for WhatsApp stickers
  11. INITIAL_QUALITY = 80
  12. MIN_QUALITY = 20
  13. QUALITY_STEP = 10
  14.  
  15. def select_source_dir():
  16.     path = filedialog.askdirectory(title="Select folder with your .webp files")
  17.     source_var.set(path)
  18.  
  19. def select_assets_dir():
  20.     path = filedialog.askdirectory(title="Select Android app's assets folder (e.g. app/src/main/assets)")
  21.     assets_var.set(path)
  22.  
  23. def select_tray_icon():
  24.     path = filedialog.askopenfilename(
  25.         title="Select your 96×96px tray‑icon PNG",
  26.         filetypes=[("PNG files", "*.png")]
  27.     )
  28.     tray_var.set(path)
  29.  
  30. def normalize_and_compress(src_path, dst_path):
  31.     """Resize, normalize, and compress a WebP to be under MAX_STICKER_SIZE, returns final size."""
  32.     img = Image.open(src_path).convert("RGBA")
  33.     w, h = img.size
  34.    
  35.     # Always create a 512×512 canvas
  36.     # WhatsApp requires EXACTLY 512×512 dimension
  37.     canvas = Image.new("RGBA", (TARGET_DIM, TARGET_DIM), (0, 0, 0, 0))
  38.    
  39.     # If image has different dimensions, resize while preserving aspect ratio
  40.     if w != TARGET_DIM or h != TARGET_DIM:
  41.         # Calculate scale to fit within canvas while maintaining aspect ratio
  42.         scale = min(TARGET_DIM / w, TARGET_DIM / h)
  43.         new_w = int(w * scale)
  44.         new_h = int(h * scale)
  45.        
  46.         # Resize the image
  47.         img = img.resize((new_w, new_h), Image.LANCZOS)
  48.        
  49.         # Center on canvas
  50.         offset_x = (TARGET_DIM - new_w) // 2
  51.         offset_y = (TARGET_DIM - new_h) // 2
  52.         canvas.paste(img, (offset_x, offset_y))
  53.     else:
  54.         # Already the right size, just paste directly
  55.         canvas.paste(img, (0, 0))
  56.    
  57.     # Try to save with progressively lower quality until size is acceptable
  58.     quality = INITIAL_QUALITY
  59.     while quality >= MIN_QUALITY:
  60.         canvas.save(dst_path, "WEBP", quality=quality, method=6)
  61.         size = dst_path.stat().st_size
  62.         if size <= MAX_STICKER_SIZE:
  63.             return size
  64.         quality -= QUALITY_STEP
  65.    
  66.     # Return final size even if still over the limit
  67.     return dst_path.stat().st_size
  68.  
  69. def run():
  70.     src = Path(source_var.get())
  71.     assets = Path(assets_var.get())
  72.     tray = Path(tray_var.get())
  73.     prefix = prefix_var.get().strip()
  74.     publisher = publisher_var.get().strip()
  75.  
  76.     # Validations
  77.     if not src.is_dir():
  78.         messagebox.showerror("Error", "Invalid .webp source folder.")
  79.         return
  80.     if not assets.is_dir():
  81.         messagebox.showerror("Error", "Invalid Android assets folder.")
  82.         return
  83.     if not (tray.is_file() and tray.suffix.lower() == ".png"):
  84.         messagebox.showerror("Error", "Invalid tray‑icon PNG.")
  85.         return
  86.     if not prefix or not publisher:
  87.         messagebox.showerror("Error", "Pack name prefix and publisher name are required.")
  88.         return
  89.  
  90.     webps = sorted(src.glob("*.webp"))
  91.     if len(webps) < 3:
  92.         messagebox.showerror("Error", "At least 3 .webp files are required.")
  93.         return
  94.  
  95.     tray_name = tray.name
  96.     # Split into packs of max 30, merge last if <3
  97.     chunks = [webps[i:i+30] for i in range(0, len(webps), 30)]
  98.     if len(chunks) > 1 and len(chunks[-1]) < 3:
  99.         chunks[-2].extend(chunks[-1])
  100.         chunks.pop()
  101.  
  102.     # Setup progress tracking
  103.     progress_window = tk.Toplevel(root)
  104.     progress_window.title("Processing Stickers")
  105.     progress_var = tk.DoubleVar()
  106.     progress_label = tk.Label(progress_window, text="Processing stickers...")
  107.     progress_label.pack(padx=10, pady=5)
  108.     progress_bar = ttk.Progressbar(progress_window, variable=progress_var, maximum=len(webps))
  109.     progress_bar.pack(padx=10, pady=10, fill=tk.X)
  110.    
  111.     sticker_packs = []
  112.     processed = 0
  113.    
  114.     for idx, chunk in enumerate(chunks, start=1):
  115.         pack_id = str(idx)
  116.         pack_name = f"{prefix} {idx}"
  117.         pack_dir = assets / pack_id
  118.         pack_dir.mkdir(parents=True, exist_ok=True)
  119.  
  120.         # Copy tray icon
  121.         shutil.copy(tray, pack_dir / tray_name)
  122.  
  123.         stickers = []
  124.         for f in chunk:
  125.             dst = pack_dir / f.name
  126.             size = normalize_and_compress(f, dst)
  127.            
  128.             if size > MAX_STICKER_SIZE:
  129.                 messagebox.showwarning(
  130.                     "Size Warning",
  131.                     f"{f.name} is still {size//1024}KB (>100KB) after compression and resizing."
  132.                 )
  133.                
  134.             # Verify dimensions are exactly 512×512
  135.             verify_img = Image.open(dst)
  136.             w, h = verify_img.size
  137.             if w != TARGET_DIM or h != TARGET_DIM:
  138.                 messagebox.showerror(
  139.                     "Dimension Error",
  140.                     f"{f.name} dimensions are {w}×{h}px. WhatsApp requires exactly {TARGET_DIM}×{TARGET_DIM}px!"
  141.                 )
  142.            
  143.             stickers.append({
  144.                 "image_file": f.name,
  145.                 "emojis": ["🔡"],
  146.                 "accessibility_text": f.stem  # Use filename as accessibility text
  147.             })
  148.            
  149.             # Update progress
  150.             processed += 1
  151.             progress_var.set(processed)
  152.             progress_window.update()
  153.  
  154.         sticker_packs.append({
  155.             "identifier": pack_id,
  156.             "name": pack_name,
  157.             "publisher": publisher,
  158.             "tray_image_file": tray_name,
  159.             "image_data_version": "1",
  160.             "stickers": stickers
  161.         })
  162.  
  163.     # Write contents.json
  164.     out_data = {
  165.         "android_play_store_link": "",
  166.         "ios_app_store_link": "",
  167.         "sticker_packs": sticker_packs
  168.     }
  169.     with open(assets / "contents.json", "w", encoding="utf-8") as fp:
  170.         json.dump(out_data, fp, indent=2, ensure_ascii=False)
  171.  
  172.     progress_window.destroy()
  173.     messagebox.showinfo("Success",
  174.         f"Generated {len(sticker_packs)} pack(s) under:\n{assets}\n(contents.json updated)")
  175.  
  176. # GUI setup
  177. root = tk.Tk()
  178. root.title("WhatsApp Sticker-Pack Generator (512×512px Enforced)")
  179.  
  180. # Add variables
  181. prefix_var = tk.StringVar(value="My Stickers")
  182. publisher_var = tk.StringVar(value="Your Name")
  183. source_var = tk.StringVar()
  184. assets_var = tk.StringVar()
  185. tray_var = tk.StringVar()
  186.  
  187. # Create a consistent padding
  188. padx = 5
  189. pady = 5
  190.  
  191. # Create a frame for better organization
  192. frame = ttk.Frame(root, padding="10")
  193. frame.pack(fill=tk.BOTH, expand=True)
  194.  
  195. # Row 0 - Pack Name Prefix
  196. ttk.Label(frame, text="Pack Name Prefix:").grid(row=0, column=0, sticky="e", padx=padx, pady=pady)
  197. ttk.Entry(frame, textvariable=prefix_var, width=40).grid(row=0, column=1, columnspan=2, sticky="ew", padx=padx)
  198.  
  199. # Row 1 - Publisher
  200. ttk.Label(frame, text="Publisher Name:").grid(row=1, column=0, sticky="e", padx=padx, pady=pady)
  201. ttk.Entry(frame, textvariable=publisher_var, width=40).grid(row=1, column=1, columnspan=2, sticky="ew", padx=padx)
  202.  
  203. # Row 2 - Source Directory
  204. ttk.Label(frame, text="Source .webp folder:").grid(row=2, column=0, sticky="e", padx=padx, pady=pady)
  205. ttk.Entry(frame, textvariable=source_var, width=40).grid(row=2, column=1, sticky="ew", padx=padx)
  206. ttk.Button(frame, text="Browse…", command=select_source_dir).grid(row=2, column=2, padx=padx)
  207.  
  208. # Row 3 - Assets Directory
  209. ttk.Label(frame, text="Android assets folder:").grid(row=3, column=0, sticky="e", padx=padx, pady=pady)
  210. ttk.Entry(frame, textvariable=assets_var, width=40).grid(row=3, column=1, sticky="ew", padx=padx)
  211. ttk.Button(frame, text="Browse…", command=select_assets_dir).grid(row=3, column=2, padx=padx)
  212.  
  213. # Row 4 - Tray Icon
  214. ttk.Label(frame, text="Tray‑icon .png:").grid(row=4, column=0, sticky="e", padx=padx, pady=pady)
  215. ttk.Entry(frame, textvariable=tray_var, width=40).grid(row=4, column=1, sticky="ew", padx=padx)
  216. ttk.Button(frame, text="Browse…", command=select_tray_icon).grid(row=4, column=2, padx=padx)
  217.  
  218. # Row 5 - Dimensions info
  219. info_text = f"Required dimensions: {TARGET_DIM}×{TARGET_DIM} pixels, Max size: {MAX_STICKER_SIZE//1024}KB"
  220. ttk.Label(frame, text=info_text, foreground="gray").grid(row=5, column=0, columnspan=3, pady=pady)
  221.  
  222. # Row 6 - Generate Button
  223. ttk.Button(frame, text="Generate Packs", command=run).grid(row=6, column=0, columnspan=3, pady=10)
  224.  
  225. # Configure grid column weights
  226. frame.columnconfigure(1, weight=1)
  227.  
  228. # Start the application
  229. root.mainloop()
Add Comment
Please, Sign In to add comment