baqu2213 commited on
Commit
03f219a
ยท
verified ยท
1 Parent(s): 2c52b6d

Upload 2 files

Browse files
NAIA/WD14_Auto_Prompt_Generator.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3e8e9ba4a43a52dc2c79dff2a71ea654095f9916eb240f916798a724200031c5
3
+ size 1551836514
NAIA/wd14_test.py ADDED
@@ -0,0 +1,782 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tkinter as tk
2
+ from tkinter import filedialog, messagebox, scrolledtext, ttk
3
+ from PIL import Image, ImageTk, ImageGrab
4
+ import threading
5
+ import os
6
+ import numpy as np
7
+ import pandas as pd
8
+ import cv2
9
+ import tempfile
10
+ import io
11
+ import json
12
+ import sys
13
+ import os
14
+
15
+ def resource_path(relative_path):
16
+ """ PyInstaller์— ์˜ํ•ด ์ž„์‹œ ํด๋”์— ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์˜ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. """
17
+ try:
18
+ # PyInstaller๋Š” ์ž„์‹œ ํด๋”๋ฅผ ์ƒ์„ฑํ•˜๊ณ  _MEIPASS์— ๊ฒฝ๋กœ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
19
+ base_path = sys._MEIPASS
20
+ except Exception:
21
+ base_path = os.path.abspath(".")
22
+
23
+ return os.path.join(base_path, relative_path)
24
+
25
+ class WD14TaggerGUI:
26
+ def __init__(self, root):
27
+ self.root = root
28
+ self.root.title("WD14 Auto Prompt Generator (aiart)")
29
+ self.root.geometry("700x870")
30
+
31
+ self.SETTINGS_FILE = "tagger_app_settings.json"
32
+
33
+ self.current_image_path = None
34
+ self.session = None
35
+ self.tags_df = None
36
+
37
+ # ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™”
38
+ self.general_threshold_var = tk.DoubleVar(value=0.5)
39
+ self.char_threshold_var = tk.DoubleVar(value=0.85)
40
+ self.rm_c_var = tk.BooleanVar()
41
+ self.rm_color_var = tk.BooleanVar()
42
+ self.rm_clothes_var = tk.BooleanVar()
43
+ self.webui_mode_var = tk.BooleanVar()
44
+ self.instant_inference_var = tk.BooleanVar(value=False)
45
+ ## 1) ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ์ƒํƒœ ๋ณ€์ˆ˜ ์ถ”๊ฐ€
46
+ self.show_removed_tags_var = tk.BooleanVar(value=True)
47
+ self.show_ignore_tags_var = tk.BooleanVar(value=False)
48
+ self.prompt_active_var = tk.BooleanVar(value=False)
49
+
50
+
51
+ self.init_filter_data()
52
+ self.setup_ui()
53
+ self.load_model()
54
+ self.show_initial_preview()
55
+ self.setup_drag_drop()
56
+
57
+ self.load_settings()
58
+ self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
59
+
60
+ ## 2) ์ „์—ญ Ctrl+V ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ
61
+ self.root.bind('<Control-v>', self.handle_paste)
62
+
63
+ def init_filter_data(self):
64
+ # ... (๊ธฐ์กด๊ณผ ๋™์ผ)
65
+ try:
66
+ from tagbag import bag_of_tags, clothes_list
67
+ from character_dictionary import character_dictionary as cd
68
+
69
+ self.bag_of_tags = bag_of_tags
70
+ self.clothes_list = clothes_list
71
+ self.character_keys = list(cd.keys()) if isinstance(cd, dict) else []
72
+
73
+ except ImportError as e:
74
+ print(f"โš  NAIA ํŒŒ์ผ import ์‹คํŒจ: {e}")
75
+ print("๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
76
+ self.bag_of_tags = [
77
+ 'smile', 'blush', 'open_mouth', 'closed_eyes', 'wink', 'frown',
78
+ 'twintails', 'ponytail', 'braid', 'long_hair', 'short_hair',
79
+ 'large_breasts', 'small_breasts', 'cleavage', 'navel'
80
+ ]
81
+ self.clothes_list = [
82
+ 'dress', 'skirt', 'shirt', 'jacket', 'uniform', 'school_uniform'
83
+ ]
84
+ self.character_keys = []
85
+
86
+ self.colors = ['black', 'white', 'blond', 'silver', 'gray', 'yellow',
87
+ 'blue', 'purple', 'red', 'pink', 'brown', 'orange',
88
+ 'green', 'aqua', 'gradient']
89
+
90
+ ## 1) ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ํ† ๊ธ€ ํ•จ์ˆ˜
91
+ def toggle_removed_tags(self):
92
+ if self.show_removed_tags_var.get():
93
+ self.removed_tags_text.grid(row=3, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
94
+ else:
95
+ self.removed_tags_text.grid_forget()
96
+
97
+ ## 1) ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ํ† ๊ธ€ ํ•จ์ˆ˜
98
+ def toggle_ignore_tags(self):
99
+ if self.show_ignore_tags_var.get():
100
+ self.ignore_tags_text.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
101
+ self.save_settings_btn.grid(row=1, column=2, sticky="ne", padx=(10, 0))
102
+ else:
103
+ self.ignore_tags_text.grid_forget()
104
+ self.save_settings_btn.grid_forget()
105
+
106
+ def toggle_prompts(self):
107
+ pass
108
+
109
+ def setup_ui(self):
110
+ main_frame = ttk.Frame(self.root, padding="10")
111
+ main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
112
+
113
+ # ... (์ƒ๋‹จ UI ์„ค์ •์€ ๊ธฐ์กด๊ณผ ๊ฑฐ์˜ ๋™์ผ)
114
+ load_frame = ttk.Frame(main_frame)
115
+ load_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
116
+ self.load_btn = ttk.Button(load_frame, text="์ด๋ฏธ์ง€ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ", command=self.load_image)
117
+ self.load_btn.grid(row=0, column=0, padx=(0, 5))
118
+ self.clipboard_btn = ttk.Button(load_frame, text="์ด๋ฏธ์ง€ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ (ํด๋ฆฝ๋ณด๋“œ)", command=self.load_from_clipboard)
119
+ self.clipboard_btn.grid(row=0, column=1)
120
+
121
+ content_frame = ttk.Frame(main_frame)
122
+ content_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
123
+
124
+ self.preview_frame = ttk.LabelFrame(content_frame, text="๋ฏธ๋ฆฌ๋ณด๊ธฐ", padding="10")
125
+ self.preview_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 2))
126
+ self.image_label = ttk.Label(self.preview_frame, text="")
127
+ self.image_label.grid(row=0, column=0)
128
+
129
+ settings_frame = ttk.LabelFrame(content_frame, text="์„ค์ •", padding="10")
130
+ settings_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(2, 0))
131
+
132
+ ttk.Label(settings_frame, text="ํƒœ๊ทธ ์ž„๊ณ„๊ฐ’:").grid(row=0, column=0, sticky=tk.W)
133
+ ttk.Spinbox(settings_frame, from_=0.0, to=1.0, increment=0.05,
134
+ textvariable=self.general_threshold_var, width=15).grid(row=0, column=1, pady=(0, 10))
135
+
136
+ ttk.Label(settings_frame, text="์บ๋ฆญํ„ฐ ์ž„๊ณ„๊ฐ’:").grid(row=1, column=0, sticky=tk.W)
137
+ ttk.Spinbox(settings_frame, from_=0.0, to=1.0, increment=0.05,
138
+ textvariable=self.char_threshold_var, width=15).grid(row=1, column=1, pady=(0, 10))
139
+
140
+ extract_control_frame = ttk.Frame(settings_frame)
141
+ extract_control_frame.grid(row=2, column=0, columnspan=2, pady=10)
142
+
143
+ self.extract_btn = ttk.Button(extract_control_frame, text="ํƒœ๊ทธ ์ถ”์ถœ",
144
+ command=self.extract_tags, state='disabled')
145
+ self.extract_btn.pack(side=tk.LEFT, padx=(0, 10))
146
+
147
+ self.instant_inference_cb = ttk.Checkbutton(extract_control_frame, text="์ฆ‰์‹œ ์ถ”๋ก ", variable=self.instant_inference_var)
148
+ self.instant_inference_cb.pack(side=tk.LEFT)
149
+
150
+ filter_frame = ttk.LabelFrame(settings_frame, text="ํƒœ๊ทธ ํ•„ํ„ฐ๋ง", padding="5")
151
+ filter_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
152
+ ttk.Checkbutton(filter_frame, text="์บ๋ฆญํ„ฐ ํŠน์ง•+์ด๋ฆ„ ์ œ๊ฑฐ", variable=self.rm_c_var).grid(row=0, column=0, sticky=tk.W, pady=2)
153
+ ttk.Checkbutton(filter_frame, text="์˜๋ฅ˜ ์ƒ‰์ƒ ์ œ๊ฑฐ", variable=self.rm_color_var).grid(row=1, column=0, sticky=tk.W, pady=2)
154
+ ttk.Checkbutton(filter_frame, text="์˜์ƒ ์ œ๊ฑฐ (๋น„์ถ”์ฒœ)", variable=self.rm_clothes_var).grid(row=2, column=0, sticky=tk.W, pady=2)
155
+ ttk.Checkbutton(filter_frame, text="WEBUI/Comfy ๋ชจ๋“œ", variable=self.webui_mode_var).grid(row=3, column=0, sticky=tk.W, pady=2)
156
+
157
+ # ์„ ํ–‰/ํ›„ํ–‰ ํ”„๋กฌํ”„ํŠธ ํ”„๋ ˆ์ž„
158
+ prompt_frame = ttk.Frame(settings_frame)
159
+ prompt_frame.grid(row=4, column=0, columnspan=2, sticky="ew", pady=(15, 0))
160
+
161
+ self.prompt_active_cb = ttk.Checkbutton(prompt_frame, text="์„ ํ–‰/ํ›„ํ–‰ ํ”„๋กฌํ”„ํŠธ ํ™œ์„ฑ",
162
+ variable=self.prompt_active_var,
163
+ command=self.toggle_prompts)
164
+ self.prompt_active_cb.grid(row=0, column=0, sticky="w")
165
+
166
+ # ํƒญ ์œ„์ ฏ (Notebook) ์ƒ์„ฑ
167
+ self.prompt_notebook = ttk.Notebook(prompt_frame)
168
+ self.prompt_notebook.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(5, 0))
169
+
170
+ # ์„ ํ–‰ ํ”„๋กฌํ”„ํŠธ ํƒญ
171
+ prefix_frame = ttk.Frame(self.prompt_notebook, padding=5)
172
+ self.prompt_notebook.add(prefix_frame, text="์„ ํ–‰ ํ”„๋กฌํ”„ํŠธ")
173
+ self.prefix_text = scrolledtext.ScrolledText(prefix_frame, height=3, width=30)
174
+ self.prefix_text.pack(fill="both", expand=True)
175
+
176
+ # ํ›„ํ–‰ ํ”„๋กฌํ”„ํŠธ ํƒญ
177
+ suffix_frame = ttk.Frame(self.prompt_notebook, padding=5)
178
+ self.prompt_notebook.add(suffix_frame, text="ํ›„ํ–‰ ํ”„๋กฌํ”„ํŠธ")
179
+ self.suffix_text = scrolledtext.ScrolledText(suffix_frame, height=3, width=30)
180
+ self.suffix_text.pack(fill="both", expand=True)
181
+
182
+ # ์ดˆ๊ธฐ ์ƒํƒœ ์„ค์ •
183
+ self.toggle_prompts()
184
+
185
+ result_frame = ttk.LabelFrame(main_frame, text="์ถ”์ถœ๋œ ํƒœ๊ทธ", padding="10")
186
+ result_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(10, 0))
187
+
188
+ self.result_text = scrolledtext.ScrolledText(result_frame, height=8)
189
+ self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
190
+ self.result_text.tag_config("prompt", foreground="grey")
191
+
192
+ copy_frame = ttk.Frame(result_frame)
193
+ copy_frame.grid(row=1, column=0, pady=(10, 5), sticky=(tk.W, tk.E))
194
+ ttk.Button(copy_frame, text="์ „์ฒด ๋ณต์‚ฌ", command=self.copy_all_tags).grid(row=0, column=0, padx=(0, 5))
195
+ ttk.Button(copy_frame, text="2๋ฒˆ ํƒœ๊ทธ๋ถ€ํ„ฐ ๋ณต์‚ฌ", command=self.copy_from_second).grid(row=0, column=1, padx=5)
196
+ ttk.Button(copy_frame, text="3๋ฒˆ ํƒœ๊ทธ๋ถ€ํ„ฐ ๋ณต์‚ฌ", command=self.copy_from_third).grid(row=0, column=2, padx=5)
197
+
198
+ ## 1) '์ œ๊ฑฐ๋œ ํƒœ๊ทธ' ๋ผ๋ฒจ์„ ์ฒดํฌ๋ฐ•์Šค๋กœ ๋ณ€๊ฒฝ
199
+ self.removed_tags_cb = ttk.Checkbutton(result_frame, text="์ œ๊ฑฐ๋œ ํƒœ๊ทธ",
200
+ variable=self.show_removed_tags_var,
201
+ command=self.toggle_removed_tags)
202
+ self.removed_tags_cb.grid(row=2, column=0, sticky=tk.W, pady=(10, 2))
203
+
204
+ self.removed_tags_text = scrolledtext.ScrolledText(result_frame, height=5)
205
+ # self.removed_tags_text.grid(...)๋Š” toggle ํ•จ์ˆ˜์—์„œ ๊ด€๋ฆฌ
206
+
207
+ ## 1) '๋ฌด์‹œํ•  ํƒœ๊ทธ' ๋ผ๋ฒจ์„ ์ฒดํฌ๋ฐ•์Šค๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋ณ„๋„ ํ”„๋ ˆ์ž„์œผ๋กœ ๋ถ„๋ฆฌ
208
+ ignore_frame = ttk.Frame(main_frame, padding="0")
209
+ ignore_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
210
+
211
+ self.ignore_tags_cb = ttk.Checkbutton(ignore_frame, text="๋ฌด์‹œํ•  ํƒœ๊ทธ (์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„)",
212
+ variable=self.show_ignore_tags_var,
213
+ command=self.toggle_ignore_tags)
214
+ self.ignore_tags_cb.grid(row=0, column=0, sticky=tk.W)
215
+
216
+ self.ignore_tags_text = scrolledtext.ScrolledText(ignore_frame, height=4)
217
+ self.save_settings_btn = ttk.Button(ignore_frame, text="์„ค์ • ์ €์žฅ", command=self.save_settings)
218
+
219
+ ignore_frame.columnconfigure(1, weight=1)
220
+
221
+ ## 1) ์ดˆ๊ธฐ ์ƒํƒœ ์„ค์ •
222
+ self.toggle_removed_tags()
223
+ self.toggle_ignore_tags()
224
+
225
+ # ... (๊ทธ๋ฆฌ๋“œ ์„ค์ •)
226
+ self.root.columnconfigure(0, weight=1)
227
+ self.root.rowconfigure(0, weight=1)
228
+ main_frame.columnconfigure(0, weight=1)
229
+ # main_frame.rowconfigure(2)์™€ (3)์€ ๋‚ด์šฉ์— ๋”ฐ๋ผ ์ž๋™ ์กฐ์ ˆ๋˜๋„๋ก weight ๋ฏธ์„ค์ •
230
+ content_frame.columnconfigure(0, weight=1)
231
+ content_frame.columnconfigure(1, weight=1)
232
+ result_frame.columnconfigure(0, weight=1)
233
+ result_frame.rowconfigure(0, weight=1)
234
+
235
+
236
+ def on_closing(self):
237
+ self.save_settings()
238
+ self.root.destroy()
239
+
240
+ def save_settings(self):
241
+ settings = {
242
+ 'general_threshold': self.general_threshold_var.get(),
243
+ 'char_threshold': self.char_threshold_var.get(),
244
+ 'rm_c': self.rm_c_var.get(),
245
+ 'rm_color': self.rm_color_var.get(),
246
+ 'rm_clothes': self.rm_clothes_var.get(),
247
+ 'webui_mode': self.webui_mode_var.get(),
248
+ 'instant_inference': self.instant_inference_var.get(),
249
+ 'ignore_tags': self.ignore_tags_text.get(1.0, tk.END).strip(),
250
+ ## 1) ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ์ƒํƒœ ์ €์žฅ
251
+ 'show_removed_tags': self.show_removed_tags_var.get(),
252
+ 'show_ignore_tags': self.show_ignore_tags_var.get(),
253
+ 'prompt_active': self.prompt_active_var.get(),
254
+ 'prefix_prompt': self.prefix_text.get(1.0, tk.END).strip(),
255
+ 'suffix_prompt': self.suffix_text.get(1.0, tk.END).strip()
256
+ }
257
+ try:
258
+ with open(self.SETTINGS_FILE, 'w', encoding='utf-8') as f:
259
+ json.dump(settings, f, indent=4)
260
+ # if self.root.focus_get() == self.save_settings_btn:
261
+ # messagebox.showinfo("์ €์žฅ ์™„๋ฃŒ", f"์„ค์ •์ด {self.SETTINGS_FILE}์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
262
+ except Exception as e:
263
+ messagebox.showerror("์ €์žฅ ์‹คํŒจ", f"์„ค์ • ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
264
+
265
+ def load_settings(self):
266
+ try:
267
+ if os.path.exists(self.SETTINGS_FILE):
268
+ with open(self.SETTINGS_FILE, 'r', encoding='utf-8') as f:
269
+ settings = json.load(f)
270
+ # ... (๊ธฐ์กด ์„ค์ • ๋กœ๋“œ)
271
+ self.general_threshold_var.set(settings.get('general_threshold', 0.5))
272
+ self.char_threshold_var.set(settings.get('char_threshold', 0.85))
273
+ self.rm_c_var.set(settings.get('rm_c', False))
274
+ self.rm_color_var.set(settings.get('rm_color', False))
275
+ self.rm_clothes_var.set(settings.get('rm_clothes', False))
276
+ self.webui_mode_var.set(settings.get('webui_mode', False))
277
+ self.instant_inference_var.set(settings.get('instant_inference', False))
278
+
279
+ self.ignore_tags_text.delete(1.0, tk.END)
280
+ self.ignore_tags_text.insert(1.0, settings.get('ignore_tags', ''))
281
+
282
+ ## 1) ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ์ƒํƒœ ๋กœ๋“œ ๋ฐ UI ์—…๋ฐ์ดํŠธ
283
+ self.show_removed_tags_var.set(settings.get('show_removed_tags', True))
284
+ self.show_ignore_tags_var.set(settings.get('show_ignore_tags', False))
285
+ self.toggle_removed_tags()
286
+ self.toggle_ignore_tags()
287
+ self.prompt_active_var.set(settings.get('prompt_active', False))
288
+
289
+ self.prefix_text.delete(1.0, tk.END)
290
+ self.prefix_text.insert(1.0, settings.get('prefix_prompt', ''))
291
+
292
+ self.suffix_text.delete(1.0, tk.END)
293
+ self.suffix_text.insert(1.0, settings.get('suffix_prompt', ''))
294
+
295
+ self.toggle_prompts() # UI ์ƒํƒœ ์—…๋ฐ์ดํŠธ
296
+
297
+ except Exception as e:
298
+ messagebox.showwarning("์„ค์ • ๋กœ๋“œ ์‹คํŒจ", f"์„ค์ • ํŒŒ์ผ({self.SETTINGS_FILE})์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
299
+
300
+ ## 2) ์ „์—ญ ๋ถ™์—ฌ๋„ฃ๊ธฐ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
301
+ def handle_paste(self, event):
302
+ try:
303
+ # 1. ํด๋ฆฝ๋ณด๋“œ์— ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
304
+ clipboard_image = ImageGrab.grabclipboard()
305
+ if isinstance(clipboard_image, Image.Image):
306
+ self.load_from_clipboard()
307
+ return "break" # ์ด๋ฒคํŠธ ์ „ํŒŒ ์ค‘๋‹จ
308
+ except (ValueError, TypeError):
309
+ pass
310
+ except Exception as e:
311
+ print(f"ํด๋ฆฝ๋ณด๋“œ ์ด๋ฏธ์ง€ ํ™•์ธ ์ค‘ ์˜ค๋ฅ˜: {e}")
312
+
313
+
314
+ try:
315
+ # ํด๋ฆฝ๋ณด๋“œ์˜ ํ…์ŠคํŠธ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
316
+ clipboard_text = self.root.clipboard_get()
317
+
318
+ # 2. ํด๋ฆฝ๋ณด๋“œ์— ํ…์ŠคํŠธ๊ฐ€ ์žˆ๊ณ , URL ํ˜•์‹์ธ์ง€ ํ™•์ธ
319
+ if isinstance(clipboard_text, str) and clipboard_text.startswith(('http://', 'https://')):
320
+ self._load_image_from_url(clipboard_text)
321
+ return "break" # ์ด๋ฒคํŠธ ์ „ํŒŒ ์ค‘๋‹จ
322
+
323
+ ## --- ์—ฌ๊ธฐ๋ถ€ํ„ฐ ์ถ”๊ฐ€๋˜๋Š” ๋ถ€๋ถ„ --- ##
324
+ # 3. ํด๋ฆฝ๋ณด๋“œ ํ…์ŠคํŠธ๊ฐ€ ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ฒฝ๋กœ์ธ์ง€ ํ™•์ธ
325
+ image_extensions = ('.jpg', '.jpeg', '.png', '.webp', '.bmp', '.tiff')
326
+ if isinstance(clipboard_text, str) and clipboard_text.lower().endswith(image_extensions):
327
+ # ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ์ง„ ๊ฒฝ๋กœ์ผ ๊ฒฝ์šฐ ์ œ๊ฑฐ (ํŒŒ์ผ ํƒ์ƒ‰๊ธฐ์—์„œ ๋ณต์‚ฌ ์‹œ)
328
+ path = clipboard_text.strip('"')
329
+ # ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์‹ค์ œ ์กด์žฌํ•˜๋Š” ๊ฒฝ๋กœ์ธ์ง€ ํ™•์ธ ํ›„ ํ—ฌํผ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
330
+ if os.path.exists(path):
331
+ self._load_image_from_path(path)
332
+ return "break" # ์ด๋ฒคํŠธ ์ „ํŒŒ ์ค‘๋‹จ
333
+ ## --- ์—ฌ๊ธฐ๊นŒ์ง€ ์ถ”๊ฐ€ --- ##
334
+
335
+ except tk.TclError:
336
+ # ํด๋ฆฝ๋ณด๋“œ์— ํ…์ŠคํŠธ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
337
+ pass
338
+ except Exception as e:
339
+ messagebox.showerror("๋ถ™์—ฌ๋„ฃ๊ธฐ ์˜ค๋ฅ˜", f"์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
340
+ return "break"
341
+
342
+ # ์œ„ ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ ๋ถ™์—ฌ๋„ฃ๊ธฐ ๋™์ž‘
343
+ return
344
+
345
+ ## 2) URL ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๋ณ„๋„ ๋ฉ”์„œ๋“œ๋กœ ๋ถ„๋ฆฌ (์žฌ์‚ฌ์šฉ์„ฑ)
346
+ def _load_image_from_url(self, url):
347
+ try:
348
+ img = self.download_image_from_url(url)
349
+ temp_dir = tempfile.gettempdir()
350
+ temp_file = os.path.join(temp_dir, f"wd14_url_{os.getpid()}.png")
351
+ img.save(temp_file)
352
+
353
+ self.current_image_path = temp_file
354
+ self.show_preview(temp_file)
355
+ if self.session is not None:
356
+ self.extract_btn.config(state='normal')
357
+ if self.instant_inference_var.get():
358
+ self.extract_tags()
359
+ except Exception as e:
360
+ messagebox.showerror("์˜ค๋ฅ˜", f"์›น ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ: {str(e)}")
361
+
362
+ def load_model(self):
363
+ try:
364
+ import onnxruntime as ort
365
+
366
+ # --- ์ˆ˜์ • ํ›„ ---
367
+ model_path = resource_path("model.onnx")
368
+ tags_path = resource_path("selected_tags.csv")
369
+
370
+ self.session = ort.InferenceSession(model_path, providers=['CPUExecutionProvider'])
371
+ self.tags_df = pd.read_csv(tags_path)
372
+
373
+ except Exception as e:
374
+ messagebox.showerror("์˜ค๋ฅ˜", f"๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ: {str(e)}")
375
+
376
+ def show_initial_preview(self):
377
+ # ... (๊ธฐ์กด๊ณผ ๋™์ผ)
378
+ try:
379
+ white_image = Image.new('RGB', (374, 374), 'white')
380
+ photo = ImageTk.PhotoImage(white_image)
381
+ self.image_label.config(image=photo, text="์ด๋ฏธ์ง€๋ฅผ Drag&Drop\nํ•ด์ฃผ์„ธ์š”")
382
+ self.image_label.image = photo
383
+ except Exception as e:
384
+ self.image_label.config(text="์ด๋ฏธ์ง€๋ฅผ Drag&Drop\nํ•ด์ฃผ์„ธ์š”")
385
+
386
+ def setup_drag_drop(self):
387
+ # ... (๊ธฐ์กด๊ณผ ๋™์ผ)
388
+ try:
389
+ from tkinterdnd2 import TkinterDnD, DND_ALL
390
+ self.TkdndVersion = TkinterDnD._require(self.root)
391
+ self.image_label.drop_target_register(DND_ALL)
392
+ self.image_label.dnd_bind('<<Drop>>', self.on_drop)
393
+ self.image_label.dnd_bind('<<DragEnter>>', self.on_drag_enter)
394
+ self.image_label.dnd_bind('<<DragLeave>>', self.on_drag_leave)
395
+ except ImportError:
396
+ print("tkinterdnd2๊ฐ€ ์„ค์น˜๋˜์ง€ ์•Š์Œ. pip install tkinterdnd2๋กœ ์„ค์น˜ํ•˜๋ฉด ๋“œ๋ž˜๊ทธ์•ค๋“œ๋กญ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
397
+ except Exception as e:
398
+ print(f"Drag & Drop ์ดˆ๊ธฐํ™” ์˜ค๋ฅ˜: {e}")
399
+
400
+ # ... (on_drag_enter, on_drag_leave, download_image_from_url ๊ธฐ์กด๊ณผ ๋™์ผ)
401
+ def on_drag_enter(self, event):
402
+ try:
403
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ด๋ฏธ์ง€๋ฅผ ๋†“์•„์ฃผ์„ธ์š”!)")
404
+ except: pass
405
+
406
+ def on_drag_leave(self, event):
407
+ try:
408
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
409
+ except: pass
410
+
411
+ def download_image_from_url(self, url):
412
+ import requests
413
+ response = requests.get(url)
414
+ response.raise_for_status()
415
+ return Image.open(io.BytesIO(response.content))
416
+
417
+ def on_drop(self, event):
418
+ try:
419
+ if not isinstance(event, str):
420
+ file_path_or_url = event.data.strip("{}")
421
+ else:
422
+ file_path_or_url = event
423
+
424
+ if not file_path_or_url:
425
+ messagebox.showwarning("๊ฒฝ๊ณ ", "๋“œ๋กญ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
426
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
427
+ return
428
+
429
+ # URL ์ฒ˜๋ฆฌ
430
+ if file_path_or_url.startswith(('http://', 'https://')):
431
+ self._load_image_from_url(file_path_or_url) ## 2) ์žฌ์‚ฌ์šฉ๋˜๋Š” URL ๋กœ๋“œ ํ•จ์ˆ˜ ํ˜ธ์ถœ
432
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
433
+ return
434
+ else:
435
+ if file_path_or_url.startswith(('blob:')):
436
+ messagebox.showwarning("Blob URL ์˜ค๋ฅ˜", "NAI ํ™ˆํŽ˜์ด์ง€์—์„œ ์ƒ์„ฑํ•œ ์ด๋ฏธ์ง€๋Š” ์ฆ‰์‹œ ๋ณต์‚ฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.\n์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋กœ๋“œํ•œ ํ›„ ๋“œ๋ž˜๊ทธ & ๋“œ๋กญํ•ด์ฃผ์„ธ์š”.")
437
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
438
+ return
439
+
440
+ # ํŒŒ์ผ ์ฒ˜๋ฆฌ
441
+ file_path_or_url = file_path_or_url.replace("\\", '/')
442
+ if os.path.exists(file_path_or_url):
443
+ self.current_image_path = file_path_or_url
444
+ self.show_preview(file_path_or_url)
445
+ if self.session is not None:
446
+ self.extract_btn.config(state='normal')
447
+ if self.instant_inference_var.get():
448
+ self.extract_tags()
449
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
450
+ else:
451
+ messagebox.showerror("์˜ค๋ฅ˜", f"์œ ํšจํ•˜์ง€ ์•Š์€ ํŒŒ์ผ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค: '{file_path_or_url}'")
452
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
453
+
454
+ except Exception as e:
455
+ messagebox.showerror("์˜ค๋ฅ˜", f"ํŒŒ์ผ ๋“œ๋กญ ์˜ค๋ฅ˜: {str(e)}")
456
+ self.preview_frame.config(text="๋ฏธ๋ฆฌ๋ณด๊ธฐ")
457
+
458
+ def _load_image_from_path(self, path):
459
+ """์ฃผ์–ด์ง„ ํŒŒ์ผ ๊ฒฝ๋กœ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๊ณ  ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค."""
460
+ # ๊ฒฝ๋กœ์˜ ์œ ํšจ์„ฑ์„ ํ•œ ๋ฒˆ ๋” ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
461
+ if os.path.exists(path):
462
+ self.current_image_path = path
463
+ self.show_preview(path)
464
+ if self.session is not None:
465
+ self.extract_btn.config(state='normal')
466
+ if self.instant_inference_var.get():
467
+ self.extract_tags()
468
+ else:
469
+ # ํ˜น์‹œ ๋ชจ๋ฅผ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•œ ๊ฒฝ๊ณ ์ฐฝ
470
+ messagebox.showwarning("๊ฒฝ๋กœ ์˜ค๋ฅ˜", f"ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {path}")
471
+
472
+ def load_from_clipboard(self):
473
+ try:
474
+ image = ImageGrab.grabclipboard()
475
+ if isinstance(image, Image.Image):
476
+ temp_dir = tempfile.gettempdir()
477
+ temp_file = os.path.join(temp_dir, f"wd14_clipboard_{os.getpid()}.png")
478
+ # RGBA ์ด๋ฏธ์ง€๋ฅผ RGB๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ JPEG/PNG ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ ๋ฐฉ์ง€
479
+ if image.mode == 'RGBA':
480
+ image = image.convert('RGB')
481
+ image.save(temp_file, 'PNG') # ํฌ๋งท์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •
482
+
483
+ self.current_image_path = temp_file
484
+ self.show_preview(temp_file)
485
+ if self.session is not None:
486
+ self.extract_btn.config(state='normal')
487
+ if self.instant_inference_var.get():
488
+ self.extract_tags()
489
+ else:
490
+ # ์ด ๋ฉ”์‹œ์ง€๋Š” handle_paste ๋กœ์ง ๋•Œ๋ฌธ์— ๊ฑฐ์˜ ๋ณด์ด์ง€ ์•Š๊ฒŒ ๋จ
491
+ messagebox.showinfo("์ •๋ณด", "ํด๋ฆฝ๋ณด๋“œ์— ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
492
+ except Exception as e:
493
+ messagebox.showerror("์˜ค๋ฅ˜", f"ํด๋ฆฝ๋ณด๋“œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์˜ค๋ฅ˜: {str(e)}")
494
+
495
+ # ... (load_image, show_preview, extract_tags, _extract_tags_thread, apply_tag_filters,
496
+ # _is_character_name, _format_results, copy ํ•จ์ˆ˜๋“ค, _update_results, make_square, smart_resize ๊ธฐ์กด๊ณผ ๋™์ผ)
497
+ def load_image(self):
498
+ file_path = filedialog.askopenfilename(
499
+ title="์ด๋ฏธ์ง€ ์„ ํƒ",
500
+ filetypes=[("์ด๋ฏธ์ง€ ํŒŒ์ผ", "*.jpg *.jpeg *.png *.bmp *.tiff *.webp")]
501
+ )
502
+ if file_path:
503
+ self.current_image_path = file_path
504
+ self.show_preview(file_path)
505
+ if self.session is not None:
506
+ self.extract_btn.config(state='normal')
507
+ if self.instant_inference_var.get():
508
+ self.extract_tags()
509
+
510
+ def show_preview(self, image_path):
511
+ try:
512
+ image = Image.open(image_path).convert('RGB')
513
+ size = max(image.size)
514
+ square_image = Image.new('RGB', (size, size), 'white')
515
+ paste_x = (size - image.width) // 2
516
+ paste_y = (size - image.height) // 2
517
+ square_image.paste(image, (paste_x, paste_y))
518
+ square_image.thumbnail((374, 374), Image.Resampling.LANCZOS)
519
+ photo = ImageTk.PhotoImage(square_image)
520
+ self.image_label.config(image=photo, text="")
521
+ self.image_label.image = photo
522
+ except Exception as e:
523
+ self.image_label.config(text=f"์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ: {str(e)}")
524
+
525
+ def extract_tags(self):
526
+ if not self.current_image_path or not self.session:
527
+ return
528
+ self.extract_btn.config(state='disabled', text='์ถ”์ถœ ์ค‘...')
529
+ self.result_text.delete(1.0, tk.END)
530
+ self.result_text.insert(tk.END, "ํƒœ๊ทธ ์ถ”์ถœ ์ค‘...")
531
+ self.removed_tags_text.delete(1.0, tk.END)
532
+ thread = threading.Thread(target=self._extract_tags_thread)
533
+ thread.daemon = True
534
+ thread.start()
535
+
536
+ def _extract_tags_thread(self):
537
+ try:
538
+ input_image = Image.open(self.current_image_path)
539
+ _, height, _, _ = self.session.get_inputs()[0].shape
540
+ image = input_image.convert('RGBA')
541
+ new_image = Image.new('RGBA', image.size, 'WHITE')
542
+ new_image.paste(image, mask=image)
543
+ image = new_image.convert('RGB')
544
+ image = np.asarray(image)[:, :, ::-1]
545
+ image = self.make_square(image, height)
546
+ image = self.smart_resize(image, height)
547
+ image = image.astype(np.float32)
548
+ image = np.expand_dims(image, 0)
549
+ input_name = self.session.get_inputs()[0].name
550
+ label_name = self.session.get_outputs()[0].name
551
+ confidents = self.session.run([label_name], {input_name: image})[0]
552
+ tags_with_conf = []
553
+ for i, conf in enumerate(confidents[0]):
554
+ if i < len(self.tags_df):
555
+ tags_with_conf.append((self.tags_df.iloc[i]['name'], float(conf)))
556
+ tags = dict(tags_with_conf[4:])
557
+
558
+ result_tuple = self._format_results(tags)
559
+ self.root.after(0, self._update_results, result_tuple)
560
+ except Exception as e:
561
+ error_msg = f"ํƒœ๊ทธ ์ถ”์ถœ ์˜ค๋ฅ˜: {str(e)}"
562
+ self.root.after(0, self._update_results, (error_msg, ""))
563
+
564
+ def apply_tag_filters(self, tags_list):
565
+ """ํƒœ๊ทธ ํ•„ํ„ฐ๋ง ์ ์šฉ (๊ณต๋ฐฑ ๊ธฐ์ค€์œผ๋กœ ๋กœ์ง ๋‹จ์ˆœํ™”)"""
566
+ # ๋ชจ๋ธ์—์„œ ๋„˜์–ด์˜จ ์›๋ณธ ํƒœ๊ทธ(_ ํฌํ•จ) ๋ฆฌ์ŠคํŠธ
567
+ original_tags = tags_list.copy()
568
+ # ๋ชจ๋“  ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๊ณต๋ฐฑ ๊ธฐ์ค€์œผ๋กœ ๋ณ€ํ™˜ํ•œ ํƒœ๊ทธ ๋ฆฌ์ŠคํŠธ
569
+ tags_to_process = [tag.replace('_', ' ') for tag in original_tags]
570
+
571
+ # ์ตœ์ข…์ ์œผ๋กœ ๋‚จ์„ ํƒœ๊ทธ๋“ค์˜ ์ธ๋ฑ์Šค๋ฅผ ๊ด€๋ฆฌ
572
+ final_indices = list(range(len(tags_to_process)))
573
+
574
+ user_removed_originals = []
575
+ other_removed_originals = []
576
+
577
+ # 1. ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ ๋ฌด์‹œํ•  ํƒœ๊ทธ ์ฒ˜๋ฆฌ
578
+ ignore_list_str = self.ignore_tags_text.get(1.0, tk.END).strip()
579
+ if ignore_list_str:
580
+ user_ignored_tags = {tag.strip().replace('_', ' ') for tag in ignore_list_str.split(',') if tag.strip()}
581
+ indices_to_remove = {i for i in final_indices if tags_to_process[i] in user_ignored_tags}
582
+ for i in indices_to_remove:
583
+ user_removed_originals.append(original_tags[i])
584
+ final_indices = [i for i in final_indices if i not in indices_to_remove]
585
+
586
+ # 2. ๋‚˜๋จธ์ง€ ํ•„ํ„ฐ๋“ค ์ ์šฉ (๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ด๋ฏธ ๊ณต๋ฐฑ ๊ธฐ์ค€์ด๋ฏ€๋กœ ์ง์ ‘ ๋น„๊ต)
587
+ if self.rm_c_var.get():
588
+ 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])}
589
+ for i in indices_to_remove:
590
+ other_removed_originals.append(original_tags[i])
591
+ final_indices = [i for i in final_indices if i not in indices_to_remove]
592
+
593
+ if self.rm_color_var.get():
594
+ 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]}
595
+ for i in indices_to_remove:
596
+ other_removed_originals.append(original_tags[i])
597
+ final_indices = [i for i in final_indices if i not in indices_to_remove]
598
+
599
+ if self.rm_clothes_var.get():
600
+ 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}
601
+ for i in indices_to_remove:
602
+ other_removed_originals.append(original_tags[i])
603
+ final_indices = [i for i in final_indices if i not in indices_to_remove]
604
+
605
+ # 3. ์ตœ์ข… ํƒœ๊ทธ ๋ชฉ๋ก ์ƒ์„ฑ ๋ฐ ํฌ๋งทํŒ…
606
+ final_tags_with_space = [tags_to_process[i] for i in final_indices]
607
+
608
+ if self.webui_mode_var.get():
609
+ final_tags_formatted = [tag.replace('(', '\\(').replace(')', '\\)') for tag in final_tags_with_space]
610
+ else:
611
+ final_tags_formatted = final_tags_with_space
612
+
613
+ # 4. ์„ ํ–‰/ํ›„ํ–‰ ํ”„๋กฌํ”„ํŠธ ์ฒ˜๋ฆฌ
614
+ final_tags_with_type = [(tag, 'original') for tag in final_tags_formatted]
615
+ if self.prompt_active_var.get():
616
+ prefix_str = self.prefix_text.get(1.0, tk.END).strip()
617
+ if prefix_str:
618
+ prefix_tags = [t.strip() for t in prefix_str.split(',') if t.strip()]
619
+ prefix_tags_with_type = [(tag, 'prompt') for tag in prefix_tags]
620
+ person_tags = {'1girl', '2girls', '3girls', '4girls', '5girls', '6+girls',
621
+ '1boy', '2boys', '3boys', '4boys', '5boys', '6+boys'}
622
+ last_person_tag_index = -1
623
+ search_range = min(len(final_tags_with_type), 4)
624
+ for i in range(search_range):
625
+ if final_tags_with_type[i][0] in person_tags:
626
+ last_person_tag_index = i
627
+ if last_person_tag_index != -1:
628
+ final_tags_with_type[last_person_tag_index+1:last_person_tag_index+1] = prefix_tags_with_type
629
+ else:
630
+ final_tags_with_type[0:0] = prefix_tags_with_type
631
+
632
+ suffix_str = self.suffix_text.get(1.0, tk.END).strip()
633
+ if suffix_str:
634
+ suffix_tags = [t.strip() for t in suffix_str.split(',') if t.strip()]
635
+ suffix_tags_with_type = [(tag, 'prompt') for tag in suffix_tags]
636
+ final_tags_with_type.extend(suffix_tags_with_type)
637
+
638
+ return final_tags_with_type, user_removed_originals, list(set(other_removed_originals))
639
+
640
+ def _is_character_name(self, tag):
641
+ if hasattr(self, 'character_keys') and tag in self.character_keys:
642
+ return True
643
+ if ('_' in tag and any(c.isupper() for c in tag) and
644
+ not tag.startswith('1') and
645
+ not any(word in tag.lower() for word in ['girl', 'boy', 'solo', 'multiple']) and
646
+ not any(word in tag.lower() for word in ['hair', 'eye', 'dress', 'shirt']) and
647
+ len(tag) > 3):
648
+ return True
649
+ return False
650
+
651
+ def _format_results(self, tags):
652
+ """๊ฒฐ๊ณผ ํฌ๋งทํŒ… (ํƒ€์ž… ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ)"""
653
+ # ... (๋ฉ”์„œ๋“œ ์ƒ๋‹จ์˜ 1์ฐจ ํ•„ํ„ฐ๋ง ๋กœ์ง์€ ๋™์ผ) ...
654
+ general_threshold = self.general_threshold_var.get()
655
+ char_threshold = self.char_threshold_var.get()
656
+
657
+ person_tags = {'1girl', '2girls', '3girls', '4girls', '5girls', '6+girls',
658
+ '1boy', '2boys', '3boys', '4boys', '5boys', '6+boys',
659
+ 'multiple_girls', 'multiple_boys', 'solo', 'no_humans'}
660
+
661
+ filtered_tags = []
662
+ for tag, conf in tags.items():
663
+ if tag in person_tags:
664
+ if conf >= 0.9: filtered_tags.append(tag)
665
+ elif any(c.isupper() for c in tag) and '_' in tag and conf >= char_threshold:
666
+ filtered_tags.append(tag)
667
+ elif conf >= general_threshold:
668
+ filtered_tags.append(tag)
669
+
670
+ # 2์ฐจ ํ•„ํ„ฐ๋ง: ํƒ€์ž… ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ์ตœ์ข… ํƒœ๊ทธ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ›์Œ
671
+ final_tags_with_type, user_removed, other_removed = self.apply_tag_filters(filtered_tags)
672
+
673
+ # ์ œ๊ฑฐ๋œ ํƒœ๊ทธ๋“ค์„ ํ‘œ์‹œ์šฉ์œผ๋กœ ์žฌ๊ตฌ์„ฑ
674
+ removed_for_display = []
675
+ for tag in user_removed:
676
+ removed_for_display.append((tag, 'ignored'))
677
+ for tag in other_removed:
678
+ if tag not in user_removed:
679
+ removed_for_display.append((tag, 'other'))
680
+
681
+ # ํŠœํ”Œ๋กœ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜: ((ํƒœ๊ทธ, ํƒ€์ž…) ๋ฆฌ์ŠคํŠธ, (ํƒœ๊ทธ, ํƒ€์ž…) ๋ฆฌ์ŠคํŠธ)
682
+ return final_tags_with_type, removed_for_display
683
+
684
+ def copy_all_tags(self):
685
+ content = self.result_text.get(1.0, tk.END).strip()
686
+ if content:
687
+ self.root.clipboard_clear()
688
+ self.root.clipboard_append(content)
689
+ #messagebox.showinfo("๋ณต์‚ฌ ์™„๋ฃŒ", "์ „์ฒด ํƒœ๊ทธ๊ฐ€ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
690
+ else:
691
+ messagebox.showwarning("๋ณต์‚ฌ ์‹คํŒจ", "๋ณต์‚ฌํ•  ํƒœ๊ทธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
692
+
693
+ def copy_from_second(self):
694
+ self._copy_from_index(1, "2๋ฒˆ ํƒœ๊ทธ๋ถ€ํ„ฐ")
695
+
696
+ def copy_from_third(self):
697
+ self._copy_from_index(2, "3๋ฒˆ ํƒœ๊ทธ๋ถ€ํ„ฐ")
698
+
699
+ def _copy_from_index(self, start_index, description):
700
+ content = self.result_text.get(1.0, tk.END).strip()
701
+ if content:
702
+ tags_list = [tag.strip() for tag in content.split(',')]
703
+ if len(tags_list) > start_index:
704
+ selected_tags = tags_list[start_index:]
705
+ result = ', '.join(selected_tags)
706
+ self.root.clipboard_clear()
707
+ self.root.clipboard_append(result)
708
+ #messagebox.showinfo("๋ณต์‚ฌ ์™„๋ฃŒ", f"{description} ํƒœ๊ทธ๊ฐ€ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
709
+ else:
710
+ messagebox.showwarning("๋ณต์‚ฌ ์‹คํŒจ", f"ํƒœ๊ทธ๊ฐ€ {start_index + 1}๊ฐœ ๋ฏธ๋งŒ์ž…๋‹ˆ๋‹ค.")
711
+ else:
712
+ messagebox.showwarning("๋ณต์‚ฌ ์‹คํŒจ", "๋ณต์‚ฌํ•  ํƒœ๊ทธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
713
+
714
+ def _update_results(self, result_tuple):
715
+ """๊ฒฐ๊ณผ ์—…๋ฐ์ดํŠธ (์„ ํ–‰/ํ›„ํ–‰ ํ”„๋กฌํ”„ํŠธ ์ƒ‰์ƒ ์ ์šฉ)"""
716
+ main_tags_info, removed_tags_info = result_tuple
717
+
718
+ # ๋ฉ”์ธ ํƒœ๊ทธ ๋ฐ•์Šค ์—…๋ฐ์ดํŠธ
719
+ self.result_text.delete(1.0, tk.END)
720
+ if main_tags_info:
721
+ for i, (tag, tag_type) in enumerate(main_tags_info):
722
+ if i > 0:
723
+ self.result_text.insert(tk.END, ', ')
724
+
725
+ # ํ”„๋กฌํ”„ํŠธ ํƒœ๊ทธ์ผ ๊ฒฝ์šฐ "prompt" ์Šคํƒ€์ผ ์ ์šฉ
726
+ if tag_type == 'prompt':
727
+ self.result_text.insert(tk.END, tag, 'prompt')
728
+ else: # 'original' ํƒœ๊ทธ
729
+ self.result_text.insert(tk.END, tag)
730
+
731
+ # ์ œ๊ฑฐ๋œ ํƒœ๊ทธ ๋ฐ•์Šค ์—…๋ฐ์ดํŠธ (๊ธฐ์กด๊ณผ ๋™์ผ)
732
+ self.removed_tags_text.config(state=tk.NORMAL)
733
+ self.removed_tags_text.delete(1.0, tk.END)
734
+ if removed_tags_info:
735
+ for i, (tag, tag_type) in enumerate(removed_tags_info):
736
+ display_tag = tag.replace('_', ' ')
737
+
738
+ if i > 0:
739
+ self.removed_tags_text.insert(tk.END, ', ')
740
+
741
+ if tag_type == 'ignored':
742
+ self.removed_tags_text.insert(tk.END, display_tag, 'ignored')
743
+ else:
744
+ self.removed_tags_text.insert(tk.END, display_tag)
745
+ self.removed_tags_text.config(state=tk.DISABLED)
746
+
747
+ self.extract_btn.config(state='normal', text='ํƒœ๊ทธ ์ถ”์ถœ')
748
+
749
+ def make_square(self, img, target_size):
750
+ old_size = img.shape[:2]
751
+ desired_size = max(old_size)
752
+ desired_size = max(desired_size, target_size)
753
+ delta_w = desired_size - old_size[1]
754
+ delta_h = desired_size - old_size[0]
755
+ top, bottom = delta_h // 2, delta_h - (delta_h // 2)
756
+ left, right = delta_w // 2, delta_w - (delta_w // 2)
757
+ color = [255, 255, 255]
758
+ new_im = cv2.copyMakeBorder(
759
+ img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color
760
+ )
761
+ return new_im
762
+
763
+ def smart_resize(self, img, size):
764
+ if img.shape[0] > size:
765
+ img = cv2.resize(img, (size, size), interpolation=cv2.INTER_AREA)
766
+ elif img.shape[0] < size:
767
+ img = cv2.resize(img, (size, size), interpolation=cv2.INTER_CUBIC)
768
+ return img
769
+
770
+ def main():
771
+ try:
772
+ from tkinterdnd2 import TkinterDnD
773
+ root = TkinterDnD.Tk()
774
+ except ImportError:
775
+ print("tkinterdnd2๊ฐ€ ์„ค์น˜๋˜์ง€ ์•Š์•„ Drag & Drop ๊ธฐ๋Šฅ์ด ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.")
776
+ root = tk.Tk()
777
+
778
+ app = WD14TaggerGUI(root)
779
+ root.mainloop()
780
+
781
+ if __name__ == "__main__":
782
+ main()