-32
@@ -52,38 +52,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
settings_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web/settings/sn0w_settings.json'))
|
||||
print(settings_path)
|
||||
try:
|
||||
with open(settings_path, 'r') as file:
|
||||
sn0w_settings = json.load(file)
|
||||
print(sn0w_settings.get("enable_shitty_nodes"))
|
||||
except FileNotFoundError:
|
||||
sn0w_settings = {}
|
||||
|
||||
if sn0w_settings.get("enable_shitty_nodes") == True:
|
||||
from .src.find_resolution import FindResolutionNode
|
||||
from .src.perlin_noise import FilmGrain
|
||||
from .src.show_sigmas import ShowSigmasNode
|
||||
from .src.get_gelbooru_tags import GetGelbooruTagsNode
|
||||
|
||||
NODE_CLASS_MAPPINGS_ADD = {
|
||||
"Find SDXL Resolution": FindResolutionNode,
|
||||
"Colored Film Grain": FilmGrain,
|
||||
"Show Sigmas": ShowSigmasNode,
|
||||
"Get Gelbooru Tags": GetGelbooruTagsNode,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS_ADD = {
|
||||
"Find SDXL Resolution": "Find SDXL Resolution",
|
||||
"Colored Film Grain": "Colored Film Grain",
|
||||
"Show Sigmas": "Show Sigmas",
|
||||
"Get Gelbooru Tags": "Get Gelbooru Tags",
|
||||
}
|
||||
|
||||
NODE_CLASS_MAPPINGS.update(NODE_CLASS_MAPPINGS_ADD)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(NODE_DISPLAY_NAME_MAPPINGS_ADD)
|
||||
|
||||
current_unique_id = 0 # Global variable to track the unique ID
|
||||
logger = Logger()
|
||||
|
||||
|
||||
+20
-3
@@ -3,14 +3,26 @@ import json
|
||||
import random
|
||||
from ..sn0w import ConfigReader, Logger, AnyType, Utility
|
||||
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
|
||||
any = AnyType("*")
|
||||
|
||||
routes = PromptServer.instance.routes
|
||||
API_PREFIX = '/sn0w'
|
||||
|
||||
@routes.post(f'{API_PREFIX}/update_characters')
|
||||
async def handle_update_characters(request):
|
||||
CharacterSelectNode.initialize()
|
||||
return web.json_response({"status": "ok"})
|
||||
|
||||
class CharacterSelectNode:
|
||||
# Initialize class variables
|
||||
character_dict = {}
|
||||
final_character_dict = {}
|
||||
final_characters = []
|
||||
cached_sorting_setting = ConfigReader.get_setting("sn0w.SortBySeries", False)
|
||||
cached_default_character_setting = ConfigReader.get_setting("sn0w.DisableDefaultCharacters", False)
|
||||
last_character = ""
|
||||
|
||||
logger = Logger()
|
||||
@@ -25,9 +37,11 @@ class CharacterSelectNode:
|
||||
|
||||
@classmethod
|
||||
def load_characters(cls, dir_path):
|
||||
json_path = os.path.join(dir_path, 'web/settings/characters.json')
|
||||
with open(json_path, 'r') as file:
|
||||
character_data = json.load(file)
|
||||
character_data = []
|
||||
if (not cls.cached_default_character_setting):
|
||||
json_path = os.path.join(dir_path, 'web/settings/characters.json')
|
||||
with open(json_path, 'r') as file:
|
||||
character_data = json.load(file)
|
||||
|
||||
custom_json_path = os.path.join(dir_path, 'web/settings/custom_characters.json')
|
||||
if os.path.exists(custom_json_path):
|
||||
@@ -67,6 +81,9 @@ class CharacterSelectNode:
|
||||
def INPUT_TYPES(cls):
|
||||
if not cls.final_characters: # Check if initialization is needed
|
||||
cls.initialize()
|
||||
elif cls.cached_default_character_setting != ConfigReader.get_setting("sn0w.DisableDefaultCharacters", False):
|
||||
cls.cached_default_character_setting = not cls.cached_default_character_setting
|
||||
cls.initialize()
|
||||
else:
|
||||
cls.sort_characters()
|
||||
character_names = ['None'] + list(cls.final_characters)
|
||||
|
||||
@@ -62,6 +62,14 @@ class CustomSchedulers:
|
||||
return (sigmas, )
|
||||
raise ValueError(f"No scheduler found with the name {name}")
|
||||
|
||||
def generate_js_object(self, name, settings):
|
||||
"""Generate JavaScript object string from settings."""
|
||||
js_obj = f" \"{name}\": {{\n"
|
||||
for setting, details in settings.items():
|
||||
js_obj += f" \"{setting}\": {json.dumps(details)},\n"
|
||||
js_obj += " },\n"
|
||||
return js_obj
|
||||
|
||||
def export_scheduler_settings_to_js(self):
|
||||
# Go settings directory
|
||||
base_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
@@ -70,34 +78,34 @@ class CustomSchedulers:
|
||||
filepath = os.path.join(target_dir, 'scheduler_settings.js')
|
||||
|
||||
# Initialize the JavaScript content with default settings
|
||||
js_content = "const widgets = {\n"
|
||||
js_content += """ "polyexponential": {
|
||||
"sigma_max_poly": ["FLOAT", 14.614642, 0.0, 5000.0, 0.01, false],
|
||||
"sigma_min_poly": ["FLOAT", 0.0291675, 0.0, 5000.0, 0.01, false],
|
||||
"rho": ["FLOAT", 1.0, 0.0, 100.0, 0.01, false]
|
||||
},
|
||||
"vp": {
|
||||
"beta_d": ["FLOAT", 14.0, 0.0, 5000.0, 0.01, false],
|
||||
"beta_min": ["FLOAT", 0.05, 0.0, 5000.0, 0.01, false],
|
||||
"eps_s": ["FLOAT", 0.075, 0.0, 1.0, 0.0001, false]
|
||||
},"""
|
||||
default_settings = {
|
||||
"polyexponential": {
|
||||
"sigma_max_poly": ["FLOAT", 14.614642, 0.0, 5000.0, 0.01, False],
|
||||
"sigma_min_poly": ["FLOAT", 0.0291675, 0.0, 5000.0, 0.01, False],
|
||||
"rho": ["FLOAT", 1.0, 0.0, 100.0, 0.01, False]
|
||||
},
|
||||
"vp": {
|
||||
"beta_d": ["FLOAT", 14.0, 0.0, 5000.0, 0.01, False],
|
||||
"beta_min": ["FLOAT", 0.05, 0.0, 5000.0, 0.01, False],
|
||||
"eps_s": ["FLOAT", 0.075, 0.0, 1.0, 0.0001, False]
|
||||
}
|
||||
}
|
||||
|
||||
js_content = "const widgets = {\n"
|
||||
for name, settings in default_settings.items():
|
||||
js_content += self.generate_js_object(name, settings)
|
||||
|
||||
# Convert the Python dictionary to a JavaScript object
|
||||
for name, settings in self.scheduler_defaults.items():
|
||||
if name in ["polyexponential", "vp"]: # Merge with defaults
|
||||
if name not in default_settings:
|
||||
js_content += self.generate_js_object(name, settings)
|
||||
else:
|
||||
# Merge with defaults
|
||||
js_content += f" \"{name}\": {{\n"
|
||||
for setting, details in settings.items():
|
||||
js_content += f" \"{setting}\": {json.dumps(details)},\n"
|
||||
js_content += " },\n"
|
||||
else:
|
||||
# Add new entries if not already covered by defaults
|
||||
js_content += f"\n \"{name}\": {{\n"
|
||||
for setting, details in settings.items():
|
||||
if isinstance(details, list) and len(details) == 6 and details[0] == "FLOAT":
|
||||
js_content += f" \"{setting}\": {json.dumps(details)},\n"
|
||||
else:
|
||||
print(f"Skipping setting {setting} due to unexpected format: {details}")
|
||||
js_content += " },\n"
|
||||
|
||||
js_content += "};\n\nexport { widgets };\n"
|
||||
|
||||
# Write the JavaScript content to the file
|
||||
|
||||
@@ -4,7 +4,7 @@ def generate_scheduler_node_class(settings, get_sigmas_function):
|
||||
class DynamicSchedulerNode:
|
||||
|
||||
@classmethod
|
||||
def create_setting_entry(setting_type, setting_value):
|
||||
def create_setting_entry(cls, setting_type, setting_value):
|
||||
if setting_type == "INT":
|
||||
return ("INT", {"default": setting_value[1], "min": setting_value[2], "max": setting_value[3]})
|
||||
elif setting_type == "FLOAT":
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
class FindResolutionNode:
|
||||
resolutions = {
|
||||
"1024 x 1024 1:1": [
|
||||
1024,
|
||||
1024
|
||||
],
|
||||
"1152 x 896 9:7": [
|
||||
1152,
|
||||
896
|
||||
],
|
||||
"1216 x 832 19:13": [
|
||||
1216,
|
||||
832
|
||||
],
|
||||
"1344 x 768 7:4": [
|
||||
1344,
|
||||
768
|
||||
],
|
||||
"1536 x 640 12:5": [
|
||||
1536,
|
||||
640
|
||||
]
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
resolution_names = sorted(cls.resolutions.keys())
|
||||
return {
|
||||
"required": {
|
||||
"resolution": (resolution_names, ),
|
||||
"flip": ("BOOLEAN", {"default": False},),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT",)
|
||||
RETURN_NAMES = ("WIDTH", "HEIGHT")
|
||||
FUNCTION = "get_resolution"
|
||||
CATEGORY = "sn0w"
|
||||
|
||||
def get_resolution(self, resolution, flip):
|
||||
resolutions = self.resolutions
|
||||
return (resolutions[resolution][1], resolutions[resolution][0]) if flip else (resolutions[resolution][0], resolutions[resolution][1])
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import os
|
||||
import requests
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
class GetGelbooruTagsNode:
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
tags_path = '../web/settings/tags/'
|
||||
files = ['body_tags.txt']
|
||||
|
||||
for file in files:
|
||||
with open(os.path.abspath(os.path.join(dir_path, f'{tags_path}{file}')), 'r') as text_file:
|
||||
valid_tags = {line.strip().lower() for line in text_file}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"url": ("STRING", {"default": ""}),
|
||||
"separator": ("STRING", {"default": ", "}),
|
||||
"replace_underscore": ("BOOLEAN", {"default": True}),
|
||||
"characteristics_tags": ("BOOLEAN", {"default": True}),
|
||||
"scene_tags": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
"optional": {
|
||||
"api_key (optional)": ("STRING", {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("PROMPT",)
|
||||
FUNCTION = "get_gelbooru_tags"
|
||||
CATEGORY = "sn0w"
|
||||
|
||||
def filter_tags(self, input_string, separator, characteristics_tags, scene_tags):
|
||||
if (characteristics_tags and scene_tags):
|
||||
return input_string
|
||||
|
||||
# Split the input string by the separator
|
||||
tags = input_string.split(separator)
|
||||
return_string = ""
|
||||
|
||||
def add_tags_to_return_string(tag):
|
||||
nonlocal return_string
|
||||
if return_string:
|
||||
return_string += separator
|
||||
return_string += tag.strip()
|
||||
|
||||
for tag in tags:
|
||||
processed_tag = tag.replace('\\', '').strip().replace(' ', '_').lower()
|
||||
|
||||
if processed_tag in self.valid_tags:
|
||||
if (characteristics_tags):
|
||||
add_tags_to_return_string(tag)
|
||||
else:
|
||||
if (scene_tags):
|
||||
add_tags_to_return_string(tag)
|
||||
|
||||
return return_string
|
||||
|
||||
def get_gelbooru_tags(self, url, separator, replace_underscore, characteristics_tags, scene_tags, **kwargs):
|
||||
API_URL = "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1"
|
||||
|
||||
if (kwargs["api_key (optional)"]):
|
||||
api_key = kwargs["api_key (optional)"]
|
||||
if (api_key.startswith("&api_key=")):
|
||||
api_key[9:]
|
||||
|
||||
API_URL = f"{API_URL}&api_key={api_key}"
|
||||
|
||||
if url.startswith("http"):
|
||||
parsed_url = urlparse(url)
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
id_value = query_params.get('id', [None])[0]
|
||||
else:
|
||||
id_value = url
|
||||
|
||||
final_api_url = API_URL + f"&id={id_value}"
|
||||
|
||||
r = requests.get(url = final_api_url)
|
||||
data = r.json()
|
||||
tags = data['post'][0]['tags']
|
||||
tags = tags.replace(" ", separator)
|
||||
if (replace_underscore):
|
||||
tags = tags.replace("_", " ")
|
||||
|
||||
tags = self.filter_tags(tags, separator, characteristics_tags, scene_tags)
|
||||
return (tags,)
|
||||
@@ -1,111 +0,0 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
class FilmGrain:
|
||||
def __init__(self):
|
||||
self.noise_cache = {}
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
self.noise_img_dir = os.path.join(script_dir, 'img', 'noise')
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"intensity": ("FLOAT", {
|
||||
"default": 0.2,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.01
|
||||
}),
|
||||
"scale": ("FLOAT", {
|
||||
"default": 10,
|
||||
"min": 1,
|
||||
"max": 100,
|
||||
"step": 1
|
||||
}),
|
||||
"cache": ("BOOLEAN", {"default": False},),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "film_grain"
|
||||
|
||||
CATEGORY = "sn0w"
|
||||
|
||||
def film_grain(self, image: torch.Tensor, intensity: float, scale: float, cache: bool):
|
||||
batch_size, height, width, channels = image.shape
|
||||
assert channels == 3, "Image must have 3 channels for RGB"
|
||||
|
||||
result = torch.zeros_like(image)
|
||||
|
||||
for b in range(batch_size):
|
||||
tensor_image = image[b].numpy()
|
||||
|
||||
grain_image = np.zeros_like(tensor_image)
|
||||
for channel in range(3):
|
||||
noise = self.get_noise_image(height, width, scale, channel) if cache else self.generate_perlin_noise((height, width), scale)
|
||||
noise = (noise - noise.min()) / (noise.max() - noise.min()) # Normalize
|
||||
noise = (noise * 2 - 1) * intensity # Apply intensity
|
||||
grain_image[:, :, channel] = np.clip(tensor_image[:, :, channel] + noise, 0, 1)
|
||||
|
||||
tensor = torch.from_numpy(grain_image).unsqueeze(0)
|
||||
result[b] = tensor
|
||||
|
||||
return (result,)
|
||||
|
||||
def generate_perlin_noise(self, shape, scale, octaves=4, persistence=0.5, lacunarity=2):
|
||||
def smoothstep(t):
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
|
||||
def lerp(t, a, b):
|
||||
return a + t * (b - a)
|
||||
|
||||
def gradient(h, x, y):
|
||||
vectors = np.array([[1, 1], [-1, 1], [1, -1], [-1, -1]])
|
||||
g = vectors[h % 4]
|
||||
return g[:, :, 0] * x + g[:, :, 1] * y
|
||||
|
||||
height, width = shape
|
||||
noise = np.zeros(shape)
|
||||
|
||||
for octave in range(octaves):
|
||||
octave_scale = scale * lacunarity ** octave
|
||||
x = np.linspace(0, 1, width, endpoint=False)
|
||||
y = np.linspace(0, 1, height, endpoint=False)
|
||||
X, Y = np.meshgrid(x, y)
|
||||
X, Y = X * octave_scale, Y * octave_scale
|
||||
|
||||
xi = X.astype(int)
|
||||
yi = Y.astype(int)
|
||||
|
||||
xf = X - xi
|
||||
yf = Y - yi
|
||||
|
||||
u = smoothstep(xf)
|
||||
v = smoothstep(yf)
|
||||
|
||||
n00 = gradient(np.random.randint(0, 4, (height, width)), xf, yf)
|
||||
n01 = gradient(np.random.randint(0, 4, (height, width)), xf, yf - 1)
|
||||
n10 = gradient(np.random.randint(0, 4, (height, width)), xf - 1, yf)
|
||||
n11 = gradient(np.random.randint(0, 4, (height, width)), xf - 1, yf - 1)
|
||||
|
||||
x1 = lerp(u, n00, n10)
|
||||
x2 = lerp(u, n01, n11)
|
||||
y1 = lerp(v, x1, x2)
|
||||
|
||||
noise += y1 * persistence ** octave
|
||||
|
||||
return noise / (1 - persistence ** octaves)
|
||||
|
||||
def get_noise_image(self, height, width, scale, channel):
|
||||
key = (height, width)
|
||||
if key in self.noise_cache and self.noise_cache[key][channel] is not None:
|
||||
return self.noise_cache[key][channel]
|
||||
else:
|
||||
noise = self.generate_perlin_noise((height, width), scale)
|
||||
if key not in self.noise_cache:
|
||||
self.noise_cache[key] = [None, None, None]
|
||||
self.noise_cache[key][channel] = noise
|
||||
return noise
|
||||
@@ -1,245 +0,0 @@
|
||||
import re
|
||||
|
||||
def simplify_tags(tags_string):
|
||||
# dictionary defines categories with specific rules for removing or keeping tags.
|
||||
# remove contains phrases that, if found in a prompt, suggest the tag should be removed under certain conditions.
|
||||
# keep contains phrases that, if found in a prompt, indicate the tag should be kept.
|
||||
# if a remove tag is found and no keep tag is found, it will remove all mentions of the category except the tags found in remove.
|
||||
# keep always takes priority over remove, if any tag in keep is found nothing will be removed
|
||||
special_phrases = {
|
||||
'eye': {
|
||||
'remove': ['covering eyes', 'over eyes', 'covered eyes', 'covering face', 'covering own eyes', 'facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
'sclera': {
|
||||
'remove': ['covering eyes', 'over eyes', 'covered eyes', 'covering face', 'covering own eyes', 'facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
'mouth': {
|
||||
'remove': ['facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
'teeth': {
|
||||
'remove': ['facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
}
|
||||
|
||||
# Regular expression to match parenthesized parts
|
||||
parenthesized_pattern = re.compile(r'\([^()]*\)')
|
||||
parenthesized_parts = []
|
||||
|
||||
# Extract and temporarily remove parenthesized parts, storing their positions
|
||||
def extract_parenthesized(match):
|
||||
parenthesized_parts.append(match.group(0))
|
||||
return "\0" # Use a unique placeholder to mark the position
|
||||
|
||||
modified_tags_string = re.sub(parenthesized_pattern, extract_parenthesized, tags_string)
|
||||
|
||||
# Split tags and initialize lists
|
||||
tags = [tag.strip() for tag in modified_tags_string.split(',')]
|
||||
final_tags = []
|
||||
removed_tags = []
|
||||
tag_map = {}
|
||||
|
||||
# Mapping non-superior tags to their superior counterparts
|
||||
for tag in tags:
|
||||
for potential_superior in tags:
|
||||
if tag != potential_superior and tag in potential_superior:
|
||||
tag_parts = set(tag.split(' '))
|
||||
superior_parts = set(potential_superior.split(' '))
|
||||
if tag_parts.issubset(superior_parts) and tag_parts != superior_parts:
|
||||
tag_map[tag] = potential_superior
|
||||
|
||||
# Check for 'keep' conditions globally
|
||||
global_keep_conditions = {keyword: any(keep_phrase in tags_string for keep_phrase in conditions['keep']) for keyword, conditions in special_phrases.items()}
|
||||
|
||||
# Identify tags that should not be removed because they are contained in 'remove' phrases
|
||||
non_removable_tags = set()
|
||||
for tag in tags:
|
||||
for keyword, conditions in special_phrases.items():
|
||||
if any(remove_phrase in tag for remove_phrase in conditions['remove']):
|
||||
non_removable_tags.add(tag)
|
||||
|
||||
for tag in tags:
|
||||
if tag in tag_map:
|
||||
# Append non-superior tags to removed_tags if their superior counterparts are not already included
|
||||
if tag_map[tag] not in final_tags:
|
||||
removed_tags.append(tag)
|
||||
continue
|
||||
|
||||
should_remove_tag = False
|
||||
for keyword, conditions in special_phrases.items():
|
||||
if keyword in tag:
|
||||
if not global_keep_conditions[keyword] and any(remove_phrase in tags_string for remove_phrase in conditions['remove']) and tag not in non_removable_tags:
|
||||
should_remove_tag = True
|
||||
break
|
||||
|
||||
if should_remove_tag:
|
||||
removed_tags.append(tag)
|
||||
else:
|
||||
final_tags.append(tag)
|
||||
|
||||
# Ensure tags explicitly listed in "remove" are not removed
|
||||
for keyword, conditions in special_phrases.items():
|
||||
for remove_tag in conditions['remove']:
|
||||
if remove_tag in removed_tags:
|
||||
removed_tags.remove(remove_tag)
|
||||
final_tags.append(remove_tag)
|
||||
|
||||
# Remove duplicates in the removed_tags list by converting it to a set and back to a list
|
||||
removed_tags = ', '.join(list(set(removed_tags)))
|
||||
|
||||
# Reinsert parenthesized parts back into their original positions
|
||||
final_tags_list = modified_tags_string.split(',')
|
||||
final_tags_with_parentheses = []
|
||||
for tag in final_tags_list:
|
||||
if "\0" in tag: # If placeholder is found, replace it with the original parenthesized part
|
||||
tag = tag.replace("\0", parenthesized_parts.pop(0), 1)
|
||||
final_tags_with_parentheses.append(tag.strip())
|
||||
|
||||
# New step: Re-sort tags to move numeric tags to the front
|
||||
numeric_tag_pattern = re.compile(r'\b\d+\+?(girls?|boys?)\b')
|
||||
numeric_tags = [tag for tag in final_tags_with_parentheses if numeric_tag_pattern.match(tag)]
|
||||
non_numeric_tags = [tag for tag in final_tags_with_parentheses if not numeric_tag_pattern.match(tag)]
|
||||
|
||||
# Prioritize numeric tags in the final list
|
||||
prioritized_final_tags = numeric_tags + non_numeric_tags
|
||||
|
||||
# Generate the final simplified tags string
|
||||
simplified_tags_string = ', '.join(prioritized_final_tags)
|
||||
|
||||
return simplified_tags_string, removed_tags
|
||||
|
||||
def simplify_tags_new(tags_string, separator):
|
||||
# dictionary defines categories with specific rules for removing or keeping tags.
|
||||
# remove contains phrases that, if found in a prompt, suggest the tag should be removed.
|
||||
# keep contains phrases that, if found in a prompt, indicate the tag should be kept.
|
||||
# if a remove tag is found and no keep tag is found, it will remove all mentions of the category except the tags found in remove.
|
||||
# keep always takes priority over remove, if any tag in keep is found nothing will be removed
|
||||
special_phrases = {
|
||||
'eye': {
|
||||
'remove': ['covering eyes', 'over eyes', 'covered eyes', 'covering face', 'covering own eyes', 'facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
'sclera': {
|
||||
'remove': ['covering eyes', 'over eyes', 'covered eyes', 'covering face', 'covering own eyes', 'facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
'mouth': {
|
||||
'remove': ['facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
'teeth': {
|
||||
'remove': ['facing away'],
|
||||
'keep': ['looking at viewer']
|
||||
},
|
||||
}
|
||||
|
||||
# Compile regex patterns outside of loops
|
||||
parenthesized_pattern = re.compile(r'\([^()]*\)')
|
||||
numeric_tag_pattern = re.compile(r'\b\d+\+?(girls?|boys?)\b')
|
||||
|
||||
# Extract parenthesized parts
|
||||
parenthesized_parts = []
|
||||
def extract_parenthesized(match):
|
||||
parenthesized_parts.append(match.group())
|
||||
return f"\0{len(parenthesized_parts)-1}\0"
|
||||
|
||||
modified_tags_string = re.sub(parenthesized_pattern, extract_parenthesized, tags_string)
|
||||
|
||||
tags = [tag.strip() for tag in modified_tags_string.split(separator)]
|
||||
final_tags = []
|
||||
removed_tags = set()
|
||||
tag_map = {}
|
||||
|
||||
for tag in tags:
|
||||
for potential_superior in tags:
|
||||
if tag in potential_superior and tag != potential_superior:
|
||||
tag_map[tag] = potential_superior
|
||||
|
||||
global_keep_conditions = {
|
||||
keyword: any(keep_phrase in tags_string for keep_phrase in conditions['keep'])
|
||||
for keyword, conditions in special_phrases.items()
|
||||
}
|
||||
|
||||
non_removable_tags = {
|
||||
tag
|
||||
for tag in tags
|
||||
for keyword, conditions in special_phrases.items()
|
||||
if any(remove_phrase in tag for remove_phrase in conditions['remove'])
|
||||
}
|
||||
|
||||
for tag in tags:
|
||||
superior_tag = tag_map.get(tag)
|
||||
if superior_tag and superior_tag not in final_tags:
|
||||
removed_tags.add(tag)
|
||||
continue
|
||||
|
||||
should_remove_tag = False
|
||||
for keyword, conditions in special_phrases.items():
|
||||
if keyword in tag and not global_keep_conditions[keyword] and any(remove_phrase in tags_string for remove_phrase in conditions['remove']) and tag not in non_removable_tags:
|
||||
should_remove_tag = True
|
||||
break
|
||||
|
||||
if should_remove_tag:
|
||||
removed_tags.add(tag)
|
||||
else:
|
||||
# Avoid adding duplicates
|
||||
if tag not in final_tags:
|
||||
final_tags.append(tag)
|
||||
|
||||
# Reinsert parenthesized parts and handle removed tags
|
||||
final_tags_with_parentheses = []
|
||||
for tag in final_tags:
|
||||
while "\0" in tag:
|
||||
index = int(tag[tag.find("\0")+1:tag.rfind("\0")])
|
||||
tag = tag.replace(f"\0{index}\0", parenthesized_parts[index], 1)
|
||||
final_tags_with_parentheses.append(tag)
|
||||
|
||||
animagine_formatting = True
|
||||
|
||||
if animagine_formatting:
|
||||
numeric_tags = [tag for tag in final_tags_with_parentheses if numeric_tag_pattern.match(tag)]
|
||||
non_numeric_tags = [tag for tag in final_tags_with_parentheses if not numeric_tag_pattern.match(tag)]
|
||||
|
||||
prioritized_final_tags = numeric_tags + non_numeric_tags
|
||||
else:
|
||||
prioritized_final_tags = final_tags_with_parentheses
|
||||
|
||||
simplified_tags_string = separator.join(prioritized_final_tags).strip(separator)
|
||||
removed_tags_string = separator.join(removed_tags).strip(separator)
|
||||
|
||||
return simplified_tags_string, removed_tags_string
|
||||
|
||||
def combine_string(separator, simplify, **kwargs):
|
||||
# Collect strings that are not None and strip them
|
||||
strings = [s.strip() for s in (kwargs.get(f"string_{char}") for char in ['a', 'b', 'c', 'd']) if s]
|
||||
|
||||
all_words = set()
|
||||
combined = []
|
||||
|
||||
for string in strings:
|
||||
# Avoid appending the separator at the end; directly construct the final string without it
|
||||
unique_words = [word for word in string.split(separator) if word not in all_words and not all_words.add(word)]
|
||||
if unique_words: # Check if there are any unique words to add
|
||||
combined.append(separator.join(unique_words))
|
||||
|
||||
# Join all unique words across all strings with the separator, handle simplify directly
|
||||
final_combined_string = separator.join(combined)
|
||||
if simplify:
|
||||
final_tags, removed_tags = simplify_tags(final_combined_string, separator)
|
||||
|
||||
return (final_tags, removed_tags)
|
||||
|
||||
# Test the function with an example string
|
||||
tags_string1 = "(mirko, boku no hero academia:1.2), "
|
||||
tags_string2 = "animal ears, breasts, dark-skinned female, large breasts, long hair, parted bangs, rabbit ears, rabbit girl, red eyes, toned, white hair, 1boy, 1girl, after ejaculation, animal ear fluff, animal hands, bell, bikini, black bikini, black thighhighs, blush, breasts apart, cat ears, cat paws, cat tail, clothed female nude male, collar, covered erect nipples, cum, cum in mouth, cum on penis, cum on tongue, facial, fake animal ears, fake tail, fang, fur collar, gloves, gluteal fold, highleg, highleg bikini, holding, holding leash, kneeling, large penis, leash, leash pull, micro bikini, navel, neck bell, nude, open mouth, paw gloves, penis, penis on face, penis over eyes, pet play, smile, stomach, swimsuit, tail, tail ornament, thighhighs, veins, veiny penis, (masterpiece, exceptional, best aesthetic, best quality, masterpiece, extremely detailed:1.1)"
|
||||
#simplified_tags_string, removed_tags = simplify_tags(tags_string)
|
||||
#print('Simplified Tags Old:', simplified_tags_string)
|
||||
#print('Removed Tags Old:', removed_tags)
|
||||
#print("\n")
|
||||
|
||||
simplified_tags_string_new, removed_tags_new = combine_string(", ", False, [tags_string1, tags_string2])
|
||||
print('Simplified Tags New:', simplified_tags_string_new)
|
||||
print('Removed Tags New:', removed_tags_new)
|
||||
@@ -7,8 +7,8 @@ import xml.etree.ElementTree as ET
|
||||
from difflib import get_close_matches
|
||||
|
||||
# Constants
|
||||
CHARACTER_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'characters.json'))
|
||||
CUSTOM_CHARACTER_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'custom_characters.json'))
|
||||
CHARACTER_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../web/settings', 'characters.json'))
|
||||
CUSTOM_CHARACTER_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../web/settings', 'custom_characters.json'))
|
||||
TAGS_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'danbooru-tags.csv'))
|
||||
LOG_FILE_PATH = os.path.abspath('testing/success_log.txt')
|
||||
BASE_URL = "https://gelbooru.com/index.php?page=dapi&s=tag&q=index&name="
|
||||
@@ -101,6 +101,8 @@ low_use_characters = [] # List to store characters with too few uses
|
||||
total_valid_characters = 0 # Counter for total valid characters
|
||||
good_characters_count = 0 # Counter for good characters
|
||||
|
||||
print(f"{GREEN_TEXT}Note:{RESET_TEXT} Warnings for tags do not mean they should be removed, it is just there to make sure there are no misspelt tags")
|
||||
|
||||
# Record successful checks and track low use characters
|
||||
with open(LOG_FILE_PATH, 'a') as log_file:
|
||||
if current_min_uses != MIN_USES:
|
||||
@@ -151,7 +153,7 @@ with open(LOG_FILE_PATH, 'a') as log_file:
|
||||
|
||||
if total_valid_characters > 0:
|
||||
good_percentage = (good_characters_count / total_valid_characters) * 100
|
||||
print(f"{good_percentage:.2f}% of characters are good.")
|
||||
print(f"{good_percentage:.2f}% of characters are valid.")
|
||||
else:
|
||||
print("No valid characters found.")
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { SettingUtils } from './sn0w.js';
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
|
||||
function updateSorting() {
|
||||
api.fetchApi(`${SettingUtils.API_PREFIX}/update_characters`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
app.refreshComboInNodes();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
const id = "sn0w.DisableDefaultCharacters";
|
||||
const settingDefinition = {
|
||||
id,
|
||||
name: "[Sn0w] Disable Default Characters",
|
||||
defaultValue: false,
|
||||
type: "boolean",
|
||||
onChange: () => updateSorting(),
|
||||
};
|
||||
|
||||
let setting;
|
||||
|
||||
const extension = {
|
||||
name: id,
|
||||
init() {
|
||||
setting = app.ui.settings.addSetting(settingDefinition);
|
||||
},
|
||||
};
|
||||
|
||||
app.registerExtension(extension);
|
||||
+2
-2
@@ -47,7 +47,7 @@ export class SettingUtils {
|
||||
const textarea = document.createElement("textarea");
|
||||
|
||||
// Generate a unique ID for associating the label with the textarea
|
||||
const uniqueId = `custom-setting-${Date.now()}`;
|
||||
const uniqueId = `${name.replaceAll(" ", "").replace("[", "").replace("]", "-")}`;
|
||||
label.setAttribute("for", uniqueId);
|
||||
label.textContent = name;
|
||||
|
||||
@@ -105,7 +105,7 @@ export class SettingUtils {
|
||||
const label = document.createElement("label");
|
||||
|
||||
// Generate a unique ID for the setting container
|
||||
const uniqueId = `custom-setting-${Date.now()}`;
|
||||
const uniqueId = `${name.replaceAll(" ", "").replace("[", "").replace("]", "-")}`;
|
||||
label.setAttribute("for", uniqueId);
|
||||
label.textContent = name;
|
||||
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
import { SettingUtils } from './sn0w.js';
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
|
||||
function updateSorting() {
|
||||
api.fetchApi(`${SettingUtils.API_PREFIX}/update_characters`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
app.refreshComboInNodes();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
const id = "sn0w.SortBySeries";
|
||||
const settingDefinition = {
|
||||
@@ -6,6 +20,7 @@ const settingDefinition = {
|
||||
name: "[Sn0w] Sort Characters By Series",
|
||||
defaultValue: false,
|
||||
type: "boolean",
|
||||
onChange: () => updateSorting(),
|
||||
};
|
||||
|
||||
let setting;
|
||||
|
||||
Reference in New Issue
Block a user