|
|
import tkinter as tk
|
|
|
from tkinter import filedialog, messagebox, scrolledtext, ttk
|
|
|
from PIL import Image, ImageTk, ImageGrab
|
|
|
import threading
|
|
|
import os
|
|
|
import numpy as np
|
|
|
import pandas as pd
|
|
|
import cv2
|
|
|
import tempfile
|
|
|
import io
|
|
|
import json
|
|
|
import sys
|
|
|
import time
|
|
|
from datetime import datetime
|
|
|
import subprocess
|
|
|
import win32clipboard
|
|
|
import random
|
|
|
|
|
|
|
|
|
def resource_path(relative_path):
|
|
|
""" PyInstaller์ ์ํด ์์ ํด๋์ ์์ฑ๋ ๋ฆฌ์์ค์ ์ ๋ ๊ฒฝ๋ก๋ฅผ ๋ฐํํฉ๋๋ค. """
|
|
|
try:
|
|
|
base_path = sys._MEIPASS
|
|
|
except Exception:
|
|
|
base_path = os.path.abspath(".")
|
|
|
return os.path.join(base_path, relative_path)
|
|
|
|
|
|
class WD14TaggerGUI:
|
|
|
def __init__(self, root):
|
|
|
self.root = root
|
|
|
self.root.title("WD14 Auto Prompt Generator (aiart) v2")
|
|
|
|
|
|
self.root.minsize(680, 800)
|
|
|
self.root.resizable(True, True)
|
|
|
|
|
|
self.SETTINGS_FILE = "tagger_app_settings.json"
|
|
|
|
|
|
|
|
|
self.current_image_path = None
|
|
|
self.session = None
|
|
|
self.tags_df = None
|
|
|
self.is_generating = False
|
|
|
self.save_counter = 1
|
|
|
self.last_generated_image = None
|
|
|
self.temp_image_path = os.path.join(tempfile.gettempdir(), "wd14_temp_generated.png")
|
|
|
self.save_session_folder = datetime.now().strftime('%Y%m%d_%H%M')
|
|
|
|
|
|
|
|
|
self.general_threshold_var = tk.DoubleVar(value=0.5)
|
|
|
self.char_threshold_var = tk.DoubleVar(value=0.85)
|
|
|
self.rm_c_var = tk.BooleanVar()
|
|
|
self.rm_color_var = tk.BooleanVar()
|
|
|
self.rm_clothes_var = tk.BooleanVar()
|
|
|
self.webui_mode_var = tk.BooleanVar()
|
|
|
self.instant_inference_var = tk.BooleanVar(value=False)
|
|
|
self.show_removed_tags_var = tk.BooleanVar(value=True)
|
|
|
self.show_ignore_tags_var = tk.BooleanVar(value=False)
|
|
|
self.prompt_active_var = tk.BooleanVar(value=False)
|
|
|
self.expand_ui_var = tk.BooleanVar(value=False)
|
|
|
self.sampler_var = tk.StringVar()
|
|
|
self.resolution_var = tk.StringVar()
|
|
|
self.steps_var = tk.StringVar(value="28")
|
|
|
self.scale_var = tk.StringVar(value="5.5")
|
|
|
self.seed_var = tk.StringVar(value="-1")
|
|
|
self.cfg_rescale_var = tk.StringVar(value="0.25")
|
|
|
self.nai_token_var = tk.StringVar()
|
|
|
self.model_var = tk.StringVar()
|
|
|
self.auto_save_var = tk.BooleanVar(value=True)
|
|
|
self.instant_generate_var = tk.BooleanVar(value=False)
|
|
|
self.auto_rating_tag_var = tk.BooleanVar(value=True)
|
|
|
self.character_prompts = []
|
|
|
self.char_check_vars = [tk.BooleanVar(value=False) for _ in range(3)]
|
|
|
self.char_check_vars[0].set(True)
|
|
|
self.last_generated_seed = random.randint(0, 9999999999)
|
|
|
|
|
|
self.init_filter_data()
|
|
|
self.setup_ui()
|
|
|
self.load_model()
|
|
|
self.show_initial_preview()
|
|
|
self.setup_drag_drop()
|
|
|
self.load_settings()
|
|
|
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
|
|
|
self.root.bind('<Control-v>', self.handle_paste)
|
|
|
|
|
|
def _create_thumbnail(self, pil_image, size=512):
|
|
|
"""PIL ์ด๋ฏธ์ง๋ฅผ ๋ฐ์ ํฐ ๋ฐฐ๊ฒฝ์ ์ ์ฌ๊ฐํ ์ธ๋ค์ผ์ ์์ฑํฉ๋๋ค."""
|
|
|
|
|
|
bg_size = max(pil_image.size)
|
|
|
thumbnail_bg = Image.new('RGB', (bg_size, bg_size), 'white')
|
|
|
|
|
|
|
|
|
paste_x = (bg_size - pil_image.width) // 2
|
|
|
paste_y = (bg_size - pil_image.height) // 2
|
|
|
thumbnail_bg.paste(pil_image, (paste_x, paste_y))
|
|
|
|
|
|
|
|
|
thumbnail_bg.thumbnail((size, size), Image.Resampling.LANCZOS)
|
|
|
return thumbnail_bg
|
|
|
|
|
|
def _update_timer(self):
|
|
|
"""์์ฑ ๋ฒํผ์ ํ์ด๋จธ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค."""
|
|
|
if self.is_generating:
|
|
|
elapsed = time.time() - self.generation_start_time
|
|
|
self.generation_btn.config(text=f"์์ฑ ์ค: {int(elapsed)}s")
|
|
|
self.root.after(1000, self._update_timer)
|
|
|
|
|
|
def _copy_image_to_clipboard(self):
|
|
|
"""์์ฑ๋ ์ด๋ฏธ์ง๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌํฉ๋๋ค (Windows ์ ์ฉ)."""
|
|
|
if self.last_generated_image is None:
|
|
|
messagebox.showinfo("์ ๋ณด", "๋ณต์ฌํ ์ด๋ฏธ์ง๊ฐ ์์ต๋๋ค.")
|
|
|
return
|
|
|
|
|
|
if win32clipboard is None:
|
|
|
messagebox.showerror("์ค๋ฅ", "์ด๋ฏธ์ง ๋ณต์ฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด 'pywin32' ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค.\n(pip install pywin32)")
|
|
|
return
|
|
|
|
|
|
if sys.platform == "win32":
|
|
|
try:
|
|
|
output = io.BytesIO()
|
|
|
self.last_generated_image.convert("RGB").save(output, "BMP")
|
|
|
data = output.getvalue()[14:]
|
|
|
output.close()
|
|
|
win32clipboard.OpenClipboard()
|
|
|
win32clipboard.EmptyClipboard()
|
|
|
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
|
|
|
win32clipboard.CloseClipboard()
|
|
|
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("๋ณต์ฌ ์คํจ", f"์ด๋ฏธ์ง ๋ณต์ฌ ์ค ์ค๋ฅ ๋ฐ์: {e}")
|
|
|
else:
|
|
|
messagebox.showinfo("์ ๋ณด", "์ด๋ฏธ์ง ๋ณต์ฌ ๊ธฐ๋ฅ์ Windows์์๋ง ์ง์๋ฉ๋๋ค.")
|
|
|
|
|
|
def _copy_file_object_to_clipboard(self):
|
|
|
"""์์ ์ ์ฅ๋ ์ด๋ฏธ์ง ํ์ผ์ ํด๋ฆฝ๋ณด๋์ ํ์ผ ๊ฐ์ฒด๋ก ๋ณต์ฌํฉ๋๋ค (Windows ์ ์ฉ)."""
|
|
|
if not os.path.exists(self.temp_image_path):
|
|
|
messagebox.showinfo("์ ๋ณด", "๋ณต์ฌํ ํ์ผ์ด ์์ต๋๋ค.")
|
|
|
return
|
|
|
|
|
|
if sys.platform == "win32":
|
|
|
try:
|
|
|
command = f"powershell -command \"Get-Item '{self.temp_image_path}' | Set-Clipboard\""
|
|
|
subprocess.run(command, check=True, shell=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
|
|
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("๋ณต์ฌ ์คํจ", f"ํ์ผ ๋ณต์ฌ ์ค ์ค๋ฅ ๋ฐ์: {e}")
|
|
|
else:
|
|
|
messagebox.showinfo("์ ๋ณด", "ํ์ผ ๋ณต์ฌ ๊ธฐ๋ฅ์ Windows์์๋ง ์ง์๋ฉ๋๋ค.")
|
|
|
|
|
|
def _show_context_menu(self, event):
|
|
|
"""์ด๋ฏธ์ง ๋ ์ด๋ธ ์์์ ์ฐํด๋ฆญ ์ ์ปจํ
์คํธ ๋ฉ๋ด๋ฅผ ํ์ํฉ๋๋ค."""
|
|
|
if self.last_generated_image:
|
|
|
self.image_context_menu.tk_popup(event.x_root, event.y_root)
|
|
|
|
|
|
def _save_image_manual(self):
|
|
|
"""(์์ ๋จ) ์ด๋ฏธ์ง๋ฅผ ์๋ ์ ์ฅ ํด๋์ ์์ฐจ์ ์ผ๋ก ์ ์ฅํฉ๋๋ค."""
|
|
|
if self.last_generated_image is None:
|
|
|
messagebox.showinfo("์ ๋ณด", "์ ์ฅํ ์ด๋ฏธ์ง๊ฐ ์์ต๋๋ค.")
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
save_dir = os.path.join("Output_WD14", self.save_session_folder)
|
|
|
os.makedirs(save_dir, exist_ok=True)
|
|
|
file_path = os.path.join(save_dir, f"{self.save_counter:05d}.png")
|
|
|
self.last_generated_image.save(file_path, "PNG")
|
|
|
|
|
|
|
|
|
self.save_counter += 1
|
|
|
self.save_btn.config(state=tk.DISABLED)
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ ์ฅ ์คํจ", f"์ด๋ฏธ์ง ์ ์ฅ ์ค ์ค๋ฅ ๋ฐ์: {e}")
|
|
|
|
|
|
def _open_save_folder(self):
|
|
|
"""์๋ ์ ์ฅ ํด๋๋ฅผ ์ฝ๋๋ค."""
|
|
|
session_path = os.path.join("Output_WD14", self.save_session_folder)
|
|
|
base_path = "Output_WD14"
|
|
|
|
|
|
try:
|
|
|
if os.path.isdir(session_path):
|
|
|
path_to_open = session_path
|
|
|
elif os.path.isdir(base_path):
|
|
|
path_to_open = base_path
|
|
|
else:
|
|
|
messagebox.showinfo("์ ๋ณด", f"'{base_path}' ํด๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.")
|
|
|
return
|
|
|
|
|
|
if sys.platform == "win32":
|
|
|
os.startfile(os.path.realpath(path_to_open))
|
|
|
elif sys.platform == "darwin":
|
|
|
subprocess.Popen(["open", os.path.realpath(path_to_open)])
|
|
|
else:
|
|
|
subprocess.Popen(["xdg-open", os.path.realpath(path_to_open)])
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ค๋ฅ", f"ํด๋๋ฅผ ์ฌ๋ ์ค ์ค๋ฅ ๋ฐ์: {e}")
|
|
|
|
|
|
|
|
|
def toggle_expand_ui(self):
|
|
|
"""์ฒดํฌ๋ฐ์ค ์ํ์ ๋ฐ๋ผ ์ฐ์ธก UI์ '์ฆ์ ์์ฑ' ๋ฒํผ์ ๊ฐ์์ฑ์ ์ ์ดํฉ๋๋ค."""
|
|
|
main_frame = self.right_pane.master
|
|
|
if self.expand_ui_var.get():
|
|
|
main_frame.grid_columnconfigure(1, weight=1, minsize=400)
|
|
|
self.right_pane.grid(row=0, column=1, sticky="nsew")
|
|
|
self.root.minsize(1220, 800)
|
|
|
|
|
|
self.instant_generate_cb.pack(side=tk.LEFT, padx=(5,0))
|
|
|
else:
|
|
|
self.right_pane.grid_forget()
|
|
|
main_frame.grid_columnconfigure(1, weight=0, minsize=0)
|
|
|
self.root.minsize(680, 800)
|
|
|
|
|
|
self.instant_generate_cb.pack_forget()
|
|
|
|
|
|
|
|
|
def show_generation_preview(self):
|
|
|
"""์ฐ์ธก ํจ๋์ 512x512 ํฐ์ ์ด๋ฏธ์ง๋ฅผ ํ์ํฉ๋๋ค."""
|
|
|
try:
|
|
|
white_image = Image.new('RGB', (512, 512), 'white')
|
|
|
photo = ImageTk.PhotoImage(white_image)
|
|
|
|
|
|
self.generation_image_label.image = photo
|
|
|
self.generation_image_label.config(image=photo)
|
|
|
except Exception as e:
|
|
|
self.generation_image_label.config(text=f"์ด๋ฏธ์ง ํ์ ์ค๋ฅ: {e}")
|
|
|
|
|
|
def random_seed(self):
|
|
|
if int(self.seed_var.get()) == -1:
|
|
|
self.seed_var.set(str(self.last_generated_seed))
|
|
|
else:
|
|
|
self.seed_var.set("-1")
|
|
|
|
|
|
def setup_ui(self):
|
|
|
|
|
|
self.root.grid_columnconfigure(0, weight=1)
|
|
|
self.root.grid_rowconfigure(0, weight=1)
|
|
|
|
|
|
main_frame = ttk.Frame(self.root, padding="10")
|
|
|
main_frame.grid(row=0, column=0, sticky="nsew")
|
|
|
|
|
|
main_frame.grid_columnconfigure(0, weight=1)
|
|
|
main_frame.grid_columnconfigure(1, weight=1, minsize=400)
|
|
|
|
|
|
main_frame.grid_rowconfigure(0, weight=1)
|
|
|
|
|
|
|
|
|
left_pane = ttk.Frame(main_frame)
|
|
|
left_pane.grid(row=0, column=0, sticky="nsew")
|
|
|
left_pane.grid_columnconfigure(0, weight=1)
|
|
|
left_pane.grid_rowconfigure(2, weight=4)
|
|
|
left_pane.grid_rowconfigure(3, weight=3)
|
|
|
|
|
|
|
|
|
load_frame = ttk.Frame(left_pane)
|
|
|
load_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
|
|
self.load_btn = ttk.Button(load_frame, text="์ด๋ฏธ์ง ๋ถ๋ฌ์ค๊ธฐ", command=self.load_image)
|
|
|
self.load_btn.pack(side="left", padx=(0, 5))
|
|
|
self.clipboard_btn = ttk.Button(load_frame, text="์ด๋ฏธ์ง ๋ถ๋ฌ์ค๊ธฐ (ํด๋ฆฝ๋ณด๋)", command=self.load_from_clipboard)
|
|
|
self.clipboard_btn.pack(side="left")
|
|
|
self.expand_ui_cb = ttk.Checkbutton(load_frame, text="์์ฑ UI ํ์ฅ",
|
|
|
variable=self.expand_ui_var,
|
|
|
command=self.toggle_expand_ui)
|
|
|
self.expand_ui_cb.pack(side="left", padx=(10, 0))
|
|
|
content_frame = ttk.Frame(left_pane)
|
|
|
content_frame.grid(row=1, column=0, sticky="new")
|
|
|
content_frame.grid_columnconfigure(1, weight=1)
|
|
|
self.preview_frame = ttk.LabelFrame(content_frame, text="๋ฏธ๋ฆฌ๋ณด๊ธฐ", padding="10")
|
|
|
self.preview_frame.grid(row=0, column=0, sticky="n", padx=(0, 10))
|
|
|
self.image_label = ttk.Label(self.preview_frame)
|
|
|
self.image_label.grid(row=0, column=0)
|
|
|
settings_frame = ttk.LabelFrame(content_frame, text="์ค์ ", padding="10")
|
|
|
settings_frame.grid(row=0, column=1, sticky="nsew")
|
|
|
settings_frame.grid_columnconfigure(1, weight=1)
|
|
|
threshold_frame = ttk.Frame(settings_frame)
|
|
|
threshold_frame.grid(row=0, column=0, columnspan=2, sticky="ew")
|
|
|
threshold_frame.grid_columnconfigure(1, weight=1)
|
|
|
ttk.Label(threshold_frame, text="ํ๊ทธ ์๊ณ๊ฐ:").grid(row=0, column=0, sticky="w")
|
|
|
ttk.Spinbox(threshold_frame, from_=0.0, to=1.0, increment=0.05,
|
|
|
textvariable=self.general_threshold_var, width=15).grid(row=0, column=1, sticky="we", pady=(0, 5))
|
|
|
|
|
|
extract_control_frame = ttk.Frame(settings_frame)
|
|
|
extract_control_frame.grid(row=1, column=0, columnspan=2, pady=5, sticky="ew")
|
|
|
|
|
|
self.extract_btn = ttk.Button(extract_control_frame, text="ํ๊ทธ ์ถ์ถ", command=self.extract_tags, state='disabled')
|
|
|
self.extract_btn.pack(side=tk.LEFT, padx=(0, 10))
|
|
|
|
|
|
self.instant_inference_cb = ttk.Checkbutton(extract_control_frame, text="์ฆ์ ์ถ๋ก ", variable=self.instant_inference_var)
|
|
|
self.instant_inference_cb.pack(side=tk.LEFT)
|
|
|
|
|
|
|
|
|
self.instant_generate_cb = ttk.Checkbutton(extract_control_frame, text="์ฆ์ ์์ฑ", variable=self.instant_generate_var)
|
|
|
|
|
|
filter_frame = ttk.LabelFrame(settings_frame, text="ํ๊ทธ ํํฐ๋ง", padding="5")
|
|
|
filter_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(5, 0))
|
|
|
ttk.Checkbutton(filter_frame, text="์บ๋ฆญํฐ ํน์ง+์ด๋ฆ ์ ๊ฑฐ",
|
|
|
variable=self.rm_c_var).pack(anchor="w")
|
|
|
ttk.Checkbutton(filter_frame, text="์๋ฅ ์์ ์ ๊ฑฐ",
|
|
|
variable=self.rm_color_var).pack(anchor="w")
|
|
|
ttk.Checkbutton(filter_frame, text="WEBUI/Comfy ๋ชจ๋",
|
|
|
variable=self.webui_mode_var).pack(anchor="w")
|
|
|
prompt_outer_frame = ttk.Frame(settings_frame)
|
|
|
prompt_outer_frame.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(10, 0))
|
|
|
prompt_outer_frame.grid_columnconfigure(0, weight=1)
|
|
|
self.prompt_active_cb = ttk.Checkbutton(prompt_outer_frame, text="์ ํ/ํํ ํ๋กฌํํธ ํ์ฑ",
|
|
|
variable=self.prompt_active_var,
|
|
|
command=self.toggle_prompts)
|
|
|
self.prompt_active_cb.grid(row=0, column=0, sticky="w")
|
|
|
self.prompt_notebook = ttk.Notebook(prompt_outer_frame)
|
|
|
prefix_frame = ttk.Frame(self.prompt_notebook, padding=5)
|
|
|
self.prompt_notebook.add(prefix_frame, text="์ ํ ํ๋กฌํํธ")
|
|
|
self.prefix_text = scrolledtext.ScrolledText(prefix_frame, height=3, width=30)
|
|
|
self.prefix_text.pack(fill="both", expand=True)
|
|
|
suffix_frame = ttk.Frame(self.prompt_notebook, padding=5)
|
|
|
self.prompt_notebook.add(suffix_frame, text="ํํ ํ๋กฌํํธ")
|
|
|
self.suffix_text = scrolledtext.ScrolledText(suffix_frame, height=3, width=30)
|
|
|
self.suffix_text.pack(fill="both", expand=True)
|
|
|
self.toggle_prompts()
|
|
|
result_frame = ttk.LabelFrame(left_pane, text="์ถ์ถ๋ ํ๊ทธ", padding="10")
|
|
|
result_frame.grid(row=2, column=0, sticky="nsew", pady=(10, 0))
|
|
|
result_frame.grid_columnconfigure(0, weight=1)
|
|
|
result_frame.grid_rowconfigure(0, weight=3)
|
|
|
result_frame.grid_rowconfigure(3, weight=2)
|
|
|
self.result_text = scrolledtext.ScrolledText(result_frame, height=7, wrap=tk.WORD)
|
|
|
self.result_text.grid(row=0, column=0, sticky="nsew")
|
|
|
self.result_text.tag_config("prompt", foreground="grey")
|
|
|
copy_frame = ttk.Frame(result_frame)
|
|
|
copy_frame.grid(row=1, column=0, pady=(10, 5), sticky="w")
|
|
|
ttk.Button(copy_frame, text="์ ์ฒด ๋ณต์ฌ", command=self.copy_all_tags).pack(side="left", padx=(0, 5))
|
|
|
ttk.Button(copy_frame, text="2๋ฒ ํ๊ทธ๋ถํฐ ๋ณต์ฌ", command=self.copy_from_second).pack(side="left", padx=5)
|
|
|
ttk.Button(copy_frame, text="3๋ฒ ํ๊ทธ๋ถํฐ ๋ณต์ฌ", command=self.copy_from_third).pack(side="left", padx=5)
|
|
|
auto_rating_cb = ttk.Checkbutton(copy_frame, text="nsfw/safe ํ๊ทธ ์๋ ๋ฐ์", variable=self.auto_rating_tag_var)
|
|
|
auto_rating_cb.pack(side="left", padx=10)
|
|
|
self.removed_tags_cb = ttk.Checkbutton(result_frame, text="์ ๊ฑฐ๋ ํ๊ทธ ๋ณด๊ธฐ",
|
|
|
variable=self.show_removed_tags_var,
|
|
|
command=self.toggle_removed_tags)
|
|
|
self.removed_tags_cb.grid(row=2, column=0, sticky="w", pady=(10, 2))
|
|
|
self.removed_tags_text = scrolledtext.ScrolledText(result_frame, height=4, wrap=tk.WORD, state=tk.DISABLED)
|
|
|
self.removed_tags_text.tag_config("ignored", foreground="red")
|
|
|
self.toggle_removed_tags()
|
|
|
bottom_frame = ttk.Frame(left_pane)
|
|
|
bottom_frame.grid(row=3, column=0, sticky="nsew", pady=(10, 0))
|
|
|
bottom_frame.grid_columnconfigure(0, weight=1)
|
|
|
bottom_frame.grid_rowconfigure(1, weight=1)
|
|
|
bottom_control_frame = ttk.Frame(bottom_frame)
|
|
|
bottom_control_frame.grid(row=0, column=0, sticky="ew")
|
|
|
self.ignore_tags_cb = ttk.Checkbutton(bottom_control_frame, text="๋ฌด์ํ ํ๊ทธ ํธ์ง (์ผํ๋ก ๊ตฌ๋ถ)",
|
|
|
variable=self.show_ignore_tags_var,
|
|
|
command=self.toggle_ignore_tags)
|
|
|
self.ignore_tags_cb.pack(side="left", anchor="w")
|
|
|
self.save_settings_btn = ttk.Button(bottom_control_frame, text="์ค์ ์ ์ฅ", command=self.save_settings)
|
|
|
self.save_settings_btn.pack(side="right", anchor="e")
|
|
|
self.ignore_tags_frame = ttk.Frame(bottom_frame)
|
|
|
self.ignore_tags_frame.grid_columnconfigure(0, weight=1)
|
|
|
self.ignore_tags_frame.grid_rowconfigure(0, weight=1)
|
|
|
self.ignore_tags_text = scrolledtext.ScrolledText(self.ignore_tags_frame, height=4, wrap=tk.WORD)
|
|
|
self.ignore_tags_text.grid(row=0, column=0, sticky="nsew")
|
|
|
self.toggle_ignore_tags()
|
|
|
|
|
|
|
|
|
|
|
|
self.right_pane = ttk.Frame(main_frame)
|
|
|
self.right_pane.grid_rowconfigure(0, weight=1)
|
|
|
self.right_pane.grid_rowconfigure(2, weight=0)
|
|
|
self.right_pane.grid_columnconfigure(0, weight=1)
|
|
|
|
|
|
generation_preview_frame = ttk.LabelFrame(self.right_pane, text="์์ฑ ๋ฏธ๋ฆฌ๋ณด๊ธฐ", padding="5")
|
|
|
generation_preview_frame.grid(row=0, column=0, sticky="nsew")
|
|
|
generation_preview_frame.grid_columnconfigure(0, weight=1)
|
|
|
generation_preview_frame.grid_rowconfigure(0, weight=1)
|
|
|
|
|
|
self.generation_image_label = ttk.Label(generation_preview_frame, anchor="center")
|
|
|
self.generation_image_label.grid(row=0, column=0, sticky="nsew")
|
|
|
self.generation_image_label.bind("<Button-3>", self._show_context_menu)
|
|
|
|
|
|
|
|
|
self.image_context_menu = tk.Menu(self.root, tearoff=0)
|
|
|
self.image_context_menu.add_command(label="์ด๋ฏธ์ง๋ฅผ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ", command=self._copy_image_to_clipboard)
|
|
|
self.image_context_menu.add_command(label="ํ์ผ์ ํด๋ฆฝ๋ณด๋์ ๋ณต์ฌ", command=self._copy_file_object_to_clipboard)
|
|
|
self.image_context_menu.add_command(label="๊ธฐ๋ณธ ๋ทฐ์ด๋ก ์ด๋ฏธ์ง ์ด๊ธฐ", command=self._show_image_with_pil)
|
|
|
|
|
|
|
|
|
generation_button_frame = ttk.Frame(self.right_pane)
|
|
|
generation_button_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0), padx=10)
|
|
|
|
|
|
self.folder_btn = ttk.Button(generation_button_frame, text="๐", width=2, command=self._open_save_folder)
|
|
|
self.folder_btn.pack(side="right")
|
|
|
self.save_btn = ttk.Button(generation_button_frame, text="Save ๐พ", width=8, command=self._save_image_manual)
|
|
|
self.save_btn.pack(side="right", padx=(5, 0))
|
|
|
self.copy_btn = ttk.Button(generation_button_frame, text=" Copy ๐๏ธ", width=8, command=self._copy_image_to_clipboard)
|
|
|
self.copy_btn.pack(side="right", padx=(5, 0))
|
|
|
|
|
|
self.generation_btn = ttk.Button(generation_button_frame, text="Generation", command=self.start_generation)
|
|
|
self.generation_btn.pack(side="left", fill="x", expand=True)
|
|
|
|
|
|
|
|
|
self.settings_notebook = ttk.Notebook(self.right_pane)
|
|
|
self.settings_notebook.grid(row=2, column=0, sticky="nsew", pady=(5, 0), padx=10)
|
|
|
|
|
|
|
|
|
basic_settings_tab = ttk.Frame(self.settings_notebook, padding="10")
|
|
|
char_prompt_tab = ttk.Frame(self.settings_notebook, padding=(10, 10, 10, 0))
|
|
|
account_settings_tab = ttk.Frame(self.settings_notebook, padding="10")
|
|
|
|
|
|
|
|
|
self.settings_notebook.add(basic_settings_tab, text="๊ธฐ๋ณธ์ค์ ")
|
|
|
self.settings_notebook.add(char_prompt_tab, text="์บ๋ฆญํฐ ํ๋กฌํํธ")
|
|
|
self.settings_notebook.add(account_settings_tab, text="NAI์ค์ ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
basic_settings_tab.grid_columnconfigure(1, weight=1); basic_settings_tab.grid_columnconfigure(3, weight=1); basic_settings_tab.grid_columnconfigure(5, weight=1)
|
|
|
sampler_list = ["k_euler + native", "k_euler + karras", "k_euler + exponential", "k_euler_ancestral + native", "k_euler_ancestral + karras","k_euler_ancestral + exponential", "k_dpmpp_2s_ancestral + native", "k_dpmpp_2s_ancestral + karras", "k_dpmpp_2s_ancestral + exponential", "k_dpmpp_sde + native", "k_dpmpp_sde + karras", "k_dpmpp_sde + exponential", "k_dpmpp_2m + native", "k_dpmpp_2m + karras", "k_dpmpp_2m + exponential","k_dpmpp_2m_sde + native", "k_dpmpp_2m_sde + karras", "k_dpmpp_2m_sde + exponential"]
|
|
|
ttk.Label(basic_settings_tab, text="Sampler:").grid(row=0, column=0, sticky="w", padx=(0, 5)); sampler_cb = ttk.Combobox(basic_settings_tab, values=sampler_list, textvariable=self.sampler_var, state="readonly", width=25); sampler_cb.grid(row=0, column=1, sticky="ew"); sampler_cb.set(sampler_list[4])
|
|
|
ttk.Label(basic_settings_tab, text="Steps:").grid(row=0, column=2, sticky="w", padx=(10, 5)); ttk.Entry(basic_settings_tab, textvariable=self.steps_var, width=5).grid(row=0, column=3, sticky="ew")
|
|
|
ttk.Label(basic_settings_tab, text="Scale:").grid(row=0, column=4, sticky="w", padx=(10, 5)); ttk.Entry(basic_settings_tab, textvariable=self.scale_var, width=5).grid(row=0, column=5, sticky="ew")
|
|
|
resolution_list = ["1024 x 1024", "960 x 1088", "896 x 1152", "832 x 1216", "1088 x 960", "1152 x 896", "1216 x 832", "์๋ ํด์๋"]
|
|
|
ttk.Label(basic_settings_tab, text="Resolution:").grid(row=1, column=0, sticky="w", pady=(5, 0), padx=(0, 5)); resolution_cb = ttk.Combobox(basic_settings_tab, values=resolution_list, textvariable=self.resolution_var, state="readonly", width=15); resolution_cb.grid(row=1, column=1, sticky="ew", pady=(5, 0)); resolution_cb.set(resolution_list[2])
|
|
|
ttk.Label(basic_settings_tab, text="CFG Rescale:").grid(row=1, column=2, sticky="w", pady=(5, 0), padx=(10, 5)); ttk.Entry(basic_settings_tab, textvariable=self.cfg_rescale_var, width=5).grid(row=1, column=3, sticky="ew", pady=(5, 0))
|
|
|
ttk.Label(basic_settings_tab, text="Seed:").grid(row=1, column=4, sticky="w", pady=(5,0), padx=(10, 5)); seed_frame = ttk.Frame(basic_settings_tab); seed_frame.grid(row=1, column=5, sticky="ew", pady=(5,0)); seed_frame.grid_columnconfigure(0, weight=1); ttk.Entry(seed_frame, textvariable=self.seed_var).pack(side="left", fill="x", expand=True); ttk.Button(seed_frame, text="๐ฒ", width=2, command=self.random_seed).pack(side="right", padx=(5,0))
|
|
|
uc_header_frame = ttk.Frame(basic_settings_tab)
|
|
|
uc_header_frame.grid(row=2, column=0, columnspan=6, sticky="ew", pady=(8, 2))
|
|
|
|
|
|
|
|
|
ttk.Label(uc_header_frame, text="Negative Prompt (UC):").pack(side="left")
|
|
|
|
|
|
auto_save_cb = ttk.Checkbutton(uc_header_frame, text="NAI ์์ฑ ์ด๋ฏธ์ง๋ฅผ ์๋์ผ๋ก ์ ์ฅ", variable=self.auto_save_var)
|
|
|
auto_save_cb.pack(side="right")
|
|
|
|
|
|
|
|
|
self.uc_text = scrolledtext.ScrolledText(basic_settings_tab, height=4, wrap=tk.WORD)
|
|
|
self.uc_text.grid(row=3, column=0, columnspan=6, sticky="nsew")
|
|
|
|
|
|
|
|
|
char_prompt_tab.grid_columnconfigure(0, weight=1)
|
|
|
for i in range(3):
|
|
|
|
|
|
char_frame = ttk.Frame(char_prompt_tab, padding=(0, 0, 0, 5))
|
|
|
|
|
|
char_frame.grid(row=i, column=0, sticky="ew", pady=2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
check_btn = ttk.Checkbutton(char_frame, text=f"C{i+1}", variable=self.char_check_vars[i],
|
|
|
command=self.update_character_layout)
|
|
|
|
|
|
prompt_label = ttk.Label(char_frame, text="P:")
|
|
|
prompt_text = scrolledtext.ScrolledText(char_frame, height=1, wrap=tk.WORD, width=46)
|
|
|
|
|
|
uc_label = ttk.Label(char_frame, text="UC:")
|
|
|
uc_text = scrolledtext.ScrolledText(char_frame, height=1, wrap=tk.WORD, width=24)
|
|
|
|
|
|
|
|
|
check_btn.grid(row=0, column=0, sticky="w", padx=(0, 5))
|
|
|
prompt_label.grid(row=0, column=1, sticky="w", padx=(0, 2))
|
|
|
|
|
|
prompt_text.grid(row=0, column=2, sticky="ew")
|
|
|
|
|
|
uc_label.grid(row=0, column=3, sticky="w", padx=(5, 2))
|
|
|
uc_text.grid(row=0, column=4, sticky="ew")
|
|
|
|
|
|
self.character_prompts.append({
|
|
|
"frame": char_frame,
|
|
|
"check_var": self.char_check_vars[i],
|
|
|
"prompt_widget": prompt_text,
|
|
|
"uc_widget": uc_text
|
|
|
})
|
|
|
|
|
|
|
|
|
token_frame = ttk.LabelFrame(account_settings_tab, text="Novel AI ์๊ตฌ ํ ํฐ ์
๋ ฅ")
|
|
|
token_frame.pack(fill="x", expand=False, padx=5, pady=5)
|
|
|
token_frame.grid_columnconfigure(0, weight=1)
|
|
|
token_entry = ttk.Entry(token_frame, textvariable=self.nai_token_var, show="*")
|
|
|
token_entry.grid(row=0, column=0, sticky="ew", padx=(5,5), pady=5)
|
|
|
verify_btn = ttk.Button(token_frame, text="๊ฒ์ฆ", command=self.verify_token)
|
|
|
verify_btn.grid(row=0, column=1, sticky="e", padx=(0,5), pady=5)
|
|
|
model_frame = ttk.LabelFrame(account_settings_tab, text="๋ชจ๋ธ ์ค์ ")
|
|
|
model_frame.pack(fill="x", expand=False, padx=5, pady=(10, 5))
|
|
|
model_frame.grid_columnconfigure(0, weight=1)
|
|
|
model_list = ["nai-diffusion-4-5-full"]
|
|
|
model_cb = ttk.Combobox(model_frame, values=model_list, textvariable=self.model_var, state="disabled")
|
|
|
model_cb.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
|
|
|
model_cb.set(model_list[0])
|
|
|
|
|
|
|
|
|
|
|
|
self.show_generation_preview()
|
|
|
self.update_character_layout()
|
|
|
self.toggle_expand_ui()
|
|
|
|
|
|
def update_character_layout(self):
|
|
|
for p_info in self.character_prompts:
|
|
|
if p_info['check_var'].get():
|
|
|
|
|
|
p_info['prompt_widget'].config(state=tk.NORMAL)
|
|
|
p_info['uc_widget'].config(state=tk.NORMAL)
|
|
|
else:
|
|
|
|
|
|
p_info['prompt_widget'].config(state=tk.DISABLED)
|
|
|
p_info['uc_widget'].config(state=tk.DISABLED)
|
|
|
|
|
|
def verify_token(self):
|
|
|
"""[๊ฒ์ฆ] ๋ฒํผ ํด๋ฆญ ์ ํ ํฐ ๊ฒ์ฆ ์ค๋ ๋๋ฅผ ์์ํ๋ ๋ฉ์๋"""
|
|
|
token = self.nai_token_var.get()
|
|
|
if not token:
|
|
|
messagebox.showwarning("์
๋ ฅ ์ค๋ฅ", "ํ ํฐ์ ์
๋ ฅํด์ฃผ์ธ์.")
|
|
|
return
|
|
|
|
|
|
|
|
|
self.root.config(cursor="watch")
|
|
|
|
|
|
|
|
|
thread = threading.Thread(target=self._verify_token_thread, args=(token,))
|
|
|
thread.daemon = True
|
|
|
thread.start()
|
|
|
|
|
|
def _verify_token_thread(self, token):
|
|
|
"""(์ค๋ ๋์์ ์คํ) ์ค์ API ์์ฒญ ๋ฐ ๊ฒ์ฆ ๋ก์ง"""
|
|
|
import requests
|
|
|
|
|
|
try:
|
|
|
response = requests.get(
|
|
|
"https://api.novelai.net/user/subscription",
|
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
|
timeout=5
|
|
|
)
|
|
|
response.raise_for_status()
|
|
|
|
|
|
data_dict = response.json()
|
|
|
|
|
|
|
|
|
if data_dict.get('perks', {}).get('unlimitedMaxPriority', False):
|
|
|
result_message = "Opus ๋ฑ๊ธ ๊ตฌ๋
์ด ํ์ธ๋์์ต๋๋ค."
|
|
|
result_type = "info"
|
|
|
else:
|
|
|
result_message = "์ ํจํ ํ ํฐ์ด๋ Opus ๋ฑ๊ธ ๊ตฌ๋
์ด ์๋๋๋ค."
|
|
|
result_type = "warning"
|
|
|
|
|
|
except requests.exceptions.HTTPError as e:
|
|
|
if e.response.status_code == 401:
|
|
|
result_message = "์ธ์ฆ ์คํจ: ์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค."
|
|
|
else:
|
|
|
result_message = f"HTTP ์ค๋ฅ ๋ฐ์: {e.response.status_code}"
|
|
|
result_type = "error"
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
result_message = f"๋คํธ์ํฌ ์ค๋ฅ: API ์๋ฒ์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.\n{e}"
|
|
|
result_type = "error"
|
|
|
except Exception as e:
|
|
|
result_message = f"์ ์ ์๋ ์ค๋ฅ ๋ฐ์: {e}"
|
|
|
result_type = "error"
|
|
|
|
|
|
|
|
|
self.root.after(0, self._update_verification_result, result_type, result_message)
|
|
|
|
|
|
def _update_verification_result(self, result_type, message):
|
|
|
"""(๋ฉ์ธ ์ค๋ ๋์์ ์คํ) ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ฅผ messagebox๋ก ํ์"""
|
|
|
self.root.config(cursor="")
|
|
|
|
|
|
if result_type == "info":
|
|
|
messagebox.showinfo("๊ฒ์ฆ ์ฑ๊ณต", message)
|
|
|
elif result_type == "warning":
|
|
|
messagebox.showwarning("๊ฒ์ฆ ํ์ธ", message)
|
|
|
else:
|
|
|
messagebox.showerror("๊ฒ์ฆ ์คํจ", message)
|
|
|
|
|
|
def init_filter_data(self):
|
|
|
try:
|
|
|
from tagbag import bag_of_tags, clothes_list
|
|
|
from character_dictionary import character_dictionary as cd
|
|
|
|
|
|
self.bag_of_tags = bag_of_tags
|
|
|
self.clothes_list = clothes_list
|
|
|
self.character_keys = list(cd.keys()) if isinstance(cd, dict) else []
|
|
|
|
|
|
except ImportError as e:
|
|
|
print(f"โ NAIA ํ์ผ import ์คํจ: {e}")
|
|
|
print("๊ธฐ๋ณธ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค.")
|
|
|
self.bag_of_tags = [
|
|
|
'smile', 'blush', 'open_mouth', 'closed_eyes', 'wink', 'frown',
|
|
|
'twintails', 'ponytail', 'braid', 'long_hair', 'short_hair',
|
|
|
'large_breasts', 'small_breasts', 'cleavage', 'navel'
|
|
|
]
|
|
|
self.clothes_list = [
|
|
|
'dress', 'skirt', 'shirt', 'jacket', 'uniform', 'school_uniform'
|
|
|
]
|
|
|
self.character_keys = []
|
|
|
|
|
|
self.colors = ['black', 'white', 'blond', 'silver', 'gray', 'yellow',
|
|
|
'blue', 'purple', 'red', 'pink', 'brown', 'orange',
|
|
|
'green', 'aqua', 'gradient']
|
|
|
|
|
|
def toggle_removed_tags(self):
|
|
|
if self.show_removed_tags_var.get():
|
|
|
self.removed_tags_text.grid(row=3, column=0, sticky="nsew", pady=(5, 0))
|
|
|
else:
|
|
|
self.removed_tags_text.grid_forget()
|
|
|
|
|
|
def toggle_ignore_tags(self):
|
|
|
if self.show_ignore_tags_var.get():
|
|
|
self.ignore_tags_frame.grid(row=1, column=0, sticky="nsew")
|
|
|
else:
|
|
|
self.ignore_tags_frame.grid_forget()
|
|
|
|
|
|
def toggle_prompts(self):
|
|
|
if self.prompt_active_var.get():
|
|
|
self.prompt_notebook.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(5, 0))
|
|
|
else:
|
|
|
self.prompt_notebook.grid_forget()
|
|
|
|
|
|
def on_closing(self):
|
|
|
self.save_settings()
|
|
|
self.root.destroy()
|
|
|
|
|
|
def save_settings(self):
|
|
|
"""ํ์ฌ UI์ ๋ชจ๋ ์ค์ ์ JSON ํ์ผ์ ์ ์ฅํฉ๋๋ค."""
|
|
|
settings = {
|
|
|
|
|
|
'general_threshold': self.general_threshold_var.get(),
|
|
|
'char_threshold': self.char_threshold_var.get(),
|
|
|
'rm_c': self.rm_c_var.get(),
|
|
|
'rm_color': self.rm_color_var.get(),
|
|
|
'rm_clothes': self.rm_clothes_var.get(),
|
|
|
'webui_mode': self.webui_mode_var.get(),
|
|
|
'instant_inference': self.instant_inference_var.get(),
|
|
|
'ignore_tags': self.ignore_tags_text.get(1.0, tk.END).strip(),
|
|
|
'show_removed_tags': self.show_removed_tags_var.get(),
|
|
|
'show_ignore_tags': self.show_ignore_tags_var.get(),
|
|
|
'prompt_active': self.prompt_active_var.get(),
|
|
|
'prefix_prompt': self.prefix_text.get(1.0, tk.END).strip(),
|
|
|
'suffix_prompt': self.suffix_text.get(1.0, tk.END).strip(),
|
|
|
|
|
|
|
|
|
'expand_ui': self.expand_ui_var.get(),
|
|
|
'sampler': self.sampler_var.get(),
|
|
|
'resolution': self.resolution_var.get(),
|
|
|
'steps': self.steps_var.get(),
|
|
|
'scale': self.scale_var.get(),
|
|
|
'seed': self.seed_var.get(),
|
|
|
'cfg_rescale': self.cfg_rescale_var.get(),
|
|
|
'uc_prompt': self.uc_text.get(1.0, tk.END).strip(),
|
|
|
'nai_token': self.nai_token_var.get()
|
|
|
}
|
|
|
settings['auto_save'] = self.auto_save_var.get()
|
|
|
settings['auto_rating_tag'] = self.auto_rating_tag_var.get()
|
|
|
|
|
|
char_prompts_data = []
|
|
|
for p_info in self.character_prompts:
|
|
|
char_prompts_data.append({
|
|
|
"checked": p_info['check_var'].get(),
|
|
|
"prompt": p_info['prompt_widget'].get(1.0, tk.END).strip(),
|
|
|
"uc": p_info['uc_widget'].get(1.0, tk.END).strip()
|
|
|
})
|
|
|
settings['character_prompts'] = char_prompts_data
|
|
|
try:
|
|
|
with open(self.SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
|
json.dump(settings, f, indent=4)
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ ์ฅ ์คํจ", f"์ค์ ์ ์ฅ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}")
|
|
|
|
|
|
def load_settings(self):
|
|
|
"""JSON ํ์ผ์์ ์ค์ ์ ๋ถ๋ฌ์ UI์ ์ ์ฉํฉ๋๋ค."""
|
|
|
try:
|
|
|
if os.path.exists(self.SETTINGS_FILE):
|
|
|
with open(self.SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
|
|
settings = json.load(f)
|
|
|
|
|
|
|
|
|
self.general_threshold_var.set(settings.get('general_threshold', 0.5))
|
|
|
self.char_threshold_var.set(settings.get('char_threshold', 0.85))
|
|
|
self.rm_c_var.set(settings.get('rm_c', False))
|
|
|
self.rm_color_var.set(settings.get('rm_color', False))
|
|
|
self.rm_clothes_var.set(settings.get('rm_clothes', False))
|
|
|
self.webui_mode_var.set(settings.get('webui_mode', False))
|
|
|
self.instant_inference_var.set(settings.get('instant_inference', False))
|
|
|
self.ignore_tags_text.delete(1.0, tk.END)
|
|
|
self.ignore_tags_text.insert(1.0, settings.get('ignore_tags', ''))
|
|
|
self.show_removed_tags_var.set(settings.get('show_removed_tags', True))
|
|
|
self.show_ignore_tags_var.set(settings.get('show_ignore_tags', False))
|
|
|
self.prompt_active_var.set(settings.get('prompt_active', False))
|
|
|
self.prefix_text.delete(1.0, tk.END)
|
|
|
self.prefix_text.insert(1.0, settings.get('prefix_prompt', ''))
|
|
|
self.suffix_text.delete(1.0, tk.END)
|
|
|
self.suffix_text.insert(1.0, settings.get('suffix_prompt', ''))
|
|
|
|
|
|
|
|
|
self.expand_ui_var.set(settings.get('expand_ui', False))
|
|
|
self.sampler_var.set(settings.get('sampler', 'k_euler_ancestral + karras'))
|
|
|
self.resolution_var.set(settings.get('resolution', '896 x 1152'))
|
|
|
self.steps_var.set(settings.get('steps', '28'))
|
|
|
self.scale_var.set(settings.get('scale', '5.8'))
|
|
|
self.seed_var.set(settings.get('seed', '-1'))
|
|
|
self.cfg_rescale_var.set(settings.get('cfg_rescale', '0.25'))
|
|
|
self.uc_text.delete(1.0, tk.END)
|
|
|
self.uc_text.insert(1.0, settings.get('uc_prompt', ''))
|
|
|
self.nai_token_var.set(settings.get('nai_token', ''))
|
|
|
self.auto_save_var.set(settings.get('auto_save', True))
|
|
|
self.auto_rating_tag_var.set(settings.get('auto_rating_tag', True))
|
|
|
|
|
|
|
|
|
char_prompts_data = settings.get('character_prompts', [])
|
|
|
for i, data in enumerate(char_prompts_data):
|
|
|
if i < len(self.character_prompts):
|
|
|
p_info = self.character_prompts[i]
|
|
|
p_info['check_var'].set(data.get('checked', i==0))
|
|
|
|
|
|
p_info['prompt_widget'].delete(1.0, tk.END)
|
|
|
p_info['prompt_widget'].insert(1.0, data.get('prompt', ''))
|
|
|
|
|
|
p_info['uc_widget'].delete(1.0, tk.END)
|
|
|
p_info['uc_widget'].insert(1.0, data.get('uc', ''))
|
|
|
|
|
|
|
|
|
self.toggle_removed_tags()
|
|
|
self.toggle_ignore_tags()
|
|
|
self.toggle_prompts()
|
|
|
self.update_character_layout()
|
|
|
self.toggle_expand_ui()
|
|
|
|
|
|
except Exception as e:
|
|
|
messagebox.showwarning("์ค์ ๋ก๋ ์คํจ", f"์ค์ ํ์ผ({self.SETTINGS_FILE})์ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค: {e}")
|
|
|
|
|
|
def handle_paste(self, event):
|
|
|
try:
|
|
|
clipboard_image = ImageGrab.grabclipboard()
|
|
|
if isinstance(clipboard_image, Image.Image):
|
|
|
self.load_from_clipboard()
|
|
|
return "break"
|
|
|
except (ValueError, TypeError):
|
|
|
pass
|
|
|
except Exception as e:
|
|
|
print(f"ํด๋ฆฝ๋ณด๋ ์ด๋ฏธ์ง ํ์ธ ์ค ์ค๋ฅ: {e}")
|
|
|
|
|
|
try:
|
|
|
clipboard_text = self.root.clipboard_get()
|
|
|
if isinstance(clipboard_text, str) and clipboard_text.startswith(('http://', 'https://')):
|
|
|
self._load_image_from_url(clipboard_text)
|
|
|
return "break"
|
|
|
|
|
|
image_extensions = ('.jpg', '.jpeg', '.png', '.webp', '.bmp', '.tiff')
|
|
|
if isinstance(clipboard_text, str) and clipboard_text.lower().endswith(image_extensions):
|
|
|
path = clipboard_text.strip('"')
|
|
|
if os.path.exists(path):
|
|
|
self._load_image_from_path(path)
|
|
|
return "break"
|
|
|
|
|
|
except tk.TclError:
|
|
|
pass
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("๋ถ์ฌ๋ฃ๊ธฐ ์ค๋ฅ", f"์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {e}")
|
|
|
return "break"
|
|
|
|
|
|
return
|
|
|
|
|
|
def _load_image_from_url(self, url):
|
|
|
try:
|
|
|
img = self.download_image_from_url(url)
|
|
|
temp_dir = tempfile.gettempdir()
|
|
|
temp_file = os.path.join(temp_dir, f"wd14_url_{os.getpid()}.png")
|
|
|
img.save(temp_file)
|
|
|
|
|
|
self.current_image_path = temp_file
|
|
|
self.show_preview(temp_file)
|
|
|
if self.session is not None:
|
|
|
self.extract_btn.config(state='normal')
|
|
|
if self.instant_inference_var.get():
|
|
|
self.extract_tags()
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ค๋ฅ", f"์น ์ด๋ฏธ์ง ๋ก๋ ์คํจ: {str(e)}")
|
|
|
|
|
|
def load_model(self):
|
|
|
try:
|
|
|
import onnxruntime as ort
|
|
|
|
|
|
model_path = resource_path("model.onnx")
|
|
|
tags_path = resource_path("selected_tags.csv")
|
|
|
|
|
|
self.session = ort.InferenceSession(model_path, providers=['CPUExecutionProvider'])
|
|
|
self.tags_df = pd.read_csv(tags_path)
|
|
|
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ค๋ฅ", f"๋ชจ๋ธ ๋ก๋ ์คํจ: {str(e)}")
|
|
|
|
|
|
def show_initial_preview(self):
|
|
|
try:
|
|
|
white_image = Image.new('RGB', (290, 290), '#F0F0F0')
|
|
|
photo = ImageTk.PhotoImage(white_image)
|
|
|
self.image_label.config(image=photo, text="์ด๋ฏธ์ง๋ฅผ Drag & Drop\n๋๋ ๋ถ์ฌ๋ฃ๊ธฐ(Ctrl+V) ํด์ฃผ์ธ์", compound="center", foreground="gray")
|
|
|
self.image_label.image = photo
|
|
|
except Exception as e:
|
|
|
self.image_label.config(text="์ด๋ฏธ์ง๋ฅผ Drag & Drop ํด์ฃผ์ธ์")
|
|
|
|
|
|
def setup_drag_drop(self):
|
|
|
try:
|
|
|
from tkinterdnd2 import TkinterDnD, DND_ALL
|
|
|
self.TkdndVersion = TkinterDnD._require(self.root)
|
|
|
self.image_label.drop_target_register(DND_ALL)
|
|
|
self.image_label.dnd_bind('<<Drop>>', self.on_drop)
|
|
|
self.image_label.dnd_bind('<<DragEnter>>', self.on_drag_enter)
|
|
|
self.image_label.dnd_bind('<<DragLeave>>', self.on_drag_leave)
|
|
|
except ImportError:
|
|
|
print("tkinterdnd2๊ฐ ์ค์น๋์ง ์์. pip install tkinterdnd2๋ก ์ค์นํ๋ฉด ๋๋๊ทธ์ค๋๋กญ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.")
|
|
|
except Exception as e:
|
|
|
print(f"Drag & Drop ์ด๊ธฐํ ์ค๋ฅ: {e}")
|
|
|
|
|
|
def on_drag_enter(self, event):
|
|
|
try:
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ด๋ฏธ์ง๋ฅผ ๋์์ฃผ์ธ์!)")
|
|
|
except: pass
|
|
|
|
|
|
def on_drag_leave(self, event):
|
|
|
try:
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
except: pass
|
|
|
|
|
|
def download_image_from_url(self, url):
|
|
|
import requests
|
|
|
response = requests.get(url)
|
|
|
response.raise_for_status()
|
|
|
return Image.open(io.BytesIO(response.content))
|
|
|
|
|
|
def on_drop(self, event):
|
|
|
try:
|
|
|
if not isinstance(event, str):
|
|
|
file_path_or_url = event.data.strip("{}")
|
|
|
else:
|
|
|
file_path_or_url = event
|
|
|
|
|
|
if not file_path_or_url:
|
|
|
messagebox.showwarning("๊ฒฝ๊ณ ", "๋๋กญ๋ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ์ ์์ต๋๋ค.")
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
return
|
|
|
|
|
|
if file_path_or_url.startswith(('http://', 'https://')):
|
|
|
self._load_image_from_url(file_path_or_url)
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
return
|
|
|
else:
|
|
|
if file_path_or_url.startswith(('blob:')):
|
|
|
messagebox.showwarning("Blob URL ์ค๋ฅ", "NAI ํํ์ด์ง์์ ์์ฑํ ์ด๋ฏธ์ง๋ ์ฆ์ ๋ณต์ฌํ ์ ์์ต๋๋ค.\n์ด๋ฏธ์ง๋ฅผ ๋ค์ด๋ก๋ํ ํ ๋๋๊ทธ & ๋๋กญํด์ฃผ์ธ์.")
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
return
|
|
|
|
|
|
file_path_or_url = file_path_or_url.replace("\\", '/')
|
|
|
if os.path.exists(file_path_or_url):
|
|
|
self.current_image_path = file_path_or_url
|
|
|
self.show_preview(file_path_or_url)
|
|
|
if self.session is not None:
|
|
|
self.extract_btn.config(state='normal')
|
|
|
if self.instant_inference_var.get():
|
|
|
self.extract_tags()
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
else:
|
|
|
messagebox.showerror("์ค๋ฅ", f"์ ํจํ์ง ์์ ํ์ผ ๊ฒฝ๋ก์
๋๋ค: '{file_path_or_url}'")
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ค๋ฅ", f"ํ์ผ ๋๋กญ ์ค๋ฅ: {str(e)}")
|
|
|
self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
|
|
|
def _load_image_from_path(self, path):
|
|
|
if os.path.exists(path):
|
|
|
self.current_image_path = path
|
|
|
self.show_preview(path)
|
|
|
if self.session is not None:
|
|
|
self.extract_btn.config(state='normal')
|
|
|
if self.instant_inference_var.get():
|
|
|
self.extract_tags()
|
|
|
else:
|
|
|
messagebox.showwarning("๊ฒฝ๋ก ์ค๋ฅ", f"ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {path}")
|
|
|
|
|
|
def load_from_clipboard(self):
|
|
|
try:
|
|
|
image = ImageGrab.grabclipboard()
|
|
|
if isinstance(image, Image.Image):
|
|
|
temp_dir = tempfile.gettempdir()
|
|
|
temp_file = os.path.join(temp_dir, f"wd14_clipboard_{os.getpid()}.png")
|
|
|
if image.mode == 'RGBA':
|
|
|
image = image.convert('RGB')
|
|
|
image.save(temp_file, 'PNG')
|
|
|
|
|
|
self.current_image_path = temp_file
|
|
|
self.show_preview(temp_file)
|
|
|
if self.session is not None:
|
|
|
self.extract_btn.config(state='normal')
|
|
|
if self.instant_inference_var.get():
|
|
|
self.extract_tags()
|
|
|
else:
|
|
|
messagebox.showinfo("์ ๋ณด", "ํด๋ฆฝ๋ณด๋์ ์ด๋ฏธ์ง๊ฐ ์์ต๋๋ค.")
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ค๋ฅ", f"ํด๋ฆฝ๋ณด๋ ๋ถ๋ฌ์ค๊ธฐ ์ค๋ฅ: {str(e)}")
|
|
|
|
|
|
def load_image(self):
|
|
|
file_path = filedialog.askopenfilename(
|
|
|
title="์ด๋ฏธ์ง ์ ํ",
|
|
|
filetypes=[("์ด๋ฏธ์ง ํ์ผ", "*.jpg *.jpeg *.png *.bmp *.tiff *.webp")]
|
|
|
)
|
|
|
if file_path:
|
|
|
self.current_image_path = file_path
|
|
|
self.show_preview(file_path)
|
|
|
if self.session is not None:
|
|
|
self.extract_btn.config(state='normal')
|
|
|
if self.instant_inference_var.get():
|
|
|
self.extract_tags()
|
|
|
|
|
|
def show_preview(self, image_path):
|
|
|
try:
|
|
|
image = Image.open(image_path).convert('RGB')
|
|
|
size = max(image.size)
|
|
|
square_image = Image.new('RGB', (size, size), 'white')
|
|
|
paste_x = (size - image.width) // 2
|
|
|
paste_y = (size - image.height) // 2
|
|
|
square_image.paste(image, (paste_x, paste_y))
|
|
|
square_image.thumbnail((290, 290), Image.Resampling.LANCZOS)
|
|
|
photo = ImageTk.PhotoImage(square_image)
|
|
|
self.image_label.config(image=photo, text="")
|
|
|
self.image_label.image = photo
|
|
|
except Exception as e:
|
|
|
self.image_label.config(text=f"์ด๋ฏธ์ง ๋ก๋ ์คํจ: {str(e)}")
|
|
|
|
|
|
def extract_tags(self):
|
|
|
if not self.current_image_path or not self.session:
|
|
|
return
|
|
|
self.extract_btn.config(state='disabled', text='์ถ์ถ ์ค...')
|
|
|
self.result_text.delete(1.0, tk.END)
|
|
|
self.result_text.insert(tk.END, "ํ๊ทธ ์ถ์ถ ์ค...")
|
|
|
self.removed_tags_text.config(state=tk.NORMAL)
|
|
|
self.removed_tags_text.delete(1.0, tk.END)
|
|
|
self.removed_tags_text.config(state=tk.DISABLED)
|
|
|
thread = threading.Thread(target=self._extract_tags_thread)
|
|
|
thread.daemon = True
|
|
|
thread.start()
|
|
|
|
|
|
def _extract_tags_thread(self):
|
|
|
try:
|
|
|
input_image = Image.open(self.current_image_path)
|
|
|
_, height, _, _ = self.session.get_inputs()[0].shape
|
|
|
image = input_image.convert('RGBA')
|
|
|
new_image = Image.new('RGBA', image.size, 'WHITE')
|
|
|
new_image.paste(image, mask=image)
|
|
|
image = new_image.convert('RGB')
|
|
|
image = np.asarray(image)[:, :, ::-1]
|
|
|
image = self.make_square(image, height)
|
|
|
image = self.smart_resize(image, height)
|
|
|
image = image.astype(np.float32)
|
|
|
image = np.expand_dims(image, 0)
|
|
|
input_name = self.session.get_inputs()[0].name
|
|
|
label_name = self.session.get_outputs()[0].name
|
|
|
confidents = self.session.run([label_name], {input_name: image})[0]
|
|
|
tags_with_conf = []
|
|
|
for i, conf in enumerate(confidents[0]):
|
|
|
if i < len(self.tags_df):
|
|
|
tags_with_conf.append((self.tags_df.iloc[i]['name'], float(conf)))
|
|
|
rating_tags = dict(tags_with_conf[:4])
|
|
|
general_tags = dict(tags_with_conf[4:])
|
|
|
best_rating = max(rating_tags, key=rating_tags.get) if rating_tags else None
|
|
|
|
|
|
result_tuple = self._format_results(general_tags, best_rating)
|
|
|
self.root.after(0, self._update_results, result_tuple)
|
|
|
except Exception as e:
|
|
|
error_msg = f"ํ๊ทธ ์ถ์ถ ์ค๋ฅ: {str(e)}"
|
|
|
self.root.after(0, self._update_results, ((None, error_msg, "")))
|
|
|
|
|
|
def apply_tag_filters(self, tags_list, best_rating=None):
|
|
|
original_tags = tags_list.copy()
|
|
|
tags_to_process = [tag.replace('_', ' ') for tag in original_tags]
|
|
|
|
|
|
final_indices = list(range(len(tags_to_process)))
|
|
|
|
|
|
user_removed_originals = []
|
|
|
other_removed_originals = []
|
|
|
|
|
|
ignore_list_str = self.ignore_tags_text.get(1.0, tk.END).strip()
|
|
|
if ignore_list_str:
|
|
|
user_ignored_tags = {tag.strip().replace('_', ' ') for tag in ignore_list_str.split(',') if tag.strip()}
|
|
|
indices_to_remove = {i for i in final_indices if tags_to_process[i] in user_ignored_tags}
|
|
|
for i in indices_to_remove:
|
|
|
user_removed_originals.append(original_tags[i])
|
|
|
final_indices = [i for i in final_indices if i not in indices_to_remove]
|
|
|
|
|
|
if self.rm_c_var.get():
|
|
|
indices_to_remove = {i for i in final_indices if tags_to_process[i] in self.bag_of_tags or self._is_character_name(tags_to_process[i])}
|
|
|
for i in indices_to_remove:
|
|
|
other_removed_originals.append(original_tags[i])
|
|
|
final_indices = [i for i in final_indices if i not in indices_to_remove]
|
|
|
|
|
|
if self.rm_color_var.get():
|
|
|
indices_to_remove = {i for i in final_indices if any(color+" " in tags_to_process[i] for color in self.colors) and " eyes" not in tags_to_process[i] and " hair" not in tags_to_process[i] and " pupils" not in tags_to_process[i]}
|
|
|
for i in indices_to_remove:
|
|
|
other_removed_originals.append(original_tags[i])
|
|
|
final_indices = [i for i in final_indices if i not in indices_to_remove]
|
|
|
|
|
|
if self.rm_clothes_var.get():
|
|
|
indices_to_remove = {i for i in final_indices if tags_to_process[i] in self.clothes_list and tags_to_process[i] not in self.bag_of_tags}
|
|
|
for i in indices_to_remove:
|
|
|
other_removed_originals.append(original_tags[i])
|
|
|
final_indices = [i for i in final_indices if i not in indices_to_remove]
|
|
|
|
|
|
final_tags_with_space = [tags_to_process[i] for i in final_indices]
|
|
|
|
|
|
|
|
|
if self.auto_rating_tag_var.get() and best_rating:
|
|
|
prefix_str = self.prefix_text.get(1.0, tk.END)
|
|
|
suffix_str = self.suffix_text.get(1.0, tk.END)
|
|
|
|
|
|
if not self.webui_mode_var.get():
|
|
|
best_rating = "rating:" + best_rating
|
|
|
|
|
|
tag_to_add = None
|
|
|
if best_rating in ["rating:explicit", "rating:questionable", "questionable", "explicit"]:
|
|
|
if "nsfw" not in prefix_str and "nsfw" not in suffix_str:
|
|
|
tag_to_add = best_rating +", nsfw"
|
|
|
elif best_rating in ["rating:general", "general"]:
|
|
|
if "safe" not in prefix_str and "safe" not in suffix_str:
|
|
|
tag_to_add = best_rating +", safe"
|
|
|
else:
|
|
|
tag_to_add = best_rating
|
|
|
|
|
|
|
|
|
if tag_to_add:
|
|
|
person_tags = {'1girl', '2girls', '3girls', '4girls', '5girls', '6+girls',
|
|
|
'1boy', '2boys', '3boys', '4boys', '5boys', '6+boys'}
|
|
|
insert_pos = 0
|
|
|
for i, tag in enumerate(final_tags_with_space):
|
|
|
if tag in person_tags:
|
|
|
insert_pos = i + 1
|
|
|
break
|
|
|
final_tags_with_space.insert(insert_pos, tag_to_add)
|
|
|
|
|
|
if self.webui_mode_var.get():
|
|
|
final_tags_formatted = [tag.replace('(', '\\(').replace(')', '\\)') for tag in final_tags_with_space]
|
|
|
else:
|
|
|
final_tags_formatted = final_tags_with_space
|
|
|
|
|
|
final_tags_with_type = [(tag, 'original') for tag in final_tags_formatted]
|
|
|
if self.prompt_active_var.get():
|
|
|
prefix_str = self.prefix_text.get(1.0, tk.END).strip()
|
|
|
if prefix_str:
|
|
|
prefix_tags = [t.strip() for t in prefix_str.split(',') if t.strip()]
|
|
|
prefix_tags_with_type = [(tag, 'prompt') for tag in prefix_tags]
|
|
|
person_tags = {'1girl', '2girls', '3girls', '4girls', '5girls', '6+girls',
|
|
|
'1boy', '2boys', '3boys', '4boys', '5boys', '6+boys'}
|
|
|
last_person_tag_index = -1
|
|
|
search_range = min(len(final_tags_with_type), 4)
|
|
|
for i in range(search_range):
|
|
|
if final_tags_with_type[i][0] in person_tags:
|
|
|
last_person_tag_index = i
|
|
|
if last_person_tag_index != -1:
|
|
|
final_tags_with_type[last_person_tag_index+1:last_person_tag_index+1] = prefix_tags_with_type
|
|
|
else:
|
|
|
final_tags_with_type[0:0] = prefix_tags_with_type
|
|
|
|
|
|
suffix_str = self.suffix_text.get(1.0, tk.END).strip()
|
|
|
if suffix_str:
|
|
|
suffix_tags = [t.strip() for t in suffix_str.split(',') if t.strip()]
|
|
|
suffix_tags_with_type = [(tag, 'prompt') for tag in suffix_tags]
|
|
|
final_tags_with_type.extend(suffix_tags_with_type)
|
|
|
|
|
|
return final_tags_with_type, user_removed_originals, list(set(other_removed_originals))
|
|
|
|
|
|
def _is_character_name(self, tag):
|
|
|
if hasattr(self, 'character_keys') and tag in self.character_keys:
|
|
|
return True
|
|
|
if ('_' in tag and any(c.isupper() for c in tag) and
|
|
|
not tag.startswith('1') and
|
|
|
not any(word in tag.lower() for word in ['girl', 'boy', 'solo', 'multiple']) and
|
|
|
not any(word in tag.lower() for word in ['hair', 'eye', 'dress', 'shirt']) and
|
|
|
len(tag) > 3):
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def _format_results(self, tags, best_rating=None):
|
|
|
general_threshold = self.general_threshold_var.get()
|
|
|
char_threshold = self.char_threshold_var.get()
|
|
|
|
|
|
person_tags = {'1girl', '2girls', '3girls', '4girls', '5girls', '6+girls',
|
|
|
'1boy', '2boys', '3boys', '4boys', '5boys', '6+boys',
|
|
|
'multiple_girls', 'multiple_boys', 'solo', 'no_humans'}
|
|
|
|
|
|
filtered_tags = []
|
|
|
for tag, conf in tags.items():
|
|
|
if tag in person_tags:
|
|
|
if conf >= 0.9: filtered_tags.append(tag)
|
|
|
elif any(c.isupper() for c in tag) and '_' in tag and conf >= char_threshold:
|
|
|
filtered_tags.append(tag)
|
|
|
elif conf >= general_threshold:
|
|
|
filtered_tags.append(tag)
|
|
|
|
|
|
if len(filtered_tags) > 99:
|
|
|
filtered_tags = filtered_tags[:99]
|
|
|
|
|
|
final_tags_with_type, user_removed, other_removed = self.apply_tag_filters(filtered_tags, best_rating)
|
|
|
|
|
|
removed_for_display = []
|
|
|
for tag in user_removed:
|
|
|
removed_for_display.append((tag, 'ignored'))
|
|
|
for tag in other_removed:
|
|
|
if tag not in user_removed:
|
|
|
removed_for_display.append((tag, 'other'))
|
|
|
|
|
|
return final_tags_with_type, removed_for_display
|
|
|
|
|
|
def copy_all_tags(self):
|
|
|
content = self.result_text.get(1.0, tk.END).strip()
|
|
|
if content:
|
|
|
self.root.clipboard_clear()
|
|
|
self.root.clipboard_append(content)
|
|
|
else:
|
|
|
messagebox.showwarning("๋ณต์ฌ ์คํจ", "๋ณต์ฌํ ํ๊ทธ๊ฐ ์์ต๋๋ค.")
|
|
|
|
|
|
def copy_from_second(self):
|
|
|
self._copy_from_index(1, "2๋ฒ ํ๊ทธ๋ถํฐ")
|
|
|
|
|
|
def copy_from_third(self):
|
|
|
self._copy_from_index(2, "3๋ฒ ํ๊ทธ๋ถํฐ")
|
|
|
|
|
|
def _copy_from_index(self, start_index, description):
|
|
|
content = self.result_text.get(1.0, tk.END).strip()
|
|
|
if content:
|
|
|
tags_list = [tag.strip() for tag in content.split(',')]
|
|
|
if len(tags_list) > start_index:
|
|
|
selected_tags = tags_list[start_index:]
|
|
|
result = ', '.join(selected_tags)
|
|
|
self.root.clipboard_clear()
|
|
|
self.root.clipboard_append(result)
|
|
|
else:
|
|
|
messagebox.showwarning("๋ณต์ฌ ์คํจ", f"ํ๊ทธ๊ฐ {start_index + 1}๊ฐ ๋ฏธ๋ง์
๋๋ค.")
|
|
|
else:
|
|
|
messagebox.showwarning("๋ณต์ฌ ์คํจ", "๋ณต์ฌํ ํ๊ทธ๊ฐ ์์ต๋๋ค.")
|
|
|
|
|
|
def _update_results(self, result_tuple):
|
|
|
main_tags_info, removed_tags_info = result_tuple[0], result_tuple[1]
|
|
|
if len(result_tuple) == 3:
|
|
|
main_tags_info, removed_tags_info = result_tuple[1], result_tuple[2]
|
|
|
|
|
|
|
|
|
self.result_text.delete(1.0, tk.END)
|
|
|
if isinstance(main_tags_info, list):
|
|
|
for i, (tag, tag_type) in enumerate(main_tags_info):
|
|
|
if i > 0:
|
|
|
self.result_text.insert(tk.END, ', ')
|
|
|
|
|
|
if tag_type == 'prompt':
|
|
|
self.result_text.insert(tk.END, tag, 'prompt')
|
|
|
else:
|
|
|
self.result_text.insert(tk.END, tag)
|
|
|
else:
|
|
|
self.result_text.insert(tk.END, main_tags_info)
|
|
|
|
|
|
self.removed_tags_text.config(state=tk.NORMAL)
|
|
|
self.removed_tags_text.delete(1.0, tk.END)
|
|
|
if removed_tags_info:
|
|
|
for i, (tag, tag_type) in enumerate(removed_tags_info):
|
|
|
display_tag = tag.replace('_', ' ')
|
|
|
|
|
|
if i > 0:
|
|
|
self.removed_tags_text.insert(tk.END, ', ')
|
|
|
|
|
|
if tag_type == 'ignored':
|
|
|
self.removed_tags_text.insert(tk.END, display_tag, 'ignored')
|
|
|
else:
|
|
|
self.removed_tags_text.insert(tk.END, display_tag)
|
|
|
self.removed_tags_text.config(state=tk.DISABLED)
|
|
|
|
|
|
self.extract_btn.config(state='normal', text='ํ๊ทธ ์ถ์ถ')
|
|
|
if (self.instant_generate_var.get() and
|
|
|
self.expand_ui_var.get() and
|
|
|
self.is_generating == False):
|
|
|
self.start_generation()
|
|
|
|
|
|
def make_square(self, img, target_size):
|
|
|
old_size = img.shape[:2]
|
|
|
desired_size = max(old_size)
|
|
|
desired_size = max(desired_size, target_size)
|
|
|
delta_w = desired_size - old_size[1]
|
|
|
delta_h = desired_size - old_size[0]
|
|
|
top, bottom = delta_h // 2, delta_h - (delta_h // 2)
|
|
|
left, right = delta_w // 2, delta_w - (delta_w // 2)
|
|
|
color = [255, 255, 255]
|
|
|
new_im = cv2.copyMakeBorder(
|
|
|
img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color
|
|
|
)
|
|
|
return new_im
|
|
|
|
|
|
def smart_resize(self, img, size):
|
|
|
if img.shape[0] > size:
|
|
|
img = cv2.resize(img, (size, size), interpolation=cv2.INTER_AREA)
|
|
|
elif img.shape[0] < size:
|
|
|
img = cv2.resize(img, (size, size), interpolation=cv2.INTER_CUBIC)
|
|
|
return img
|
|
|
|
|
|
def find_max_resolution(self, width, height, max_pixels=1048576, multiple_of=64):
|
|
|
"""๊ฐ๋ก์ธ๋ก ๋น์จ์ ์ ์งํ๋ฉฐ ์ต๋ ํฝ์
์์ ๋ง๋ ์ต๋ ํด์๋๋ฅผ ์ฐพ์ต๋๋ค."""
|
|
|
ratio = width / height
|
|
|
|
|
|
max_width = int((max_pixels * ratio)**0.5)
|
|
|
max_height = int((max_pixels / ratio)**0.5)
|
|
|
|
|
|
max_width = (max_width // multiple_of) * multiple_of
|
|
|
max_height = (max_height // multiple_of) * multiple_of
|
|
|
|
|
|
while max_width * max_height > max_pixels:
|
|
|
max_width -= multiple_of
|
|
|
max_height = int(max_width / ratio)
|
|
|
max_height = (max_height // multiple_of) * multiple_of
|
|
|
|
|
|
return (max_width, max_height)
|
|
|
|
|
|
def start_generation(self):
|
|
|
if not self.nai_token_var.get():
|
|
|
messagebox.showerror("ํ ํฐ ์ค๋ฅ", "NAI ์ค์ ํญ์์ API ํ ํฐ์ ๋จผ์ ์
๋ ฅํด์ฃผ์ธ์.")
|
|
|
return
|
|
|
if not self.result_text.get(1.0, tk.END).strip():
|
|
|
messagebox.showerror("ํ๋กฌํํธ ์ค๋ฅ", "์ข์ธก '์ถ์ถ๋ ํ๊ทธ'์ ํ๋กฌํํธ๊ฐ ์์ต๋๋ค.")
|
|
|
return
|
|
|
|
|
|
|
|
|
self.is_generating = True
|
|
|
self.generation_start_time = time.time()
|
|
|
self.generation_btn.config(state=tk.DISABLED)
|
|
|
self.save_btn.config(state=tk.NORMAL)
|
|
|
self.generation_image_label.config(image=None, text="๐จ ์ด๋ฏธ์ง ์์ฑ ์ค...")
|
|
|
self._update_timer()
|
|
|
|
|
|
thread = threading.Thread(target=self._generation_thread)
|
|
|
thread.daemon = True
|
|
|
thread.start()
|
|
|
|
|
|
def _generation_thread(self):
|
|
|
"""(๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋) ํ๋ผ๋ฏธํฐ ์์ง, API ์์ฒญ ๋ฐ ๊ฒฐ๊ณผ ๋ฐํ"""
|
|
|
try:
|
|
|
|
|
|
access_token = self.nai_token_var.get()
|
|
|
model_name = self.model_var.get()
|
|
|
main_prompt = self.result_text.get(1.0, tk.END).strip()
|
|
|
negative_prompt = self.uc_text.get(1.0, tk.END).strip()
|
|
|
|
|
|
|
|
|
resolution_str = self.resolution_var.get()
|
|
|
if resolution_str == "์๋ ํด์๋":
|
|
|
if self.current_image_path and os.path.exists(self.current_image_path):
|
|
|
with Image.open(self.current_image_path) as img:
|
|
|
w, h = img.size
|
|
|
width, height = self.find_max_resolution(w, h)
|
|
|
else:
|
|
|
width, height = self.find_max_resolution(1024, 1024)
|
|
|
else:
|
|
|
parts = resolution_str.split(' x ')
|
|
|
width, height = int(parts[0]), int(parts[1])
|
|
|
|
|
|
|
|
|
sampler_parts = self.sampler_var.get().split(' + ')
|
|
|
sampler = sampler_parts[0]
|
|
|
noise_schedule = sampler_parts[1] if len(sampler_parts) > 1 else 'karras'
|
|
|
|
|
|
seed = int(self.seed_var.get())
|
|
|
if seed == -1:
|
|
|
seed = random.randint(0, 9999999999)
|
|
|
self.last_generated_seed = seed
|
|
|
|
|
|
|
|
|
parameters = {
|
|
|
"width": width,
|
|
|
"height": height,
|
|
|
"n_samples": 1,
|
|
|
"seed": seed,
|
|
|
"extra_noise_seed": seed,
|
|
|
"sampler": sampler,
|
|
|
"steps": int(self.steps_var.get()),
|
|
|
"scale": float(self.scale_var.get()),
|
|
|
"negative_prompt": negative_prompt,
|
|
|
"cfg_rescale": float(self.cfg_rescale_var.get()),
|
|
|
"noise_schedule": noise_schedule,
|
|
|
}
|
|
|
|
|
|
data = {
|
|
|
"input": main_prompt,
|
|
|
"model": model_name,
|
|
|
"action": "generate",
|
|
|
"parameters": parameters
|
|
|
}
|
|
|
|
|
|
|
|
|
if 'nai-diffusion-4' in model_name:
|
|
|
data['parameters'].update({
|
|
|
'params_version': 3,
|
|
|
'add_original_image': True,
|
|
|
'legacy': False,
|
|
|
'legacy_uc': False,
|
|
|
'autoSmea': True,
|
|
|
'prefer_brownian': True,
|
|
|
'ucPreset': 0,
|
|
|
'use_coords': False,
|
|
|
'v4_negative_prompt': {'caption': {'base_caption': negative_prompt, 'char_captions': []}, 'legacy_uc': False},
|
|
|
'v4_prompt': {'caption': {'base_caption': main_prompt, 'char_captions': []}, 'use_coords': False, 'use_order': True}
|
|
|
})
|
|
|
|
|
|
|
|
|
active_chars = [p for p in self.character_prompts if p['check_var'].get()]
|
|
|
if active_chars:
|
|
|
for char_info in active_chars:
|
|
|
char_prompt = char_info['prompt_widget'].get(1.0, tk.END).strip()
|
|
|
char_uc = char_info['uc_widget'].get(1.0, tk.END).strip()
|
|
|
|
|
|
char_v4_prompt = {'char_caption': char_prompt, 'centers': [{'x': 0.5, 'y': 0.5}]}
|
|
|
char_v4_uc = {'char_caption': char_uc, 'centers': [{'x': 0.5, 'y': 0.5}]}
|
|
|
|
|
|
data['parameters']['v4_prompt']['caption']['char_captions'].append(char_v4_prompt)
|
|
|
data['parameters']['v4_negative_prompt']['caption']['char_captions'].append(char_v4_uc)
|
|
|
|
|
|
|
|
|
import requests
|
|
|
response = requests.post(
|
|
|
"https://image.novelai.net/ai/generate-image",
|
|
|
headers={"Authorization": f"Bearer {access_token}"},
|
|
|
json=data,
|
|
|
timeout=180
|
|
|
)
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
|
image = self._process_nai_response(response.content)
|
|
|
self.root.after(0, self._update_generation_result, image)
|
|
|
|
|
|
except Exception as e:
|
|
|
self.root.after(0, self._update_generation_result, e)
|
|
|
|
|
|
def _process_nai_response(self, content):
|
|
|
"""NAI ์๋ต(zip)์ ์ฒ๋ฆฌํ์ฌ PIL Image ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค."""
|
|
|
import zipfile
|
|
|
try:
|
|
|
zipped = zipfile.ZipFile(io.BytesIO(content))
|
|
|
image_bytes = zipped.read(zipped.infolist()[0])
|
|
|
image = Image.open(io.BytesIO(image_bytes))
|
|
|
return image
|
|
|
except Exception as e:
|
|
|
raise Exception(f"์๋ต ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์คํจ: {e}")
|
|
|
|
|
|
def start_generation(self):
|
|
|
|
|
|
self.is_generating = True
|
|
|
self.generation_start_time = time.time()
|
|
|
self.generation_btn.config(state=tk.DISABLED)
|
|
|
self.save_btn.config(state=tk.NORMAL)
|
|
|
self.generation_image_label.config(image=None, text="๐จ ์ด๋ฏธ์ง ์์ฑ ์ค...")
|
|
|
self._update_timer()
|
|
|
|
|
|
thread = threading.Thread(target=self._generation_thread)
|
|
|
thread.daemon = True
|
|
|
thread.start()
|
|
|
|
|
|
def _update_generation_result(self, result):
|
|
|
self.is_generating = False
|
|
|
self.generation_btn.config(state=tk.NORMAL, text="Generation")
|
|
|
|
|
|
if isinstance(result, Image.Image):
|
|
|
self.last_generated_image = result
|
|
|
self.last_generated_image.save(self.temp_image_path, "PNG")
|
|
|
|
|
|
if self.auto_save_var.get():
|
|
|
try:
|
|
|
save_dir = os.path.join("Output_WD14", self.save_session_folder)
|
|
|
os.makedirs(save_dir, exist_ok=True)
|
|
|
file_path = os.path.join(save_dir, f"{self.save_counter:05d}.png")
|
|
|
self.last_generated_image.save(file_path, "PNG")
|
|
|
self.save_counter += 1
|
|
|
except Exception as e:
|
|
|
print(f"์๋ ์ ์ฅ ์คํจ: {e}")
|
|
|
|
|
|
thumbnail = self._create_thumbnail(result)
|
|
|
photo = ImageTk.PhotoImage(thumbnail)
|
|
|
self.generation_image_label.config(image=photo, text="")
|
|
|
self.generation_image_label.image = photo
|
|
|
|
|
|
elif isinstance(result, Exception):
|
|
|
self.show_generation_preview()
|
|
|
messagebox.showerror("์์ฑ ์คํจ", f"์ด๋ฏธ์ง ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค:\n{result}")
|
|
|
|
|
|
def _show_image_with_pil(self):
|
|
|
"""PIL ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ง์ง๋ง์ผ๋ก ์์ฑ๋ ์ด๋ฏธ์ง๋ฅผ ๊ธฐ๋ณธ ์ด๋ฏธ์ง ๋ทฐ์ด๋ก ์ฝ๋๋ค."""
|
|
|
if self.last_generated_image:
|
|
|
try:
|
|
|
self.last_generated_image.show()
|
|
|
except Exception as e:
|
|
|
messagebox.showerror("์ค๋ฅ", f"์ด๋ฏธ์ง๋ฅผ ์ฌ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}")
|
|
|
else:
|
|
|
messagebox.showinfo("์ ๋ณด", "ํ์ํ ์ด๋ฏธ์ง๊ฐ ์์ต๋๋ค.")
|
|
|
|
|
|
def main():
|
|
|
try:
|
|
|
from tkinterdnd2 import TkinterDnD
|
|
|
root = TkinterDnD.Tk()
|
|
|
except ImportError:
|
|
|
print("tkinterdnd2๊ฐ ์ค์น๋์ง ์์ Drag & Drop ๊ธฐ๋ฅ์ด ๋นํ์ฑํ๋ฉ๋๋ค.")
|
|
|
root = tk.Tk()
|
|
|
|
|
|
app = WD14TaggerGUI(root)
|
|
|
root.mainloop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
main() |