DucHaiten commited on
Commit
45a5974
1 Parent(s): e708dfd

Update image_to_tag.py

Browse files
Files changed (1) hide show
  1. image_to_tag.py +784 -784
image_to_tag.py CHANGED
@@ -1,784 +1,784 @@
1
- import tkinter as tk
2
- from tkinter import filedialog, messagebox, ttk
3
- from PIL import Image as PILImage, ImageTk
4
- import os
5
- import queue
6
- import threading
7
- import subprocess
8
- import sys
9
- import json
10
-
11
- # Global variables to control the process and track errors
12
- stop_processing = False
13
- error_messages = []
14
- selected_files = []
15
- save_directory = "" # Sử dụng thư mục của ảnh đầu tiên được chọn
16
- caption_window = None
17
- caption_frame = None
18
- thumbnails = [] # To hold references to thumbnail objects
19
- caption_text_widgets = [] # To hold Text widgets containing captions
20
- tag_dict = {} # Store tags and their counts
21
- selected_tag = None # Store the selected tag widget
22
- edit_buttons = {} # To hold the edit and delete buttons for each tag
23
- error_window = None # To ensure only one error window is open at a time
24
- tag_text_frame = None # Define globally to manage tag frame
25
-
26
- # Global Tkinter variable placeholders
27
- status_var = None
28
- num_files_var = None
29
- errors_var = None
30
- progress = None
31
- character_threshold_var = None
32
- general_threshold_var = None
33
- thread_count_var = None
34
- batch_size_var = None
35
- start_button = None
36
- stop_button = None
37
- prepend_text_var = None
38
- append_text_var = None
39
- current_page = 0
40
- images_per_page = 20
41
- total_pages = 1 # Initialize total pages
42
-
43
- def update_and_save_config():
44
- """Update and save the configuration to JSON."""
45
- save_config_to_json(
46
- model="swinv2", # Default model
47
- general_threshold=general_threshold_var.get(),
48
- character_threshold=character_threshold_var.get(),
49
- model_dir="D:/test/models/wd-swinv2-tagger-v3" # Default model directory
50
- )
51
-
52
- def show_errors(root):
53
- """Display error details in a new window."""
54
- global error_window
55
- if error_window is not None:
56
- return # Only one error window can be open at a time
57
-
58
- error_window = tk.Toplevel(root)
59
- error_window.title("Error Details")
60
- error_window.geometry("500x400")
61
-
62
- error_text = tk.Text(error_window, wrap='word')
63
- error_text.pack(expand=True, fill='both')
64
-
65
- if error_messages:
66
- for error in error_messages:
67
- error_text.insert('end', error + '\n')
68
- else:
69
- error_text.insert('end', "No errors recorded.")
70
-
71
- error_text.config(state='disabled')
72
-
73
- def on_close_error_window():
74
- global error_window
75
- error_window.destroy()
76
- error_window = None
77
-
78
- error_window.protocol("WM_DELETE_WINDOW", on_close_error_window)
79
-
80
- def save_config_to_json(model, general_threshold, character_threshold, model_dir, filepath='config.json'):
81
- """Save the model and threshold values to a JSON file."""
82
- config = {
83
- 'model': model,
84
- 'general_threshold': general_threshold,
85
- 'character_threshold': character_threshold,
86
- 'model_dir': model_dir
87
- }
88
- try:
89
- with open(filepath, 'w') as f:
90
- json.dump(config, f, indent=2)
91
- except Exception as e:
92
- print(f"Error saving config to JSON: {e}")
93
-
94
- def open_image_to_tag():
95
- global stop_processing, error_messages, selected_files, save_directory, caption_window, caption_frame, thumbnails, caption_text_widgets, tag_dict, selected_tag, edit_buttons, tag_text_frame, current_page, total_pages, content_canvas
96
- global status_var, num_files_var, errors_var, progress, character_threshold_var, general_threshold_var, thread_count_var, batch_size_var
97
- global start_button, stop_button, prepend_text_var, append_text_var
98
-
99
- # Create Tkinter window
100
- root = tk.Tk()
101
- root.title("Image Caption Generator")
102
-
103
- # Initialize Tkinter variables
104
- status_var = tk.StringVar()
105
- num_files_var = tk.StringVar()
106
- errors_var = tk.StringVar(value="Errors: 0")
107
- progress = tk.IntVar()
108
- character_threshold_var = tk.DoubleVar(value=0.35)
109
- general_threshold_var = tk.DoubleVar(value=0.35)
110
- thread_count_var = tk.IntVar(value=4)
111
- batch_size_var = tk.IntVar(value=4)
112
- prepend_text_var = tk.StringVar()
113
- append_text_var = tk.StringVar()
114
- q = queue.Queue()
115
-
116
- def center_window(window, width_extra=0, height_extra=0):
117
- window.update_idletasks()
118
- width = 100 + width_extra
119
- height = 820 + height_extra
120
- x = (window.winfo_screenwidth() // 2) - (width // 2)
121
- y = (window.winfo_screenheight() // 2) - (height // 2)
122
- window.geometry(f'{width}x{height}+{x}+{y}')
123
-
124
- def select_files():
125
- global selected_files, save_directory, total_pages
126
- filetypes = [
127
- ("All Image files", "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.tif;*.svg;*.webp"),
128
- ("JPEG files", "*.jpg;*.jpeg"),
129
- ("PNG files", "*.png"),
130
- ("GIF files", "*.gif"),
131
- ("BMP files", "*.bmp"),
132
- ("TIFF files", "*.tiff;*.tif"),
133
- ("SVG files", "*.svg"),
134
- ("WEBP files", "*.webp")
135
- ]
136
- filepaths = filedialog.askopenfilenames(title="Select Image Files", filetypes=filetypes)
137
- if filepaths:
138
- selected_files.clear()
139
- selected_files.extend(filepaths)
140
- num_files_var.set(f"{len(selected_files)} files selected.")
141
- save_directory = os.path.dirname(selected_files[0]) # Lấy thư mục của ảnh đầu tiên
142
- total_pages = (len(selected_files) + images_per_page - 1) // images_per_page # Cập nhật tổng số trang
143
- if caption_window is not None:
144
- update_image_preview(content_canvas)
145
-
146
- def toggle_buttons(state):
147
- """Enable or disable buttons based on the state."""
148
- state = tk.NORMAL if state else tk.DISABLED
149
- select_files_button.config(state=state)
150
- show_captions_button.config(state=state)
151
- general_threshold_entry.config(state=state)
152
- character_threshold_entry.config(state=state)
153
- thread_count_entry.config(state=state)
154
- batch_size_entry.config(state=state)
155
- prepend_text_entry.config(state=state)
156
- append_text_entry.config(state=state)
157
- start_button.config(state=state)
158
- # Stop button should always be enabled
159
- stop_button.config(state=tk.NORMAL)
160
-
161
- def generate_caption(image_path, save_directory, q):
162
- """Generate captions for a single image using the wd-swinv2-tagger-v3 model."""
163
- if stop_processing:
164
- return
165
-
166
- try:
167
- filename = os.path.basename(image_path)
168
- output_path = os.path.join(save_directory, f"{filename}_tags.txt")
169
-
170
- command = [
171
- sys.executable, 'D:/test/wdv3-timm-main/wdv3_timm.py',
172
- '--model', "swinv2",
173
- '--image_path', image_path,
174
- '--model_dir', "D:/test/models/wd-swinv2-tagger-v3",
175
- '--general_threshold', str(general_threshold_var.get()),
176
- '--character_threshold', str(character_threshold_var.get())
177
- ]
178
-
179
- result = subprocess.run(command, capture_output=True, text=True, check=True)
180
- output = result.stdout
181
- print(output) # Debug: In ra toàn bộ đầu ra từ lệnh subprocess
182
- error_output = result.stderr
183
- print(output) # In ra đầu ra từ lệnh subprocess
184
- print(error_output) # In ra đầu ra lỗi từ lệnh subprocess
185
-
186
- # Filter out information to contain only Caption or General tags
187
- filtered_output = []
188
- recording = False
189
- for line in output.split('\n'):
190
- if "General tags" in line:
191
- recording = True
192
- continue
193
- if recording:
194
- if line.startswith(' '):
195
- tag = line.strip().split(':')[0].replace('_', ' ')
196
- filtered_output.append(tag)
197
- else:
198
- recording = False
199
- break
200
-
201
- # Convert list of tags to comma-separated string
202
- final_tags = ','.join(filtered_output) if filtered_output else "No tags found"
203
- print("Filtered output:", final_tags) # Debug: In ra các nhãn cuối cùng sau khi lọc
204
-
205
- # Add prepend and append text
206
- final_tags = f"{prepend_text_var.get()},{final_tags},{append_text_var.get()}".strip(',')
207
-
208
- # Save result to text file
209
- with open(output_path, 'w', encoding='utf-8') as file:
210
- file.write(final_tags)
211
-
212
- q.put(image_path)
213
- except Exception as e:
214
- error_message = f"Error processing image {image_path}: {str(e)}"
215
- print(error_message) # Debug: In ra thông báo lỗi
216
- q.put(error_message)
217
- error_messages.append(error_message)
218
-
219
- def worker(save_directory, num_threads, batch_size):
220
- try:
221
- progress.set(0)
222
- batch = []
223
- threads = []
224
- for i, input_path in enumerate(selected_files, 1):
225
- if stop_processing:
226
- break
227
-
228
- batch.append(input_path)
229
- if len(batch) == batch_size or i == len(selected_files):
230
- for image_path in batch:
231
- thread = threading.Thread(target=generate_caption, args=(image_path, save_directory, q))
232
- threads.append(thread)
233
- thread.start()
234
-
235
- for thread in threads:
236
- thread.join()
237
-
238
- batch.clear()
239
- threads.clear()
240
-
241
- q.put(None)
242
- except Exception as e:
243
- if not stop_processing:
244
- q.put(e)
245
-
246
- def update_progress():
247
- try:
248
- completed = 0
249
- while True:
250
- item = q.get()
251
- if item is None:
252
- break
253
- if isinstance(item, str):
254
- if "Error" in item:
255
- root.after(0, errors_var.set, f"Errors: {len(error_messages)}")
256
- continue
257
- completed += 1
258
- progress.set(int((completed / len(selected_files)) * 100))
259
- if not stop_processing:
260
- root.after(0, status_var.set, f"Processed {completed} files")
261
- root.after(0, root.update_idletasks)
262
- if caption_window is not None:
263
- root.after(0, update_image_preview, content_canvas)
264
- if not stop_processing:
265
- root.after(0, progress.set(100))
266
- show_completion_message(completed)
267
- except Exception as e:
268
- if not stop_processing:
269
- root.after(0, status_var.set, f"Error: {e}")
270
- finally:
271
- # Re-enable buttons after processing completes
272
- toggle_buttons(True)
273
-
274
- def show_completion_message(completed):
275
- message = f"Processing complete. {completed} files processed."
276
- if error_messages:
277
- message += f" {len(error_messages)} errors occurred."
278
- messagebox.showinfo("Process Complete", message)
279
-
280
- def process_files():
281
- global stop_processing, error_messages
282
- stop_processing = False
283
- error_messages.clear()
284
- errors_var.set("Errors: 0")
285
- if not selected_files or not save_directory:
286
- status_var.set("Please select images.")
287
- return
288
-
289
- # Disable buttons during processing
290
- toggle_buttons(False)
291
-
292
- threading.Thread(target=worker, args=(save_directory, thread_count_var.get(), batch_size_var.get())).start()
293
- threading.Thread(target=update_progress).start()
294
-
295
- def stop_processing_func():
296
- global stop_processing
297
- stop_processing = True
298
- status_var.set("Processing stopped.")
299
-
300
- def return_to_menu():
301
- stop_processing_func()
302
- root.destroy()
303
- import main
304
- main.open_main_menu()
305
-
306
- def on_closing():
307
- # Save all captions before closing
308
- save_all_captions()
309
- return_to_menu()
310
-
311
- def validate_threshold(P):
312
- """Validate that the threshold value is between 0 and 1."""
313
- if P == "" or (P.replace('.', '', 1).isdigit() and 0 <= float(P) <= 1):
314
- return True
315
- return False
316
-
317
- def validate_thread_count(P):
318
- """Validate that the thread count is a positive integer."""
319
- if P == "" or P.isdigit() and int(P) > 0:
320
- return True
321
- return False
322
-
323
- def validate_batch_size(P):
324
- """Validate that the batch size is a positive integer."""
325
- if P == "" or P.isdigit() and int(P) > 0:
326
- return True
327
- return False
328
-
329
- def open_caption_window():
330
- global caption_window, caption_frame, caption_text_widgets, tag_text_frame, current_page, total_pages, content_canvas
331
- if caption_window is not None:
332
- return # A caption window is already open, do not create a new one
333
-
334
- caption_window = tk.Toplevel(root)
335
- caption_window.title("Image Thumbnails and Captions")
336
- caption_window.geometry("1400x900") # Điều chỉnh kích thước cửa sổ
337
-
338
- # Create main frame to hold canvas and other elements
339
- main_frame = tk.Frame(caption_window)
340
- main_frame.pack(fill=tk.BOTH, expand=True)
341
-
342
- # Create canvas for content
343
- content_canvas = tk.Canvas(main_frame)
344
- content_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
345
-
346
- # Create frame for content
347
- caption_frame = tk.Frame(content_canvas)
348
- content_canvas.create_window((0, 0), window=caption_frame, anchor='nw')
349
-
350
- # Add scrollbar for caption_frame
351
- caption_scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=content_canvas.yview)
352
- caption_scrollbar.pack(side=tk.LEFT, fill=tk.Y)
353
- content_canvas.configure(yscrollcommand=caption_scrollbar.set)
354
-
355
- caption_frame.bind("<Configure>", lambda e: content_canvas.configure(scrollregion=content_canvas.bbox("all")))
356
-
357
- # Ensure caption_window is reset to None when the window is closed
358
- def on_caption_window_close():
359
- global caption_window, tag_text_frame
360
- caption_window.destroy()
361
- caption_window = None
362
- tag_text_frame = None # Reset tag_text_frame when window is closed
363
-
364
- caption_window.protocol("WM_DELETE_WINDOW", on_caption_window_close)
365
-
366
- # Display content and set dividers
367
- update_image_preview(content_canvas)
368
-
369
- # Create frame for tags (on the right side)
370
- tag_frame = tk.Frame(main_frame, padx=0)
371
- tag_frame.pack(side=tk.RIGHT, fill=tk.Y)
372
-
373
- # Create sorting buttons
374
- sort_label = tk.Label(tag_frame, text="Sort by:", font=('Helvetica', 12))
375
- sort_label.pack(anchor='nw', pady=5)
376
-
377
- sort_alpha_button = tk.Button(tag_frame, text="Alphabetical", command=sort_tags_alpha)
378
- sort_alpha_button.pack(anchor='nw', pady=2)
379
-
380
- sort_count_button = tk.Button(tag_frame, text="Count", command=sort_tags_count)
381
- sort_count_button.pack(anchor='nw', pady=2)
382
-
383
- # Create display area for tags
384
- tag_canvas = tk.Canvas(tag_frame)
385
- tag_text_frame = tk.Frame(tag_canvas)
386
- tag_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
387
- tag_canvas.create_window((0, 0), window=tag_text_frame, anchor='nw')
388
-
389
- # Add scrollbar for tag_frame
390
- tag_scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=tag_canvas.yview)
391
- tag_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
392
- tag_canvas.configure(yscrollcommand=tag_scrollbar.set)
393
-
394
- tag_text_frame.bind("<Configure>", lambda e: tag_canvas.configure(scrollregion=tag_canvas.bbox("all")))
395
-
396
- # Update initial tag display
397
- update_tag_display()
398
-
399
- def update_image_preview(content_canvas):
400
- global thumbnails, caption_text_widgets, current_page, images_per_page, total_pages
401
- if caption_frame is None:
402
- return
403
-
404
- for widget in caption_frame.winfo_children():
405
- if isinstance(widget, tk.Label) or isinstance(widget, tk.Text) or isinstance(widget, tk.Frame):
406
- widget.destroy()
407
-
408
- thumbnails.clear() # Clear old thumbnails
409
- caption_text_widgets.clear() # Clear old caption_entries
410
-
411
- if not selected_files:
412
- return
413
-
414
- start_index = current_page * images_per_page
415
- end_index = start_index + images_per_page
416
- files_to_display = selected_files[start_index:end_index]
417
-
418
- for i, file_path in enumerate(files_to_display):
419
- thumbnail_size = (200, 200)
420
- try:
421
- image = PILImage.open(file_path)
422
- image.thumbnail(thumbnail_size)
423
- thumbnail = ImageTk.PhotoImage(image)
424
- thumbnails.append(thumbnail) # Keep reference to thumbnail object
425
-
426
- # Display thumbnail
427
- img_label = tk.Label(caption_frame, image=thumbnail)
428
- img_label.grid(row=i*2, column=0, padx=5, pady=5, sticky="nsew")
429
-
430
- # Display file name
431
- file_label = tk.Label(caption_frame, text=os.path.basename(file_path), font=('Helvetica', 12))
432
- file_label.grid(row=i*2, column=1, padx=5, pady=5, sticky="nsew")
433
-
434
- # Check and display caption if available
435
- caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
436
- if os.path.exists(caption_file):
437
- with open(caption_file, 'r', encoding='utf-8') as file:
438
- caption_text = file.read()
439
- else:
440
- caption_text = "" # Set empty string if no caption available
441
-
442
- caption_text_widget = tk.Text(caption_frame, width=50, height=3, wrap=tk.WORD, font=('Helvetica', 12))
443
- caption_text_widget.insert(tk.END, caption_text)
444
- caption_text_widget.grid(row=i*2, column=2, padx=5, pady=5, sticky="nsew")
445
- caption_text_widget.bind("<FocusOut>", lambda e, fp=file_path: save_caption(fp, caption_text_widget.get("1.0", "end-1c")))
446
- caption_text_widgets.append(caption_text_widget)
447
-
448
- # Update tags in tag_dict
449
- update_tag_dict(caption_text)
450
-
451
- except Exception as e:
452
- tk.Label(caption_frame, text="Error loading image").grid(row=i*2, column=0, columnspan=4, padx=5, pady=5)
453
-
454
- # Add navigation buttons
455
- nav_frame = tk.Frame(caption_frame)
456
- nav_frame.grid(row=images_per_page*2, column=0, columnspan=3, pady=10)
457
-
458
- if current_page > 0:
459
- prev_button = tk.Button(nav_frame, text="Previous", command=lambda: navigate(-1, content_canvas))
460
- prev_button.pack(side=tk.LEFT)
461
-
462
- page_label = tk.Label(nav_frame, text=f"Page {current_page + 1} of {total_pages}")
463
- page_label.pack(side=tk.LEFT, padx=5)
464
-
465
- page_entry = tk.Entry(nav_frame, width=5)
466
- page_entry.pack(side=tk.LEFT)
467
-
468
- go_button = tk.Button(nav_frame, text="Go", command=lambda: go_to_page(page_entry.get(), content_canvas))
469
- go_button.pack(side=tk.LEFT, padx=5)
470
-
471
- if current_page < total_pages - 1:
472
- next_button = tk.Button(nav_frame, text="Next", command=lambda: navigate(1, content_canvas))
473
- next_button.pack(side=tk.RIGHT)
474
-
475
- # Update tag display after loading images
476
- update_tag_display()
477
-
478
- def navigate(direction, content_canvas):
479
- global current_page
480
- current_page += direction
481
- update_image_preview(content_canvas)
482
-
483
- def go_to_page(page_number, content_canvas):
484
- global current_page, total_pages
485
- try:
486
- page_number = int(page_number)
487
- if 1 <= page_number <= total_pages:
488
- current_page = page_number - 1
489
- update_image_preview(content_canvas)
490
- else:
491
- messagebox.showerror("Invalid Page", f"Please enter a valid page number between 1 and {total_pages}.")
492
- except ValueError:
493
- messagebox.showerror("Invalid Input", "Please enter a valid integer for the page number.")
494
-
495
- def save_caption(file_path, caption_text):
496
- """Save caption when user changes it."""
497
- output_path = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
498
- with open(output_path, 'w', encoding='utf-8') as file:
499
- file.write(caption_text)
500
-
501
- # Update tags after editing caption
502
- update_tag_dict(caption_text)
503
- update_tag_display()
504
-
505
- def save_all_captions():
506
- """Save all captions when closing the window."""
507
- for i, caption_text_widget in enumerate(caption_text_widgets):
508
- if caption_text_widget.winfo_exists():
509
- file_path = selected_files[current_page * images_per_page + i]
510
- caption_text = caption_text_widget.get("1.0", "end-1c")
511
- save_caption(file_path, caption_text)
512
-
513
- def update_tag_dict(caption_text=None):
514
- """Update tag dictionary based on caption_text."""
515
- global tag_dict
516
- new_tag_dict = {}
517
- for caption_widget in caption_text_widgets:
518
- if caption_widget.winfo_exists():
519
- caption_text = caption_widget.get("1.0", "end-1c")
520
- tags = caption_text.split(',')
521
- for tag in tags:
522
- tag = tag.strip()
523
- if tag:
524
- if tag in new_tag_dict:
525
- new_tag_dict[tag] += 1
526
- else:
527
- new_tag_dict[tag] = 1
528
- tag_dict = new_tag_dict
529
-
530
- def update_tag_display():
531
- """Update tag display on Text widget."""
532
- global tag_dict, selected_tag, edit_buttons, tag_text_frame
533
- if tag_text_frame is None:
534
- return # If the tag_text_frame is not created yet, return
535
- for widget in tag_text_frame.winfo_children():
536
- widget.destroy()
537
-
538
- row = 0
539
- for tag, count in tag_dict.items():
540
- tag_label = tk.Label(tag_text_frame, text=tag, bg="white", padx=5, pady=2, anchor="w", relief="solid", borderwidth=1)
541
- tag_label.grid(row=row, column=0, sticky="w", padx=2, pady=1)
542
- count_label = tk.Label(tag_text_frame, text=f"({count})", anchor='w', width=5)
543
- count_label.grid(row=row, column=1, padx=5, pady=2, sticky='w')
544
-
545
- # Add tag manipulation buttons
546
- add_tag_button = tk.Button(tag_text_frame, text="Add", command=lambda r=row: add_tag(r))
547
- add_tag_button.grid(row=row, column=2, padx=2)
548
- edit_button = tk.Button(tag_text_frame, text="Edit", command=lambda t=tag_label: edit_tag(t))
549
- edit_button.grid(row=row, column=3, padx=2)
550
- delete_button = tk.Button(tag_text_frame, text="Delete", command=lambda t=tag_label: delete_tag(t))
551
- delete_button.grid(row=row, column=4, padx=2)
552
- delete_all_button = tk.Button(tag_text_frame, text="Delete All", command=lambda t=tag_label: delete_all_files_with_tag(t))
553
- delete_all_button.grid(row=row, column=5, padx=2)
554
-
555
- edit_buttons[tag_label] = [add_tag_button, edit_button, delete_button, delete_all_button]
556
-
557
- row += 1
558
-
559
- # Điều chỉnh trọng số cột để không mở rộng quá mức
560
- tag_text_frame.grid_columnconfigure(0, weight=1) # Cột cho tag
561
- tag_text_frame.grid_columnconfigure(1, weight=0) # Cột cho số lượng
562
- tag_text_frame.grid_columnconfigure(2, weight=0) # Cột cho nút Add
563
- tag_text_frame.grid_columnconfigure(3, weight=0) # Cột cho nút Edit
564
- tag_text_frame.grid_columnconfigure(4, weight=0) # Cột cho nút Delete
565
- tag_text_frame.grid_columnconfigure(5, weight=0) # Cột cho nút Delete All
566
-
567
- tag_text_frame.update_idletasks() # Làm mới giao diện sau khi cập nhật danh sách tag
568
- tag_text_frame.update() # Làm mới giao diện ngay lập tức
569
-
570
- def add_tag(row):
571
- """Add a new tag above the selected row."""
572
- def save_new_tag(event=None):
573
- new_tag = entry.get().strip()
574
- if new_tag:
575
- # Add the new tag to the tag_dict
576
- if new_tag in tag_dict:
577
- tag_dict[new_tag] += 1
578
- else:
579
- tag_dict[new_tag] = 1
580
-
581
- # Update the captions in the respective files
582
- for file_path in selected_files:
583
- caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
584
- if os.path.exists(caption_file):
585
- with open(caption_file, 'r', encoding='utf-8') as file:
586
- caption_text = file.read()
587
- new_caption_text = caption_text + ',' + new_tag if caption_text else new_tag
588
- with open(caption_file, 'w', encoding='utf-8') as file:
589
- file.write(new_caption_text)
590
-
591
- update_image_preview(content_canvas) # Update image preview
592
- update_tag_display() # Update the display of tags
593
-
594
- # Create entry above the row
595
- for widget in tag_text_frame.grid_slaves():
596
- if int(widget.grid_info()["row"]) >= row:
597
- widget.grid_configure(row=int(widget.grid_info()["row"]) + 1)
598
-
599
- entry = tk.Entry(tag_text_frame)
600
- entry.grid(row=row, column=0, sticky="w", padx=2, pady=1)
601
- entry.bind("<Return>", save_new_tag)
602
- entry.bind("<FocusOut>", save_new_tag)
603
- entry.bind("<Escape>", lambda e: entry.destroy())
604
- entry.focus_set()
605
-
606
- def edit_tag(tag_label):
607
- """Edit the selected tag."""
608
- def save_edit(event=None):
609
- new_tag = entry.get().strip()
610
- old_tag = tag_label.cget("text").strip()
611
- if new_tag and new_tag != old_tag:
612
- # Update the tag_dict with new tag
613
- tag_dict[new_tag] = tag_dict.pop(old_tag)
614
-
615
- # Update the captions in the respective files
616
- for i, file_path in enumerate(selected_files):
617
- caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
618
- if os.path.exists(caption_file):
619
- with open(caption_file, 'r', encoding='utf-8') as file:
620
- caption_text = file.read()
621
- new_caption_text = caption_text.replace(old_tag, new_tag)
622
- with open(caption_file, 'w', encoding='utf-8') as file:
623
- file.write(new_caption_text)
624
-
625
- update_image_preview(content_canvas) # Update image preview
626
- update_tag_display() # Update the display of tags
627
-
628
- old_tag = tag_label.cget("text").strip()
629
- entry = tk.Entry(tag_text_frame)
630
- entry.insert(0, old_tag)
631
- entry.grid(row=tag_label.grid_info()["row"], column=0, sticky="w", padx=2, pady=1)
632
- entry.bind("<Return>", save_edit)
633
- entry.bind("<FocusOut>", save_edit)
634
- entry.bind("<Escape>", lambda e: entry.destroy())
635
- entry.focus_set()
636
-
637
- def delete_tag(tag_label):
638
- """Delete the selected tag."""
639
- global selected_tag
640
- if tag_label:
641
- tag_to_delete = tag_label.cget("text").strip()
642
- if tag_to_delete in tag_dict:
643
- del tag_dict[tag_to_delete]
644
-
645
- # Update the captions in the respective files
646
- for i, file_path in enumerate(selected_files):
647
- caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
648
- if os.path.exists(caption_file):
649
- with open(caption_file, 'r', encoding='utf-8') as file:
650
- caption_text = file.read()
651
- new_caption_text = caption_text.replace(tag_to_delete, "")
652
- with open(caption_file, 'w', encoding='utf-8') as file:
653
- file.write(new_caption_text)
654
-
655
- update_image_preview(content_canvas) # Update image preview
656
- update_tag_display() # Update the display of tags
657
-
658
- def delete_all_files_with_tag(tag_label):
659
- """Delete all images and their corresponding text files containing the tag."""
660
- tag_to_delete = tag_label.cget("text").strip()
661
- if tag_to_delete in tag_dict:
662
- del tag_dict[tag_to_delete]
663
-
664
- # Delete the files containing the tag
665
- files_to_delete = []
666
- for i, file_path in enumerate(selected_files):
667
- caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
668
- if os.path.exists(caption_file):
669
- with open(caption_file, 'r', encoding='utf-8') as file:
670
- caption_text = file.read()
671
- if tag_to_delete in caption_text:
672
- files_to_delete.append((file_path, caption_file))
673
- for image_file, caption_file in files_to_delete:
674
- # Remove image file
675
- try:
676
- os.remove(image_file)
677
- except Exception as e:
678
- error_messages.append(f"Error deleting image {image_file}: {str(e)}")
679
- # Remove caption file
680
- try:
681
- os.remove(caption_file)
682
- except Exception as e:
683
- error_messages.append(f"Error deleting caption {caption_file}: {str(e)}")
684
-
685
- # Remove deleted files from selected_files list
686
- selected_files[:] = [file for file in selected_files if file not in [img for img, cap in files_to_delete]]
687
-
688
- # Clear tag_dict and update tag display
689
- update_tag_dict() # Update tag dictionary
690
- update_tag_display() # Update the display of tags
691
- update_image_preview(content_canvas) # Update image preview
692
-
693
- def sort_tags_alpha():
694
- """Sort tags alphabetically."""
695
- global tag_dict
696
- sorted_tags = sorted(tag_dict.items())
697
- tag_dict = dict(sorted_tags)
698
- update_tag_display()
699
-
700
- def sort_tags_count():
701
- """Sort tags by count."""
702
- global tag_dict
703
- sorted_tags = sorted(tag_dict.items(), key=lambda item: item[1], reverse=True)
704
- tag_dict = dict(sorted_tags)
705
- update_tag_display()
706
-
707
- # Create GUI components
708
- back_button = tk.Button(root, text="<-", font=('Helvetica', 14), command=return_to_menu)
709
- back_button.pack(anchor='nw', padx=10, pady=10)
710
-
711
- title_label = tk.Label(root, text="Image Tag Generator", font=('Helvetica', 16))
712
- title_label.pack(pady=10)
713
-
714
- select_files_button = tk.Button(root, text="Select Files", command=select_files)
715
- select_files_button.pack(pady=10)
716
-
717
- num_files_label = tk.Label(root, textvariable=num_files_var)
718
- num_files_label.pack(pady=5)
719
-
720
- show_captions_button = tk.Button(root, text="Show Captions", command=open_caption_window)
721
- show_captions_button.pack(pady=10)
722
-
723
- general_threshold_label = tk.Label(root, text="General Threshold:")
724
- general_threshold_label.pack(pady=5)
725
- general_threshold_entry = tk.Entry(root, textvariable=general_threshold_var, justify='center', width=5, validate='key')
726
- general_threshold_entry.pack(pady=5)
727
-
728
- character_threshold_label = tk.Label(root, text="Character Threshold:")
729
- character_threshold_label.pack(pady=5)
730
- character_threshold_entry = tk.Entry(root, textvariable=character_threshold_var, justify='center', width=5, validate='key')
731
- character_threshold_entry.pack(pady=5)
732
-
733
- prepend_text_label = tk.Label(root, text="Prepend Text:")
734
- prepend_text_label.pack(pady=5)
735
- prepend_text_entry = tk.Entry(root, textvariable=prepend_text_var, justify='center', width=20)
736
- prepend_text_entry.pack(pady=5)
737
-
738
- append_text_label = tk.Label(root, text="Append Text:")
739
- append_text_label.pack(pady=5)
740
- append_text_entry = tk.Entry(root, textvariable=append_text_var, justify='center', width=20)
741
- append_text_entry.pack(pady=5)
742
-
743
- thread_count_label = tk.Label(root, text="Thread Count:")
744
- thread_count_label.pack(pady=5)
745
- thread_count_entry = tk.Entry(root, textvariable=thread_count_var, justify='center', width=5, validate='key')
746
- thread_count_entry.pack(pady=5)
747
-
748
- batch_size_label = tk.Label(root, text="Batch Size:")
749
- batch_size_label.pack(pady=5)
750
- batch_size_entry = tk.Entry(root, textvariable=batch_size_var, justify='center', width=5, validate='key')
751
- batch_size_entry.pack(pady=5)
752
-
753
- errors_button = tk.Button(root, textvariable=errors_var, command=lambda: show_errors(root))
754
- errors_button.pack(pady=10)
755
-
756
- start_button = tk.Button(root, text="Generate Captions", command=lambda: [process_files(), update_and_save_config()])
757
- start_button.pack(pady=10)
758
-
759
- stop_button = tk.Button(root, text="Stop", command=stop_processing_func)
760
- stop_button.pack(pady=10)
761
-
762
- progress_bar = ttk.Progressbar(root, variable=progress, maximum=100)
763
- progress_bar.pack(pady=10, fill=tk.X)
764
-
765
- status_label = tk.Label(root, textvariable=status_var, fg="green")
766
- status_label.pack(pady=5)
767
-
768
- # Add input validation
769
- general_threshold_entry.config(validatecommand=(root.register(validate_threshold), '%P'))
770
- character_threshold_entry.config(validatecommand=(root.register(validate_threshold), '%P'))
771
- thread_count_entry.config(validatecommand=(root.register(validate_thread_count), '%P'))
772
- batch_size_entry.config(validatecommand=(root.register(validate_batch_size), '%P'))
773
-
774
- # Update config when the thresholds or thread count change
775
- general_threshold_var.trace_add('write', lambda *args: update_and_save_config())
776
- character_threshold_var.trace_add('write', lambda *args: update_and_save_config())
777
- thread_count_var.trace_add('write', lambda *args: update_and_save_config())
778
-
779
- center_window(root, width_extra=200)
780
- root.protocol("WM_DELETE_WINDOW", on_closing)
781
- root.mainloop()
782
-
783
- if __name__ == "__main__":
784
- open_image_to_tag()
 
