import os import threading from tkinter import Tk, Button, filedialog, messagebox, ttk, Label, Entry, StringVar, Checkbutton, BooleanVar from PIL import Image class ImageResizerApp: def __init__(self, root): self.root = root self.root.title("Image Resizer") self.root.geometry("450x250") self.root.resizable(False, False) # Instructions Label(root, text="Select images to resize").pack(pady=(10, 5)) # Frame for size inputs size_frame = ttk.Frame(root) size_frame.pack(pady=5) Label(size_frame, text="Width:").grid(row=0, column=0, padx=(0,5)) self.width_var = StringVar(value="300") Entry(size_frame, textvariable=self.width_var, width=6).grid(row=0, column=1, padx=(0,15)) Label(size_frame, text="Height:").grid(row=0, column=2, padx=(0,5)) self.height_var = StringVar(value="300") Entry(size_frame, textvariable=self.height_var, width=6).grid(row=0, column=3) # Checkbox for convert to webp self.convert_var = BooleanVar(value=False) Checkbutton(root, text="Convert outputs to .webp (quality=50)", variable=self.convert_var).pack(pady=5) # Button to choose images Button(root, text="Choose Images", command=self.choose_images).pack(pady=10) # Progress bar self.progress = ttk.Progressbar(root, orient='horizontal', mode='determinate', length=350) self.progress.pack(pady=10) # Status label self.status_label = Label(root, text="") self.status_label.pack() def choose_images(self): # Validate size inputs before file dialog try: width = int(self.width_var.get()) height = int(self.height_var.get()) if width <= 0 or height <= 0: raise ValueError except ValueError: messagebox.showerror("Invalid size", "Please enter positive integer values for width and height.") return filetypes = [("Image files", "*.jpg *.jpeg *.png *.webp"), ("All files", "*.*")] files = filedialog.askopenfilenames(title="Select images", filetypes=filetypes) if files: # Disable UI while processing self.status_label.config(text="Starting...") threading.Thread(target=self.resize_images, args=(files, width, height, self.convert_var.get()), daemon=True).start() def resize_images(self, files, target_w, target_h, convert_to_webp): total = len(files) self.progress["maximum"] = total self.progress["value"] = 0 self.status_label.config(text="Processing...") for i, filepath in enumerate(files, start=1): try: img = Image.open(filepath) # Use thumbnail to preserve aspect ratio img.thumbnail((target_w, target_h)) base_dir = os.path.dirname(filepath) thumbs_dir = os.path.join(base_dir, "thumbs") os.makedirs(thumbs_dir, exist_ok=True) orig_name = os.path.basename(filepath) name_root, orig_ext = os.path.splitext(orig_name) if convert_to_webp: save_name = f"{name_root}.webp" save_path = os.path.join(thumbs_dir, save_name) # Always save as webp with quality=50 img.save(save_path, format="WEBP", quality=50) else: # Keep original format/extension save_name = orig_name save_path = os.path.join(thumbs_dir, save_name) # Determine format from original extension (Pillow infers from save_path extension) # For safety, specify format explicitly for common cases ext_lower = orig_ext.lower() if ext_lower in [".jpg", ".jpeg"]: img.save(save_path, format="JPEG") elif ext_lower == ".png": img.save(save_path, format="PNG") elif ext_lower == ".webp": img.save(save_path, format="WEBP") else: # Let PIL infer or default to PNG try: img.save(save_path) except Exception: # fallback to PNG fallback = os.path.join(thumbs_dir, f"{name_root}.png") img.save(fallback, format="PNG") # End try saving except Exception as e: print(f"Failed to process {filepath}: {e}") # Update progress bar self.progress["value"] = i self.root.update_idletasks() self.status_label.config(text="Done!") messagebox.showinfo("Complete", f"Processed {total} images.\nSaved thumbnails in 'thumbs' folders.") if __name__ == "__main__": # Check for Pillow import try: from PIL import Image except ImportError: messagebox.showerror("Missing Dependency", "Please install Pillow: pip install pillow") exit(1) root = Tk() app = ImageResizerApp(root) root.mainloop()