import tkinter as tk from tkinter import filedialog, messagebox, ttk import os import threading import queue import shutil import subprocess # Global variables for controlling filtering and error handling stop_event = threading.Event() error_messages = [] error_window = None selected_files = [] worker_thread = None def open_photo_fantasy(): global error_messages, error_window, selected_files global save_dir_var, status_var, num_files_var, errors_var, thread_count_var, progress global q, worker_thread, root, stop_button, saved_files # Create the Tkinter window root = tk.Tk() root.title("Photo Fantasy") # Initialize Tkinter variables save_dir_var = tk.StringVar() status_var = tk.StringVar() num_files_var = tk.StringVar() errors_var = tk.StringVar(value="Errors: 0") thread_count_var = tk.StringVar(value="4") progress = tk.IntVar() q = queue.Queue() def center_window(window): window.update_idletasks() width = window.winfo_width() + 120 # Add 120 pixels to width height = window.winfo_height() x = (window.winfo_screenwidth() // 2) - (width // 2) y = (window.winfo_screenheight() // 2) - (height // 2) window.geometry(f'{width}x{height}+{x}+{y}') def select_directory(): filepaths = filedialog.askopenfilenames( title="Select Images", filetypes=[("All Image files", "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff")] ) if filepaths: selected_files.clear() selected_files.extend(filepaths) update_selected_files_label() def choose_directory(): directory = filedialog.askdirectory() if directory: save_dir_var.set(directory) save_dir_entry.config(state='normal') save_dir_entry.delete(0, tk.END) save_dir_entry.insert(0, directory) save_dir_entry.config(state='readonly') def save_file_with_unique_name(filepath, save_directory, saved_files): """Save file with a unique name to avoid overwriting.""" if filepath in saved_files: return # File already saved, do not save again base_name, ext = os.path.splitext(os.path.basename(filepath)) save_path = os.path.join(save_directory, f"{base_name}{ext}") counter = 1 while os.path.exists(save_path): save_path = os.path.join(save_directory, f"{base_name} ({counter}){ext}") counter += 1 try: shutil.copy(filepath, save_path) saved_files.add(filepath) # Mark this file as saved except Exception as e: error_messages.append(f"Error saving file {filepath}: {e}") update_error_count() def update_selected_files_label(): """Update the label showing the number of selected files.""" num_files_var.set(f"{len(selected_files)} files selected.") def update_error_count(): """Update the error count displayed in the Errors button.""" errors_var.set(f"Errors: {len(error_messages)}") def run_task(task_func): """Run the given task function in a separate thread.""" global worker_thread stop_event.clear() disable_buttons() worker_thread = threading.Thread(target=task_func) worker_thread.start() root.after(100, check_thread) def check_thread(): """Check if the worker thread is still running.""" if worker_thread.is_alive(): root.after(100, check_thread) else: enable_buttons() if stop_event.is_set(): status_var.set("Task stopped.") else: status_var.set("Task completed.") def disable_buttons(): """Disable all buttons except the stop button.""" select_directory_button.config(state='disabled') choose_dir_button.config(state='disabled') auto_adjust_button.config(state='disabled') enhance_vivid_button.config(state='disabled') horror_theme_button.config(state='disabled') cinematic_theme_button.config(state='disabled') cyberpunk_theme_button.config(state='disabled') fairytale_theme_button.config(state='disabled') classic_vintage_button.config(state='disabled') dark_fantasy_button.config(state='disabled') stop_button.config(state='normal') def enable_buttons(): """Enable all buttons.""" select_directory_button.config(state='normal') choose_dir_button.config(state='normal') auto_adjust_button.config(state='normal') enhance_vivid_button.config(state='normal') horror_theme_button.config(state='normal') cinematic_theme_button.config(state='normal') cyberpunk_theme_button.config(state='normal') fairytale_theme_button.config(state='normal') classic_vintage_button.config(state='normal') dark_fantasy_button.config(state='normal') stop_button.config(state='normal') def process_images(process_func): global saved_files saved_files = set() # Initialize saved_files set if not selected_files or not save_dir_var.get(): messagebox.showerror("Input Error", "Please select images and a save directory.") enable_buttons() return save_directory = save_dir_var.get() if not os.path.exists(save_directory): os.makedirs(save_directory) for file in selected_files: if stop_event.is_set(): break base_name, ext = os.path.splitext(os.path.basename(file)) save_path = os.path.join(save_directory, f"{base_name}{ext}") counter = 1 while os.path.exists(save_path): save_path = os.path.join(save_directory, f"{base_name} ({counter}){ext}") counter += 1 try: process_func(file, save_path) saved_files.add(file) # Mark this file as saved except subprocess.CalledProcessError as e: error_messages.append(f"Error processing file {file}: {e}") update_error_count() messagebox.showinfo("Processing Complete", f"Processed {len(saved_files)} files.") def auto_adjust_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-enhance', '-contrast-stretch', '0.1x0.1%', '-sharpen', '0x1', save_path], check=True)) def enhance_vivid_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-enhance', '-contrast-stretch', '0.1x0.1%', '-sharpen', '0x1', '-modulate', '105,120,100', save_path], check=True)) def horror_theme_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-modulate', '100,90,100', '-level', '-5%,95%', '-brightness-contrast', '1x1', '-sigmoidal-contrast', '3x50%', '-noise', '3', '-sharpen', '0x1.5', '(', '+clone', '-fill', 'black', '-colorize', '5%', ')', '-compose', 'multiply', '-flatten', save_path], check=True)) def cinematic_theme_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-level', '-5%,95%', '-modulate', '100,150,100', '-colorize', '0,5,0', '-brightness-contrast', '5x-0', '-sigmoidal-contrast', '3x50%', '-sharpen', '0x1.5', '-noise', '0.1', '(', '+clone', '-blur', '0x1', ')', '-compose', 'blend', '-define', 'compose:args=10', '-composite', '(', '+clone', '-fill', 'black', '-colorize', '10%', ')', '-compose', 'multiply', '-flatten', save_path], check=True)) def cyberpunk_theme_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-modulate', '100,130,100', '-level', '-5%,95%', '-colorize', '10,0,20', '-brightness-contrast', '1x1', '-sigmoidal-contrast', '3x50%', '-sharpen', '0x0.5', '-noise', '0.5', '(', '+clone', '-blur', '0x2', ')', '-compose', 'blend', '-define', 'compose:args=20', '-composite', '(', '+clone', '-fill', 'black', '-colorize', '10%', ')', '-compose', 'multiply', '-flatten', save_path], check=True)) def fairytale_theme_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-modulate', '100,120,100', '-blur', '0x1.2', '-brightness-contrast', '2x-1', '(', '+clone', '-alpha', 'extract', '-virtual-pixel', 'black', '-blur', '0x15', '-shade', '120x45', ')', '-compose', 'softlight', '-composite', save_path], check=True)) def classic_vintage_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-modulate', '110,80,100', '-fill', '#704214', '-colorize', '10%', '-attenuate', '0.3', '+noise', 'Multiplicative', '-blur', '0x1.2', '-level', '5%,90%', '-unsharp', '0x5', '-colorspace', 'sRGB', '-brightness-contrast', '-5x15', save_path], check=True)) def dark_fantasy_images(): process_images(lambda file, save_path: subprocess.run(['magick', 'convert', file, '-modulate', '110,130,100', '-blur', '0x1', '-brightness-contrast', '5x-10', '-attenuate', '0.1', '+noise', 'Multiplicative', '-unsharp', '0x5', '-level', '5%,95%', '-modulate', '105,125,100', '-brightness-contrast', '0x1', '(', '+clone', '-fill', 'black', '-colorize', '10%', ')', '-compose', 'multiply', '-flatten', '-colorspace', 'sRGB', save_path], check=True)) def stop_filtering_func(): stop_event.set() # Signal the worker thread to stop status_var.set("Filtering stopped.") # Do not disable the Stop button to keep it always enabled # Re-enable all buttons enable_buttons() if worker_thread is not None: worker_thread.join() # Wait for worker thread to finish def return_to_menu(): stop_filtering_func() root.destroy() # Import main menu and open it from main import open_main_menu open_main_menu() def on_closing(): stop_filtering_func() return_to_menu() def show_errors(): global error_window if error_window is not None: return error_window = tk.Toplevel(root) error_window.title("Error Details") error_window.geometry("500x400") error_text = tk.Text(error_window, wrap='word') error_text.pack(expand=True, fill='both') if error_messages: for error in error_messages: error_text.insert('end', error + '\n') else: error_text.insert('end', "No errors recorded.") error_text.config(state='disabled') def on_close_error_window(): global error_window error_window.destroy() error_window = None error_window.protocol("WM_DELETE_WINDOW", on_close_error_window) def validate_number(P): if P.isdigit() or P == "": return True else: messagebox.showerror("Input Error", "Please enter only numbers.") return False validate_command = root.register(validate_number) # Create UI elements back_button = tk.Button(root, text="<-", font=('Helvetica', 14), command=return_to_menu) back_button.pack(anchor='nw', padx=10, pady=10) title_label = tk.Label(root, text="Photo Fantasy", font=('Helvetica', 16)) title_label.pack(pady=10) select_directory_button = tk.Button(root, text="Select Images", command=select_directory) select_directory_button.pack(pady=5) num_files_label = tk.Label(root, textvariable=num_files_var) num_files_label.pack(pady=5) choose_dir_button = tk.Button(root, text="Choose Save Directory", command=choose_directory) choose_dir_button.pack(pady=5) save_dir_entry = tk.Entry(root, textvariable=save_dir_var, state='readonly', justify='center') save_dir_entry.pack(pady=5, fill=tk.X) # Add Auto Adjust button auto_adjust_button = tk.Button(root, text="Auto Adjust Images", command=lambda: run_task(auto_adjust_images)) auto_adjust_button.pack(pady=10) # Add Enhance Vivid button enhance_vivid_button = tk.Button(root, text="Enhance Vivid Images", command=lambda: run_task(enhance_vivid_images)) enhance_vivid_button.pack(pady=10) # Add Horror Theme button horror_theme_button = tk.Button(root, text="Horror Theme Images", command=lambda: run_task(horror_theme_images)) horror_theme_button.pack(pady=10) # Add Cinematic Theme button cinematic_theme_button = tk.Button(root, text="Cinematic Theme Images", command=lambda: run_task(cinematic_theme_images)) cinematic_theme_button.pack(pady=10) # Add Cyberpunk Theme button cyberpunk_theme_button = tk.Button(root, text="Cyberpunk Theme Images", command=lambda: run_task(cyberpunk_theme_images)) cyberpunk_theme_button.pack(pady=10) # Add Fairytale Theme button fairytale_theme_button = tk.Button(root, text="Fairytale Theme Images", command=lambda: run_task(fairytale_theme_images)) fairytale_theme_button.pack(pady=10) # Add Classic Vintage button classic_vintage_button = tk.Button(root, text="Classic Vintage Images", command=lambda: run_task(classic_vintage_images)) classic_vintage_button.pack(pady=10) # Add Dark Fantasy button dark_fantasy_button = tk.Button(root, text="Dark Fantasy Images", command=lambda: run_task(dark_fantasy_images)) dark_fantasy_button.pack(pady=10) # Add label and entry for thread count thread_count_label = tk.Label(root, text="Number of Threads:") thread_count_label.pack(pady=5) thread_count_entry = tk.Entry(root, textvariable=thread_count_var, validate="key", validatecommand=(validate_command, '%P'), justify='center', width=4) thread_count_entry.pack(pady=5) stop_button = tk.Button(root, text="Stop", command=stop_filtering_func) # Ensure stop button is a global variable stop_button.pack(pady=5) errors_button = tk.Button(root, textvariable=errors_var, command=show_errors) errors_button.pack(pady=5) progress_bar = ttk.Progressbar(root, variable=progress, maximum=100) progress_bar.pack(pady=5, fill=tk.X) status_label = tk.Label(root, textvariable=status_var, fg="green") status_label.pack(pady=5) center_window(root) root.protocol("WM_DELETE_WINDOW", on_closing) root.mainloop() if __name__ == "__main__": open_photo_fantasy()