Merge pull request #2 from sn0w12/dev

Dev
This commit is contained in:
sn0w
2024-06-25 02:48:58 +02:00
committed by GitHub
12 changed files with 107 additions and 548 deletions
-32
View File
@@ -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
View File
@@ -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)
+29 -21
View File
@@ -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
+1 -1
View 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":
-43
View File
@@ -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])
-87
View File
@@ -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,)
-111
View File
@@ -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
-245
View File
@@ -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)
+5 -3
View File
@@ -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.")
+35
View File
@@ -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
View File
@@ -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;
+15
View File
@@ -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;