Files
2025-08-09 05:12:51 +09:00

327 lines
11 KiB
Python

from .imgio.converter import PILHandlingHodes
from .autonode import node_wrapper, get_node_names_mappings, validate, anytype, PILImage
from .utils.tagger import get_tags, tagger_keys
from PIL import Image, ImageFilter
auxilary_classes = []
auxilary_node = node_wrapper(auxilary_classes)
@auxilary_node
class GetRatingNode:
FUNCTION = "get_rating_class"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get Rating Class"
@staticmethod
def get_rating_class(image, model_name):
image = PILHandlingHodes.handle_input(image)
result_dict = get_tags(image, model_name=model_name)
return (result_dict['rating'], )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
@auxilary_node
class GetRatingFromTextNode:
FUNCTION = "get_rating_class"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get Rating Class From Text"
@staticmethod
def get_rating_class(image, model_name):
image = PILHandlingHodes.handle_input(image)
result_dict = get_tags(image, model_name=model_name)
return (result_dict['rating'], )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("STRING", {"default": "/path/to/image.jpg"}),
},
"optional": {
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
def pixelate(image, pixelation_factor=0.1):
# Downscale the image
small = image.resize(
(int(image.width * pixelation_factor), int(image.height * pixelation_factor)),
resample=Image.NEAREST
)
# Upscale back to original size
return small.resize(image.size, Image.NEAREST)
def pixelate_target_tiles(image, max_tiles=100):
aspect_ratio = image.width / image.height
# solve h * a * h = max_tiles
h = int((max_tiles / aspect_ratio) ** 0.5)
w = int(aspect_ratio * h)
h, w = max(4, h), max(4, w)
# Downscale the image
small = image.resize(
(w, h),
resample=Image.NEAREST,
)
# Upscale back to original size
return small.resize(image.size, Image.NEAREST)
@auxilary_node
class CensorImageByRating:
FUNCTION = "censor_image"
RETURN_TYPES = ("IMAGE",)
CATEGORY = "image"
custom_name = "Censor Image by Rating"
@staticmethod
@PILHandlingHodes.output_wrapper
def censor_image(image, rating_threshold, censor_method, model_name=None):
# Convert input to a PIL image
image = PILHandlingHodes.handle_input(image)
result_dict = get_tags(image, model_name=model_name)
rating = result_dict['rating']
# If rating is general, no censorship required
if rating.lower() == "general":
return (image,)
censor_image = False
if rating_threshold == "general":
# censor if not general
if rating.lower() != "general":
censor_image = True
elif rating_threshold == "sensitive":
# censor if not general or sensitive
if rating.lower() not in ["general", "sensitive"]:
censor_image = True
elif rating_threshold == "questionable":
# censor if not general, sensitive or questionable
if rating.lower() not in ["general", "sensitive", "questionable"]:
censor_image = True
elif rating_threshold == "explicit":
return (image,) # why are you using this?
if censor_image:
if censor_method.lower() == "white":
# Return a white image of the same size
censored_image = Image.new("RGB", image.size, (255, 255, 255))
return (censored_image,)
elif censor_method.lower() == "blur":
# Apply a strong blur (you can adjust radius as needed)
censored_image = image.filter(ImageFilter.GaussianBlur(radius=40))
return (censored_image,)
elif censor_method.lower() == "pixelate":
# first, blur
censored_image = image.filter(ImageFilter.GaussianBlur(radius=20))
censored_image = pixelate_target_tiles(censored_image, max_tiles=100)
return (censored_image,)
# If unknown method is provided, just return the original image
return (image,)
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"rating_threshold": (["general", "sensitive", "questionable", "explicit"],),
"censor_method": (["blur", "white","pixelate"],),
},
"optional": {
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
@auxilary_node
class FilterTagsNode:
"""
Filters tags, given a list of tags splitted by ",".
We assume the input text is splittable by "," (or separator). Then, if any tags contain the filter, we remove matching tags.
"""
FUNCTION = "filter_tags"
RETURN_TYPES = ("STRING",)
CATEGORY = "safety"
custom_name = "Filter Tags"
@staticmethod
def filter_tags(tags, filter_tags, separator):
filter_tags = filter_tags.split(",")
filter_tags = [tag.strip() for tag in filter_tags]
tags = tags.split(separator)
tags = [tag.strip() for tag in tags]
filtered = []
for tag in tags:
if all(filter_tag not in tag for filter_tag in filter_tags):
filtered.append(tag)
return (separator.join(filtered), )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"tags": ("STRING",),
"filter_tags": ("STRING",),
},
# optional separator
"optional": {
"separator": ("STRING", {"default": ","}),
}
}
@auxilary_node
class GetTagsAboveThresholdNode:
FUNCTION = "get_tags_above_threshold"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get Tags Above Threshold"
@staticmethod
def get_tags_above_threshold(image, threshold, replace, model_name):
image = PILHandlingHodes.handle_input(image)
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
return (", ".join(result_dict['tags']), )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"threshold": ("FLOAT", {"default": 0.4}),
"replace": ("BOOLEAN", {"default": False}),
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
@auxilary_node
class GetTagsAboveThresholdFromTextNode:
FUNCTION = "get_tags_above_threshold"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get Tags Above Threshold From Text"
@staticmethod
def get_tags_above_threshold(image, threshold, replace, model_name):
image = PILHandlingHodes.handle_input(image)
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
return (", ".join(result_dict['tags']), )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"threshold": ("FLOAT", {"default": 0.4}),
"replace": ("BOOLEAN", {"default": False}),
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
@auxilary_node
class GetCharactersAboveThresholdNode:
FUNCTION = "get_tags_above_threshold"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get Chars Above Threshold"
@staticmethod
def get_tags_above_threshold(image, threshold, replace, model_name):
image = PILHandlingHodes.handle_input(image)
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
return (", ".join(result_dict['chars']), )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"threshold": ("FLOAT", {"default": 0.4}),
"replace": ("BOOLEAN", {"default": False}),
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
@auxilary_node
class GetCharactersAboveThresholdFromTextNode:
FUNCTION = "get_tags_above_threshold"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get Chars Above Threshold From Text"
@staticmethod
def get_tags_above_threshold(image, threshold, replace, model_name):
image = PILHandlingHodes.handle_input(image)
result_dict = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
return (", ".join(result_dict['chars']), )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"threshold": ("FLOAT", {"default": 0.4}),
"replace": ("BOOLEAN", {"default": False}),
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
@auxilary_node
class GetAllTagsAboveThresholdNode:
FUNCTION = "get_tags"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get All Tags Above Threshold"
@staticmethod
def get_tags(image, threshold, replace, model_name):
image = PILHandlingHodes.handle_input(image)
result = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
result_list = []
result_list.append(result['rating'])
result_list.extend(result['tags'])
result_list.extend(result['chars'])
return (", ".join(result_list), )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"threshold": ("FLOAT", {"default": 0.4}),
"replace": ("BOOLEAN", {"default": False}),
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
@auxilary_node
class GetAllTagsExceptCharacterAboveThresholdNode:
FUNCTION = "get_tags"
RETURN_TYPES = ("STRING",)
CATEGORY = "tagger"
custom_name = "Get All Tags Above Threshold Except Characters"
@staticmethod
def get_tags(image, threshold, replace, model_name):
image = PILHandlingHodes.handle_input(image)
result = get_tags(image, threshold=threshold, replace=replace, model_name=model_name)
result_list = []
result_list.append(result['rating'])
result_list.extend(result['tags'])
return (", ".join(result_list), )
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"threshold": ("FLOAT", {"default": 0.4}),
"replace": ("BOOLEAN", {"default": False}),
"model_name": (tagger_keys, {"default": tagger_keys[0]}),
}
}
CLASS_MAPPINGS, CLASS_NAMES = get_node_names_mappings(auxilary_classes)
validate(auxilary_classes)