diff --git "a/Danbooru Prompt Selector/TEST/prompt_selector_auto_1208_testv7.py" "b/Danbooru Prompt Selector/TEST/prompt_selector_auto_1208_testv7.py" new file mode 100644--- /dev/null +++ "b/Danbooru Prompt Selector/TEST/prompt_selector_auto_1208_testv7.py" @@ -0,0 +1,2328 @@ +import tkinter as tk +import tkinter.ttk as ttk +from tkinter import filedialog +import os +import csv +import random +import time +from collections import Counter +import re +import ast +import pyautogui +import threading +from pynput import keyboard +from base64 import urlsafe_b64encode +from hashlib import blake2b +import argon2 +import requests +import json +from os import environ as env +import zipfile +import io +from pathlib import Path +from datetime import datetime +import numpy as np +from PIL import Image, ImageOps, ImageDraw, ImageFont,ImageTk +from IPython.display import display +import win32clipboard +from io import BytesIO +from datetime import datetime +import arti_list, tagbag, wlist + +BASE_URL="https://api.novelai.net" + +def argon_hash(email: str, password: str, size: int, domain: str) -> str: + pre_salt = f"{password[:6]}{email}{domain}" + # salt + blake = blake2b(digest_size=16) + blake.update(pre_salt.encode()) + salt = blake.digest() + raw = argon2.low_level.hash_secret_raw( + password.encode(), + salt, + 2, + int(2000000 / 1024), + 1, + size, + argon2.low_level.Type.ID, + ) + hashed = urlsafe_b64encode(raw).decode() + return hashed + +def login(key) -> str: + response = requests.post(f"{BASE_URL}/user/login", json={ "key": key }) + # catch any errors + return response.json()["accessToken"] + +def get_access_key(email: str, password: str) -> str: + return argon_hash(email, password, 64, "novelai_data_access_key")[:64] + +def get_max_size(): + global window + width = window.winfo_screenheight() + return 768 if width < 1440 else 1024 + +def generate_image(access_token, prompt, model, action, parameters): + data = { + "input": prompt, + "model": model, + "action": action, + "parameters": parameters, + } + + response = requests.post(f"{BASE_URL}/ai/generate-image", json=data, headers={ "Authorization": f"Bearer {access_token}" }) + # catch any errors + return response.content + +def generate(width, height, positive, negative, button): + global temp_clipboard_image + global current_sampler + global NAI_width, NAI_height + global running_flag + global last_generation_seed + try: + scale = float(entry_CFG_value.get()) + except: + scale = 5.0 + if scale > 7.0: scale = 7.0 + elif scale < 4.0: scale = 4.0 + else: scale = round(scale, 1) + + try: + scale2 = float(entry_rescale_value.get()) + except: + scale2 = 0 + if scale2 > 1.00: scale = 1.00 + elif scale2 < 0.0: scale = 0.00 + else: scale2 = round(scale2, 2) + + params = { + "legacy": False, + "quality_toggle": False, + "width": width, + "height": height, + "n_samples": 1, + "seed": random.randint(0,9999999999), + "extra_noise_seed": random.randint(0,9999999999), + "sampler": current_sampler, + "steps": 28, + "scale": scale, + "uncond_scale": 1.0, + "negative_prompt": negative_text.get("1.0", "end-1c").strip(), + "sm" : sema_button_var.get(), + "sm_dyn" : dyn_button_var.get(), + "decrisper": False, + "controlnet_strength": 1.0, + "add_original_image": False, + "cfg_rescale": scale2, + "noise_schedule": "native", + } + + if (hold_seed_var.get()): + params['seed'] = last_generation_seed + + def resize_and_fill(image, max_size=None): + if max_size is None: + max_size = get_max_size() + original_width, original_height = image.size + if original_width > max_size or original_height > max_size: + # 비율을 유지하면서 크기 조정 + image.thumbnail((max_size, max_size)) + + # 새 이미지 크기 계산 + width, height = image.size + new_image = Image.new("RGB", (max_size, max_size), "black") + new_image.paste(image, ((max_size - width) // 2, (max_size - height) // 2)) + return new_image + else: + return image + + def log_error(e, output_file_path="output_file_path"): + # 현재 시간을 얻습니다 + current_time = datetime.now().strftime("%m/%d %H:%M:%S") + + # 에러 로그 메시지 + error_message = f"#### Error occured at {current_time} ####\nError: {e}\n############################################\n" + + # 지정된 출력 폴더의 error_log.txt 파일에 쓰기 + with open(f"error_log.txt", "a") as file: + file.write(error_message) + + global access_token, access_token_multi, error_count, multi_token_enable + while (running_flag): + time.sleep(1) + try: + running_flag = True + tprint("Running : NAI request received") + if not (multi_token_enable): zipped_bytes = generate_image(access_token, positive, "nai-diffusion-3", "generate", params) + else: zipped_bytes = generate_image(access_token_multi, positive, "nai-diffusion-3", "generate", params) + d = Path(f"output_NAI/{start_time}/txt2img") + d.mkdir(parents=True, exist_ok=True) + zipped = zipfile.ZipFile(io.BytesIO(zipped_bytes)) + image_bytes = zipped.read(zipped.infolist()[0]) + (d / f"{datetime.now().strftime('%Y%m%d_%H%M%S')}.png" ).write_bytes(image_bytes) + i = Image.open(io.BytesIO(image_bytes)) + i = ImageOps.exif_transpose(i).convert("RGB") + if temp_clipboard_image is not None: + temp_clipboard_image.close() + temp_clipboard_image = i + i_resized = resize_and_fill(i) + if 'tk_image' in globals(): + globals()['tk_image'] = None + tk_image = ImageTk.PhotoImage(i_resized) + image_label.config(image=tk_image) + image_label.original_image = i.copy() + image_label.image = tk_image # 참조 유지 + output_file_path = "output_image.jpg" + i.save(output_file_path) + error_count = 0 + except Exception as e: + #text_output.insert(tk.END, f"Error: {e}", fg='red') + log_error(zipped_bytes.decode('utf-8')[2:-2], "path_to_output_folder") + time.sleep(random.uniform(1.0, 2.5)) + error_count += 1 + if '429' in zipped_bytes.decode('utf-8')[2:-2] and access_token_multi: + if not multi_token_enable: + multi_token_enable = True + else: + mac_var.set(0) + text_output.delete('1.0', tk.END) + text_output.insert(tk.END, zipped_bytes.decode('utf-8')[2:-2]) + else: + mac_var.set(0) + text_output.delete('1.0', tk.END) + text_output.insert(tk.END, zipped_bytes.decode('utf-8')[2:-2]) + button.config(state=tk.NORMAL) + + global delay_offset + time.sleep(delay_offset) + + running_flag = False + last_generation_seed = params['seed'] + entry_seed_value.set(last_generation_seed) + + if mac_var.get() and not running_flag: + random_function() + time.sleep(random.uniform(5.1, 8.5)) + button.config(state=tk.DISABLED) + window.event_generate(GENERATE_EVENT, when="tail") + else: + time.sleep(random.uniform(1.0, 2.5)) + button.config(state=tk.NORMAL) + tprint('state : idle') + +def filter_csv(input_file, output_file, _search_strings): + output_directory = os.getcwd() + output_file = os.path.join(output_directory, output_file) + search_strings = [s.strip() for s in _search_strings.split(',')] + with open(input_file, 'r', newline='', encoding='utf-8') as f_in, \ + open(output_file, 'w', newline='', encoding='utf-8') as f_out: + reader = csv.reader(f_in) + writer = csv.writer(f_out) + writer_count = 0 + for row in reader: + if all(search_str in value for search_str in search_strings for value in row): + writer.writerow(row) + writer_count += 1 + return writer_count + +def open_file(): + initial_dir = os.getcwd() + filepath = filedialog.askopenfilename( + initialdir=initial_dir, + filetypes=[("CSV Files", "*.csv")] + ) + if filepath: + entry_file_path.delete(0, tk.END) + entry_file_path.insert(0, filepath) + +def search(): + global total_rows, cached_rows # cached_rows를 global로 선언 + input_file = entry_file_path.get() + keywords = entry_keyword.get() + + output_file = 'txt2img_temp_prompt.csv' + writer_count = filter_csv(input_file, output_file, keywords) + total_rows = writer_count + total_rows_count_label.config(text=f".csv 내 프롬프트 행: {writer_count}", fg="blue") + text_output.insert(tk.END, f"총 {writer_count}개의 문자열이 검색되어 저장되었습니다.\n") + + # 검색 후 cached_rows 초기화 + cached_rows = [] + +def exclude(): + global total_rows, cached_rows # cached_rows를 global로 선언 + input_file = entry_file_path.get() + keywords = entry_keyword.get() + output_file = 'txt2img_temp_prompt.csv' + keyword_label.config(text="검색할 키워드: ") + + output_directory = os.getcwd() + output_file = os.path.join(output_directory, output_file) + search_strings = [s.strip() for s in keywords.split(',')] + + with open(input_file, 'r', newline='', encoding='utf-8') as f_in, \ + open(output_file, 'w', newline='', encoding='utf-8') as f_out: + reader = csv.reader(f_in) + writer = csv.writer(f_out) + writer_count = 0 + for row in reader: + if not any(search_str in value for search_str in search_strings for value in row): + writer.writerow(row) + writer_count += 1 + total_rows = writer_count + total_rows_count_label.config(text=f".csv 내 프롬프트 행: {writer_count}", fg="red") + text_output.insert(tk.END, f"총 {writer_count}개의 문자열이 검색되어 저장되었습니다.\n") + + # 제외 후 cached_rows 초기화 + cached_rows = [] + +def copy_image_to_clipboard(): + global temp_clipboard_image + image = temp_clipboard_image + output = BytesIO() + image.convert('RGB').save(output, format='BMP') + data = output.getvalue()[14:] # BMP 파일 헤더 제거 + output.close() + + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) + win32clipboard.CloseClipboard() + +def reset(): + cached_rows = None + entry_file_path.delete(0, tk.END) + entry_keyword.delete(0, tk.END) + entry_deep_search.delete(0, tk.END) + text_output.delete('1.0', tk.END) + +def random_function(): + global last_deep_search_keywords, cached_rows, last_selected_row_keywords, top_100_keywords, previous_artist, top_100_counts + global current_wildcard_character, current_wildcard_artist + current_deep_search_keywords = entry_deep_search.get().strip() + auto_hide_keywords = entry_auto_hide.get().split(',') + auto_hide_keywords = [keyword.strip() for keyword in auto_hide_keywords if keyword.strip()] + current_artist = None + + curly_brackets_keywords = re.findall(r'\{([^}]+)\}', current_deep_search_keywords) + # 각 브래킷 내 키워드 그룹을 처리하여 완벽 일치 키워드와 일반 키워드를 구분 + processed_keywords = [] + for group in curly_brackets_keywords: + perfect_match_group = [kw[1:].strip() for kw in group.split('|') if kw.startswith('*')] + regular_group = [kw.strip() for kw in group.split('|') if not kw.startswith('*')] + processed_keywords.append((perfect_match_group, regular_group)) + + modified_deep_search_keywords = re.sub(r'\{[^}]+\}', '', current_deep_search_keywords) + + # 남은 키워드들을 쉼표로 분리하여 처리 + remaining_keywords = [kw.strip() for kw in modified_deep_search_keywords.split(',') if kw.strip()] + perfect_match_keywords = [kw[1:].strip() for kw in remaining_keywords if kw.startswith('*')] + + # exclude_keywords와 include_keywords 정의 + exclude_keywords = [kw[1:].strip() for kw in remaining_keywords if kw.startswith('~') and not kw[1:].startswith('*')] + include_keywords = [kw for kw in remaining_keywords if not kw.startswith('~') and not kw.startswith('*')] + + if current_deep_search_keywords != last_deep_search_keywords or not cached_rows: + entry_auto_hide.delete(0, tk.END) + entry_auto_hide.insert(tk.END, 'monochrome, doujin cover, bad source, censored, bar censor, photoshop (medium)') + with open('txt2img_temp_prompt.csv', 'r', newline='', encoding='utf-8') as f: + reader = csv.reader(f) + rows = [] + for row in reader: + if all(re.search(r'(?:^|, )' + re.escape(kw) + r'(?:,|$)', ', '.join(row)) for kw in perfect_match_keywords) \ + and not any(exclude in cell for exclude in exclude_keywords for cell in row) \ + and all(include in cell for include in include_keywords for cell in row): + # '{}' 안의 키워드 처리 + if all(any(re.search(r'(?:^|, )' + re.escape(kw) + r'(?:,|$)', ', '.join(cell)) for kw in perfect_group) or + any(kw in cell for kw in regular_group) for perfect_group, regular_group in processed_keywords for cell in row): + rows.append(row) + cached_rows = rows + + last_deep_search_keywords = current_deep_search_keywords + if not toggle_prompt_var.get(): text_output.delete('1.0', tk.END) + + if random_artist_var.get(): + if(not entry_fixed_prompt.get()): entry_fixed_prompt.insert(tk.END,'1girl') + + if not os.path.exists("counting_result.txt"): + keyword_counts = analyze_cached_rows(cached_rows) + excluded_keywords = set(whitelist[:2974]) + with open("counting_result.txt", "w") as file: + for keyword, count in sorted(keyword_counts.items(), key=lambda item: item[1], reverse=True): + if keyword not in excluded_keywords and keyword in afilter_30000: + file.write(f"{keyword}: {count}\n") + with open("counting_result.txt", "r") as file: + lines = file.readlines() + top_100_data = [ + (line.split(":")[0].strip(), int(line.split(":")[1].strip())) + for line in lines[:3000] + if line.split(":")[0].strip() in afilter_30000[:8528] + ] + top_100_keywords, top_100_counts = zip(*top_100_data) if top_100_data else ([], []) + elif (not top_100_keywords): + with open("counting_result.txt", "r") as file: + lines = file.readlines() + top_100_data = [ + (line.split(":")[0].strip(), int(line.split(":")[1].strip())) + for line in lines[:3000] + if line.split(":")[0].strip() in afilter_30000[:8528] + ] + top_100_keywords, top_100_counts = zip(*top_100_data) if top_100_data else ([], []) + + formatted_artist_random_keyword = [] + if top_100_keywords: + temp_keyword = [] + temp_keyword += top_100_keywords + whatthefuck = random.random() + if(whatthefuck > 0.9): + for i in range(4): + random_keyword = random.choice(temp_keyword) + temp_keyword.remove(random_keyword) + if(i == 0): formatted_artist_random_keyword = "{{{artist:"+random_keyword+"}}}" + if(i == 1): formatted_artist_random_keyword += ",{artist:"+random_keyword+"}" + if(i == 2): formatted_artist_random_keyword += ",artist:"+random_keyword + if(i == 3): formatted_artist_random_keyword += ",[[artist:"+random_keyword+"]]" + elif(whatthefuck > 0.7): + for i in range(3): + random_keyword = random.choice(temp_keyword) + temp_keyword.remove(random_keyword) + if(i == 0): formatted_artist_random_keyword = "{{{artist:"+random_keyword+"}}}" + if(i == 1): formatted_artist_random_keyword += ",{artist:"+random_keyword+"}" + if(i == 2): formatted_artist_random_keyword += ",[[artist:"+random_keyword+"]]" + elif(whatthefuck > 0.45): + for i in range(2): + random_keyword = random.choice(temp_keyword) + temp_keyword.remove(random_keyword) + if(i == 0): formatted_artist_random_keyword = "{{{artist:"+random_keyword+"}}}" + if(i == 2): formatted_artist_random_keyword += ",[[artist:"+random_keyword+"]]" + else: + random_keyword = random.choice(temp_keyword) + temp_keyword.remove(random_keyword) + formatted_artist_random_keyword = "{{artist:"+random_keyword+"}}" + current_artist = formatted_artist_random_keyword + if not top_100_keywords: random_artist_var.set(0) + else: + formatted_artist_random_keyword = f"작가 키워드가 없음!!" + + if cached_rows: + if toggle_prompt_var.get() and last_selected_row_keywords: + random_row_keywords = text_output.get("1.0", "end-1c").split(',') + #random_row_keywords[-1].strip() + #if random_row_keywords[0]=='': random_row_keywords = last_selected_row_keywords + text_output.delete('1.0', tk.END) + else: + random_index = random.randint(0, len(cached_rows) - 1) + random_row = cached_rows.pop(random_index) + random_row_keywords = [keyword.strip() for keyword in random_row[0].split(',')] + last_selected_row_keywords = random_row_keywords.copy() + + if(entry_fixed_prompt.get()): + entry_text_keywords = entry_fixed_prompt.get() + if(',' not in entry_text_keywords[-2:]): entry_text_keywords += ',' + entry_text_keywords = entry_text_keywords.split(',') + entry_text_keywords = [kw.strip() for kw in entry_text_keywords if kw.strip()] + + temp_first_keywords = [] + #print("1 :", temp_first_keywords) + + # entry_text_keywords의 모든 키워드를 temp_first_keywords에 추가 + for et_kw in entry_text_keywords: + if ('boy' in et_kw or 'girl' in et_kw) or (' '+et_kw not in random_row_keywords and ' '+et_kw not in random_row_keywords): + temp_first_keywords.append(et_kw) + + # 'boy' 키워드 처리 + boy_keywords = [kw for kw in random_row_keywords if 'boy' in kw and len(kw) <= 7] + + # 'girl' 키워드 처리 및 위치 조정 + girl_keywords = [] + if 'girl' in entry_text_keywords[0] and len(entry_text_keywords[0]) <= 7: + girl_keywords = [kw for kw in random_row_keywords if 'girl' in kw and len(kw) <= 7] + + # random_row_keywords에서 boy와 girl 키워드 제거 + for kw in boy_keywords + girl_keywords: + if kw in random_row_keywords: + random_row_keywords.remove(kw) + + for kw in temp_first_keywords: + if kw in random_row_keywords: + random_row_keywords.remove(kw) + + + # temp_first_keywords에 boy와 girl 키워드를 적절한 위치에 추가 + if(girl_keywords and 'girl' in temp_first_keywords[0]): temp_first_keywords.pop(0) + #print(wildcard_var.get()) + if(wildcard_var2.get() == 1): + temp_len = len(girl_keywords) + girl_keywords.append(get_random_keyword('character')) + if(wildcard_var.get() == 1): + if(wildcard_var2.get() == 1): + girl_keywords.insert(temp_len+1,get_random_keyword('artist')) + else: + girl_keywords.append(get_random_keyword('artist')) + print(boy_keywords) + print(girl_keywords) + print(temp_first_keywords) + if temp_first_keywords and 'girl' in temp_first_keywords[0]: + girl_in_gkey = False + for gkey in girl_keywords: + if 'girl' in gkey: + girl_in_gkey = True + if not girl_in_gkey: + temp_gkey = temp_first_keywords.pop(0) + girl_keywords.insert(0, temp_gkey) + + temp_first_keywords = boy_keywords + girl_keywords + temp_first_keywords + #print("2 :", temp_first_keywords) + + if remove_artist_var.get(): + for keyword in random_row_keywords: + if keyword in afilter_30000: + random_row_keywords.remove(keyword) + temp_rm = [] + if rm_characteristic_var.get(): + for keyword in random_row_keywords: + if keyword in bag_of_tags or "(" in keyword or "horns" in keyword: + temp_rm.append(keyword) + for keyword in temp_rm: + random_row_keywords.remove(keyword) + + for keyword in random_row_keywords[:]: # 복사본을 순회하여 원본 리스트를 수정 + if " (" in keyword: + temp_first_keywords.append(keyword) + random_row_keywords.remove(keyword) + else: + if not random_row_keywords: random_row_keywords = last_selected_row_keywords + temp_first_keywords = [] + for keyword in random_row_keywords[:]: # 복사본을 순회하여 원본 리스트를 수정 + if " (" in keyword: + temp_first_keywords.append(keyword) + random_row_keywords.remove(keyword) + + #last_selected_row_keywords =boy_keywords + girl_keywords[:0] + random_row_keywords + #print("3 :", temp_first_keywords) + if random_artist_var.get(): + temp_first_keywords.append(formatted_artist_random_keyword) + #print("4 :", temp_first_keywords) + if (previous_artist and previous_artist in temp_first_keywords): temp_first_keywords.remove(previous_artist) + if (current_artist is (not None)): + for i in range (len(current_artist)): + previous_artist += current_artist[i] + random_row_keywords = temp_first_keywords + random_row_keywords + #print("5 :", random_row_keywords) + #elif toggle_prompt_var.get(): random_row_keywords = temp_first_keywords + random_row_keywords + else: random_row_keywords = temp_first_keywords + random_row_keywords + + patterns = [r'\b{}\b'.format(re.escape(keyword)) for keyword in auto_hide_keywords] + + for pattern in patterns: + random_row_keywords = [re.sub(pattern + '(, )?', '', keyword) for keyword in random_row_keywords] + random_row_keywords = [re.sub('(?<=, )' + pattern, '', keyword) for keyword in random_row_keywords] + + if entry_fixed_prompt_after.get(): + entry_after_text = entry_fixed_prompt_after.get().strip().split(',') + for at_kw in entry_after_text: + if at_kw not in random_row_keywords and ' '+at_kw not in random_row_keywords: + random_row_keywords.append(at_kw) + + random_row_keywords = [keyword.strip(', ') for keyword in random_row_keywords] + random_row_keywords = [keyword for keyword in random_row_keywords if keyword] + #print(random_row_keywords) + text_output.insert(tk.END, f"{', '.join(random_row_keywords)}") + if auto_copy_var.get(): + copy_to_clipboard() + else: + text_output.insert(tk.END, "검색 조건에 맞는 데이터가 없거나 CSV 파일에 데이터가 없습니다.\n") + + cached_rows_count_label.config(text=f"심층검색 프롬프트 행: {len(cached_rows)}") + +def add_low_frequency_keywords(): + global keyword_counts, most_common_count + keyword_counts = analyze_cached_rows(cached_rows) + if not keyword_counts: # keyword_counts가 비어있는 경우 함수를 빠져나옵니다. + return + + # 가장 흔한 키워드의 빈도수를 가져옵니다. + most_common_count = keyword_counts.most_common(1)[0][1] + + # 조건에 맞는 키워드들을 찾습니다. + low_freq_keywords = [keyword for keyword, count in keyword_counts.items() + if keyword not in last_deep_search_keywords and keyword not in whitelist and count / most_common_count <= 0.05] + + # 자동 숨김 키워드 입력창에 업데이트합니다. + existing_keywords = entry_auto_hide.get().split(',') + existing_keywords = [keyword.strip() for keyword in existing_keywords if keyword.strip()] # 공백 제거 및 빈 문자열 제거 + + # 기존 키워드에 새로운 저빈도 키워드를 추가합니다. + updated_keywords = existing_keywords + low_freq_keywords + unique_keywords = list(set(updated_keywords)) # 중복 제거 + for keyword in unique_keywords: + if(keyword in afilter_30000): + unique_keywords.remove(keyword) + entry_auto_hide.delete(0, tk.END) + entry_auto_hide.insert(0, ', '.join(unique_keywords)) + +def copy_to_clipboard(): + window.clipboard_clear() + entry_text = entry_fixed_prompt.get() + if(',' not in entry_text and entry_text): entry_text += ',' + combined_text = text_output.get("1.0", tk.END) + window.clipboard_append(combined_text) + +def turbo_stop(button): + global turbo_stop_bit + button.config(state=tk.DISABLED) + turbo_stop_bit = True + tprint("Stopping pipelines ... ") + +def exit_program(): + global stop_event, auto_thread + if stop_event is not None: + stop_event.set() + if auto_thread is not None and auto_thread.is_alive(): + auto_thread.join() # 스레드가 종료될 때까지 기다림 + window.destroy() + +def save_settings(): + global NAI_ID + with open('app_settings.txt', 'w', encoding='utf-8') as f: #1 + if(len(entry_file_path.get()) > 4): + f.write(entry_file_path.get() + '\n') + else: f.write(" "+'\n') + if(len(entry_keyword.get()) > 3): #2 + f.write(entry_keyword.get() + '\n') + else: f.write(" "+'\n') + if(len(entry_deep_search.get()) > 3): #3 + f.write(entry_deep_search.get() + '\n') + else: f.write(" "+'\n') + if(len(entry_fixed_prompt.get()) > 4): #4 + f.write(entry_fixed_prompt.get() + '\n') + else: f.write(" "+'\n') + if(len(entry_fixed_prompt_after.get()) > 4): #5 + f.write(entry_fixed_prompt_after.get() + '\n') + else: f.write(" "+'\n') + if(len(entry_auto_hide.get()) > 3): #6 + f.write(entry_auto_hide.get() + '\n') + else: f.write(" "+'\n') + if(len(negative_text.get("1.0", tk.END)) > 4): #7 + f.write(negative_text.get("1.0", tk.END)) + else: f.write(" "+'\n') + if(NAI_ID): f.write(NAI_ID + '\n' ) #항상 마지막에 오도록 기억 #8 + +def load_settings(): + NAI_ID = "" + if os.path.exists('app_settings.txt'): + with open('app_settings.txt', 'r', encoding='utf-8') as f: + settings = f.readlines() + entry_file_path.insert(0, settings[0].strip()) + entry_keyword.insert(0, settings[1].strip()) + entry_deep_search.insert(0, settings[2].strip()) + entry_fixed_prompt.insert(0, settings[3].strip()) + entry_fixed_prompt_after.insert(0, settings[4].strip()) + entry_auto_hide.insert(0, settings[5].strip()) + if(len(settings)>=7): + negative_text.insert(tk.END, settings[6].strip()) + if(len(settings)>=8): + NAI_ID = settings[7].strip() #항상 마지막에 오도록 기억 + return NAI_ID + +def open_prompt_window(): + # 새 창 생성 + prompt_window = tk.Toplevel(window) + prompt_title = f"{entry_keyword.get()}+{last_deep_search_keywords}" if last_deep_search_keywords else "추천 프롬프트" + prompt_window.title(prompt_title) + + + # 첫 번째 Text Output Box 설정 + text_output1 = tk.Text(prompt_window, height=10, width=50) + text_output1.pack(padx=10, pady=10) + + # 굵은 글씨 스타일 태그를 설정합니다. + text_output1.tag_configure('bold', font=('Arial', 10, 'bold')) + + # 두 번째 Text Output Box 설정 + text_output2 = tk.Text(prompt_window, height=10, width=50) + text_output2.pack(padx=10, pady=10) + + # 세 번째 Text Output Box 설정 + text_output3 = tk.Text(prompt_window, height=10, width=50) + text_output3.pack(padx=10, pady=10) + + # cached_rows 분석 + keyword_counts = analyze_cached_rows(cached_rows) + + # 가장 높은 count 값을 가져옵니다. + most_common_count = keyword_counts.most_common(1)[0][1] if keyword_counts else 0 + + # text_output1에 가장 흔한 키워드를 출력하고, 조건에 따라 굵게 표시합니다. + for keyword, count in keyword_counts.most_common(50): + if count / most_common_count >= 0.5: # count가 가장 높은 키워드 대비 60% 이상인 경우 + text_output1.insert(tk.END, f"{keyword}, ", 'bold') + else: + text_output1.insert(tk.END, f"{keyword}, ") + + # text_output2에 모든 키워드와 빈도수 출력 + for keyword, count in keyword_counts.most_common(200): + text_output2.insert(tk.END, f"{keyword}: {count}, ") + + excluded_keywords = set(whitelist[:2974]) + artist_count = 0 + with open("counting_result.txt", "w") as file: + for keyword, count in sorted(keyword_counts.items(), key=lambda item: item[1], reverse=True): + if keyword not in excluded_keywords and keyword in afilter_30000: + file.write(f"{keyword}: {count}\n") + if(artist_count < 150): + text_output3.insert(tk.END, f"{keyword}: {count}, ") + artist_count += 1 + + # 닫기 버튼 설정 + close_button = tk.Button(prompt_window, text="닫기", command=prompt_window.destroy) + close_button.pack(pady=10) + +def analyze_cached_rows(cached_rows): + global top_100_keywords + top_100_keywords = [] + counts = Counter() + for row in cached_rows: + substrings = [substring.strip() for value in row for substring in value.split(',') if substring.strip()] + counts.update(substrings) + return counts + +def exit_program(): + save_settings() + window.destroy() + +def on_ctrl_enter(event): + random_function() + +def on_shift_enter(event): + random_function() + copy_to_clipboard() + +def export_csv(): + source_file = os.path.join(os.getcwd(), 'txt2img_temp_prompt.csv') + if not os.path.exists(source_file): + print("소스 파일이 존재하지 않습니다.") + return + + # 기본 파일 이름을 사용자가 입력한 키워드로 설정 + default_filename = f"{entry_keyword.get()}.csv" if entry_keyword.get() else "exported.csv" + file_types = [('CSV 파일', '*.csv')] + dest_file = filedialog.asksaveasfilename(defaultextension='.csv', filetypes=file_types, initialfile=default_filename) + + if dest_file: # 사용자가 파일 이름을 선택했을 경우 + import shutil + shutil.copy(source_file, dest_file) + print(f"파일이 저장되었습니다: {dest_file}") + +def export_csv_search(): + + if not cached_rows: + print("비어있는 프롬프트행.") + return + + # 기본 파일 이름을 사용자가 입력한 키워드로 설정 + default_filename = f"{entry_keyword.get()}.csv" if entry_keyword.get() else "exported.csv" + file_types = [('CSV 파일', '*.csv')] + dest_file = filedialog.asksaveasfilename(defaultextension='.csv', filetypes=file_types, initialfile=default_filename) + + if dest_file: # 사용자가 파일 위치를 선택했다면 + with open(dest_file, 'w', newline='', encoding='utf-8') as file: + writer = csv.writer(file) + for row in cached_rows: + writer.writerow(row) # 각 행을 csv 파일에 쓴다 + +def simulate_shortcuts(): + global listener, on_press_flag + + on_press_flag = True + random_function() + on_press_flag = False + try: + current_keys.clear() + current_keys.add(keyboard.Key.ctrl_l) + except KeyError: + pass + +def on_activate_simulate_shortcuts(): + simulate_shortcuts() + +def for_canonical(f): + return lambda k: f(listener.canonical(k)) + +def start_keyboard_listener(): + global listener + listener = keyboard.Listener(on_press=on_press, on_release=on_release) + listener.start() + +def on_press(key): + global on_press_flag + + current_keys.add(key) + + # Ctrl + ` 조합 감지 + if keyboard.Key.ctrl_l in current_keys: + if any(k.vk == 192 for k in current_keys if hasattr(k, 'vk')): + on_press_flag = True + simulate_shortcuts() + +def on_release(key): + try: + current_keys.remove(key) + except KeyError: + pass + +def v_automatic(save_position, prompt_position): + # 프롬프트 창 클릭 + copy_to_clipboard() + pyautogui.click(prompt_position) + time.sleep(0.5) + random_function() + pyautogui.hotkey('ctrl', 'a', 'v','Right','Right','Enter') + time.sleep(0.5) + pyautogui.click(save_position) + +def start_automation(save_position, prompt_position, label, window_to_close, automation_event): + def automation_task(): + while automation_event.is_set(): + random_delay = random.uniform(17.5, 23.5) + next_click_time = time.time() + random_delay + while time.time() < next_click_time and automation_event.is_set(): + time_remaining = max(next_click_time - time.time(), 0) + label.config(text=f"다음 클릭까지: {time_remaining:.2f}초") + time.sleep(0.1) + if automation_event.is_set(): + v_automatic(save_position, prompt_position) + + automation_thread = threading.Thread(target=automation_task, daemon=True) + automation_thread.start() + random_function() + + def stop_automation(): + automation_event.clear() + window_to_close.destroy() + + window_to_close.protocol("WM_DELETE_WINDOW", stop_automation) + +def stop_automation(window_to_close, automation_event, start_button, stop_button): + automation_event.clear() # Stop the automation task + start_button['state'] = 'normal' # Enable the start button + stop_button['state'] = 'disabled' # Disable the stop button + window_to_close.destroy() # Close the automation window + window.deiconify() + +# 라벨을 업데이트하는 함수 +def update_label(label, text): + def task(): + label.config(text=text) + label.after(1000, task) # 메인 스레드에서 UI 업데이트 + +def open_automation_window(): + window.iconify() + automation_event = threading.Event() + automation_event.set() # Enable the automation event + auto_window = tk.Toplevel(window) + auto_window.title("자동화 설정") + auto_window.geometry("300x200") # 창 크기 조정 + auto_window.attributes('-topmost', True) # 창을 항상 맨 위에 위치 + auto_window.focus_force() # 창 열릴 때 포커스 강제 이동 + + save_position = None + prompt_position = None + automation_running = threading.Event() + + def get_mouse_position(event=None): + nonlocal save_position, prompt_position + x, y = pyautogui.position() + if not save_position: + save_position = (x, y) + position_label.config(text=f"저장 버튼의 좌표: {x}, {y}\n프롬프트 창의 좌표: ") + elif not prompt_position: + prompt_position = (x, y) + position_label.config(text=f"저장 버튼의 좌표: {save_position[0]}, {save_position[1]}\n프롬프트 창의 좌표: {x}, {y}") + start_button.config(state="normal") + if save_position and prompt_position: + start_button.config(state="normal") + stop_button.config(state="normal") + + + position_label = tk.Label(auto_window, text="엔터를 눌러 마우스 좌표를 저장하세요\n(클릭 X, 1.저장->2.프롬프트창 순서)", justify=tk.LEFT) + position_label.pack(pady=10) + + start_button = tk.Button(auto_window, text="시작", state="disabled", + command=lambda: start_automation(save_position, prompt_position, countdown_label, auto_window, automation_event)) + start_button.pack(side=tk.LEFT, fill='x', expand=True) + + stop_button = tk.Button(auto_window, text="중지", state="disabled", + command=lambda: stop_automation(auto_window, automation_event, start_button, stop_button)) + stop_button.pack(side=tk.RIGHT, fill='x', expand=True) + + countdown_label = tk.Label(auto_window, text="") + countdown_label.pack(pady=10) + + auto_window.bind('', get_mouse_position) + +def NAI_generation(width, height, button): + global access_token_multi, NAI_width, NAI_height + global button_stop, running_flag + + def run_function(): + time.sleep(1) + random_function() + time.sleep(2) + window.event_generate(GENERATE_EVENT, when="tail") + + pretest = text_output.get("1.0", tk.END).split(', ') + if (turbo_var.get() == 1): + if ( 'sex' not in pretest or '1girl' not in pretest or '1boy' not in pretest): + tprint('부스트 기능은 1girl, 1boy, sex 세가지 키워드가 있어야 동작합니다.','color=red') + if not mac_var.get(): + button.config(state='normal') + return + else: + thread = threading.Thread(target=run_function) + thread.start() + return + + if(rand_resolution_var.get() == 1): + resolutions = ["1024 x 1024", "960 x 1088", "896 x 1152", "832 x 1216", "1088 x 960", "1152 x 896", "1216 x 832"] + random_resolution = random.choice(resolutions) + selected_resolution.set(random_resolution) + + if(turbo_var.get() == 1 and access_token): + button_stop.config(state='normal') + NAI_generation_turbo(NAI_width, NAI_height, button) + else: + if not (running_flag): + NAI_generation_normal(NAI_width, NAI_height, button) + +def Auto_login_check(): + global access_token, access_token_multi + if os.path.exists('token.txt'): + with open('token.txt', 'r', encoding='utf-8') as f: + tokens = f.readlines() + if(tokens[0]): + access_token = tokens[0].strip() + tprint("1 Account automatically loaded : ",access_token[-5:]) + if(len(tokens)>=2): + access_token_multi = tokens[1].strip() + tprint("2 Account automatically loaded : ",access_token[-5:]+", "+access_token_multi[-5:]) + if(access_token): button_generate.config(state='normal') + +def NAI_generation_normal(width, height, button): + button.config(state=tk.DISABLED) + positive = text_output.get("1.0", tk.END) + negative = negative_text.get("1.0", "end-1c").strip() + global auto_count_left_flag, auto_count_left + if(auto_count_left_flag and auto_count_left > 0): + auto_count_left -= 1 + thread = threading.Thread(target=generate, args=(width, height, positive, negative, button)) + thread.start() + +def NAI_generation_turbo(width, height, button): + global turbo_count, access_token, access_token_multi + button.config(state=tk.DISABLED) + positive = text_output.get("1.0", tk.END) + negative = negative_text.get("1.0", "end-1c").strip() + + def SELECT(positive): + lines = positive[:25] + result = { + "boys": False, + "girls": False, + "1girl": False, + "1boy": False, + "1other": False, + "others": False + } + state = { + "nude,": False, + "pov,": False, + "cum,": False, + "after ": False, + "pussy juice": False, + "barefoot": False, + "breasts": False, + "ejaculation": False, + } + + def insert_spaces(source_list, reference_list): + modified_list = source_list.copy() + for index, keyword in enumerate(reference_list): + if keyword not in source_list: + space_count = len(keyword) # 키워드 길이만큼의 공백 문자 + modified_list.insert(index, ' ' * space_count) + return modified_list + + keywords = positive.split(', ') + filtered_keywords = [] + removed_indices = [] + positive0, positive1, positive2, positive3 = None, None, None, None + + for word in result.keys(): + if word in lines: + result[word] = True + for word in state.keys(): + if word in positive: + state[word] = True + + if(result["boys"]): + #group sex + if(result["girls"]): + pass + #gangbang + elif(result["1girl"]): + pass + + key_index = int((len(keywords)/2)-1) + + global bag_of_tags + eye_color = ['aqua eyes', 'black eyes', 'blue eyes', 'brown eyes', 'green eyes', 'grey eyes', 'orange eyes', 'purple eyes', 'pink eyes', 'red eyes', 'white eyes', 'yellow eyes', 'amber eyes'] + hair_color = ['aqua hair', 'black hair', 'blonde hair', 'blue hair', 'light blue hair', 'dark blue hair', 'brown hair', 'light brown hair', 'green hair', 'dark green hair', 'light green hair', 'grey hair', 'orange hair', 'pink hair', 'purple hair', 'light purple hair', 'red hair', 'white hair'] + + if not wildcard_var2.get() and not rm_characteristic_var.get(): + key_features = [] + for tag in bag_of_tags: + if tag in keywords: + key_features.append(tag) + keywords.remove(tag) + if not any(color in key_features for color in eye_color): key_features.append(random.choice(eye_color)) + if not any(color in key_features for color in hair_color): key_features.append(random.choice(hair_color)) + key_feature = "{" +' ,'.join(key_features) + "}" + keywords.insert(2, key_feature) + + if(result["1boy"]): + if(result["girls"]): + pass + elif(result["1girl"]): + if('sex,' in positive): + sex_pos_keywords = ['stomach bulge','insertion', 'fucked silly', 'x-ray', 'orgasm', 'cross-section', 'uterus', 'overflow', 'rape'] + facial_keywords = ['tongue','ahegao'] + temp_sex_pos = [] + temp_facial = [] + cum_events = [] + explicit_check = [] + if 'open mouth' in keywords: keywords.remove('open mouth') + if 'closed mouth' in keywords: keywords.remove('closed mouth') + if 'after rape' in keywords: + keywords.remove('after rape') + explicit_check.append('after rape') + for keyword in keywords: + if ('sex' not in keyword and 'cum' not in keyword and 'ejaculation' not in keyword and 'vaginal' not in keyword and 'penetration' not in keyword) and all(sex_pos not in keyword for sex_pos in sex_pos_keywords) and all(facial not in keyword for facial in facial_keywords): + filtered_keywords.append(keyword) + elif 'sex' in keyword: + removed_indices.append(keyword) + elif 'penetration' in keyword: + removed_indices.append(keyword) + elif 'cum' in keyword and keyword != 'cum': + cum_events.append(keyword) + elif any(sex_pos in keyword for sex_pos in sex_pos_keywords): + for sex_pos in sex_pos_keywords: + if sex_pos in keyword: + temp_sex_pos.append(sex_pos) + elif any(facial not in keyword for facial in facial_keywords): + for facial in facial_keywords: + if facial in keyword: + temp_facial.append(facial) + filtered_keywords.insert(int((len(filtered_keywords)/2)-1), ' no penetration, imminent penetration') + filtered_keywords_positive0 = filtered_keywords.copy() + filtered_keywords.remove(' no penetration, imminent penetration') + #0 imminent penetration, imminent sex + for i, keyword in enumerate(filtered_keywords): + if 'pantyhose' in keyword: + filtered_keywords[i] = 'torn ' + filtered_keywords[i] + #1 default + key_index = int((len(filtered_keywords)/2)-1) + if 'pussy' in filtered_keywords: key_index = filtered_keywords.index('pussy') + if 'penis' in filtered_keywords: key_index = filtered_keywords.index('penis') + filtered_keywords[key_index:key_index] = ['motion lines', 'surprised'] + for keyword in removed_indices: + if 'cum' not in keyword and 'ejaculation' not in keyword: + filtered_keywords.insert(key_index,keyword) + if(temp_sex_pos): filtered_keywords[key_index:key_index] = temp_sex_pos + if('clothed sex' in filtered_keywords and not 'bottomless' in filtered_keywords): filtered_keywords.insert(filtered_keywords.index('clothed sex')+1, 'bottomless') + pos1_copied_keywords = filtered_keywords.copy() + for i, keyword in enumerate(pos1_copied_keywords): + if 'closed eyes' in keyword: + rand_num = random.randint(0,2) + if(rand_num == 0): pos1_copied_keywords[i] = 'half-' + pos1_copied_keywords[i] + elif(rand_num == 1 and 'closed eyes' in pos1_copied_keywords): + pos1_copied_keywords.remove('closed eyes') + filtered_keywords[i] = 'half-closed eyes' + filtered_keywords_positive1 = pos1_copied_keywords.copy() + #2 ejaculation,cum in pussy + key_index = filtered_keywords.index('surprised') + filtered_keywords.remove('surprised') + filtered_keywords[key_index:key_index] = ["ejaculation","cum"] + for keyword in removed_indices: + if 'cum' in keyword: + filtered_keywords.insert(key_index,keyword) + if(temp_facial): filtered_keywords[key_index:key_index] =temp_facial + filtered_keywords_positive2 = filtered_keywords.copy() + #3 after sex, after ejaculation + for i, keyword in enumerate(filtered_keywords): + if 'closed eyes' in keyword: + rand_num = random.randint(0,2) + if(rand_num == 0 and filtered_keywords[i] != 'half-closed eyes'): filtered_keywords[i] = 'half-' + filtered_keywords[i] + elif(rand_num == 1): filtered_keywords[i] = 'empty eyes' + else: filtered_keywords[i] = 'empty eyes, half-closed eyes' + key_index = filtered_keywords.index('sex') + filtered_keywords.remove('ejaculation') + filtered_keywords[key_index:key_index] = ['cum drip', 'erection'] + cum_events + if(explicit_check): filtered_keywords[key_index:key_index] = explicit_check + if('pussy' in filtered_keywords and not 'anal' in filtered_keywords): filtered_keywords.insert(filtered_keywords.index('sex')+1, 'after vaginal, spread pussy') + elif('anal' in filtered_keywords): filtered_keywords.insert(filtered_keywords.index('sex')+1, 'after anus, cum in ass') + filtered_keywords.insert(filtered_keywords.index('sex'), 'after sex') + filtered_keywords.remove('sex') + positive0 = ', '.join(insert_spaces(filtered_keywords_positive0, filtered_keywords)).strip() + print("round 1 : ",positive0) + positive1 = ', '.join(insert_spaces(filtered_keywords_positive1, filtered_keywords)).strip() + print("round 2 : ",positive1) + positive2 = ', '.join(insert_spaces(filtered_keywords_positive2, filtered_keywords)).strip() + print("round 3 : ",positive2) + positive3 = ', '.join(filtered_keywords).strip() + print("round 4 : ",positive3) + elif(', penis' in positive): + if(', oral' in positive): + pass + elif(', paizuri' in positive): + pass + elif('job,' in positive): + if('handjob' in positive): + pass + elif('footjob' in positive): + pass + + return positive0, positive1, positive2, positive3 + + positive0, positive1, positive2, positive3 = SELECT(text_output.get("1.0", tk.END)); + + if not hold_seed_var.get(): seed = random.randint(0,9999999999) + else: seed = last_generation_seed + + sleep0 = random.uniform(13.5,19.5) + sleep1 = sleep0 + random.uniform(13.5,19.5) + sleep2 = sleep1 + random.uniform(13.5,19.5) + sleep3 = sleep2 + random.uniform(13.5,19.5) + + global auto_count_left_flag, auto_count_left, turbo_stop_bit + if(auto_count_left_flag and auto_count_left > 0): + auto_count_left -= 5 + thread_init = threading.Thread(target=generate_turbo, args=(seed,turbo_count, access_token, width, height, positive, negative, button, 0, 0)) + thread_init.start() + thread0 = threading.Thread(target=generate_turbo, args=(seed,turbo_count, access_token, width, height, positive0, negative, button, 1, sleep0)) + thread0.start() + thread1 = threading.Thread(target=generate_turbo, args=(seed,turbo_count, access_token, width, height, positive1, negative, button, 2, sleep1)) + thread1.start() + thread2 = threading.Thread(target=generate_turbo, args=(seed,turbo_count, access_token, width, height, positive2, negative, button, 3, sleep2)) + thread2.start() + thread3 = threading.Thread(target=generate_turbo, args=(seed,turbo_count, access_token, width, height, positive3, negative, button, 4, sleep3)) + thread3.start() + + turbo_count += 1 + +def generate_turbo(_seed, turbo_count, tb_access_token,width, height, positive, negative, button, pipe_number, sleeptime): + global temp_clipboard_image + global current_sampler + global NAI_width, NAI_height + global running_flag + global image_queue + global turbo_stop_bit + global task_finished + + if(pipe_number == 0): image_queue.clear() + + try: + scale = float(entry_CFG_value.get()) + except: + scale = 5.0 + if scale > 7.0: scale = 7.0 + elif scale < 4.0: scale = 4.0 + else: scale = round(scale, 1) + + try: + scale2 = float(entry_rescale_value.get()) + except: + scale2 = 0 + if scale2 > 1.00: scale = 1.00 + elif scale2 < 0.0: scale = 0.00 + else: scale2 = round(scale2, 2) + + params = { + "legacy": False, + "quality_toggle": False, + "width": width, + "height": height, + "n_samples": 1, + "seed": _seed, + "extra_noise_seed": random.randint(0,9999999999), + "sampler": current_sampler, + "steps": 28, + "scale": scale, + "uncond_scale": 1.0, + "negative_prompt": negative, + "sm" : sema_button_var.get(), + "sm_dyn" : dyn_button_var.get(), + "decrisper": False, + "controlnet_strength": 1.0, + "add_original_image": False, + "cfg_rescale": scale2, + "noise_schedule": "native", + } + + if (hold_seed_var.get()): + global last_generation_seed + params['seed'] = last_generation_seed + + def merge_images(image_queue): + if len(image_queue) < 4: + return None # 이미지가 4개 미만인 경우 None을 반환 + + # 모든 이미지의 크기를 동일하게 조정 + # 예시를 위해 첫 번째 이미지의 크기를 사용합니다. 필요에 따라 수정 가능 + width, height = image_queue[0].size + new_image = Image.new('RGB', (width * 2, height * 2)) + + # 이미지 위치 매핑 + positions = [(0, 0), (width, 0), (0, height), (width, height)] + + # 이미지 병합 + for i, img in enumerate(image_queue): + pos = positions[i] + new_image.paste(img, pos) + + return new_image + + def insert_queue(i, _number, turbo_count): + global image_queue, temp_clipboard_image + while len(image_queue) < 4: image_queue.append(None) + if _number <= 4: + image_queue[_number-1] = i.copy() + i_resized = resize_and_fill(i) + box_position = (i_resized.width - 25, 10) + draw = ImageDraw.Draw(i_resized) + draw.rectangle([box_position, (box_position[0] + 25, box_position[1] + 25)], fill="black") + font = ImageFont.load_default() + text_position = (box_position[0] + 12, box_position[1] + 12) # 텍스트 위치 조정 + draw.text(text_position, str(_number), fill="white", font=font) + if 'tk_image' in globals(): + globals()['tk_image'] = None + tk_image = ImageTk.PhotoImage(i_resized) + image_label.config(image=tk_image) + image_label.original_image = i.copy() + image_label.image = tk_image # 참조 유지 + if all(img is not None for img in image_queue): + time.sleep(5) + merged_image = merge_images(image_queue) + temp_clipboard_image = merged_image.copy() + if merged_image is not None: + output_directory = Path(f"output_NAI/{start_time}/turbo/grid") + output_directory.mkdir(parents=True, exist_ok=True) + file_path = output_directory / f"{turbo_count:05}.png" + merged_image.save(file_path) + i_resized = resize_and_fill(merged_image) + if 'tk_image' in globals(): + globals()['tk_image'] = None + tk_image = ImageTk.PhotoImage(i_resized) + image_label.config(image=tk_image) + image_label.original_image = i_resized.copy() + image_label.image = tk_image # 참조 유지 + output_file_path = "output_image.jpg" + merged_image.save(output_file_path) + image_queue = [] # 이미지 큐 초기화 + + def resize_and_fill(image, max_size=None): + if max_size is None: + max_size = get_max_size() + original_width, original_height = image.size + if original_width > max_size or original_height > max_size: + # 비율을 유지하면서 크기 조정 + image.thumbnail((max_size, max_size)) + + # 새 이미지 크기 계산 + width, height = image.size + new_image = Image.new("RGB", (max_size, max_size), "black") + new_image.paste(image, ((max_size - width) // 2, (max_size - height) // 2)) + return new_image + else: + return image + + def log_error(e, output_file_path="output_file_path"): + # 현재 시간을 얻습니다 + current_time = datetime.now().strftime("%m/%d %H:%M:%S") + + # 에러 로그 메시지 + error_message = f"#### Error occured at {current_time} ####\nError: {e}\n############################################\n" + + # 지정된 출력 폴더의 error_log.txt 파일에 쓰기 + with open(f"error_log.txt", "a") as file: + file.write(error_message) + + if(pipe_number != 0): + pipe_start_time = time.time() + while time.time() - pipe_start_time < sleeptime: + if(turbo_stop_bit): + return + time.sleep(1) + + while running_flag: + time.sleep(1) + print('running_flag : ',running_flag,'pipe_number : ',pipe_number, ' : already running ') + if(turbo_stop_bit): + return + print('containing : ',pipe_number) + + running_flag = True + tprint("Running : request received, pipeline : "+str(pipe_number)) + global error_count + try: + zipped_bytes = generate_image(tb_access_token, positive, "nai-diffusion-3", "generate", params) + if(pipe_number != 0): + d = Path(f"output_NAI/{start_time}/turbo") + d.mkdir(parents=True, exist_ok=True) + else: + d = Path(f"output_NAI/{start_time}/turbo/preprocessing") + d.mkdir(parents=True, exist_ok=True) + zipped = zipfile.ZipFile(io.BytesIO(zipped_bytes)) + image_bytes = zipped.read(zipped.infolist()[0]) + (d / f"{turbo_count:05}_{pipe_number}.png" ).write_bytes(image_bytes) + i = Image.open(io.BytesIO(image_bytes)) + i = ImageOps.exif_transpose(i).convert("RGB") + error_count = 0 + if(pipe_number != 0): + if temp_clipboard_image is not None: + temp_clipboard_image.close() + if(pipe_number == 1 and len(image_queue) < 4): image_queue.clear() + temp_clipboard_image = i + insert_queue(i, pipe_number, turbo_count) + tprint("image inserted, pipe number : "+str(pipe_number)) + else: + if temp_clipboard_image is not None: + temp_clipboard_image.close() + temp_clipboard_image = i + i_resized = resize_and_fill(i) + if 'tk_image' in globals(): + globals()['tk_image'] = None + tk_image = ImageTk.PhotoImage(i_resized) + image_label.config(image=tk_image) + image_label.original_image = i.copy() + image_label.image = tk_image # 참조 유지 + except Exception as e: + #text_output.insert(tk.END, f"Error: {e}", fg='red') + log_error(zipped_bytes.decode('utf-8')[2:-2], "path_to_output_folder") + error_count += 1 + if error_count > 5: + mac_var.set(0) + time.sleep(random.uniform(3.0, 9.0)) + insert_queue(temp_clipboard_image, pipe_number, turbo_count) + + task_finished += 1 #터보 기능의 안전장치입니다. + + time.sleep(random.uniform(3.0, 5.5)) + if(pipe_number == 4): button.config(state=tk.NORMAL) + running_flag = False + + last_generation_seed = params['seed'] + entry_seed_value.set(last_generation_seed) + + global delay_offset + time.sleep(delay_offset) + + stopped = False + if (turbo_stop_bit): + time.sleep(1.5) + turbo_stop_bit = False + button.config(state=tk.NORMAL) + stopped = True + if (pipe_number != 4): + tprint("Stopped : request stopped before assign pipeline :"+str(pipe_number+1)) + + if pipe_number == 4 and mac_var.get(): + wait_limit = 0 + while(task_finished < 5 and wait_limit < 20): + wait_limit += 1 + print(f"Wait other pipeline ... : ({wait_limit}/20)") + time.sleep(3) + if(wait_limit == 20): + print(f"Process : {turbo_count} request time-out") + task_finished = 0 + else: + task_finished = 0 + running_flag = False + random_function() + time.sleep(random.uniform(2.1, 5.5)) + if not (turbo_stop_bit): + button.config(state=tk.DISABLED) + window.event_generate(GENERATE_EVENT, when="tail") + else: + turbo_stop_bit = False + if(mac_var.get() and not running_flag): + button.config(state=tk.DISABLED) + window.event_generate(GENERATE_EVENT, when="tail") + else: + if(pipe_number == 4): + global button_stop + button_stop.config(state='disabled') + tprint('state : idle') + +def NAI_setting(button, button_generate): + global NAI_ID + NAI_setting_window = tk.Toplevel(window) + NAI_setting_window_title = "NAI 로그인" + + def NAI_close(): + NAI_setting_window.destroy() + + def NAI_token_save(button): + global access_token + with open('token.txt', 'w', encoding='utf-8') as f: + f.write(access_token) + NAI_State_label.config(text="(프로그램 경로에 token.txt가 저장되었습니다.)", fg="blue") + button.config(state=tk.DISABLED) + + def NAI_connect(button, connect_button, token_button): + global access_token, NAI_ID + username = entry_NAI_ID.get().strip() + password = entry_NAI_PW.get().strip() + access_key = get_access_key(username, password) + try: + access_token = login(access_key) + access_result = requests.get("https://api.novelai.net/user/information", headers={ "Authorization": f"Bearer {access_token}" }) + NAI_State_label.config(text="(로그인 성공, 닫기 버튼을 눌러주세요.)", fg="blue") + button.config(state=tk.DISABLED) + button_generate.config(state="normal") + connect_button.config(state=tk.DISABLED) + NAI_ID = username + token_button.config(state=tk.NORMAL) + except Exception as e: + print(e) + NAI_close() + + button_frame0 = tk.Frame(NAI_setting_window) + button_frame0.grid(row=0, padx=5, pady=5, sticky='ew') + button_frame1 = tk.Frame(button_frame0) + button_frame1.grid(row=0, padx=5, pady=5, sticky='ew') + button_frame1.columnconfigure(0, weight=1) + button_frame1.columnconfigure(1, weight=3) + button_frame1.columnconfigure(2, weight=3) + button_frame1.columnconfigure(3, weight=1) + NAI_ID_label = tk.Label(button_frame1, text="NAI ID: ") + NAI_ID_label.grid(row=0, column=0, sticky='ew') + entry_NAI_ID = tk.Entry(button_frame1) + entry_NAI_ID.grid(row=0, column=1, columnspan=3, padx=5, pady=5, sticky='ew') + if(NAI_ID): entry_NAI_ID.insert(tk.END,NAI_ID) + NAI_PW_label = tk.Label(button_frame1, text="NAI PW: ") + NAI_PW_label.grid(row=1, column=0,sticky='ew') + entry_NAI_PW = tk.Entry(button_frame1, show="*") + entry_NAI_PW.grid(row=1, column=1, columnspan=3,padx=5, pady=5, sticky='ew') + token_output = tk.Button(button_frame0, text="로그인 정보 기억하기", command=lambda: NAI_token_save(token_output)) + token_output.grid(row=2, column=1, padx=5, pady=5, sticky='ew') + connect_button = tk.Button(button_frame0, text="연결", command=lambda: NAI_connect(button, connect_button, token_output)) + connect_button.grid(row=2, column=0, padx=5, pady=5, sticky='ew') + close_button = tk.Button(button_frame0, text="닫기", command=NAI_close) + close_button.grid(row=2, column=2, padx=5, pady=5, sticky='ew') + NAI_State_label = tk.Label(button_frame0, text="해당 접속기능은 정상적인 접속 패턴이 아닌점 참고 부탁드립니다.", fg="red") + NAI_State_label.grid(row=3, column=0, sticky='ew') + +def NAI_setting_multi(button): + global NAI_ID_multi + NAI_setting_window_multi = tk.Toplevel(window) + NAI_setting_window_title = "NAI 로그인" + + def NAI_close(): + NAI_setting_window_multi.destroy() + + NAI_ID_label = tk.Label(NAI_setting_window_multi, text="NAI ID: ") + NAI_ID_label.grid(row=0, column=0, columnspan=2, sticky='w') + entry_NAI_ID = tk.Entry(NAI_setting_window_multi, width=50) + entry_NAI_ID.grid(row=0, column=1, columnspan=2, padx=100, pady=5, sticky='e') + if(NAI_ID_multi): entry_NAI_ID.insert(tk.END,NAI_ID_multi) + NAI_PW_label = tk.Label(NAI_setting_window_multi, text="NAI PW: ") + NAI_PW_label.grid(row=1, column=0, columnspan=2, sticky='w') + entry_NAI_PW = tk.Entry(NAI_setting_window_multi, width=50, show="*") + entry_NAI_PW.grid(row=1, column=1, columnspan=2, padx=100, pady=5, sticky='e') + + def NAI_connect(button, connect_button): + global access_token_multi, NAI_ID, NAI_ID_multi + username = entry_NAI_ID.get().strip() + password = entry_NAI_PW.get().strip() + access_key = get_access_key(username, password) + try: + access_token_multi = login(access_key) + access_result = requests.get("https://api.novelai.net/user/information", headers={ "Authorization": f"Bearer {access_token}" }) + + if NAI_ID == NAI_ID_multi: NAI_State_label.config(text="(같은 계정으로 로그인 하셨습니다.)", fg="red") + else: NAI_State_label.config(text="(로그인 성공, 닫기 버튼을 눌러주세요.)", fg="blue") + button.config(state=tk.DISABLED) + connect_button.config(state=tk.DISABLED) + NAI_ID_multi = username + except Exception as e: + print(e) + NAI_close() + + connect_button = tk.Button(NAI_setting_window_multi, text="연결", command=lambda: NAI_connect(button, connect_button)) + connect_button.grid(row=2, column=1, columnspan=2, padx=5, pady=5, sticky='w') + close_button = tk.Button(NAI_setting_window_multi, text="닫기", command=NAI_close) + close_button.grid(row=2, column=2, columnspan=2, padx=5, pady=5, sticky='w') + NAI_State_label = tk.Label(NAI_setting_window_multi, text="해당 기능은 NAI Opus 구독 계정 2개가 요구됩니다..", fg="red") + NAI_State_label.grid(row=3, column=1, sticky='ew') + +def on_resolution_change(*args): + global NAI_width, NAI_height + resolution = selected_resolution.get() + NAI_width, NAI_height = resolution.split(' x ') + +def on_option_select(value): + global current_sampler + current_sampler = value + +def open_file_explorer(): + output_file_path = "output_NAI" + if os.path.exists(output_file_path): + os.startfile(output_file_path) + +def on_generate_event(event): + global running_flag + if not (running_flag): + NAI_generation(NAI_width, NAI_height, button_generate) + +def random_artist_management(): + global top_100_keywords, top_100_counts, cached_rows + if not cached_rows: return + if not (top_100_keywords): + if not os.path.exists("counting_result.txt"): + keyword_counts = analyze_cached_rows(cached_rows) + excluded_keywords = set(whitelist[:2974]) + with open("counting_result.txt", "w") as file: + for keyword, count in sorted(keyword_counts.items(), key=lambda item: item[1], reverse=True): + if keyword not in excluded_keywords and keyword in afilter_30000: + file.write(f"{keyword}: {count}\n") + with open("counting_result.txt", "r") as file: + lines = file.readlines() + top_100_data = [ + (line.split(":")[0].strip(), int(line.split(":")[1].strip())) + for line in lines[:3000] + if line.split(":")[0].strip() in afilter_30000[:8528] + ] + top_100_keywords, top_100_counts = zip(*top_100_data) if top_100_data else ([], []) + elif (not top_100_keywords): + with open("counting_result.txt", "r") as file: + lines = file.readlines() + top_100_data = [ + (line.split(":")[0].strip(), int(line.split(":")[1].strip())) + for line in lines[:3000] + if line.split(":")[0].strip() in afilter_30000[:8528] + ] + top_100_keywords, top_100_counts = zip(*top_100_data) if top_100_data else ([], []) + + # top_100_keywords 내의 + # 새 창 생성 + random_artist_window = tk.Toplevel(window) + + # 첫 번째 Text Output Box 설정 + text_output1 = tk.Text(random_artist_window, height=40, width=50) + text_output1.pack(padx=10, pady=10) + for keyword, count in zip(top_100_keywords, top_100_counts): + text_output1.insert(tk.END, f"{keyword}: {count}\n") + + def update_lists(): + # Text 위젯에서 내용을 가져옵니다. + content = text_output1.get("1.0", tk.END) + lines = content.strip().split("\n") # 줄바꿈으로 구분하여 리스트를 생성합니다. + + # 갱신된 데이터를 저장할 두 개의 리스트를 생성합니다. + updated_keywords = [] + updated_counts = [] + + # 각 줄을 처리하여 리스트를 업데이트합니다. + for line in lines: + if line: # 빈 줄이 아닌 경우에만 처리합니다. + parts = line.split(":") + if len(parts) == 2: # "키워드: 카운트" 형식이 맞는지 확인합니다. + updated_keywords.append(parts[0].strip()) + updated_counts.append(int(parts[1].strip())) + + # 전역 리스트를 갱신합니다. + global top_100_keywords, top_100_counts + top_100_keywords = updated_keywords + top_100_counts = updated_counts + with open("random_artist_keywords_"+str(len(top_100_keywords))+".txt", "w") as f: + for keyword in updated_keywords: + f.write('100: artist:'+keyword + "\n") + + # 버튼들을 담을 프레임 생성 + buttons_frame = tk.Frame(random_artist_window) + buttons_frame.pack(pady=10) + + # '저장' 버튼을 프레임의 왼쪽에 배치 + save_button = tk.Button(buttons_frame, text="저장", command=update_lists) + save_button.pack(side=tk.LEFT, padx=5) + + # '닫기' 버튼을 프레임의 오른쪽에 배치 + close_button = tk.Button(buttons_frame, text="닫기", command=random_artist_window.destroy) + close_button.pack(side=tk.RIGHT, padx=5) + + random_artist_label = tk.Label(random_artist_window, text='작가이름 총 '+str(len(top_100_keywords))+'개, 저장위치: exe파일 위치') + random_artist_label.pack() + +def open_wildcard_setting(): + global artist_wildcard_saved, character_wildcard_saved + # 새 창 생성 + wildcard_window = tk.Toplevel(window) + wildcard_window.title("와일드 카드 설정") + + # 작가명 파일 불러오기 + artist_label = tk.Label(wildcard_window, text="작가명 파일 불러오기 ->") + artist_label.grid(row=0, column=0) + artist_button = tk.Button(wildcard_window, text="열기", command=lambda: load_wildcard(artist_text, wildcard_window, 'artist')) + artist_button.grid(row=0, column=1) + + artist_text = tk.Text(wildcard_window, height=15, width=60) + artist_text.grid(row=1, column=0, columnspan=2) + if artist_wildcard_saved: + artist_text_content = '\n'.join(artist_wildcard_saved) + artist_text.insert('1.0', artist_text_content) + + # 캐릭터 파일 불러오기 + character_label = tk.Label(wildcard_window, text="캐릭터 파일 불러오기 ->") + character_label.grid(row=2, column=0) + character_button = tk.Button(wildcard_window, text="열기", command=lambda: load_wildcard(character_text, wildcard_window, 'character')) + character_button.grid(row=2, column=1) + + character_text = tk.Text(wildcard_window, height=15, width=60) + character_text.grid(row=3, column=0, columnspan=2) + if character_wildcard_saved: + character_text_content = '\n'.join(character_wildcard_saved) + character_text.insert('1.0', character_text_content) + + # 버튼들 + buttons_frame = tk.Frame(wildcard_window) + buttons_frame.grid(row=4, column=0, columnspan=2) + + text_label = tk.Label(wildcard_window, text="wildcard 문법(작가):\noffset:wildcard1 150:null (helloworld)\noffset:wildcard2 100:none (goodbye)\n없으면 100: 으로 간주\noffset/offset 총합: wildcard 확률") + text_label.grid(row=5, column=0,sticky='ew') + text_label2 = tk.Label(wildcard_window, text="wildcard 문법(캐릭):\n200:{{arona (blue archive)}}, blue hair, blue eyes, hair over one eye, ...\n150:{{nahida (genshin impact)}}, green eyes, pointy ears, ...\n...") + text_label2.grid(row=6, column=0,sticky='ew') + + load_button = tk.Button(buttons_frame, text="와일드카드 탑재", command=lambda: apply_wildcard(artist_text, character_text, wildcard_window)) + load_button.pack(side=tk.LEFT, padx=10) + + close_button = tk.Button(buttons_frame, text="닫기", command=wildcard_window.destroy) + close_button.pack(side=tk.RIGHT, padx=10) + +def apply_wildcard(artist_text, character_text, window): + global artist_wildcard_saved + artist_wildcard_check = artist_text.get("1.0", tk.END) + if len(artist_wildcard_check) > 5: + lines = artist_wildcard_check.strip().split("\n") + artist_wildcard_saved = lines.copy() + akeywords = [] + current_index = 0 + for line in lines: + parts = line.strip().split(':', 1) + if len(parts) == 2 and parts[0].strip().isdigit(): + weight = int(parts[0].strip()) + else: + weight = 100 + parts = ['100', line.strip()] # '100:'을 앞에 추가합니다. + keyword = parts[1].strip() + akeywords.append((current_index, current_index + weight - 1, keyword)) + current_index += weight + global artist_wildcard + artist_wildcard = akeywords + check_wildcard.config(state='normal') + + character_wildcard_check = character_text.get("1.0", tk.END) + global character_wildcard_saved + if len(character_wildcard_check) > 5: + lines = character_wildcard_check.strip().split("\n") + character_wildcard_saved = lines.copy() + ckeywords = [] + current_index = 0 + for line in lines: + parts = line.strip().split(':', 1) + if len(parts) == 2 and parts[0].strip().isdigit(): + weight = int(parts[0].strip()) + else: + weight = 100 + parts = ['100', line.strip()] # '100:'을 앞에 추가합니다. + keyword = parts[1].strip() + ckeywords.append((current_index, current_index + weight - 1, keyword)) + current_index += weight + global character_wildcard + character_wildcard = ckeywords + check_wildcard2.config(state='normal') + + window.destroy() + +def load_wildcard(text_widget, window, _type): + global artist_wildcard_saved, character_wildcard_saved + window.iconify() + filepath = filedialog.askopenfilename(filetypes=[("텍스트 파일", "*.txt")]) + if filepath: + with open(filepath, 'r', encoding='utf-8') as file: + lines = file.readlines() + updated_lines = [] + for line in lines: + line = line.strip() + if ':' in line: + prefix, _ = line.split(':', 1) + if not prefix.strip().isdigit(): + line = f"100: {line}" + else: + line = f"100: {line}" + updated_lines.append(line) + + text_widget.delete('1.0', tk.END) + text_widget.insert('1.0', '\n'.join(updated_lines)) + + if(_type == 'artist'): + artist_wildcard_saved = updated_lines.copy() + elif(_type == 'character'): + character_wildcard_saved = updated_lines.copy() + + window.deiconify() + +def find_keyword(index, keywords): + for start, end, keyword in keywords: + if start <= index <= end: + return keyword + return None + +def get_random_keyword(req_keyword): + #called from artist_wildcard, character_wildcard + global artist_wildcard, character_wildcard + if(req_keyword == 'artist'): keywords = artist_wildcard + elif(req_keyword == 'character'): keywords = character_wildcard + print('lenghth = ',len(keywords)) + max_index = keywords[-1][1] + random_index = random.randint(0, max_index) + return find_keyword(random_index, keywords) + +def tprint(*args): + ctext = "" + color = None + for t in args: + if(type(t) is str and 'color=' in t): + color = t[6:] + else: + ctext += str(t) + if not color: running_state.config(text=ctext, fg='black') + else: running_state.config(text=ctext, fg='red') + + +def show_automation_option(): + global auto_thread, stop_event + top = tk.Toplevel(window) + top.title("Automation Stop Condition") + + global var, timer_label, timer_entry, count_label, count_entry + var = tk.StringVar(value="unlimited") + + def show_option(): + if var.get() == 'timer': + timer_label.grid(row=4, column=0) + timer_entry.grid(row=4, column=1) + count_label.grid_forget() + count_entry.grid_forget() + elif var.get() == 'count': + count_label.grid(row=4, column=0) + count_entry.grid(row=4, column=1) + timer_label.grid_forget() + timer_entry.grid_forget() + else: + timer_label.grid_forget() + timer_entry.grid_forget() + count_label.grid_forget() + count_entry.grid_forget() + + label_time = tk.Label(top, text="이미지 생성당 지연시간 추가 (초)") + label_time.grid(row=0, column=0, columnspan=3) + + delay_entry = tk.Entry(top) + delay_entry.grid(row=1, column=0, columnspan=3) + + label = tk.Label(top, text="자동화 종료 조건") + label.grid(row=2, column=0, columnspan=3) + + radio1 = tk.Radiobutton(top, text="무제한", variable=var, value="unlimited", command=show_option) + radio1.grid(row=3, column=0) + + radio2 = tk.Radiobutton(top, text="타이머", variable=var, value="timer", command=show_option) + radio2.grid(row=3, column=1) + + radio3 = tk.Radiobutton(top, text="생성 카운트", variable=var, value="count", command=show_option) + radio3.grid(row=3, column=2) + + timer_label = tk.Label(top, text="자동화 동작 시간(분) :") + timer_entry = tk.Entry(top) + + count_label = tk.Label(top, text="자동 생성 횟수:") + count_entry = tk.Entry(top) + + # "A.설정" 창에서 버튼 클릭 이벤트 처리 + def on_apply(top): + global auto_time_left_flag, auto_time_left, auto_count_left_flag, auto_count_left, delay_offset, delay_offset_label + + selected_option = var.get() + if delay_entry.get(): + try: + delay_offset = round(float(delay_entry.get()), 1) + delay_offset_label.config(text="Dealy offset : "+str(delay_offset)) + except ValueError as e: + print(e) + delay_offset = 0 + else: + delay_offset = 0 + delay_offset_label.config(text="") + if selected_option == "timer": + auto_time_left_flag = True + auto_count_left_flag = False + try: + # 엔트리에서 입력된 시간을 분 단위에서 초 단위로 변환 + auto_time_left = int(timer_entry.get()) * 60 + except ValueError: + # 잘못된 입력 처리 + print("Invalid input for timer. Please enter a number.") + return + elif selected_option == "count": + auto_count_left_flag = True + auto_time_left_flag = False + try: + auto_count_left = int(count_entry.get()) + except ValueError: + # 잘못된 입력 처리 + print("Invalid input for count. Please enter a number.") + return + else: + auto_time_left_flag = False + auto_count_left_flag = False + stop_auto_thread() # 이전 스레드 종료 + start_auto_thread() # 새 스레드 시작 + mac_var.set(1) + top.destroy() + + apply_button = tk.Button(top, text="적용", command=lambda: on_apply(top)) + stop_button = tk.Button(top, text="중단", command=on_stop) + close_button = tk.Button(top, text="닫기", command=top.destroy) + + apply_button.grid(row=5, column=0, pady=5) + stop_button.grid(row=5, column=1, pady=5) + close_button.grid(row=5, column=2, pady=5) + +def auto_time_thread(stop_event): + global auto_time_left, auto_time_left_flag + + def seconds_to_hms(seconds): + h = seconds // 3600 + m = (seconds % 3600) // 60 + s = seconds % 60 + return f"{h:02d}:{m:02d}:{s:02d}" + + def update_label_for_time_finished(): + automation_state.config(text="지정한 시간이 모두 소진되어 자동화가 종료되었습니다.") + auto_time_left_flag = False + mac_var.set(0) + + while not stop_event.is_set() and auto_time_left > 0: + time.sleep(1) + auto_time_left -= 1 + remaining_time = seconds_to_hms(auto_time_left) + window.after(0, lambda: automation_state.config(text=f"자동화 남은시간 : {remaining_time}")) + + if auto_time_left <= 0: + window.after(0, update_label_for_time_finished) + + if stop_event.is_set(): + window.after(0, lambda: automation_state.config(text="Conditional automation not set (A.설정)")) + auto_time_left_flag = False + +def auto_count_thread(stop_event): + global auto_count_left, auto_count_left_flag + + def update_label_for_count_finished(): + mac_var.set(0) # 자동화 종료 조건 충족 + auto_count_left_flag = False + automation_state.config(text="지정한 횟수가 모두 소진되어 자동화가 종료되었습니다.") + + while not stop_event.is_set(): + time.sleep(1) # 주기적으로 확인 + window.after(0, lambda: automation_state.config(text=f"자동화 남은횟수 : {auto_count_left}")) + if auto_count_left <= 0: + window.after(0, update_label_for_count_finished) + break # 루프 탈출 + if stop_event.is_set(): + window.after(0, lambda: automation_state.config(text="Conditional automation not set (A.설정)")) + mac_var.set(0) + auto_count_left_flag = False + +# 스레드 시작 및 종료 함수 +def start_auto_thread(): + global auto_thread, stop_event + stop_event = threading.Event() + auto_thread = None + + if auto_time_left_flag: + auto_thread = threading.Thread(target=auto_time_thread, args=(stop_event,)) + elif auto_count_left_flag: + auto_thread = threading.Thread(target=auto_count_thread, args=(stop_event,)) + + if auto_thread is not None: + auto_thread.start() + +def stop_auto_thread(): + global stop_event, auto_thread + if stop_event is not None: + stop_event.set() + #if auto_thread is not None and auto_thread.is_alive(): + # auto_thread.join() + +def on_stop(): + global auto_thread, stop_event + stop_auto_thread() # 스레드 종료 + +def on_hold_seed_button_click(): + global last_generation_seed + last_generation_seed = entry_seed_value.get() + +def update_fullscreen_image(new_window, new_image_label): + last_updated_image = None + while True: + time.sleep(1) + if not new_window.winfo_exists(): + break + try: + current_image = getattr(image_label, 'original_image', None) + if current_image and current_image != last_updated_image: + resized_image = resize_image_to_fit(current_image, new_window.winfo_height()) + tk_image = ImageTk.PhotoImage(resized_image) + new_window.after(0, lambda img=tk_image: new_image_label.config(image=img)) + new_image_label.image = tk_image + last_updated_image = current_image + except ValueError: + print(ValueError) + +def show_fullscreen_image(): + new_window = tk.Toplevel() + new_window.attributes('-fullscreen', True) + new_window.configure(bg='black') + + new_image_label = tk.Label(new_window, relief='solid', borderwidth=1, bg='black') + new_image_label.pack(expand=True, fill='both') + new_window.bind("", lambda e: new_window.destroy()) + + update_thread = threading.Thread(target=update_fullscreen_image, args=(new_window, new_image_label)) + update_thread.daemon = True + update_thread.start() + +def resize_image_to_fit(image, target_height): + original_width, original_height = image.size + + ratio = target_height / float(original_height) + new_width = int(original_width * ratio) + + resized_image = image.resize((new_width, target_height), Image.Resampling.LANCZOS) + return resized_image + +auto_time_left_flag = False +auto_time_left = 0 +auto_count_left_flag = False +auto_count_left = 0 +stop_event = None +auto_thread = None +delay_offset = 0 + +start_time = datetime.now().strftime('%Y%m%d_%H%M') +thread_controller = False +turbo_count = 0 +on_press_flag = False +window = tk.Tk() +window.title("Prompt Selector for Danbooru tags") +last_deep_search_keywords = None +cached_rows = [] +total_rows = 0 +last_selected_row_keywords = ['1girl','nahida (genshin impact)', 'looking at viewer', 'garden'] +top_100_keywords = [] +top_100_counts = [] +previous_artist = None +listener = None +current_keys = set() +click_count = 0 +access_token = None +temp_clipboard_image = None +NAI_width = 1024 +NAI_height = 1024 +current_sampler = "k_euler_ancestral" +running_flag = False +NAI_ID = None +artist_wildcard = [] +character_wildcard_saved = [] +artist_wildcard_saved = [] +character_wildcard =[] +access_token_multi = None +NAI_ID_multi = None +image_queue = [] +last_generation_seed = random.randint(0,9999999999) +turbo_stop_bit = False +task_finished = 0 +error_count = 0 +multi_token_enable = False + +whitelist = wlist.whitelist +bag_of_tags = tagbag.bag_of_tags +afilter_30000 = arti_list.afilter_30000 + +current_wildcard_artist = [] +current_wildcard_character = [] + +GENERATE_EVENT = "<>" +window.bind(GENERATE_EVENT, on_generate_event) + +left_frame = tk.Frame(window) +left_frame.grid(row=0, column=0, sticky="nsew") +#left_frame.grid_columnconfigure(0, weight=1) +right_frame = tk.Frame(window) +right_frame.grid(row=0, column=1, sticky="nsew") +window.grid_columnconfigure(0, weight=1) +window.grid_columnconfigure(1, weight=1) + +#파일 경로 및 키워드 입력창 그리드 관리 +frame_row0 = tk.Frame(left_frame) +frame_row0.grid(row=0, column=0, padx=5, pady=5, sticky='w') + +# 파일 경로 입력창 +label_file_path = tk.Label(frame_row0, text="CSV 파일 경로:") +label_file_path.grid(row=0, column=0, sticky='w') +entry_file_path = tk.Entry(frame_row0, width=55) +entry_file_path.grid(row=0, column=1, columnspan=4, padx=5, pady=5, sticky='ew') +button_open_file = tk.Button(frame_row0, text="파일 열기", command=open_file) +button_open_file.grid(row=0, column=5, padx=5, pady=6, sticky='e') + +# 키워드 입력창 +keyword_label = tk.Label(frame_row0, text="초기화 키워드: ") +keyword_label.grid(row=1, column=0, sticky='w') +entry_keyword = tk.Entry(frame_row0) +entry_keyword.grid(row=1, column=1, columnspan=4, padx=5, pady=5, sticky='ew') +button_export_csv = tk.Button(frame_row0, text=".csv 내보내기", command=export_csv) +button_export_csv.grid(row=1, column=5, padx=5, pady=6, sticky='e') + +# 버튼 프레임 +frame_buttons = tk.Frame(left_frame) +frame_buttons.grid(row=1, column=0, sticky='ew') +frame_buttons.columnconfigure(0, weight=1) # 왼쪽에 가중치 부여 +frame_buttons.columnconfigure(1, weight=0) # 버튼 열은 가중치 없음 +frame_buttons.columnconfigure(2, weight=0) # 버튼 열은 가중치 없음 +frame_buttons.columnconfigure(3, weight=1) # 오른쪽에 가중치 부여 + +# 버튼들 +button_search = tk.Button(frame_buttons, text="해당 키워드 검색", command=search) +button_search.grid(row=0, column=1, padx=5, sticky='ew') # 변경: column=1 +button_exclude = tk.Button(frame_buttons, text="해당 키워드 제외", command=exclude) +button_exclude.grid(row=0, column=2, padx=5, sticky='ew') # 변경: column=2 + +total_rows_count_label = tk.Label(frame_buttons, text=".csv 내 프롬프트 행: 0") +total_rows_count_label.grid(row=0, column=3, padx=5, sticky='ew') + +# 심층검색 키워드 입력창 +deep_search_frame = tk.Frame(left_frame) +deep_search_frame.grid(row=2, column=0, pady=10, sticky='w') +deep_search_frame.columnconfigure(0, weight=2) +deep_search_frame.columnconfigure(1, weight=2) +deep_search_frame.columnconfigure(2, weight=1) + +label_deep_search = tk.Label(deep_search_frame, text="심층검색 키워드 입력: key,*key,~key,{key1|key2} ") +label_deep_search.grid(row=0, column=0, sticky='w') + +# Tkinter UI 설정 부분에 레이블 추가 +cached_rows_count_label = tk.Label(deep_search_frame, text="심층검색 프롬프트 행: 0") +cached_rows_count_label.grid(row=0, column=1, padx=5, sticky='w') + +button_export_deep_csv = tk.Button(deep_search_frame, text=".csv 내보내기", command=export_csv_search) +button_export_deep_csv.grid(row=0, column=2, padx=5, sticky='e') + +entry_deep_search = tk.Entry(deep_search_frame, width=82) +entry_deep_search.grid(row=1, column=0, columnspan=3,padx=5, pady=5, sticky='ew') + +# 버튼들을 포함할 프레임 생성 +button_frame = tk.Frame(left_frame) +button_frame.grid(row=5, column=0, padx=5, sticky='w') + +# 버튼 프레임 내의 열 설정 +button_frame.columnconfigure(0, weight=1) +button_frame.columnconfigure(1, weight=1) +button_frame.columnconfigure(2, weight=1) +button_frame.columnconfigure(3, weight=1) +button_frame.columnconfigure(4, weight=1) +button_frame.columnconfigure(5, weight=1) +button_frame.columnconfigure(6, weight=1) + +# "랜덤" 버튼 +button_random = tk.Button(button_frame, text="랜덤", command=random_function) +button_random.grid(row=0, column=0, sticky='ew') + +# "프롬프트 고정" 토글 버튼 +toggle_prompt_var = tk.IntVar() +button_toggle_prompt = tk.Checkbutton(button_frame, text="프롬프트 고정", variable=toggle_prompt_var) +button_toggle_prompt.grid(row=0, column=1, sticky='ew') + +# "자동 복사" 체크박스 +auto_copy_var = tk.IntVar() +check_auto_copy = tk.Checkbutton(button_frame, text="자동 복사", variable=auto_copy_var) +check_auto_copy.grid(row=0, column=2, sticky='ew') + +# "종료" 버튼 +button_exit = tk.Button(button_frame, text="종료", command=exit_program) +button_exit.grid(row=0, column=3, sticky='ew') + +# "생성" 버튼 +button_generate = tk.Button(button_frame, text="NAI 요청", command=lambda: NAI_generation(NAI_width, NAI_height, button_generate)) +button_generate.grid(row=0, column=4, sticky='ew') +button_generate.config(state='disabled') + +# "중단"버튼 +button_stop = tk.Button(button_frame, text="중단",command=lambda: turbo_stop(button_stop)) +button_stop.grid(row=0, column=5, sticky='ew') +button_stop.config(state='disabled') + +# "설정" 버튼 +button_setting = tk.Button(button_frame, text="NAI 설정", command=lambda: NAI_setting(button_setting, button_generate)) +button_setting.grid(row=0, column=6, sticky='ew') + +# '자동화'와 '터보on'을 포함할 서브 프레임 생성 +sub_frame = tk.Frame(button_frame) +sub_frame.grid(row=0, column=7, sticky='ns') # 서브 프레임을 열 8에 배치 + +# '자동화' 체크박스를 서브 프레임에 배치 +mac_var = tk.IntVar() +mac_button = tk.Checkbutton(sub_frame, text="자동화", variable=mac_var) +mac_button.grid(row=0, column=0, sticky='ew') # 첫 번째 행에 배치 + +# '터보on' 체크박스를 서브 프레임에 배치 +turbo_var = tk.IntVar() +turbo_button = tk.Checkbutton(sub_frame, text="터보on", variable=turbo_var) +turbo_button.grid(row=1, column=0, sticky='ew') # 두 번째 행에 배치 + +# "A.설정" 버튼 +button_auto_setting = tk.Button(button_frame, text="A.설정", command=show_automation_option) +button_auto_setting.grid(row=0, column=8, sticky='ew') + +# 텍스트 및 고정 프롬프트 네거티브 등 프롬프트 창 +text_frame = tk.Frame(left_frame) +text_frame.grid(row=6, column=0, padx=5, pady=5, sticky='w') +text_frame.columnconfigure(0, weight=1) +text_frame.columnconfigure(1, weight=7) + +text_frame2 = tk.Frame(left_frame) +text_frame2.grid(row=8, column=0, padx=5, pady=5, sticky='w') +text_frame2.columnconfigure(0, weight=1) +text_frame2.columnconfigure(1, weight=7) + +_size = get_max_size() + +# 출력 텍스트 창 +text_output_label = tk.Label(text_frame, text="프롬프트", borderwidth=1, relief="solid",height=6) +text_output_label.grid(row=0, column=0, padx=5, pady=5, sticky='w') +if _size<=768: text_output_width = 74 +else: text_output_width = 65 +text_output = tk.Text(text_frame, width=text_output_width, height=7) +text_output.grid(row=0, column=1, sticky='ew') + +fixed_prompt_frame = tk.Frame(left_frame) +fixed_prompt_frame.grid(row=7, column=0, padx=5, pady=5, sticky='w') +fixed_prompt_frame.columnconfigure(0, weight=1) +fixed_prompt_frame.columnconfigure(1, weight=1) + +fixed_prompt_label_left = tk.Label(fixed_prompt_frame, text="선행 고정 프롬프트 (프롬프트 앞)") +fixed_prompt_label_left.grid(row=0, column=0, sticky='ew') +fixed_prompt_label_right = tk.Label(fixed_prompt_frame, text="후행 고정 프롬프트 (프롬프트 뒤)") +fixed_prompt_label_right.grid(row=0, column=1, sticky='ew') +entry_fixed_prompt = tk.Entry(fixed_prompt_frame, width=40) +entry_fixed_prompt.grid(row=1, column=0,padx=5, pady=5, sticky='ew') +entry_fixed_prompt_after = tk.Entry(fixed_prompt_frame, width=40) +entry_fixed_prompt_after.grid(row=1, column=1, padx=5, pady=5, sticky='ew') + +# 네거티브 프롬프트 +NP_label = tk.Label(text_frame2, text="네거티브\n프롬프트", borderwidth=1, relief="solid",height=2) +NP_label.grid(row=0,column=0, padx=5, pady=5, sticky='ew') +negative_text = tk.Text(text_frame2, width=65, height=3) +negative_text.grid(row=0, column=1, padx=5, pady=5, sticky='w') + +auto_hide_frame = tk.Frame(left_frame) +auto_hide_frame.grid(row=9, column=0, padx=5, pady=5, sticky='w') +auto_hide_frame.columnconfigure(0, weight=2) +auto_hide_frame.columnconfigure(1, weight=2) +auto_hide_frame.columnconfigure(2, weight=1) + +# 자동 숨김 키워드 라벨과 입력창 추가 +auto_hide_label = tk.Label(auto_hide_frame, text="자동 숨김 키워드: keyword1, keyword2, ...") +auto_hide_label.grid(row=0, column=0, sticky='w') +entry_auto_hide = tk.Entry(auto_hide_frame, width=82) +entry_auto_hide.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky='ew') +# 추천 프롬프트 조회 버튼 설정 +recommend_prompt_button = tk.Button(auto_hide_frame, text="추천 프롬프트", command=open_prompt_window) +recommend_prompt_button.grid(row=0, column=2, padx=2, pady=5, sticky='e') # sticky 옵션을 'e'로 변경하여 오른쪽 정렬 + + +# 체크박스들을 포함할 프레임 생성 +checkbox_frame = tk.Frame(left_frame) +checkbox_frame.grid(row=10, column=0, pady=15, sticky='ew') +checkbox_frame2 = tk.Frame(left_frame) +checkbox_frame2.grid(row=10, column=1, padx=5, pady=15, sticky='ew') + +# 체크박스 프레임 내의 열 설정 +checkbox_frame.columnconfigure(0, weight=1) +checkbox_frame.columnconfigure(1, weight=1) +checkbox_frame.columnconfigure(2, weight=1) +checkbox_frame.columnconfigure(3, weight=1) +checkbox_frame.columnconfigure(4, weight=1) +checkbox_frame.columnconfigure(5, weight=1) + + +# "작가명 제거" 체크박스 +remove_artist_var = tk.IntVar() # 체크박스 상태를 저장할 변수 +check_remove_artist = tk.Checkbutton(checkbox_frame, text="작가명 제거", variable=remove_artist_var) +check_remove_artist.grid(row=0, column=0, sticky='ew') + +# "랜덤 작가 추가" 체크박스 +random_artist_var = tk.IntVar() +check_random_artist = tk.Checkbutton(checkbox_frame, text="랜덤 작가 추가", variable=random_artist_var) +check_random_artist.grid(row=0, column=1, sticky='ew') + +# 랜덤작가 관리 +random_artist_setting = tk.Button(checkbox_frame, text="랜덤 작가 관리", command=random_artist_management) #output_file_path +random_artist_setting.grid(row=0, column=2, sticky='ew') + +# "캐릭터 특징 제거" 체크박스 +rm_characteristic_var = tk.IntVar() +rm_characteristic_label = tk.Checkbutton(checkbox_frame, text="캐릭터 특징 제거", variable=rm_characteristic_var) +rm_characteristic_label.grid(row=0, column=3, sticky='ew') + +# 와일드 카드 +wildcard_var = tk.IntVar(value= 0) +check_wildcard = tk.Checkbutton(checkbox_frame, text="와일드카드(작가)", variable=wildcard_var, state='disabled') +check_wildcard.grid(row=1, column=0, sticky='ew') + +# 와일드 카드2 +wildcard_var2 = tk.IntVar(value= 0) +check_wildcard2 = tk.Checkbutton(checkbox_frame, text="와일드카드(캐릭터)", variable=wildcard_var2, state='disabled') +check_wildcard2.grid(row=1, column=1, sticky='ew') + +# 와일드 카드 설정 +widldcard_button = tk.Button(checkbox_frame, text="와일드카드 설정", command=open_wildcard_setting) #output_file_path +widldcard_button.grid(row=1, column=2, sticky='ew') + +#폴더 호출 버튼 +folder_button = tk.Button(checkbox_frame, text="저장 폴더", command=open_file_explorer) #output_file_path +folder_button.grid(row=1, column=3, padx=5, sticky='ew') + + +window.bind('', on_ctrl_enter) +window.bind('', on_shift_enter) + +NAI_ID = load_settings() +if not (entry_keyword.get()): entry_keyword.insert(0, "1girl") +if not (entry_fixed_prompt.get()): entry_fixed_prompt.insert(0, '1girl,') +if not (entry_fixed_prompt_after.get()): entry_fixed_prompt_after.insert(0, 'great quality, aesthetic, absurdres') +if not (entry_auto_hide.get()): entry_auto_hide.insert(0, 'monochrome, doujin cover, bad source, censored, bar censor') + +resolution1_frame = tk.Frame(left_frame) +resolution1_frame.grid(row=15, column=0, padx=5, pady=5, sticky='ew') +resolution2_frame = tk.Frame(left_frame) +resolution2_frame.grid(row=16, column=0, padx=5, pady=5, sticky='ew') +selected_resolution = tk.StringVar(value="1024 x 1024") + +tk.Radiobutton(resolution1_frame, text="1024 x 1024", variable=selected_resolution, value="1024 x 1024").grid(row=0, column=0, sticky='w') +tk.Radiobutton(resolution1_frame, text="960 x 1088", variable=selected_resolution, value="960 x 1088").grid(row=0, column=1, sticky='w') +tk.Radiobutton(resolution1_frame, text="896 x 1152", variable=selected_resolution, value="896 x 1152").grid(row=0, column=2, sticky='w') +tk.Radiobutton(resolution1_frame, text="832 x 1216", variable=selected_resolution, value="832 x 1216").grid(row=0, column=3, sticky='w') +tk.Radiobutton(resolution2_frame, text="1088 x 960", variable=selected_resolution, value="1088 x 960").grid(row=0, column=0, sticky='w') +tk.Radiobutton(resolution2_frame, text="1152 x 896", variable=selected_resolution, value="1152 x 896").grid(row=0, column=1, sticky='w') +tk.Radiobutton(resolution2_frame, text="1216 x 832", variable=selected_resolution, value="1216 x 832").grid(row=0, column=2, sticky='w') +selected_resolution.trace_add('write', on_resolution_change) + +sema_button_var = tk.IntVar(value=1) +sema_button = tk.Checkbutton(resolution1_frame, text="SEMA", variable=sema_button_var) +sema_button.grid(row=0, column=5, sticky='w') + +dyn_button_var = tk.IntVar() +dyn_button = tk.Checkbutton(resolution1_frame, text="+DYN", variable=dyn_button_var) +dyn_button.grid(row=0, column=6, sticky='w') + +rand_resolution_var = tk.IntVar() +rand_resolution_button = tk.Checkbutton(resolution1_frame, text="랜덤 해상도", variable=rand_resolution_var) +rand_resolution_button.grid(row=0, column=7, sticky='w') + +entry_CFG_value = tk.StringVar() +entry_CFG_label = tk.Label(resolution2_frame, text=" CFG Scale : ", justify=tk.LEFT) +entry_CFG_label.grid(row=0, column=4, sticky='w') +entry_CFG_value.set("5.0") + +entry_CFG = tk.Entry(resolution2_frame, width=5, textvariable=entry_CFG_value) +entry_CFG.grid(row=0, column=5, sticky='w') + +options = ["k_euler", "k_euler_ancestral", "k_dpmpp_2s_ancestral", "k_dpmpp_sde"] +selected_option = tk.StringVar() +selected_option.set(options[1]) +option_menu = tk.OptionMenu(resolution2_frame, selected_option, *options, command=on_option_select) +option_menu.grid(row=0, column=6, padx=5, sticky='ew') + +resolution3_frame = tk.Frame(left_frame) +resolution3_frame.grid(row=17, column=0, padx=5, pady=5, sticky='ew') +resolution3_frame.columnconfigure(0, weight=2) +resolution3_frame.columnconfigure(1, weight=1) +resolution3_frame.columnconfigure(2, weight=1) + +running_state = tk.Label(resolution3_frame, text="state : idle") +running_state.grid(row = 0, column= 0, sticky='w') +automation_state = tk.Label(resolution3_frame, text="Conditional automation not set (A.설정)") +automation_state.grid(row = 1, column= 0, sticky='w') +delay_offset_label = tk.Label(resolution3_frame, text="Dealy offset : "+str(delay_offset)) +delay_offset_label.grid(row = 1, column= 1, sticky='e') +entry_rescale_value = tk.StringVar() +entry_rescale_label = tk.Label(resolution3_frame, text=" Prompt Guidance Rescale : ", justify=tk.LEFT) +entry_rescale_label.grid(row=0, column=1, sticky='e') +entry_rescale_value.set("0.0") +entry_rescale = tk.Entry(resolution3_frame, width=5, textvariable=entry_rescale_value) +entry_rescale.grid(row=0, column=2, sticky='w') + +image_label = tk.Label(right_frame, relief='solid', borderwidth=1) +image_label.grid(row=0, column=0, rowspan=15, padx=10, pady=10, sticky="n") + +right_frame_area16 = tk.Frame(right_frame) +right_frame_area16.grid(row=16, column=0, padx=5, pady=5, sticky='ew') +entry_seed_value = tk.IntVar() +entry_seed_label = tk.Label(right_frame_area16, text=" seed : ", justify=tk.LEFT) +entry_seed_label.grid(row=0, column=0, sticky='ew') +entry_seed_value.set(last_generation_seed) +entry_seed = tk.Entry(right_frame_area16, width=15, textvariable=entry_seed_value) +entry_seed.grid(row=0, column=1, sticky='ew') +hold_seed_var = tk.IntVar(value=0) +hold_seed_button = tk.Checkbutton(right_frame_area16, text="시드고정", variable=hold_seed_var, command=on_hold_seed_button_click) +hold_seed_button.grid(row=0, column=2, sticky='ew') + +btn_add_low_freq = tk.Button(right_frame_area16, text="이미지를 클립보드에 복사", fg="blue", command=copy_image_to_clipboard) +btn_add_low_freq.grid(row=0, column=3, padx=5, pady=5, sticky='ew') + +show_fullscreen_btn = tk.Button(right_frame_area16, text="전체화면 보기 (ESC로 닫기)", command=show_fullscreen_image) +show_fullscreen_btn.grid(row=0, column=4, padx=5, pady=5, sticky='ew') + +# 하얀색 이미지 생성 +white_image = Image.new('RGB', (_size, _size), 'white') +white_photo = ImageTk.PhotoImage(white_image) + +# 라벨에 이미지 설정 +image_label.config(image=white_photo) +image_label.image = white_photo + +# 리스너 시작 +listener = keyboard.Listener(on_press=on_press, on_release=on_release) +listener.start() + +# 키보드 리스너 스레드 시작 +listener_thread = threading.Thread(target=listener.join, daemon=True) +listener_thread.start() + +Auto_login_check() + +window.protocol("WM_DELETE_WINDOW", exit_program) +window.mainloop() \ No newline at end of file