| import tkinter as tk |
| from tkinter import filedialog, messagebox, ttk |
| import os |
| import threading |
| import queue |
| import shutil |
| import subprocess |
|
|
| |
| 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 |
|
|
| |
| root = tk.Tk() |
| root.title("Photo Fantasy") |
|
|
| |
| 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="1") |
| progress = tk.IntVar() |
| q = queue.Queue() |
|
|
| def center_window(window): |
| window.update_idletasks() |
| width = window.winfo_width() + 120 |
| 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 |
| |
| 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) |
| 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() |
|
|
| 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) |
| 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() |
| status_var.set("Filtering stopped.") |
| |
| |
| enable_buttons() |
| if worker_thread is not None: |
| worker_thread.join() |
|
|
| def return_to_menu(): |
| stop_filtering_func() |
| root.destroy() |
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| auto_adjust_button = tk.Button(root, text="Auto Adjust Images", command=lambda: run_task(auto_adjust_images)) |
| auto_adjust_button.pack(pady=10) |
|
|
| |
| enhance_vivid_button = tk.Button(root, text="Enhance Vivid Images", command=lambda: run_task(enhance_vivid_images)) |
| enhance_vivid_button.pack(pady=10) |
|
|
| |
| horror_theme_button = tk.Button(root, text="Horror Theme Images", command=lambda: run_task(horror_theme_images)) |
| horror_theme_button.pack(pady=10) |
|
|
| |
| cinematic_theme_button = tk.Button(root, text="Cinematic Theme Images", command=lambda: run_task(cinematic_theme_images)) |
| cinematic_theme_button.pack(pady=10) |
|
|
| |
| cyberpunk_theme_button = tk.Button(root, text="Cyberpunk Theme Images", command=lambda: run_task(cyberpunk_theme_images)) |
| cyberpunk_theme_button.pack(pady=10) |
|
|
| |
| fairytale_theme_button = tk.Button(root, text="Fairytale Theme Images", command=lambda: run_task(fairytale_theme_images)) |
| fairytale_theme_button.pack(pady=10) |
|
|
| |
| classic_vintage_button = tk.Button(root, text="Classic Vintage Images", command=lambda: run_task(classic_vintage_images)) |
| classic_vintage_button.pack(pady=10) |
|
|
| |
| dark_fantasy_button = tk.Button(root, text="Dark Fantasy Images", command=lambda: run_task(dark_fantasy_images)) |
| dark_fantasy_button.pack(pady=10) |
|
|
| |
| 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) |
| 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() |
|
|