|
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() |
|
|