1
+ import tkinter as tk
2
+ from tkinter import filedialog, messagebox, ttk
3
+ from PIL import Image as PILImage, ImageTk
4
+ import os
5
+ import queue
6
+ import threading
7
+ import subprocess
8
+ import sys
9
+ import json
10
+
11
+ # Global variables to control the process and track errors
12
+ stop_processing = False
13
+ error_messages = []
14
+ selected_files = []
15
+ save_directory = "" # Sử dụng thư mục của ảnh đầu tiên được chọn
16
+ caption_window = None
17
+ caption_frame = None
18
+ thumbnails = [] # To hold references to thumbnail objects
19
+ caption_text_widgets = [] # To hold Text widgets containing captions
20
+ tag_dict = {} # Store tags and their counts
21
+ selected_tag = None # Store the selected tag widget
22
+ edit_buttons = {} # To hold the edit and delete buttons for each tag
23
+ error_window = None # To ensure only one error window is open at a time
24
+ tag_text_frame = None # Define globally to manage tag frame
25
+
26
+ # Global Tkinter variable placeholders
27
+ status_var = None
28
+ num_files_var = None
29
+ errors_var = None
30
+ progress = None
31
+ character_threshold_var = None
32
+ general_threshold_var = None
33
+ thread_count_var = None
34
+ batch_size_var = None
35
+ start_button = None
36
+ stop_button = None
37
+ prepend_text_var = None
38
+ append_text_var = None
39
+ current_page = 0
40
+ images_per_page = 20
41
+ total_pages = 1 # Initialize total pages
42
+
43
+ def update_and_save_config():
44
+ """Update and save the configuration to JSON."""
45
+ save_config_to_json(
46
+ model="swinv2", # Default model
47
+ general_threshold=general_threshold_var.get(),
48
+ character_threshold=character_threshold_var.get(),
49
+ model_dir="D:/test/models/wd-swinv2-tagger-v3" # Default model directory
50
+ )
51
+
52
+ def show_errors(root):
53
+ """Display error details in a new window."""
54
+ global error_window
55
+ if error_window is not None:
56
+ return # Only one error window can be open at a time
57
+
58
+ error_window = tk.Toplevel(root)
59
+ error_window.title("Error Details")
60
+ error_window.geometry("500x400")
61
+
62
+ error_text = tk.Text(error_window, wrap='word')
63
+ error_text.pack(expand=True, fill='both')
64
+
65
+ if error_messages:
66
+ for error in error_messages:
67
+ error_text.insert('end', error + '\n')
68
+ else:
69
+ error_text.insert('end', "No errors recorded.")
70
+
71
+ error_text.config(state='disabled')
72
+
73
+ def on_close_error_window():
74
+ global error_window
75
+ error_window.destroy()
76
+ error_window = None
77
+
78
+ error_window.protocol("WM_DELETE_WINDOW", on_close_error_window)
79
+
80
+ def save_config_to_json(model, general_threshold, character_threshold, model_dir, filepath='config.json'):
81
+ """Save the model and threshold values to a JSON file."""
82
+ config = {
83
+ 'model': model,
84
+ 'general_threshold': general_threshold,
85
+ 'character_threshold': character_threshold,
86
+ 'model_dir': model_dir
87
+ }
88
+ try:
89
+ with open(filepath, 'w') as f:
90
+ json.dump(config, f, indent=2)
91
+ except Exception as e:
92
+ print(f"Error saving config to JSON: {e}")
93
+
94
+ def open_image_to_tag():
95
+ global stop_processing, error_messages, selected_files, save_directory, caption_window, caption_frame, thumbnails, caption_text_widgets, tag_dict, selected_tag, edit_buttons, tag_text_frame, current_page, total_pages, content_canvas
96
+ global status_var, num_files_var, errors_var, progress, character_threshold_var, general_threshold_var, thread_count_var, batch_size_var
97
+ global start_button, stop_button, prepend_text_var, append_text_var
98
+
99
+ # Create Tkinter window
100
+ root = tk.Tk()
101
+ root.title("Image Caption Generator")
102
+
103
+ # Initialize Tkinter variables
104
+ status_var = tk.StringVar()
105
+ num_files_var = tk.StringVar()
106
+ errors_var = tk.StringVar(value="Errors: 0")
107
+ progress = tk.IntVar()
108
+ character_threshold_var = tk.DoubleVar(value=0.35)
109
+ general_threshold_var = tk.DoubleVar(value=0.35)
110
+ thread_count_var = tk.IntVar(value=4)
111
+ batch_size_var = tk.IntVar(value=4)
112
+ prepend_text_var = tk.StringVar()
113
+ append_text_var = tk.StringVar()
114
+ q = queue.Queue()
115
+
116
+ def center_window(window, width_extra=0, height_extra=0):
117
+ window.update_idletasks()
118
+ width = 100 + width_extra
119
+ height = 820 + height_extra
120
+ x = (window.winfo_screenwidth() // 2) - (width // 2)
121
+ y = (window.winfo_screenheight() // 2) - (height // 2)
122
+ window.geometry(f'{width}x{height}+{x}+{y}')
123
+
124
+ def select_files():
125
+ global selected_files, save_directory, total_pages
126
+ filetypes = [
127
+ ("All Image files", "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.tif;*.svg;*.webp"),
128
+ ("JPEG files", "*.jpg;*.jpeg"),
129
+ ("PNG files", "*.png"),
130
+ ("GIF files", "*.gif"),
131
+ ("BMP files", "*.bmp"),
132
+ ("TIFF files", "*.tiff;*.tif"),
133
+ ("SVG files", "*.svg"),
134
+ ("WEBP files", "*.webp")
135
+ ]
136
+ filepaths = filedialog.askopenfilenames(title="Select Image Files", filetypes=filetypes)
137
+ if filepaths:
138
+ selected_files.clear()
139
+ selected_files.extend(filepaths)
140
+ num_files_var.set(f"{len(selected_files)} files selected.")
141
+ save_directory = os.path.dirname(selected_files[0]) # Lấy thư mục của ảnh đầu tiên
142
+ total_pages = (len(selected_files) + images_per_page - 1) // images_per_page # Cập nhật tổng số trang
143
+ if caption_window is not None:
144
+ update_image_preview(content_canvas)
145
+
146
+ def toggle_buttons(state):
147
+ """Enable or disable buttons based on the state."""
148
+ state = tk.NORMAL if state else tk.DISABLED
149
+ select_files_button.config(state=state)
150
+ show_captions_button.config(state=state)
151
+ general_threshold_entry.config(state=state)
152
+ character_threshold_entry.config(state=state)
153
+ thread_count_entry.config(state=state)
154
+ batch_size_entry.config(state=state)
155
+ prepend_text_entry.config(state=state)
156
+ append_text_entry.config(state=state)
157
+ start_button.config(state=state)
158
+ # Stop button should always be enabled
159
+ stop_button.config(state=tk.NORMAL)
160
+
161
+ def generate_caption(image_path, save_directory, q):
162
+ """Generate captions for a single image using the wd-swinv2-tagger-v3 model."""
163
+ if stop_processing:
164
+ return
165
+
166
+ try:
167
+ filename = os.path.splitext(os.path.basename(image_path))[0]
168
+ output_path = os.path.join(save_directory, f"{filename}.txt")
169
+
170
+ command = [
171
+ sys.executable, 'D:/test/wdv3-timm-main/wdv3_timm.py',
172
+ '--model', "swinv2",
173
+ '--image_path', image_path,
174
+ '--model_dir', "D:/test/models/wd-swinv2-tagger-v3",
175
+ '--general_threshold', str(general_threshold_var.get()),
176
+ '--character_threshold', str(character_threshold_var.get())
177
+ ]
178
+
179
+ result = subprocess.run(command, capture_output=True, text=True, check=True)
180
+ output = result.stdout
181
+ print(output) # Debug: In ra toàn bộ đầu ra từ lệnh subprocess
182
+ error_output = result.stderr
183
+ print(output) # In ra đầu ra từ lệnh subprocess
184
+ print(error_output) # In ra đầu ra lỗi từ lệnh subprocess
185
+
186
+ # Filter out information to contain only Caption or General tags
187
+ filtered_output = []
188
+ recording = False
189
+ for line in output.split('\n'):
190
+ if "General tags" in line:
191
+ recording = True
192
+ continue
193
+ if recording:
194
+ if line.startswith(' '):
195
+ tag = line.strip().split(':')[0].replace('_', ' ')
196
+ filtered_output.append(tag)
197
+ else:
198
+ recording = False
199
+ break
200
+
201
+ # Convert list of tags to comma-separated string
202
+ final_tags = ','.join(filtered_output) if filtered_output else "No tags found"
203
+ print("Filtered output:", final_tags) # Debug: In ra các nhãn cuối cùng sau khi lọc
204
+
205
+ # Add prepend and append text
206
+ final_tags = f"{prepend_text_var.get()},{final_tags},{append_text_var.get()}".strip(',')
207
+
208
+ # Save result to text file
209
+ with open(output_path, 'w', encoding='utf-8') as file:
210
+ file.write(final_tags)
211
+
212
+ q.put(image_path)
213
+ except Exception as e:
214
+ error_message = f"Error processing image {image_path}: {str(e)}"
215
+ print(error_message) # Debug: In ra thông báo lỗi
216
+ q.put(error_message)
217
+ error_messages.append(error_message)
218
+
219
+ def worker(save_directory, num_threads, batch_size):
220
+ try:
221
+ progress.set(0)
222
+ batch = []
223
+ threads = []
224
+ for i, input_path in enumerate(selected_files, 1):
225
+ if stop_processing:
226
+ break
227
+
228
+ batch.append(input_path)
229
+ if len(batch) == batch_size or i == len(selected_files):
230
+ for image_path in batch:
231
+ thread = threading.Thread(target=generate_caption, args=(image_path, save_directory, q))
232
+ threads.append(thread)
233
+ thread.start()
234
+
235
+ for thread in threads:
236
+ thread.join()
237
+
238
+ batch.clear()
239
+ threads.clear()
240
+
241
+ q.put(None)
242
+ except Exception as e:
243
+ if not stop_processing:
244
+ q.put(e)
245
+
246
+ def update_progress():
247
+ try:
248
+ completed = 0
249
+ while True:
250
+ item = q.get()
251
+ if item is None:
252
+ break
253
+ if isinstance(item, str):
254
+ if "Error" in item:
255
+ root.after(0, errors_var.set, f"Errors: {len(error_messages)}")
256
+ continue
257
+ completed += 1
258
+ progress.set(int((completed / len(selected_files)) * 100))
259
+ if not stop_processing:
260
+ root.after(0, status_var.set, f"Processed {completed} files")
261
+ root.after(0, root.update_idletasks)
262
+ if caption_window is not None:
263
+ root.after(0, update_image_preview, content_canvas)
264
+ if not stop_processing:
265
+ root.after(0, progress.set(100))
266
+ show_completion_message(completed)
267
+ except Exception as e:
268
+ if not stop_processing:
269
+ root.after(0, status_var.set, f"Error: {e}")
270
+ finally:
271
+ # Re-enable buttons after processing completes
272
+ toggle_buttons(True)
273
+
274
+ def show_completion_message(completed):
275
+ message = f"Processing complete. {completed} files processed."
276
+ if error_messages:
277
+ message += f" {len(error_messages)} errors occurred."
278
+ messagebox.showinfo("Process Complete", message)
279
+
280
+ def process_files():
281
+ global stop_processing, error_messages
282
+ stop_processing = False
283
+ error_messages.clear()
284
+ errors_var.set("Errors: 0")
285
+ if not selected_files or not save_directory:
286
+ status_var.set("Please select images.")
287
+ return
288
+
289
+ # Disable buttons during processing
290
+ toggle_buttons(False)
291
+
292
+ threading.Thread(target=worker, args=(save_directory, thread_count_var.get(), batch_size_var.get())).start()
293
+ threading.Thread(target=update_progress).start()
294
+
295
+ def stop_processing_func():
296
+ global stop_processing
297
+ stop_processing = True
298
+ status_var.set("Processing stopped.")
299
+
300
+ def return_to_menu():
301
+ stop_processing_func()
302
+ root.destroy()
303
+ import main
304
+ main.open_main_menu()
305
+
306
+ def on_closing():
307
+ # Save all captions before closing
308
+ save_all_captions()
309
+ return_to_menu()
310
+
311
+ def validate_threshold(P):
312
+ """Validate that the threshold value is between 0 and 1."""
313
+ if P == "" or (P.replace('.', '', 1).isdigit() and 0 <= float(P) <= 1):
314
+ return True
315
+ return False
316
+
317
+ def validate_thread_count(P):
318
+ """Validate that the thread count is a positive integer."""
319
+ if P == "" or P.isdigit() and int(P) > 0:
320
+ return True
321
+ return False
322
+
323
+ def validate_batch_size(P):
324
+ """Validate that the batch size is a positive integer."""
325
+ if P == "" or P.isdigit() and int(P) > 0:
326
+ return True
327
+ return False
328
+
329
+ def open_caption_window():
330
+ global caption_window, caption_frame, caption_text_widgets, tag_text_frame, current_page, total_pages, content_canvas
331
+ if caption_window is not None:
332
+ return # A caption window is already open, do not create a new one
333
+
334
+ caption_window = tk.Toplevel(root)
335
+ caption_window.title("Image Thumbnails and Captions")
336
+ caption_window.geometry("1400x900") # Điều chỉnh kích thước cửa sổ
337
+
338
+ # Create main frame to hold canvas and other elements
339
+ main_frame = tk.Frame(caption_window)
340
+ main_frame.pack(fill=tk.BOTH, expand=True)
341
+
342
+ # Create canvas for content
343
+ content_canvas = tk.Canvas(main_frame)
344
+ content_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
345
+
346
+ # Create frame for content
347
+ caption_frame = tk.Frame(content_canvas)
348
+ content_canvas.create_window((0, 0), window=caption_frame, anchor='nw')
349
+
350
+ # Add scrollbar for caption_frame
351
+ caption_scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=content_canvas.yview)
352
+ caption_scrollbar.pack(side=tk.LEFT, fill=tk.Y)
353
+ content_canvas.configure(yscrollcommand=caption_scrollbar.set)
354
+
355
+ caption_frame.bind("<Configure>", lambda e: content_canvas.configure(scrollregion=content_canvas.bbox("all")))
356
+
357
+ # Ensure caption_window is reset to None when the window is closed
358
+ def on_caption_window_close():
359
+ global caption_window, tag_text_frame
360
+ caption_window.destroy()
361
+ caption_window = None
362
+ tag_text_frame = None # Reset tag_text_frame when window is closed
363
+
364
+ caption_window.protocol("WM_DELETE_WINDOW", on_caption_window_close)
365
+
366
+ # Display content and set dividers
367
+ update_image_preview(content_canvas)
368
+
369
+ # Create frame for tags (on the right side)
370
+ tag_frame = tk.Frame(main_frame, padx=0)
371
+ tag_frame.pack(side=tk.RIGHT, fill=tk.Y)
372
+
373
+ # Create sorting buttons
374
+ sort_label = tk.Label(tag_frame, text="Sort by:", font=('Helvetica', 12))
375
+ sort_label.pack(anchor='nw', pady=5)
376
+
377
+ sort_alpha_button = tk.Button(tag_frame, text="Alphabetical", command=sort_tags_alpha)
378
+ sort_alpha_button.pack(anchor='nw', pady=2)
379
+
380
+ sort_count_button = tk.Button(tag_frame, text="Count", command=sort_tags_count)
381
+ sort_count_button.pack(anchor='nw', pady=2)
382
+
383
+ # Create display area for tags
384
+ tag_canvas = tk.Canvas(tag_frame)
385
+ tag_text_frame = tk.Frame(tag_canvas)
386
+ tag_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
387
+ tag_canvas.create_window((0, 0), window=tag_text_frame, anchor='nw')
388
+
389
+ # Add scrollbar for tag_frame
390
+ tag_scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=tag_canvas.yview)
391
+ tag_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
392
+ tag_canvas.configure(yscrollcommand=tag_scrollbar.set)
393
+
394
+ tag_text_frame.bind("<Configure>", lambda e: tag_canvas.configure(scrollregion=tag_canvas.bbox("all")))
395
+
396
+ # Update initial tag display
397
+ update_tag_display()
398
+
399
+ def update_image_preview(content_canvas):
400
+ global thumbnails, caption_text_widgets, current_page, images_per_page, total_pages
401
+ if caption_frame is None:
402
+ return
403
+
404
+ for widget in caption_frame.winfo_children():
405
+ if isinstance(widget, tk.Label) or isinstance(widget, tk.Text) or isinstance(widget, tk.Frame):
406
+ widget.destroy()
407
+
408
+ thumbnails.clear() # Clear old thumbnails
409
+ caption_text_widgets.clear() # Clear old caption_entries
410
+
411
+ if not selected_files:
412
+ return
413
+
414
+ start_index = current_page * images_per_page
415
+ end_index = start_index + images_per_page
416
+ files_to_display = selected_files[start_index:end_index]
417
+
418
+ for i, file_path in enumerate(files_to_display):
419
+ thumbnail_size = (200, 200)
420
+ try:
421
+ image = PILImage.open(file_path)
422
+ image.thumbnail(thumbnail_size)
423
+ thumbnail = ImageTk.PhotoImage(image)
424
+ thumbnails.append(thumbnail) # Keep reference to thumbnail object
425
+
426
+ # Display thumbnail
427
+ img_label = tk.Label(caption_frame, image=thumbnail)
428
+ img_label.grid(row=i*2, column=0, padx=5, pady=5, sticky="nsew")
429
+
430
+ # Display file name
431
+ file_label = tk.Label(caption_frame, text=os.path.basename(file_path), font=('Helvetica', 12), wraplength=300, justify="left")
432
+ file_label.grid(row=i*2, column=1, padx=5, pady=5, sticky="nsew")
433
+
434
+ # Check and display caption if available
435
+ caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
436
+ if os.path.exists(caption_file):
437
+ with open(caption_file, 'r', encoding='utf-8') as file:
438
+ caption_text = file.read()
439
+ else:
440
+ caption_text = "" # Set empty string if no caption available
441
+
442
+ caption_text_widget = tk.Text(caption_frame, width=50, height=3, wrap=tk.WORD, font=('Helvetica', 12))
443
+ caption_text_widget.insert(tk.END, caption_text)
444
+ caption_text_widget.grid(row=i*2, column=2, padx=5, pady=5, sticky="nsew")
445
+ caption_text_widget.bind("<FocusOut>", lambda e, fp=file_path: save_caption(fp, caption_text_widget.get("1.0", "end-1c")))
446
+ caption_text_widgets.append(caption_text_widget)
447
+
448
+ # Update tags in tag_dict
449
+ update_tag_dict(caption_text)
450
+
451
+ except Exception as e:
452
+ tk.Label(caption_frame, text="Error loading image").grid(row=i*2, column=0, columnspan=4, padx=5, pady=5)
453
+
454
+ # Add navigation buttons
455
+ nav_frame = tk.Frame(caption_frame)
456
+ nav_frame.grid(row=images_per_page*2, column=0, columnspan=3, pady=10)
457
+
458
+ if current_page > 0:
459
+ prev_button = tk.Button(nav_frame, text="Previous", command=lambda: navigate(-1, content_canvas))
460
+ prev_button.pack(side=tk.LEFT)
461
+
462
+ page_label = tk.Label(nav_frame, text=f"Page {current_page + 1} of {total_pages}")
463
+ page_label.pack(side=tk.LEFT, padx=5)
464
+
465
+ page_entry = tk.Entry(nav_frame, width=5)
466
+ page_entry.pack(side=tk.LEFT)
467
+
468
+ go_button = tk.Button(nav_frame, text="Go", command=lambda: go_to_page(page_entry.get(), content_canvas))
469
+ go_button.pack(side=tk.LEFT, padx=5)
470
+
471
+ if current_page < total_pages - 1:
472
+ next_button = tk.Button(nav_frame, text="Next", command=lambda: navigate(1, content_canvas))
473
+ next_button.pack(side=tk.RIGHT)
474
+
475
+ # Update tag display after loading images
476
+ update_tag_display()
477
+
478
+ def navigate(direction, content_canvas):
479
+ global current_page
480
+ current_page += direction
481
+ update_image_preview(content_canvas)
482
+
483
+ def go_to_page(page_number, content_canvas):
484
+ global current_page, total_pages
485
+ try:
486
+ page_number = int(page_number)
487
+ if 1 <= page_number <= total_pages:
488
+ current_page = page_number - 1
489
+ update_image_preview(content_canvas)
490
+ else:
491
+ messagebox.showerror("Invalid Page", f"Please enter a valid page number between 1 and {total_pages}.")
492
+ except ValueError:
493
+ messagebox.showerror("Invalid Input", "Please enter a valid integer for the page number.")
494
+
495
+ def save_caption(file_path, caption_text):
496
+ """Save caption when user changes it."""
497
+ output_path = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
498
+ with open(output_path, 'w', encoding='utf-8') as file:
499
+ file.write(caption_text)
500
+
501
+ # Update tags after editing caption
502
+ update_tag_dict(caption_text)
503
+ update_tag_display()
504
+
505
+ def save_all_captions():
506
+ """Save all captions when closing the window."""
507
+ for i, caption_text_widget in enumerate(caption_text_widgets):
508
+ if caption_text_widget.winfo_exists():
509
+ file_path = selected_files[current_page * images_per_page + i]
510
+ caption_text = caption_text_widget.get("1.0", "end-1c")
511
+ save_caption(file_path, caption_text)
512
+
513
+ def update_tag_dict(caption_text=None):
514
+ """Update tag dictionary based on caption_text."""
515
+ global tag_dict
516
+ new_tag_dict = {}
517
+ for caption_widget in caption_text_widgets:
518
+ if caption_widget.winfo_exists():
519
+ caption_text = caption_widget.get("1.0", "end-1c")
520
+ tags = caption_text.split(',')
521
+ for tag in tags:
522
+ tag = tag.strip()
523
+ if tag:
524
+ if tag in new_tag_dict:
525
+ new_tag_dict[tag] += 1
526
+ else:
527
+ new_tag_dict[tag] = 1
528
+ tag_dict = new_tag_dict
529
+
530
+ def update_tag_display():
531
+ """Update tag display on Text widget."""
532
+ global tag_dict, selected_tag, edit_buttons, tag_text_frame
533
+ if tag_text_frame is None:
534
+ return # If the tag_text_frame is not created yet, return
535
+ for widget in tag_text_frame.winfo_children():
536
+ widget.destroy()
537
+
538
+ row = 0
539
+ for tag, count in tag_dict.items():
540
+ tag_label = tk.Label(tag_text_frame, text=tag, bg="white", padx=5, pady=2, anchor="w", relief="solid", borderwidth=1)
541
+ tag_label.grid(row=row, column=0, sticky="w", padx=2, pady=1)
542
+ count_label = tk.Label(tag_text_frame, text=f"({count})", anchor='w', width=5)
543
+ count_label.grid(row=row, column=1, padx=5, pady=2, sticky='w')
544
+
545
+ # Add tag manipulation buttons
546
+ add_tag_button = tk.Button(tag_text_frame, text="Add", command=lambda r=row: add_tag(r))
547
+ add_tag_button.grid(row=row, column=2, padx=2)
548
+ edit_button = tk.Button(tag_text_frame, text="Edit", command=lambda t=tag_label: edit_tag(t))
549
+ edit_button.grid(row=row, column=3, padx=2)
550
+ delete_button = tk.Button(tag_text_frame, text="Delete", command=lambda t=tag_label: delete_tag(t))
551
+ delete_button.grid(row=row, column=4, padx=2)
552
+ delete_all_button = tk.Button(tag_text_frame, text="Delete All", command=lambda t=tag_label: delete_all_files_with_tag(t))
553
+ delete_all_button.grid(row=row, column=5, padx=2)
554
+
555
+ edit_buttons[tag_label] = [add_tag_button, edit_button, delete_button, delete_all_button]
556
+
557
+ row += 1
558
+
559
+ # Điều chỉnh trọng số cột để không mở rộng quá mức
560
+ tag_text_frame.grid_columnconfigure(0, weight=1) # Cột cho tag
561
+ tag_text_frame.grid_columnconfigure(1, weight=0) # Cột cho số lượng
562
+ tag_text_frame.grid_columnconfigure(2, weight=0) # Cột cho nút Add
563
+ tag_text_frame.grid_columnconfigure(3, weight=0) # Cột cho nút Edit
564
+ tag_text_frame.grid_columnconfigure(4, weight=0) # Cột cho nút Delete
565
+ tag_text_frame.grid_columnconfigure(5, weight=0) # Cột cho nút Delete All
566
+
567
+ tag_text_frame.update_idletasks() # Làm mới giao diện sau khi cập nhật danh sách tag
568
+ tag_text_frame.update() # Làm mới giao diện ngay lập tức
569
+
570
+ def add_tag(row):
571
+ """Add a new tag above the selected row."""
572
+ def save_new_tag(event=None):
573
+ new_tag = entry.get().strip()
574
+ if new_tag:
575
+ # Add the new tag to the tag_dict
576
+ if new_tag in tag_dict:
577
+ tag_dict[new_tag] += 1
578
+ else:
579
+ tag_dict[new_tag] = 1
580
+
581
+ # Update the captions in the respective files
582
+ for file_path in selected_files:
583
+ caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
584
+ if os.path.exists(caption_file):
585
+ with open(caption_file, 'r', encoding='utf-8') as file:
586
+ caption_text = file.read()
587
+ new_caption_text = caption_text + ',' + new_tag if caption_text else new_tag
588
+ with open(caption_file, 'w', encoding='utf-8') as file:
589
+ file.write(new_caption_text)
590
+
591
+ update_image_preview(content_canvas) # Update image preview
592
+ update_tag_display() # Update the display of tags
593
+
594
+ # Create entry above the row
595
+ for widget in tag_text_frame.grid_slaves():
596
+ if int(widget.grid_info()["row"]) >= row:
597
+ widget.grid_configure(row=int(widget.grid_info()["row"]) + 1)
598
+
599
+ entry = tk.Entry(tag_text_frame)
600
+ entry.grid(row=row, column=0, sticky="w", padx=2, pady=1)
601
+ entry.bind("<Return>", save_new_tag)
602
+ entry.bind("<FocusOut>", save_new_tag)
603
+ entry.bind("<Escape>", lambda e: entry.destroy())
604
+ entry.focus_set()
605
+
606
+ def edit_tag(tag_label):
607
+ """Edit the selected tag."""
608
+ def save_edit(event=None):
609
+ new_tag = entry.get().strip()
610
+ old_tag = tag_label.cget("text").strip()
611
+ if new_tag and new_tag != old_tag:
612
+ # Update the tag_dict with new tag
613
+ tag_dict[new_tag] = tag_dict.pop(old_tag)
614
+
615
+ # Update the captions in the respective files
616
+ for i, file_path in enumerate(selected_files):
617
+ caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
618
+ if os.path.exists(caption_file):
619
+ with open(caption_file, 'r', encoding='utf-8') as file:
620
+ caption_text = file.read()
621
+ new_caption_text = caption_text.replace(old_tag, new_tag)
622
+ with open(caption_file, 'w', encoding='utf-8') as file:
623
+ file.write(new_caption_text)
624
+
625
+ update_image_preview(content_canvas) # Update image preview
626
+ update_tag_display() # Update the display of tags
627
+
628
+ old_tag = tag_label.cget("text").strip()
629
+ entry = tk.Entry(tag_text_frame)
630
+ entry.insert(0, old_tag)
631
+ entry.grid(row=tag_label.grid_info()["row"], column=0, sticky="w", padx=2, pady=1)
632
+ entry.bind("<Return>", save_edit)
633
+ entry.bind("<FocusOut>", save_edit)
634
+ entry.bind("<Escape>", lambda e: entry.destroy())
635
+ entry.focus_set()
636
+
637
+ def delete_tag(tag_label):
638
+ """Delete the selected tag."""
639
+ global selected_tag
640
+ if tag_label:
641
+ tag_to_delete = tag_label.cget("text").strip()
642
+ if tag_to_delete in tag_dict:
643
+ del tag_dict[tag_to_delete]
644
+
645
+ # Update the captions in the respective files
646
+ for i, file_path in enumerate(selected_files):
647
+ caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
648
+ if os.path.exists(caption_file):
649
+ with open(caption_file, 'r', encoding='utf-8') as file:
650
+ caption_text = file.read()
651
+ new_caption_text = caption_text.replace(tag_to_delete, "")
652
+ with open(caption_file, 'w', encoding='utf-8') as file:
653
+ file.write(new_caption_text)
654
+
655
+ update_image_preview(content_canvas) # Update image preview
656
+ update_tag_display() # Update the display of tags
657
+
658
+ def delete_all_files_with_tag(tag_label):
659
+ """Delete all images and their corresponding text files containing the tag."""
660
+ tag_to_delete = tag_label.cget("text").strip()
661
+ if tag_to_delete in tag_dict:
662
+ del tag_dict[tag_to_delete]
663
+
664
+ # Delete the files containing the tag
665
+ files_to_delete = []
666
+ for i, file_path in enumerate(selected_files):
667
+ caption_file = os.path.join(save_directory, f"{os.path.basename(file_path)}_tags.txt")
668
+ if os.path.exists(caption_file):
669
+ with open(caption_file, 'r', encoding='utf-8') as file:
670
+ caption_text = file.read()
671
+ if tag_to_delete in caption_text:
672
+ files_to_delete.append((file_path, caption_file))
673
+ for image_file, caption_file in files_to_delete:
674
+ # Remove image file
675
+ try:
676
+ os.remove(image_file)
677
+ except Exception as e:
678
+ error_messages.append(f"Error deleting image {image_file}: {str(e)}")
679
+ # Remove caption file
680
+ try:
681
+ os.remove(caption_file)
682
+ except Exception as e:
683
+ error_messages.append(f"Error deleting caption {caption_file}: {str(e)}")
684
+
685
+ # Remove deleted files from selected_files list
686
+ selected_files[:] = [file for file in selected_files if file not in [img for img, cap in files_to_delete]]
687
+
688
+ # Clear tag_dict and update tag display
689
+ update_tag_dict() # Update tag dictionary
690
+ update_tag_display() # Update the display of tags
691
+ update_image_preview(content_canvas) # Update image preview
692
+
693
+ def sort_tags_alpha():
694
+ """Sort tags alphabetically."""
695
+ global tag_dict
696
+ sorted_tags = sorted(tag_dict.items())
697
+ tag_dict = dict(sorted_tags)
698
+ update_tag_display()
699
+
700
+ def sort_tags_count():
701
+ """Sort tags by count."""
702
+ global tag_dict
703
+ sorted_tags = sorted(tag_dict.items(), key=lambda item: item[1], reverse=True)
704
+ tag_dict = dict(sorted_tags)
705
+ update_tag_display()
706
+
707
+ # Create GUI components
708
+ back_button = tk.Button(root, text="<-", font=('Helvetica', 14), command=return_to_menu)
709
+ back_button.pack(anchor='nw', padx=10, pady=10)
710
+
711
+ title_label = tk.Label(root, text="Image Tag Generator", font=('Helvetica', 16))
712
+ title_label.pack(pady=10)
713
+
714
+ select_files_button = tk.Button(root, text="Select Files", command=select_files)
715
+ select_files_button.pack(pady=10)
716
+
717
+ num_files_label = tk.Label(root, textvariable=num_files_var)
718
+ num_files_label.pack(pady=5)
719
+
720
+ show_captions_button = tk.Button(root, text="Show Captions", command=open_caption_window)
721
+ show_captions_button.pack(pady=10)
722
+
723
+ general_threshold_label = tk.Label(root, text="General Threshold:")
724
+ general_threshold_label.pack(pady=5)
725
+ general_threshold_entry = tk.Entry(root, textvariable=general_threshold_var, justify='center', width=5, validate='key')
726
+ general_threshold_entry.pack(pady=5)
727
+
728
+ character_threshold_label = tk.Label(root, text="Character Threshold:")
729
+ character_threshold_label.pack(pady=5)
730
+ character_threshold_entry = tk.Entry(root, textvariable=character_threshold_var, justify='center', width=5, validate='key')
731
+ character_threshold_entry.pack(pady=5)
732
+
733
+ prepend_text_label = tk.Label(root, text="Prepend Text:")
734
+ prepend_text_label.pack(pady=5)
735
+ prepend_text_entry = tk.Entry(root, textvariable=prepend_text_var, justify='center', width=20)
736
+ prepend_text_entry.pack(pady=5)
737
+
738
+ append_text_label = tk.Label(root, text="Append Text:")
739
+ append_text_label.pack(pady=5)
740
+ append_text_entry = tk.Entry(root, textvariable=append_text_var, justify='center', width=20)
741
+ append_text_entry.pack(pady=5)
742
+
743
+ thread_count_label = tk.Label(root, text="Thread Count:")
744
+ thread_count_label.pack(pady=5)
745
+ thread_count_entry = tk.Entry(root, textvariable=thread_count_var, justify='center', width=5, validate='key')
746
+ thread_count_entry.pack(pady=5)
747
+
748
+ batch_size_label = tk.Label(root, text="Batch Size:")
749
+ batch_size_label.pack(pady=5)
750
+ batch_size_entry = tk.Entry(root, textvariable=batch_size_var, justify='center', width=5, validate='key')
751
+ batch_size_entry.pack(pady=5)
752
+
753
+ errors_button = tk.Button(root, textvariable=errors_var, command=lambda: show_errors(root))
754
+ errors_button.pack(pady=10)
755
+
756
+ start_button = tk.Button(root, text="Generate Captions", command=lambda: [process_files(), update_and_save_config()])
757
+ start_button.pack(pady=10)
758
+
759
+ stop_button = tk.Button(root, text="Stop", command=stop_processing_func)
760
+ stop_button.pack(pady=10)
761
+
762
+ progress_bar = ttk.Progressbar(root, variable=progress, maximum=100)
763
+ progress_bar.pack(pady=10, fill=tk.X)
764
+
765
+ status_label = tk.Label(root, textvariable=status_var, fg="green")
766
+ status_label.pack(pady=5)
767
+
768
+ # Add input validation
769
+ general_threshold_entry.config(validatecommand=(root.register(validate_threshold), '%P'))
770
+ character_threshold_entry.config(validatecommand=(root.register(validate_threshold), '%P'))
771
+ thread_count_entry.config(validatecommand=(root.register(validate_thread_count), '%P'))
772
+ batch_size_entry.config(validatecommand=(root.register(validate_batch_size), '%P'))
773
+
774
+ # Update config when the thresholds or thread count change
775
+ general_threshold_var.trace_add('write', lambda *args: update_and_save_config())
776
+ character_threshold_var.trace_add('write', lambda *args: update_and_save_config())
777
+ thread_count_var.trace_add('write', lambda *args: update_and_save_config())
778
+
779
+ center_window(root, width_extra=200)
780
+ root.protocol("WM_DELETE_WINDOW", on_closing)
781
+ root.mainloop()
782
+
783
+ if __name__ == "__main__":
784
+ open_image_to_tag()