Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- modules/art_tools.py +173 -0
- modules/cl_tagger_module.py +297 -0
- modules/config_manager.py +243 -0
- modules/hydra_layers.py +191 -0
- modules/localization/languages.py +611 -0
- modules/managers/category_manager.py +167 -0
- modules/managers/localization_manager.py +31 -0
- modules/managers/rule_manager.py +186 -0
- modules/prompt_generator.py +413 -0
- modules/shared_state.py +35 -0
- modules/tagger.py +269 -0
- modules/tagger_refinement.py +270 -0
- modules/taggers/anime.py +91 -0
- modules/taggers/base.py +18 -0
- modules/taggers/cl.py +27 -0
- modules/taggers/gemini.py +30 -0
- modules/taggers/image_utils.py +126 -0
- modules/taggers/joint.py +121 -0
- modules/taggers/pixai.py +104 -0
- modules/tools.py +33 -0
- modules/tools_image.py +193 -0
- modules/tools_renaming.py +94 -0
- modules/tools_text.py +98 -0
- modules/ui/common.py +96 -0
- modules/ui/tab_batch.py +87 -0
- modules/ui/tab_dual.py +97 -0
- modules/ui/tab_settings.py +269 -0
- modules/ui/tab_single.py +148 -0
- modules/ui/tab_tools.py +69 -0
- modules/ui/tools/art_ui.py +50 -0
- modules/ui/tools/category_ui.py +82 -0
- modules/ui/tools/image_ui.py +123 -0
- modules/ui/tools/rule_ui.py +132 -0
- modules/ui/tools/text_ui.py +34 -0
- modules/utils/file_utils.py +22 -0
- modules/utils/tag_utils.py +30 -0
- modules/video_creator.py +263 -0
modules/art_tools.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/art_tools.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import math
|
| 5 |
+
import numpy as np
|
| 6 |
+
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageOps
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import io
|
| 9 |
+
|
| 10 |
+
# --- ADOPTABLE GRID MAKER ---
|
| 11 |
+
|
| 12 |
+
def hex_to_rgb(hex_color):
|
| 13 |
+
"""Hex color kodunu (ör: #FF0000) (255, 0, 0) tuple'ına çevirir."""
|
| 14 |
+
hex_color = hex_color.lstrip('#')
|
| 15 |
+
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) + (255,) # Alpha ekle
|
| 16 |
+
|
| 17 |
+
def create_grid_collage(files, columns, add_labels, label_type, start_number, bg_color_hex, title_text, banner_color_hex, title_color_hex):
|
| 18 |
+
"""
|
| 19 |
+
Adoptable Style Grid Collage Maker
|
| 20 |
+
"""
|
| 21 |
+
if not files:
|
| 22 |
+
return None, "⚠️ Lütfen resim dosyalarını seçin."
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
images = []
|
| 26 |
+
for file_obj in files:
|
| 27 |
+
try:
|
| 28 |
+
img = Image.open(file_obj.name).convert("RGBA")
|
| 29 |
+
images.append(img)
|
| 30 |
+
except:
|
| 31 |
+
continue
|
| 32 |
+
|
| 33 |
+
if not images:
|
| 34 |
+
return None, "⚠️ Geçerli resim bulunamadı."
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
count = len(images)
|
| 38 |
+
cols = int(columns)
|
| 39 |
+
rows = math.ceil(count / cols)
|
| 40 |
+
|
| 41 |
+
# 1. Boyut Standardizasyonu (İlk resme göre)
|
| 42 |
+
base_w, base_h = images[0].size
|
| 43 |
+
resized_images = []
|
| 44 |
+
for img in images:
|
| 45 |
+
if img.size != (base_w, base_h):
|
| 46 |
+
resized_images.append(img.resize((base_w, base_h), Image.Resampling.LANCZOS))
|
| 47 |
+
else:
|
| 48 |
+
resized_images.append(img)
|
| 49 |
+
|
| 50 |
+
# Tasarım Parametreleri
|
| 51 |
+
padding = 40 # Resimler arası boşluk biraz arttı
|
| 52 |
+
frame_width = 15 # Çerçeve kalınlığı arttı
|
| 53 |
+
|
| 54 |
+
# Renkler
|
| 55 |
+
bg_color = hex_to_rgb(bg_color_hex)
|
| 56 |
+
banner_color = hex_to_rgb(banner_color_hex)
|
| 57 |
+
title_color = hex_to_rgb(title_color_hex)
|
| 58 |
+
frame_color = title_color # Çerçeve rengi başlık rengiyle aynı olsun
|
| 59 |
+
|
| 60 |
+
# Canvas Boyutu (Önce hesapla)
|
| 61 |
+
grid_w = (cols * base_w) + ((cols + 1) * padding)
|
| 62 |
+
|
| 63 |
+
# Header Yüksekliği: Grid genişliğine orantılı olsun (Genişlik arttıkça yükseklik de artsın ki text büyüyebilsin)
|
| 64 |
+
# Min 250px, yoksa genişliğin %15'i
|
| 65 |
+
header_height = max(250, int(grid_w * 0.15))
|
| 66 |
+
|
| 67 |
+
grid_h = header_height + (rows * base_h) + ((rows + 1) * padding)
|
| 68 |
+
|
| 69 |
+
grid_img = Image.new("RGBA", (grid_w, grid_h), bg_color)
|
| 70 |
+
draw = ImageDraw.Draw(grid_img)
|
| 71 |
+
|
| 72 |
+
# --- HEADER (BANNER) ÇİZİMİ ---
|
| 73 |
+
draw.rectangle([(0, 0), (grid_w, header_height)], fill=banner_color)
|
| 74 |
+
draw.rectangle([(0, header_height-15), (grid_w, header_height)], fill=frame_color)
|
| 75 |
+
|
| 76 |
+
# Başlangıç Font Boyutu (Çok daha büyük başlıyoruz - Grid genişliğine orantılı)
|
| 77 |
+
font_title_size = int(grid_w * 0.1) # Genişliğin %10'u ile başla
|
| 78 |
+
|
| 79 |
+
# --- BAŞLIK FONTU BÜYÜKLÜĞÜNÜ AYARLA (Agresif Scaling) ---
|
| 80 |
+
target_width = grid_w * 0.95 # Banner genişliğinin %95'ini doldurmalı
|
| 81 |
+
max_height_text = header_height * 0.8 # Yüksekliğin %80'ini geçmesin (Hava kalsın)
|
| 82 |
+
|
| 83 |
+
try:
|
| 84 |
+
font_path = "timesbd.ttf"
|
| 85 |
+
font_title = ImageFont.truetype(font_path, font_title_size)
|
| 86 |
+
except:
|
| 87 |
+
font_path = None
|
| 88 |
+
font_title = ImageFont.load_default()
|
| 89 |
+
|
| 90 |
+
if font_path:
|
| 91 |
+
# İterasyonla en uygun boyutu bul
|
| 92 |
+
for _ in range(50): # Sonsuz döngüden kaçınmak için limitli
|
| 93 |
+
bbox_title = draw.textbbox((0, 0), title_text, font=font_title)
|
| 94 |
+
|
| 95 |
+
w_total = (bbox_title[2] - bbox_title[0])
|
| 96 |
+
h_max = bbox_title[3] - bbox_title[1]
|
| 97 |
+
|
| 98 |
+
# Çok büyükse küçült
|
| 99 |
+
if w_total > target_width or h_max > max_height_text:
|
| 100 |
+
font_title_size -= 5
|
| 101 |
+
if font_title_size < 20: break
|
| 102 |
+
font_title = ImageFont.truetype(font_path, font_title_size)
|
| 103 |
+
# Çok küçükse büyüt (Eğer dikeyde ve yatayda yer varsa)
|
| 104 |
+
elif w_total < target_width * 0.9 and h_max < max_height_text * 0.9:
|
| 105 |
+
font_title_size += 5
|
| 106 |
+
font_title = ImageFont.truetype(font_path, font_title_size)
|
| 107 |
+
else:
|
| 108 |
+
break # Tamamdır
|
| 109 |
+
|
| 110 |
+
# Son Ölçümler & Çizim
|
| 111 |
+
bbox_title = draw.textbbox((0, 0), title_text, font=font_title)
|
| 112 |
+
w_title = bbox_title[2] - bbox_title[0]
|
| 113 |
+
h_title = bbox_title[3] - bbox_title[1]
|
| 114 |
+
|
| 115 |
+
start_x_text = (grid_w - w_title) // 2
|
| 116 |
+
bg_center_y = header_height // 2
|
| 117 |
+
|
| 118 |
+
# Başlık çiz
|
| 119 |
+
draw.text((start_x_text, bg_center_y - h_title//2), title_text, font=font_title, fill=title_color, stroke_width=int(font_title_size*0.02), stroke_fill="black")
|
| 120 |
+
|
| 121 |
+
# --- RESİMLERİ YERLEŞTİR ---
|
| 122 |
+
# Numara Fontunu Resim Boyutuna Göre Ayarla (Çok Büyük)
|
| 123 |
+
try:
|
| 124 |
+
num_font_size = int(base_w * 0.25) # Resim genişliğinin %25'i kadar (Kocaman)
|
| 125 |
+
font_num = ImageFont.truetype("impact.ttf", num_font_size)
|
| 126 |
+
except:
|
| 127 |
+
font_num = ImageFont.load_default()
|
| 128 |
+
|
| 129 |
+
current_num = int(start_number)
|
| 130 |
+
|
| 131 |
+
for i, img in enumerate(resized_images):
|
| 132 |
+
r = i // cols
|
| 133 |
+
c = i % cols
|
| 134 |
+
|
| 135 |
+
x = padding + (c * (base_w + padding))
|
| 136 |
+
y = header_height + padding + (r * (base_h + padding))
|
| 137 |
+
|
| 138 |
+
# Kalın Çerçeve
|
| 139 |
+
draw.rectangle(
|
| 140 |
+
[(x - frame_width, y - frame_width),
|
| 141 |
+
(x + base_w + frame_width, y + base_h + frame_width)],
|
| 142 |
+
fill=None, outline=frame_color, width=frame_width
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
# Resim
|
| 146 |
+
grid_img.paste(img, (x, y), mask=img)
|
| 147 |
+
|
| 148 |
+
# --- OVERLAY NUMARA (DEVASA) ---
|
| 149 |
+
if add_labels:
|
| 150 |
+
if str(label_type).startswith("Sadece"):
|
| 151 |
+
num_str = f"{current_num}"
|
| 152 |
+
else:
|
| 153 |
+
num_str = f"#{current_num}"
|
| 154 |
+
current_num += 1
|
| 155 |
+
|
| 156 |
+
bbox_num = draw.textbbox((0, 0), num_str, font=font_num)
|
| 157 |
+
w_num = bbox_num[2] - bbox_num[0]
|
| 158 |
+
h_num = bbox_num[3] - bbox_num[1]
|
| 159 |
+
|
| 160 |
+
# Ortala ve Biraz daha yukarı al (Resmin altından %10 yukarıda)
|
| 161 |
+
num_x = x + (base_w - w_num) // 2
|
| 162 |
+
num_y = y + base_h - h_num - int(base_h * 0.05) - 20
|
| 163 |
+
|
| 164 |
+
# Çok Kalın Outline
|
| 165 |
+
outline_width = int(num_font_size * 0.08) # Font boyutuna göre outline kalınlığı
|
| 166 |
+
draw.text((num_x, num_y), num_str, font=font_num, fill="white", stroke_width=outline_width, stroke_fill="black")
|
| 167 |
+
|
| 168 |
+
return grid_img, "✅ Adoptable Grid oluşturuldu!"
|
| 169 |
+
|
| 170 |
+
except Exception as e:
|
| 171 |
+
import traceback
|
| 172 |
+
traceback.print_exc()
|
| 173 |
+
return None, f"❌ Hata: {str(e)}"
|
modules/cl_tagger_module.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/cl_tagger_module.py
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import onnxruntime as ort
|
| 8 |
+
from huggingface_hub import hf_hub_download
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from typing import Dict
|
| 11 |
+
|
| 12 |
+
# --- Data Classes ---
|
| 13 |
+
@dataclass
|
| 14 |
+
class LabelData:
|
| 15 |
+
names: list[str]
|
| 16 |
+
rating: list[np.int64]
|
| 17 |
+
general: list[np.int64]
|
| 18 |
+
artist: list[np.int64]
|
| 19 |
+
character: list[np.int64]
|
| 20 |
+
copyright: list[np.int64]
|
| 21 |
+
meta: list[np.int64]
|
| 22 |
+
quality: list[np.int64]
|
| 23 |
+
model: list[np.int64]
|
| 24 |
+
|
| 25 |
+
# --- Helper Functions ---
|
| 26 |
+
def pil_ensure_rgb(image: Image.Image) -> Image.Image:
|
| 27 |
+
if image.mode not in ["RGB", "RGBA"]:
|
| 28 |
+
image = image.convert("RGBA") if "transparency" in image.info else image.convert("RGB")
|
| 29 |
+
if image.mode == "RGBA":
|
| 30 |
+
background = Image.new("RGB", image.size, (255, 255, 255))
|
| 31 |
+
background.paste(image, mask=image.split()[3])
|
| 32 |
+
image = background
|
| 33 |
+
return image
|
| 34 |
+
|
| 35 |
+
def pil_pad_square(image: Image.Image) -> Image.Image:
|
| 36 |
+
width, height = image.size
|
| 37 |
+
if width == height: return image
|
| 38 |
+
new_size = max(width, height)
|
| 39 |
+
new_image = Image.new(image.mode, (new_size, new_size), (255, 255, 255))
|
| 40 |
+
paste_position = ((new_size - width) // 2, (new_size - height) // 2)
|
| 41 |
+
new_image.paste(image, paste_position)
|
| 42 |
+
return new_image
|
| 43 |
+
|
| 44 |
+
def load_tag_mapping(mapping_path):
|
| 45 |
+
with open(mapping_path, 'r', encoding='utf-8') as f: tag_mapping_data = json.load(f)
|
| 46 |
+
if isinstance(tag_mapping_data, dict) and "idx_to_tag" in tag_mapping_data:
|
| 47 |
+
idx_to_tag = {int(k): v for k, v in tag_mapping_data["idx_to_tag"].items()}
|
| 48 |
+
tag_to_category = tag_mapping_data["tag_to_category"]
|
| 49 |
+
elif isinstance(tag_mapping_data, dict):
|
| 50 |
+
try:
|
| 51 |
+
tag_mapping_data_int_keys = {int(k): v for k, v in tag_mapping_data.items()}
|
| 52 |
+
idx_to_tag = {idx: data['tag'] for idx, data in tag_mapping_data_int_keys.items()}
|
| 53 |
+
tag_to_category = {data['tag']: data['category'] for data in tag_mapping_data_int_keys.values()}
|
| 54 |
+
except (KeyError, ValueError) as e:
|
| 55 |
+
raise ValueError(f"Unsupported tag mapping format (dict): {e}. Expected int keys with 'tag' and 'category'.")
|
| 56 |
+
else:
|
| 57 |
+
raise ValueError("Unsupported tag mapping format: Expected a dictionary.")
|
| 58 |
+
|
| 59 |
+
names = [None] * (max(idx_to_tag.keys()) + 1)
|
| 60 |
+
rating, general, artist, character, copyright, meta, quality, model_name = [], [], [], [], [], [], [], []
|
| 61 |
+
for idx, tag in idx_to_tag.items():
|
| 62 |
+
if idx >= len(names): names.extend([None] * (idx - len(names) + 1))
|
| 63 |
+
names[idx] = tag
|
| 64 |
+
category = tag_to_category.get(tag, 'Unknown')
|
| 65 |
+
idx_int = int(idx)
|
| 66 |
+
if category == 'Rating': rating.append(idx_int)
|
| 67 |
+
elif category == 'General': general.append(idx_int)
|
| 68 |
+
elif category == 'Artist': artist.append(idx_int)
|
| 69 |
+
elif category == 'Character': character.append(idx_int)
|
| 70 |
+
elif category == 'Copyright': copyright.append(idx_int)
|
| 71 |
+
elif category == 'Meta': meta.append(idx_int)
|
| 72 |
+
elif category == 'Quality': quality.append(idx_int)
|
| 73 |
+
elif category == 'Model': model_name.append(idx_int)
|
| 74 |
+
|
| 75 |
+
return LabelData(names=names, rating=np.array(rating, dtype=np.int64), general=np.array(general, dtype=np.int64), artist=np.array(artist, dtype=np.int64),
|
| 76 |
+
character=np.array(character, dtype=np.int64), copyright=np.array(copyright, dtype=np.int64), meta=np.array(meta, dtype=np.int64), quality=np.array(quality, dtype=np.int64), model=np.array(model_name, dtype=np.int64)), idx_to_tag, tag_to_category
|
| 77 |
+
|
| 78 |
+
def preprocess_image(image: Image.Image, target_size=(448, 448)):
|
| 79 |
+
image = pil_ensure_rgb(image)
|
| 80 |
+
image = pil_pad_square(image)
|
| 81 |
+
image_resized = image.resize(target_size, Image.BICUBIC)
|
| 82 |
+
img_array = np.array(image_resized, dtype=np.float32) / 255.0
|
| 83 |
+
img_array = img_array.transpose(2, 0, 1) # HWC -> CHW
|
| 84 |
+
img_array = img_array[::-1, :, :] # BGR conversion
|
| 85 |
+
mean = np.array([0.5, 0.5, 0.5], dtype=np.float32).reshape(3, 1, 1)
|
| 86 |
+
std = np.array([0.5, 0.5, 0.5], dtype=np.float32).reshape(3, 1, 1)
|
| 87 |
+
img_array = (img_array - mean) / std
|
| 88 |
+
img_array = np.expand_dims(img_array, axis=0) # Add batch dimension
|
| 89 |
+
return image, img_array
|
| 90 |
+
|
| 91 |
+
def get_tags(probs, labels: LabelData, gen_threshold, char_threshold):
|
| 92 |
+
result = {
|
| 93 |
+
"rating": [], "general": [], "character": [], "copyright": [],
|
| 94 |
+
"artist": [], "meta": [], "quality": [], "model": []
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
# Rating (select max)
|
| 98 |
+
if len(labels.rating) > 0:
|
| 99 |
+
valid_indices = labels.rating[labels.rating < len(probs)]
|
| 100 |
+
if len(valid_indices) > 0:
|
| 101 |
+
rating_probs = probs[valid_indices]
|
| 102 |
+
if len(rating_probs) > 0:
|
| 103 |
+
rating_idx_local = np.argmax(rating_probs)
|
| 104 |
+
rating_idx_global = valid_indices[rating_idx_local]
|
| 105 |
+
if rating_idx_global < len(labels.names) and labels.names[rating_idx_global] is not None:
|
| 106 |
+
rating_name = labels.names[rating_idx_global]
|
| 107 |
+
rating_conf = float(rating_probs[rating_idx_local])
|
| 108 |
+
result["rating"].append((rating_name, rating_conf))
|
| 109 |
+
|
| 110 |
+
# Quality (select max)
|
| 111 |
+
if len(labels.quality) > 0:
|
| 112 |
+
valid_indices = labels.quality[labels.quality < len(probs)]
|
| 113 |
+
if len(valid_indices) > 0:
|
| 114 |
+
quality_probs = probs[valid_indices]
|
| 115 |
+
if len(quality_probs) > 0:
|
| 116 |
+
quality_idx_local = np.argmax(quality_probs)
|
| 117 |
+
quality_idx_global = valid_indices[quality_idx_local]
|
| 118 |
+
if quality_idx_global < len(labels.names) and labels.names[quality_idx_global] is not None:
|
| 119 |
+
quality_name = labels.names[quality_idx_global]
|
| 120 |
+
quality_conf = float(quality_probs[quality_idx_local])
|
| 121 |
+
result["quality"].append((quality_name, quality_conf))
|
| 122 |
+
|
| 123 |
+
# Threshold-based categories
|
| 124 |
+
category_map = {
|
| 125 |
+
"general": (labels.general, gen_threshold),
|
| 126 |
+
"character": (labels.character, char_threshold),
|
| 127 |
+
"copyright": (labels.copyright, char_threshold),
|
| 128 |
+
"artist": (labels.artist, char_threshold),
|
| 129 |
+
"meta": (labels.meta, gen_threshold),
|
| 130 |
+
"model": (labels.model, gen_threshold)
|
| 131 |
+
}
|
| 132 |
+
for category, (indices, threshold) in category_map.items():
|
| 133 |
+
if len(indices) > 0:
|
| 134 |
+
valid_indices = indices[(indices < len(probs))]
|
| 135 |
+
if len(valid_indices) > 0:
|
| 136 |
+
category_probs = probs[valid_indices]
|
| 137 |
+
mask = category_probs >= threshold
|
| 138 |
+
selected_indices_local = np.where(mask)[0]
|
| 139 |
+
if len(selected_indices_local) > 0:
|
| 140 |
+
selected_indices_global = valid_indices[selected_indices_local]
|
| 141 |
+
selected_probs = category_probs[selected_indices_local]
|
| 142 |
+
for idx_global, prob_val in zip(selected_indices_global, selected_probs):
|
| 143 |
+
if idx_global < len(labels.names) and labels.names[idx_global] is not None:
|
| 144 |
+
result[category].append((labels.names[idx_global], float(prob_val)))
|
| 145 |
+
|
| 146 |
+
for k in result:
|
| 147 |
+
result[k] = sorted(result[k], key=lambda x: x[1], reverse=True)
|
| 148 |
+
return result
|
| 149 |
+
|
| 150 |
+
# --- Constants ---
|
| 151 |
+
REPO_ID = "cella110n/cl_tagger"
|
| 152 |
+
MODEL_OPTIONS = {
|
| 153 |
+
"cl_tagger_1_00": "cl_tagger_1_00/model_optimized.onnx"
|
| 154 |
+
}
|
| 155 |
+
DEFAULT_MODEL = "cl_tagger_1_00"
|
| 156 |
+
CACHE_DIR = "./models/model_cache"
|
| 157 |
+
|
| 158 |
+
class CLTagger:
|
| 159 |
+
def __init__(self):
|
| 160 |
+
self.onnx_model_path = None
|
| 161 |
+
self.tag_mapping_path = None
|
| 162 |
+
self.labels_data = None
|
| 163 |
+
self.idx_to_tag = None
|
| 164 |
+
self.tag_to_category = None
|
| 165 |
+
self.current_model = None
|
| 166 |
+
self.session = None
|
| 167 |
+
self.initialize_onnx_paths(DEFAULT_MODEL)
|
| 168 |
+
|
| 169 |
+
def initialize_onnx_paths(self, model_choice=DEFAULT_MODEL):
|
| 170 |
+
if not model_choice in MODEL_OPTIONS:
|
| 171 |
+
print(f"Invalid model choice: {model_choice}, falling back to default: {DEFAULT_MODEL}")
|
| 172 |
+
model_choice = DEFAULT_MODEL
|
| 173 |
+
|
| 174 |
+
self.current_model = model_choice
|
| 175 |
+
model_dir = model_choice
|
| 176 |
+
onnx_filename = MODEL_OPTIONS[model_choice]
|
| 177 |
+
tag_mapping_filename = f"{model_dir}/tag_mapping.json"
|
| 178 |
+
|
| 179 |
+
print(f"Initializing ONNX paths and labels for model: {model_choice}...")
|
| 180 |
+
hf_token = os.environ.get("HF_TOKEN")
|
| 181 |
+
|
| 182 |
+
try:
|
| 183 |
+
print(f"Attempting to download ONNX model: {onnx_filename}")
|
| 184 |
+
self.onnx_model_path = hf_hub_download(
|
| 185 |
+
repo_id=REPO_ID,
|
| 186 |
+
filename=onnx_filename,
|
| 187 |
+
cache_dir=CACHE_DIR,
|
| 188 |
+
token=hf_token,
|
| 189 |
+
force_download=False
|
| 190 |
+
)
|
| 191 |
+
print(f"ONNX model path: {self.onnx_model_path}")
|
| 192 |
+
|
| 193 |
+
print(f"Attempting to download Tag mapping: {tag_mapping_filename}")
|
| 194 |
+
self.tag_mapping_path = hf_hub_download(
|
| 195 |
+
repo_id=REPO_ID,
|
| 196 |
+
filename=tag_mapping_filename,
|
| 197 |
+
cache_dir=CACHE_DIR,
|
| 198 |
+
token=hf_token,
|
| 199 |
+
force_download=False
|
| 200 |
+
)
|
| 201 |
+
print(f"Tag mapping path: {self.tag_mapping_path}")
|
| 202 |
+
|
| 203 |
+
print("Loading labels from mapping...")
|
| 204 |
+
self.labels_data, self.idx_to_tag, self.tag_to_category = load_tag_mapping(self.tag_mapping_path)
|
| 205 |
+
print(f"Labels loaded. Count: {len(self.labels_data.names)}")
|
| 206 |
+
|
| 207 |
+
print("Creating ONNX Runtime session (CPUExecutionProvider)...")
|
| 208 |
+
self.session = ort.InferenceSession(
|
| 209 |
+
self.onnx_model_path,
|
| 210 |
+
providers=["CPUExecutionProvider"]
|
| 211 |
+
)
|
| 212 |
+
print("ONNX Runtime session ready.")
|
| 213 |
+
return True
|
| 214 |
+
|
| 215 |
+
except Exception as e:
|
| 216 |
+
print(f"Error during initialization of CLTagger: {e}")
|
| 217 |
+
import traceback; traceback.print_exc()
|
| 218 |
+
self.onnx_model_path = None
|
| 219 |
+
self.tag_mapping_path = None
|
| 220 |
+
self.labels_data = None
|
| 221 |
+
self.idx_to_tag = None
|
| 222 |
+
self.tag_to_category = None
|
| 223 |
+
self.current_model = None
|
| 224 |
+
self.session = None
|
| 225 |
+
raise
|
| 226 |
+
|
| 227 |
+
def predict(self, image_input: Image.Image, gen_threshold: float, char_threshold: float) -> tuple[str, Image.Image | None, Dict]:
|
| 228 |
+
"""
|
| 229 |
+
Tahmin işlemini gerçekleştirir.
|
| 230 |
+
:param image_input: Giriş PIL Image nesnesi.
|
| 231 |
+
:param gen_threshold: Genel/Meta/Model etiketleri için eşik değeri.
|
| 232 |
+
:param char_threshold: Karakter/Telif Hakkı/Sanatçı etiketleri için eşik değeri.
|
| 233 |
+
:return: (etiketler_string, None, ham_tahminler_sözlüğü)
|
| 234 |
+
"""
|
| 235 |
+
|
| 236 |
+
if self.onnx_model_path is None or self.labels_data is None or self.session is None:
|
| 237 |
+
message = "Error: CLTagger not initialized. Check startup logs."
|
| 238 |
+
print(message)
|
| 239 |
+
return message, None, {}
|
| 240 |
+
|
| 241 |
+
try:
|
| 242 |
+
original_pil_image, input_tensor = preprocess_image(image_input)
|
| 243 |
+
input_tensor = input_tensor.astype(np.float32)
|
| 244 |
+
|
| 245 |
+
except Exception as e:
|
| 246 |
+
message = f"Error processing input image in CLTagger: {e}"
|
| 247 |
+
print(message)
|
| 248 |
+
return message, None, {}
|
| 249 |
+
|
| 250 |
+
try:
|
| 251 |
+
input_name = self.session.get_inputs()[0].name
|
| 252 |
+
output_name = self.session.get_outputs()[0].name
|
| 253 |
+
outputs = self.session.run([output_name], {input_name: input_tensor})[0]
|
| 254 |
+
|
| 255 |
+
if np.isnan(outputs).any() or np.isinf(outputs).any():
|
| 256 |
+
outputs = np.nan_to_num(outputs, nan=0.0, posinf=1.0, neginf=0.0)
|
| 257 |
+
|
| 258 |
+
def stable_sigmoid(x):
|
| 259 |
+
return 1 / (1 + np.exp(-np.clip(x, -30, 30)))
|
| 260 |
+
probs = stable_sigmoid(outputs[0])
|
| 261 |
+
|
| 262 |
+
except Exception as e:
|
| 263 |
+
message = f"Error during ONNX inference in CLTagger: {e}"
|
| 264 |
+
print(message)
|
| 265 |
+
import traceback; traceback.print_exc()
|
| 266 |
+
return message, None, {}
|
| 267 |
+
|
| 268 |
+
try:
|
| 269 |
+
predictions = get_tags(probs, self.labels_data, gen_threshold, char_threshold)
|
| 270 |
+
|
| 271 |
+
output_tags = []
|
| 272 |
+
if predictions.get("rating"): output_tags.append(predictions["rating"][0][0].replace("_", " "))
|
| 273 |
+
if predictions.get("quality"): output_tags.append(predictions["quality"][0][0].replace("_", " "))
|
| 274 |
+
for category in ["artist", "character", "copyright", "general", "meta", "model"]:
|
| 275 |
+
tags_in_category = predictions.get(category, [])
|
| 276 |
+
for tag, prob in tags_in_category:
|
| 277 |
+
if category == "meta" and any(p in tag.lower() for p in ['id', 'commentary', 'request', 'mismatch']):
|
| 278 |
+
continue
|
| 279 |
+
output_tags.append(tag.replace("_", " "))
|
| 280 |
+
output_text = ", ".join(output_tags)
|
| 281 |
+
|
| 282 |
+
# Visualization removed. Returning None for the image placeholder to maintain signature compatibility.
|
| 283 |
+
return output_text, None, predictions
|
| 284 |
+
|
| 285 |
+
except Exception as e:
|
| 286 |
+
message = f"Error during post-processing in CLTagger: {e}"
|
| 287 |
+
print(message)
|
| 288 |
+
import traceback; traceback.print_exc()
|
| 289 |
+
return message, None, {}
|
| 290 |
+
|
| 291 |
+
# Module initialization
|
| 292 |
+
if __name__ == "__main__":
|
| 293 |
+
try:
|
| 294 |
+
tagger_instance = CLTagger()
|
| 295 |
+
print("CLTagger module loaded successfully for testing.")
|
| 296 |
+
except Exception as e:
|
| 297 |
+
print(f"Failed to initialize CLTagger module: {e}")
|
modules/config_manager.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/config_manager.py
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
import gradio as gr
|
| 5 |
+
|
| 6 |
+
# --- Yapılandırma ve Dosya Yolları ---
|
| 7 |
+
CONFIG_FILE = "data/configs/config.json"
|
| 8 |
+
TEMPLATES_FILE = "data/configs/renaming_templates.json" # Yeni Şablon Dosyası
|
| 9 |
+
|
| 10 |
+
# --- ŞABLON YÖNETİMİ FONKSİYONLARI (YENİ) ---
|
| 11 |
+
def load_renaming_templates():
|
| 12 |
+
default_templates = ["Adoptable {Number} (Female)", "{Number}", "Character_{Number}", "Design #{Number}"]
|
| 13 |
+
if os.path.exists(TEMPLATES_FILE):
|
| 14 |
+
try:
|
| 15 |
+
with open(TEMPLATES_FILE, "r", encoding="utf-8") as f:
|
| 16 |
+
loaded = json.load(f)
|
| 17 |
+
if isinstance(loaded, list) and loaded: return loaded
|
| 18 |
+
except: pass
|
| 19 |
+
return default_templates
|
| 20 |
+
|
| 21 |
+
def save_renaming_templates_to_file(templates):
|
| 22 |
+
try:
|
| 23 |
+
os.makedirs(os.path.dirname(TEMPLATES_FILE), exist_ok=True)
|
| 24 |
+
with open(TEMPLATES_FILE, "w", encoding="utf-8") as f:
|
| 25 |
+
json.dump(templates, f, indent=4, ensure_ascii=False)
|
| 26 |
+
except Exception as e: print(f"Şablon kaydetme hatası: {e}")
|
| 27 |
+
|
| 28 |
+
def add_renaming_template(new_template, current_list):
|
| 29 |
+
if not new_template: return gr.update(), current_list
|
| 30 |
+
if "{Number}" not in new_template:
|
| 31 |
+
return gr.update(value="❌ Şablon '{Number}' içermelidir!"), current_list
|
| 32 |
+
|
| 33 |
+
if new_template not in current_list:
|
| 34 |
+
current_list.append(new_template)
|
| 35 |
+
save_renaming_templates_to_file(current_list)
|
| 36 |
+
# Dropdown'ı güncelle ve yeni ekleneni seçili yap
|
| 37 |
+
return gr.update(choices=current_list, value=new_template), current_list
|
| 38 |
+
return gr.update(value=new_template), current_list
|
| 39 |
+
|
| 40 |
+
def delete_renaming_template(selected_template, current_list):
|
| 41 |
+
if selected_template in current_list:
|
| 42 |
+
current_list.remove(selected_template)
|
| 43 |
+
save_renaming_templates_to_file(current_list)
|
| 44 |
+
new_val = current_list[0] if current_list else ""
|
| 45 |
+
return gr.update(choices=current_list, value=new_val), current_list
|
| 46 |
+
return gr.update(), current_list
|
| 47 |
+
|
| 48 |
+
# --- Yapılandırma Yönetimi Fonksiyonları ---
|
| 49 |
+
|
| 50 |
+
def save_config(config_data):
|
| 51 |
+
"""
|
| 52 |
+
Klasör yollarını ve tüm uygulama ayarlarını bir JSON dosyasına kaydeder.
|
| 53 |
+
"""
|
| 54 |
+
try:
|
| 55 |
+
os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
|
| 56 |
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
| 57 |
+
json.dump(config_data, f, indent=4)
|
| 58 |
+
except IOError as e:
|
| 59 |
+
print(f"Yapılandırma dosyası yazma hatası: {e}")
|
| 60 |
+
|
| 61 |
+
def load_config():
|
| 62 |
+
"""
|
| 63 |
+
Tüm uygulama ayarlarını bir JSON dosyasından yükler veya varsayılan değerleri döndürür.
|
| 64 |
+
"""
|
| 65 |
+
default_config = {
|
| 66 |
+
"folder_paths": [],
|
| 67 |
+
"general_settings": {
|
| 68 |
+
"device": "Auto",
|
| 69 |
+
"language": "tr",
|
| 70 |
+
"theme": "Default"
|
| 71 |
+
},
|
| 72 |
+
"global_tagger_settings": {
|
| 73 |
+
"use_joint": True,
|
| 74 |
+
"joint_thresh": 0.25,
|
| 75 |
+
"use_cl_tagger": True,
|
| 76 |
+
"cl_gen_thresh": 0.55,
|
| 77 |
+
"cl_char_thresh": 0.60,
|
| 78 |
+
"use_pixai_tagger": False,
|
| 79 |
+
"pixai_general_thresh": 0.30,
|
| 80 |
+
"pixai_char_thresh": 0.85,
|
| 81 |
+
"use_animetagger": False,
|
| 82 |
+
"animetagger_model": "MobileNet V4 (Hızlı)",
|
| 83 |
+
"animetagger_thresh": 0.35,
|
| 84 |
+
|
| 85 |
+
# Gemini Ayarları
|
| 86 |
+
"use_gemini": False,
|
| 87 |
+
"gemini_api_key": "",
|
| 88 |
+
"gemini_mode": "Vision",
|
| 89 |
+
"gemini_model": "gemini-2.5-flash",
|
| 90 |
+
"gemini_prompt_vision": "Describe this image in a detailed, natural language caption for an AI image generator dataset. Focus on the main subject, clothing, pose, and background. Do not use bullet points.",
|
| 91 |
+
"gemini_prompt_tags": "Create a fluent, natural language caption based on the provided tags. Focus on describing the scene vividly.",
|
| 92 |
+
"gemini_prompt_hybrid": "Describe this image using the visual details and refine the description with the provided tags for accuracy. Focus on a cohesive narrative.",
|
| 93 |
+
"gemini_system_instruction": "You are a helpful assistant that generates detailed and accurate image captions for AI image generation datasets.",
|
| 94 |
+
|
| 95 |
+
# Kural Dosyaları
|
| 96 |
+
"replacement_file": "",
|
| 97 |
+
"synonym_file": "",
|
| 98 |
+
"addition_file": "",
|
| 99 |
+
"sort_order": "Alfabetik",
|
| 100 |
+
"context_weight": 0.0,
|
| 101 |
+
|
| 102 |
+
# Ayrı kategorizasyon ayarları
|
| 103 |
+
"tekil_enable_categorization": False,
|
| 104 |
+
"tekil_selected_categories": [],
|
| 105 |
+
"toplu_enable_categorization": False,
|
| 106 |
+
"toplu_selected_categories": [],
|
| 107 |
+
"dual1_enable_categorization": False,
|
| 108 |
+
"dual1_selected_categories": [],
|
| 109 |
+
"dual2_enable_categorization": False,
|
| 110 |
+
"dual2_selected_categories": []
|
| 111 |
+
},
|
| 112 |
+
"image_tools_settings": {
|
| 113 |
+
"folder_paths": [],
|
| 114 |
+
"scale_factor": 1.0,
|
| 115 |
+
"selected_renaming_file": "",
|
| 116 |
+
"renaming_type": "Rastgele",
|
| 117 |
+
"watermark_text": "Örnek Filigran",
|
| 118 |
+
"watermark_opacity": 0.35,
|
| 119 |
+
"watermark_font_ratio": 0.05,
|
| 120 |
+
"watermark_angle": -45,
|
| 121 |
+
"renaming_templates": [],
|
| 122 |
+
"brightness_level": 0,
|
| 123 |
+
"contrast_level": 0,
|
| 124 |
+
"denoise_level": 0,
|
| 125 |
+
"sharpen_amount": 0.0,
|
| 126 |
+
"sequential_pattern": "Adoptable #Number",
|
| 127 |
+
"sequential_start_number": 1,
|
| 128 |
+
"sequential_digit_count": 4
|
| 129 |
+
},
|
| 130 |
+
"art_tools_settings": {
|
| 131 |
+
"grid_cols": 3,
|
| 132 |
+
"grid_bg_color": "#000000",
|
| 133 |
+
"grid_title_text": "ADOPTABLE SALE!",
|
| 134 |
+
"grid_banner_color": "#000000",
|
| 135 |
+
"grid_title_color": "#FFD700",
|
| 136 |
+
"grid_add_labels": True,
|
| 137 |
+
"grid_label_type": "Numara (#1, #2...)",
|
| 138 |
+
"grid_start_num": 1
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
if os.path.exists(CONFIG_FILE):
|
| 144 |
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
| 145 |
+
loaded_config = json.load(f)
|
| 146 |
+
def update_dict(d, u):
|
| 147 |
+
for k, v in u.items():
|
| 148 |
+
if isinstance(v, dict):
|
| 149 |
+
# Eğer u[k] dict değilse (eski config uyumsuzluğu), d[k]'yı koru veya override et?
|
| 150 |
+
# Basitçe d[k] var ve dict ise recursive, değilse direk al.
|
| 151 |
+
d[k] = update_dict(d.get(k, {}), v)
|
| 152 |
+
else:
|
| 153 |
+
d[k] = v
|
| 154 |
+
return d
|
| 155 |
+
return update_dict(default_config.copy(), loaded_config)
|
| 156 |
+
except (IOError, json.JSONDecodeError) as e:
|
| 157 |
+
print(f"Yapılandırma dosyası okuma hatası veya dosya yok: {e}. Varsayılanlar yükleniyor.")
|
| 158 |
+
return default_config
|
| 159 |
+
|
| 160 |
+
# --- Ayarları Kaydetme Fonksiyonları ---
|
| 161 |
+
|
| 162 |
+
def save_global_tagger_settings(
|
| 163 |
+
device, language, theme,
|
| 164 |
+
use_joint, joint_thresh,
|
| 165 |
+
use_cl_tagger, cl_gen_thresh, cl_char_thresh,
|
| 166 |
+
use_pixai_tagger, pixai_general_thresh, pixai_char_thresh,
|
| 167 |
+
use_animetagger, animetagger_model, animetagger_thresh,
|
| 168 |
+
# Gemini Parametreleri
|
| 169 |
+
use_gemini, gemini_api_key, gemini_mode, gemini_model,
|
| 170 |
+
gemini_prompt_vision, gemini_prompt_tags, gemini_prompt_hybrid,
|
| 171 |
+
gemini_system_instruction,
|
| 172 |
+
# Kural Dosyaları
|
| 173 |
+
replacement_file, synonym_file, addition_file,
|
| 174 |
+
sort_order,
|
| 175 |
+
context_weight,
|
| 176 |
+
# Kategorizasyon
|
| 177 |
+
tekil_enable, tekil_cats,
|
| 178 |
+
toplu_enable, toplu_cats,
|
| 179 |
+
dual1_enable, dual1_cats,
|
| 180 |
+
dual2_enable, dual2_cats
|
| 181 |
+
):
|
| 182 |
+
config_data = load_config()
|
| 183 |
+
config_data["general_settings"] = {"device": device, "language": language, "theme": theme}
|
| 184 |
+
config_data["global_tagger_settings"] = {
|
| 185 |
+
"use_joint": use_joint, "joint_thresh": joint_thresh,
|
| 186 |
+
"use_cl_tagger": use_cl_tagger, "cl_gen_thresh": cl_gen_thresh, "cl_char_thresh": cl_char_thresh,
|
| 187 |
+
"use_pixai_tagger": use_pixai_tagger, "pixai_general_thresh": pixai_general_thresh, "pixai_char_thresh": pixai_char_thresh,
|
| 188 |
+
"use_animetagger": use_animetagger, "animetagger_model": animetagger_model, "animetagger_thresh": animetagger_thresh,
|
| 189 |
+
|
| 190 |
+
# Gemini
|
| 191 |
+
"use_gemini": use_gemini, "gemini_api_key": gemini_api_key, "gemini_mode": gemini_mode,
|
| 192 |
+
"gemini_model": gemini_model,
|
| 193 |
+
"gemini_prompt_vision": gemini_prompt_vision,
|
| 194 |
+
"gemini_prompt_tags": gemini_prompt_tags,
|
| 195 |
+
"gemini_prompt_hybrid": gemini_prompt_hybrid,
|
| 196 |
+
"gemini_system_instruction": gemini_system_instruction,
|
| 197 |
+
|
| 198 |
+
# Dosyalar
|
| 199 |
+
"replacement_file": replacement_file, "synonym_file": synonym_file, "addition_file": addition_file,
|
| 200 |
+
"sort_order": sort_order,
|
| 201 |
+
"context_weight": context_weight,
|
| 202 |
+
|
| 203 |
+
"tekil_enable_categorization": tekil_enable, "tekil_selected_categories": tekil_cats,
|
| 204 |
+
"toplu_enable_categorization": toplu_enable, "toplu_selected_categories": toplu_cats,
|
| 205 |
+
"dual1_enable_categorization": dual1_enable, "dual1_selected_categories": dual1_cats,
|
| 206 |
+
"dual2_enable_categorization": dual2_enable, "dual2_selected_categories": dual2_cats
|
| 207 |
+
}
|
| 208 |
+
save_config(config_data)
|
| 209 |
+
return gr.update(value="✅ Tüm genel ve özelleştirilmiş kategorizasyon ayarları kaydedildi.", visible=True)
|
| 210 |
+
|
| 211 |
+
def save_image_tools_settings(folder_paths_str, scale_factor, watermark_text, watermark_opacity, watermark_font_ratio, watermark_angle, brightness_level, contrast_level, denoise_level, sharpen_amount):
|
| 212 |
+
config_data = load_config()
|
| 213 |
+
config_data["image_tools_settings"]["folder_paths"] = [path.strip() for path in folder_paths_str.split('\n') if path.strip()]
|
| 214 |
+
config_data["image_tools_settings"]["scale_factor"] = scale_factor
|
| 215 |
+
config_data["image_tools_settings"]["watermark_text"] = watermark_text
|
| 216 |
+
config_data["image_tools_settings"]["watermark_opacity"] = watermark_opacity
|
| 217 |
+
config_data["image_tools_settings"]["watermark_font_ratio"] = watermark_font_ratio
|
| 218 |
+
config_data["image_tools_settings"]["watermark_angle"] = watermark_angle
|
| 219 |
+
config_data["image_tools_settings"]["brightness_level"] = brightness_level
|
| 220 |
+
config_data["image_tools_settings"]["contrast_level"] = contrast_level
|
| 221 |
+
config_data["image_tools_settings"]["denoise_level"] = denoise_level
|
| 222 |
+
config_data["image_tools_settings"]["sharpen_amount"] = sharpen_amount
|
| 223 |
+
|
| 224 |
+
save_config(config_data)
|
| 225 |
+
return gr.update(value="✅ Resim araçları ayarları kaydedildi.", visible=True)
|
| 226 |
+
|
| 227 |
+
def save_art_tools_settings(grid_cols, grid_bg_color, grid_title_text, grid_banner_color, grid_title_color, grid_add_labels, grid_label_type, grid_start_num):
|
| 228 |
+
config_data = load_config()
|
| 229 |
+
# Eğer art_tools_settings keyi yoksa oluştur (eski configler için)
|
| 230 |
+
if "art_tools_settings" not in config_data:
|
| 231 |
+
config_data["art_tools_settings"] = {}
|
| 232 |
+
|
| 233 |
+
config_data["art_tools_settings"]["grid_cols"] = grid_cols
|
| 234 |
+
config_data["art_tools_settings"]["grid_bg_color"] = grid_bg_color
|
| 235 |
+
config_data["art_tools_settings"]["grid_title_text"] = grid_title_text
|
| 236 |
+
config_data["art_tools_settings"]["grid_banner_color"] = grid_banner_color
|
| 237 |
+
config_data["art_tools_settings"]["grid_title_color"] = grid_title_color
|
| 238 |
+
config_data["art_tools_settings"]["grid_add_labels"] = grid_add_labels
|
| 239 |
+
config_data["art_tools_settings"]["grid_label_type"] = grid_label_type
|
| 240 |
+
config_data["art_tools_settings"]["grid_start_num"] = grid_start_num
|
| 241 |
+
|
| 242 |
+
save_config(config_data)
|
| 243 |
+
return gr.update(value="✅ Art Studio ayarları kaydedildi.", visible=True)
|
modules/hydra_layers.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/hydra_layers.py
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import re
|
| 5 |
+
from typing import Any
|
| 6 |
+
import torch
|
| 7 |
+
from torch import Tensor
|
| 8 |
+
from torch.nn import (
|
| 9 |
+
Module, ModuleList, Parameter, Buffer,
|
| 10 |
+
Linear, LayerNorm, RMSNorm, Dropout, Flatten, Identity,
|
| 11 |
+
init
|
| 12 |
+
)
|
| 13 |
+
from torch.nn.functional import pad, scaled_dot_product_attention, silu, gelu
|
| 14 |
+
from einops import rearrange
|
| 15 |
+
|
| 16 |
+
# --- GLU.PY ---
|
| 17 |
+
class GatedUnit(Module):
|
| 18 |
+
def __init__(self, dim: int = -1) -> None:
|
| 19 |
+
super().__init__()
|
| 20 |
+
self.dim = dim
|
| 21 |
+
def _activation(self, x: Tensor) -> Tensor:
|
| 22 |
+
raise NotImplementedError
|
| 23 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 24 |
+
f, g = x.chunk(2, dim=self.dim)
|
| 25 |
+
return self._activation(f) * g
|
| 26 |
+
|
| 27 |
+
class SwiGLU(GatedUnit):
|
| 28 |
+
def __init__(self, dim: int = -1) -> None:
|
| 29 |
+
super().__init__(dim)
|
| 30 |
+
def _activation(self, x: Tensor) -> Tensor:
|
| 31 |
+
return silu(x)
|
| 32 |
+
|
| 33 |
+
# --- HYDRA_POOL.PY UTILS ---
|
| 34 |
+
class IndexedAdd(Module):
|
| 35 |
+
def __init__(self, n_indices: int, dim: int, weight_shape: tuple[int, ...] | None = None, *, inplace: bool = False, device=None, dtype=None) -> None:
|
| 36 |
+
super().__init__()
|
| 37 |
+
self.dim = dim
|
| 38 |
+
self.inplace = inplace
|
| 39 |
+
self.index = Buffer(torch.empty(2, n_indices, device=device, dtype=torch.int32))
|
| 40 |
+
self.weight = Parameter(torch.ones(*(sz if sz != -1 else n_indices for sz in weight_shape), device=device, dtype=dtype)) if weight_shape is not None else None
|
| 41 |
+
|
| 42 |
+
def forward(self, dst: Tensor, src: Tensor) -> Tensor:
|
| 43 |
+
src = src.index_select(self.dim, self.index[0])
|
| 44 |
+
if self.weight is not None: src.mul_(self.weight)
|
| 45 |
+
return dst.index_add_(self.dim, self.index[1], src) if self.inplace else dst.index_add(self.dim, self.index[1], src)
|
| 46 |
+
|
| 47 |
+
class BatchLinear(Module):
|
| 48 |
+
def __init__(self, batch_shape: tuple[int, ...] | int, in_features: int, out_features: int, *, bias: bool = False, flatten: bool = False, bias_inplace: bool = True, device=None, dtype=None) -> None:
|
| 49 |
+
super().__init__()
|
| 50 |
+
if isinstance(batch_shape, int): batch_shape = (batch_shape,)
|
| 51 |
+
self.flatten = -(len(batch_shape) + 1) if flatten else 0
|
| 52 |
+
self.weight = Parameter(torch.empty(*batch_shape, in_features, out_features, device=device, dtype=dtype))
|
| 53 |
+
bt = self.weight.flatten(end_dim=-3).mT
|
| 54 |
+
for idx in range(bt.size(0)): init.kaiming_uniform_(bt[idx], a=math.sqrt(5))
|
| 55 |
+
self.bias = Parameter(torch.zeros(*batch_shape, out_features, device=device, dtype=dtype)) if bias else None
|
| 56 |
+
self.bias_inplace = bias_inplace
|
| 57 |
+
|
| 58 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 59 |
+
x = torch.matmul(x.unsqueeze(-2), self.weight).squeeze(-2)
|
| 60 |
+
if self.bias is not None:
|
| 61 |
+
if self.bias_inplace: x.add_(self.bias)
|
| 62 |
+
else: x = x + self.bias
|
| 63 |
+
if self.flatten: x = x.flatten(self.flatten)
|
| 64 |
+
return x
|
| 65 |
+
|
| 66 |
+
class Mean(Module):
|
| 67 |
+
def __init__(self, dim: tuple[int, ...] | int = -1, *, keepdim: bool = False) -> None:
|
| 68 |
+
super().__init__()
|
| 69 |
+
self.dim = dim
|
| 70 |
+
self.keepdim = keepdim
|
| 71 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 72 |
+
return x.mean(self.dim, self.keepdim)
|
| 73 |
+
|
| 74 |
+
class _MidBlock(Module):
|
| 75 |
+
def __init__(self, attn_dim: int, head_dim: int, n_classes: int, *, ff_ratio: float, ff_dropout: float, q_cls_inplace: bool = True, device=None, dtype=None) -> None:
|
| 76 |
+
super().__init__()
|
| 77 |
+
self.head_dim = head_dim
|
| 78 |
+
self.q_cls_inplace = q_cls_inplace
|
| 79 |
+
hidden_dim = int(attn_dim * ff_ratio)
|
| 80 |
+
self.q_proj = Linear(attn_dim, attn_dim, bias=False, device=device, dtype=dtype)
|
| 81 |
+
self.q_cls = Parameter(torch.zeros(n_classes, attn_dim, device=device, dtype=dtype))
|
| 82 |
+
self.q_norm = RMSNorm(head_dim, eps=1e-5, elementwise_affine=False)
|
| 83 |
+
self.attn_out = Linear(attn_dim, attn_dim, bias=False, device=device, dtype=dtype)
|
| 84 |
+
self.ff_norm = LayerNorm(attn_dim, device=device, dtype=dtype)
|
| 85 |
+
self.ff_in = Linear(attn_dim, hidden_dim * 2, bias=False, device=device, dtype=dtype)
|
| 86 |
+
self.ff_act = SwiGLU()
|
| 87 |
+
self.ff_drop = Dropout(ff_dropout)
|
| 88 |
+
self.ff_out = Linear(hidden_dim, attn_dim, bias=False, device=device, dtype=dtype)
|
| 89 |
+
|
| 90 |
+
def _forward_q(self, x: Tensor) -> Tensor:
|
| 91 |
+
x = self.q_proj(x)
|
| 92 |
+
if self.q_cls_inplace: x.add_(self.q_cls)
|
| 93 |
+
else: x = x + self.q_cls
|
| 94 |
+
x = self.q_norm(x)
|
| 95 |
+
return rearrange(x, "... s (h e) -> ... h s e", e=self.head_dim)
|
| 96 |
+
|
| 97 |
+
def _forward_attn(self, x: Tensor, k: Tensor, v: Tensor, attn_mask: Tensor | None) -> Tensor:
|
| 98 |
+
a = scaled_dot_product_attention(self._forward_q(x), k, v, attn_mask=attn_mask)
|
| 99 |
+
a = rearrange(a, "... h s e -> ... s (h e)")
|
| 100 |
+
return x + self.attn_out(a)
|
| 101 |
+
|
| 102 |
+
def _forward_ff(self, x: Tensor) -> Tensor:
|
| 103 |
+
f = self.ff_out(self.ff_drop(self.ff_act(self.ff_in(self.ff_norm(x)))))
|
| 104 |
+
return x + f
|
| 105 |
+
|
| 106 |
+
def forward(self, x: Tensor, k: Tensor, v: Tensor, attn_mask: Tensor | None = None) -> Tensor:
|
| 107 |
+
return self._forward_ff(self._forward_attn(x, k, v, attn_mask))
|
| 108 |
+
|
| 109 |
+
class HydraPool(Module):
|
| 110 |
+
def __init__(self, attn_dim: int, head_dim: int, n_classes: int, *, mid_blocks: int = 0, roots: tuple[int, int, int] = (0, 0, 0), ff_ratio: float = 3.0, ff_dropout: float = 0.0, input_dim: int = -1, output_dim: int = 1, device=None, dtype=None) -> None:
|
| 111 |
+
super().__init__()
|
| 112 |
+
if input_dim < 0: input_dim = attn_dim
|
| 113 |
+
self.n_classes = n_classes
|
| 114 |
+
self.head_dim = head_dim
|
| 115 |
+
self.output_dim = output_dim
|
| 116 |
+
self._has_roots = False
|
| 117 |
+
self._has_ff = False
|
| 118 |
+
self._q_normed = None
|
| 119 |
+
|
| 120 |
+
if roots != (0, 0, 0):
|
| 121 |
+
self._has_roots = True
|
| 122 |
+
n_roots, n_classroots, n_subclasses = roots
|
| 123 |
+
self.cls = Parameter(torch.randn(attn_dim // head_dim, n_classes, head_dim, device=device, dtype=dtype))
|
| 124 |
+
self.roots = Parameter(torch.randn(attn_dim // head_dim, n_roots, head_dim, device=device, dtype=dtype)) if n_roots > 0 else None
|
| 125 |
+
self.clsroots = IndexedAdd(n_classroots, dim=-2, weight_shape=(attn_dim // head_dim, -1, 1), device=device, dtype=dtype) if n_classroots > 0 else None
|
| 126 |
+
self.clscls = IndexedAdd(n_subclasses, dim=-2, weight_shape=(attn_dim // head_dim, -1, 1), inplace=True, device=device, dtype=dtype) if n_subclasses > 0 else None
|
| 127 |
+
self.q = Buffer(torch.empty(attn_dim // head_dim, n_classes, head_dim, device=device, dtype=dtype))
|
| 128 |
+
else:
|
| 129 |
+
self.q = Parameter(torch.randn(attn_dim // head_dim, n_classes, head_dim, device=device, dtype=dtype))
|
| 130 |
+
self._q_normed = False
|
| 131 |
+
|
| 132 |
+
self.kv = Linear(input_dim, attn_dim * 2, bias=False, device=device, dtype=dtype)
|
| 133 |
+
self.qk_norm = RMSNorm(head_dim, eps=1e-5, elementwise_affine=False)
|
| 134 |
+
|
| 135 |
+
if ff_ratio > 0.0:
|
| 136 |
+
self._has_ff = True
|
| 137 |
+
hidden_dim = int(attn_dim * ff_ratio)
|
| 138 |
+
self.ff_norm = LayerNorm(attn_dim, device=device, dtype=dtype)
|
| 139 |
+
self.ff_in = Linear(attn_dim, hidden_dim * 2, bias=False, device=device, dtype=dtype)
|
| 140 |
+
self.ff_act = SwiGLU()
|
| 141 |
+
self.ff_drop = Dropout(ff_dropout)
|
| 142 |
+
self.ff_out = Linear(hidden_dim, attn_dim, bias=False, device=device, dtype=dtype)
|
| 143 |
+
|
| 144 |
+
self.mid_blocks = ModuleList(_MidBlock(attn_dim, head_dim, n_classes, ff_ratio=ff_ratio, ff_dropout=ff_dropout, device=device, dtype=dtype) for _ in range(mid_blocks))
|
| 145 |
+
self.out_proj = BatchLinear(n_classes, attn_dim, output_dim * 2, device=device, dtype=dtype)
|
| 146 |
+
self.out_act = SwiGLU()
|
| 147 |
+
|
| 148 |
+
def create_head(self) -> Module:
|
| 149 |
+
return Flatten(-2) if self.output_dim == 1 else Mean(-1)
|
| 150 |
+
|
| 151 |
+
def _forward_q(self) -> Tensor:
|
| 152 |
+
if self._q_normed is None:
|
| 153 |
+
q = self.qk_norm(self.roots) if self.roots is not None else self.cls
|
| 154 |
+
if self.clsroots is not None: q = self.clsroots(self.cls, q)
|
| 155 |
+
if self.clscls is not None: q = self.clscls(q, q.detach())
|
| 156 |
+
return self.qk_norm(q)
|
| 157 |
+
elif self._q_normed is False: return self.qk_norm(self.q)
|
| 158 |
+
else: return self.q
|
| 159 |
+
|
| 160 |
+
def _forward_attn(self, x: Tensor, attn_mask: Tensor | None) -> tuple[Tensor, Tensor, Tensor]:
|
| 161 |
+
q = self._forward_q().expand(*x.shape[:-2], -1, -1, -1)
|
| 162 |
+
x = self.kv(x)
|
| 163 |
+
k, v = rearrange(x, "... s (n h e) -> n ... h s e", n=2, e=self.head_dim).unbind(0)
|
| 164 |
+
k = self.qk_norm(k)
|
| 165 |
+
x = scaled_dot_product_attention(q, k, v, attn_mask=attn_mask)
|
| 166 |
+
return rearrange(x, "... h s e -> ... s (h e)"), k, v
|
| 167 |
+
|
| 168 |
+
def _forward_ff(self, x: Tensor) -> Tensor:
|
| 169 |
+
if not self._has_ff: return x
|
| 170 |
+
return x + self.ff_out(self.ff_drop(self.ff_act(self.ff_in(self.ff_norm(x)))))
|
| 171 |
+
|
| 172 |
+
def forward(self, x: Tensor, attn_mask: Tensor | None = None) -> Tensor:
|
| 173 |
+
x, k, v = self._forward_attn(x, attn_mask)
|
| 174 |
+
x = self._forward_ff(x)
|
| 175 |
+
for block in self.mid_blocks: x = block(x, k, v, attn_mask)
|
| 176 |
+
return self.out_act(self.out_proj(x))
|
| 177 |
+
|
| 178 |
+
@staticmethod
|
| 179 |
+
def for_state(state_dict: dict[str, Any], prefix: str = "", *, ff_dropout: float = 0.0, device=None, dtype=None) -> "HydraPool":
|
| 180 |
+
n_heads, n_classes, head_dim = state_dict[f"{prefix}q"].shape
|
| 181 |
+
attn_dim = n_heads * head_dim
|
| 182 |
+
roots_t, clsroots_t, clscls_t = state_dict.get(f"{prefix}roots"), state_dict.get(f"{prefix}clsroots.index"), state_dict.get(f"{prefix}clscls.index")
|
| 183 |
+
roots = (roots_t.size(1) if roots_t is not None else 0, clsroots_t.size(1) if clsroots_t is not None else 0, clscls_t.size(1) if clscls_t is not None else 0)
|
| 184 |
+
input_dim = state_dict[f"{prefix}kv.weight"].size(1)
|
| 185 |
+
output_dim = state_dict[f"{prefix}out_proj.weight"].size(2) // 2
|
| 186 |
+
ffout_t = state_dict.get(f"{prefix}ff_out.weight")
|
| 187 |
+
hidden_dim = ffout_t.size(1) + 0.5 if ffout_t is not None else 0
|
| 188 |
+
ff_ratio = hidden_dim / attn_dim
|
| 189 |
+
pattern = re.compile(rf"^{re.escape(prefix)}mid_blocks\.([0-9]+)\.")
|
| 190 |
+
mid_blocks = max([-1, *(int(match[1]) for key in state_dict if (match := pattern.match(key)) is not None)]) + 1
|
| 191 |
+
return HydraPool(attn_dim, head_dim, n_classes, mid_blocks=mid_blocks, roots=roots, ff_ratio=ff_ratio, ff_dropout=ff_dropout, input_dim=input_dim, output_dim=output_dim, device=device, dtype=dtype)
|
modules/localization/languages.py
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Dil seçeneğini ConfigManager'dan (circular import olmaması için) veya direkt dosyadan okuyabiliriz.
|
| 3 |
+
# En sağlıklısı: Bir "current_language" global değişkeni tutmak ve bunu main.py veya config_manager update ettiğinde değiştirmek.
|
| 4 |
+
# Ancak basit olması açısından burada dosyayı okuyalım (cache ile).
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
_CACHED_LANG = None
|
| 10 |
+
|
| 11 |
+
def get_db_lang():
|
| 12 |
+
global _CACHED_LANG
|
| 13 |
+
# Her çağrıda dosya okumak performansı düşürebilir, bu yüzden basit bir cache mekanizması ekleyelim.
|
| 14 |
+
# Ancak dil değiştiğinde uygulamanın yeniden başlatılması gerektiği için bu güvenlidir.
|
| 15 |
+
if _CACHED_LANG: return _CACHED_LANG
|
| 16 |
+
|
| 17 |
+
config_path = "data/configs/config.json"
|
| 18 |
+
if os.path.exists(config_path):
|
| 19 |
+
try:
|
| 20 |
+
with open(config_path, "r", encoding="utf-8") as f:
|
| 21 |
+
data = json.load(f)
|
| 22 |
+
_CACHED_LANG = data.get("general_settings", {}).get("language", "tr")
|
| 23 |
+
return _CACHED_LANG
|
| 24 |
+
except: pass
|
| 25 |
+
return "tr"
|
| 26 |
+
|
| 27 |
+
def get_str(key):
|
| 28 |
+
lang = get_db_lang()
|
| 29 |
+
|
| 30 |
+
# 1. Hedef dil sözlüğünü al
|
| 31 |
+
target_dict = STRINGS.get(lang, STRINGS["tr"])
|
| 32 |
+
|
| 33 |
+
# 2. Key varsa döndür
|
| 34 |
+
if key in target_dict:
|
| 35 |
+
return target_dict[key]
|
| 36 |
+
|
| 37 |
+
# 3. Yedeğe (İngilizce'ye) bak (Eğer hedef dilde yoksa)
|
| 38 |
+
if lang != "en" and "en" in STRINGS and key in STRINGS["en"]:
|
| 39 |
+
return STRINGS["en"][key]
|
| 40 |
+
|
| 41 |
+
# 4. Hiçbir yerde yoksa key'i olduğu gibi döndür
|
| 42 |
+
return key
|
| 43 |
+
|
| 44 |
+
STRINGS = {
|
| 45 |
+
"tr": {
|
| 46 |
+
# Settings Tab
|
| 47 |
+
"tab_settings_title": "⚙️ Ayarlar",
|
| 48 |
+
"settings_header": "<h2 style='color:#2c3e50;'>Genel Yapılandırma ve Model Ayarları</h2>",
|
| 49 |
+
"save_settings_btn": "💾 Tüm Ayarları Kaydet",
|
| 50 |
+
"status_label": "Durum",
|
| 51 |
+
"device_section": "### 🖥️ Cihaz, Dil & Kural Dosyaları",
|
| 52 |
+
"device_label": "Çalıştırma Cihazı",
|
| 53 |
+
"device_info": "Modellerin hangi işlemcide çalışacağını belirler.",
|
| 54 |
+
"language_label": "Dil / Language",
|
| 55 |
+
"language_info": "Uygulama dilini değiştirir (Yeniden başlatma gerekir).",
|
| 56 |
+
"theme_label": "Arayüz Teması / UI Theme",
|
| 57 |
+
"theme_info": "Uygulama temasını değiştirir (Yeniden başlatma gerekir).",
|
| 58 |
+
"replacement_file_label": "Değiştirme Dosyası (Regex)",
|
| 59 |
+
"synonym_file_label": "Birleştirme Dosyası (Eşanlamlı)",
|
| 60 |
+
"addition_file_label": "Ekleme Dosyası (Sona Eklenecekler)",
|
| 61 |
+
"sort_order_label": "Etiket Sıralama Yöntemi",
|
| 62 |
+
"sort_alpha": "Alfabetik",
|
| 63 |
+
"sort_len": "Uzunluğa Göre",
|
| 64 |
+
"sort_random": "Rastgele",
|
| 65 |
+
"sort_orig": "Orijinal",
|
| 66 |
+
|
| 67 |
+
"model_mobilenet": "MobileNet V4 (Hızlı)",
|
| 68 |
+
"model_convnext": "ConvNeXt V2 Huge (Pro)",
|
| 69 |
+
"model_caformer": "Caformer B36 (Yeni)",
|
| 70 |
+
"categorization_section": "### 🗂️ Mod Bazlı Kategorizasyon Ayarları",
|
| 71 |
+
"categorization_desc": "Aşağıdaki sekmelerden her bir mod için ayrı kategorizasyon ayarı yapabilirsiniz.",
|
| 72 |
+
"tab_single": "Tekil Mod",
|
| 73 |
+
"tab_batch": "Toplu Mod",
|
| 74 |
+
"tab_dual_left": "Dual Mod (Sol)",
|
| 75 |
+
"tab_dual_right": "Dual Mod (Sağ)",
|
| 76 |
+
"enable_cat_single": "Tekil Modda Kategorizasyonu Etkinleştir",
|
| 77 |
+
"cat_single_label": "Tekil Mod Kategorileri",
|
| 78 |
+
"enable_cat_batch": "Toplu Modda Kategorizasyonu Etkinleştir",
|
| 79 |
+
"cat_batch_label": "Toplu Mod Kategorileri",
|
| 80 |
+
"enable_cat_dual1": "Dual Mod Resim 1 (Sol) Kategorizasyon",
|
| 81 |
+
"cat_dual1_label": "Dual Resim 1 Kategorileri",
|
| 82 |
+
"enable_cat_dual2": "Dual Mod Resim 2 (Sağ) Kategorizasyon",
|
| 83 |
+
"cat_dual2_label": "Dual Resim 2 Kategorileri",
|
| 84 |
+
"model_sensitivity_section": "### 🤖 Model Hassasiyet Ayarları",
|
| 85 |
+
"context_weight_label": "Bağlam Ağırlığı (Context Weight)",
|
| 86 |
+
"context_weight_info": "Etiketleri rafine ederken veritabanında olmayan 'Unknown' etiketleri temizleme şiddeti.",
|
| 87 |
+
"joint_settings": "Joint Tagger Ayarları",
|
| 88 |
+
"use_joint": "Joint Etiketleyiciyi Kullan",
|
| 89 |
+
"joint_thresh": "Joint Eşiği",
|
| 90 |
+
"cl_settings": "CL Tagger Ayarları",
|
| 91 |
+
"use_cl": "CL Tagger'ı Kullan",
|
| 92 |
+
"cl_gen_thresh": "CL General/Meta/Model Eşiği",
|
| 93 |
+
"cl_char_thresh": "CL Character/Copyright/Artist Eşiği",
|
| 94 |
+
"pixai_settings": "PixAI Tagger Ayarları",
|
| 95 |
+
"use_pixai": "PixAI Tagger'ı Kullan",
|
| 96 |
+
"pixai_gen_thresh": "PixAI General Eşiği",
|
| 97 |
+
"pixai_char_thresh": "PixAI Character Eşiği",
|
| 98 |
+
"anime_settings": "Anime Tagger (MobileNet/ConvNeXt/Caformer)",
|
| 99 |
+
"use_anime": "Anime Tagger'ı Kullan",
|
| 100 |
+
"anime_model_label": "Model Seçimi",
|
| 101 |
+
"anime_thresh": "Etiket Eşiği",
|
| 102 |
+
"gemini_settings": "✨ Gemini Caption (Doğal Dil)",
|
| 103 |
+
"gemini_desc": "Görselleri etiket listesi yerine **doğal cümlelerle** betimler.",
|
| 104 |
+
"use_gemini": "Gemini Caption Kullan",
|
| 105 |
+
"gemini_api_key": "Google Gemini API Key",
|
| 106 |
+
"gemini_model": "Gemini Modeli",
|
| 107 |
+
"gemini_model_info": "Kullanılacak Gemini model versiyonunu seçin.",
|
| 108 |
+
"gemini_mode_label": "Gemini Modu",
|
| 109 |
+
"gemini_mode_info": "Vision: Sadece resme bakar. Tags: Sadece etiketleri kullanır. Vision + Tags: İkisini birleştirir.",
|
| 110 |
+
"gemini_system_instr": "Sistem Talimatı (System Instruction)",
|
| 111 |
+
"gemini_system_info": "Modelin genel davranışını ve kimliğini belirleyen ana talimat.",
|
| 112 |
+
"gemini_prompt_settings": "#### 📝 Mod Bazlı Prompt Ayarları",
|
| 113 |
+
"prompt_vision": "Vision Modu Prompt",
|
| 114 |
+
"prompt_tags": "Tags Modu Prompt",
|
| 115 |
+
"prompt_hybrid": "Vision + Tags Modu Prompt",
|
| 116 |
+
"save_success": "✅ Tüm genel ve özelleştirilmiş kategorizasyon ayarları kaydedildi. Dil değişikliğinin etkili olması için uygulamayı yeniden başlatın.",
|
| 117 |
+
|
| 118 |
+
# App Title
|
| 119 |
+
"app_title": "Ultra Pro Tagger V1",
|
| 120 |
+
"app_subtitle": "Yapay Zeka Destekli Otomatik Etiketleme ve Düzenleme Suiti",
|
| 121 |
+
|
| 122 |
+
# Main UI
|
| 123 |
+
"btn_close_app": "Uygulamayı Kapat",
|
| 124 |
+
"btn_restart_app": "Uygulamayı Yeniden Başlat",
|
| 125 |
+
|
| 126 |
+
# Single Tab
|
| 127 |
+
"tab_single_title": "✨ Tekil Etiketleme",
|
| 128 |
+
"btn_process_single": "Görseli Etiketle (Genel Ayarlar ile)",
|
| 129 |
+
"label_image_upload": "Görsel Yükle",
|
| 130 |
+
"tab_raw_tags": "Has Etiketler",
|
| 131 |
+
"label_unique_tags": "Birleşik Benzersiz Etiketler",
|
| 132 |
+
"btn_send_to_cat": "Bu Etiketleri Kategorizasyona Gönder ➡️",
|
| 133 |
+
"tab_refined_tags": "Rafine Edilmiş",
|
| 134 |
+
"label_refined_tags": "Rafine Edilmiş (Temizlenmiş) Etiketler",
|
| 135 |
+
"tab_categorized_tags": "Kategorize Edilmiş",
|
| 136 |
+
"label_categorized_tags": "Kategorize Edilmiş Etiketler",
|
| 137 |
+
"header_wildcard": "### 📂 Wildcard Paketine Ekle",
|
| 138 |
+
"tab_gemini_caption": "Cümlelendirilmiş (GeminiCaption)",
|
| 139 |
+
"label_gemini_caption": "Gemini Betimlemesi",
|
| 140 |
+
"tab_combined_caption": "Kategorize + Cümlelendirilmiş (GeminiCaption)",
|
| 141 |
+
"label_combined_caption": "Kategorize ve Gemini Birleşimi",
|
| 142 |
+
|
| 143 |
+
# Batch Tab
|
| 144 |
+
"tab_batch_title": "📚 Toplu Etiketleme",
|
| 145 |
+
"btn_process_batch": "Resimleri İşle (Genel Ayarlar ile)",
|
| 146 |
+
"label_batch_input": "Resimleri Yükleyin (Çoklu Seçim)",
|
| 147 |
+
"tab_batch_original": "Orijinal Etiketler (Birleşik)",
|
| 148 |
+
"label_batch_original": "Tüm Resimlerden Orijinal Etiketler",
|
| 149 |
+
"tab_batch_refined": "Rafine Edilmiş (Birleşik)",
|
| 150 |
+
"label_batch_refined": "Tüm Resimlerden Rafine Edilmiş Etiketler",
|
| 151 |
+
"tab_batch_cat": "Kategorize Edilmiş (Satır Satır)",
|
| 152 |
+
"tab_batch_gemini": "Gemini Caption (Tümü)",
|
| 153 |
+
"label_batch_gemini": "Tüm Görsellerin Betimlemeleri",
|
| 154 |
+
"tab_batch_html": "Detaylı HTML Raporu",
|
| 155 |
+
"label_batch_html": "Detaylı Sonuçlar",
|
| 156 |
+
"btn_batch_download": "Tüm Sonuç Etiketleri İndir (.txt)",
|
| 157 |
+
|
| 158 |
+
# Dual Tab
|
| 159 |
+
"tab_dual_title": "↔️ Dual Etiketleme",
|
| 160 |
+
"dual_note": "<p style='color:#666; font-size:0.9em;'>Not: Dual modda her iki resim için 'Ayarlar' sekmesindeki ayrı kategorizasyon ayarları kullanılır.</p>",
|
| 161 |
+
"btn_process_dual": "Görselleri Karşılaştır ve Etiketle",
|
| 162 |
+
"label_img1": "Görsel 1",
|
| 163 |
+
"label_img2": "Görsel 2",
|
| 164 |
+
"tab_dual_combined": "Birleştirilmiş",
|
| 165 |
+
"label_dual_combined": "İki Resmin Birleşimi (Benzersiz)",
|
| 166 |
+
"label_dual_refined": "İki Resmin Birleşimi (Rafine)",
|
| 167 |
+
"label_dual_cat": "Birleşik Kategorize Edilmiş",
|
| 168 |
+
"label_gemini_img1": "Görsel 1 Gemini",
|
| 169 |
+
"label_gemini_img2": "Görsel 2 Gemini",
|
| 170 |
+
|
| 171 |
+
# Tools Tab
|
| 172 |
+
"tab_prompt_gen": "📝 Prompt Oluşturucu",
|
| 173 |
+
"tab_category_tools": "🏷️ Kategori Araçları",
|
| 174 |
+
"tab_rule_management": "🔁 Kural Yönetimi",
|
| 175 |
+
"tab_text_tools": "📝 Metin Araçları",
|
| 176 |
+
"tab_image_tools": "🖼️ Resim Araçları",
|
| 177 |
+
"tab_video_creator": "🎬 Video Oluşturucu",
|
| 178 |
+
"tab_art_studio": "🎨 Sanat Stüdyosu",
|
| 179 |
+
"label_tags_input": "Etiketleri girin (virgülle ayırın)",
|
| 180 |
+
"placeholder_tags_input": "örnek: 1kız, mavi saç, uzun saç, kedi kulakları...",
|
| 181 |
+
|
| 182 |
+
# Category Tools
|
| 183 |
+
"cat_live_tab_title": "Canlı Kategorizasyon",
|
| 184 |
+
"cat_live_header": "<h2 style='color:#2563eb'>Etiketleri Kategorilere Ayırın</h2>",
|
| 185 |
+
"cat_live_main_tab": "Ana Kategorizasyon",
|
| 186 |
+
"cat_live_main_select": "Gösterilecek Ana Kategorileri Seç",
|
| 187 |
+
"cat_live_main_btn": "Ana Kategorilere Ayır",
|
| 188 |
+
"cat_live_unmatched_btn": "⚠️ Eşleşmeyenleri Düzenleyiciye Aktar ve Düzenle ➡️",
|
| 189 |
+
"cat_live_main_output": "Ana Kategorize Edilmiş Etiketler",
|
| 190 |
+
|
| 191 |
+
"cat_manage_tab_title": "Kategori/Etiket Düzenleme",
|
| 192 |
+
"cat_manage_header": "<h3 style='color:#007bff;'>Kategori ve Etiket Dosyalarını Yönetin</h3>",
|
| 193 |
+
"cat_manage_main_accordion": "Ana Kategori Yönetimi",
|
| 194 |
+
"cat_manage_main_grid": "Tüm Kategoriler",
|
| 195 |
+
"cat_manage_main_refresh": "↻ Ana Kategorileri Yenile",
|
| 196 |
+
"cat_manage_main_select": "Kategori Seç",
|
| 197 |
+
"cat_manage_tags_input": "Etiketler (virgülle ayırın)",
|
| 198 |
+
"btn_add": "Ekle",
|
| 199 |
+
"btn_delete": "Sil",
|
| 200 |
+
|
| 201 |
+
# Category Redesign
|
| 202 |
+
"cat_redesign_live_tab": "Kategorilere Ayır",
|
| 203 |
+
"cat_redesign_main_tab": "📂 Kategori Yönetimi",
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
# Image Tools
|
| 208 |
+
"img_tools_header": "<h3 style='color:#0077b6;'>Toplu Resim İşleme</h3>",
|
| 209 |
+
"img_tools_paths_input": "Klasör Yolları (Her satıra bir yol)",
|
| 210 |
+
"img_tools_select_folder_btn": "📁 Klasör Seç",
|
| 211 |
+
"img_tools_clear_btn": "🗑️ Temizle",
|
| 212 |
+
"img_tools_save_settings_btn": "Yolu ve Araç Ayarlarını Kaydet",
|
| 213 |
+
"img_tools_res_accordion": "1. 📐 Çözünürlük",
|
| 214 |
+
"img_tools_scale_label": "Oran",
|
| 215 |
+
"btn_apply": "Uygula",
|
| 216 |
+
"img_tools_rename_accordion": "2. 📝 İsimlendirme (Şablonlu)",
|
| 217 |
+
"img_tools_rename_desc": "Dosyaları sırasıyla yeniden adlandırır. Şablonun içinde mutlaka **`{Number}`** olmalıdır.",
|
| 218 |
+
"img_tools_template_select": "Hazır Şablonlar",
|
| 219 |
+
"img_tools_delete_template_btn": "🗑️ Şablonu Sil",
|
| 220 |
+
"img_tools_new_template_input": "Yeni Şablon Ekle",
|
| 221 |
+
"img_tools_new_template_placeholder": "Örn: Adoptable {Number} (Elf)",
|
| 222 |
+
"img_tools_add_template_btn": "➕ Listeye Ekle",
|
| 223 |
+
"img_tools_start_num": "Başlangıç Numarası (Start)",
|
| 224 |
+
"img_tools_digit_count": "Basamak Sayısı (001 için 3)",
|
| 225 |
+
"img_tools_rename_btn": "Dosyaları Yeniden Adlandır",
|
| 226 |
+
"img_tools_watermark_accordion": "3. 🖋️ Filigran",
|
| 227 |
+
"img_tools_watermark_default": "Örnek Filigran",
|
| 228 |
+
"img_tools_watermark_text": "Metin",
|
| 229 |
+
"img_tools_watermark_opacity": "Opaklık",
|
| 230 |
+
"img_tools_watermark_size": "Font Boyutu",
|
| 231 |
+
"img_tools_watermark_angle": "Açı",
|
| 232 |
+
"img_tools_format_accordion": "4. 🖼️ Format",
|
| 233 |
+
"img_tools_format_label": "Format",
|
| 234 |
+
"btn_convert": "Dönüştür",
|
| 235 |
+
"img_tools_bc_accordion": "5. 🔅 Parlaklık/Kontrast",
|
| 236 |
+
"img_tools_brightness": "Parlaklık",
|
| 237 |
+
"img_tools_contrast": "Kontrast",
|
| 238 |
+
"img_tools_ds_accordion": "6. 🧼 Gürültü/Netlik",
|
| 239 |
+
"img_tools_denoise": "Gürültü Azaltma",
|
| 240 |
+
"img_tools_sharpen": "Netleştirme",
|
| 241 |
+
|
| 242 |
+
# Text Tools
|
| 243 |
+
"txt_tools_header": "<h3 style='color:#28a745;'>Manuel Metin Araçları</h3>",
|
| 244 |
+
"txt_tools_input": "İşlenecek Metin",
|
| 245 |
+
"txt_tools_tab_clean": "Temizleme",
|
| 246 |
+
"txt_tools_clean_btn": "Temizle ve Tekilleştir",
|
| 247 |
+
"label_result": "Sonuç",
|
| 248 |
+
"txt_tools_tab_format": "Format Değiştirme",
|
| 249 |
+
"txt_tools_lines_to_comma": "Satır -> Virgül",
|
| 250 |
+
"txt_tools_comma_to_lines": "Virgül -> Satır",
|
| 251 |
+
"txt_tools_tab_random": "Rastgele Seç",
|
| 252 |
+
"txt_tools_line_count": "Satır Sayısı",
|
| 253 |
+
"btn_select": "Seç",
|
| 254 |
+
|
| 255 |
+
"btn_filter": "Filtrele",
|
| 256 |
+
|
| 257 |
+
# Rule Tools
|
| 258 |
+
"rule_tools_intro": "Bu alanda etiketlerin nasıl değiştirileceğini, birleştirileceğini ve sonuçlara hangi etiketlerin ekleneceğini yönetebilirsiniz.",
|
| 259 |
+
"rule_rep_desc": "Etiketleri otomatik olarak başka etiketlerle değiştirir.",
|
| 260 |
+
"rule_rep_accordion": "Değiştirme Kuralları (Regex Destekli)",
|
| 261 |
+
"rule_active_file": "Aktif Dosya",
|
| 262 |
+
"rule_new_file_name": "Yeni Dosya Adı",
|
| 263 |
+
"rule_create_file_btn": "Yeni Dosya Oluştur",
|
| 264 |
+
"rule_tab_quick_add": "Hızlı Kural Ekle",
|
| 265 |
+
"rule_rep_old": "Eski Etiket (veya Regex)",
|
| 266 |
+
"rule_rep_new": "Yeni Etiket",
|
| 267 |
+
"rule_add_to_file_btn": "Kuralı Dosyaya Ekle",
|
| 268 |
+
"rule_tab_edit_file": "Dosyayı Düzenle",
|
| 269 |
+
"rule_file_content_manual": "Dosya İçeriği (Manuel Düzenleme)",
|
| 270 |
+
"rule_save_all_btn": "Tüm İçeriği Kaydet",
|
| 271 |
+
"rule_syn_accordion": "Birleştirme Kuralları (Eşanlamlı)",
|
| 272 |
+
"rule_syn_intro": "Birden fazla etiketi tek bir ana etikette birleştirir (Diğerlerini siler).",
|
| 273 |
+
"rule_syn_main": "Ana Etiket (Kalan)",
|
| 274 |
+
"rule_syn_remove": "Silinecek Eşanlamlılar (Virgülle ayırın)",
|
| 275 |
+
"rule_add_accordion": "Ekleme Kuralları (Sonuçlara Otomatik Eklenecekler)",
|
| 276 |
+
"rule_add_intro": "Bu bölümdeki etiketler, işlem bittikten sonra sonuç listesinin sonuna otomatik olarak eklenir.",
|
| 277 |
+
"rule_add_content_input": "Eklenecek Etiketler (Her satıra veya virgülle)",
|
| 278 |
+
"rule_add_save_btn": "Ekleme Kuralını Kaydet",
|
| 279 |
+
|
| 280 |
+
# Art Tools
|
| 281 |
+
"art_header": "### 🎨 Furry & Adoptable Araçları",
|
| 282 |
+
"art_desc": "Satış için grid oluşturma araçları.",
|
| 283 |
+
"art_tab_grid": "田 Adoptable Grid Oluşturucu",
|
| 284 |
+
"art_file_input": "Görselleri Seç (Çoklu)",
|
| 285 |
+
"art_grid_settings": "Grid Ayarları",
|
| 286 |
+
"art_cols": "Sütun Sayısı (Yan Yana Kaç Resim?)",
|
| 287 |
+
"art_bg_color": "Arkaplan Rengi",
|
| 288 |
+
"art_label_settings": "Etiket Ayarları",
|
| 289 |
+
"art_add_labels_check": "Resim Altına Yazı Ekle",
|
| 290 |
+
"art_label_type": "Yazı Tipi",
|
| 291 |
+
"art_start_num": "Başlangıç Numarası / Fiyatı",
|
| 292 |
+
"art_create_btn": "Grid Oluştur",
|
| 293 |
+
"art_output": "Grid Sonucu",
|
| 294 |
+
|
| 295 |
+
# Prompt Generator
|
| 296 |
+
"prompt_template_mgmt_header": "### 📝 Şablon Yönetimi",
|
| 297 |
+
"prompt_select_template": "Şablon Seçiniz",
|
| 298 |
+
"prompt_new_template_name": "Yeni Şablon Adı",
|
| 299 |
+
"prompt_template_content": "Şablon İçeriği",
|
| 300 |
+
"btn_update": "💾 Güncelle",
|
| 301 |
+
"btn_add_new": "➕ Yeni Ekle",
|
| 302 |
+
"prompt_generation_header": "### 🚀 Üretim",
|
| 303 |
+
"prompt_gen_btn": "SEÇİLİ ŞABLONU OLUŞTUR",
|
| 304 |
+
"prompt_gen_all_btn": "TÜM ŞABLONLARI OLUŞTUR",
|
| 305 |
+
"prompt_var_settings_accordion": "🛠️ Değişken Ayarları (Seçenek Ekle/Düzenle)",
|
| 306 |
+
"prompt_select_var": "Düzenlenecek Değişkeni Seç",
|
| 307 |
+
"prompt_var_label": "Etiket Adı",
|
| 308 |
+
"prompt_var_default": "Varsayılan Değer",
|
| 309 |
+
"prompt_var_options": "Seçenekler (Her satıra bir seçenek yazın)",
|
| 310 |
+
"prompt_update_var_btn": "Değişkeni Güncelle",
|
| 311 |
+
|
| 312 |
+
# Video Creator
|
| 313 |
+
"vid_header": "### 🎬 Pro Video Stüdyosu",
|
| 314 |
+
"vid_desc": "Görselleri ve müzikleri birleştirin, format seçin ve efekt ekleyin.",
|
| 315 |
+
"vid_img_input": "1. Görselleri Seçin (Slayt için çoklu)",
|
| 316 |
+
"vid_audio_input": "2. Müzikleri Seçin (Çoklu)",
|
| 317 |
+
"vid_resolution": "Video Formatı (Çözünürlük)",
|
| 318 |
+
"vid_speed": "İşleme Hızı",
|
| 319 |
+
"vid_text_overlay": "Video Üzerine Yazı Ekle (Opsiyonel)",
|
| 320 |
+
"vid_spectrum_check": "Ses Dalgası (Spectrum) Ekle (DİKKAT: Render süresini çok uzatır)",
|
| 321 |
+
"vid_create_btn": "VİDEOYU OLUŞTUR",
|
| 322 |
+
"vid_preview": "Önizleme",
|
| 323 |
+
"vid_status": "İşlem Durumu",
|
| 324 |
+
},
|
| 325 |
+
"en": {
|
| 326 |
+
# Settings Tab
|
| 327 |
+
"tab_settings_title": "⚙️ Tagger & Model Settings",
|
| 328 |
+
"settings_header": "<h2 style='color:#2c3e50;'>General Configuration and Model Settings</h2><p style='color:#666;'>Manage tagging modes, sensitivity, and language options here.</p>",
|
| 329 |
+
"save_settings_btn": "💾 Save All Settings",
|
| 330 |
+
"status_label": "Status",
|
| 331 |
+
"device_section": "### 🖥️ Device, Language & Rule Files",
|
| 332 |
+
"device_label": "Execution Device",
|
| 333 |
+
"device_info": "Determines which processor the models will run on.",
|
| 334 |
+
"language_label": "Dil / Language",
|
| 335 |
+
"language_info": "Changes application language (Requires restart).",
|
| 336 |
+
"theme_label": "UI Theme",
|
| 337 |
+
"theme_info": "Changes the application theme (Requires restart).",
|
| 338 |
+
"replacement_file_label": "Replacement File (Regex)",
|
| 339 |
+
"synonym_file_label": "Synonym File",
|
| 340 |
+
"addition_file_label": "Addition File (Append to End)",
|
| 341 |
+
"sort_order_label": "Tag Sorting Method",
|
| 342 |
+
"sort_alpha": "Alphabetical",
|
| 343 |
+
"sort_len": "By Length",
|
| 344 |
+
"sort_random": "Random",
|
| 345 |
+
"sort_orig": "Original",
|
| 346 |
+
|
| 347 |
+
"model_mobilenet": "MobileNet V4 (Fast)",
|
| 348 |
+
"model_convnext": "ConvNeXt V2 Huge (Pro)",
|
| 349 |
+
"model_caformer": "Caformer B36 (New)",
|
| 350 |
+
"categorization_section": "### 🗂️ Mode-Based Categorization Settings",
|
| 351 |
+
"categorization_desc": "Configure separate categorization settings for each mode below.",
|
| 352 |
+
"tab_single": "Single Mode",
|
| 353 |
+
"tab_batch": "Batch Mode",
|
| 354 |
+
"tab_dual_left": "Dual Mode (Left)",
|
| 355 |
+
"tab_dual_right": "Dual Mode (Right)",
|
| 356 |
+
"enable_cat_single": "Enable Categorization in Single Mode",
|
| 357 |
+
"cat_single_label": "Single Mode Categories",
|
| 358 |
+
"enable_cat_batch": "Enable Categorization in Batch Mode",
|
| 359 |
+
"cat_batch_label": "Batch Mode Categories",
|
| 360 |
+
"enable_cat_dual1": "Dual Mode Image 1 (Left) Categorization",
|
| 361 |
+
"cat_dual1_label": "Dual Image 1 Categories",
|
| 362 |
+
"enable_cat_dual2": "Dual Mode Image 2 (Right) Categorization",
|
| 363 |
+
"cat_dual2_label": "Dual Image 2 Categories",
|
| 364 |
+
"model_sensitivity_section": "### 🤖 Model Sensitivity Settings",
|
| 365 |
+
"context_weight_label": "Context Weight",
|
| 366 |
+
"context_weight_info": "Intensity of removing 'Unknown' tags not in database during refinement.",
|
| 367 |
+
"joint_settings": "Joint Tagger Settings",
|
| 368 |
+
"use_joint": "Use Joint Tagger",
|
| 369 |
+
"joint_thresh": "Joint Threshold",
|
| 370 |
+
"cl_settings": "CL Tagger Settings",
|
| 371 |
+
"use_cl": "Use CL Tagger",
|
| 372 |
+
"cl_gen_thresh": "CL General/Meta/Model Threshold",
|
| 373 |
+
"cl_char_thresh": "CL Character/Copyright/Artist Threshold",
|
| 374 |
+
"pixai_settings": "PixAI Tagger Settings",
|
| 375 |
+
"use_pixai": "Use PixAI Tagger",
|
| 376 |
+
"pixai_gen_thresh": "PixAI General Threshold",
|
| 377 |
+
"pixai_char_thresh": "PixAI Character Threshold",
|
| 378 |
+
"anime_settings": "Anime Tagger (MobileNet/ConvNeXt/Caformer)",
|
| 379 |
+
"use_anime": "Use Anime Tagger",
|
| 380 |
+
"anime_model_label": "Model Selection",
|
| 381 |
+
"anime_thresh": "Tag Threshold",
|
| 382 |
+
"gemini_settings": "✨ Gemini Caption (Natural Language)",
|
| 383 |
+
"gemini_desc": "Describes images with **natural sentences** instead of just tag lists.",
|
| 384 |
+
"use_gemini": "Use Gemini Caption",
|
| 385 |
+
"gemini_api_key": "Google Gemini API Key",
|
| 386 |
+
"gemini_model": "Gemini Model",
|
| 387 |
+
"gemini_model_info": "Select the Gemini model version to use.",
|
| 388 |
+
"gemini_mode_label": "Gemini Mode",
|
| 389 |
+
"gemini_mode_info": "Vision: Only looks at image. Tags: Only uses tags. Vision + Tags: Combines both.",
|
| 390 |
+
"gemini_system_instr": "System Instruction",
|
| 391 |
+
"gemini_system_info": "Main instruction defining the model's behavior and identity.",
|
| 392 |
+
"gemini_prompt_settings": "#### 📝 Mode-Based Prompt Settings",
|
| 393 |
+
"prompt_vision": "Vision Mode Prompt",
|
| 394 |
+
"prompt_tags": "Tags Mode Prompt",
|
| 395 |
+
"prompt_hybrid": "Vision + Tags Mode Prompt",
|
| 396 |
+
"save_success": "✅ All general and categorization settings saved. Please restart to apply language changes.",
|
| 397 |
+
|
| 398 |
+
# App Title
|
| 399 |
+
"app_title": "Ultra Pro Tagger V1",
|
| 400 |
+
"app_subtitle": "AI-Powered Automatic Tagging and Editing Suite",
|
| 401 |
+
|
| 402 |
+
# Main UI
|
| 403 |
+
"btn_close_app": "Close Application",
|
| 404 |
+
"btn_restart_app": "Restart Application",
|
| 405 |
+
|
| 406 |
+
# Single Tab
|
| 407 |
+
"tab_single_title": "✨ Single Tagging",
|
| 408 |
+
"btn_process_single": "Tag Image (With General Settings)",
|
| 409 |
+
"label_image_upload": "Upload Image",
|
| 410 |
+
"tab_raw_tags": "Raw Tags",
|
| 411 |
+
"label_unique_tags": "Merged Unique Tags",
|
| 412 |
+
"btn_send_to_cat": "Send These Tags to Categorization ➡️",
|
| 413 |
+
"tab_refined_tags": "Refined",
|
| 414 |
+
"label_refined_tags": "Refined (Cleaned) Tags",
|
| 415 |
+
"tab_categorized_tags": "Categorized",
|
| 416 |
+
"label_categorized_tags": "Categorized Tags",
|
| 417 |
+
"header_wildcard": "### 📂 Add to Wildcard Pack",
|
| 418 |
+
"tab_gemini_caption": "Captioned (GeminiCaption)",
|
| 419 |
+
"label_gemini_caption": "Gemini Caption",
|
| 420 |
+
"tab_combined_caption": "Categorized + Captioned (GeminiCaption)",
|
| 421 |
+
"label_combined_caption": "Categorized and Gemini Combination",
|
| 422 |
+
|
| 423 |
+
# Batch Tab
|
| 424 |
+
"tab_batch_title": "📚 Batch Tagging",
|
| 425 |
+
"btn_process_batch": "Process Images (With General Settings)",
|
| 426 |
+
"label_batch_input": "Upload Images (Multiple Selection)",
|
| 427 |
+
"tab_batch_original": "Original Tags (Combined)",
|
| 428 |
+
"label_batch_original": "Original Tags from All Images",
|
| 429 |
+
"tab_batch_refined": "Refined (Combined)",
|
| 430 |
+
"label_batch_refined": "Refined Tags from All Images",
|
| 431 |
+
"tab_batch_cat": "Categorized (Line by Line)",
|
| 432 |
+
"tab_batch_gemini": "Gemini Caption (All)",
|
| 433 |
+
"label_batch_gemini": "Captions for All Images",
|
| 434 |
+
"tab_batch_html": "Detailed HTML Report",
|
| 435 |
+
"label_batch_html": "Detailed Results",
|
| 436 |
+
"btn_batch_download": "Download All Result Tags (.txt)",
|
| 437 |
+
|
| 438 |
+
# Dual Tab
|
| 439 |
+
"tab_dual_title": "↔️ Dual Tagging",
|
| 440 |
+
"dual_note": "<p style='color:#666; font-size:0.9em;'>Note: Dual mode uses separate categorization settings for each image from the 'Settings' tab.</p>",
|
| 441 |
+
"btn_process_dual": "Compare and Tag Images",
|
| 442 |
+
"label_img1": "Image 1",
|
| 443 |
+
"label_img2": "Image 2",
|
| 444 |
+
"tab_dual_combined": "Combined",
|
| 445 |
+
"label_dual_combined": "Combined Unique Tags",
|
| 446 |
+
"label_dual_refined": "Combined Refined Tags",
|
| 447 |
+
"label_dual_cat": "Combined Categorized",
|
| 448 |
+
"label_gemini_img1": "Image 1 Gemini",
|
| 449 |
+
"label_gemini_img2": "Image 2 Gemini",
|
| 450 |
+
|
| 451 |
+
# Tools Tab
|
| 452 |
+
"tab_prompt_gen": "📝 Prompt Generator",
|
| 453 |
+
"tab_category_tools": "🏷️ Category Tools",
|
| 454 |
+
"tab_rule_management": "🔁 Rule Management",
|
| 455 |
+
"tab_text_tools": "📝 Text Tools",
|
| 456 |
+
"tab_image_tools": "🖼️ Image Tools",
|
| 457 |
+
"tab_video_creator": "🎬 Video Creator",
|
| 458 |
+
"tab_art_studio": "🎨 Art Studio",
|
| 459 |
+
"label_tags_input": "Enter Tags (comma separated)",
|
| 460 |
+
"placeholder_tags_input": "example: 1girl, blue hair, long hair, cat ears...",
|
| 461 |
+
|
| 462 |
+
# Category Tools
|
| 463 |
+
"cat_live_tab_title": "Live Categorization",
|
| 464 |
+
"cat_live_header": "<h2 style='color:#2563eb'>Categorize Tags</h2>",
|
| 465 |
+
"cat_live_main_tab": "Main Categorization",
|
| 466 |
+
"cat_live_main_select": "Select Main Categories to Show",
|
| 467 |
+
"cat_live_main_btn": "Categorize by Main",
|
| 468 |
+
"cat_live_unmatched_btn": "⚠️ Transfer Unmatched to Editor and Edit ➡️",
|
| 469 |
+
"cat_live_main_output": "Main Categorized Tags",
|
| 470 |
+
"cat_live_sub_tab": "Sub Categorization",
|
| 471 |
+
"cat_live_sub_select": "Select Sub Categories to Show",
|
| 472 |
+
"cat_live_sub_btn": "Categorize by Sub",
|
| 473 |
+
"cat_live_sub_output": "Sub Categorized Tags",
|
| 474 |
+
"cat_manage_tab_title": "Category/Tag Editing",
|
| 475 |
+
"cat_manage_header": "<h3 style='color:#007bff;'>Manage Category and Tag Files</h3>",
|
| 476 |
+
"cat_manage_main_accordion": "Main Category Management",
|
| 477 |
+
"cat_manage_main_grid": "All Categories",
|
| 478 |
+
"cat_manage_main_refresh": "↻ Refresh Main Categories",
|
| 479 |
+
"cat_manage_main_select": "Select Category",
|
| 480 |
+
"cat_manage_tags_input": "Tags (comma separated)",
|
| 481 |
+
"btn_add": "Add",
|
| 482 |
+
"btn_delete": "Delete",
|
| 483 |
+
"cat_manage_sub_accordion": "Sub Category Management",
|
| 484 |
+
"cat_manage_sub_grid": "All Sub Categories",
|
| 485 |
+
"cat_manage_sub_refresh": "↻ Refresh Sub Categories",
|
| 486 |
+
# Category Redesign
|
| 487 |
+
"cat_redesign_live_tab": "⚡ Live Test",
|
| 488 |
+
"cat_redesign_main_tab": "📂 Main Category Management",
|
| 489 |
+
"cat_redesign_sub_tab": "📁 Sub Category Management",
|
| 490 |
+
"cat_manage_sub_select": "Select Sub Category",
|
| 491 |
+
|
| 492 |
+
# Image Tools
|
| 493 |
+
"img_tools_header": "<h3 style='color:#0077b6;'>Batch Image Processing</h3>",
|
| 494 |
+
"img_tools_paths_input": "Folder Paths (One per line)",
|
| 495 |
+
"img_tools_select_folder_btn": "📁 Select Folder",
|
| 496 |
+
"img_tools_clear_btn": "🗑️ Clear",
|
| 497 |
+
"img_tools_save_settings_btn": "Save Path and Tool Settings",
|
| 498 |
+
"img_tools_res_accordion": "1. 📐 Resolution",
|
| 499 |
+
"img_tools_scale_label": "Scale",
|
| 500 |
+
"btn_apply": "Apply",
|
| 501 |
+
"img_tools_rename_accordion": "2. 📝 Renaming (Templated)",
|
| 502 |
+
"img_tools_rename_desc": "Renames files sequentially. Template must include **`{Number}`**.",
|
| 503 |
+
"img_tools_template_select": "Ready Templates",
|
| 504 |
+
"img_tools_delete_template_btn": "🗑️ Delete Template",
|
| 505 |
+
"img_tools_new_template_input": "Add New Template",
|
| 506 |
+
"img_tools_new_template_placeholder": "Ex: Adoptable {Number} (Elf)",
|
| 507 |
+
"img_tools_add_template_btn": "➕ Add to List",
|
| 508 |
+
"img_tools_start_num": "Start Number",
|
| 509 |
+
"img_tools_digit_count": "Digits (e.g. 3 for 001)",
|
| 510 |
+
"img_tools_rename_btn": "Rename Files",
|
| 511 |
+
"img_tools_watermark_accordion": "3. 🖋️ Watermark",
|
| 512 |
+
"img_tools_watermark_default": "Sample Watermark",
|
| 513 |
+
"img_tools_watermark_text": "Text",
|
| 514 |
+
"img_tools_watermark_opacity": "Opacity",
|
| 515 |
+
"img_tools_watermark_size": "Font Size",
|
| 516 |
+
"img_tools_watermark_angle": "Angle",
|
| 517 |
+
"img_tools_format_accordion": "4. 🖼️ Format",
|
| 518 |
+
"img_tools_format_label": "Format",
|
| 519 |
+
"btn_convert": "Convert",
|
| 520 |
+
"img_tools_bc_accordion": "5. 🔅 Brightness/Contrast",
|
| 521 |
+
"img_tools_brightness": "Brightness",
|
| 522 |
+
"img_tools_contrast": "Contrast",
|
| 523 |
+
"img_tools_ds_accordion": "6. 🧼 Denoise/Sharpen",
|
| 524 |
+
"img_tools_denoise": "Denoise",
|
| 525 |
+
"img_tools_sharpen": "Sharpen",
|
| 526 |
+
|
| 527 |
+
# Text Tools
|
| 528 |
+
"txt_tools_header": "<h3 style='color:#28a745;'>Manual Text Tools</h3>",
|
| 529 |
+
"txt_tools_input": "Text to Process",
|
| 530 |
+
"txt_tools_tab_clean": "Cleaning",
|
| 531 |
+
"txt_tools_clean_btn": "Clean and Uniquify",
|
| 532 |
+
"label_result": "Result",
|
| 533 |
+
"txt_tools_tab_format": "Change Format",
|
| 534 |
+
"txt_tools_lines_to_comma": "Lines -> Comma",
|
| 535 |
+
"txt_tools_comma_to_lines": "Comma -> Lines",
|
| 536 |
+
"txt_tools_tab_random": "Select Random",
|
| 537 |
+
"txt_tools_line_count": "Line Count",
|
| 538 |
+
"btn_select": "Select",
|
| 539 |
+
"txt_tools_tab_refine": "Refine",
|
| 540 |
+
"txt_tools_refine_desc": "Words to remove are read from **`{filename}`**.",
|
| 541 |
+
"btn_filter": "Filter",
|
| 542 |
+
|
| 543 |
+
# Rule Tools
|
| 544 |
+
"rule_tools_intro": "Manage how tags are replaced, merged, and appended to results in this area.",
|
| 545 |
+
"rule_rep_desc": "Automatically replaces tags with other tags.",
|
| 546 |
+
"rule_rep_accordion": "Replacement Rules (Regex Supported)",
|
| 547 |
+
"rule_active_file": "Active File",
|
| 548 |
+
"rule_new_file_name": "New File Name",
|
| 549 |
+
"rule_create_file_btn": "Create New File",
|
| 550 |
+
"rule_tab_quick_add": "Quick Add Rule",
|
| 551 |
+
"rule_rep_old": "Old Tag (or Regex)",
|
| 552 |
+
"rule_rep_new": "New Tag",
|
| 553 |
+
"rule_add_to_file_btn": "Add Rule to File",
|
| 554 |
+
"rule_tab_edit_file": "Edit File",
|
| 555 |
+
"rule_file_content_manual": "File Content (Manual Edit)",
|
| 556 |
+
"rule_save_all_btn": "Save All Content",
|
| 557 |
+
"rule_syn_accordion": "Merging Rules (Synonyms)",
|
| 558 |
+
"rule_syn_intro": "Merges multiple tags into one main tag (Deletes others).",
|
| 559 |
+
"rule_syn_main": "Main Tag (Kept)",
|
| 560 |
+
"rule_syn_remove": "Synonyms to Delete (Comma separated)",
|
| 561 |
+
"rule_add_accordion": "Addition Rules (Auto Append)",
|
| 562 |
+
"rule_add_intro": "Tags in this section are automatically appended to the end of the result list.",
|
| 563 |
+
"rule_add_content_input": "Tags to Add (Line by line or comma)",
|
| 564 |
+
"rule_add_save_btn": "Save Addition Rules",
|
| 565 |
+
|
| 566 |
+
# Art Tools
|
| 567 |
+
"art_header": "### 🎨 Furry & Adoptable Tools",
|
| 568 |
+
"art_desc": "Grid creation tools for sales.",
|
| 569 |
+
"art_tab_grid": "田 Adoptable Grid Creator",
|
| 570 |
+
"art_file_input": "Select Images (Multiple)",
|
| 571 |
+
"art_grid_settings": "Grid Settings",
|
| 572 |
+
"art_cols": "Columns (How many images side-by-side?)",
|
| 573 |
+
"art_bg_color": "Background Color",
|
| 574 |
+
"art_label_settings": "Label Settings",
|
| 575 |
+
"art_add_labels_check": "Add Text Below Image",
|
| 576 |
+
"art_label_type": "Label Type",
|
| 577 |
+
"art_start_num": "Start Number / Price",
|
| 578 |
+
"art_create_btn": "Create Grid",
|
| 579 |
+
"art_output": "Grid Result",
|
| 580 |
+
|
| 581 |
+
# Prompt Generator
|
| 582 |
+
"prompt_template_mgmt_header": "### 📝 Template Management",
|
| 583 |
+
"prompt_select_template": "Select Template",
|
| 584 |
+
"prompt_new_template_name": "New Template Name",
|
| 585 |
+
"prompt_template_content": "Template Content",
|
| 586 |
+
"btn_update": "💾 Update",
|
| 587 |
+
"btn_add_new": "➕ Add New",
|
| 588 |
+
"prompt_generation_header": "### 🚀 Generation",
|
| 589 |
+
"prompt_gen_btn": "GENERATE SELECTED TEMPLATE",
|
| 590 |
+
"prompt_gen_all_btn": "GENERATE ALL TEMPLATES",
|
| 591 |
+
"prompt_var_settings_accordion": "🛠️ Variable Settings (Add/Edit Options)",
|
| 592 |
+
"prompt_select_var": "Select Variable to Edit",
|
| 593 |
+
"prompt_var_label": "Label Name",
|
| 594 |
+
"prompt_var_default": "Default Value",
|
| 595 |
+
"prompt_var_options": "Options (One per line)",
|
| 596 |
+
"prompt_update_var_btn": "Update Variable",
|
| 597 |
+
|
| 598 |
+
# Video Creator
|
| 599 |
+
"vid_header": "### 🎬 Pro Video Studio",
|
| 600 |
+
"vid_desc": "Combine images and music, select format and add effects.",
|
| 601 |
+
"vid_img_input": "1. Select Images (Multiple for Slideshow)",
|
| 602 |
+
"vid_audio_input": "2. Select Music (Multiple)",
|
| 603 |
+
"vid_resolution": "Video Format (Resolution)",
|
| 604 |
+
"vid_speed": "Processing Speed",
|
| 605 |
+
"vid_text_overlay": "Add Text Over Video (Optional)",
|
| 606 |
+
"vid_spectrum_check": "Add Audio Spectrum (WARNING: Increases render time significantly)",
|
| 607 |
+
"vid_create_btn": "CREATE VIDEO",
|
| 608 |
+
"vid_preview": "Preview",
|
| 609 |
+
"vid_status": "Process Status",
|
| 610 |
+
},
|
| 611 |
+
}
|
modules/managers/category_manager.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import functools
|
| 3 |
+
import re
|
| 4 |
+
import gradio as gr
|
| 5 |
+
import random
|
| 6 |
+
|
| 7 |
+
# --- Klasör Sabitleri ---
|
| 8 |
+
KATEGORILER_KLASORU = "data/rules/kategoriler"
|
| 9 |
+
ALT_KATEGORILER_KLASORU = "data/rules/alt_kategoriler"
|
| 10 |
+
|
| 11 |
+
# --- KATEGORİZASYON ---
|
| 12 |
+
|
| 13 |
+
@functools.lru_cache(maxsize=32)
|
| 14 |
+
def kategori_sozlugu_yukle():
|
| 15 |
+
kategori_sozluk = {}
|
| 16 |
+
if not os.path.exists(KATEGORILER_KLASORU): os.makedirs(KATEGORILER_KLASORU, exist_ok=True); return {}
|
| 17 |
+
try:
|
| 18 |
+
for dosya in os.listdir(KATEGORILER_KLASORU):
|
| 19 |
+
if dosya.endswith(".txt"):
|
| 20 |
+
kat = dosya.replace(".txt", "").replace("_", " ")
|
| 21 |
+
with open(os.path.join(KATEGORILER_KLASORU, dosya), "r", encoding="utf-8") as f:
|
| 22 |
+
kategori_sozluk[kat] = {s.strip().lower() for s in f if s.strip()}
|
| 23 |
+
except: pass
|
| 24 |
+
return kategori_sozluk
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def kategorizelendir_ileri(girdi_etiketleri_str, secilen_kategoriler_listesi, kategori_sozluk_ref):
|
| 29 |
+
if not girdi_etiketleri_str or not secilen_kategoriler_listesi: return ""
|
| 30 |
+
girdi = [t.strip().lower() for t in girdi_etiketleri_str.split(",") if t.strip()]
|
| 31 |
+
secilen = set(secilen_kategoriler_listesi)
|
| 32 |
+
sonuc = []
|
| 33 |
+
for t in girdi:
|
| 34 |
+
for k, v in kategori_sozluk_ref.items():
|
| 35 |
+
if k in secilen and t in v:
|
| 36 |
+
sonuc.append(t); break
|
| 37 |
+
return ", ".join(sorted(list(set(sonuc))))
|
| 38 |
+
|
| 39 |
+
def kategori_html_ve_kopyala(kategori_adi, etiketler):
|
| 40 |
+
renk_map = {
|
| 41 |
+
0: ("#fef9c3", "#fde047", "🔶"), 1: ("#dbeafe", "#60a5fa", "🔷"),
|
| 42 |
+
2: ("#fee2e2", "#f87171", "🟥"), 3: ("#dcfce7", "#4ade80", "🟩"),
|
| 43 |
+
4: ("#f3e8ff", "#c084fc", "🟪"), "DİĞER / EŞLEŞMEYENLER": ("#f1f5f9", "#94a3b8", "🚫")
|
| 44 |
+
}
|
| 45 |
+
idx = 0
|
| 46 |
+
try: idx = int(kategori_adi.split()[0])
|
| 47 |
+
except: pass
|
| 48 |
+
|
| 49 |
+
if kategori_adi.upper().startswith("DİĞER"): renk, border, ikon = renk_map["DİĞER / EŞLEŞMEYENLER"]
|
| 50 |
+
else: renk, border, ikon = renk_map.get(idx % 5, ("#f3f4f6", "#d1d5db", "🗂️"))
|
| 51 |
+
|
| 52 |
+
text_id = f"text_{random.randint(0,999999)}"
|
| 53 |
+
metin = ", ".join(etiketler)
|
| 54 |
+
chipler = "".join([f"<span style='background:rgba(255,255,255,0.6); padding:4px 8px; border-radius:6px; border:1px solid rgba(0,0,0,0.1); font-size:0.9em; color:#374151; display:inline-block;'>{e}</span>" for e in sorted(etiketler)])
|
| 55 |
+
|
| 56 |
+
html = f"""
|
| 57 |
+
<div style="background:{renk}; border:2px solid {border}; border-radius:12px; padding:12px; margin-bottom:15px; display:flex; flex-direction:column; gap:10px; box-shadow: 0 2px 5px rgba(0,0,0,0.05);">
|
| 58 |
+
<div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid rgba(0,0,0,0.05); padding-bottom:8px;">
|
| 59 |
+
<div style="font-weight:bold; font-size:1.05em; color:#1f2937; display:flex; align-items:center; gap:6px;">
|
| 60 |
+
<span>{ikon}</span> <span>{kategori_adi.upper()}</span>
|
| 61 |
+
<span style="background:rgba(0,0,0,0.1); color:#4b5563; font-size:0.8em; padding:1px 6px; border-radius:10px;">{len(etiketler)}</span>
|
| 62 |
+
</div>
|
| 63 |
+
<button onclick="navigator.clipboard.writeText(document.getElementById('{text_id}').value); this.innerText='Kopyalandı!'" style="background:#2563eb; color:white; border:none; padding:4px 12px; border-radius:6px; cursor:pointer; font-size:0.85em; font-weight:500; transition: background 0.2s;">Kopyala</button>
|
| 64 |
+
</div>
|
| 65 |
+
<div style="display:flex; flex-wrap:wrap; gap:6px; max-height:150px; overflow-y:auto; padding-right:5px;">
|
| 66 |
+
{chipler if etiketler else "<span style='color:#6b7280; font-style:italic; font-size:0.9em;'>Bu kategoride etiket bulunamadı.</span>"}
|
| 67 |
+
</div>
|
| 68 |
+
<textarea id='{text_id}' style='display:none;'>{metin}</textarea>
|
| 69 |
+
</div>
|
| 70 |
+
"""
|
| 71 |
+
return html
|
| 72 |
+
|
| 73 |
+
def full_kategori_html_ve_kopyala(kategoriler, etiketler_list):
|
| 74 |
+
html = "<div style='margin-bottom:20px;'>"
|
| 75 |
+
for k, e in zip(kategoriler, etiketler_list): html += kategori_html_ve_kopyala(k, e)
|
| 76 |
+
return html + "</div>"
|
| 77 |
+
|
| 78 |
+
def etiket_ekle(kategori, etiketler_str):
|
| 79 |
+
if not kategori: return gr.update(value="Kategori seçin")
|
| 80 |
+
yeni_etiketler = {e.strip().lower() for e in etiketler_str.split(",") if e.strip()}
|
| 81 |
+
if not yeni_etiketler: return gr.update(value="Eklenecek geçerli etiket yok.")
|
| 82 |
+
try:
|
| 83 |
+
if os.path.exists(KATEGORILER_KLASORU):
|
| 84 |
+
for dosya in os.listdir(KATEGORILER_KLASORU):
|
| 85 |
+
if dosya.endswith(".txt"):
|
| 86 |
+
dosya_kategori_adi = dosya.replace(".txt", "").replace("_", " ")
|
| 87 |
+
if dosya_kategori_adi == kategori: continue
|
| 88 |
+
dosya_yolu = os.path.join(KATEGORILER_KLASORU, dosya)
|
| 89 |
+
mevcut_diger = set()
|
| 90 |
+
with open(dosya_yolu, "r", encoding="utf-8") as f: mevcut_diger = {l.strip().lower() for l in f if l.strip()}
|
| 91 |
+
if not mevcut_diger.isdisjoint(yeni_etiketler):
|
| 92 |
+
guncel_etiketler = mevcut_diger - yeni_etiketler
|
| 93 |
+
with open(dosya_yolu, "w", encoding="utf-8") as f:
|
| 94 |
+
for e in sorted(guncel_etiketler): f.write(e + "\n")
|
| 95 |
+
safe = re.sub(r'[^\w\.-]', '_', kategori)
|
| 96 |
+
path = os.path.join(KATEGORILER_KLASORU, f"{safe}.txt")
|
| 97 |
+
mevcut_hedef = set()
|
| 98 |
+
if os.path.exists(path):
|
| 99 |
+
with open(path, "r", encoding="utf-8") as f: mevcut_hedef = {l.strip().lower() for l in f if l.strip()}
|
| 100 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 101 |
+
for e in sorted(mevcut_hedef | yeni_etiketler): f.write(e + "\n")
|
| 102 |
+
kategori_sozlugu_yukle.cache_clear()
|
| 103 |
+
return gr.update(value=f"✅ {len(yeni_etiketler)} etiket '{kategori}' kategorisine taşındı.", visible=True)
|
| 104 |
+
except Exception as e: return gr.update(value=f"❌ Hata: {e}")
|
| 105 |
+
|
| 106 |
+
def etiket_sil(kategori, etiketler_str):
|
| 107 |
+
safe = re.sub(r'[^\w\.-]', '_', kategori)
|
| 108 |
+
path = os.path.join(KATEGORILER_KLASORU, f"{safe}.txt")
|
| 109 |
+
if not os.path.exists(path): return gr.update(value="Dosya yok")
|
| 110 |
+
sil = {e.strip().lower() for e in etiketler_str.split(",") if e.strip()}
|
| 111 |
+
try:
|
| 112 |
+
mevcut = {l.strip().lower() for l in open(path, encoding="utf-8") if l.strip()}
|
| 113 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 114 |
+
for e in sorted(mevcut - sil): f.write(e + "\n")
|
| 115 |
+
kategori_sozlugu_yukle.cache_clear()
|
| 116 |
+
return gr.update(value=f"{len(sil & mevcut)} etiket silindi.", visible=True)
|
| 117 |
+
except Exception as e: return gr.update(value=f"Hata: {e}")
|
| 118 |
+
|
| 119 |
+
def kategorileri_grid_html():
|
| 120 |
+
d = kategori_sozlugu_yukle()
|
| 121 |
+
html = "<div class='kategori-grid-app2'>"
|
| 122 |
+
for i, k in enumerate(sorted(d.keys())):
|
| 123 |
+
count = len(d[k])
|
| 124 |
+
# Kart yapısı
|
| 125 |
+
html += f"""
|
| 126 |
+
<div class='kategori-card-app2'>
|
| 127 |
+
<div class='kategori-title-app2'>{k.upper()}</div>
|
| 128 |
+
<div style='margin-top:auto; display:flex; justify-content:space-between; align-items:center;'>
|
| 129 |
+
<span style='font-size:0.85em; color:#64748b;'>Toplam Etiket</span>
|
| 130 |
+
<span style='background:#f1f5f9; color:#334151; padding:4px 12px; border-radius:20px; font-weight:bold; font-size:0.9em; border:1px solid #e2e8f0;'>{count}</span>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
"""
|
| 134 |
+
return html + "</div>"
|
| 135 |
+
|
| 136 |
+
def grid_guncelle():
|
| 137 |
+
kategori_sozlugu_yukle.cache_clear()
|
| 138 |
+
return kategorileri_grid_html()
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def kategori_listesi(): return list(kategori_sozlugu_yukle().keys())
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def filter_and_display_main_categorized_tags(girdi_etiketleri_str, selected_main_categories):
|
| 148 |
+
ref = kategori_sozlugu_yukle()
|
| 149 |
+
girdi_etiketleri = {etiket.strip().lower() for etiket in girdi_etiketleri_str.split(",") if etiket.strip()}
|
| 150 |
+
sonuc = {k: [] for k in ref if k in (selected_main_categories if selected_main_categories else ref.keys())}
|
| 151 |
+
diger = set()
|
| 152 |
+
for etiket in girdi_etiketleri:
|
| 153 |
+
found = False
|
| 154 |
+
for kategori, kume in ref.items():
|
| 155 |
+
if etiket in kume and kategori in sonuc:
|
| 156 |
+
sonuc[kategori].append(etiket); found = True; break
|
| 157 |
+
if not found: diger.add(etiket)
|
| 158 |
+
cats, tags = [], []
|
| 159 |
+
if diger: cats.append("DİĞER / EŞLEŞMEYENLER"); tags.append(sorted(list(diger)))
|
| 160 |
+
for k in sorted(sonuc.keys()): cats.append(k); tags.append(sorted(sonuc[k]))
|
| 161 |
+
|
| 162 |
+
html_output = full_kategori_html_ve_kopyala(cats, tags)
|
| 163 |
+
eslesmeyen_str = ", ".join(sorted(list(diger)))
|
| 164 |
+
return html_output, eslesmeyen_str
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
|
modules/managers/localization_manager.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from modules.config_manager import load_config
|
| 3 |
+
from modules.localization.languages import STRINGS
|
| 4 |
+
|
| 5 |
+
class LocalizationManager:
|
| 6 |
+
_instance = None
|
| 7 |
+
|
| 8 |
+
def __new__(cls):
|
| 9 |
+
if cls._instance is None:
|
| 10 |
+
cls._instance = super(LocalizationManager, cls).__new__(cls)
|
| 11 |
+
cls._instance.config = load_config()
|
| 12 |
+
cls._instance.language = cls._instance.config.get("general_settings", {}).get("language", "tr")
|
| 13 |
+
cls._instance.strings = STRINGS.get(cls._instance.language, STRINGS["tr"])
|
| 14 |
+
return cls._instance
|
| 15 |
+
|
| 16 |
+
def get(self, key):
|
| 17 |
+
"""Returns the localized string for the given key."""
|
| 18 |
+
return self.strings.get(key, key)
|
| 19 |
+
|
| 20 |
+
def set_language(self, lang_code):
|
| 21 |
+
if lang_code in STRINGS:
|
| 22 |
+
self.language = lang_code
|
| 23 |
+
self.strings = STRINGS[lang_code]
|
| 24 |
+
return True
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
# Global instance
|
| 28 |
+
loc = LocalizationManager()
|
| 29 |
+
|
| 30 |
+
def get_str(key):
|
| 31 |
+
return loc.get(key)
|
modules/managers/rule_manager.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import functools
|
| 3 |
+
import re
|
| 4 |
+
import gradio as gr
|
| 5 |
+
|
| 6 |
+
# --- Klasör Sabitleri ---
|
| 7 |
+
REPLACEMENT_RULES_KLASORU = "data/rules/replacement_rules"
|
| 8 |
+
SYNONYM_RULES_KLASORU = "data/rules/synonym_rules"
|
| 9 |
+
ADDITION_RULES_KLASORU = "data/rules/addition_rules"
|
| 10 |
+
|
| 11 |
+
# --- DEĞİŞTİRME KURALLARI (REPLACEMENT) ---
|
| 12 |
+
|
| 13 |
+
@functools.lru_cache(maxsize=32)
|
| 14 |
+
def get_replacement_files():
|
| 15 |
+
if not os.path.exists(REPLACEMENT_RULES_KLASORU): os.makedirs(REPLACEMENT_RULES_KLASORU); return []
|
| 16 |
+
return [os.path.join(REPLACEMENT_RULES_KLASORU, f) for f in os.listdir(REPLACEMENT_RULES_KLASORU) if f.endswith(".txt")]
|
| 17 |
+
|
| 18 |
+
@functools.lru_cache(maxsize=32)
|
| 19 |
+
def load_replacement_rules(file_path):
|
| 20 |
+
rules = {}
|
| 21 |
+
if not file_path or not os.path.exists(file_path): return rules
|
| 22 |
+
try:
|
| 23 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 24 |
+
for line in f:
|
| 25 |
+
line = line.strip()
|
| 26 |
+
if not line or line.startswith('#'): continue
|
| 27 |
+
parts = line.split('->')
|
| 28 |
+
if len(parts) == 2:
|
| 29 |
+
old_tag = parts[0].strip().lower().replace(r'\b', '')
|
| 30 |
+
new_tag = parts[1].strip().lower()
|
| 31 |
+
rules[old_tag] = new_tag
|
| 32 |
+
return rules
|
| 33 |
+
except: return {}
|
| 34 |
+
|
| 35 |
+
def apply_replacements(tags_string, replacement_file_path):
|
| 36 |
+
if not tags_string or not replacement_file_path: return tags_string
|
| 37 |
+
rules = load_replacement_rules(replacement_file_path)
|
| 38 |
+
if not rules: return tags_string
|
| 39 |
+
tags = [t.strip().lower() for t in tags_string.split(",") if t.strip()]
|
| 40 |
+
processed_tags = []
|
| 41 |
+
for tag in tags:
|
| 42 |
+
if tag in rules: processed_tags.append(rules[tag])
|
| 43 |
+
else: processed_tags.append(tag)
|
| 44 |
+
return ", ".join(processed_tags)
|
| 45 |
+
|
| 46 |
+
def create_replacement_file(file_name):
|
| 47 |
+
if not file_name.strip(): return "Hata"
|
| 48 |
+
path = os.path.join(REPLACEMENT_RULES_KLASORU, f"{re.sub(r'[^\w\.-]', '_', file_name)}.txt")
|
| 49 |
+
if not os.path.exists(REPLACEMENT_RULES_KLASORU): os.makedirs(REPLACEMENT_RULES_KLASORU)
|
| 50 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 51 |
+
f.write("# Eski Etiket -> Yeni Etiket\n")
|
| 52 |
+
f.write("# blue_hair -> cyan hair\n")
|
| 53 |
+
f.write("# Artık regex kullanmanıza gerek yoktur, tam eşleşme yapılır.\n")
|
| 54 |
+
get_replacement_files.cache_clear()
|
| 55 |
+
return f"Oluşturuldu: {path}"
|
| 56 |
+
|
| 57 |
+
def read_replacement_file_content(file_path):
|
| 58 |
+
try: return open(file_path, "r", encoding="utf-8").read()
|
| 59 |
+
except: return ""
|
| 60 |
+
|
| 61 |
+
def save_replacement_file_content(file_path, content):
|
| 62 |
+
try:
|
| 63 |
+
with open(file_path, "w", encoding="utf-8") as f: f.write(content)
|
| 64 |
+
load_replacement_rules.cache_clear()
|
| 65 |
+
return "Kaydedildi"
|
| 66 |
+
except: return "Hata"
|
| 67 |
+
|
| 68 |
+
def append_replacement_rule_to_file(file_path, old_tag, new_tag):
|
| 69 |
+
if not file_path or not os.path.exists(file_path): return gr.update(value="❌ Geçerli bir dosya seçili değil.")
|
| 70 |
+
if not old_tag.strip(): return gr.update(value="❌ Eski etiket boş olamaz.")
|
| 71 |
+
clean_old = old_tag.strip()
|
| 72 |
+
clean_new = new_tag.strip()
|
| 73 |
+
rule_line = f"\n{clean_old} -> {clean_new}"
|
| 74 |
+
try:
|
| 75 |
+
with open(file_path, "a", encoding="utf-8") as f: f.write(rule_line)
|
| 76 |
+
load_replacement_rules.cache_clear()
|
| 77 |
+
return gr.update(value=f"✅ Kural eklendi: '{clean_old}' -> '{clean_new}'", visible=True)
|
| 78 |
+
except Exception as e: return gr.update(value=f"❌ Hata: {e}", visible=True)
|
| 79 |
+
|
| 80 |
+
# --- BİRLEŞTİRME KURALLARI (SYNONYM) ---
|
| 81 |
+
|
| 82 |
+
@functools.lru_cache(maxsize=32)
|
| 83 |
+
def get_synonym_files():
|
| 84 |
+
if not os.path.exists(SYNONYM_RULES_KLASORU): os.makedirs(SYNONYM_RULES_KLASORU); return []
|
| 85 |
+
return [os.path.join(SYNONYM_RULES_KLASORU, f) for f in os.listdir(SYNONYM_RULES_KLASORU) if f.endswith(".txt")]
|
| 86 |
+
|
| 87 |
+
@functools.lru_cache(maxsize=32)
|
| 88 |
+
def load_synonym_rules(file_path):
|
| 89 |
+
rules = {}
|
| 90 |
+
if not file_path or not os.path.exists(file_path): return rules
|
| 91 |
+
try:
|
| 92 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 93 |
+
for line in f:
|
| 94 |
+
if ':' in line and not line.strip().startswith('#'):
|
| 95 |
+
p = line.split(':', 1)
|
| 96 |
+
rules.setdefault(p[0].strip().lower(), set()).update({s.strip().lower() for s in p[1].split(',') if s.strip()})
|
| 97 |
+
return rules
|
| 98 |
+
except: return {}
|
| 99 |
+
|
| 100 |
+
def apply_synonym_consolidation(tags_string, synonym_file_path):
|
| 101 |
+
if not tags_string or not synonym_file_path: return tags_string
|
| 102 |
+
rules = load_synonym_rules(synonym_file_path)
|
| 103 |
+
tag_set = {t.strip() for t in tags_string.split(',') if t.strip()}
|
| 104 |
+
remove = set()
|
| 105 |
+
for main, syns in rules.items():
|
| 106 |
+
if main in tag_set:
|
| 107 |
+
for s in syns:
|
| 108 |
+
if s in tag_set: remove.add(s)
|
| 109 |
+
return ", ".join(list(tag_set - remove))
|
| 110 |
+
|
| 111 |
+
def create_synonym_file(file_name):
|
| 112 |
+
path = os.path.join(SYNONYM_RULES_KLASORU, f"{re.sub(r'[^\w\.-]', '_', file_name)}.txt")
|
| 113 |
+
if not os.path.exists(SYNONYM_RULES_KLASORU): os.makedirs(SYNONYM_RULES_KLASORU)
|
| 114 |
+
with open(path, "w", encoding="utf-8") as f: f.write("# main: s1, s2\n")
|
| 115 |
+
get_synonym_files.cache_clear()
|
| 116 |
+
return f"Oluşturuldu: {path}"
|
| 117 |
+
|
| 118 |
+
def read_synonym_file_content(file_path):
|
| 119 |
+
try: return open(file_path, "r", encoding="utf-8").read()
|
| 120 |
+
except: return ""
|
| 121 |
+
|
| 122 |
+
def save_synonym_file_content(file_path, content):
|
| 123 |
+
try:
|
| 124 |
+
with open(file_path, "w", encoding="utf-8") as f: f.write(content)
|
| 125 |
+
load_synonym_rules.cache_clear()
|
| 126 |
+
return "Kaydedildi"
|
| 127 |
+
except: return "Hata"
|
| 128 |
+
|
| 129 |
+
def append_synonym_rule_to_file(file_path, main_tag, remove_tags_str):
|
| 130 |
+
if not file_path or not os.path.exists(file_path): return gr.update(value="❌ Geçerli bir dosya seçili değil.")
|
| 131 |
+
if not main_tag.strip() or not remove_tags_str.strip(): return gr.update(value="❌ Ana etiket veya silinecekler boş olamaz.")
|
| 132 |
+
rule_line = f"\n{main_tag.strip()}: {remove_tags_str.strip()}"
|
| 133 |
+
try:
|
| 134 |
+
with open(file_path, "a", encoding="utf-8") as f: f.write(rule_line)
|
| 135 |
+
load_synonym_rules.cache_clear()
|
| 136 |
+
return gr.update(value=f"✅ Kural eklendi: '{main_tag}' şunları silecek: {remove_tags_str}", visible=True)
|
| 137 |
+
except Exception as e: return gr.update(value=f"❌ Hata: {e}", visible=True)
|
| 138 |
+
|
| 139 |
+
# --- EKLEME KURALLARI (ADDITION) ---
|
| 140 |
+
|
| 141 |
+
@functools.lru_cache(maxsize=32)
|
| 142 |
+
def get_addition_files():
|
| 143 |
+
if not os.path.exists(ADDITION_RULES_KLASORU): os.makedirs(ADDITION_RULES_KLASORU); return []
|
| 144 |
+
return [os.path.join(ADDITION_RULES_KLASORU, f) for f in os.listdir(ADDITION_RULES_KLASORU) if f.endswith(".txt")]
|
| 145 |
+
|
| 146 |
+
@functools.lru_cache(maxsize=32)
|
| 147 |
+
def load_addition_rules(file_path):
|
| 148 |
+
tags = []
|
| 149 |
+
if not file_path or not os.path.exists(file_path): return ""
|
| 150 |
+
try:
|
| 151 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 152 |
+
for line in f:
|
| 153 |
+
line = line.strip()
|
| 154 |
+
if not line or line.startswith('#'): continue
|
| 155 |
+
parts = [t.strip() for t in line.split(',') if t.strip()]
|
| 156 |
+
tags.extend(parts)
|
| 157 |
+
return ", ".join(tags)
|
| 158 |
+
except: return ""
|
| 159 |
+
|
| 160 |
+
def apply_additions(tags_string, addition_file_path):
|
| 161 |
+
if not addition_file_path: return tags_string
|
| 162 |
+
additional_tags = load_addition_rules(addition_file_path)
|
| 163 |
+
if not additional_tags: return tags_string
|
| 164 |
+
if not tags_string.strip(): return additional_tags
|
| 165 |
+
return f"{tags_string}, {additional_tags}"
|
| 166 |
+
|
| 167 |
+
def create_addition_file(file_name):
|
| 168 |
+
if not file_name.strip(): return "Hata"
|
| 169 |
+
path = os.path.join(ADDITION_RULES_KLASORU, f"{re.sub(r'[^\w\.-]', '_', file_name)}.txt")
|
| 170 |
+
if not os.path.exists(ADDITION_RULES_KLASORU): os.makedirs(ADDITION_RULES_KLASORU)
|
| 171 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 172 |
+
f.write("# İşlem sonunda eklenecek etiketler (virgülle veya yeni satırla)\n")
|
| 173 |
+
f.write("# best quality, masterpiece\n")
|
| 174 |
+
get_addition_files.cache_clear()
|
| 175 |
+
return f"Oluşturuldu: {path}"
|
| 176 |
+
|
| 177 |
+
def read_addition_file_content(file_path):
|
| 178 |
+
try: return open(file_path, "r", encoding="utf-8").read()
|
| 179 |
+
except: return ""
|
| 180 |
+
|
| 181 |
+
def save_addition_file_content(file_path, content):
|
| 182 |
+
try:
|
| 183 |
+
with open(file_path, "w", encoding="utf-8") as f: f.write(content)
|
| 184 |
+
load_addition_rules.cache_clear()
|
| 185 |
+
return "Kaydedildi"
|
| 186 |
+
except: return "Hata"
|
modules/prompt_generator.py
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/prompt_generator.py
|
| 2 |
+
|
| 3 |
+
import gradio as gr
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
import random
|
| 8 |
+
from modules.localization.languages import get_str
|
| 9 |
+
|
| 10 |
+
# --- Veriler ve Sabitler ---
|
| 11 |
+
|
| 12 |
+
SABLON_DOSYA_YOLU = "data/configs/prompt_sablonlar.json"
|
| 13 |
+
DEGISKENLER_DOSYA_YOLU = "data/configs/prompt_variables.json"
|
| 14 |
+
|
| 15 |
+
# Varsayılan Veriler (İlk kurulum için)
|
| 16 |
+
varsayilan_etiketler_ham = [
|
| 17 |
+
"Güzellik ve Makyaj", "Karakterin Yaşı", "Göz Rengi", "Saç Rengi ve Stili",
|
| 18 |
+
"Göğüs ve Göğüs Büyüklüğü", "Dönüşüm Öncesi Yüz İfadesi", "Dönüşüm Sırasındaki Yüz İfadesi",
|
| 19 |
+
"Dönüşüm Sonrası Yüz İfadesi", "Dönüşülen Hayvan", "Dönüşülen Hayvanın Fiziksel Özellikleri",
|
| 20 |
+
"Arkaplan/Mekan", "Dönüşüm Öncesi Poz", "Dönüşüm Esnası Poz 1", "Dönüşüm Esnası Poz 2",
|
| 21 |
+
"Dönüşüm Sonu Poz", "Dönüşüm Sonrası Poz 1 (Nude 1)", "Dönüşüm Sonrası Poz 2 (Nude 2)",
|
| 22 |
+
"Dönüşüm Sonrası Poz 3 (Blowjob)", "Dönüşüm Sonrası Poz 4 (Blowjob 2)", "Dönüşüm Sonrası Poz 5 (Missionary Sex)",
|
| 23 |
+
"Dönüşüm Sonrası Poz 6 (Cowgirl Position Sex)", "Dönüşüm Sonrası Poz 7 (Doggystyle Sex)",
|
| 24 |
+
"Dönüşüm Sonrası Poz 8 (Doggystyle Sex From Side)", "Dönüşüm Sonrası Poz 9 (Carrying Sex)",
|
| 25 |
+
"Dönüşüm Sonrası Poz 10 (On Side Sex)", "Dönüşüm Sonrası Poz 11 (Reverse Cowgirl Sex)",
|
| 26 |
+
"Dönüşüm Sonrası Poz 12 (After Sex Ejaculation)"
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
varsayilan_degerler_ham = [
|
| 30 |
+
"sexy", "25", "green eyes", "hair, red hair, long hair",
|
| 31 |
+
"breasts, fake tits, huge breasts", "seductive smile, closed mouth",
|
| 32 |
+
"(open mouth:1.1), (shocked, surprised expression:1.2), (wide-eyed:1.3), (screaming in pain:1.2), crying, gritting teeth, painful expression",
|
| 33 |
+
"(moaning, embarrassed:1.2)", "deer",
|
| 34 |
+
"animal ears, anthro, biped, black nose, brown body, brown fur, deer, deer ears, deer girl, deer tail, fur, furry, furry female, inner ear fluff, mammal, markings, multicolored body, multicolored fur, short tail, spikes, spots, spotted body, spotted fur, tail, tuft, white body, white fur, antlers, white antlers",
|
| 35 |
+
"blurry, blurry background, forest, nature, outdoors, outside, plant, tree",
|
| 36 |
+
"accessory, bare shoulders, blue ribbon, blush, bodily fluids, breasts, cleavage, cleavage cutout, closed mouth, clothed, clothing, clothing cutout, cowboy shot, dress, gloves, handwear, looking at viewer, ribbon, ribbons, sensitive, short dress, sleeveless, sleeveless dress, solo, sweat, white clothing, white dress, white gloves",
|
| 37 |
+
"all fours, blush, collarbone, genitals, kneeling, leaning, leaning backward, looking at viewer, nipples, nude, solo",
|
| 38 |
+
"all fours, blush, collarbone, genitals, kneeling, leaning, leaning backward, looking at viewer, nipples, nude, solo",
|
| 39 |
+
"all fours, blush, collarbone, kneeling, leaning, leaning backward, looking at viewer, nipples, nude, solo",
|
| 40 |
+
"anus, breasts with fur, clitoral hood, clitoris, completely nude, explicit, genitals, leg up, legs up, looking at viewer, looking up, lying, nipples, nude, on back, open mouth, paw pose, presenting, presenting pussy, pussy, solo, spread legs, spreading, teeth, tongue, tongue out, from above",
|
| 41 |
+
"all fours, anus, barefoot, bodily fluids, breasts with fur, completely nude, explicit, from behind, genitals, looking at viewer, looking back, nipples, nude, open mouth, presenting, pussy, solo, teeth, tongue, tongue out",
|
| 42 |
+
"all fours, anthro on human, anthro penetrated, ass up, balls, blush, bodily fluids, butt from the front, completely nude, deepthroat, duo, erection, explicit, faceless character, faceless male, fellatio, first person view, genitals, human male penetrating, human male penetrating anthro, human on anthro, human penetrating anthro, human pov, human/anthro, looking at viewer, lying, male, male on anthro, male pov, male/anthro, nude, oral, oral penetration, penetrating pov, penetration, penile, penile penetration, penis, penis in mouth, pov, saliva, saliva on penis, seductive, sex, solo focus, spread legs, spreading, sweat, sweaty butt, testicles, vein, wet",
|
| 43 |
+
"anthro penetrated, balls, breasts with fur, completely nude, duo, erection, explicit, faceless character, faceless human, faceless male, fellatio, from side, genitals, hetero, human male penetrating, human male penetrating anthro, human on anthro, human penetrating, human penetrating anthro, human/anthro, humanoid genitalia, humanoid penis, looking at another, male, male/anthro, nipples, nude, open mouth, oral, oral penetration, penetration, penile, penile penetration, penis, penis in mouth, pink nipples, sex, side view, solo focus, testicles",
|
| 44 |
+
"anthro penetrated, breasts with fur, cum, cum from pussy, cum in pussy, cum inside, cum on fur, duo, erection, explicit, faceless character, faceless male, first person view, from front position, genitals, human male penetrating, human male penetrating anthro, human penetrating, human penetrating anthro, human pov, human/anthro, humanoid penis, leaking cum, looking pleasured, lying anthro, male, male pov, male/anthro, missionary, missionary position, nipples, open mouth, orgasm, penetrating pov, penetration, penile, penile penetration, penis, penis in pussy, pov, pov crotch, pussy, saliva, sex, solo focus, spread legs, spreading, tongue, tongue out, vaginal, vaginal penetration, from above, male kneeling",
|
| 45 |
+
"anthro penetrated, arms behind back, breasts with fur, collarbone, completely nude, cowgirl position, duo, erection, explicit, first person view, from front position, genitals, anthro on top, hetero, human male penetrating, human male penetrating anthro, human on anthro, human penetrating, human penetrating anthro, human/anthro, looking at viewer, male, male on anthro, male on bottom, male pov, male/anthro, nipples, nude, on bottom, on top, open mouth, penetration, penile, penile penetration, penis, penis in pussy, pov, pussy, sex, solo focus, spread legs, straddling, sweat, vaginal, vaginal penetration, vein, veiny penis, male lying, anthro kneeling",
|
| 46 |
+
"ahegao, all fours, anthro penetrated, anus, back, back boob, balls, big penis, blush, bodily fluids, breasts with fur, deep penetration, doggystyle, duo, erection, explicit, first person view, from behind, from behind position, fucked silly, genital fluids, genitals, glistening, glistening butt, hetero, huge penis, human male penetrating, human male penetrating anthro, human on anthro, human penetrating, human penetrating anthro, human/anthro, humanoid genitalia, humanoid penis, large penis, looking at viewer, looking back, looking back at viewer, looking pleasured, male, male pov, male/anthro, open mouth, penetrating pov, penetration, penile, penile penetration, penis, penis in pussy, pov, pussy, pussy juice, rear view, sex, sex from behind, solo focus, sweat, sweaty butt, tongue, tongue out, trembling, vaginal, vaginal fluids, vaginal penetration, vein, veiny penis",
|
| 47 |
+
"all fours, ambiguous penetration, anthro penetrated, bodily fluids, breasts with fur, breath, butt grab, completely nude, cum, cum in pussy, cum inside, cum overflow, doggystyle, duo, explicit, faceless character, faceless male, from behind position, genital fluids, hand on butt, hetero, human male penetrating, human male penetrating anthro, human penetrated, human penetrating, human/anthro, looking pleasured, male, male/anthro, motion lines, nipples, nude, open mouth, penetration, penile, saliva, sex, sex from behind, solo focus, sweat, tongue, tongue out",
|
| 48 |
+
"anthro penetrated, balls, breasts with fur, clitoris, cum, cum in pussy, cum inside, duo, erection, explicit, folded, fucked silly, full nelson, genital fluids, human male penetrating, human male penetrating anthro, human on anthro, human penetrating, human penetrating anthro, human/anthro, humanoid penis, large penis, legs up, looking pleasured, male, male/anthro, nipples, nude, open mouth, penetration, penile, penile penetration, penis, penis in pussy, pussy, reverse suspended congress, saliva, sex, sex from behind, solo focus, spread legs, teeth, testicles, tongue, tongue out, butt fur, feet out of frame, male standing, anthro on lap, fur legs",
|
| 49 |
+
"anthro penetrated, breasts with fur, clitoral hood, clitoris, duo, erection, explicit, faceless character, faceless male, human male penetrating, human male penetrating anthro, human on anthro, human penetrating, human penetrating anthro, human/anthro, leg lift, looking at viewer, lying, male, male on anthro, male/anthro, nipples, nude, on side, open mouth, penetration, penile, penile penetration, penis, penis in mouth, pussy, pussy juice, raised leg, sex, anthro focus, spread legs, spreading, vein, veiny penis, fur legs, male kneeling, anthro lying, male grabbing",
|
| 50 |
+
"anthro penetrated, anus, back, balls, big penis, breasts with fur, duo, erection, explicit, eye roll, faceless character, faceless human, faceless male, anthro on top, first person view, from behind, fucked silly, genital fluids, genitals, happy, hetero, human male penetrating, human male penetrating anthro, human on anthro, human penetrating, human penetrating anthro, human/anthro, humanoid penis, looking back, looking pleasured, lying, male, male on anthro, male on bottom, male pov, male/anthro, nipples, nude, on bottom, on top, open mouth, penetration, penile, penile penetration, penis, penis in pussy, pov, puckered anus, pussy, reverse cowgirl position, sex, sex from behind, solo focus, straddling, toned, tongue, tongue out, vaginal, vaginal penetration, vein, veiny penis, male lying, from below",
|
| 51 |
+
"after sex, anus, balls, breasts with fur, completely nude, cum, cum from pussy, cum in pussy, cum inside, cum on fur, cum on penis, cumdrip, duo, erection, explicit, faceless character, faceless male, first person view, hetero, human on anthro, human pov, human/anthro, humanoid penis, leaking cum, looking at viewer, lying, male, male pov, male/anthro, nipples, nude, on back, on back, open mouth, penis, pov, pussy, solo focus, spread legs, spreading, sweat, teeth, from above, anthro lying, male kneeling"
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
VARSAYILAN_SABLONLAR = {
|
| 55 |
+
"Kalıp 1: Dönüşüm Öncesi": "{0}, {1}yo, {2}, {3}, {4}, {5}, {11}, {10}",
|
| 56 |
+
"Kalıp 2: Dönüşüm Esnası 1": "(mammal humanoid, {8} humanoid:1.1), transformation, species transformation, {9}, The scene features a {8} hybrid, {8} fur, {8} tail, {8} snout and ears, photomorph, {6}, {0}, {1}yo, {2}, {3}, {4}, {12}, {10}",
|
| 57 |
+
"Kalıp 3: Dönüşüm Esnası 2": "(mammal humanoid, {8} anthro:1.1), transformation, species transformation, {9}, The scene features a {8} hybrid, {8} fur, {8} tail, {8} snout and ears, photomorph, {6}, {0}, {1}yo, {2}, {3}, {4}, {12}, {10}",
|
| 58 |
+
"Kalıp 5: Dönüşüm Sonu": "(mammal anthro, {8} anthro:1.1), transformation, species transformation, {9}, The scene features a {8} anthro, {8} fur, {8} tail, {8} snout and ears, photomorph, {6}, {0}, {1}yo, {2}, {3}, {4}, {12}, {10}",
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
sablonlar = {}
|
| 62 |
+
degiskenler = [] # Liste: [{'id': 0, 'label': '...', 'default': '...', 'options': [...]}]
|
| 63 |
+
|
| 64 |
+
# --- Yardımcı Fonksiyonlar ---
|
| 65 |
+
|
| 66 |
+
def sort_keys_naturally(keys):
|
| 67 |
+
def extract_number(key):
|
| 68 |
+
match = re.search(r'Kalıp (\d+):', key)
|
| 69 |
+
if match: return int(match.group(1))
|
| 70 |
+
return 9999
|
| 71 |
+
return sorted(keys, key=extract_number)
|
| 72 |
+
|
| 73 |
+
def sablonlari_yukle():
|
| 74 |
+
global sablonlar
|
| 75 |
+
os.makedirs(os.path.dirname(SABLON_DOSYA_YOLU), exist_ok=True)
|
| 76 |
+
loaded_data = {}
|
| 77 |
+
if os.path.exists(SABLON_DOSYA_YOLU):
|
| 78 |
+
try:
|
| 79 |
+
with open(SABLON_DOSYA_YOLU, "r", encoding="utf-8") as f:
|
| 80 |
+
loaded_data = json.load(f)
|
| 81 |
+
except: loaded_data = {}
|
| 82 |
+
|
| 83 |
+
for k, v in VARSAYILAN_SABLONLAR.items():
|
| 84 |
+
if k not in loaded_data: loaded_data[k] = v
|
| 85 |
+
|
| 86 |
+
sorted_keys = sort_keys_naturally(list(loaded_data.keys()))
|
| 87 |
+
sablonlar = {k: loaded_data[k] for k in sorted_keys}
|
| 88 |
+
sablonlari_kaydet()
|
| 89 |
+
|
| 90 |
+
def sablonlari_kaydet():
|
| 91 |
+
try:
|
| 92 |
+
with open(SABLON_DOSYA_YOLU, "w", encoding="utf-8") as f:
|
| 93 |
+
json.dump(sablonlar, f, ensure_ascii=False, indent=4)
|
| 94 |
+
return "Şablonlar başarıyla kaydedildi."
|
| 95 |
+
except Exception as e: return f"Kaydetme hatası: {e}"
|
| 96 |
+
|
| 97 |
+
def degiskenleri_yukle():
|
| 98 |
+
"""Değişkenleri JSON'dan yükler, yoksa varsayılan listelerden oluşturur."""
|
| 99 |
+
global degiskenler
|
| 100 |
+
os.makedirs(os.path.dirname(DEGISKENLER_DOSYA_YOLU), exist_ok=True)
|
| 101 |
+
|
| 102 |
+
if os.path.exists(DEGISKENLER_DOSYA_YOLU):
|
| 103 |
+
try:
|
| 104 |
+
with open(DEGISKENLER_DOSYA_YOLU, "r", encoding="utf-8") as f:
|
| 105 |
+
degiskenler = json.load(f)
|
| 106 |
+
except:
|
| 107 |
+
degiskenler = []
|
| 108 |
+
|
| 109 |
+
# Dosya yoksa veya boşsa varsayılanları yükle
|
| 110 |
+
if not degiskenler and varsayilan_etiketler_ham:
|
| 111 |
+
for i, (etiket, deger) in enumerate(zip(varsayilan_etiketler_ham, varsayilan_degerler_ham)):
|
| 112 |
+
degiskenler.append({
|
| 113 |
+
"id": i,
|
| 114 |
+
"label": etiket,
|
| 115 |
+
"default": deger,
|
| 116 |
+
"options": [deger]
|
| 117 |
+
})
|
| 118 |
+
degiskenleri_kaydet()
|
| 119 |
+
|
| 120 |
+
def degiskenleri_kaydet():
|
| 121 |
+
try:
|
| 122 |
+
with open(DEGISKENLER_DOSYA_YOLU, "w", encoding="utf-8") as f:
|
| 123 |
+
json.dump(degiskenler, f, ensure_ascii=False, indent=4)
|
| 124 |
+
return "Değişkenler kaydedildi."
|
| 125 |
+
except Exception as e: return f"Hata: {e}"
|
| 126 |
+
|
| 127 |
+
# Başlangıç Yüklemeleri
|
| 128 |
+
sablonlari_yukle()
|
| 129 |
+
degiskenleri_yukle()
|
| 130 |
+
|
| 131 |
+
def metin_olusturucu(sablon_metni, *girdiler):
|
| 132 |
+
try:
|
| 133 |
+
clean_inputs = [str(g).strip() for g in girdiler]
|
| 134 |
+
sonuc_metni = sablon_metni.format(*clean_inputs)
|
| 135 |
+
return sonuc_metni.strip()
|
| 136 |
+
except IndexError: return "Hata: Şablondaki değişken sayısı, girilen değer sayısından fazla."
|
| 137 |
+
except Exception as e: return f"Bilinmeyen hata: {e}"
|
| 138 |
+
|
| 139 |
+
def sablon_secim_degisti(secim):
|
| 140 |
+
return sablonlar.get(secim, "")
|
| 141 |
+
|
| 142 |
+
def mevcut_sablonu_guncelle(secim_adi, yeni_icerik):
|
| 143 |
+
if not secim_adi: return "Lütfen bir şablon seçin."
|
| 144 |
+
sablonlar[secim_adi] = yeni_icerik
|
| 145 |
+
sonuc = sablonlari_kaydet()
|
| 146 |
+
return f"'{secim_adi}' güncellendi. {sonuc}"
|
| 147 |
+
|
| 148 |
+
def yeni_sablon_ekle(yeni_ad, yeni_icerik):
|
| 149 |
+
if not yeni_ad: return "Hata: İsim boş.", gr.update(), gr.update(), gr.update()
|
| 150 |
+
if yeni_ad in sablonlar: return "Hata: İsim zaten var.", gr.update(), gr.update(), gr.update()
|
| 151 |
+
sablonlar[yeni_ad] = yeni_icerik
|
| 152 |
+
sorted_keys = sort_keys_naturally(list(sablonlar.keys()))
|
| 153 |
+
sablonlar = {k: sablonlar[k] for k in sorted_keys}
|
| 154 |
+
sablonlari_kaydet()
|
| 155 |
+
# Hem generatör hem manager dropdown'ını güncelle
|
| 156 |
+
update = gr.update(choices=list(sablonlar.keys()), value=yeni_ad)
|
| 157 |
+
return f"'{yeni_ad}' eklendi.", update, update, yeni_icerik
|
| 158 |
+
|
| 159 |
+
def sablon_sil(secim_adi):
|
| 160 |
+
if not secim_adi: return "Hata: Seçim yok.", gr.update(), gr.update(), gr.update()
|
| 161 |
+
if secim_adi in sablonlar:
|
| 162 |
+
del sablonlar[secim_adi]
|
| 163 |
+
sablonlari_kaydet()
|
| 164 |
+
keys = list(sablonlar.keys())
|
| 165 |
+
val = keys[0] if keys else None
|
| 166 |
+
cont = sablonlar[val] if val else ""
|
| 167 |
+
update = gr.update(choices=keys, value=val)
|
| 168 |
+
return f"'{secim_adi}' silindi.", update, update, cont
|
| 169 |
+
return "Hata: Bulunamadı.", gr.update(), gr.update(), gr.update()
|
| 170 |
+
|
| 171 |
+
def tum_metinleri_olustur(*girdiler):
|
| 172 |
+
tum_sonuclar = []
|
| 173 |
+
clean_inputs = [str(g).strip() for g in girdiler]
|
| 174 |
+
for ad, sablon in sablonlar.items():
|
| 175 |
+
try:
|
| 176 |
+
text = sablon.format(*clean_inputs)
|
| 177 |
+
# DÜZELTME: Artık başlıklar eklenmiyor, sadece metin ekleniyor.
|
| 178 |
+
tum_sonuclar.append(f"{text}")
|
| 179 |
+
except: pass
|
| 180 |
+
# Sonuçlar arasında 2 satır boşluk bırakılır.
|
| 181 |
+
return "\n\n".join(tum_sonuclar)
|
| 182 |
+
|
| 183 |
+
# --- Değişken Seçenekleri Yönetim Fonksiyonları ---
|
| 184 |
+
|
| 185 |
+
def get_degisken_bilgisi(label_with_id):
|
| 186 |
+
"""Dropdown'dan seçilen etikete göre bilgileri getirir. Seçenekleri satır satır döndürür."""
|
| 187 |
+
try:
|
| 188 |
+
# "{0} - Göz Rengi" formatından ID'yi ayıkla
|
| 189 |
+
match = re.match(r"\{(\d+)\}", label_with_id)
|
| 190 |
+
if not match: return "", "", ""
|
| 191 |
+
|
| 192 |
+
index = int(match.group(1))
|
| 193 |
+
|
| 194 |
+
if 0 <= index < len(degiskenler):
|
| 195 |
+
d = degiskenler[index]
|
| 196 |
+
# Seçenekleri listesinden alıp satır satır string'e çevir
|
| 197 |
+
opts = "\n".join(d.get("options", []))
|
| 198 |
+
return d.get("label", ""), d.get("default", ""), opts
|
| 199 |
+
except:
|
| 200 |
+
pass
|
| 201 |
+
return "", "", ""
|
| 202 |
+
|
| 203 |
+
def degisken_guncelle(label_with_id, label_new, default_val, options_text):
|
| 204 |
+
"""Mevcut değişkenin etiketini ve seçeneklerini günceller. (Satır bazlı)"""
|
| 205 |
+
global degiskenler
|
| 206 |
+
|
| 207 |
+
try:
|
| 208 |
+
match = re.match(r"\{(\d+)\}", label_with_id)
|
| 209 |
+
if not match: return "Hata: Geçersiz değişken seçimi.", gr.update(), gr.update(), gr.update()
|
| 210 |
+
|
| 211 |
+
index = int(match.group(1))
|
| 212 |
+
|
| 213 |
+
# Satır satır gelen metni listeye çevir
|
| 214 |
+
options_list = [line.strip() for line in options_text.split('\n') if line.strip()]
|
| 215 |
+
|
| 216 |
+
# Seçenekler boşsa default değeri ekle
|
| 217 |
+
if not options_list and default_val:
|
| 218 |
+
options_list.append(default_val)
|
| 219 |
+
|
| 220 |
+
# Default değer seçeneklerde yoksa en başa ekle (Kullanım kolaylığı için)
|
| 221 |
+
if default_val and default_val not in options_list:
|
| 222 |
+
options_list.insert(0, default_val)
|
| 223 |
+
|
| 224 |
+
if 0 <= index < len(degiskenler):
|
| 225 |
+
degiskenler[index]["label"] = label_new
|
| 226 |
+
degiskenler[index]["default"] = default_val
|
| 227 |
+
degiskenler[index]["options"] = options_list
|
| 228 |
+
|
| 229 |
+
degiskenleri_kaydet()
|
| 230 |
+
|
| 231 |
+
msg = f"Değişken {{{index}}} güncellendi."
|
| 232 |
+
# Seçenekler metin kutusuna geri satır satır yazılır
|
| 233 |
+
updated_options_text = "\n".join(options_list)
|
| 234 |
+
|
| 235 |
+
return (
|
| 236 |
+
msg,
|
| 237 |
+
gr.update(choices=options_list, value=default_val, label=f"{{{index}}} - {label_new}"), # Ana UI kutusu
|
| 238 |
+
gr.update(choices=[f"{{{d['id']}}} - {d['label']}" for d in degiskenler], value=f"{{{index}}} - {label_new}"), # Yönetim listesi
|
| 239 |
+
gr.update(value=updated_options_text)
|
| 240 |
+
)
|
| 241 |
+
else:
|
| 242 |
+
return "Hata: Değişken bulunamadı.", gr.update(), gr.update(), gr.update()
|
| 243 |
+
|
| 244 |
+
except Exception as e:
|
| 245 |
+
return f"Hata: {e}", gr.update(), gr.update(), gr.update()
|
| 246 |
+
|
| 247 |
+
# --- UI ---
|
| 248 |
+
|
| 249 |
+
def create_prompt_generator_ui():
|
| 250 |
+
degiskenleri_yukle() # UI oluşturulurken en güncel veriyi çek
|
| 251 |
+
|
| 252 |
+
# Değişken listesini yönetim paneli için hazırla
|
| 253 |
+
degisken_secim_listesi = [f"{{{d['id']}}} - {d['label']}" for d in degiskenler]
|
| 254 |
+
|
| 255 |
+
# Helper function for dropdown
|
| 256 |
+
def get_sablon_keys():
|
| 257 |
+
return list(sablonlar.keys())
|
| 258 |
+
|
| 259 |
+
sablon_keys = get_sablon_keys()
|
| 260 |
+
|
| 261 |
+
unsorted_inputs = [] # Generator inputs container
|
| 262 |
+
giris_kutulari = [] # Final sorted inputs
|
| 263 |
+
|
| 264 |
+
with gr.Tabs():
|
| 265 |
+
|
| 266 |
+
# --- 1. SEKME: ÜRETİCİ (GENERATOR) ---
|
| 267 |
+
with gr.TabItem("🚀 Üretici"):
|
| 268 |
+
gr.Markdown("### 📝 Hızlı Prompt Oluştur")
|
| 269 |
+
|
| 270 |
+
with gr.Row():
|
| 271 |
+
sablon_secici_generator = gr.Dropdown(choices=sablon_keys, label=get_str("prompt_select_template"), interactive=True, scale=3)
|
| 272 |
+
btn_olustur = gr.Button(get_str("prompt_gen_btn"), variant="primary", scale=1)
|
| 273 |
+
btn_tumu_olustur = gr.Button(get_str("prompt_gen_all_btn"), variant="secondary", scale=1)
|
| 274 |
+
|
| 275 |
+
cikti_alani = gr.Textbox(label=get_str("label_result"), lines=5, interactive=False, show_copy_button=True)
|
| 276 |
+
|
| 277 |
+
with gr.Accordion("Gelişmiş Değişken Girdileri", open=True):
|
| 278 |
+
# Dinamik Girdi Kutuları (Dropdown) - 3 Kolonlu düzen
|
| 279 |
+
with gr.Row():
|
| 280 |
+
# Kolon 1
|
| 281 |
+
with gr.Column():
|
| 282 |
+
for i in range(0, len(degiskenler), 3):
|
| 283 |
+
d = degiskenler[i]
|
| 284 |
+
comp = gr.Dropdown(choices=d["options"], label=d["label"], value=d["default"], interactive=True)
|
| 285 |
+
unsorted_inputs.append((i, comp))
|
| 286 |
+
# Kolon 2
|
| 287 |
+
with gr.Column():
|
| 288 |
+
for i in range(1, len(degiskenler), 3):
|
| 289 |
+
d = degiskenler[i]
|
| 290 |
+
comp = gr.Dropdown(choices=d["options"], label=d["label"], value=d["default"], interactive=True)
|
| 291 |
+
unsorted_inputs.append((i, comp))
|
| 292 |
+
# Kolon 3
|
| 293 |
+
with gr.Column():
|
| 294 |
+
for i in range(2, len(degiskenler), 3):
|
| 295 |
+
d = degiskenler[i]
|
| 296 |
+
comp = gr.Dropdown(choices=d["options"], label=d["label"], value=d["default"], interactive=True)
|
| 297 |
+
unsorted_inputs.append((i, comp))
|
| 298 |
+
|
| 299 |
+
# Listeyi indekse göre sırala ve sadece componentleri al
|
| 300 |
+
unsorted_inputs.sort(key=lambda x: x[0])
|
| 301 |
+
giris_kutulari = [x[1] for x in unsorted_inputs]
|
| 302 |
+
|
| 303 |
+
# --- 2. SEKME: ŞABLON YÖNETİCİSİ ---
|
| 304 |
+
with gr.TabItem("🛠️ Şablon Yöneticisi"):
|
| 305 |
+
gr.Markdown(get_str("prompt_template_mgmt_header"))
|
| 306 |
+
|
| 307 |
+
with gr.Row():
|
| 308 |
+
sablon_secici_manager = gr.Dropdown(choices=sablon_keys, label="Düzenlenecek Şabloyu Seç", interactive=True, scale=2)
|
| 309 |
+
btn_sil = gr.Button("🗑️ Seçili Şablonu Sil", variant="stop", scale=1)
|
| 310 |
+
|
| 311 |
+
gr.Markdown("---")
|
| 312 |
+
gr.Markdown("#### Şablon Ekle / Düzenle")
|
| 313 |
+
|
| 314 |
+
yeni_sablon_adi = gr.Textbox(label=get_str("prompt_new_template_name"), placeholder="Yeni şablon ise adını girin (Mevcut birini seçtiyseniz burası otomatik dolar)")
|
| 315 |
+
sablon_icerik = gr.Textbox(label=get_str("prompt_template_content"), lines=12, interactive=True, placeholder="Şablon içeriğini buraya yazın. Değişkenler için {0}, {1}... kullanın.")
|
| 316 |
+
|
| 317 |
+
with gr.Row():
|
| 318 |
+
btn_guncelle = gr.Button(get_str("btn_update"), variant="primary")
|
| 319 |
+
btn_yeni_ekle = gr.Button(get_str("btn_add_new"), variant="secondary")
|
| 320 |
+
|
| 321 |
+
status_msg = gr.Textbox(label=get_str("status_label"), interactive=False, visible=True)
|
| 322 |
+
|
| 323 |
+
# --- 3. SEKME: DEĞİŞKEN YÖNETİCİSİ ---
|
| 324 |
+
with gr.TabItem("🧩 Değişken Yöneticisi"):
|
| 325 |
+
gr.Markdown("### Değişkenleri Düzenle")
|
| 326 |
+
|
| 327 |
+
yonetim_secim_dd = gr.Dropdown(
|
| 328 |
+
label=get_str("prompt_select_var"),
|
| 329 |
+
choices=degisken_secim_listesi,
|
| 330 |
+
value=degisken_secim_listesi[0] if degisken_secim_listesi else None,
|
| 331 |
+
interactive=True
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
with gr.Row():
|
| 335 |
+
yonetim_label = gr.Textbox(label=get_str("prompt_var_label"), interactive=True)
|
| 336 |
+
yonetim_default = gr.Textbox(label=get_str("prompt_var_default"), interactive=True)
|
| 337 |
+
|
| 338 |
+
yonetim_options = gr.Textbox(
|
| 339 |
+
label=get_str("prompt_var_options"),
|
| 340 |
+
placeholder="Mavi Göz\nYeşil Göz\nEla Göz",
|
| 341 |
+
lines=8,
|
| 342 |
+
interactive=True
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
yonetim_kaydet_btn = gr.Button(get_str("prompt_update_var_btn"), variant="primary")
|
| 346 |
+
yonetim_durum = gr.Textbox(label=get_str("status_label"), visible=False)
|
| 347 |
+
|
| 348 |
+
# --- Event Bağlantıları ---
|
| 349 |
+
|
| 350 |
+
# 1. Üretici (Generator) Eventleri
|
| 351 |
+
# Şablon seçildiğinde içeriği almamıza gerek yok, içerik backend'de tutuluyor.
|
| 352 |
+
# Ama generate fonksiyonuna içeriği text olarak göndermek daha esnek.
|
| 353 |
+
# Bu yüzden generator'da gizli bir textbox tutabiliriz veya direk seçimi gönderebiliriz.
|
| 354 |
+
# Mevcut yapıda 'sablon_icerik' kullanılıyordu. Generator sekmesinde bunu gizli bir state veya textbox olarak tutalım.
|
| 355 |
+
gen_gizli_icerik = gr.State("")
|
| 356 |
+
|
| 357 |
+
sablon_secici_generator.change(fn=sablon_secim_degisti, inputs=sablon_secici_generator, outputs=gen_gizli_icerik)
|
| 358 |
+
|
| 359 |
+
# Oluşturma
|
| 360 |
+
btn_olustur.click(fn=metin_olusturucu, inputs=[gen_gizli_icerik] + giris_kutulari, outputs=cikti_alani)
|
| 361 |
+
btn_tumu_olustur.click(fn=tum_metinleri_olustur, inputs=giris_kutulari, outputs=cikti_alani)
|
| 362 |
+
|
| 363 |
+
# 2. Şablon Yöneticisi Eventleri
|
| 364 |
+
|
| 365 |
+
# Seçim değişince ismi ve içeriği doldur
|
| 366 |
+
def manager_select_change(secim):
|
| 367 |
+
content = sablon_secim_degisti(secim)
|
| 368 |
+
return secim, content # İsim kutusuna ve içerik kutusuna yaz
|
| 369 |
+
|
| 370 |
+
sablon_secici_manager.change(fn=manager_select_change, inputs=sablon_secici_manager, outputs=[yeni_sablon_adi, sablon_icerik])
|
| 371 |
+
|
| 372 |
+
# Güncelle (Sadece içeriği güncelle, isim zaten seçili)
|
| 373 |
+
# create_prompt_generator_ui içinde helper
|
| 374 |
+
def manager_update_wrapper(name, content):
|
| 375 |
+
if not name: return "Lütfen bir şablon se��in veya adını yazın."
|
| 376 |
+
return mevcut_sablonu_guncelle(name, content)
|
| 377 |
+
|
| 378 |
+
btn_guncelle.click(fn=manager_update_wrapper, inputs=[yeni_sablon_adi, sablon_icerik], outputs=status_msg)
|
| 379 |
+
|
| 380 |
+
# Yeni Ekle
|
| 381 |
+
# Çıktılar: status, gen_dropdown, man_dropdown, content_box (temizleme veya aynı kalma?)
|
| 382 |
+
btn_yeni_ekle.click(
|
| 383 |
+
fn=yeni_sablon_ekle,
|
| 384 |
+
inputs=[yeni_sablon_adi, sablon_icerik],
|
| 385 |
+
outputs=[status_msg, sablon_secici_generator, sablon_secici_manager, sablon_icerik]
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
# Sil
|
| 389 |
+
btn_sil.click(
|
| 390 |
+
fn=sablon_sil,
|
| 391 |
+
inputs=[sablon_secici_manager],
|
| 392 |
+
outputs=[status_msg, sablon_secici_generator, sablon_secici_manager, sablon_icerik]
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
# 3. Değişken Yöneticisi Eventleri
|
| 396 |
+
yonetim_secim_dd.change(
|
| 397 |
+
fn=get_degisken_bilgisi,
|
| 398 |
+
inputs=[yonetim_secim_dd],
|
| 399 |
+
outputs=[yonetim_label, yonetim_default, yonetim_options]
|
| 400 |
+
)
|
| 401 |
+
|
| 402 |
+
def yonetim_wrapper(dd_val, lbl, dflt, opts):
|
| 403 |
+
res = degisken_guncelle(dd_val, lbl, dflt, opts)
|
| 404 |
+
return res[0], res[2], res[3]
|
| 405 |
+
|
| 406 |
+
yonetim_kaydet_btn.click(
|
| 407 |
+
fn=yonetim_wrapper,
|
| 408 |
+
inputs=[yonetim_secim_dd, yonetim_label, yonetim_default, yonetim_options],
|
| 409 |
+
outputs=[yonetim_durum, yonetim_secim_dd, yonetim_options]
|
| 410 |
+
).then(
|
| 411 |
+
fn=None,
|
| 412 |
+
_js="() => { alert('Değişken güncellendi! Seçeneklerin ana ekrana yansıması için lütfen sayfayı yenileyin veya uygulamayı yeniden başlatın.'); }"
|
| 413 |
+
)
|
modules/shared_state.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# modules/shared_state.py
|
| 3 |
+
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
class SharedState:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.web_image_data = None
|
| 9 |
+
self.last_update_time = 0
|
| 10 |
+
self.force_tab_switch = False
|
| 11 |
+
self.new_image_event = False
|
| 12 |
+
|
| 13 |
+
shared_state = SharedState()
|
| 14 |
+
|
| 15 |
+
def set_web_image(data):
|
| 16 |
+
"""Web'den gelen resim verisini ayarlar (Base64 string)."""
|
| 17 |
+
shared_state.web_image_data = data
|
| 18 |
+
shared_state.last_update_time = time.time()
|
| 19 |
+
shared_state.force_tab_switch = True
|
| 20 |
+
shared_state.new_image_event = True
|
| 21 |
+
|
| 22 |
+
def reset_image_event():
|
| 23 |
+
"""Resim işlendiğinde event'i sıfırlar."""
|
| 24 |
+
shared_state.new_image_event = False
|
| 25 |
+
|
| 26 |
+
def check_and_reset_tab_switch():
|
| 27 |
+
"""Sekme değişikliği gerekiyorsa True döner ve flag'i sıfırlar."""
|
| 28 |
+
if shared_state.force_tab_switch:
|
| 29 |
+
shared_state.force_tab_switch = False
|
| 30 |
+
return True
|
| 31 |
+
return False
|
| 32 |
+
|
| 33 |
+
def get_web_image():
|
| 34 |
+
"""Web'den gelen son resim verisini ve zaman damgasını döndürür."""
|
| 35 |
+
return shared_state.web_image_data, shared_state.last_update_time
|
modules/tagger.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import tempfile
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from PIL import Image
|
| 6 |
+
|
| 7 |
+
# Import Tagger Processors
|
| 8 |
+
from modules.taggers.joint import JointTaggerProcessor, run_joint_classifier
|
| 9 |
+
from modules.taggers.cl import CLTaggerProcessor
|
| 10 |
+
from modules.taggers.pixai import PixaiTaggerProcessor
|
| 11 |
+
from modules.taggers.anime import AnimeTaggerProcessor
|
| 12 |
+
from modules.taggers.gemini import GeminiTaggerProcessor
|
| 13 |
+
|
| 14 |
+
# Import Managers and Utils
|
| 15 |
+
from modules.managers.category_manager import kategori_sozlugu_yukle, kategorizelendir_ileri
|
| 16 |
+
from modules.tagger_refinement import rafine_etiketler
|
| 17 |
+
from modules.utils.tag_utils import unique_and_sort_tags
|
| 18 |
+
from modules.managers.rule_manager import apply_additions
|
| 19 |
+
|
| 20 |
+
# --- CL Tagger Modülünü İçe Aktarma ---
|
| 21 |
+
try:
|
| 22 |
+
from modules.cl_tagger_module import CLTagger
|
| 23 |
+
except ImportError:
|
| 24 |
+
try: from .cl_tagger_module import CLTagger # Fallback for relative import
|
| 25 |
+
except ImportError:
|
| 26 |
+
print("cl_tagger_module.py bulunamadı.")
|
| 27 |
+
CLTagger = None
|
| 28 |
+
|
| 29 |
+
# Global Instance Variables
|
| 30 |
+
cl_tagger_instance = None
|
| 31 |
+
try:
|
| 32 |
+
if CLTagger:
|
| 33 |
+
cl_tagger_instance = CLTagger()
|
| 34 |
+
print("CL Tagger (ONNX) başarıyla yüklendi.")
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"CL Tagger (ONNX) yüklenirken hata: {e}")
|
| 37 |
+
cl_tagger_instance = None
|
| 38 |
+
|
| 39 |
+
# --- Ortak İşleme Fonksiyonu ---
|
| 40 |
+
def _process_single_image(
|
| 41 |
+
image,
|
| 42 |
+
joint_thresh, use_joint,
|
| 43 |
+
cl_gen_thresh, cl_char_thresh, use_cl_tagger,
|
| 44 |
+
pixai_general_thresh, pixai_char_thresh, use_pixai_tagger,
|
| 45 |
+
animetagger_model, animetagger_thresh, use_animetagger,
|
| 46 |
+
use_gemini, gemini_api_key, gemini_mode, gemini_model_id,
|
| 47 |
+
gemini_prompt_vision, gemini_prompt_tags, gemini_prompt_hybrid,
|
| 48 |
+
gemini_system_instruction,
|
| 49 |
+
replacement_file_path, synonym_file_path, addition_file_path,
|
| 50 |
+
sort_order="Alfabetik",
|
| 51 |
+
device: str = "Auto",
|
| 52 |
+
context_weight: float = 0.0,
|
| 53 |
+
enable_cat_for_gemini=False, selected_cats_for_gemini=None
|
| 54 |
+
):
|
| 55 |
+
if image is None: return "", "", "", "⚠️ Resim yüklenmedi.", []
|
| 56 |
+
|
| 57 |
+
alert_messages = []
|
| 58 |
+
all_tags = set()
|
| 59 |
+
all_raw_tags_for_original_sort = []
|
| 60 |
+
gemini_result_text = ""
|
| 61 |
+
|
| 62 |
+
current_joint_processor = JointTaggerProcessor(replacement_file_path, synonym_file_path, addition_file_path)
|
| 63 |
+
current_cl_tagger_processor = CLTaggerProcessor(cl_tagger_instance, replacement_file_path, synonym_file_path, addition_file_path)
|
| 64 |
+
current_pixai_processor = PixaiTaggerProcessor(replacement_file_path, synonym_file_path, addition_file_path)
|
| 65 |
+
current_animetagger_processor = AnimeTaggerProcessor(replacement_file_path, synonym_file_path, addition_file_path)
|
| 66 |
+
current_gemini_processor = GeminiTaggerProcessor()
|
| 67 |
+
|
| 68 |
+
if use_joint:
|
| 69 |
+
j_raw, j_alert, j_order = current_joint_processor.predict(image, joint_thresh, replacement_file_path, synonym_file_path, addition_file_path, sort_order, device)
|
| 70 |
+
if "Hata" not in j_raw and "❌" not in j_alert:
|
| 71 |
+
all_tags.update(t.strip() for t in j_raw.split(',') if t.strip())
|
| 72 |
+
all_raw_tags_for_original_sort.extend(j_order)
|
| 73 |
+
else: alert_messages.append(f"Joint: {j_alert}")
|
| 74 |
+
|
| 75 |
+
if use_cl_tagger:
|
| 76 |
+
cl_raw, cl_alert, cl_order = current_cl_tagger_processor.predict(image, cl_gen_thresh, cl_char_thresh, replacement_file_path, synonym_file_path, addition_file_path, sort_order)
|
| 77 |
+
if "Hata" not in cl_raw and "❌" not in cl_alert:
|
| 78 |
+
all_tags.update(t.strip() for t in cl_raw.split(',') if t.strip())
|
| 79 |
+
all_raw_tags_for_original_sort.extend(cl_order)
|
| 80 |
+
else: alert_messages.append(f"CL: {cl_alert}")
|
| 81 |
+
|
| 82 |
+
if use_pixai_tagger:
|
| 83 |
+
p_raw, p_alert, p_order = current_pixai_processor.predict(image, pixai_general_thresh, pixai_char_thresh, replacement_file_path, synonym_file_path, addition_file_path, "Orijinal", device)
|
| 84 |
+
if "Hata" not in p_raw and "❌" not in p_alert:
|
| 85 |
+
all_tags.update(t.strip() for t in p_raw.split(',') if t.strip())
|
| 86 |
+
all_raw_tags_for_original_sort.extend(p_order)
|
| 87 |
+
else: alert_messages.append(f"PixAI: {p_alert}")
|
| 88 |
+
|
| 89 |
+
if use_animetagger:
|
| 90 |
+
a_raw, a_alert, a_order = current_animetagger_processor.predict(image, animetagger_model, animetagger_thresh, replacement_file_path, synonym_file_path, addition_file_path, "Orijinal", device)
|
| 91 |
+
if "Hata" not in a_raw and "❌" not in a_alert:
|
| 92 |
+
all_tags.update(t.strip() for t in a_raw.split(',') if t.strip())
|
| 93 |
+
all_raw_tags_for_original_sort.extend(a_order)
|
| 94 |
+
else: alert_messages.append(f"AnimeTagger: {a_alert}")
|
| 95 |
+
|
| 96 |
+
combined_final_tags_string = unique_and_sort_tags(", ".join(list(all_tags)), sort_order, original_order_ref=all_raw_tags_for_original_sort)
|
| 97 |
+
|
| 98 |
+
refined_final_tags_string = rafine_etiketler(combined_final_tags_string, context_weight)
|
| 99 |
+
|
| 100 |
+
refined_final_tags_string = apply_additions(refined_final_tags_string, addition_file_path)
|
| 101 |
+
|
| 102 |
+
if use_gemini:
|
| 103 |
+
active_prompt = gemini_prompt_vision
|
| 104 |
+
if gemini_mode == "Tags": active_prompt = gemini_prompt_tags
|
| 105 |
+
elif gemini_mode == "Vision + Tags": active_prompt = gemini_prompt_hybrid
|
| 106 |
+
|
| 107 |
+
tags_content_for_gemini = ""
|
| 108 |
+
if gemini_mode in ["Tags", "Vision + Tags"]:
|
| 109 |
+
source_tags = refined_final_tags_string if refined_final_tags_string else combined_final_tags_string
|
| 110 |
+
if enable_cat_for_gemini and selected_cats_for_gemini:
|
| 111 |
+
tags_content_for_gemini = kategorizelendir_ileri(source_tags, selected_cats_for_gemini, kategori_sozlugu_yukle())
|
| 112 |
+
else:
|
| 113 |
+
tags_content_for_gemini = source_tags
|
| 114 |
+
|
| 115 |
+
g_text, g_alert = current_gemini_processor.predict(
|
| 116 |
+
image, gemini_api_key, active_prompt, gemini_mode,
|
| 117 |
+
tags_content_for_gemini, gemini_model_id,
|
| 118 |
+
gemini_system_instruction
|
| 119 |
+
)
|
| 120 |
+
if "❌" in g_alert or "⚠️" in g_alert: alert_messages.append(f"Gemini: {g_alert}")
|
| 121 |
+
else: gemini_result_text = g_text
|
| 122 |
+
|
| 123 |
+
final_alert = "✅ İşlem tamamlandı!" if not alert_messages else "❌ Hatalar: " + "; ".join(alert_messages)
|
| 124 |
+
|
| 125 |
+
return combined_final_tags_string, refined_final_tags_string, gemini_result_text, final_alert, all_raw_tags_for_original_sort
|
| 126 |
+
|
| 127 |
+
# --- Ana Etiketleme Fonksiyonları ---
|
| 128 |
+
|
| 129 |
+
def toplu_islem(
|
| 130 |
+
image,
|
| 131 |
+
joint_thresh, use_joint,
|
| 132 |
+
cl_gen_thresh, cl_char_thresh, use_cl_tagger,
|
| 133 |
+
pixai_general_thresh, pixai_char_thresh, use_pixai_tagger,
|
| 134 |
+
animetagger_model, animetagger_thresh, use_animetagger,
|
| 135 |
+
use_gemini, gemini_api_key, gemini_mode, gemini_model_id,
|
| 136 |
+
gemini_prompt_vision, gemini_prompt_tags, gemini_prompt_hybrid,
|
| 137 |
+
gemini_system_instruction,
|
| 138 |
+
replacement_file_path, synonym_file_path, addition_file_path,
|
| 139 |
+
enable_categorization, selected_categories, sort_order="Alfabetik", device: str = "Auto", context_weight: float = 0.0
|
| 140 |
+
):
|
| 141 |
+
final_tags_string, refined_tags_string, gemini_out, alert_message, _ = _process_single_image(
|
| 142 |
+
image,
|
| 143 |
+
joint_thresh, use_joint,
|
| 144 |
+
cl_gen_thresh, cl_char_thresh, use_cl_tagger,
|
| 145 |
+
pixai_general_thresh, pixai_char_thresh, use_pixai_tagger,
|
| 146 |
+
animetagger_model, animetagger_thresh, use_animetagger,
|
| 147 |
+
use_gemini, gemini_api_key, gemini_mode, gemini_model_id,
|
| 148 |
+
gemini_prompt_vision, gemini_prompt_tags, gemini_prompt_hybrid,
|
| 149 |
+
gemini_system_instruction,
|
| 150 |
+
replacement_file_path, synonym_file_path, addition_file_path,
|
| 151 |
+
sort_order, device, context_weight,
|
| 152 |
+
enable_cat_for_gemini=enable_categorization, selected_cats_for_gemini=selected_categories
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
source_for_cat = refined_tags_string if refined_tags_string else final_tags_string
|
| 156 |
+
categorized_tags_output = kategorizelendir_ileri(source_for_cat, selected_categories, kategori_sozlugu_yukle()) if (enable_categorization and selected_categories) else ""
|
| 157 |
+
|
| 158 |
+
parts = []
|
| 159 |
+
if categorized_tags_output: parts.append(categorized_tags_output)
|
| 160 |
+
if gemini_out: parts.append(gemini_out)
|
| 161 |
+
combined_cat_gem = ", ".join(parts)
|
| 162 |
+
|
| 163 |
+
return alert_message, final_tags_string, refined_tags_string, categorized_tags_output, gemini_out, combined_cat_gem
|
| 164 |
+
|
| 165 |
+
def toplu_islem_batch(
|
| 166 |
+
progress=gr.Progress(), images=None,
|
| 167 |
+
joint_thresh=0.25, use_joint=True,
|
| 168 |
+
cl_gen_thresh=0.55, cl_char_thresh=0.60, use_cl_tagger=True,
|
| 169 |
+
pixai_general_thresh=0.30, pixai_char_thresh=0.85, use_pixai_tagger=False,
|
| 170 |
+
animetagger_model="MobileNet V4 (Hızlı)", animetagger_thresh=0.35, use_animetagger=False,
|
| 171 |
+
use_gemini=False, gemini_api_key="", gemini_mode="Vision", gemini_model_id="gemini-2.5-flash",
|
| 172 |
+
gemini_prompt_vision="", gemini_prompt_tags="", gemini_prompt_hybrid="",
|
| 173 |
+
gemini_system_instruction="",
|
| 174 |
+
replacement_file_path="", synonym_file_path="", addition_file_path="",
|
| 175 |
+
enable_categorization=False, selected_categories=None, sort_order="Alfabetik", device: str = "Auto", context_weight: float = 0.0
|
| 176 |
+
):
|
| 177 |
+
if not images: return "⚠️ Resim yüklenmedi.", "", None, "", "", "", ""
|
| 178 |
+
all_final, all_refined, all_cat, all_orig, all_gemini, results_html, alerts = [], [], [], [], [], [], []
|
| 179 |
+
total = len(images)
|
| 180 |
+
|
| 181 |
+
for i, img_obj in enumerate(images):
|
| 182 |
+
progress(i / total, desc=f"İşleniyor: {os.path.basename(img_obj.name)} ({i+1}/{total})")
|
| 183 |
+
try: image = Image.open(img_obj.name)
|
| 184 |
+
except Exception as e: alerts.append(f"{img_obj.name}: Okuma hatası: {e}"); continue
|
| 185 |
+
|
| 186 |
+
comb, ref, gem_out, alert, orig = _process_single_image(
|
| 187 |
+
image,
|
| 188 |
+
joint_thresh, use_joint,
|
| 189 |
+
cl_gen_thresh, cl_char_thresh, use_cl_tagger,
|
| 190 |
+
pixai_general_thresh, pixai_char_thresh, use_pixai_tagger,
|
| 191 |
+
animetagger_model, animetagger_thresh, use_animetagger,
|
| 192 |
+
use_gemini, gemini_api_key, gemini_mode, gemini_model_id,
|
| 193 |
+
gemini_prompt_vision, gemini_prompt_tags, gemini_prompt_hybrid,
|
| 194 |
+
gemini_system_instruction,
|
| 195 |
+
replacement_file_path, synonym_file_path, addition_file_path,
|
| 196 |
+
sort_order, device, context_weight,
|
| 197 |
+
enable_cat_for_gemini=enable_categorization, selected_cats_for_gemini=selected_categories
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
if "❌" in alert or "⚠️" in alert: alerts.append(f"{img_obj.name}: {alert}")
|
| 201 |
+
else:
|
| 202 |
+
all_final.append(comb); all_refined.append(ref); all_orig.extend(orig)
|
| 203 |
+
if gem_out: all_gemini.append(f"--- {os.path.basename(img_obj.name)} ---\n{gem_out}")
|
| 204 |
+
|
| 205 |
+
source = ref if ref else comb
|
| 206 |
+
cat_out = kategorizelendir_ileri(source, selected_categories, kategori_sozlugu_yukle()) if (enable_categorization and selected_categories) else ""
|
| 207 |
+
if cat_out: all_cat.append(cat_out)
|
| 208 |
+
|
| 209 |
+
html_part = f"<div style='border:1px solid #ddd; padding:10px; margin:5px;'><b>{os.path.basename(img_obj.name)}</b><br>Tags: {ref}"
|
| 210 |
+
if gem_out: html_part += f"<br><i>Gemini: {gem_out}</i>"
|
| 211 |
+
html_part += "</div>"
|
| 212 |
+
results_html.append(html_part)
|
| 213 |
+
|
| 214 |
+
progress(1.0, desc="✅ Bitti.")
|
| 215 |
+
final_alert = "✅ Başarılı!" if not alerts else "⚠️ Hatalar:\n" + "\n".join(alerts)
|
| 216 |
+
out_file = None
|
| 217 |
+
if all_final:
|
| 218 |
+
with tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False, suffix=".txt") as tf:
|
| 219 |
+
tf.write("\n".join(all_final)); out_file = tf.name
|
| 220 |
+
|
| 221 |
+
gemini_combined_str = "\n\n".join(all_gemini)
|
| 222 |
+
return final_alert, "<br>".join(results_html), out_file, "\n".join(all_cat), unique_and_sort_tags(", ".join(list(set(all_orig))), "Alfabetik"), unique_and_sort_tags(", ".join(all_refined), "Alfabetik"), gemini_combined_str
|
| 223 |
+
|
| 224 |
+
def process_dual_images(image1, image2, joint_thresh1, use_joint1, joint_thresh2, use_joint2, cl_gen_thresh1, cl_char_thresh1, use_cl_tagger1, cl_gen_thresh2, cl_char_thresh2, use_cl_tagger2, pixai_general_thresh1, pixai_char_thresh1, use_pixai_tagger1, pixai_general_thresh2, pixai_char_thresh2, use_pixai_tagger2, animetagger_model1, animetagger_thresh1, use_animetagger1, animetagger_model2, animetagger_thresh2, use_animetagger2,
|
| 225 |
+
use_gemini, gemini_api_key, gemini_mode, gemini_model_id,
|
| 226 |
+
gemini_prompt_vision, gemini_prompt_tags, gemini_prompt_hybrid,
|
| 227 |
+
gemini_system_instruction,
|
| 228 |
+
replacement_file_path, synonym_file_path, addition_file_path,
|
| 229 |
+
enable_categorization_img1, selected_categories_img1, enable_categorization_img2, selected_categories_img2, sort_order="Alfabetik", device: str = "Auto", context_weight: float = 0.0):
|
| 230 |
+
images_settings = [
|
| 231 |
+
(image1, use_joint1, joint_thresh1, cl_gen_thresh1, cl_char_thresh1, use_cl_tagger1, pixai_general_thresh1, pixai_char_thresh1, use_pixai_tagger1, animetagger_model1, animetagger_thresh1, use_animetagger1, enable_categorization_img1, selected_categories_img1),
|
| 232 |
+
(image2, use_joint2, joint_thresh2, cl_gen_thresh2, cl_char_thresh2, use_cl_tagger2, pixai_general_thresh2, pixai_char_thresh2, use_pixai_tagger2, animetagger_model2, animetagger_thresh2, use_animetagger2, enable_categorization_img2, selected_categories_img2)
|
| 233 |
+
]
|
| 234 |
+
alerts, cats, all_tags_sets, refined_sets, orig_tags, gemini_results = [], [], [], [], [], []
|
| 235 |
+
|
| 236 |
+
for img, uj, jt, cg, cc, uc, pg, pc, up, am, at, ua, enc, selc in images_settings:
|
| 237 |
+
comb, ref, gem_out, alert, orig = _process_single_image(
|
| 238 |
+
img, jt, uj, cg, cc, uc, pg, pc, up, am, at, ua,
|
| 239 |
+
use_gemini, gemini_api_key, gemini_mode, gemini_model_id,
|
| 240 |
+
gemini_prompt_vision, gemini_prompt_tags, gemini_prompt_hybrid,
|
| 241 |
+
gemini_system_instruction,
|
| 242 |
+
replacement_file_path, synonym_file_path, addition_file_path,
|
| 243 |
+
sort_order, device, context_weight,
|
| 244 |
+
enable_cat_for_gemini=enc, selected_cats_for_gemini=selc
|
| 245 |
+
)
|
| 246 |
+
if "❌" in alert: alerts.append(alert)
|
| 247 |
+
all_tags_sets.append(set(comb.split(', '))); refined_sets.append(set(ref.split(', '))); orig_tags.extend(orig)
|
| 248 |
+
gemini_results.append(gem_out)
|
| 249 |
+
src = ref if ref else comb
|
| 250 |
+
cats.append(kategorizelendir_ileri(src, selc, kategori_sozlugu_yukle()) if (enc and selc) else "")
|
| 251 |
+
|
| 252 |
+
combined_all = unique_and_sort_tags(", ".join(list(set().union(*all_tags_sets))), sort_order, orig_tags)
|
| 253 |
+
combined_ref = unique_and_sort_tags(", ".join(list(set().union(*refined_sets))), sort_order, orig_tags)
|
| 254 |
+
|
| 255 |
+
combined_all = apply_additions(combined_all, addition_file_path)
|
| 256 |
+
combined_ref = apply_additions(combined_ref, addition_file_path)
|
| 257 |
+
|
| 258 |
+
combined_cat = unique_and_sort_tags(", ".join([c for c in cats if c]), sort_order, orig_tags)
|
| 259 |
+
|
| 260 |
+
valid_captions = [g.strip() for g in gemini_results if g and g.strip()]
|
| 261 |
+
gemini_combined_text = " ".join(valid_captions)
|
| 262 |
+
|
| 263 |
+
parts = []
|
| 264 |
+
if combined_cat: parts.append(combined_cat)
|
| 265 |
+
if gemini_combined_text: parts.append(gemini_combined_text)
|
| 266 |
+
|
| 267 |
+
combined_cat_plus_gem = ", ".join(parts)
|
| 268 |
+
|
| 269 |
+
return alerts, combined_all, combined_ref, combined_cat, gemini_combined_text, combined_cat_plus_gem
|
modules/tagger_refinement.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# modules/tagger_refinement.py
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import csv
|
| 7 |
+
import joblib
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pathlib
|
| 10 |
+
import logging
|
| 11 |
+
from collections import Counter, OrderedDict
|
| 12 |
+
from lark import Lark, Token
|
| 13 |
+
from lark.exceptions import ParseError
|
| 14 |
+
import hnswlib
|
| 15 |
+
import compress_fasttext
|
| 16 |
+
from scipy.sparse import csr_matrix
|
| 17 |
+
|
| 18 |
+
# --- LOGGING SETUP (Sessizleştirme) ---
|
| 19 |
+
logging.basicConfig(level=logging.WARNING)
|
| 20 |
+
for _name in ("gensim", "hnswlib"):
|
| 21 |
+
logging.getLogger(_name).setLevel(logging.ERROR)
|
| 22 |
+
|
| 23 |
+
# --- Dosya Yolları Sabitleri (Veri ve Modeller) ---
|
| 24 |
+
PATH_TF_IDF = "models/binaries/tf_idf_files_420.joblib"
|
| 25 |
+
PATH_HNSW_TAGS = "models/binaries/tfidf_hnsw_tags.bin"
|
| 26 |
+
PATH_FASTTEXT = "models/binaries/e621FastTextModel010Replacement_small.bin"
|
| 27 |
+
PATH_DB_RATING = "data/databases/word_rating_probabilities.csv"
|
| 28 |
+
PATH_DB_FLUFFYROCK = "data/databases/fluffyrock_3m.csv"
|
| 29 |
+
|
| 30 |
+
# --- GLOBAL MODEL DEĞİŞKENLERİ ---
|
| 31 |
+
_HNSW_TAG = None
|
| 32 |
+
_HNSW_DIM = None
|
| 33 |
+
_HNSW_N_TAG = None
|
| 34 |
+
_TF_IDF_COMPONENTS = None
|
| 35 |
+
_FASTTEXT_MODEL = None
|
| 36 |
+
_TAG_DATA_CACHE = None
|
| 37 |
+
_NSFW_TAGS = None
|
| 38 |
+
_ARTIST_SET = None
|
| 39 |
+
|
| 40 |
+
# --- PARSER (Lark Grammar) ---
|
| 41 |
+
grammar = r"""
|
| 42 |
+
!start: (prompt | /[][():]/+)*
|
| 43 |
+
prompt: (emphasized | plain | comma | WHITESPACE)*
|
| 44 |
+
!emphasized: "(" prompt ")"
|
| 45 |
+
| "(" prompt ":" [WHITESPACE] NUMBER [WHITESPACE] ")"
|
| 46 |
+
comma: ","
|
| 47 |
+
WHITESPACE: /\s+/
|
| 48 |
+
plain: /([^,\\\[\]():|]|\\.)+/
|
| 49 |
+
%import common.SIGNED_NUMBER -> NUMBER
|
| 50 |
+
"""
|
| 51 |
+
parser = Lark(grammar, start='start')
|
| 52 |
+
|
| 53 |
+
def load_tf_idf_components():
|
| 54 |
+
global _TF_IDF_COMPONENTS, _NSFW_TAGS, _ARTIST_SET
|
| 55 |
+
|
| 56 |
+
if _TF_IDF_COMPONENTS is None:
|
| 57 |
+
fname = PATH_TF_IDF
|
| 58 |
+
if os.path.exists(fname):
|
| 59 |
+
try:
|
| 60 |
+
_TF_IDF_COMPONENTS = joblib.load(fname)
|
| 61 |
+
if 'tag_to_row_index' in _TF_IDF_COMPONENTS:
|
| 62 |
+
_TF_IDF_COMPONENTS['row_to_tag'] = {idx: tag for tag, idx in _TF_IDF_COMPONENTS['tag_to_row_index'].items()}
|
| 63 |
+
idf = _TF_IDF_COMPONENTS['idf']
|
| 64 |
+
if isinstance(idf, dict):
|
| 65 |
+
t2c = _TF_IDF_COMPONENTS['tag_to_column_index']
|
| 66 |
+
n_cols = max(t2c.values()) + 1
|
| 67 |
+
idf_by_col = np.ones(n_cols, dtype=np.float32)
|
| 68 |
+
for term, col in t2c.items(): idf_by_col[col] = float(idf.get(term, 1.0))
|
| 69 |
+
_TF_IDF_COMPONENTS['idf'] = idf_by_col
|
| 70 |
+
except Exception:
|
| 71 |
+
_TF_IDF_COMPONENTS = {}
|
| 72 |
+
else:
|
| 73 |
+
_TF_IDF_COMPONENTS = {}
|
| 74 |
+
|
| 75 |
+
if _NSFW_TAGS is None:
|
| 76 |
+
_NSFW_TAGS = set()
|
| 77 |
+
if os.path.exists(PATH_DB_RATING):
|
| 78 |
+
with open(PATH_DB_RATING, 'r', newline='', encoding='utf-8') as csvfile:
|
| 79 |
+
reader = csv.reader(csvfile); next(reader, None)
|
| 80 |
+
for row in reader:
|
| 81 |
+
if float(row[1]) >= 0.95: _NSFW_TAGS.add(row[0])
|
| 82 |
+
|
| 83 |
+
if _ARTIST_SET is None:
|
| 84 |
+
_ARTIST_SET = set()
|
| 85 |
+
if os.path.exists(PATH_DB_FLUFFYROCK):
|
| 86 |
+
with open(PATH_DB_FLUFFYROCK, 'r', newline='', encoding='utf-8') as csvfile:
|
| 87 |
+
reader = csv.reader(csvfile)
|
| 88 |
+
for row in reader:
|
| 89 |
+
if row[0].startswith('by_'): _ARTIST_SET.add(row[0][3:])
|
| 90 |
+
|
| 91 |
+
def get_tag_data_cache():
|
| 92 |
+
global _TAG_DATA_CACHE
|
| 93 |
+
if _TAG_DATA_CACHE is None:
|
| 94 |
+
if not os.path.exists(PATH_DB_FLUFFYROCK): return None
|
| 95 |
+
def build_aliases(rev=False):
|
| 96 |
+
d = {}
|
| 97 |
+
with open(PATH_DB_FLUFFYROCK, 'r', newline='', encoding='utf-8') as f:
|
| 98 |
+
r = csv.reader(f)
|
| 99 |
+
for row in r:
|
| 100 |
+
tag = ''.join(c for c in row[0] if ord(c)<128)
|
| 101 |
+
alist = [] if row[3] == "null" else [''.join(c for c in alias if ord(c)<128) for alias in row[3].split(',')]
|
| 102 |
+
if rev:
|
| 103 |
+
for a in alist: d.setdefault(a, []).append(tag)
|
| 104 |
+
else: d[tag] = alist
|
| 105 |
+
return d
|
| 106 |
+
def build_counts():
|
| 107 |
+
d = {}
|
| 108 |
+
with open(PATH_DB_FLUFFYROCK, 'r', newline='', encoding='utf-8') as f:
|
| 109 |
+
r = csv.reader(f)
|
| 110 |
+
for row in r:
|
| 111 |
+
if row[2].isdigit(): d[row[0]] = int(row[2])
|
| 112 |
+
return d
|
| 113 |
+
_TAG_DATA_CACHE = {'tag2aliases': build_aliases(), 'alias2tags': build_aliases(rev=True), 'tag2count': build_counts()}
|
| 114 |
+
return _TAG_DATA_CACHE
|
| 115 |
+
|
| 116 |
+
def is_artist(name):
|
| 117 |
+
load_tf_idf_components()
|
| 118 |
+
return name in _ARTIST_SET
|
| 119 |
+
|
| 120 |
+
def _l2_normalize_rows(mat):
|
| 121 |
+
mat = np.asarray(mat, dtype=np.float32)
|
| 122 |
+
norms = np.linalg.norm(mat, axis=1, keepdims=True)
|
| 123 |
+
norms[norms == 0.0] = 1.0
|
| 124 |
+
return mat / norms
|
| 125 |
+
|
| 126 |
+
def _ensure_hnsw_indexes():
|
| 127 |
+
global _HNSW_TAG, _HNSW_DIM, _HNSW_N_TAG
|
| 128 |
+
if _HNSW_TAG is not None: return
|
| 129 |
+
load_tf_idf_components()
|
| 130 |
+
if not _TF_IDF_COMPONENTS: return
|
| 131 |
+
reduced_matrix = _TF_IDF_COMPONENTS.get('reduced_matrix')
|
| 132 |
+
row_to_tag = _TF_IDF_COMPONENTS.get('row_to_tag')
|
| 133 |
+
if reduced_matrix is None: return
|
| 134 |
+
rm = _l2_normalize_rows(reduced_matrix).astype(np.float32)
|
| 135 |
+
n_items, dim = rm.shape
|
| 136 |
+
_HNSW_DIM = dim
|
| 137 |
+
tag_rows = []
|
| 138 |
+
for i in range(n_items):
|
| 139 |
+
tag = row_to_tag.get(i, "")
|
| 140 |
+
base = tag[3:] if tag.startswith("by_") else tag
|
| 141 |
+
if tag in {"by_unknown_artist", "by_conditional_dnp"}: tag_rows.append(i); continue
|
| 142 |
+
if not is_artist(base): tag_rows.append(i)
|
| 143 |
+
|
| 144 |
+
tag_path = pathlib.Path(PATH_HNSW_TAGS)
|
| 145 |
+
idx = hnswlib.Index(space='cosine', dim=dim)
|
| 146 |
+
if tag_path.exists():
|
| 147 |
+
try:
|
| 148 |
+
idx.load_index(str(tag_path), max_elements=max(1, len(tag_rows)))
|
| 149 |
+
idx.set_ef(200)
|
| 150 |
+
_HNSW_TAG = idx
|
| 151 |
+
_HNSW_N_TAG = len(tag_rows)
|
| 152 |
+
except Exception: pass
|
| 153 |
+
|
| 154 |
+
def _hnsw_query(vec, k=2000):
|
| 155 |
+
_ensure_hnsw_indexes()
|
| 156 |
+
if _HNSW_TAG is None: return [], []
|
| 157 |
+
q = np.asarray(vec, dtype=np.float32).reshape(-1)
|
| 158 |
+
q_norm = np.linalg.norm(q)
|
| 159 |
+
if q_norm > 0: q = q / q_norm
|
| 160 |
+
labels, dists = _HNSW_TAG.knn_query(q, k=min(k, _HNSW_N_TAG))
|
| 161 |
+
return labels[0], 1.0 - dists[0]
|
| 162 |
+
|
| 163 |
+
special_tags = ["score:0", "score:1", "score:2", "score:3", "score:4", "score:5", "score:6", "score:7", "score:8", "score:9", "rating:s", "rating:q", "rating:e"]
|
| 164 |
+
MODEL_SPECIFIC_TAGS = {"masterpiece", "best quality", "good quality", "normal quality", "low quality", "worst quality", "highres", "lowres", "absurdres", "source_pony", "source_furry", "rating_safe", "rating_explicit", "rating_questionable"}
|
| 165 |
+
|
| 166 |
+
def remove_special_tags(original_string):
|
| 167 |
+
tags = [tag.strip() for tag in original_string.split(",")]
|
| 168 |
+
remaining = [t for t in tags if t not in special_tags]
|
| 169 |
+
return ", ".join(remaining)
|
| 170 |
+
|
| 171 |
+
def extract_tags(tree):
|
| 172 |
+
tags_with_positions = []
|
| 173 |
+
def _traverse(node):
|
| 174 |
+
if isinstance(node, Token) and node.type == '__ANON_1': tags_with_positions.append((node.value, node.start_pos))
|
| 175 |
+
elif not isinstance(node, Token):
|
| 176 |
+
for child in node.children: _traverse(child)
|
| 177 |
+
_traverse(tree)
|
| 178 |
+
return tags_with_positions
|
| 179 |
+
|
| 180 |
+
def construct_pseudo_vector(pseudo_doc_terms):
|
| 181 |
+
load_tf_idf_components()
|
| 182 |
+
if not _TF_IDF_COMPONENTS: return None
|
| 183 |
+
idf = _TF_IDF_COMPONENTS['idf']
|
| 184 |
+
t2c = _TF_IDF_COMPONENTS['tag_to_column_index']
|
| 185 |
+
cols, data = [], []
|
| 186 |
+
for term, w in pseudo_doc_terms.items():
|
| 187 |
+
j = t2c.get(term)
|
| 188 |
+
if j is None: continue
|
| 189 |
+
cols.append(j); data.append(w * idf[j])
|
| 190 |
+
return csr_matrix((data, cols, [0, len(cols)]), shape=(1, len(idf)), dtype=np.float32)
|
| 191 |
+
|
| 192 |
+
def get_similar_tags_tfidf(pseudo_doc_terms):
|
| 193 |
+
load_tf_idf_components()
|
| 194 |
+
if not _TF_IDF_COMPONENTS: return {}
|
| 195 |
+
pseudo = construct_pseudo_vector(pseudo_doc_terms)
|
| 196 |
+
if pseudo is None: return {}
|
| 197 |
+
svd = _TF_IDF_COMPONENTS['svd_model']
|
| 198 |
+
reduced = svd.transform(pseudo)
|
| 199 |
+
top_inds, top_sims = _hnsw_query(reduced)
|
| 200 |
+
row_to_tag = _TF_IDF_COMPONENTS['row_to_tag']
|
| 201 |
+
sim_dict = {}
|
| 202 |
+
for i, sim in zip(top_inds, top_sims):
|
| 203 |
+
tag = row_to_tag.get(int(i))
|
| 204 |
+
if tag and tag not in _NSFW_TAGS: sim_dict[tag] = float(sim)
|
| 205 |
+
sorted_sim = OrderedDict(sorted(sim_dict.items(), key=lambda x: x[1], reverse=True))
|
| 206 |
+
return OrderedDict((k.replace('_', ' ').replace('(', '\\(').replace(')', '\\)'), v) for k, v in sorted_sim.items())
|
| 207 |
+
|
| 208 |
+
def rafine_etiketler(tags_string, context_weight=0.5):
|
| 209 |
+
global _FASTTEXT_MODEL
|
| 210 |
+
if not tags_string or not tags_string.strip(): return ""
|
| 211 |
+
load_tf_idf_components()
|
| 212 |
+
data_cache = get_tag_data_cache()
|
| 213 |
+
if _FASTTEXT_MODEL is None and os.path.exists(PATH_FASTTEXT):
|
| 214 |
+
try: _FASTTEXT_MODEL = compress_fasttext.models.CompressedFastTextKeyedVectors.load(PATH_FASTTEXT)
|
| 215 |
+
except: pass
|
| 216 |
+
if (not _FASTTEXT_MODEL or not data_cache or not _TF_IDF_COMPONENTS) and context_weight < 0.01: return tags_string
|
| 217 |
+
|
| 218 |
+
try:
|
| 219 |
+
input_str = remove_special_tags(tags_string.lower())
|
| 220 |
+
try: parsed = parser.parse(input_str)
|
| 221 |
+
except ParseError: return tags_string
|
| 222 |
+
raw_tags = extract_tags(parsed)
|
| 223 |
+
tag_data = []
|
| 224 |
+
for tag_text, start_pos in raw_tags:
|
| 225 |
+
mod_tag = tag_text.replace('_', ' ').replace('\\(', '(').replace('\\)', ')').strip()
|
| 226 |
+
tf_idf_tag = re.sub(r'\\([()])', r'\1', re.sub(r' ', '_', tag_text.strip().removeprefix('by ').removeprefix('by_')))
|
| 227 |
+
tag_data.append({"original_tag": tag_text, "modified_tag": mod_tag, "tf_idf_matrix_tag": tf_idf_tag})
|
| 228 |
+
|
| 229 |
+
terms = [t["tf_idf_matrix_tag"] for t in tag_data]
|
| 230 |
+
suggested_context = {}
|
| 231 |
+
if _TF_IDF_COMPONENTS and _HNSW_TAG: suggested_context = get_similar_tags_tfidf(dict(Counter(terms)))
|
| 232 |
+
|
| 233 |
+
valid_tags = []
|
| 234 |
+
for item in tag_data:
|
| 235 |
+
orig, mod = item["original_tag"], item["modified_tag"]
|
| 236 |
+
search = mod.replace(' ', '_')
|
| 237 |
+
if mod in special_tags or orig in MODEL_SPECIFIC_TAGS: continue
|
| 238 |
+
if not data_cache:
|
| 239 |
+
valid_tags.append(orig); continue
|
| 240 |
+
if is_artist(search.lower().removeprefix('by_')):
|
| 241 |
+
valid_tags.append(orig); continue
|
| 242 |
+
if search in data_cache['tag2count'] or search in data_cache['tag2aliases']:
|
| 243 |
+
valid_tags.append(orig); continue
|
| 244 |
+
if not _FASTTEXT_MODEL:
|
| 245 |
+
if context_weight < 0.5: valid_tags.append(orig)
|
| 246 |
+
continue
|
| 247 |
+
|
| 248 |
+
similar_words = _FASTTEXT_MODEL.most_similar(search, topn=20)
|
| 249 |
+
candidates = []
|
| 250 |
+
seen_cand = set()
|
| 251 |
+
for sim_word, sim in similar_words:
|
| 252 |
+
if sim_word in seen_cand: continue
|
| 253 |
+
if sim_word in data_cache['tag2aliases']:
|
| 254 |
+
candidates.append((sim_word.replace('_', ' '), sim)); seen_cand.add(sim_word)
|
| 255 |
+
else:
|
| 256 |
+
targets = data_cache['alias2tags'].get(sim_word, [])
|
| 257 |
+
for t in targets:
|
| 258 |
+
if t not in seen_cand: candidates.append((t.replace('_', ' '), sim)); seen_cand.add(t)
|
| 259 |
+
|
| 260 |
+
scored_candidates = []
|
| 261 |
+
for word, sim in candidates:
|
| 262 |
+
ctx_score = float(suggested_context.get(word) or suggested_context.get(word.replace('(', '\\(').replace(')', '\\)')) or 0.0)
|
| 263 |
+
final_score = (1.0 - context_weight) * sim + (context_weight * ctx_score)
|
| 264 |
+
scored_candidates.append((word, final_score))
|
| 265 |
+
scored_candidates.sort(key=lambda x: x[1], reverse=True)
|
| 266 |
+
if scored_candidates:
|
| 267 |
+
best_candidate, best_score = scored_candidates[0]
|
| 268 |
+
if best_score > 0.6: valid_tags.append(best_candidate)
|
| 269 |
+
return ", ".join(valid_tags)
|
| 270 |
+
except Exception: return tags_string
|
modules/taggers/anime.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import gc
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import timm
|
| 6 |
+
import torch
|
| 7 |
+
from safetensors.torch import load_file
|
| 8 |
+
from modules.taggers.base import TaggerProcessor
|
| 9 |
+
from modules.taggers.joint import get_torch_device
|
| 10 |
+
|
| 11 |
+
# --- ANIME TAGGER Global Durum ---
|
| 12 |
+
ANIMETAGGER_MODEL_MAP = {
|
| 13 |
+
"mobilenetv4": {"folder": "models/anime_taggers/mobilenetv4_conv_aa_large.dbv4-full", "arch": "mobilenetv4_conv_aa_large"},
|
| 14 |
+
"convnextv2": {"folder": "models/anime_taggers/convnextv2_huge.dbv4-full", "arch": "convnextv2_huge"},
|
| 15 |
+
"caformer": {"folder": "models/anime_taggers/caformer_s36.dbv4-full", "arch": "caformer_s36"},
|
| 16 |
+
# Legacy Support
|
| 17 |
+
"MobileNet V4 (Hızlı)": {"folder": "models/anime_taggers/mobilenetv4_conv_aa_large.dbv4-full", "arch": "mobilenetv4_conv_aa_large"},
|
| 18 |
+
"ConvNeXt V2 Huge (Pro)": {"folder": "models/anime_taggers/convnextv2_huge.dbv4-full", "arch": "convnextv2_huge"},
|
| 19 |
+
"Caformer B36 (Yeni)": {"folder": "models/anime_taggers/caformer_s36.dbv4-full", "arch": "caformer_s36"},
|
| 20 |
+
}
|
| 21 |
+
CURRENT_ANIMETAGGER_MODEL = None
|
| 22 |
+
CURRENT_ANIMETAGGER_TRANSFORM = None
|
| 23 |
+
CURRENT_ANIMETAGGER_TAGS = []
|
| 24 |
+
LOADED_ANIMETAGGER_KEY = None
|
| 25 |
+
|
| 26 |
+
def load_animetagger_model(selection_key, device_pref: str):
|
| 27 |
+
global CURRENT_ANIMETAGGER_MODEL, CURRENT_ANIMETAGGER_TRANSFORM, CURRENT_ANIMETAGGER_TAGS, LOADED_ANIMETAGGER_KEY
|
| 28 |
+
target_device = get_torch_device(device_pref)
|
| 29 |
+
if LOADED_ANIMETAGGER_KEY == selection_key and CURRENT_ANIMETAGGER_MODEL is not None:
|
| 30 |
+
if next(CURRENT_ANIMETAGGER_MODEL.parameters()).device.type != target_device:
|
| 31 |
+
CURRENT_ANIMETAGGER_MODEL.to(target_device)
|
| 32 |
+
return True
|
| 33 |
+
|
| 34 |
+
print(f"\n--- ANIME TAGGER YÜKLENİYOR: {selection_key} ---")
|
| 35 |
+
if CURRENT_ANIMETAGGER_MODEL is not None:
|
| 36 |
+
del CURRENT_ANIMETAGGER_MODEL
|
| 37 |
+
CURRENT_ANIMETAGGER_MODEL = None
|
| 38 |
+
gc.collect()
|
| 39 |
+
if torch.cuda.is_available(): torch.cuda.empty_cache()
|
| 40 |
+
|
| 41 |
+
config = ANIMETAGGER_MODEL_MAP.get(selection_key)
|
| 42 |
+
if not config: return False
|
| 43 |
+
folder_path = config["folder"]
|
| 44 |
+
model_path = os.path.join(folder_path, "model.safetensors")
|
| 45 |
+
tags_path = os.path.join(folder_path, "selected_tags.csv")
|
| 46 |
+
if not os.path.exists(model_path) or not os.path.exists(tags_path): return False
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
df = pd.read_csv(tags_path)
|
| 50 |
+
CURRENT_ANIMETAGGER_TAGS = df['name'].tolist() if 'name' in df.columns else df.iloc[:, 0].tolist()
|
| 51 |
+
model = timm.create_model(config['arch'], pretrained=False, num_classes=len(CURRENT_ANIMETAGGER_TAGS))
|
| 52 |
+
model.load_state_dict(load_file(model_path))
|
| 53 |
+
model.to(target_device).eval()
|
| 54 |
+
CURRENT_ANIMETAGGER_MODEL = model
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"Model yükleme hatası ({selection_key}): {e}")
|
| 57 |
+
return False
|
| 58 |
+
|
| 59 |
+
from timm.data import resolve_data_config
|
| 60 |
+
from timm.data.transforms_factory import create_transform
|
| 61 |
+
data_config = resolve_data_config({}, model=CURRENT_ANIMETAGGER_MODEL)
|
| 62 |
+
CURRENT_ANIMETAGGER_TRANSFORM = create_transform(**data_config)
|
| 63 |
+
LOADED_ANIMETAGGER_KEY = selection_key
|
| 64 |
+
return True
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class AnimeTaggerProcessor(TaggerProcessor):
|
| 68 |
+
def predict(self, image, model_key, threshold, replacement_file_path, synonym_file_path, addition_file_path, sort_order="Alfabetik", device_pref: str = "Auto"):
|
| 69 |
+
self.replacement_file = replacement_file_path
|
| 70 |
+
self.synonym_file = synonym_file_path
|
| 71 |
+
self.addition_file = addition_file_path
|
| 72 |
+
|
| 73 |
+
if image is None: return "", "⚠️ Resim yüklenmedi.", []
|
| 74 |
+
try:
|
| 75 |
+
if not load_animetagger_model(model_key, device_pref):
|
| 76 |
+
return "", f"❌ Anime Tagger modeli yüklenemedi: {model_key}.", []
|
| 77 |
+
target_device = get_torch_device(device_pref)
|
| 78 |
+
img = image.convert("RGB") if image.mode != "RGB" else image
|
| 79 |
+
tensor = CURRENT_ANIMETAGGER_TRANSFORM(img).unsqueeze(0).to(target_device)
|
| 80 |
+
with torch.no_grad():
|
| 81 |
+
output = CURRENT_ANIMETAGGER_MODEL(tensor)
|
| 82 |
+
probs = torch.sigmoid(output)[0].cpu().numpy()
|
| 83 |
+
results = dict(zip(CURRENT_ANIMETAGGER_TAGS, probs))
|
| 84 |
+
filtered_results = {tag: float(score) for tag, score in results.items() if score > threshold}
|
| 85 |
+
sorted_items = sorted(filtered_results.items(), key=lambda x: x[1], reverse=True)
|
| 86 |
+
original_order_for_anime = [tag.replace("_", " ") for tag, score in sorted_items]
|
| 87 |
+
raw_tags_string = ", ".join(original_order_for_anime)
|
| 88 |
+
final_tags = self.process_tags(raw_tags_string, sort_order, original_order_for_anime)
|
| 89 |
+
return final_tags, "✅ Anime Tagger işlemi tamamlandı!", original_order_for_anime
|
| 90 |
+
except Exception as e:
|
| 91 |
+
return f"Hata: {e}", f"❌ Anime Tagger hata: {e}", []
|
modules/taggers/base.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from modules.utils.tag_utils import clean_tags_underscore, unique_and_sort_tags
|
| 3 |
+
from modules.managers.rule_manager import apply_replacements, apply_synonym_consolidation, apply_additions
|
| 4 |
+
|
| 5 |
+
class TaggerProcessor:
|
| 6 |
+
def __init__(self, replacement_file_path, synonym_file_path, addition_file_path):
|
| 7 |
+
self.replacement_file = replacement_file_path
|
| 8 |
+
self.synonym_file = synonym_file_path
|
| 9 |
+
self.addition_file = addition_file_path
|
| 10 |
+
|
| 11 |
+
def process_tags(self, raw_tags_string, sort_order="Alfabetik", original_order_ref=None):
|
| 12 |
+
cleaned = clean_tags_underscore(raw_tags_string)
|
| 13 |
+
replaced = apply_replacements(cleaned, self.replacement_file)
|
| 14 |
+
consolidated = apply_synonym_consolidation(replaced, self.synonym_file)
|
| 15 |
+
final_tags = unique_and_sort_tags(consolidated, sort_order, original_order_ref)
|
| 16 |
+
# Ekleme Kurallarını en son uygula (Listenin sonuna ekler)
|
| 17 |
+
final_tags_with_additions = apply_additions(final_tags, self.addition_file)
|
| 18 |
+
return final_tags_with_additions
|
modules/taggers/cl.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from modules.taggers.base import TaggerProcessor
|
| 3 |
+
|
| 4 |
+
class CLTaggerProcessor(TaggerProcessor):
|
| 5 |
+
def __init__(self, cl_tagger_instance_ref, replacement_file_path, synonym_file_path, addition_file_path):
|
| 6 |
+
super().__init__(replacement_file_path, synonym_file_path, addition_file_path)
|
| 7 |
+
self.cl_tagger_ref = cl_tagger_instance_ref
|
| 8 |
+
|
| 9 |
+
def predict(self, image, gen_threshold, char_threshold, replacement_file_path, synonym_file_path, addition_file_path, sort_order="Alfabetik", device_pref: str = "Auto"):
|
| 10 |
+
self.replacement_file = replacement_file_path
|
| 11 |
+
self.synonym_file = synonym_file_path
|
| 12 |
+
self.addition_file = addition_file_path
|
| 13 |
+
|
| 14 |
+
if self.cl_tagger_ref is None or self.cl_tagger_ref.session is None:
|
| 15 |
+
return "", "❌ CL Tagger modülü yüklenemedi.", []
|
| 16 |
+
if image is None: return "", "⚠️ Resim yüklenmedi.", []
|
| 17 |
+
try:
|
| 18 |
+
ai_tags_string_raw, _, raw_predictions_dict = self.cl_tagger_ref.predict(image, gen_threshold, char_threshold)
|
| 19 |
+
all_raw_tags_with_probs = []
|
| 20 |
+
for category_key in ["rating", "quality", "artist", "character", "copyright", "general", "meta", "model"]:
|
| 21 |
+
all_raw_tags_with_probs.extend(raw_predictions_dict.get(category_key, []))
|
| 22 |
+
all_raw_tags_with_probs_sorted = sorted(all_raw_tags_with_probs, key=lambda x: x[1], reverse=True)
|
| 23 |
+
original_order_for_cl = [tag_name.replace("_", " ") for tag_name, _ in all_raw_tags_with_probs_sorted]
|
| 24 |
+
final_tags = self.process_tags(ai_tags_string_raw, sort_order, original_order_for_cl)
|
| 25 |
+
return final_tags, "✅ CL Tagger işlemi tamamlandı!", original_order_for_cl
|
| 26 |
+
except Exception as e:
|
| 27 |
+
return f"Hata: {e}", f"❌ CL Tagger hata: {e}", []
|
modules/taggers/gemini.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
try:
|
| 3 |
+
import google.generativeai as genai
|
| 4 |
+
GEMINI_AVAILABLE = True
|
| 5 |
+
except ImportError:
|
| 6 |
+
GEMINI_AVAILABLE = False
|
| 7 |
+
|
| 8 |
+
class GeminiTaggerProcessor:
|
| 9 |
+
def predict(self, image, api_key, prompt, mode="Vision", tags_text="", model_id="gemini-2.5-flash", system_instruction=None):
|
| 10 |
+
if not GEMINI_AVAILABLE: return "Kütüphane eksik: pip install google-generativeai", "⚠️ Kütüphane eksik."
|
| 11 |
+
if not api_key: return "API Key Eksik", "⚠️ Gemini API Key girilmedi."
|
| 12 |
+
try:
|
| 13 |
+
genai.configure(api_key=api_key)
|
| 14 |
+
model = genai.GenerativeModel(
|
| 15 |
+
model_id,
|
| 16 |
+
system_instruction=system_instruction if system_instruction and system_instruction.strip() else None
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
if mode == "Tags":
|
| 20 |
+
full_prompt = f"{prompt}\n\nEtiketler:\n{tags_text}"
|
| 21 |
+
response = model.generate_content(full_prompt)
|
| 22 |
+
elif mode == "Vision + Tags":
|
| 23 |
+
full_prompt = f"{prompt}\n\nTespit Edilen Etiketler:\n{tags_text}"
|
| 24 |
+
response = model.generate_content([full_prompt, image])
|
| 25 |
+
else:
|
| 26 |
+
response = model.generate_content([prompt, image])
|
| 27 |
+
|
| 28 |
+
return response.text.strip(), "✅ Gemini işlemi tamamlandı!"
|
| 29 |
+
except Exception as e:
|
| 30 |
+
return f"Hata: {str(e)}", f"❌ Gemini Hatası: {str(e)}"
|
modules/taggers/image_utils.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import math
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
from torch import Tensor
|
| 6 |
+
from einops import rearrange
|
| 7 |
+
from PIL import Image, ImageCms
|
| 8 |
+
from PIL.Image import Resampling
|
| 9 |
+
from PIL.ImageCms import (
|
| 10 |
+
Direction, Intent, ImageCmsProfile, createProfile, getDefaultIntent,
|
| 11 |
+
isIntentSupported, profileToProfile
|
| 12 |
+
)
|
| 13 |
+
from PIL.ImageOps import exif_transpose
|
| 14 |
+
import timm
|
| 15 |
+
|
| 16 |
+
_SRGB = createProfile(colorSpace='sRGB')
|
| 17 |
+
_INTENT_FLAGS = {
|
| 18 |
+
Intent.PERCEPTUAL: ImageCms.FLAGS["HIGHRESPRECALC"],
|
| 19 |
+
Intent.RELATIVE_COLORIMETRIC: (ImageCms.FLAGS["HIGHRESPRECALC"] | ImageCms.FLAGS["BLACKPOINTCOMPENSATION"]),
|
| 20 |
+
Intent.ABSOLUTE_COLORIMETRIC: ImageCms.FLAGS["HIGHRESPRECALC"]
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
def _coalesce_intent(intent: Intent | int) -> Intent:
|
| 24 |
+
if isinstance(intent, Intent): return intent
|
| 25 |
+
mapping = {0: Intent.PERCEPTUAL, 1: Intent.RELATIVE_COLORIMETRIC, 2: Intent.SATURATION, 3: Intent.ABSOLUTE_COLORIMETRIC}
|
| 26 |
+
if intent in mapping: return mapping[intent]
|
| 27 |
+
raise ValueError("invalid intent")
|
| 28 |
+
|
| 29 |
+
def process_srgb(img: Image, *, resize=None, crop=None, expect: tuple = None) -> Image:
|
| 30 |
+
img.load()
|
| 31 |
+
try: exif_transpose(img, in_place=True)
|
| 32 |
+
except Exception: pass
|
| 33 |
+
size = (img.width, img.height)
|
| 34 |
+
if expect is not None and size != expect: raise RuntimeError(f"Image size mismatch.")
|
| 35 |
+
|
| 36 |
+
if (icc_raw := img.info.get("icc_profile")) is not None:
|
| 37 |
+
try:
|
| 38 |
+
profile = ImageCmsProfile(torch.io.BytesIO(icc_raw))
|
| 39 |
+
working_mode = img.mode
|
| 40 |
+
if img.mode.startswith(("RGB", "BGR", "P")): working_mode = "RGBA" if img.has_transparency_data else "RGB"
|
| 41 |
+
elif img.mode.startswith(("L", "I", "F")) or img.mode == "1": working_mode = "LA" if img.has_transparency_data else "L"
|
| 42 |
+
if img.mode != working_mode: img = img.convert(working_mode)
|
| 43 |
+
mode = "RGBA" if img.has_transparency_data else "RGB"
|
| 44 |
+
intent = Intent.RELATIVE_COLORIMETRIC
|
| 45 |
+
if isIntentSupported(profile, intent, Direction.INPUT) != 1: intent = _coalesce_intent(getDefaultIntent(profile))
|
| 46 |
+
if (flags := _INTENT_FLAGS.get(intent)) is None: raise RuntimeError("Unsupported intent")
|
| 47 |
+
|
| 48 |
+
if img.mode == mode: profileToProfile(img, profile, _SRGB, renderingIntent=intent, inPlace=True, flags=flags)
|
| 49 |
+
else: img = cast(Image, profileToProfile(img, profile, _SRGB, renderingIntent=intent, outputMode=mode, flags=flags))
|
| 50 |
+
except Exception: pass
|
| 51 |
+
|
| 52 |
+
if img.has_transparency_data:
|
| 53 |
+
if img.mode != "RGBa":
|
| 54 |
+
try: img = img.convert("RGBa")
|
| 55 |
+
except ValueError: img = img.convert("RGBA").convert("RGBa")
|
| 56 |
+
elif img.mode != "RGB": img = img.convert("RGB")
|
| 57 |
+
|
| 58 |
+
if crop is not None:
|
| 59 |
+
if not isinstance(crop, tuple): crop = crop(size)
|
| 60 |
+
img = img.crop(crop)
|
| 61 |
+
size = (img.width, img.height)
|
| 62 |
+
|
| 63 |
+
if resize is not None:
|
| 64 |
+
if not isinstance(resize, tuple): resize = resize(size)
|
| 65 |
+
if size != resize:
|
| 66 |
+
img = img.resize(resize, Resampling.LANCZOS, box=None, reducing_gap=3.0)
|
| 67 |
+
|
| 68 |
+
return img
|
| 69 |
+
|
| 70 |
+
def put_srgb_patch(img: Image, patch_data: Tensor, patch_coord: Tensor, patch_valid: Tensor, patch_size: int) -> None:
|
| 71 |
+
if img.mode not in ("RGB", "RGBA", "RGBa"): raise ValueError(f"Image has non-RGB mode {img.mode}.")
|
| 72 |
+
patches = rearrange(np.asarray(img)[:, :, :3], "(h p1) (w p2) c -> h w (p1 p2 c)", p1=patch_size, p2=patch_size)
|
| 73 |
+
coords = np.stack(np.meshgrid(np.arange(patches.shape[0], dtype=np.int16), np.arange(patches.shape[1], dtype=np.int16), indexing="ij"), axis=-1)
|
| 74 |
+
coords = rearrange(coords, "h w c -> (h w) c")
|
| 75 |
+
patches = rearrange(patches, "h w p -> (h w) p")
|
| 76 |
+
n = patches.shape[0]
|
| 77 |
+
np.copyto(patch_data[:n].numpy(), patches, casting="no")
|
| 78 |
+
np.copyto(patch_coord[:n].numpy(), coords, casting="no")
|
| 79 |
+
patch_valid[:n] = True
|
| 80 |
+
|
| 81 |
+
def sdpa_attn_mask(patch_valid: Tensor, num_prefix_tokens: int = 0, symmetric: bool = True, q_len: int | None = None, dtype: torch.dtype | None = None) -> Tensor:
|
| 82 |
+
mask = patch_valid.unflatten(-1, (1, 1, -1))
|
| 83 |
+
if num_prefix_tokens:
|
| 84 |
+
mask = torch.cat((torch.ones(*mask.shape[:-1], num_prefix_tokens, device=patch_valid.device, dtype=torch.bool), mask), dim=-1)
|
| 85 |
+
return mask
|
| 86 |
+
|
| 87 |
+
# Monkey Patching Timm for NaFlexVit
|
| 88 |
+
try:
|
| 89 |
+
timm.models.naflexvit.create_attention_mask = sdpa_attn_mask
|
| 90 |
+
except AttributeError:
|
| 91 |
+
pass
|
| 92 |
+
|
| 93 |
+
def get_image_size_for_seq(image_hw, patch_size: int = 16, max_seq_len: int = 1024, max_ratio: float = 1.0, eps: float = 1e-5) -> tuple[int, int]:
|
| 94 |
+
h, w = image_hw
|
| 95 |
+
max_py = int(max((h * max_ratio) // patch_size, 1))
|
| 96 |
+
max_px = int(max((w * max_ratio) // patch_size, 1))
|
| 97 |
+
if (max_py * max_px) <= max_seq_len: return max_py * patch_size, max_px * patch_size
|
| 98 |
+
|
| 99 |
+
def patchify(ratio: float) -> tuple[int, int]:
|
| 100 |
+
return (min(int(math.ceil((h * ratio) / patch_size)), max_py), min(int(math.ceil((w * ratio) / patch_size)), max_px))
|
| 101 |
+
|
| 102 |
+
py, px = patchify(eps)
|
| 103 |
+
if (py * px) > max_seq_len: raise ValueError(f"Image too large.")
|
| 104 |
+
ratio = eps
|
| 105 |
+
while (max_ratio - ratio) >= eps:
|
| 106 |
+
mid = (ratio + max_ratio) / 2.0
|
| 107 |
+
mpy, mpx = patchify(mid)
|
| 108 |
+
if (mpy * mpx) > max_seq_len: max_ratio = mid
|
| 109 |
+
else:
|
| 110 |
+
ratio = mid
|
| 111 |
+
py, px = mpy, mpx
|
| 112 |
+
if (py * px) == max_seq_len: break
|
| 113 |
+
return py * patch_size, px * patch_size
|
| 114 |
+
|
| 115 |
+
def process_image_jtp(img: Image, patch_size: int, max_seq_len: int) -> Image:
|
| 116 |
+
def compute_resize(wh):
|
| 117 |
+
h, w = get_image_size_for_seq((wh[1], wh[0]), patch_size, max_seq_len)
|
| 118 |
+
return w, h
|
| 119 |
+
return process_srgb(img, resize=compute_resize)
|
| 120 |
+
|
| 121 |
+
def patchify_image(img: Image, patch_size: int, max_seq_len: int) -> tuple[Tensor, Tensor, Tensor]:
|
| 122 |
+
patches = torch.zeros(max_seq_len, patch_size * patch_size * 3, device="cpu", dtype=torch.uint8)
|
| 123 |
+
patch_coords = torch.zeros(max_seq_len, 2, device="cpu", dtype=torch.int16)
|
| 124 |
+
patch_valid = torch.zeros(max_seq_len, device="cpu", dtype=torch.bool)
|
| 125 |
+
put_srgb_patch(img, patches, patch_coords, patch_valid, patch_size)
|
| 126 |
+
return patches, patch_coords, patch_valid
|
modules/taggers/joint.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import timm
|
| 4 |
+
import torch
|
| 5 |
+
import huggingface_hub
|
| 6 |
+
from safetensors import safe_open
|
| 7 |
+
from PIL import Image
|
| 8 |
+
|
| 9 |
+
from modules.hydra_layers import HydraPool
|
| 10 |
+
from modules.taggers.image_utils import process_image_jtp, patchify_image
|
| 11 |
+
from modules.taggers.base import TaggerProcessor
|
| 12 |
+
|
| 13 |
+
# Global State
|
| 14 |
+
INITIAL_TORCH_DEVICE = ["cpu", "cuda"][torch.cuda.is_available()]
|
| 15 |
+
JOINT_MODEL = None
|
| 16 |
+
JOINT_TAGS = []
|
| 17 |
+
PATCH_SIZE = 16
|
| 18 |
+
MAX_SEQ_LEN = 1024
|
| 19 |
+
|
| 20 |
+
def get_torch_device(device_pref: str) -> str:
|
| 21 |
+
if device_pref == "CUDA" and torch.cuda.is_available(): return "cuda"
|
| 22 |
+
elif device_pref == "Auto" and torch.cuda.is_available(): return "cuda"
|
| 23 |
+
return "cpu"
|
| 24 |
+
|
| 25 |
+
run_joint_classifier = None
|
| 26 |
+
|
| 27 |
+
# Initialize Model Loading on Import (or lazily)
|
| 28 |
+
# To preserve behavior, we'll try to load it immediately but wrap in try/except
|
| 29 |
+
try:
|
| 30 |
+
print("Joint Tagger (JTP-3 Hydra) Yükleniyor...")
|
| 31 |
+
jtp3_path = huggingface_hub.hf_hub_download(repo_id="RedRocket/JTP-3", filename="models/jtp-3-hydra.safetensors")
|
| 32 |
+
|
| 33 |
+
with safe_open(jtp3_path, framework="pt", device="cpu") as f:
|
| 34 |
+
metadata = f.metadata()
|
| 35 |
+
state_dict = {key: f.get_tensor(key) for key in f.keys()}
|
| 36 |
+
|
| 37 |
+
tags = metadata["classifier.labels"].split("\n")
|
| 38 |
+
JOINT_TAGS = [t.replace("_", " ").replace("vulva", "pussy") for t in tags]
|
| 39 |
+
|
| 40 |
+
joint_model = timm.create_model(
|
| 41 |
+
'naflexvit_so400m_patch16_siglip',
|
| 42 |
+
pretrained=False, num_classes=0,
|
| 43 |
+
pos_embed_interp_mode="bilinear",
|
| 44 |
+
weight_init="skip", fix_init=False,
|
| 45 |
+
device="cpu", dtype=torch.bfloat16
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
joint_model.attn_pool = HydraPool.for_state(state_dict, "attn_pool.", device="cpu", dtype=torch.bfloat16)
|
| 49 |
+
joint_model.head = joint_model.attn_pool.create_head()
|
| 50 |
+
joint_model.num_classes = len(tags)
|
| 51 |
+
|
| 52 |
+
joint_model.load_state_dict(state_dict, strict=False)
|
| 53 |
+
joint_model.attn_pool._q_normed = True
|
| 54 |
+
|
| 55 |
+
joint_model.eval().to(dtype=torch.bfloat16)
|
| 56 |
+
joint_model.to(INITIAL_TORCH_DEVICE)
|
| 57 |
+
JOINT_MODEL = joint_model
|
| 58 |
+
|
| 59 |
+
def run_joint_classifier_func(image: Image, threshold, execution_device: str):
|
| 60 |
+
device_for_tensor = get_torch_device(execution_device)
|
| 61 |
+
processed_img = process_image_jtp(image, PATCH_SIZE, MAX_SEQ_LEN)
|
| 62 |
+
patches, patch_coords, patch_valid = patchify_image(processed_img, PATCH_SIZE, MAX_SEQ_LEN)
|
| 63 |
+
|
| 64 |
+
patches = patches.unsqueeze(0).to(device=device_for_tensor, non_blocking=True)
|
| 65 |
+
patch_coords = patch_coords.unsqueeze(0).to(device=device_for_tensor, non_blocking=True)
|
| 66 |
+
patch_valid = patch_valid.unsqueeze(0).to(device=device_for_tensor, non_blocking=True)
|
| 67 |
+
|
| 68 |
+
patches = patches.to(dtype=torch.bfloat16).div_(127.5).sub_(1.0)
|
| 69 |
+
patch_coords = patch_coords.to(dtype=torch.int32)
|
| 70 |
+
|
| 71 |
+
if next(JOINT_MODEL.parameters()).device.type != device_for_tensor:
|
| 72 |
+
JOINT_MODEL.to(device_for_tensor)
|
| 73 |
+
|
| 74 |
+
with torch.no_grad():
|
| 75 |
+
features = JOINT_MODEL.forward_intermediates(
|
| 76 |
+
patches,
|
| 77 |
+
patch_coord=patch_coords,
|
| 78 |
+
patch_valid=patch_valid,
|
| 79 |
+
output_dict=True,
|
| 80 |
+
output_fmt='NLC'
|
| 81 |
+
)
|
| 82 |
+
logits = JOINT_MODEL.forward_head(features["image_features"], patch_valid=patch_valid)
|
| 83 |
+
|
| 84 |
+
probits = logits[0].float().sigmoid_().mul_(2.0).sub_(1.0)
|
| 85 |
+
|
| 86 |
+
values, indices = probits.cpu().topk(len(JOINT_TAGS))
|
| 87 |
+
|
| 88 |
+
raw_results = []
|
| 89 |
+
for idx, val in zip(indices, values):
|
| 90 |
+
score = val.item()
|
| 91 |
+
if score >= threshold:
|
| 92 |
+
raw_results.append((JOINT_TAGS[idx.item()], score))
|
| 93 |
+
|
| 94 |
+
text_no_impl = ", ".join([t[0] for t in raw_results])
|
| 95 |
+
sorted_tag_score = dict(raw_results)
|
| 96 |
+
|
| 97 |
+
return text_no_impl, sorted_tag_score
|
| 98 |
+
|
| 99 |
+
run_joint_classifier = run_joint_classifier_func
|
| 100 |
+
print(f"JTP-3 Hydra Modeli Başarıyla Yüklendi ({INITIAL_TORCH_DEVICE})")
|
| 101 |
+
|
| 102 |
+
except Exception as e:
|
| 103 |
+
print(f"Joint Tagger (JTP-3) yüklenirken hata: {e}")
|
| 104 |
+
run_joint_classifier = None
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class JointTaggerProcessor(TaggerProcessor):
|
| 108 |
+
def predict(self, image, threshold, replacement_file_path, synonym_file_path, addition_file_path, sort_order="Alfabetik", device_pref: str = "Auto"):
|
| 109 |
+
self.replacement_file = replacement_file_path
|
| 110 |
+
self.synonym_file = synonym_file_path
|
| 111 |
+
self.addition_file = addition_file_path
|
| 112 |
+
|
| 113 |
+
if run_joint_classifier is None: return "", "❌ Joint Tagger (JTP-3) yüklenemedi.", []
|
| 114 |
+
if image is None: return "", "⚠️ Resim yüklenmedi.", []
|
| 115 |
+
try:
|
| 116 |
+
ai_tags_string_raw, raw_tags_sorted_by_confidence = run_joint_classifier(image, threshold, device_pref)
|
| 117 |
+
original_order_for_joint = list(raw_tags_sorted_by_confidence.keys())
|
| 118 |
+
final_tags = self.process_tags(ai_tags_string_raw, sort_order, original_order_for_joint)
|
| 119 |
+
return final_tags, "✅ Joint (JTP-3) işlemi tamamlandı!", original_order_for_joint
|
| 120 |
+
except Exception as e:
|
| 121 |
+
return f"Hata: {e}", f"❌ Joint hata: {e}", []
|
modules/taggers/pixai.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import numpy as np
|
| 6 |
+
import onnxruntime as ort
|
| 7 |
+
import huggingface_hub
|
| 8 |
+
from PIL import Image
|
| 9 |
+
from PIL.Image import Resampling
|
| 10 |
+
from modules.taggers.base import TaggerProcessor
|
| 11 |
+
|
| 12 |
+
# --- PIXAI Tagger Global Durum ---
|
| 13 |
+
PIXAI_MODEL = None
|
| 14 |
+
PIXAI_MODEL_NAME = None
|
| 15 |
+
PIXAI_TAGS_DF = None
|
| 16 |
+
PIXAI_D_IPS = None
|
| 17 |
+
PIXAI_PREPROCESS_FUNC = None
|
| 18 |
+
PIXAI_THRESHOLDS = None
|
| 19 |
+
PIXAI_CATEGORY_NAMES = None
|
| 20 |
+
|
| 21 |
+
def _download_pixai_files(model_name: str):
|
| 22 |
+
repo_id = model_name if '/' in model_name else f'deepghs/pixai-tagger-{model_name}-onnx'
|
| 23 |
+
return (
|
| 24 |
+
huggingface_hub.hf_hub_download(repo_id=repo_id, filename='model.onnx', library_name="pixai-tagger"),
|
| 25 |
+
huggingface_hub.hf_hub_download(repo_id=repo_id, filename='selected_tags.csv', library_name="pixai-tagger"),
|
| 26 |
+
huggingface_hub.hf_hub_download(repo_id=repo_id, filename='preprocess.json', library_name="pixai-tagger"),
|
| 27 |
+
huggingface_hub.hf_hub_download(repo_id=repo_id, filename='thresholds.csv', library_name="pixai-tagger")
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
def _load_pixai_model_components(device_pref: str):
|
| 31 |
+
global PIXAI_MODEL, PIXAI_MODEL_NAME, PIXAI_TAGS_DF, PIXAI_D_IPS, PIXAI_PREPROCESS_FUNC, PIXAI_THRESHOLDS, PIXAI_CATEGORY_NAMES
|
| 32 |
+
model_name = 'deepghs/pixai-tagger-v0.9-onnx'
|
| 33 |
+
if PIXAI_MODEL_NAME != model_name:
|
| 34 |
+
try:
|
| 35 |
+
m_path, t_path, p_path, th_path = _download_pixai_files(model_name)
|
| 36 |
+
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if device_pref == "CUDA" else ['CPUExecutionProvider']
|
| 37 |
+
PIXAI_MODEL = ort.InferenceSession(m_path, providers=providers)
|
| 38 |
+
PIXAI_TAGS_DF = pd.read_csv(t_path)
|
| 39 |
+
PIXAI_D_IPS = {}
|
| 40 |
+
if 'ips' in PIXAI_TAGS_DF.columns:
|
| 41 |
+
PIXAI_TAGS_DF['ips'] = PIXAI_TAGS_DF['ips'].apply(lambda x: json.loads(x) if pd.notna(x) and x != '{}' else {})
|
| 42 |
+
|
| 43 |
+
def transform(img):
|
| 44 |
+
if img.mode != 'RGB': img = img.convert('RGB')
|
| 45 |
+
img = img.resize((448, 448), Resampling.LANCZOS)
|
| 46 |
+
img_array = np.array(img).astype(np.float32) / 255.0
|
| 47 |
+
mean = np.array([0.48145466, 0.4578275, 0.40821073]).astype(np.float32)
|
| 48 |
+
std = np.array([0.26862954, 0.26130258, 0.27577711]).astype(np.float32)
|
| 49 |
+
img_array = (img_array - mean) / std
|
| 50 |
+
return np.transpose(img_array, (2, 0, 1))
|
| 51 |
+
|
| 52 |
+
PIXAI_PREPROCESS_FUNC = transform
|
| 53 |
+
if th_path and os.path.exists(th_path):
|
| 54 |
+
df_th = pd.read_csv(th_path)
|
| 55 |
+
PIXAI_THRESHOLDS = {row['category']: row['threshold'] for _, row in df_th.iterrows()}
|
| 56 |
+
PIXAI_CATEGORY_NAMES = {row['category']: row['name'] for _, row in df_th.iterrows()}
|
| 57 |
+
else:
|
| 58 |
+
PIXAI_THRESHOLDS = {0: 0.3, 4: 0.85, 9: 0.85}
|
| 59 |
+
PIXAI_CATEGORY_NAMES = {0: 'general', 4: 'character', 9: 'rating'}
|
| 60 |
+
PIXAI_MODEL_NAME = model_name
|
| 61 |
+
except Exception as e: print(f"PixAI yükleme hatası: {e}"); raise
|
| 62 |
+
return PIXAI_MODEL, PIXAI_TAGS_DF, PIXAI_D_IPS, PIXAI_PREPROCESS_FUNC, PIXAI_THRESHOLDS, PIXAI_CATEGORY_NAMES
|
| 63 |
+
|
| 64 |
+
def get_pixai_tags(image: Image, thresholds: dict, device_pref: str):
|
| 65 |
+
model, df_tags, _, preprocess, default_thresh, cat_names = _load_pixai_model_components(device_pref)
|
| 66 |
+
input_tensor = preprocess(image)
|
| 67 |
+
if len(input_tensor.shape) == 3: input_tensor = np.expand_dims(input_tensor, axis=0)
|
| 68 |
+
|
| 69 |
+
out = model.run(None, {'input': input_tensor.astype(np.float32)})[0][0]
|
| 70 |
+
|
| 71 |
+
mapped_thresh = {}
|
| 72 |
+
for cat_id, cat_name in cat_names.items():
|
| 73 |
+
if cat_name == 'general': mapped_thresh[cat_id] = thresholds.get('pixai_general_thresh', default_thresh.get(cat_id, 0.3))
|
| 74 |
+
elif cat_name in ['character', 'copyright', 'artist']: mapped_thresh[cat_id] = thresholds.get('pixai_char_thresh', default_thresh.get(cat_id, 0.85))
|
| 75 |
+
else: mapped_thresh[cat_id] = default_thresh.get(cat_id, 0.85)
|
| 76 |
+
|
| 77 |
+
all_tags = []
|
| 78 |
+
for cat in sorted(set(df_tags['category'])):
|
| 79 |
+
mask = df_tags['category'] == cat
|
| 80 |
+
names = df_tags.loc[mask, 'name']
|
| 81 |
+
preds = out[mask]
|
| 82 |
+
thresh = mapped_thresh.get(cat, 0.85)
|
| 83 |
+
sel_mask = preds >= thresh
|
| 84 |
+
for n, s in zip(names[sel_mask], preds[sel_mask]): all_tags.append((n, float(s)))
|
| 85 |
+
|
| 86 |
+
return ", ".join([t[0] for t in all_tags]), [t[0].replace("_", " ") for t in sorted(all_tags, key=lambda x: x[1], reverse=True)]
|
| 87 |
+
|
| 88 |
+
class PixaiTaggerProcessor(TaggerProcessor):
|
| 89 |
+
def predict(self, image, pixai_general_thresh, pixai_char_thresh, replacement_file_path, synonym_file_path, addition_file_path, sort_order="Alfabetik", device_pref: str = "Auto"):
|
| 90 |
+
self.replacement_file = replacement_file_path
|
| 91 |
+
self.synonym_file = synonym_file_path
|
| 92 |
+
self.addition_file = addition_file_path
|
| 93 |
+
|
| 94 |
+
if PIXAI_MODEL is None:
|
| 95 |
+
try: _load_pixai_model_components(device_pref)
|
| 96 |
+
except Exception as e: return "", f"❌ PixAI Tagger modülü yüklenemedi: {e}", []
|
| 97 |
+
if image is None: return "", "⚠️ Resim yüklenmedi.", []
|
| 98 |
+
try:
|
| 99 |
+
thresholds = {'pixai_general_thresh': pixai_general_thresh, 'pixai_char_thresh': pixai_char_thresh}
|
| 100 |
+
ai_tags_string_raw, original_order_for_pixai = get_pixai_tags(image, thresholds, device_pref)
|
| 101 |
+
final_tags = self.process_tags(ai_tags_string_raw, sort_order, original_order_for_pixai)
|
| 102 |
+
return final_tags, "✅ PixAI işlemi tamamlandı!", original_order_for_pixai
|
| 103 |
+
except Exception as e:
|
| 104 |
+
return f"Hata: {e}", f"❌ PixAI hata: {e}", []
|
modules/tools.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from modules.tools_text import (
|
| 3 |
+
clean_and_uniquify_text_simple,
|
| 4 |
+
convert_lines_to_comma,
|
| 5 |
+
convert_comma_to_lines,
|
| 6 |
+
select_and_copy_random_lines,
|
| 7 |
+
refine_text_by_file,
|
| 8 |
+
append_text_to_wildcard_file,
|
| 9 |
+
REFINELIST_FILENAME
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
from modules.tools_image import (
|
| 13 |
+
process_brightness_contrast,
|
| 14 |
+
process_denoise_sharpen,
|
| 15 |
+
process_resolution_change,
|
| 16 |
+
process_text_watermark,
|
| 17 |
+
process_format_change
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
from modules.tools_renaming import (
|
| 21 |
+
apply_renaming_from_configlist,
|
| 22 |
+
apply_custom_renaming
|
| 23 |
+
)
|
| 24 |
+
from modules.utils.file_utils import get_configlist_files
|
| 25 |
+
|
| 26 |
+
# Export Everything
|
| 27 |
+
__all__ = [
|
| 28 |
+
'clean_and_uniquify_text_simple', 'convert_lines_to_comma', 'convert_comma_to_lines',
|
| 29 |
+
'select_and_copy_random_lines', 'refine_text_by_file', 'process_resolution_change',
|
| 30 |
+
'apply_renaming_from_configlist', 'apply_custom_renaming', 'get_configlist_files',
|
| 31 |
+
'process_text_watermark', 'process_format_change', 'process_brightness_contrast',
|
| 32 |
+
'process_denoise_sharpen', 'append_text_to_wildcard_file', 'REFINELIST_FILENAME'
|
| 33 |
+
]
|
modules/tools_image.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import gradio as gr
|
| 4 |
+
from PIL import Image, ImageEnhance, ImageFilter, ImageDraw, ImageFont
|
| 5 |
+
from modules.utils.file_utils import _get_all_image_paths
|
| 6 |
+
|
| 7 |
+
def _apply_pil_enhancement(img: Image.Image, enhancer_type: ImageEnhance, level: int) -> Image.Image:
|
| 8 |
+
"""PIL ImageEnhance kullanarak parlaklık veya kontrast ayarı yapar."""
|
| 9 |
+
# Level: -100'den 100'e. Factor: 0.0'dan 2.0'a (1.0 orijinal)
|
| 10 |
+
factor = (level / 100.0) + 1.0
|
| 11 |
+
factor = max(0.0, factor)
|
| 12 |
+
enhancer = enhancer_type(img)
|
| 13 |
+
return enhancer.enhance(factor)
|
| 14 |
+
|
| 15 |
+
def process_brightness_contrast(progress=gr.Progress(), folder_paths_str: str="", brightness_level: int=0, contrast_level: int=0):
|
| 16 |
+
folder_paths = [p.strip() for p in folder_paths_str.split('\n') if p.strip()]
|
| 17 |
+
if not folder_paths: return "⚠️ Klasör yolu girin.", gr.update(value="⚠️ İptal.", visible=True)
|
| 18 |
+
if brightness_level == 0 and contrast_level == 0: return "⚠️ Değişiklik yok.", gr.update(value="⚠️ İptal.", visible=True)
|
| 19 |
+
|
| 20 |
+
processed_count = 0
|
| 21 |
+
output_messages = [f"ℹ️ Parlaklık/Kontrast işlemi (P: {brightness_level}, K: {contrast_level})."]
|
| 22 |
+
all_image_paths = _get_all_image_paths(folder_paths)
|
| 23 |
+
total = len(all_image_paths)
|
| 24 |
+
|
| 25 |
+
if total == 0: return "⚠️ Resim bulunamadı.", gr.update(value="⚠️ Resim Yok.", visible=True)
|
| 26 |
+
|
| 27 |
+
for i, file_path in enumerate(all_image_paths):
|
| 28 |
+
filename = os.path.basename(file_path)
|
| 29 |
+
progress((i / total), desc=f"İşleniyor: {filename}")
|
| 30 |
+
try:
|
| 31 |
+
with Image.open(file_path) as img:
|
| 32 |
+
orig_mode = img.mode
|
| 33 |
+
if brightness_level != 0: img = _apply_pil_enhancement(img, ImageEnhance.Brightness, brightness_level)
|
| 34 |
+
if contrast_level != 0: img = _apply_pil_enhancement(img, ImageEnhance.Contrast, contrast_level)
|
| 35 |
+
|
| 36 |
+
ext = os.path.splitext(filename)[1].lower()
|
| 37 |
+
if ext in ('.jpg', '.jpeg'): img.convert("RGB").save(file_path, "JPEG", quality=95)
|
| 38 |
+
elif ext == '.webp': img.save(file_path, "WEBP", lossless=True)
|
| 39 |
+
else:
|
| 40 |
+
if orig_mode in ('RGBA', 'P'): img.save(file_path)
|
| 41 |
+
else: img.convert(orig_mode).save(file_path)
|
| 42 |
+
processed_count += 1
|
| 43 |
+
except Exception as e: output_messages.append(f"❌ Hata ({filename}): {e}")
|
| 44 |
+
|
| 45 |
+
return "\n".join(output_messages), gr.update(value=f"✅ {processed_count} resim işlendi.", visible=True)
|
| 46 |
+
|
| 47 |
+
def process_denoise_sharpen(progress=gr.Progress(), folder_paths_str: str="", denoise_level: int=0, sharpen_amount: float=0.0):
|
| 48 |
+
folder_paths = [p.strip() for p in folder_paths_str.split('\n') if p.strip()]
|
| 49 |
+
if not folder_paths: return "⚠️ Klasör yolu girin.", gr.update(value="⚠️ İptal.", visible=True)
|
| 50 |
+
if denoise_level == 0 and sharpen_amount == 0.0: return "⚠️ Değişiklik yok.", gr.update(value="⚠️ İptal.", visible=True)
|
| 51 |
+
|
| 52 |
+
processed_count = 0
|
| 53 |
+
output_messages = [f"ℹ️ Gürültü/Netlik işlemi (G: {denoise_level}, N: {sharpen_amount})."]
|
| 54 |
+
all_image_paths = _get_all_image_paths(folder_paths)
|
| 55 |
+
total = len(all_image_paths)
|
| 56 |
+
|
| 57 |
+
if total == 0: return "⚠️ Resim bulunamadı.", gr.update(value="⚠️ Resim Yok.", visible=True)
|
| 58 |
+
|
| 59 |
+
for i, file_path in enumerate(all_image_paths):
|
| 60 |
+
filename = os.path.basename(file_path)
|
| 61 |
+
progress((i / total), desc=f"İşleniyor: {filename}")
|
| 62 |
+
try:
|
| 63 |
+
with Image.open(file_path) as img:
|
| 64 |
+
orig_mode = img.mode
|
| 65 |
+
if denoise_level > 0: img = img.filter(ImageFilter.MedianFilter(size=denoise_level * 2 + 1))
|
| 66 |
+
if sharpen_amount > 0.0: img = ImageEnhance.Sharpness(img).enhance(sharpen_amount)
|
| 67 |
+
|
| 68 |
+
ext = os.path.splitext(filename)[1].lower()
|
| 69 |
+
if ext in ('.jpg', '.jpeg'): img.convert("RGB").save(file_path, "JPEG", quality=95)
|
| 70 |
+
elif ext == '.webp': img.save(file_path, "WEBP", lossless=True)
|
| 71 |
+
else:
|
| 72 |
+
if orig_mode in ('RGBA', 'P'): img.save(file_path)
|
| 73 |
+
else: img.convert(orig_mode).save(file_path)
|
| 74 |
+
processed_count += 1
|
| 75 |
+
except Exception as e: output_messages.append(f"❌ Hata ({filename}): {e}")
|
| 76 |
+
|
| 77 |
+
return "\n".join(output_messages), gr.update(value=f"✅ {processed_count} resim işlendi.", visible=True)
|
| 78 |
+
|
| 79 |
+
def process_resolution_change(progress=gr.Progress(), folder_paths_str="", scale_factor=1.0):
|
| 80 |
+
folder_paths = [p.strip() for p in folder_paths_str.split('\n') if p.strip()]
|
| 81 |
+
if not folder_paths: return "⚠️ Klasör yolu girin.", gr.update(value="⚠️ İptal.", visible=True)
|
| 82 |
+
|
| 83 |
+
scale = float(scale_factor)
|
| 84 |
+
processed_count = 0
|
| 85 |
+
output_messages = ["ℹ️ Çözünürlük değiştirme işlemi başladı."]
|
| 86 |
+
all_image_paths = _get_all_image_paths(folder_paths)
|
| 87 |
+
total = len(all_image_paths)
|
| 88 |
+
|
| 89 |
+
if total == 0: return "⚠️ Resim bulunamadı.", gr.update(value="⚠️ Resim Yok.", visible=True)
|
| 90 |
+
|
| 91 |
+
for i, file_path in enumerate(all_image_paths):
|
| 92 |
+
filename = os.path.basename(file_path)
|
| 93 |
+
progress((i / total), desc=f"İşleniyor: {filename}")
|
| 94 |
+
try:
|
| 95 |
+
with Image.open(file_path) as img:
|
| 96 |
+
nw = int(img.width * scale)
|
| 97 |
+
nh = int(img.height * scale)
|
| 98 |
+
if abs(nw - img.width) < 1 and abs(nh - img.height) < 1: continue
|
| 99 |
+
img.resize((nw, nh), Image.Resampling.LANCZOS).save(file_path)
|
| 100 |
+
processed_count += 1
|
| 101 |
+
except Exception as e: output_messages.append(f"❌ Hata ({filename}): {e}")
|
| 102 |
+
|
| 103 |
+
return "\n".join(output_messages), gr.update(value=f"✅ {processed_count} resim yeniden boyutlandırıldı.", visible=True)
|
| 104 |
+
|
| 105 |
+
def process_text_watermark(progress=gr.Progress(), folder_paths_str="", watermark_text="", opacity=0.35, font_size_ratio=0.05, rotation_angle=-45):
|
| 106 |
+
folder_paths = [p.strip() for p in folder_paths_str.split('\n') if p.strip()]
|
| 107 |
+
if not folder_paths or not watermark_text: return "⚠️ Yol veya metin eksik.", gr.update(value="⚠️ İptal.", visible=True)
|
| 108 |
+
|
| 109 |
+
processed = 0
|
| 110 |
+
all_paths = _get_all_image_paths(folder_paths)
|
| 111 |
+
total = len(all_paths)
|
| 112 |
+
if total == 0: return "⚠️ Resim yok.", gr.update(value="⚠️ İptal.", visible=True)
|
| 113 |
+
|
| 114 |
+
font_path = "arial.ttf"
|
| 115 |
+
try: ImageFont.truetype(font_path, 10)
|
| 116 |
+
except: font_path = None # Default kullan
|
| 117 |
+
|
| 118 |
+
for i, path in enumerate(all_paths):
|
| 119 |
+
progress(i/total, desc=f"Filigran: {os.path.basename(path)}")
|
| 120 |
+
try:
|
| 121 |
+
with Image.open(path).convert("RGBA") as base:
|
| 122 |
+
W, H = base.size
|
| 123 |
+
fs = max(10, int(W * font_size_ratio))
|
| 124 |
+
font = ImageFont.truetype(font_path, fs) if font_path else ImageFont.load_default()
|
| 125 |
+
|
| 126 |
+
# Layer oluştur
|
| 127 |
+
txt_layer = Image.new('RGBA', (W*2, H*2), (0,0,0,0))
|
| 128 |
+
draw = ImageDraw.Draw(txt_layer)
|
| 129 |
+
fill = (255, 255, 255, int(255 * opacity))
|
| 130 |
+
|
| 131 |
+
# Metin boyutu
|
| 132 |
+
bbox = draw.textbbox((0,0), watermark_text, font=font)
|
| 133 |
+
tw, th = bbox[2]-bbox[0], bbox[3]-bbox[1]
|
| 134 |
+
|
| 135 |
+
# Döşeme (Tiling)
|
| 136 |
+
xspace, yspace = max(50, tw*2), max(50, th*4)
|
| 137 |
+
for y in range(0, H*2, yspace):
|
| 138 |
+
for x in range(0, W*2, xspace):
|
| 139 |
+
draw.text((x + (y//yspace % 2)*(xspace//2), y), watermark_text, font=font, fill=fill)
|
| 140 |
+
|
| 141 |
+
# Döndür ve Kırp
|
| 142 |
+
if rotation_angle != 0:
|
| 143 |
+
txt_layer = txt_layer.rotate(rotation_angle, resample=Image.BICUBIC)
|
| 144 |
+
|
| 145 |
+
cx, cy = txt_layer.width//2, txt_layer.height//2
|
| 146 |
+
final_layer = txt_layer.crop((cx - W//2, cy - H//2, cx + W//2, cy + H//2))
|
| 147 |
+
|
| 148 |
+
base.alpha_composite(final_layer)
|
| 149 |
+
|
| 150 |
+
ext = os.path.splitext(path)[1].lower()
|
| 151 |
+
if ext in ('.jpg', '.jpeg'): base.convert("RGB").save(path, "JPEG", quality=95)
|
| 152 |
+
else: base.save(path)
|
| 153 |
+
processed += 1
|
| 154 |
+
except Exception as e: print(f"Hata: {e}")
|
| 155 |
+
|
| 156 |
+
return "İşlem tamam.", gr.update(value=f"✅ {processed} resme filigran eklendi.", visible=True)
|
| 157 |
+
|
| 158 |
+
def process_format_change(progress=gr.Progress(), folder_paths_str="", target_format=""):
|
| 159 |
+
folder_paths = [p.strip() for p in folder_paths_str.split('\n') if p.strip()]
|
| 160 |
+
if not folder_paths: return "⚠️ Yol girin.", gr.update(value="⚠️ İptal.", visible=True)
|
| 161 |
+
|
| 162 |
+
fmt = target_format.strip().lower()
|
| 163 |
+
if fmt == '.svg': return "❌ SVG desteklenmez.", gr.update(value="❌ Hata.", visible=True)
|
| 164 |
+
|
| 165 |
+
processed = 0
|
| 166 |
+
all_paths = _get_all_image_paths(folder_paths)
|
| 167 |
+
total = len(all_paths)
|
| 168 |
+
|
| 169 |
+
for i, path in enumerate(all_paths):
|
| 170 |
+
progress(i/total, desc=f"Dönüştürülüyor: {os.path.basename(path)}")
|
| 171 |
+
name, ext = os.path.splitext(path)
|
| 172 |
+
if ext.lower() == fmt: continue
|
| 173 |
+
|
| 174 |
+
try:
|
| 175 |
+
with Image.open(path) as img:
|
| 176 |
+
new_path = f"{name}{fmt}"
|
| 177 |
+
save_fmt = "JPEG" if fmt in ('.jpg', '.jpeg') else fmt.replace('.', '').upper()
|
| 178 |
+
|
| 179 |
+
if img.mode == 'RGBA' and save_fmt == 'JPEG':
|
| 180 |
+
bg = Image.new('RGB', img.size, (255,255,255))
|
| 181 |
+
bg.paste(img, mask=img.split()[3])
|
| 182 |
+
bg.save(new_path, quality=95)
|
| 183 |
+
elif save_fmt == 'PNG':
|
| 184 |
+
img.save(new_path) # RGBA korur
|
| 185 |
+
else:
|
| 186 |
+
img.convert('RGB').save(new_path)
|
| 187 |
+
|
| 188 |
+
# Eskiyi sil
|
| 189 |
+
if os.path.exists(new_path): os.remove(path)
|
| 190 |
+
processed += 1
|
| 191 |
+
except Exception as e: print(f"Hata: {e}")
|
| 192 |
+
|
| 193 |
+
return "İşlem tamam.", gr.update(value=f"✅ {processed} resim dönüştürüldü.", visible=True)
|
modules/tools_renaming.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import shutil
|
| 4 |
+
import random
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from modules.utils.file_utils import _get_all_image_paths
|
| 7 |
+
|
| 8 |
+
def apply_renaming_from_configlist(progress=gr.Progress(), folder_paths_str="", configlist_file="", renaming_type="Rastgele"):
|
| 9 |
+
"""Liste dosyasından rastgele isim seçip dosyaları adlandırır."""
|
| 10 |
+
folder_paths = [p.strip() for p in folder_paths_str.split('\n') if p.strip()]
|
| 11 |
+
if not folder_paths: return "⚠️ Klasör yolu girin.", gr.update(value="⚠️ İptal.", visible=True)
|
| 12 |
+
if not configlist_file or not os.path.exists(configlist_file): return "❌ Liste dosyası seçilmedi.", gr.update(value="❌ Hata.", visible=True)
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
with open(configlist_file, 'r', encoding='utf-8') as f: name_list = [line.strip() for line in f if line.strip()]
|
| 16 |
+
except Exception as e: return f"❌ Dosya okuma hatası: {e}", gr.update(value="❌ Hata.", visible=True)
|
| 17 |
+
|
| 18 |
+
if not name_list: return "⚠️ Liste boş.", gr.update(value="⚠️ İptal.", visible=True)
|
| 19 |
+
|
| 20 |
+
output_messages = ["ℹ️ Liste bazlı adlandırma başladı."]
|
| 21 |
+
renamed_count = 0
|
| 22 |
+
all_image_paths = _get_all_image_paths(folder_paths)
|
| 23 |
+
total = len(all_image_paths)
|
| 24 |
+
|
| 25 |
+
if total == 0: return "⚠️ Resim bulunamadı.", gr.update(value="⚠️ Resim Yok.", visible=True)
|
| 26 |
+
|
| 27 |
+
# İsim listesini karıştır (Rastgele mod ise)
|
| 28 |
+
if renaming_type == "Rastgele": random.shuffle(name_list)
|
| 29 |
+
|
| 30 |
+
# Yeterli isim var mı?
|
| 31 |
+
if len(name_list) < total: output_messages.append("⚠️ Uyarı: İsim listesi dosya sayısından az, bazıları tekrar edebilir veya işlenmeyebilir.")
|
| 32 |
+
|
| 33 |
+
for i, file_path in enumerate(all_image_paths):
|
| 34 |
+
if i >= len(name_list): break # İsim bitti
|
| 35 |
+
filename = os.path.basename(file_path)
|
| 36 |
+
progress((i / total), desc=f"Adlandırılıyor: {filename}")
|
| 37 |
+
|
| 38 |
+
ext = os.path.splitext(filename)[1]
|
| 39 |
+
new_base = name_list[i].replace(' ', '_').replace(',', '_')
|
| 40 |
+
new_name = f"{new_base}{ext}"
|
| 41 |
+
new_path = os.path.join(os.path.dirname(file_path), new_name)
|
| 42 |
+
|
| 43 |
+
if file_path != new_path:
|
| 44 |
+
try:
|
| 45 |
+
shutil.move(file_path, new_path)
|
| 46 |
+
renamed_count += 1
|
| 47 |
+
except Exception as e: output_messages.append(f"❌ Hata ({filename}): {e}")
|
| 48 |
+
|
| 49 |
+
return "\n".join(output_messages), gr.update(value=f"✅ {renamed_count} dosya adlandırıldı.", visible=True)
|
| 50 |
+
|
| 51 |
+
def apply_custom_renaming(progress=gr.Progress(), folder_paths_str: str="", pattern: str="", start_number: int=1, digit_count: int=3):
|
| 52 |
+
"""
|
| 53 |
+
Dosyaları şablon + numara mantığıyla adlandırır.
|
| 54 |
+
Pattern içinde {Number} olmalıdır. Örn: "Adoptable {Number}"
|
| 55 |
+
"""
|
| 56 |
+
folder_paths = [p.strip() for p in folder_paths_str.split('\n') if p.strip()]
|
| 57 |
+
if not folder_paths: return "⚠️ Klasör yolu girin.", gr.update(value="⚠️ İptal.", visible=True)
|
| 58 |
+
if not pattern or "{Number}" not in pattern: return "❌ Şablon '{Number}' içermelidir!", gr.update(value="❌ Hatalı Şablon.", visible=True)
|
| 59 |
+
|
| 60 |
+
renamed_count = 0
|
| 61 |
+
curr_num = int(start_number)
|
| 62 |
+
output_messages = [f"ℹ️ Şablonlu adlandırma: '{pattern}', Başlangıç: {curr_num}"]
|
| 63 |
+
|
| 64 |
+
# Her klasör için ayrı işlem (Sıralamayı korumak için)
|
| 65 |
+
for folder in folder_paths:
|
| 66 |
+
if not os.path.isdir(folder): continue
|
| 67 |
+
images = [f for f in os.listdir(folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp'))]
|
| 68 |
+
images.sort() # İsme göre sırala
|
| 69 |
+
|
| 70 |
+
if not images: continue
|
| 71 |
+
|
| 72 |
+
for i, img_name in enumerate(images):
|
| 73 |
+
progress(0, desc=f"Adlandırılıyor: {img_name}") # Basit progress
|
| 74 |
+
|
| 75 |
+
ext = os.path.splitext(img_name)[1]
|
| 76 |
+
num_str = str(curr_num).zfill(int(digit_count))
|
| 77 |
+
new_base = pattern.replace("{Number}", num_str).strip()
|
| 78 |
+
new_name = f"{new_base}{ext}"
|
| 79 |
+
|
| 80 |
+
old_path = os.path.join(folder, img_name)
|
| 81 |
+
new_path = os.path.join(folder, new_name)
|
| 82 |
+
|
| 83 |
+
if old_path != new_path:
|
| 84 |
+
try:
|
| 85 |
+
if os.path.exists(new_path) and old_path.lower() != new_path.lower():
|
| 86 |
+
output_messages.append(f"⚠️ Çakışma: '{new_name}' zaten var. Atlandı.")
|
| 87 |
+
else:
|
| 88 |
+
shutil.move(old_path, new_path)
|
| 89 |
+
renamed_count += 1
|
| 90 |
+
except Exception as e: output_messages.append(f"❌ Hata ({img_name}): {e}")
|
| 91 |
+
|
| 92 |
+
curr_num += 1
|
| 93 |
+
|
| 94 |
+
return "\n".join(output_messages), gr.update(value=f"✅ {renamed_count} dosya adlandırıldı. Son No: {curr_num-1}", visible=True)
|
modules/tools_text.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
import random
|
| 5 |
+
import gradio as gr
|
| 6 |
+
|
| 7 |
+
REFINELIST_PATH = "data/text_lists/RafineListesi.txt"
|
| 8 |
+
REFINELIST_FILENAME = "RafineListesi.txt"
|
| 9 |
+
|
| 10 |
+
def clean_and_uniquify_text_simple(text):
|
| 11 |
+
"""Metni temizler, alt çizgileri boşluğa çevirir ve etiketleri tekilleştirir."""
|
| 12 |
+
if not text: return ""
|
| 13 |
+
cleaned = re.sub(r'[\s_]+', ' ', text).strip()
|
| 14 |
+
tags = {t.strip() for t in cleaned.split(',') if t.strip()}
|
| 15 |
+
return ", ".join(sorted(tags))
|
| 16 |
+
|
| 17 |
+
def convert_lines_to_comma(text):
|
| 18 |
+
"""Satırları virgülle ayrılmış metne çevirir."""
|
| 19 |
+
lines = [line.strip() for line in text.split('\n') if line.strip()]
|
| 20 |
+
if not lines: return "", gr.update(value="⚠️ Girişte geçerli metin bulunamadı.", visible=True)
|
| 21 |
+
return ", ".join(lines), gr.update(value="✅ Satırlar virgüllü metne çevrildi.", visible=True)
|
| 22 |
+
|
| 23 |
+
def convert_comma_to_lines(text):
|
| 24 |
+
"""Virgülle ayrılmış metni satırlara çevirir."""
|
| 25 |
+
lines = [line.strip() for line in text.split(',') if line.strip()]
|
| 26 |
+
if not lines: return "", gr.update(value="⚠️ Girişte geçerli metin bulunamadı.", visible=True)
|
| 27 |
+
return "\n".join(lines), gr.update(value="✅ Virgüllü metin satırlara çevrildi.", visible=True)
|
| 28 |
+
|
| 29 |
+
def select_and_copy_random_lines(text_input_raw, num_lines_to_copy_slider):
|
| 30 |
+
"""Metinden rastgele satırları seçer."""
|
| 31 |
+
lines = [line.strip() for line in text_input_raw.split('\n') if line.strip()]
|
| 32 |
+
num_lines = int(num_lines_to_copy_slider)
|
| 33 |
+
if not lines: return "", gr.update(value="⚠️ Girişte geçerli metin bulunamadı.", visible=True)
|
| 34 |
+
if num_lines > len(lines):
|
| 35 |
+
return "", gr.update(value=f"⚠️ {len(lines)} satır var, ancak {num_lines} satır istendi. İşlem iptal edildi.", visible=True)
|
| 36 |
+
selected = random.sample(lines, num_lines)
|
| 37 |
+
return "\n".join(selected), gr.update(value=f"✅ {num_lines} adet rastgele satır seçildi.", visible=True)
|
| 38 |
+
|
| 39 |
+
def refine_text_by_file(input_text: str):
|
| 40 |
+
"""Kullanıcı metnini RafineListesi.txt dosyasındaki anahtar kelimelere göre filtreler."""
|
| 41 |
+
if not input_text.strip():
|
| 42 |
+
return "", gr.update(value="⚠️ Girişte geçerli metin bulunamadı.", visible=True)
|
| 43 |
+
|
| 44 |
+
target_path = REFINELIST_PATH
|
| 45 |
+
try:
|
| 46 |
+
if not os.path.exists(target_path):
|
| 47 |
+
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
| 48 |
+
with open(target_path, "w", encoding="utf-8") as f: f.write("")
|
| 49 |
+
return input_text, gr.update(value=f"⚠️ Uyarı: '{target_path}' dosyası bulunamadı, boş bir dosya oluşturuldu.", visible=True)
|
| 50 |
+
|
| 51 |
+
with open(target_path, 'r', encoding='utf-8') as f:
|
| 52 |
+
kaldirilacak_kelimeler = [satir.strip().lower() for satir in f if satir.strip()]
|
| 53 |
+
|
| 54 |
+
if not kaldirilacak_kelimeler:
|
| 55 |
+
return input_text, gr.update(value="⚠️ Kaldırılacak kelime listesi boş.", visible=True)
|
| 56 |
+
except Exception as e:
|
| 57 |
+
return input_text, gr.update(value=f"❌ Dosya okuma hatası: {e}", visible=True)
|
| 58 |
+
|
| 59 |
+
bolumler = [b.strip() for b in re.split(r'[, \n]+', input_text) if b.strip()]
|
| 60 |
+
temiz_bolumler = []
|
| 61 |
+
kaldirilan_sayisi = 0
|
| 62 |
+
|
| 63 |
+
for bolum in bolumler:
|
| 64 |
+
bolum_kucuk_harf = bolum.lower()
|
| 65 |
+
gecerli_kalacak_mi = True
|
| 66 |
+
for kelime in kaldirilacak_kelimeler:
|
| 67 |
+
if kelime in bolum_kucuk_harf:
|
| 68 |
+
gecerli_kalacak_mi = False; break
|
| 69 |
+
if gecerli_kalacak_mi: temiz_bolumler.append(bolum)
|
| 70 |
+
else: kaldirilan_sayisi += 1
|
| 71 |
+
|
| 72 |
+
temiz_bolumler_set = sorted(list(set(temiz_bolumler)))
|
| 73 |
+
return ', '.join(temiz_bolumler_set), gr.update(value=f"✅ {len(temiz_bolumler_set)} etiket kaldı, {kaldirilan_sayisi} etiket kaldırıldı.", visible=True)
|
| 74 |
+
|
| 75 |
+
def append_text_to_wildcard_file(text, filename):
|
| 76 |
+
if not text.strip(): return gr.update(value="⚠️ Metin boş.")
|
| 77 |
+
folder = "data/wildcards"
|
| 78 |
+
os.makedirs(folder, exist_ok=True)
|
| 79 |
+
filepath = os.path.join(folder, f"{filename}.txt")
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
# Mevcut içeriği oku ve duplicate kontrolü yap
|
| 83 |
+
existing_lines = set()
|
| 84 |
+
if os.path.exists(filepath):
|
| 85 |
+
with open(filepath, "r", encoding="utf-8") as f:
|
| 86 |
+
existing_lines = {line.strip() for line in f if line.strip()}
|
| 87 |
+
|
| 88 |
+
new_text = text.strip()
|
| 89 |
+
|
| 90 |
+
# Eğer bu etiket listesi zaten eklenmişse uyarı ver
|
| 91 |
+
if new_text in existing_lines:
|
| 92 |
+
return gr.update(value=f"⚠️ Bu etiket listesi '{filename}' paketine zaten eklenmiş!", visible=True)
|
| 93 |
+
|
| 94 |
+
# Eklenmemişse dosyaya ekle
|
| 95 |
+
with open(filepath, "a", encoding="utf-8") as f:
|
| 96 |
+
f.write(new_text + "\n")
|
| 97 |
+
return gr.update(value=f"✅ '{filename}' paketine eklendi.", visible=True)
|
| 98 |
+
except Exception as e: return gr.update(value=f"❌ Hata: {e}", visible=True)
|
modules/ui/common.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/ui/common.py
|
| 2 |
+
|
| 3 |
+
CSS_STYLES = """
|
| 4 |
+
body { font-family: 'Inter', 'Segoe UI', Arial, sans-serif; background: #f0f2f5; color: #333; }
|
| 5 |
+
.gradio-container { box-shadow: 0 8px 25px rgba(0, 0, 0, 0.07); border-radius: 12px; overflow: hidden; background: #ffffff; max-width: 96% !important; }
|
| 6 |
+
h1, h2, h3, h4, h5, h6 { color: #2c3e50; font-weight: 600; }
|
| 7 |
+
|
| 8 |
+
.tab-nav { display: flex; justify-content: center; width: 100%; }
|
| 9 |
+
.gr-tab-item { padding: 15px 25px; font-size: 1.1em; font-weight: 500; }
|
| 10 |
+
.gr-tab-item.selected { background: linear-gradient(45deg, #6a11cb 0%, #2575fc 100%); color: white; border-radius: 8px 8px 0 0; }
|
| 11 |
+
.gr-button { border-radius: 8px; font-weight: 600; transition: all 0.3s ease; }
|
| 12 |
+
.gr-button.primary { background: linear-gradient(45deg, #6a11cb 0%, #2575fc 100%); color: white; border: none; }
|
| 13 |
+
.gr-button.primary:hover { opacity: 0.9; transform: translateY(-1px); }
|
| 14 |
+
.gr-button.stop { background: linear-gradient(45deg, #ff4e50 0%, #f9d423 100%); color: white; border: none; }
|
| 15 |
+
.gr-button.stop:hover { opacity: 0.9; transform: translateY(-1px); }
|
| 16 |
+
.gr-textbox, .gr-dropdown, .gr-slider { border-radius: 8px; border: 1px solid #e0e0e0; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); }
|
| 17 |
+
.gr-image, .gr-dataframe { border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06); }
|
| 18 |
+
.kategori-grid-app2 { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; padding: 10px; }
|
| 19 |
+
.kategori-card-app2 { background: #ffffff; border: 1px solid #e2e8f0; border-radius: 16px; padding: 20px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); transition: all 0.3s ease; display: flex; flex-direction: column; }
|
| 20 |
+
.kategori-card-app2:hover { transform: translateY(-4px); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); border-color: #cbd5e1; }
|
| 21 |
+
.kategori-title-app2 { font-size: 1.25rem; font-weight: 700; color: #1e293b; margin-bottom: 12px; border-bottom: 2px solid #f1f5f9; padding-bottom: 8px; letter-spacing: -0.025em; }
|
| 22 |
+
.kategori-tags-app2 { font-size: 0.95rem; color: #475569; line-height: 1.6; font-family: 'Consolas', 'Monaco', monospace; background: #f8fafc; padding: 10px; border-radius: 8px; border: 1px solid #e2e8f0; flex-grow: 1; }
|
| 23 |
+
.grid-tag { background:#fff3; display:inline-block; margin:2px 6px 2px 0; padding:5px 10px; border-radius:12px; border:1px solid #cbd5e1; font-size:14px; color:#334155; }
|
| 24 |
+
#alert-box { margin-top: 15px; padding: 12px 18px; border-radius: 8px; font-weight: 600; text-align: center; }
|
| 25 |
+
#alert-box[value^="✅"] { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
|
| 26 |
+
#alert-box[value^="⚠️"] { background-color: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
|
| 27 |
+
#alert-box[value^="❌"] { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
| 28 |
+
#alert-box[value^="🗑️"] { background-color: #e2e3e5; color: #383d41; border: 1px solid #d6d8db; }
|
| 29 |
+
#renaming-alert-box { margin-top: 15px; padding: 12px 18px; border-radius: 8px; font-weight: 600; text-align: left; white-space: pre-wrap; }
|
| 30 |
+
#renaming-alert-box[value^="✅"] { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
|
| 31 |
+
#renaming-alert-box[value^="⚠️"] { background-color: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
|
| 32 |
+
#renaming-alert-box[value^="❌"] { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
| 33 |
+
#renaming-alert-box[value^="ℹ️"] { background-color: #cce5ff; color: #004085; border: 1px solid #b8daff; }
|
| 34 |
+
.gr-box { border: 1px solid #e0e0e0 !important; border-radius: 12px !important; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important; }
|
| 35 |
+
.gr-row, .gr-column { padding: 10px; }
|
| 36 |
+
.copy-output-btn { margin-top: 10px; padding: 8px 12px; border-radius: 6px; background-color: #f0f0f0; border: 1px solid #ccc; cursor: pointer; font-weight: 500; transition: all 0.2s ease; width: 100%; }
|
| 37 |
+
.copy-output-btn:hover { background-color: #e0e0e0; }
|
| 38 |
+
.vertical-divider { border-left: 2px solid #e0e0e0; height: auto; margin: 0 20px; }
|
| 39 |
+
|
| 40 |
+
/* --- Tab Styling Improvements --- */
|
| 41 |
+
.gradio-container .tabs > .tab-nav {
|
| 42 |
+
border-bottom: 2px solid #e5e7eb;
|
| 43 |
+
margin-bottom: 20px;
|
| 44 |
+
background-color: #f8fafc;
|
| 45 |
+
border-radius: 12px 12px 0 0;
|
| 46 |
+
padding: 10px 10px 0 10px;
|
| 47 |
+
|
| 48 |
+
/* Force single line */
|
| 49 |
+
display: flex;
|
| 50 |
+
justify-content: center; /* Or center, depending on preference */
|
| 51 |
+
flex-wrap: nowrap;
|
| 52 |
+
overflow-x: auto; /* Allow scrolling if it really doesn't fit */
|
| 53 |
+
white-space: nowrap;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.gradio-container .tabs > .tab-nav > button {
|
| 57 |
+
display: inline-flex;
|
| 58 |
+
justify-content: center;
|
| 59 |
+
align-items: center;
|
| 60 |
+
padding: 5px 8px; /* Reduced padding further */
|
| 61 |
+
font-size: 0.9em; /* Reduced font size further */
|
| 62 |
+
font-weight: 700 !important;
|
| 63 |
+
color: #64748b;
|
| 64 |
+
border: none;
|
| 65 |
+
background: transparent;
|
| 66 |
+
cursor: pointer;
|
| 67 |
+
transition: all 0.2s ease;
|
| 68 |
+
border-radius: 8px 8px 0 0;
|
| 69 |
+
margin-right: 2px;
|
| 70 |
+
font-family: 'Segoe UI', sans-serif;
|
| 71 |
+
letter-spacing: 0.01em;
|
| 72 |
+
flex-shrink: 0; /* Prevent squishing too much */
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.gradio-container .tabs > .tab-nav > button:hover {
|
| 76 |
+
color: #3b82f6;
|
| 77 |
+
background-color: #e2e8f0;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.gradio-container .tabs > .tab-nav > button.selected {
|
| 81 |
+
color: #2563eb;
|
| 82 |
+
background-color: #ffffff;
|
| 83 |
+
border-bottom: 3px solid #2563eb;
|
| 84 |
+
box-shadow: 0 -2px 4px -1px rgba(0, 0, 0, 0.05);
|
| 85 |
+
font-size: 0.95em; /* Slightly larger than normal but smaller than before */
|
| 86 |
+
z-index: 10;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
.footer-content { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-top: 15px; }
|
| 93 |
+
|
| 94 |
+
/* Hidden Timer Buttons */
|
| 95 |
+
#timer_btn, #main_loop_btn { display: none !important; }
|
| 96 |
+
"""
|
modules/ui/tab_batch.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# modules/ui/tab_batch.py
|
| 3 |
+
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from modules.tagger import toplu_islem_batch
|
| 6 |
+
from modules.tools import append_text_to_wildcard_file
|
| 7 |
+
from modules.managers.localization_manager import get_str
|
| 8 |
+
|
| 9 |
+
def create_batch_tab(settings_inputs):
|
| 10 |
+
"""
|
| 11 |
+
Toplu etiketleme sekmesini oluşturur.
|
| 12 |
+
settings_inputs: create_settings_tab fonksiyonundan dönen sözlük.
|
| 13 |
+
"""
|
| 14 |
+
with gr.TabItem(get_str("tab_batch_title")):
|
| 15 |
+
with gr.Row():
|
| 16 |
+
toplu_batch_process_button = gr.Button(get_str("btn_process_batch"), variant="primary", size="lg")
|
| 17 |
+
|
| 18 |
+
with gr.Row():
|
| 19 |
+
with gr.Column(scale=3):
|
| 20 |
+
toplu_batch_image_input = gr.File(label=get_str("label_batch_input"), file_count="multiple", file_types=["image"], interactive=True)
|
| 21 |
+
|
| 22 |
+
with gr.Column(scale=7):
|
| 23 |
+
toplu_batch_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", interactive=False)
|
| 24 |
+
with gr.Tab(get_str("tab_batch_original")):
|
| 25 |
+
toplu_batch_combined_original_tags = gr.Textbox(label=get_str("label_batch_original"), interactive=False, lines=15, show_copy_button=True)
|
| 26 |
+
# --- BUTON EKLENDİ ---
|
| 27 |
+
btn_send_toplu = gr.Button(get_str("btn_send_to_cat"), variant="secondary")
|
| 28 |
+
|
| 29 |
+
with gr.Tab(get_str("tab_batch_refined")):
|
| 30 |
+
toplu_batch_refined_tags = gr.Textbox(label=get_str("label_batch_refined"), interactive=False, lines=15, show_copy_button=True)
|
| 31 |
+
with gr.Tab(get_str("tab_batch_cat")):
|
| 32 |
+
toplu_batch_categorized_output = gr.Textbox(label=get_str("label_categorized_tags"), interactive=False, lines=15, show_copy_button=True)
|
| 33 |
+
# --- WILDCARD BUTONLARI (TOPLU) ---
|
| 34 |
+
gr.Markdown(get_str("header_wildcard"))
|
| 35 |
+
with gr.Row():
|
| 36 |
+
btn_female_sfw_toplu = gr.Button("FEMALE SFWPACK", size="sm")
|
| 37 |
+
btn_female_nsfw_toplu = gr.Button("FEMALE NSFWPACK", size="sm")
|
| 38 |
+
btn_female_nude_toplu = gr.Button("FEMALE NUDEPACK", size="sm")
|
| 39 |
+
with gr.Row():
|
| 40 |
+
btn_futa_sfw_toplu = gr.Button("FUTANARI SFWPACK", size="sm")
|
| 41 |
+
btn_futa_nsfw_toplu = gr.Button("FUTANARI NSFWPACK", size="sm")
|
| 42 |
+
btn_futa_nude_toplu = gr.Button("FUTANARI NUDEPACK", size="sm")
|
| 43 |
+
with gr.Row():
|
| 44 |
+
btn_furry_toplu = gr.Button("FURRY-ANTHRO", size="sm")
|
| 45 |
+
btn_hair_toplu = gr.Button("HAIR-EYE", size="sm")
|
| 46 |
+
|
| 47 |
+
with gr.Tab(get_str("tab_batch_gemini")):
|
| 48 |
+
toplu_batch_gemini_output = gr.Textbox(label=get_str("label_batch_gemini"), interactive=False, lines=15, show_copy_button=True)
|
| 49 |
+
with gr.Tab(get_str("tab_batch_html")):
|
| 50 |
+
toplu_batch_results_html = gr.HTML(label=get_str("label_batch_html"))
|
| 51 |
+
|
| 52 |
+
toplu_batch_download_button = gr.File(label=get_str("btn_batch_download"), visible=False, interactive=False)
|
| 53 |
+
|
| 54 |
+
batch_inputs = [
|
| 55 |
+
toplu_batch_image_input,
|
| 56 |
+
settings_inputs["set_joint_thresh"], settings_inputs["set_use_joint"],
|
| 57 |
+
settings_inputs["set_cl_gen_thresh"], settings_inputs["set_cl_char_thresh"], settings_inputs["set_use_cl_tagger"],
|
| 58 |
+
settings_inputs["set_pixai_gen_thresh"], settings_inputs["set_pixai_char_thresh"], settings_inputs["set_use_pixai_tagger"],
|
| 59 |
+
settings_inputs["set_animetagger_model"], settings_inputs["set_animetagger_thresh"], settings_inputs["set_use_animetagger"],
|
| 60 |
+
# Gemini
|
| 61 |
+
settings_inputs["set_use_gemini"], settings_inputs["set_gemini_api_key"], settings_inputs["set_gemini_mode"], settings_inputs["set_gemini_model"],
|
| 62 |
+
settings_inputs["set_gemini_prompt_vision"], settings_inputs["set_gemini_prompt_tags"], settings_inputs["set_gemini_prompt_hybrid"],
|
| 63 |
+
settings_inputs["set_gemini_system_instruction"],
|
| 64 |
+
# Dosyalar
|
| 65 |
+
settings_inputs["set_replacement_file"], settings_inputs["set_synonym_file"], settings_inputs["set_addition_file"],
|
| 66 |
+
settings_inputs["set_toplu_enable_categorization"], settings_inputs["set_toplu_selected_categories"],
|
| 67 |
+
settings_inputs["set_sort_order"], settings_inputs["set_device"], settings_inputs["set_context_weight"]
|
| 68 |
+
]
|
| 69 |
+
batch_outputs = [toplu_batch_alert, toplu_batch_results_html, toplu_batch_download_button, toplu_batch_categorized_output, toplu_batch_combined_original_tags, toplu_batch_refined_tags, toplu_batch_gemini_output]
|
| 70 |
+
toplu_batch_process_button.click(fn=toplu_islem_batch, inputs=batch_inputs, outputs=batch_outputs).then(lambda file_output: gr.update(visible=file_output is not None), inputs=[toplu_batch_download_button], outputs=[toplu_batch_download_button])
|
| 71 |
+
|
| 72 |
+
# WILDCARD EVENTLERI (TOPLU)
|
| 73 |
+
btn_female_sfw_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE SFWPACK"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 74 |
+
btn_female_nsfw_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE NSFWPACK"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 75 |
+
btn_female_nude_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE NUDEPACK"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 76 |
+
|
| 77 |
+
btn_futa_sfw_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI SFWPACK"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 78 |
+
btn_futa_nsfw_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI NSFWPACK"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 79 |
+
btn_futa_nude_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI NUDEPACK"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 80 |
+
|
| 81 |
+
btn_furry_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "FURRY-ANTHRO"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 82 |
+
btn_hair_toplu.click(fn=lambda x: append_text_to_wildcard_file(x, "HAIR-EYE"), inputs=[toplu_batch_categorized_output], outputs=[toplu_batch_alert])
|
| 83 |
+
|
| 84 |
+
return {
|
| 85 |
+
"btn_send_toplu": btn_send_toplu,
|
| 86 |
+
"toplu_batch_combined_original_tags": toplu_batch_combined_original_tags
|
| 87 |
+
}
|
modules/ui/tab_dual.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# modules/ui/tab_dual.py
|
| 3 |
+
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from modules.tagger import process_dual_images
|
| 6 |
+
from modules.tools import append_text_to_wildcard_file
|
| 7 |
+
from modules.managers.localization_manager import get_str
|
| 8 |
+
|
| 9 |
+
def create_dual_tab(settings_inputs):
|
| 10 |
+
"""
|
| 11 |
+
Dual (ikili) etiketleme sekmesini oluşturur.
|
| 12 |
+
settings_inputs: create_settings_tab fonksiyonundan dönen sözlük.
|
| 13 |
+
"""
|
| 14 |
+
with gr.TabItem(get_str("tab_dual_title")):
|
| 15 |
+
gr.Markdown(get_str("dual_note"))
|
| 16 |
+
with gr.Row():
|
| 17 |
+
dual_process_button_top = gr.Button(get_str("btn_process_dual"), variant="primary", size="lg", visible=False)
|
| 18 |
+
|
| 19 |
+
with gr.Row():
|
| 20 |
+
with gr.Column(scale=3):
|
| 21 |
+
dual_image_input1 = gr.Image(type="pil", label=get_str("label_img1"), width=300, height=300)
|
| 22 |
+
dual_image_input2 = gr.Image(type="pil", label=get_str("label_img2"), width=300, height=300)
|
| 23 |
+
|
| 24 |
+
with gr.Column(scale=7):
|
| 25 |
+
dual_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False, interactive=False)
|
| 26 |
+
with gr.Tab(get_str("tab_dual_combined")):
|
| 27 |
+
dual_combined_all_tags = gr.Textbox(label=get_str("label_dual_combined"), interactive=True, lines=8, show_copy_button=True)
|
| 28 |
+
# --- BUTON EKLENDİ ---
|
| 29 |
+
btn_send_dual = gr.Button(get_str("btn_send_to_cat"), variant="secondary")
|
| 30 |
+
|
| 31 |
+
with gr.Tab(get_str("tab_refined_tags")):
|
| 32 |
+
dual_combined_refined_tags = gr.Textbox(label=get_str("label_dual_refined"), interactive=True, lines=8, show_copy_button=True)
|
| 33 |
+
with gr.Tab(get_str("tab_categorized_tags")):
|
| 34 |
+
dual_combined_categorized_output = gr.Textbox(label=get_str("label_dual_cat"), interactive=True, lines=8, show_copy_button=True)
|
| 35 |
+
# --- WILDCARD BUTONLARI (DUAL) ---
|
| 36 |
+
gr.Markdown(get_str("header_wildcard"))
|
| 37 |
+
with gr.Row():
|
| 38 |
+
btn_female_sfw_dual = gr.Button("FEMALE SFWPACK", size="sm")
|
| 39 |
+
btn_female_nsfw_dual = gr.Button("FEMALE NSFWPACK", size="sm")
|
| 40 |
+
btn_female_nude_dual = gr.Button("FEMALE NUDEPACK", size="sm")
|
| 41 |
+
with gr.Row():
|
| 42 |
+
btn_futa_sfw_dual = gr.Button("FUTANARI SFWPACK", size="sm")
|
| 43 |
+
btn_futa_nsfw_dual = gr.Button("FUTANARI NSFWPACK", size="sm")
|
| 44 |
+
btn_futa_nude_dual = gr.Button("FUTANARI NUDEPACK", size="sm")
|
| 45 |
+
with gr.Row():
|
| 46 |
+
btn_furry_dual = gr.Button("FURRY-ANTHRO", size="sm")
|
| 47 |
+
btn_hair_dual = gr.Button("HAIR-EYE", size="sm")
|
| 48 |
+
|
| 49 |
+
with gr.Tab(get_str("tab_gemini_caption")):
|
| 50 |
+
with gr.Row():
|
| 51 |
+
dual_gemini_output_img1 = gr.Textbox(label=get_str("label_gemini_img1"), interactive=True, lines=5, show_copy_button=True)
|
| 52 |
+
dual_gemini_output_img2 = gr.Textbox(label=get_str("label_gemini_img2"), interactive=True, lines=5, show_copy_button=True)
|
| 53 |
+
with gr.Tab(get_str("tab_combined_caption")):
|
| 54 |
+
dual_combined_cat_gem_output = gr.Textbox(label=get_str("label_combined_caption"), interactive=True, lines=10, show_copy_button=True)
|
| 55 |
+
|
| 56 |
+
dual_inputs = [
|
| 57 |
+
dual_image_input1, dual_image_input2,
|
| 58 |
+
settings_inputs["set_joint_thresh"], settings_inputs["set_use_joint"], settings_inputs["set_joint_thresh"], settings_inputs["set_use_joint"],
|
| 59 |
+
settings_inputs["set_cl_gen_thresh"], settings_inputs["set_cl_char_thresh"], settings_inputs["set_use_cl_tagger"], settings_inputs["set_cl_gen_thresh"], settings_inputs["set_cl_char_thresh"], settings_inputs["set_use_cl_tagger"],
|
| 60 |
+
settings_inputs["set_pixai_gen_thresh"], settings_inputs["set_pixai_char_thresh"], settings_inputs["set_use_pixai_tagger"], settings_inputs["set_pixai_gen_thresh"], settings_inputs["set_pixai_char_thresh"], settings_inputs["set_use_pixai_tagger"],
|
| 61 |
+
settings_inputs["set_animetagger_model"], settings_inputs["set_animetagger_thresh"], settings_inputs["set_use_animetagger"], settings_inputs["set_animetagger_model"], settings_inputs["set_animetagger_thresh"], settings_inputs["set_use_animetagger"],
|
| 62 |
+
# Gemini
|
| 63 |
+
settings_inputs["set_use_gemini"], settings_inputs["set_gemini_api_key"], settings_inputs["set_gemini_mode"], settings_inputs["set_gemini_model"],
|
| 64 |
+
settings_inputs["set_gemini_prompt_vision"], settings_inputs["set_gemini_prompt_tags"], settings_inputs["set_gemini_prompt_hybrid"],
|
| 65 |
+
settings_inputs["set_gemini_system_instruction"],
|
| 66 |
+
# Dosyalar
|
| 67 |
+
settings_inputs["set_replacement_file"], settings_inputs["set_synonym_file"], settings_inputs["set_addition_file"],
|
| 68 |
+
settings_inputs["set_dual1_enable_categorization"], settings_inputs["set_dual1_selected_categories"],
|
| 69 |
+
settings_inputs["set_dual2_enable_categorization"], settings_inputs["set_dual2_selected_categories"],
|
| 70 |
+
settings_inputs["set_sort_order"], settings_inputs["set_device"], settings_inputs["set_context_weight"]
|
| 71 |
+
]
|
| 72 |
+
def update_dual_outputs_wrapper(*args):
|
| 73 |
+
final_overall_alert, all_tags_str, refined_tags_str, combined_cat_tags_str, gemini_out1, gemini_out2, combined_cat_gem = process_dual_images(*args)
|
| 74 |
+
return (gr.update(value=final_overall_alert, visible=True), all_tags_str, refined_tags_str, combined_cat_tags_str, gemini_out1, gemini_out2, combined_cat_gem)
|
| 75 |
+
|
| 76 |
+
dual_process_button_top.click(fn=update_dual_outputs_wrapper, inputs=dual_inputs, outputs=[dual_alert, dual_combined_all_tags, dual_combined_refined_tags, dual_combined_categorized_output, dual_gemini_output_img1, dual_gemini_output_img2, dual_combined_cat_gem_output])
|
| 77 |
+
|
| 78 |
+
# --- OTOMATİK TETİKLEME (RESİM DEĞİŞİNCE) ---
|
| 79 |
+
dual_image_input1.change(fn=update_dual_outputs_wrapper, inputs=dual_inputs, outputs=[dual_alert, dual_combined_all_tags, dual_combined_refined_tags, dual_combined_categorized_output, dual_gemini_output_img1, dual_gemini_output_img2, dual_combined_cat_gem_output])
|
| 80 |
+
dual_image_input2.change(fn=update_dual_outputs_wrapper, inputs=dual_inputs, outputs=[dual_alert, dual_combined_all_tags, dual_combined_refined_tags, dual_combined_categorized_output, dual_gemini_output_img1, dual_gemini_output_img2, dual_combined_cat_gem_output])
|
| 81 |
+
|
| 82 |
+
# WILDCARD EVENTLERI (DUAL)
|
| 83 |
+
btn_female_sfw_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE SFWPACK"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 84 |
+
btn_female_nsfw_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE NSFWPACK"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 85 |
+
btn_female_nude_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE NUDEPACK"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 86 |
+
|
| 87 |
+
btn_futa_sfw_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI SFWPACK"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 88 |
+
btn_futa_nsfw_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI NSFWPACK"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 89 |
+
btn_futa_nude_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI NUDEPACK"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 90 |
+
|
| 91 |
+
btn_furry_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "FURRY-ANTHRO"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 92 |
+
btn_hair_dual.click(fn=lambda x: append_text_to_wildcard_file(x, "HAIR-EYE"), inputs=[dual_combined_categorized_output], outputs=[dual_alert])
|
| 93 |
+
|
| 94 |
+
return {
|
| 95 |
+
"btn_send_dual": btn_send_dual,
|
| 96 |
+
"dual_combined_all_tags": dual_combined_all_tags
|
| 97 |
+
}
|
modules/ui/tab_settings.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# modules/ui/tab_settings.py
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from modules.config_manager import save_global_tagger_settings
|
| 7 |
+
from modules.managers.rule_manager import (
|
| 8 |
+
get_replacement_files,
|
| 9 |
+
get_synonym_files,
|
| 10 |
+
get_addition_files
|
| 11 |
+
)
|
| 12 |
+
from modules.managers.category_manager import kategori_listesi
|
| 13 |
+
from modules.managers.localization_manager import get_str # Localization import
|
| 14 |
+
# cl_tagger_instance'ı kontrol etmek için (opsiyonel, main'den de geçilebilir ama import etmek daha kolay)
|
| 15 |
+
from modules.tagger import cl_tagger_instance
|
| 16 |
+
|
| 17 |
+
REPLACEMENT_RULES_KLASORU = "data/rules/replacement_rules"
|
| 18 |
+
SYNONYM_RULES_KLASORU = "data/rules/synonym_rules"
|
| 19 |
+
ADDITION_RULES_KLASORU = "data/rules/addition_rules"
|
| 20 |
+
|
| 21 |
+
def create_settings_tab(app_config):
|
| 22 |
+
initial_global_settings = app_config.get("global_tagger_settings", {})
|
| 23 |
+
initial_general_settings = app_config.get("general_settings", {"device": "Auto", "language": "tr"})
|
| 24 |
+
|
| 25 |
+
# Normalizasyon Haritaları (Legacy -> New Key)
|
| 26 |
+
sort_normalization = {
|
| 27 |
+
"Alfabetik": "alpha",
|
| 28 |
+
"Uzunluğa Göre": "length",
|
| 29 |
+
"Rastgele": "random",
|
| 30 |
+
"Orijinal": "original"
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
anime_normalization = {
|
| 34 |
+
"MobileNet V4 (Hızlı)": "mobilenetv4",
|
| 35 |
+
"ConvNeXt V2 Huge (Pro)": "convnextv2",
|
| 36 |
+
"Caformer B36 (Yeni)": "caformer"
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
# config'den gelen değerleri normalize et
|
| 40 |
+
current_sort = initial_global_settings.get("sort_order", "alpha")
|
| 41 |
+
current_sort = sort_normalization.get(current_sort, current_sort) # Eğer map'te varsa değiştir, yoksa olduğu gibi bırak (zaten key ise)
|
| 42 |
+
|
| 43 |
+
current_anime = initial_global_settings.get("animetagger_model", "mobilenetv4")
|
| 44 |
+
current_anime = anime_normalization.get(current_anime, current_anime)
|
| 45 |
+
|
| 46 |
+
default_synonym_file = os.path.join(SYNONYM_RULES_KLASORU, "varsayilan_birlesimler.txt")
|
| 47 |
+
default_replacement_file = os.path.join(REPLACEMENT_RULES_KLASORU, "varsayilan_degisiklikler.txt")
|
| 48 |
+
default_addition_file = os.path.join(ADDITION_RULES_KLASORU, "varsayilan_eklemeler.txt")
|
| 49 |
+
|
| 50 |
+
with gr.TabItem(get_str("tab_settings_title")):
|
| 51 |
+
gr.Markdown(get_str("settings_header"))
|
| 52 |
+
|
| 53 |
+
with gr.Row():
|
| 54 |
+
save_global_btn = gr.Button(get_str("save_settings_btn"), variant="primary", size="lg")
|
| 55 |
+
global_settings_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False, interactive=False)
|
| 56 |
+
|
| 57 |
+
# 1. Çalıştırma Cihazı Ayarı
|
| 58 |
+
with gr.Accordion("🖥️ Çalıştırma Cihazı Ayarı", open=False):
|
| 59 |
+
set_device = gr.Radio(["Auto", "CPU", "CUDA"], value=initial_general_settings.get("device", "Auto"), label=get_str("device_label"), info=get_str("device_info"))
|
| 60 |
+
|
| 61 |
+
# 2. Dil ve Tema Ayarı
|
| 62 |
+
with gr.Accordion("🌐 Dil ve Tema Ayarı", open=False):
|
| 63 |
+
with gr.Row():
|
| 64 |
+
set_language = gr.Dropdown(
|
| 65 |
+
label=get_str("language_label"),
|
| 66 |
+
choices=[("Türkçe", "tr"), ("English", "en")],
|
| 67 |
+
value=initial_general_settings.get("language", "tr"),
|
| 68 |
+
interactive=True,
|
| 69 |
+
info=get_str("language_info")
|
| 70 |
+
)
|
| 71 |
+
set_theme = gr.Dropdown(
|
| 72 |
+
label=get_str("theme_label"),
|
| 73 |
+
choices=["Default", "Soft", "Glass", "Monochrome"],
|
| 74 |
+
value=initial_general_settings.get("theme", "Default"),
|
| 75 |
+
interactive=True,
|
| 76 |
+
info=get_str("theme_info")
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
# 3. Kural Yönetimi Ayarları
|
| 80 |
+
with gr.Accordion("📜 Kural Yönetimi Ayarları", open=False):
|
| 81 |
+
set_replacement_file = gr.Dropdown(label=get_str("replacement_file_label"), choices=get_replacement_files(), value=initial_global_settings.get("replacement_file") if initial_global_settings.get("replacement_file") in get_replacement_files() else (default_replacement_file if os.path.exists(default_replacement_file) else None), interactive=True)
|
| 82 |
+
set_synonym_file = gr.Dropdown(label=get_str("synonym_file_label"), choices=get_synonym_files(), value=initial_global_settings.get("synonym_file") if initial_global_settings.get("synonym_file") in get_synonym_files() else (default_synonym_file if os.path.exists(default_synonym_file) else None), interactive=True)
|
| 83 |
+
set_addition_file = gr.Dropdown(label=get_str("addition_file_label"), choices=get_addition_files(), value=initial_global_settings.get("addition_file") if initial_global_settings.get("addition_file") in get_addition_files() else (default_addition_file if os.path.exists(default_addition_file) else None), interactive=True)
|
| 84 |
+
|
| 85 |
+
# 4. Etiket Sıralama Ayarı
|
| 86 |
+
with gr.Accordion("🔡 Etiket Sıralama Ayarı", open=False):
|
| 87 |
+
set_sort_order = gr.Radio(
|
| 88 |
+
label=get_str("sort_order_label"),
|
| 89 |
+
choices=[
|
| 90 |
+
(get_str("sort_alpha"), "alpha"),
|
| 91 |
+
(get_str("sort_len"), "length"),
|
| 92 |
+
(get_str("sort_random"), "random"),
|
| 93 |
+
(get_str("sort_orig"), "original")
|
| 94 |
+
],
|
| 95 |
+
value=current_sort
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
# 5. Rafine Bağlam Ağırlığı Ayarı
|
| 99 |
+
with gr.Accordion("⚖️ Rafine Bağlam Ağırlığı Ayarı", open=False):
|
| 100 |
+
set_context_weight = gr.Slider(minimum=0.0, maximum=1.0, value=initial_global_settings.get("context_weight", 0.0), step=0.05, label=get_str("context_weight_label"), info=get_str("context_weight_info"))
|
| 101 |
+
|
| 102 |
+
# 6. Kategorizasyon Ayarları
|
| 103 |
+
with gr.Accordion("📂 Kategorizasyon Ayarları", open=False):
|
| 104 |
+
|
| 105 |
+
with gr.Tabs():
|
| 106 |
+
with gr.TabItem(get_str("tab_single")):
|
| 107 |
+
set_tekil_enable_categorization = gr.Checkbox(label=get_str("enable_cat_single"), value=initial_global_settings.get("tekil_enable_categorization", False))
|
| 108 |
+
set_tekil_selected_categories = gr.Dropdown(label=get_str("cat_single_label"), choices=kategori_listesi(), multiselect=True, interactive=True, value=initial_global_settings.get("tekil_selected_categories"), visible=initial_global_settings.get("tekil_enable_categorization", False))
|
| 109 |
+
|
| 110 |
+
with gr.TabItem(get_str("tab_batch")):
|
| 111 |
+
set_toplu_enable_categorization = gr.Checkbox(label=get_str("enable_cat_batch"), value=initial_global_settings.get("toplu_enable_categorization", False))
|
| 112 |
+
set_toplu_selected_categories = gr.Dropdown(label=get_str("cat_batch_label"), choices=kategori_listesi(), multiselect=True, interactive=True, value=initial_global_settings.get("toplu_selected_categories"), visible=initial_global_settings.get("toplu_enable_categorization", False))
|
| 113 |
+
|
| 114 |
+
with gr.TabItem(get_str("tab_dual_left")):
|
| 115 |
+
set_dual1_enable_categorization = gr.Checkbox(label=get_str("enable_cat_dual1"), value=initial_global_settings.get("dual1_enable_categorization", False))
|
| 116 |
+
set_dual1_selected_categories = gr.Dropdown(label=get_str("cat_dual1_label"), choices=kategori_listesi(), multiselect=True, interactive=True, value=initial_global_settings.get("dual1_selected_categories"), visible=initial_global_settings.get("dual1_enable_categorization", False))
|
| 117 |
+
|
| 118 |
+
with gr.TabItem(get_str("tab_dual_right")):
|
| 119 |
+
set_dual2_enable_categorization = gr.Checkbox(label=get_str("enable_cat_dual2"), value=initial_global_settings.get("dual2_enable_categorization", False))
|
| 120 |
+
set_dual2_selected_categories = gr.Dropdown(label=get_str("cat_dual2_label"), choices=kategori_listesi(), multiselect=True, interactive=True, value=initial_global_settings.get("dual2_selected_categories"), visible=initial_global_settings.get("dual2_enable_categorization", False))
|
| 121 |
+
|
| 122 |
+
set_tekil_enable_categorization.change(lambda value: gr.update(visible=value), inputs=[set_tekil_enable_categorization], outputs=[set_tekil_selected_categories])
|
| 123 |
+
set_toplu_enable_categorization.change(lambda value: gr.update(visible=value), inputs=[set_toplu_enable_categorization], outputs=[set_toplu_selected_categories])
|
| 124 |
+
set_dual1_enable_categorization.change(lambda value: gr.update(visible=value), inputs=[set_dual1_enable_categorization], outputs=[set_dual1_selected_categories])
|
| 125 |
+
set_dual2_enable_categorization.change(lambda value: gr.update(visible=value), inputs=[set_dual2_enable_categorization], outputs=[set_dual2_selected_categories])
|
| 126 |
+
|
| 127 |
+
# 7. Tagger Ayarları
|
| 128 |
+
with gr.Accordion("🤖 Tagger Ayarları", open=False):
|
| 129 |
+
with gr.Tabs():
|
| 130 |
+
with gr.TabItem("Joint Tagger"):
|
| 131 |
+
with gr.Row():
|
| 132 |
+
set_use_joint = gr.Checkbox(label=get_str("use_joint"), value=initial_global_settings.get("use_joint", True))
|
| 133 |
+
set_joint_thresh = gr.Slider(minimum=0.01, maximum=1.0, value=initial_global_settings.get("joint_thresh", 0.25), step=0.01, label=get_str("joint_thresh"))
|
| 134 |
+
|
| 135 |
+
with gr.TabItem("CL Tagger"):
|
| 136 |
+
with gr.Row():
|
| 137 |
+
set_use_cl_tagger = gr.Checkbox(label=get_str("use_cl"), value=initial_global_settings.get("use_cl_tagger", True), interactive=(cl_tagger_instance is not None))
|
| 138 |
+
with gr.Row():
|
| 139 |
+
set_cl_gen_thresh = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=initial_global_settings.get("cl_gen_thresh", 0.55), label=get_str("cl_gen_thresh"))
|
| 140 |
+
set_cl_char_thresh = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=initial_global_settings.get("cl_char_thresh", 0.60), label=get_str("cl_char_thresh"))
|
| 141 |
+
|
| 142 |
+
with gr.TabItem("PixAI Tagger"):
|
| 143 |
+
with gr.Row():
|
| 144 |
+
set_use_pixai_tagger = gr.Checkbox(label=get_str("use_pixai"), value=initial_global_settings.get("use_pixai_tagger", False))
|
| 145 |
+
with gr.Row():
|
| 146 |
+
set_pixai_gen_thresh = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=initial_global_settings.get("pixai_general_thresh", 0.30), label=get_str("pixai_gen_thresh"))
|
| 147 |
+
set_pixai_char_thresh = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=initial_global_settings.get("pixai_char_thresh", 0.85), label=get_str("pixai_char_thresh"))
|
| 148 |
+
|
| 149 |
+
with gr.TabItem("Anime Tagger"):
|
| 150 |
+
with gr.Row():
|
| 151 |
+
set_use_animetagger = gr.Checkbox(label=get_str("use_anime"), value=initial_global_settings.get("use_animetagger", False))
|
| 152 |
+
set_animetagger_model = gr.Dropdown(
|
| 153 |
+
label=get_str("anime_model_label"),
|
| 154 |
+
choices=[
|
| 155 |
+
(get_str("model_mobilenet"), "mobilenetv4"),
|
| 156 |
+
(get_str("model_convnext"), "convnextv2"),
|
| 157 |
+
(get_str("model_caformer"), "caformer")
|
| 158 |
+
],
|
| 159 |
+
value=current_anime,
|
| 160 |
+
interactive=True
|
| 161 |
+
)
|
| 162 |
+
set_animetagger_thresh = gr.Slider(minimum=0.01, maximum=1.0, value=initial_global_settings.get("animetagger_thresh", 0.35), step=0.01, label=get_str("anime_thresh"))
|
| 163 |
+
|
| 164 |
+
with gr.TabItem("Gemini Caption"):
|
| 165 |
+
gr.Markdown(get_str("gemini_desc"))
|
| 166 |
+
set_use_gemini = gr.Checkbox(label=get_str("use_gemini"), value=initial_global_settings.get("use_gemini", False))
|
| 167 |
+
set_gemini_api_key = gr.Textbox(label=get_str("gemini_api_key"), placeholder="AIzaSy...", type="password", value=initial_global_settings.get("gemini_api_key", ""))
|
| 168 |
+
|
| 169 |
+
set_gemini_model = gr.Dropdown(
|
| 170 |
+
label=get_str("gemini_model"),
|
| 171 |
+
choices=["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash"],
|
| 172 |
+
value=initial_global_settings.get("gemini_model", "gemini-2.5-flash"),
|
| 173 |
+
interactive=True,
|
| 174 |
+
info=get_str("gemini_model_info")
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
set_gemini_mode = gr.Radio(
|
| 178 |
+
label=get_str("gemini_mode_label"),
|
| 179 |
+
choices=["Vision", "Tags", "Vision + Tags"],
|
| 180 |
+
value=initial_global_settings.get("gemini_mode", "Vision"),
|
| 181 |
+
info=get_str("gemini_mode_info")
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
set_gemini_system_instruction = gr.Textbox(
|
| 185 |
+
label=get_str("gemini_system_instr"),
|
| 186 |
+
value=initial_global_settings.get("gemini_system_instruction", "You are a helpful assistant that generates detailed and accurate image captions for AI image generation datasets."),
|
| 187 |
+
lines=2,
|
| 188 |
+
placeholder="Örn: You are an expert photographer describing images...",
|
| 189 |
+
info=get_str("gemini_system_info")
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
gr.Markdown(get_str("gemini_prompt_settings"))
|
| 193 |
+
with gr.Tabs():
|
| 194 |
+
with gr.TabItem(get_str("prompt_vision")):
|
| 195 |
+
set_gemini_prompt_vision = gr.Textbox(
|
| 196 |
+
label=get_str("prompt_vision"),
|
| 197 |
+
value=initial_global_settings.get("gemini_prompt_vision", "Describe this image in a detailed, natural language caption for an AI image generator dataset. Focus on the main subject, clothing, pose, and background. Do not use bullet points."),
|
| 198 |
+
lines=3
|
| 199 |
+
)
|
| 200 |
+
with gr.TabItem(get_str("prompt_tags")):
|
| 201 |
+
set_gemini_prompt_tags = gr.Textbox(
|
| 202 |
+
label=get_str("prompt_tags"),
|
| 203 |
+
value=initial_global_settings.get("gemini_prompt_tags", "Create a fluent, natural language caption based on the provided tags. Focus on describing the scene vividly."),
|
| 204 |
+
lines=3
|
| 205 |
+
)
|
| 206 |
+
with gr.TabItem(get_str("prompt_hybrid")):
|
| 207 |
+
set_gemini_prompt_hybrid = gr.Textbox(
|
| 208 |
+
label=get_str("prompt_hybrid"),
|
| 209 |
+
value=initial_global_settings.get("gemini_prompt_hybrid", "Describe this image using the visual details and refine the description with the provided tags for accuracy. Focus on a cohesive narrative."),
|
| 210 |
+
lines=3
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
save_global_btn.click(fn=save_global_tagger_settings, inputs=[
|
| 214 |
+
set_device, set_language, set_theme,
|
| 215 |
+
set_use_joint, set_joint_thresh,
|
| 216 |
+
set_use_cl_tagger, set_cl_gen_thresh, set_cl_char_thresh,
|
| 217 |
+
set_use_pixai_tagger, set_pixai_gen_thresh, set_pixai_char_thresh,
|
| 218 |
+
set_use_animetagger, set_animetagger_model, set_animetagger_thresh,
|
| 219 |
+
# Gemini
|
| 220 |
+
set_use_gemini, set_gemini_api_key, set_gemini_mode, set_gemini_model,
|
| 221 |
+
set_gemini_prompt_vision, set_gemini_prompt_tags, set_gemini_prompt_hybrid,
|
| 222 |
+
set_gemini_system_instruction,
|
| 223 |
+
# Dosyalar
|
| 224 |
+
set_replacement_file, set_synonym_file, set_addition_file,
|
| 225 |
+
set_sort_order,
|
| 226 |
+
set_context_weight,
|
| 227 |
+
# Kategorizasyon
|
| 228 |
+
set_tekil_enable_categorization, set_tekil_selected_categories,
|
| 229 |
+
set_toplu_enable_categorization, set_toplu_selected_categories,
|
| 230 |
+
set_dual1_enable_categorization, set_dual1_selected_categories,
|
| 231 |
+
set_dual2_enable_categorization, set_dual2_selected_categories
|
| 232 |
+
], outputs=global_settings_alert)
|
| 233 |
+
|
| 234 |
+
# Diğer modüllerde (tab_single, tab_batch, vb.) kullanılmak üzere bu ayarları döndürüyoruz
|
| 235 |
+
return {
|
| 236 |
+
"set_device": set_device,
|
| 237 |
+
"set_use_joint": set_use_joint,
|
| 238 |
+
"set_joint_thresh": set_joint_thresh,
|
| 239 |
+
"set_use_cl_tagger": set_use_cl_tagger,
|
| 240 |
+
"set_cl_gen_thresh": set_cl_gen_thresh,
|
| 241 |
+
"set_cl_char_thresh": set_cl_char_thresh,
|
| 242 |
+
"set_use_pixai_tagger": set_use_pixai_tagger,
|
| 243 |
+
"set_pixai_gen_thresh": set_pixai_gen_thresh,
|
| 244 |
+
"set_pixai_char_thresh": set_pixai_char_thresh,
|
| 245 |
+
"set_use_animetagger": set_use_animetagger,
|
| 246 |
+
"set_animetagger_model": set_animetagger_model,
|
| 247 |
+
"set_animetagger_thresh": set_animetagger_thresh,
|
| 248 |
+
"set_use_gemini": set_use_gemini,
|
| 249 |
+
"set_gemini_api_key": set_gemini_api_key,
|
| 250 |
+
"set_gemini_mode": set_gemini_mode,
|
| 251 |
+
"set_gemini_model": set_gemini_model,
|
| 252 |
+
"set_gemini_prompt_vision": set_gemini_prompt_vision,
|
| 253 |
+
"set_gemini_prompt_tags": set_gemini_prompt_tags,
|
| 254 |
+
"set_gemini_prompt_hybrid": set_gemini_prompt_hybrid,
|
| 255 |
+
"set_gemini_system_instruction": set_gemini_system_instruction,
|
| 256 |
+
"set_replacement_file": set_replacement_file,
|
| 257 |
+
"set_synonym_file": set_synonym_file,
|
| 258 |
+
"set_addition_file": set_addition_file,
|
| 259 |
+
"set_sort_order": set_sort_order,
|
| 260 |
+
"set_context_weight": set_context_weight,
|
| 261 |
+
"set_tekil_enable_categorization": set_tekil_enable_categorization,
|
| 262 |
+
"set_tekil_selected_categories": set_tekil_selected_categories,
|
| 263 |
+
"set_toplu_enable_categorization": set_toplu_enable_categorization,
|
| 264 |
+
"set_toplu_selected_categories": set_toplu_selected_categories,
|
| 265 |
+
"set_dual1_enable_categorization": set_dual1_enable_categorization,
|
| 266 |
+
"set_dual1_selected_categories": set_dual1_selected_categories,
|
| 267 |
+
"set_dual2_enable_categorization": set_dual2_enable_categorization,
|
| 268 |
+
"set_dual2_selected_categories": set_dual2_selected_categories
|
| 269 |
+
}
|
modules/ui/tab_single.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# modules/ui/tab_single.py
|
| 3 |
+
|
| 4 |
+
import gradio as gr
|
| 5 |
+
from modules.tagger import toplu_islem
|
| 6 |
+
from modules.managers.localization_manager import get_str
|
| 7 |
+
from modules.tools import append_text_to_wildcard_file
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
# JS Timer main.py üzerinden yönetiliyor
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
import base64
|
| 14 |
+
import io
|
| 15 |
+
from PIL import Image
|
| 16 |
+
from modules.shared_state import get_web_image, reset_image_event
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def fetch_web_image(last_known_ts):
|
| 20 |
+
"""Shared state'den son gelen web resmini kontrol eder. Yeni ise döndürür."""
|
| 21 |
+
data, timestamp = get_web_image()
|
| 22 |
+
|
| 23 |
+
# Henüz veri yoksa veya veri eskiyse (zaten işlendiyse) işlem yapma
|
| 24 |
+
if not data or timestamp == last_known_ts:
|
| 25 |
+
return gr.update(), gr.update(), last_known_ts
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
if "," in data: data = data.split(",")[1]
|
| 29 |
+
|
| 30 |
+
image_data = base64.b64decode(data)
|
| 31 |
+
image = Image.open(io.BytesIO(image_data))
|
| 32 |
+
|
| 33 |
+
# Resmi başarılı şekilde aldık, yeni timestamp'i döndür
|
| 34 |
+
reset_image_event() # Flag'i sıfırla ki JS tekrar basmasın
|
| 35 |
+
return image, "✅ Web'den yeni resim alındı!", timestamp
|
| 36 |
+
except Exception as e:
|
| 37 |
+
return gr.update(), f"❌ Hata: {str(e)}", last_known_ts
|
| 38 |
+
|
| 39 |
+
def create_single_tab(settings_inputs):
|
| 40 |
+
"""
|
| 41 |
+
Tekil etiketleme sekmesini oluşturur.
|
| 42 |
+
settings_inputs: create_settings_tab fonksiyonundan dönen sözlük.
|
| 43 |
+
"""
|
| 44 |
+
with gr.TabItem(get_str("tab_single_title"), id="tab_single"):
|
| 45 |
+
with gr.Row():
|
| 46 |
+
tekil_process_button_top = gr.Button(get_str("btn_process_single"), variant="primary", size="lg", visible=False)
|
| 47 |
+
|
| 48 |
+
with gr.Row():
|
| 49 |
+
with gr.Column(scale=3):
|
| 50 |
+
# Manuel buton artık gereksiz olduğu için gizlendi (Otomatik çalışıyor)
|
| 51 |
+
btn_get_from_web = gr.Button("🌐 Web'den Yapıştır (Eklenti)", variant="secondary", size="sm", visible=False)
|
| 52 |
+
toplu_image_input = gr.Image(type="pil", label=get_str("label_image_upload"), width=512, height=512)
|
| 53 |
+
|
| 54 |
+
with gr.Column(scale=7):
|
| 55 |
+
toplu_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False, interactive=False)
|
| 56 |
+
|
| 57 |
+
with gr.Tab(get_str("tab_raw_tags")):
|
| 58 |
+
toplu_filtered_output = gr.Textbox(label=get_str("label_unique_tags"), interactive=True, lines=10, show_copy_button=True)
|
| 59 |
+
# --- BUTON EKLENDİ ---
|
| 60 |
+
btn_send_tekil = gr.Button(get_str("btn_send_to_cat"), variant="secondary")
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
with gr.Tab(get_str("tab_refined_tags")):
|
| 64 |
+
tekil_refined_output = gr.Textbox(label=get_str("label_refined_tags"), interactive=True, lines=10, show_copy_button=True)
|
| 65 |
+
with gr.Tab(get_str("tab_categorized_tags")):
|
| 66 |
+
tekil_categorized_output = gr.Textbox(label=get_str("label_categorized_tags"), interactive=False, lines=10, show_copy_button=True)
|
| 67 |
+
# --- WILDCARD BUTONLARI (TEKİL) ---
|
| 68 |
+
gr.Markdown(get_str("header_wildcard"))
|
| 69 |
+
with gr.Row():
|
| 70 |
+
btn_female_sfw_tekil = gr.Button("FEMALE SFWPACK", size="sm")
|
| 71 |
+
btn_female_nsfw_tekil = gr.Button("FEMALE NSFWPACK", size="sm")
|
| 72 |
+
btn_female_nude_tekil = gr.Button("FEMALE NUDEPACK", size="sm")
|
| 73 |
+
with gr.Row():
|
| 74 |
+
btn_futa_sfw_tekil = gr.Button("FUTANARI SFWPACK", size="sm")
|
| 75 |
+
btn_futa_nsfw_tekil = gr.Button("FUTANARI NSFWPACK", size="sm")
|
| 76 |
+
btn_futa_nude_tekil = gr.Button("FUTANARI NUDEPACK", size="sm")
|
| 77 |
+
with gr.Row():
|
| 78 |
+
btn_furry_tekil = gr.Button("FURRY-ANTHRO", size="sm")
|
| 79 |
+
btn_hair_tekil = gr.Button("HAIR-EYE", size="sm")
|
| 80 |
+
|
| 81 |
+
with gr.Tab(get_str("tab_gemini_caption")):
|
| 82 |
+
tekil_gemini_output = gr.Textbox(label=get_str("label_gemini_caption"), interactive=True, lines=5, show_copy_button=True)
|
| 83 |
+
with gr.Tab(get_str("tab_combined_caption")):
|
| 84 |
+
tekil_combined_cat_gem_output = gr.Textbox(label=get_str("label_combined_caption"), interactive=True, lines=10, show_copy_button=True)
|
| 85 |
+
|
| 86 |
+
# --- OTOMATİK KONTROL İÇİN STATE VE TIMER BUTTON ---
|
| 87 |
+
ts_state = gr.State(value=0) # Son işlenen resmin zaman damgası
|
| 88 |
+
|
| 89 |
+
# Gradio 3.x uyumlu Timer alternatifi: JS ile tıklanan buton
|
| 90 |
+
# Butonu visible=True yapıp style ile gizliyoruz ki JS bulabilsin
|
| 91 |
+
timer_btn = gr.Button("Timer", elem_id="timer_btn", visible=True)
|
| 92 |
+
# CSS ile gizle: #timer_btn { display: none !important; } (Bunu common.py'ye veya inline css'e ekleyeceğiz)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
inputs_for_tekil = [
|
| 96 |
+
toplu_image_input,
|
| 97 |
+
settings_inputs["set_joint_thresh"], settings_inputs["set_use_joint"],
|
| 98 |
+
settings_inputs["set_cl_gen_thresh"], settings_inputs["set_cl_char_thresh"], settings_inputs["set_use_cl_tagger"],
|
| 99 |
+
settings_inputs["set_pixai_gen_thresh"], settings_inputs["set_pixai_char_thresh"], settings_inputs["set_use_pixai_tagger"],
|
| 100 |
+
settings_inputs["set_animetagger_model"], settings_inputs["set_animetagger_thresh"], settings_inputs["set_use_animetagger"],
|
| 101 |
+
# Gemini
|
| 102 |
+
settings_inputs["set_use_gemini"], settings_inputs["set_gemini_api_key"], settings_inputs["set_gemini_mode"], settings_inputs["set_gemini_model"],
|
| 103 |
+
settings_inputs["set_gemini_prompt_vision"], settings_inputs["set_gemini_prompt_tags"], settings_inputs["set_gemini_prompt_hybrid"],
|
| 104 |
+
settings_inputs["set_gemini_system_instruction"],
|
| 105 |
+
# Dosyalar
|
| 106 |
+
settings_inputs["set_replacement_file"], settings_inputs["set_synonym_file"], settings_inputs["set_addition_file"],
|
| 107 |
+
settings_inputs["set_tekil_enable_categorization"], settings_inputs["set_tekil_selected_categories"],
|
| 108 |
+
settings_inputs["set_sort_order"], settings_inputs["set_device"], settings_inputs["set_context_weight"]
|
| 109 |
+
]
|
| 110 |
+
outputs_for_tekil = [toplu_alert, toplu_filtered_output, tekil_refined_output, tekil_categorized_output, tekil_gemini_output, tekil_combined_cat_gem_output]
|
| 111 |
+
# Button click event'i (Artık gizli ama kod yapısı bozulmasın diye tutuyoruz)
|
| 112 |
+
tekil_process_button_top.click(fn=toplu_islem, inputs=inputs_for_tekil, outputs=outputs_for_tekil)
|
| 113 |
+
|
| 114 |
+
# --- OTOMATİK TETİKLEME (RESİM DEĞİŞİNCE) ---
|
| 115 |
+
toplu_image_input.change(fn=toplu_islem, inputs=inputs_for_tekil, outputs=outputs_for_tekil)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# Web'den resim alma eventi (JS Tetiklemeli)
|
| 119 |
+
timer_btn.click(
|
| 120 |
+
fn=fetch_web_image,
|
| 121 |
+
inputs=[ts_state],
|
| 122 |
+
outputs=[toplu_image_input, toplu_alert, ts_state],
|
| 123 |
+
show_progress="hidden" # Yükleniyor animasyonu gizlendi
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
# MANUEL BUTON
|
| 127 |
+
btn_get_from_web.click(
|
| 128 |
+
fn=fetch_web_image,
|
| 129 |
+
inputs=[ts_state],
|
| 130 |
+
outputs=[toplu_image_input, toplu_alert, ts_state]
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# WILDCARD EVENTLERI (TEKİL)
|
| 134 |
+
btn_female_sfw_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE SFWPACK"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 135 |
+
btn_female_nsfw_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE NSFWPACK"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 136 |
+
btn_female_nude_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "FEMALE NUDEPACK"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 137 |
+
|
| 138 |
+
btn_futa_sfw_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI SFWPACK"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 139 |
+
btn_futa_nsfw_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI NSFWPACK"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 140 |
+
btn_futa_nude_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "FUTANARI NUDEPACK"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 141 |
+
|
| 142 |
+
btn_furry_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "FURRY-ANTHRO"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 143 |
+
btn_hair_tekil.click(fn=lambda x: append_text_to_wildcard_file(x, "HAIR-EYE"), inputs=[tekil_categorized_output], outputs=[toplu_alert])
|
| 144 |
+
|
| 145 |
+
return {
|
| 146 |
+
"btn_send_tekil": btn_send_tekil,
|
| 147 |
+
"toplu_filtered_output": toplu_filtered_output
|
| 148 |
+
}
|
modules/ui/tab_tools.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# modules/ui/tab_tools.py
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from modules.prompt_generator import create_prompt_generator_ui
|
| 7 |
+
from modules.video_creator import create_video_ui
|
| 8 |
+
|
| 9 |
+
# New Component Imports
|
| 10 |
+
from modules.ui.tools.category_ui import create_category_ui
|
| 11 |
+
from modules.ui.tools.rule_ui import create_rule_ui
|
| 12 |
+
from modules.ui.tools.text_ui import create_text_ui
|
| 13 |
+
from modules.ui.tools.image_ui import create_image_ui
|
| 14 |
+
from modules.ui.tools.art_ui import create_art_ui
|
| 15 |
+
from modules.localization.languages import get_str
|
| 16 |
+
|
| 17 |
+
REPLACEMENT_RULES_KLASORU = "data/rules/replacement_rules"
|
| 18 |
+
SYNONYM_RULES_KLASORU = "data/rules/synonym_rules"
|
| 19 |
+
ADDITION_RULES_KLASORU = "data/rules/addition_rules"
|
| 20 |
+
|
| 21 |
+
def create_tools_tab(app_config):
|
| 22 |
+
initial_image_tools_settings = app_config.get("image_tools_settings", {})
|
| 23 |
+
initial_global_settings = app_config.get("global_tagger_settings", {})
|
| 24 |
+
|
| 25 |
+
default_synonym_file = os.path.join(SYNONYM_RULES_KLASORU, "varsayilan_birlesimler.txt")
|
| 26 |
+
default_replacement_file = os.path.join(REPLACEMENT_RULES_KLASORU, "varsayilan_degisiklikler.txt")
|
| 27 |
+
default_addition_file = os.path.join(ADDITION_RULES_KLASORU, "varsayilan_eklemeler.txt")
|
| 28 |
+
|
| 29 |
+
# --- 1. KATEGORİ ARAÇLARI ---
|
| 30 |
+
with gr.TabItem(get_str("tab_category_tools"), id="tab_kategori_araclari"):
|
| 31 |
+
with gr.Tabs() as tabs_kategori_ic:
|
| 32 |
+
# Common input is now created inside create_category_ui
|
| 33 |
+
etiket_input_common = create_category_ui(tabs_kategori_ic)
|
| 34 |
+
|
| 35 |
+
# --- 2. RESİM ARAÇLARI ---
|
| 36 |
+
with gr.TabItem(get_str("tab_image_tools")):
|
| 37 |
+
create_image_ui(initial_image_tools_settings)
|
| 38 |
+
|
| 39 |
+
# --- 3. METİN ARAÇLARI ---
|
| 40 |
+
with gr.TabItem(get_str("tab_text_tools")):
|
| 41 |
+
create_text_ui()
|
| 42 |
+
|
| 43 |
+
# --- 4. PROMPT OLUŞTURUCU ---
|
| 44 |
+
with gr.TabItem(get_str("tab_prompt_gen")):
|
| 45 |
+
create_prompt_generator_ui()
|
| 46 |
+
|
| 47 |
+
# --- 5. VIDEO CREATOR ---
|
| 48 |
+
with gr.TabItem(get_str("tab_video_creator")):
|
| 49 |
+
create_video_ui()
|
| 50 |
+
|
| 51 |
+
# --- 6. SANAT STÜDYOSU ---
|
| 52 |
+
with gr.TabItem(get_str("tab_art_studio")):
|
| 53 |
+
create_art_ui()
|
| 54 |
+
|
| 55 |
+
# --- 7. KURAL YÖNETİMİ ---
|
| 56 |
+
with gr.TabItem(get_str("tab_rule_management")):
|
| 57 |
+
replacement_file_content, synonym_file_content, addition_file_content = create_rule_ui(
|
| 58 |
+
initial_global_settings,
|
| 59 |
+
default_replacement_file,
|
| 60 |
+
default_synonym_file,
|
| 61 |
+
default_addition_file
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
return {
|
| 65 |
+
"etiket_input_common": etiket_input_common,
|
| 66 |
+
"replacement_file_content": replacement_file_content,
|
| 67 |
+
"synonym_file_content": synonym_file_content,
|
| 68 |
+
"addition_file_content": addition_file_content
|
| 69 |
+
}
|
modules/ui/tools/art_ui.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from modules.art_tools import create_grid_collage
|
| 3 |
+
from modules.localization.languages import get_str
|
| 4 |
+
from modules.config_manager import save_art_tools_settings, load_config
|
| 5 |
+
|
| 6 |
+
def create_art_ui():
|
| 7 |
+
# Ayarları Yükle
|
| 8 |
+
config = load_config()
|
| 9 |
+
art_settings = config.get("art_tools_settings", {})
|
| 10 |
+
|
| 11 |
+
with gr.Tabs():
|
| 12 |
+
with gr.TabItem(get_str("art_tab_grid")):
|
| 13 |
+
with gr.Row():
|
| 14 |
+
with gr.Column():
|
| 15 |
+
grid_files = gr.File(label=get_str("art_file_input"), file_count="multiple", file_types=["image"])
|
| 16 |
+
with gr.Accordion(get_str("art_grid_settings"), open=True):
|
| 17 |
+
grid_cols = gr.Slider(minimum=1, maximum=5, value=art_settings.get("grid_cols", 3), step=1, label=get_str("art_cols"))
|
| 18 |
+
grid_bg_color = gr.ColorPicker(value=art_settings.get("grid_bg_color", "#000000"), label="Arka Plan Rengi")
|
| 19 |
+
|
| 20 |
+
with gr.Accordion("Banner & Yazı Ayarları", open=True):
|
| 21 |
+
with gr.Row():
|
| 22 |
+
grid_title_text = gr.Textbox(value=art_settings.get("grid_title_text", "ADOPTABLE SALE!"), label="Banner Başlığı")
|
| 23 |
+
with gr.Row():
|
| 24 |
+
grid_banner_color = gr.ColorPicker(value=art_settings.get("grid_banner_color", "#000000"), label="Banner Arka Planı")
|
| 25 |
+
grid_title_color = gr.ColorPicker(value=art_settings.get("grid_title_color", "#FFD700"), label="Başlık Rengi (Gold)")
|
| 26 |
+
|
| 27 |
+
with gr.Accordion(get_str("art_label_settings"), open=True):
|
| 28 |
+
grid_add_labels = gr.Checkbox(label=get_str("art_add_labels_check"), value=art_settings.get("grid_add_labels", True))
|
| 29 |
+
grid_label_type = gr.Dropdown(["Numara (#1, #2...)", "Sadece Numaralar"], value=art_settings.get("grid_label_type", "Numara (#1, #2...)"), label=get_str("art_label_type"))
|
| 30 |
+
grid_start_num = gr.Number(value=art_settings.get("grid_start_num", 1), label=get_str("art_start_num"), precision=0)
|
| 31 |
+
|
| 32 |
+
with gr.Row():
|
| 33 |
+
grid_btn = gr.Button(get_str("art_create_btn"), variant="primary", scale=2)
|
| 34 |
+
save_art_btn = gr.Button("💾 Ayarları Kaydet", variant="secondary", scale=1)
|
| 35 |
+
|
| 36 |
+
with gr.Column():
|
| 37 |
+
grid_output = gr.Image(type="pil", label=get_str("art_output"))
|
| 38 |
+
grid_status = gr.Textbox(label=get_str("status_label"), interactive=False)
|
| 39 |
+
|
| 40 |
+
grid_btn.click(
|
| 41 |
+
fn=create_grid_collage,
|
| 42 |
+
inputs=[grid_files, grid_cols, grid_add_labels, grid_label_type, grid_start_num, grid_bg_color, grid_title_text, grid_banner_color, grid_title_color],
|
| 43 |
+
outputs=[grid_output, grid_status]
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
save_art_btn.click(
|
| 47 |
+
fn=save_art_tools_settings,
|
| 48 |
+
inputs=[grid_cols, grid_bg_color, grid_title_text, grid_banner_color, grid_title_color, grid_add_labels, grid_label_type, grid_start_num],
|
| 49 |
+
outputs=[grid_status]
|
| 50 |
+
)
|
modules/ui/tools/category_ui.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from modules.managers.category_manager import (
|
| 3 |
+
kategori_listesi,
|
| 4 |
+
filter_and_display_main_categorized_tags,
|
| 5 |
+
kategorileri_grid_html,
|
| 6 |
+
grid_guncelle,
|
| 7 |
+
etiket_ekle,
|
| 8 |
+
etiket_sil
|
| 9 |
+
)
|
| 10 |
+
from modules.localization.languages import get_str
|
| 11 |
+
|
| 12 |
+
def create_category_ui(tabs_kategori_ic):
|
| 13 |
+
|
| 14 |
+
# --- 1. SEKME: CANLI TEST (LIVE TEST) ---
|
| 15 |
+
with gr.TabItem(get_str("cat_redesign_live_tab"), id="tab_cat_live"):
|
| 16 |
+
|
| 17 |
+
# Giriş Alanı (Buraya taşındı, sadece bu sekmede görünsün diye)
|
| 18 |
+
etiket_input_common = gr.Textbox(lines=5, label=get_str("label_tags_input"), interactive=True, placeholder=get_str("placeholder_tags_input"))
|
| 19 |
+
|
| 20 |
+
# Butonlar (Yan Yana)
|
| 21 |
+
with gr.Row():
|
| 22 |
+
main_cat_button = gr.Button(get_str("cat_live_main_btn"), variant="primary", scale=1) # Ana Kategoriye Göre
|
| 23 |
+
|
| 24 |
+
# Transfer Butonu (Sonuçların üstünde)
|
| 25 |
+
btn_transfer_unmatched = gr.Button(get_str("cat_live_unmatched_btn"), variant="stop", visible=False)
|
| 26 |
+
hidden_unmatched_tags = gr.Textbox(visible=False)
|
| 27 |
+
|
| 28 |
+
gr.Markdown("<h3 style='text-align: center; margin-bottom: 20px;'>🔍 Sonuçlar</h3>")
|
| 29 |
+
main_html_output = gr.HTML(label=get_str("cat_live_main_output"), min_height=200)
|
| 30 |
+
|
| 31 |
+
# Event Bağlantıları (Canlı Test)
|
| 32 |
+
def kategorize_ve_buton_kontrol(tags):
|
| 33 |
+
html, unmatched = filter_and_display_main_categorized_tags(tags, None)
|
| 34 |
+
return html, unmatched, gr.update(visible=bool(unmatched)), gr.update(visible=True)
|
| 35 |
+
|
| 36 |
+
main_cat_button.click(
|
| 37 |
+
fn=kategorize_ve_buton_kontrol,
|
| 38 |
+
inputs=[etiket_input_common],
|
| 39 |
+
outputs=[main_html_output, hidden_unmatched_tags, btn_transfer_unmatched, main_html_output]
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def transfer_et_ve_gec(unmatched_text):
|
| 43 |
+
# Ana Yönetim sekmesine geçiş yap ve input kutusunu doldur
|
| 44 |
+
return (
|
| 45 |
+
unmatched_text,
|
| 46 |
+
gr.Tabs(selected="tab_cat_main"),
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
# --- 2. SEKME: ANA KATEGORİ YÖNETİMİ ---
|
| 50 |
+
with gr.TabItem(get_str("cat_redesign_main_tab"), id="tab_cat_main"):
|
| 51 |
+
|
| 52 |
+
# Üst Panel: Yönetim
|
| 53 |
+
with gr.Group():
|
| 54 |
+
with gr.Row(variant="panel"):
|
| 55 |
+
with gr.Column(scale=1):
|
| 56 |
+
kategori_dd = gr.Dropdown(choices=kategori_listesi(), label="Kategori Seç / Select Category", interactive=True)
|
| 57 |
+
with gr.Column(scale=3):
|
| 58 |
+
with gr.Row():
|
| 59 |
+
etik_giris = gr.Textbox(label="Etiket Ekle/Sil (Virgül ile ayırın)", interactive=True, scale=3)
|
| 60 |
+
with gr.Column(scale=1, min_width=150):
|
| 61 |
+
btn_etik_ekle = gr.Button(get_str("btn_add"), variant="primary", scale=1)
|
| 62 |
+
btn_etik_sil = gr.Button(get_str("btn_delete"), variant="stop", scale=1)
|
| 63 |
+
|
| 64 |
+
alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False)
|
| 65 |
+
|
| 66 |
+
# Alt Panel: Grid Görünümü
|
| 67 |
+
gr.Markdown("<h3 style='text-align: center; margin-bottom: 20px;'>📂 Tüm Kategoriler</h3>")
|
| 68 |
+
|
| 69 |
+
grid_out = gr.HTML(value=kategorileri_grid_html())
|
| 70 |
+
|
| 71 |
+
# Event Bağlantıları (Ana Yönetim)
|
| 72 |
+
btn_etik_ekle.click(fn=etiket_ekle, inputs=[kategori_dd, etik_giris], outputs=alert).then(grid_guncelle, outputs=grid_out)
|
| 73 |
+
btn_etik_sil.click(fn=etiket_sil, inputs=[kategori_dd, etik_giris], outputs=alert).then(grid_guncelle, outputs=grid_out)
|
| 74 |
+
|
| 75 |
+
# --- Geç Bağlantılar (Cross-Tab Events) ---
|
| 76 |
+
btn_transfer_unmatched.click(
|
| 77 |
+
fn=transfer_et_ve_gec,
|
| 78 |
+
inputs=[hidden_unmatched_tags],
|
| 79 |
+
outputs=[etik_giris, tabs_kategori_ic] # tabs_kategori_ic ana tab objesi
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
return etiket_input_common
|
modules/ui/tools/image_ui.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import gradio as gr
|
| 3 |
+
import tkinter as tk
|
| 4 |
+
from tkinter import filedialog
|
| 5 |
+
from modules.config_manager import (
|
| 6 |
+
save_image_tools_settings,
|
| 7 |
+
load_renaming_templates,
|
| 8 |
+
add_renaming_template,
|
| 9 |
+
delete_renaming_template
|
| 10 |
+
)
|
| 11 |
+
from modules.localization.languages import get_str
|
| 12 |
+
from modules.tools import (
|
| 13 |
+
apply_custom_renaming,
|
| 14 |
+
process_resolution_change,
|
| 15 |
+
process_text_watermark,
|
| 16 |
+
process_format_change,
|
| 17 |
+
process_brightness_contrast,
|
| 18 |
+
process_denoise_sharpen
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
def create_image_ui(initial_image_tools_settings):
|
| 22 |
+
initial_folder_paths_str = "\n".join(initial_image_tools_settings.get("folder_paths", []))
|
| 23 |
+
|
| 24 |
+
initial_watermark_text = initial_image_tools_settings.get("watermark_text", get_str("img_tools_watermark_default"))
|
| 25 |
+
initial_watermark_opacity = initial_image_tools_settings.get("watermark_opacity", 0.35)
|
| 26 |
+
initial_watermark_font_ratio = initial_image_tools_settings.get("watermark_font_ratio", 0.05)
|
| 27 |
+
initial_watermark_angle = initial_image_tools_settings.get("watermark_angle", -45)
|
| 28 |
+
|
| 29 |
+
initial_brightness_level = initial_image_tools_settings.get("brightness_level", 0)
|
| 30 |
+
initial_contrast_level = initial_image_tools_settings.get("contrast_level", 0)
|
| 31 |
+
initial_denoise_level = initial_image_tools_settings.get("denoise_level", 0)
|
| 32 |
+
initial_sharpen_amount = initial_image_tools_settings.get("sharpen_amount", 0.0)
|
| 33 |
+
|
| 34 |
+
current_templates = load_renaming_templates()
|
| 35 |
+
state_templates = gr.State(current_templates)
|
| 36 |
+
|
| 37 |
+
gr.Markdown(get_str("img_tools_header"))
|
| 38 |
+
|
| 39 |
+
def select_folder_dialog(current_paths):
|
| 40 |
+
try:
|
| 41 |
+
root = tk.Tk()
|
| 42 |
+
root.withdraw()
|
| 43 |
+
root.attributes('-topmost', True)
|
| 44 |
+
folder_path = filedialog.askdirectory(title=get_str("img_tools_select_folder_btn")) # Using button text as title, simpler
|
| 45 |
+
root.destroy()
|
| 46 |
+
if folder_path:
|
| 47 |
+
existing_paths = current_paths.strip() if current_paths else ""
|
| 48 |
+
if existing_paths: return f"{existing_paths}\n{folder_path}"
|
| 49 |
+
else: return folder_path
|
| 50 |
+
return current_paths
|
| 51 |
+
except Exception as e:
|
| 52 |
+
print(f"Klasör seçme hatası: {e}")
|
| 53 |
+
return current_paths
|
| 54 |
+
|
| 55 |
+
with gr.Row():
|
| 56 |
+
with gr.Column(scale=1):
|
| 57 |
+
folder_path_input = gr.Textbox(label=get_str("img_tools_paths_input"), value=initial_folder_paths_str, lines=5)
|
| 58 |
+
with gr.Row():
|
| 59 |
+
select_folder_btn = gr.Button(get_str("img_tools_select_folder_btn"), variant="secondary", size="sm")
|
| 60 |
+
clear_paths_btn = gr.Button(get_str("img_tools_clear_btn"), variant="stop", size="sm")
|
| 61 |
+
save_paths_button = gr.Button(get_str("img_tools_save_settings_btn"), variant="secondary")
|
| 62 |
+
image_tools_alert = gr.Textbox(label=get_str("status_label"), elem_id="renaming-alert-box", interactive=False, lines=5)
|
| 63 |
+
|
| 64 |
+
select_folder_btn.click(fn=select_folder_dialog, inputs=[folder_path_input], outputs=[folder_path_input])
|
| 65 |
+
clear_paths_btn.click(fn=lambda: "", inputs=[], outputs=[folder_path_input])
|
| 66 |
+
|
| 67 |
+
with gr.Column(scale=3):
|
| 68 |
+
with gr.Accordion(get_str("img_tools_res_accordion"), open=False):
|
| 69 |
+
scale_factor_dropdown = gr.Dropdown(label=get_str("img_tools_scale_label"), choices=[1.001, 1.01, 1.1, 1.25, 1.5, 2.0, 4.0], value=1.0, interactive=True)
|
| 70 |
+
process_resolution_button = gr.Button(get_str("btn_apply"), variant="primary")
|
| 71 |
+
|
| 72 |
+
with gr.Accordion(get_str("img_tools_rename_accordion"), open=True):
|
| 73 |
+
gr.Markdown(get_str("img_tools_rename_desc"))
|
| 74 |
+
with gr.Row():
|
| 75 |
+
template_dropdown = gr.Dropdown(label=get_str("img_tools_template_select"), choices=current_templates, value=current_templates[0] if current_templates else "", interactive=True, allow_custom_value=True)
|
| 76 |
+
btn_del_template = gr.Button(get_str("img_tools_delete_template_btn"), variant="stop")
|
| 77 |
+
|
| 78 |
+
with gr.Row():
|
| 79 |
+
new_template_input = gr.Textbox(label=get_str("img_tools_new_template_input"), placeholder=get_str("img_tools_new_template_placeholder"))
|
| 80 |
+
btn_add_template = gr.Button(get_str("img_tools_add_template_btn"), variant="secondary")
|
| 81 |
+
|
| 82 |
+
with gr.Row():
|
| 83 |
+
start_num_input = gr.Number(label=get_str("img_tools_start_num"), value=1, precision=0)
|
| 84 |
+
digit_count_slider = gr.Slider(label=get_str("img_tools_digit_count"), value=3, minimum=1, maximum=10, step=1)
|
| 85 |
+
|
| 86 |
+
apply_renaming_btn = gr.Button(get_str("img_tools_rename_btn"), variant="primary")
|
| 87 |
+
|
| 88 |
+
btn_add_template.click(fn=add_renaming_template, inputs=[new_template_input, state_templates], outputs=[template_dropdown, state_templates])
|
| 89 |
+
btn_del_template.click(fn=delete_renaming_template, inputs=[template_dropdown, state_templates], outputs=[template_dropdown, state_templates])
|
| 90 |
+
apply_renaming_btn.click(fn=apply_custom_renaming, inputs=[folder_path_input, template_dropdown, start_num_input, digit_count_slider], outputs=[image_tools_alert, image_tools_alert])
|
| 91 |
+
|
| 92 |
+
with gr.Accordion(get_str("img_tools_watermark_accordion"), open=False):
|
| 93 |
+
watermark_text_input = gr.Textbox(label=get_str("img_tools_watermark_text"), value=initial_watermark_text)
|
| 94 |
+
watermark_opacity_slider = gr.Slider(minimum=0.05, maximum=1.0, value=initial_watermark_opacity, label=get_str("img_tools_watermark_opacity"))
|
| 95 |
+
watermark_font_ratio = gr.Slider(minimum=0.01, maximum=0.2, value=initial_watermark_font_ratio, label=get_str("img_tools_watermark_size"))
|
| 96 |
+
watermark_angle_slider = gr.Slider(minimum=-90, maximum=90, value=initial_watermark_angle, label=get_str("img_tools_watermark_angle"))
|
| 97 |
+
process_watermark_button = gr.Button(get_str("btn_add"), variant="primary")
|
| 98 |
+
|
| 99 |
+
with gr.Accordion(get_str("img_tools_format_accordion"), open=False):
|
| 100 |
+
format_dropdown = gr.Dropdown(label=get_str("img_tools_format_label"), choices=[".png", ".jpeg", ".webp"], value=".png")
|
| 101 |
+
process_format_button = gr.Button(get_str("btn_convert"), variant="primary")
|
| 102 |
+
|
| 103 |
+
with gr.Accordion(get_str("img_tools_bc_accordion"), open=False):
|
| 104 |
+
brightness_level = gr.Slider(minimum=-100, maximum=100, step=5, value=initial_brightness_level, label=get_str("img_tools_brightness"))
|
| 105 |
+
contrast_level = gr.Slider(minimum=-100, maximum=100, step=5, value=initial_contrast_level, label=get_str("img_tools_contrast"))
|
| 106 |
+
process_brightness_contrast_button = gr.Button(get_str("btn_apply"), variant="primary")
|
| 107 |
+
|
| 108 |
+
with gr.Accordion(get_str("img_tools_ds_accordion"), open=False):
|
| 109 |
+
denoise_level = gr.Slider(minimum=0, maximum=5, step=1, value=initial_denoise_level, label=get_str("img_tools_denoise"))
|
| 110 |
+
sharpen_amount = gr.Slider(minimum=0.0, maximum=3.0, step=0.1, value=initial_sharpen_amount, label=get_str("img_tools_sharpen"))
|
| 111 |
+
process_denoise_sharpen_button = gr.Button(get_str("btn_apply"), variant="primary")
|
| 112 |
+
|
| 113 |
+
process_resolution_button.click(fn=process_resolution_change, inputs=[folder_path_input, scale_factor_dropdown], outputs=[image_tools_alert])
|
| 114 |
+
process_watermark_button.click(fn=process_text_watermark, inputs=[folder_path_input, watermark_text_input, watermark_opacity_slider, watermark_font_ratio, watermark_angle_slider], outputs=[image_tools_alert])
|
| 115 |
+
process_format_button.click(fn=process_format_change, inputs=[folder_path_input, format_dropdown], outputs=[image_tools_alert])
|
| 116 |
+
process_brightness_contrast_button.click(fn=process_brightness_contrast, inputs=[folder_path_input, brightness_level, contrast_level], outputs=[image_tools_alert])
|
| 117 |
+
process_denoise_sharpen_button.click(fn=process_denoise_sharpen, inputs=[folder_path_input, denoise_level, sharpen_amount], outputs=[image_tools_alert])
|
| 118 |
+
|
| 119 |
+
save_paths_button.click(fn=save_image_tools_settings, inputs=[
|
| 120 |
+
folder_path_input, scale_factor_dropdown,
|
| 121 |
+
watermark_text_input, watermark_opacity_slider, watermark_font_ratio, watermark_angle_slider,
|
| 122 |
+
brightness_level, contrast_level, denoise_level, sharpen_amount
|
| 123 |
+
], outputs=[image_tools_alert])
|
modules/ui/tools/rule_ui.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import gradio as gr
|
| 4 |
+
from modules.managers.rule_manager import (
|
| 5 |
+
get_replacement_files,
|
| 6 |
+
get_synonym_files,
|
| 7 |
+
get_addition_files,
|
| 8 |
+
create_replacement_file,
|
| 9 |
+
read_replacement_file_content,
|
| 10 |
+
save_replacement_file_content,
|
| 11 |
+
append_replacement_rule_to_file,
|
| 12 |
+
create_synonym_file,
|
| 13 |
+
read_synonym_file_content,
|
| 14 |
+
save_synonym_file_content,
|
| 15 |
+
append_synonym_rule_to_file,
|
| 16 |
+
create_addition_file,
|
| 17 |
+
read_addition_file_content,
|
| 18 |
+
save_addition_file_content
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
from modules.localization.languages import get_str
|
| 22 |
+
|
| 23 |
+
def create_rule_ui(initial_global_settings, default_replacement_file, default_synonym_file, default_addition_file):
|
| 24 |
+
|
| 25 |
+
gr.Markdown(get_str("rule_tools_intro"))
|
| 26 |
+
|
| 27 |
+
replacement_file_content = gr.Textbox(label=get_str("rule_file_content_manual"), visible=False)
|
| 28 |
+
synonym_file_content = gr.Textbox(label=get_str("rule_file_content_manual"), visible=False)
|
| 29 |
+
addition_file_content = gr.Textbox(label=get_str("rule_file_content_manual"), visible=False)
|
| 30 |
+
|
| 31 |
+
# --- 1. DEĞİŞTİRME KURALLARI ---
|
| 32 |
+
with gr.Accordion(get_str("rule_rep_accordion"), open=True):
|
| 33 |
+
gr.Markdown(get_str("rule_rep_desc"))
|
| 34 |
+
with gr.Row():
|
| 35 |
+
with gr.Column(scale=1):
|
| 36 |
+
# initial value logic is complex, simplify
|
| 37 |
+
rep_val = initial_global_settings.get("replacement_file")
|
| 38 |
+
if rep_val not in get_replacement_files():
|
| 39 |
+
rep_val = default_replacement_file if os.path.exists(default_replacement_file) else None
|
| 40 |
+
|
| 41 |
+
replacement_file_selector = gr.Dropdown(label=get_str("rule_active_file"), choices=get_replacement_files(), value=rep_val, interactive=True)
|
| 42 |
+
replacement_file_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False)
|
| 43 |
+
|
| 44 |
+
with gr.Column(scale=1):
|
| 45 |
+
new_replacement_file_name = gr.Textbox(label=get_str("rule_new_file_name"), placeholder="ozel_degisimler")
|
| 46 |
+
create_replacement_btn = gr.Button(get_str("rule_create_file_btn"))
|
| 47 |
+
|
| 48 |
+
with gr.Tabs():
|
| 49 |
+
with gr.TabItem(get_str("rule_tab_quick_add")):
|
| 50 |
+
with gr.Row():
|
| 51 |
+
txt_rep_old = gr.Textbox(label=get_str("rule_rep_old"), placeholder="örn: blue_hair")
|
| 52 |
+
txt_rep_new = gr.Textbox(label=get_str("rule_rep_new"), placeholder="örn: cyan hair")
|
| 53 |
+
btn_add_rep = gr.Button(get_str("rule_add_to_file_btn"), variant="primary")
|
| 54 |
+
|
| 55 |
+
with gr.TabItem(get_str("rule_tab_edit_file")):
|
| 56 |
+
replacement_file_content = gr.Textbox(label=get_str("rule_file_content_manual"), interactive=True, lines=10)
|
| 57 |
+
save_replacement_rules_btn = gr.Button(get_str("rule_save_all_btn"), variant="secondary")
|
| 58 |
+
|
| 59 |
+
# --- 2. BİRLEŞTİRME KURALLARI ---
|
| 60 |
+
with gr.Accordion(get_str("rule_syn_accordion"), open=False):
|
| 61 |
+
gr.Markdown(get_str("rule_syn_intro"))
|
| 62 |
+
with gr.Row():
|
| 63 |
+
with gr.Column(scale=1):
|
| 64 |
+
syn_val = initial_global_settings.get("synonym_file")
|
| 65 |
+
if syn_val not in get_synonym_files():
|
| 66 |
+
syn_val = default_synonym_file if os.path.exists(default_synonym_file) else None
|
| 67 |
+
|
| 68 |
+
synonym_file_selector = gr.Dropdown(label=get_str("rule_active_file"), choices=get_synonym_files(), value=syn_val, interactive=True)
|
| 69 |
+
synonym_file_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False)
|
| 70 |
+
with gr.Column(scale=1):
|
| 71 |
+
new_synonym_file_name = gr.Textbox(label=get_str("rule_new_file_name"), placeholder="ozel_birlesimler")
|
| 72 |
+
create_synonym_btn = gr.Button(get_str("rule_create_file_btn"))
|
| 73 |
+
|
| 74 |
+
with gr.Tabs():
|
| 75 |
+
with gr.TabItem(get_str("rule_tab_quick_add")):
|
| 76 |
+
with gr.Row():
|
| 77 |
+
txt_syn_main = gr.Textbox(label=get_str("rule_syn_main"), placeholder="örn: underwear")
|
| 78 |
+
txt_syn_remove = gr.Textbox(label=get_str("rule_syn_remove"), placeholder="örn: panties, briefs")
|
| 79 |
+
btn_add_syn = gr.Button(get_str("rule_add_to_file_btn"), variant="primary")
|
| 80 |
+
|
| 81 |
+
with gr.TabItem(get_str("rule_tab_edit_file")):
|
| 82 |
+
synonym_file_content = gr.Textbox(label=get_str("rule_file_content_manual"), interactive=True, lines=10)
|
| 83 |
+
save_synonym_rules_btn = gr.Button(get_str("rule_save_all_btn"), variant="secondary")
|
| 84 |
+
|
| 85 |
+
# --- 3. EKLEME KURALLARI ---
|
| 86 |
+
with gr.Accordion(get_str("rule_add_accordion"), open=False):
|
| 87 |
+
gr.Markdown(get_str("rule_add_intro"))
|
| 88 |
+
with gr.Row():
|
| 89 |
+
with gr.Column(scale=1):
|
| 90 |
+
add_val = initial_global_settings.get("addition_file")
|
| 91 |
+
if add_val not in get_addition_files():
|
| 92 |
+
add_val = default_addition_file if os.path.exists(default_addition_file) else None
|
| 93 |
+
|
| 94 |
+
addition_file_selector = gr.Dropdown(label=get_str("rule_active_file"), choices=get_addition_files(), value=add_val, interactive=True)
|
| 95 |
+
addition_file_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False)
|
| 96 |
+
with gr.Column(scale=1):
|
| 97 |
+
new_addition_file_name = gr.Textbox(label=get_str("rule_new_file_name"), placeholder="imza_etiketlerim")
|
| 98 |
+
create_addition_btn = gr.Button(get_str("rule_create_file_btn"))
|
| 99 |
+
|
| 100 |
+
addition_file_content = gr.Textbox(label=get_str("rule_add_content_input"), interactive=True, lines=10, placeholder="best quality, masterpiece\nsigned by artist")
|
| 101 |
+
save_addition_rules_btn = gr.Button(get_str("rule_add_save_btn"), variant="primary")
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
# --- EVENT HANDLERS ---
|
| 105 |
+
def update_dropdowns(val1, val2, val3):
|
| 106 |
+
files_rep = get_replacement_files()
|
| 107 |
+
files_syn = get_synonym_files()
|
| 108 |
+
files_add = get_addition_files()
|
| 109 |
+
return (
|
| 110 |
+
gr.update(choices=files_rep, value=val1 if val1 in files_rep else None),
|
| 111 |
+
gr.update(choices=files_syn, value=val2 if val2 in files_syn else None),
|
| 112 |
+
gr.update(choices=files_add, value=val3 if val3 in files_add else None)
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
# Replacement Events
|
| 116 |
+
create_replacement_btn.click(fn=create_replacement_file, inputs=[new_replacement_file_name], outputs=[replacement_file_alert]).then(fn=lambda: update_dropdowns(None, None, None)[0], outputs=[replacement_file_selector])
|
| 117 |
+
replacement_file_selector.change(fn=read_replacement_file_content, inputs=[replacement_file_selector], outputs=[replacement_file_content])
|
| 118 |
+
save_replacement_rules_btn.click(fn=save_replacement_file_content, inputs=[replacement_file_selector, replacement_file_content], outputs=[replacement_file_alert])
|
| 119 |
+
btn_add_rep.click(fn=append_replacement_rule_to_file, inputs=[replacement_file_selector, txt_rep_old, txt_rep_new], outputs=[replacement_file_alert]).then(fn=read_replacement_file_content, inputs=[replacement_file_selector], outputs=[replacement_file_content])
|
| 120 |
+
|
| 121 |
+
# Synonym Events
|
| 122 |
+
create_synonym_btn.click(fn=create_synonym_file, inputs=[new_synonym_file_name], outputs=[synonym_file_alert]).then(fn=lambda: update_dropdowns(None, None, None)[1], outputs=[synonym_file_selector])
|
| 123 |
+
synonym_file_selector.change(fn=read_synonym_file_content, inputs=[synonym_file_selector], outputs=[synonym_file_content])
|
| 124 |
+
save_synonym_rules_btn.click(fn=save_synonym_file_content, inputs=[synonym_file_selector, synonym_file_content], outputs=[synonym_file_alert])
|
| 125 |
+
btn_add_syn.click(fn=append_synonym_rule_to_file, inputs=[synonym_file_selector, txt_syn_main, txt_syn_remove], outputs=[synonym_file_alert]).then(fn=read_synonym_file_content, inputs=[synonym_file_selector], outputs=[synonym_file_content])
|
| 126 |
+
|
| 127 |
+
# Addition Events
|
| 128 |
+
create_addition_btn.click(fn=create_addition_file, inputs=[new_addition_file_name], outputs=[addition_file_alert]).then(fn=lambda: update_dropdowns(None, None, None)[2], outputs=[addition_file_selector])
|
| 129 |
+
addition_file_selector.change(fn=read_addition_file_content, inputs=[addition_file_selector], outputs=[addition_file_content])
|
| 130 |
+
save_addition_rules_btn.click(fn=save_addition_file_content, inputs=[addition_file_selector, addition_file_content], outputs=[addition_file_alert])
|
| 131 |
+
|
| 132 |
+
return replacement_file_content, synonym_file_content, addition_file_content
|
modules/ui/tools/text_ui.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from modules.localization.languages import get_str
|
| 4 |
+
from modules.tools import (
|
| 5 |
+
clean_and_uniquify_text_simple,
|
| 6 |
+
convert_lines_to_comma,
|
| 7 |
+
convert_comma_to_lines,
|
| 8 |
+
select_and_copy_random_lines
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
def create_text_ui():
|
| 12 |
+
with gr.Row():
|
| 13 |
+
with gr.Column():
|
| 14 |
+
gr.Markdown(get_str("txt_tools_header"))
|
| 15 |
+
text_input_raw = gr.Textbox(label=get_str("txt_tools_input"), lines=5, placeholder="örnek: \nred_hair\nlong__hair\nblue_eyes")
|
| 16 |
+
text_cleaner_alert = gr.Textbox(label=get_str("status_label"), elem_id="alert-box", visible=False)
|
| 17 |
+
|
| 18 |
+
with gr.Tabs():
|
| 19 |
+
with gr.Tab(get_str("txt_tools_tab_clean")):
|
| 20 |
+
text_cleaner_process_button = gr.Button(get_str("txt_tools_clean_btn"), variant="primary")
|
| 21 |
+
text_cleaner_output = gr.Textbox(label=get_str("label_result"), lines=5, interactive=False, show_copy_button=True)
|
| 22 |
+
with gr.Tab(get_str("txt_tools_tab_format")):
|
| 23 |
+
format_output = gr.Textbox(label=get_str("label_result"), lines=5, interactive=False, show_copy_button=True)
|
| 24 |
+
with gr.Row():
|
| 25 |
+
lines_to_comma_btn = gr.Button(get_str("txt_tools_lines_to_comma"), variant="primary")
|
| 26 |
+
comma_to_lines_btn = gr.Button(get_str("txt_tools_comma_to_lines"), variant="primary")
|
| 27 |
+
with gr.Tab(get_str("txt_tools_tab_random")):
|
| 28 |
+
num_lines_to_copy_slider = gr.Slider(minimum=1, maximum=300, value=1, step=1, label=get_str("txt_tools_line_count"))
|
| 29 |
+
select_random_lines_button = gr.Button(get_str("btn_select"), variant="primary")
|
| 30 |
+
random_lines_output = gr.Textbox(label=get_str("label_result"), lines=5, interactive=False, show_copy_button=True)
|
| 31 |
+
text_cleaner_process_button.click(fn=lambda text: (clean_and_uniquify_text_simple(text), gr.update(value="✅ Metin temizlendi.", visible=True)), inputs=[text_input_raw], outputs=[text_cleaner_output, text_cleaner_alert])
|
| 32 |
+
lines_to_comma_btn.click(fn=convert_lines_to_comma, inputs=[text_input_raw], outputs=[format_output, text_cleaner_alert])
|
| 33 |
+
comma_to_lines_btn.click(fn=convert_comma_to_lines, inputs=[text_input_raw], outputs=[format_output, text_cleaner_alert])
|
| 34 |
+
select_random_lines_button.click(fn=select_and_copy_random_lines, inputs=[text_input_raw, num_lines_to_copy_slider], outputs=[random_lines_output, text_cleaner_alert])
|
modules/utils/file_utils.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
CONFIGLIST_KLASORU = "data/rules/configlist"
|
| 6 |
+
|
| 7 |
+
def get_configlist_files():
|
| 8 |
+
"""Configlist klasöründeki tüm txt dosyalarını döndürür."""
|
| 9 |
+
if os.path.exists(CONFIGLIST_KLASORU):
|
| 10 |
+
return [os.path.join(CONFIGLIST_KLASORU, f) for f in os.listdir(CONFIGLIST_KLASORU) if f.endswith(".txt")]
|
| 11 |
+
return []
|
| 12 |
+
|
| 13 |
+
def _get_all_image_paths(folder_paths: List[str]) -> List[str]:
|
| 14 |
+
"""Tüm klasörlerdeki resim dosyalarının yollarını toplar."""
|
| 15 |
+
image_extensions = (('.png', '.jpg', '.jpeg', '.webp'))
|
| 16 |
+
all_paths = []
|
| 17 |
+
for folder_path in folder_paths:
|
| 18 |
+
if os.path.isdir(folder_path):
|
| 19 |
+
for filename in os.listdir(folder_path):
|
| 20 |
+
if filename.lower().endswith(image_extensions):
|
| 21 |
+
all_paths.append(os.path.join(folder_path, filename))
|
| 22 |
+
return all_paths
|
modules/utils/tag_utils.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import re
|
| 3 |
+
import random
|
| 4 |
+
|
| 5 |
+
def clean_tags_underscore(tags_string):
|
| 6 |
+
if not tags_string: return ""
|
| 7 |
+
cleaned = re.sub(r'\_+', ' ', tags_string).strip()
|
| 8 |
+
return re.sub(r'\s+', ' ', cleaned)
|
| 9 |
+
|
| 10 |
+
def unique_and_sort_tags(tags_string, sort_order="alpha", original_order_ref=None):
|
| 11 |
+
if not tags_string:
|
| 12 |
+
return ""
|
| 13 |
+
|
| 14 |
+
tags = [t.strip() for t in tags_string.split(',') if t.strip()]
|
| 15 |
+
unique_tags = []
|
| 16 |
+
[unique_tags.append(t) for t in tags if t not in unique_tags]
|
| 17 |
+
|
| 18 |
+
if sort_order == "alpha" or sort_order == "Alfabetik":
|
| 19 |
+
return ", ".join(sorted(unique_tags))
|
| 20 |
+
elif sort_order == "length" or sort_order == "Uzunluğa Göre":
|
| 21 |
+
return ", ".join(sorted(unique_tags, key=len))
|
| 22 |
+
elif sort_order == "random" or sort_order == "Rastgele":
|
| 23 |
+
# random is already imported at the top, no need to re-import
|
| 24 |
+
random.shuffle(unique_tags)
|
| 25 |
+
return ", ".join(unique_tags)
|
| 26 |
+
elif sort_order == "original" or sort_order == "Orijinal":
|
| 27 |
+
return ", ".join(unique_tags) # Zaten unique yaparken sıra korundu
|
| 28 |
+
|
| 29 |
+
# Default case if sort_order is not recognized, or if original_order_ref was intended for a different sort
|
| 30 |
+
return ", ".join(unique_tags)
|
modules/video_creator.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modules/video_creator.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import time
|
| 5 |
+
import numpy as np
|
| 6 |
+
import gradio as gr
|
| 7 |
+
from PIL import Image, ImageFilter, ImageDraw, ImageFont
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
import io
|
| 10 |
+
|
| 11 |
+
# MoviePy Kütüphanesi (v2.0+ Uyumlu)
|
| 12 |
+
from moviepy import (
|
| 13 |
+
ImageClip, AudioFileClip, concatenate_audioclips,
|
| 14 |
+
concatenate_videoclips, CompositeVideoClip, VideoClip, ColorClip, TextClip
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# Çıktı klasörü
|
| 18 |
+
OUTPUT_DIR = "outputs"
|
| 19 |
+
|
| 20 |
+
# Çözünürlük Ayarları
|
| 21 |
+
RESOLUTIONS = {
|
| 22 |
+
"YouTube (16:9)": (1920, 1080),
|
| 23 |
+
"Shorts/Reels (9:16)": (1080, 1920),
|
| 24 |
+
"Kare (1:1)": (1080, 1080)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
def create_blurred_background(pil_img, target_size):
|
| 28 |
+
"""Resmi bulanıklaştırıp arka plana yayar, orijinali ortada tutar."""
|
| 29 |
+
target_w, target_h = target_size
|
| 30 |
+
img_w, img_h = pil_img.size
|
| 31 |
+
|
| 32 |
+
# 1. Arka planı oluştur (Resmi kaplayacak şekilde büyüt ve bulanıklaştır)
|
| 33 |
+
bg_ratio = max(target_w / img_w, target_h / img_h)
|
| 34 |
+
bg_new_size = (int(img_w * bg_ratio), int(img_h * bg_ratio))
|
| 35 |
+
background = pil_img.resize(bg_new_size, Image.Resampling.LANCZOS)
|
| 36 |
+
|
| 37 |
+
# Ortalamak için kırp
|
| 38 |
+
left = (bg_new_size[0] - target_w) / 2
|
| 39 |
+
top = (bg_new_size[1] - target_h) / 2
|
| 40 |
+
background = background.crop((left, top, left + target_w, top + target_h))
|
| 41 |
+
|
| 42 |
+
# Bulanıklaştır
|
| 43 |
+
background = background.filter(ImageFilter.GaussianBlur(radius=20))
|
| 44 |
+
|
| 45 |
+
# 2. Ön planı oluştur (Sığacak şekilde ayarla)
|
| 46 |
+
fg_ratio = min(target_w / img_w, target_h / img_h)
|
| 47 |
+
fg_new_size = (int(img_w * fg_ratio), int(img_h * fg_ratio))
|
| 48 |
+
foreground = pil_img.resize(fg_new_size, Image.Resampling.LANCZOS)
|
| 49 |
+
|
| 50 |
+
# 3. Birleştir
|
| 51 |
+
final_img = background.copy()
|
| 52 |
+
fg_x = (target_w - fg_new_size[0]) // 2
|
| 53 |
+
fg_y = (target_h - fg_new_size[1]) // 2
|
| 54 |
+
final_img.paste(foreground, (fg_x, fg_y))
|
| 55 |
+
|
| 56 |
+
return final_img
|
| 57 |
+
|
| 58 |
+
def create_text_overlay(txt, target_size, duration):
|
| 59 |
+
"""Basit metin görseli oluşturur (PIL kullanarak)"""
|
| 60 |
+
w, h = target_size
|
| 61 |
+
# Şeffaf katman
|
| 62 |
+
img = Image.new('RGBA', target_size, (255, 255, 255, 0))
|
| 63 |
+
draw = ImageDraw.Draw(img)
|
| 64 |
+
|
| 65 |
+
# Font boyutu ve ayarı
|
| 66 |
+
font_size = int(h * 0.05) # Yüksekliğin %5'i
|
| 67 |
+
try:
|
| 68 |
+
font = ImageFont.truetype("arial.ttf", font_size)
|
| 69 |
+
except:
|
| 70 |
+
font = ImageFont.load_default()
|
| 71 |
+
|
| 72 |
+
# Metin boyutunu hesapla
|
| 73 |
+
bbox = draw.textbbox((0, 0), txt, font=font)
|
| 74 |
+
text_w = bbox[2] - bbox[0]
|
| 75 |
+
text_h = bbox[3] - bbox[1]
|
| 76 |
+
|
| 77 |
+
# Alt ortaya yerleştir (biraz yukarıda)
|
| 78 |
+
x = (w - text_w) / 2
|
| 79 |
+
y = h - (text_h * 2.5)
|
| 80 |
+
|
| 81 |
+
# Arka plan kutusu (Siyah, yarı saydam)
|
| 82 |
+
padding = 10
|
| 83 |
+
draw.rectangle(
|
| 84 |
+
[x - padding, y - padding, x + text_w + padding, y + text_h + padding],
|
| 85 |
+
fill=(0, 0, 0, 160)
|
| 86 |
+
)
|
| 87 |
+
# Metni yaz
|
| 88 |
+
draw.text((x, y), txt, font=font, fill="white")
|
| 89 |
+
|
| 90 |
+
return ImageClip(np.array(img)).with_duration(duration)
|
| 91 |
+
|
| 92 |
+
def make_audio_spectrum_clip(audio_clip, target_size, duration):
|
| 93 |
+
"""Ses dalgası animasyonu oluşturur (Matplotlib ile)"""
|
| 94 |
+
w, h = target_size
|
| 95 |
+
fps = 10
|
| 96 |
+
|
| 97 |
+
audio_array = audio_clip.to_soundarray(fps=fps)
|
| 98 |
+
if audio_array.ndim > 1:
|
| 99 |
+
audio_array = audio_array.mean(axis=1)
|
| 100 |
+
|
| 101 |
+
audio_array = audio_array / np.max(np.abs(audio_array) + 0.0001)
|
| 102 |
+
|
| 103 |
+
def make_frame(t):
|
| 104 |
+
index = int(t * fps)
|
| 105 |
+
if index >= len(audio_array):
|
| 106 |
+
index = len(audio_array) - 1
|
| 107 |
+
|
| 108 |
+
val = abs(audio_array[index])
|
| 109 |
+
|
| 110 |
+
fig, ax = plt.subplots(figsize=(w/100, h/400), dpi=72)
|
| 111 |
+
fig.patch.set_alpha(0)
|
| 112 |
+
ax.axis('off')
|
| 113 |
+
|
| 114 |
+
bar_height = val
|
| 115 |
+
ax.barh([0], [bar_height], color='cyan', height=0.5, alpha=0.8)
|
| 116 |
+
ax.set_xlim(0, 1)
|
| 117 |
+
ax.set_ylim(-0.5, 0.5)
|
| 118 |
+
|
| 119 |
+
buf = io.BytesIO()
|
| 120 |
+
fig.savefig(buf, format='png', bbox_inches='tight', pad_inches=0, transparent=True)
|
| 121 |
+
buf.seek(0)
|
| 122 |
+
plt.close(fig)
|
| 123 |
+
|
| 124 |
+
img = Image.open(buf).convert("RGBA")
|
| 125 |
+
img = img.resize((w, int(h*0.2)))
|
| 126 |
+
|
| 127 |
+
full_frame = Image.new('RGBA', (w, h), (0,0,0,0))
|
| 128 |
+
full_frame.paste(img, (0, int(h * 0.8)))
|
| 129 |
+
|
| 130 |
+
return np.array(full_frame)
|
| 131 |
+
|
| 132 |
+
clip = VideoClip(make_frame, duration=duration)
|
| 133 |
+
mask = VideoClip(make_frame, duration=duration).to_mask()
|
| 134 |
+
return clip.with_mask(mask)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def create_video_logic(
|
| 138 |
+
image_files, audio_files,
|
| 139 |
+
resolution_key,
|
| 140 |
+
custom_text,
|
| 141 |
+
enable_spectrum,
|
| 142 |
+
video_preset, # YENİ PARAMETRE
|
| 143 |
+
progress=gr.Progress()
|
| 144 |
+
):
|
| 145 |
+
if not image_files: return None, "⚠️ Lütfen en az bir resim seçin."
|
| 146 |
+
if not audio_files: return None, "⚠️ Lütfen en az bir ses dosyası seçin."
|
| 147 |
+
|
| 148 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 149 |
+
output_filename = f"pro_video_{int(time.time())}.mp4"
|
| 150 |
+
output_path = os.path.join(OUTPUT_DIR, output_filename)
|
| 151 |
+
|
| 152 |
+
target_size = RESOLUTIONS.get(resolution_key, (1920, 1080))
|
| 153 |
+
|
| 154 |
+
try:
|
| 155 |
+
progress(0.1, desc="Sesler işleniyor...")
|
| 156 |
+
audio_clips = [AudioFileClip(f.name if hasattr(f, 'name') else f) for f in audio_files]
|
| 157 |
+
final_audio = concatenate_audioclips(audio_clips)
|
| 158 |
+
total_duration = final_audio.duration
|
| 159 |
+
|
| 160 |
+
progress(0.2, desc="Görseller hazırlanıyor (Slayt Modu)...")
|
| 161 |
+
|
| 162 |
+
img_duration = total_duration / len(image_files)
|
| 163 |
+
video_clips = []
|
| 164 |
+
|
| 165 |
+
for img_file in image_files:
|
| 166 |
+
img_path = img_file.name if hasattr(img_file, 'name') else img_file
|
| 167 |
+
|
| 168 |
+
with Image.open(img_path) as pil_img:
|
| 169 |
+
processed_img = create_blurred_background(pil_img, target_size)
|
| 170 |
+
|
| 171 |
+
clip = ImageClip(np.array(processed_img)).with_duration(img_duration)
|
| 172 |
+
video_clips.append(clip)
|
| 173 |
+
|
| 174 |
+
base_video = concatenate_videoclips(video_clips, method="compose")
|
| 175 |
+
base_video = base_video.with_audio(final_audio)
|
| 176 |
+
|
| 177 |
+
final_layers = [base_video]
|
| 178 |
+
|
| 179 |
+
if custom_text and custom_text.strip():
|
| 180 |
+
progress(0.4, desc="Metin katmanı oluşturuluyor...")
|
| 181 |
+
txt_clip = create_text_overlay(custom_text, target_size, total_duration)
|
| 182 |
+
final_layers.append(txt_clip)
|
| 183 |
+
|
| 184 |
+
if enable_spectrum:
|
| 185 |
+
progress(0.5, desc="Ses dalgası grafiği oluşturuluyor (Bu işlem ÇOK yavaş olabilir)...")
|
| 186 |
+
try:
|
| 187 |
+
spectrum_clip = make_audio_spectrum_clip(final_audio, target_size, total_duration)
|
| 188 |
+
final_layers.append(spectrum_clip)
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"Spectrum hatası: {e}")
|
| 191 |
+
|
| 192 |
+
final_video = CompositeVideoClip(final_layers, size=target_size)
|
| 193 |
+
|
| 194 |
+
progress(0.7, desc=f"Video render ediliyor ({video_preset})... Bu işlem uzun sürebilir, lütfen bekleyin.")
|
| 195 |
+
|
| 196 |
+
# Render işlemi - logger='bar' konsola ilerleme yazar
|
| 197 |
+
final_video.write_videofile(
|
| 198 |
+
output_path,
|
| 199 |
+
fps=24,
|
| 200 |
+
codec='libx264',
|
| 201 |
+
audio_codec='aac',
|
| 202 |
+
preset=video_preset,
|
| 203 |
+
threads=os.cpu_count() or 4, # CPU sayısına göre otomatik
|
| 204 |
+
logger='bar' # Konsola ilerleme yazsın
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
# Bellek temizliği
|
| 208 |
+
final_video.close()
|
| 209 |
+
for clip in video_clips:
|
| 210 |
+
clip.close()
|
| 211 |
+
for ac in audio_clips:
|
| 212 |
+
ac.close()
|
| 213 |
+
final_audio.close()
|
| 214 |
+
|
| 215 |
+
progress(1.0, desc="✅ İşlem Tamamlandı!")
|
| 216 |
+
|
| 217 |
+
return output_path, f"✅ Pro Video Hazır! ({resolution_key})"
|
| 218 |
+
|
| 219 |
+
except Exception as e:
|
| 220 |
+
import traceback
|
| 221 |
+
traceback.print_exc()
|
| 222 |
+
return None, f"⛔ Hata: {str(e)}"
|
| 223 |
+
|
| 224 |
+
# --- UI ---
|
| 225 |
+
|
| 226 |
+
import gradio as gr
|
| 227 |
+
from modules.localization.languages import get_str
|
| 228 |
+
|
| 229 |
+
def create_video_ui():
|
| 230 |
+
gr.Markdown(get_str("vid_header"))
|
| 231 |
+
gr.Markdown(get_str("vid_desc"))
|
| 232 |
+
|
| 233 |
+
with gr.Row():
|
| 234 |
+
with gr.Column():
|
| 235 |
+
image_input = gr.File(label=get_str("vid_img_input"), file_count="multiple", file_types=["image"])
|
| 236 |
+
audio_input = gr.File(label=get_str("vid_audio_input"), file_count="multiple", file_types=["audio"])
|
| 237 |
+
|
| 238 |
+
with gr.Row():
|
| 239 |
+
resolution_dropdown = gr.Dropdown(
|
| 240 |
+
choices=["1920x1080", "1280x720", "1080x1920", "720x1280", "1080x1080"],
|
| 241 |
+
value="1920x1080",
|
| 242 |
+
label=get_str("vid_resolution")
|
| 243 |
+
)
|
| 244 |
+
preset_radio = gr.Radio(
|
| 245 |
+
choices=["ultrafast", "superfast", "veryfast", "faster", "fast", "medium"],
|
| 246 |
+
value="medium",
|
| 247 |
+
label=get_str("vid_speed")
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
text_overlay = gr.Textbox(label=get_str("vid_text_overlay"), placeholder="Örn: My Artworks 2024")
|
| 251 |
+
spectrum_check = gr.Checkbox(label=get_str("vid_spectrum_check"), value=False)
|
| 252 |
+
|
| 253 |
+
create_btn = gr.Button(get_str("vid_create_btn"), variant="primary")
|
| 254 |
+
|
| 255 |
+
with gr.Column():
|
| 256 |
+
video_output = gr.Video(label=get_str("vid_preview"))
|
| 257 |
+
status_output = gr.Textbox(label=get_str("vid_status"), interactive=False)
|
| 258 |
+
|
| 259 |
+
create_btn.click(
|
| 260 |
+
fn=create_video_logic,
|
| 261 |
+
inputs=[image_input, audio_input, resolution_dropdown, text_overlay, spectrum_check, preset_radio],
|
| 262 |
+
outputs=[video_output, status_output]
|
| 263 |
+
)
|