Add node to generate all needed character images

This commit is contained in:
Lucas Sköld
2025-01-12 02:16:22 +01:00
parent 44ee36e996
commit 3ec8fa68c1
3 changed files with 166 additions and 3 deletions
+3
View File
@@ -20,6 +20,7 @@ from .src.load_lora_from_folder import LoadLoraFolderNode
from .src.textbox import TextboxNode
from .src.simple_ksampler import SimpleSamplerCustom
from .src.filter_tags import FilterTags
from .src.generate_all_character_images import GenerateCharactersNode
# Constants
WEB_DIRECTORY = "./web"
@@ -40,6 +41,7 @@ NODE_CLASS_MAPPINGS = {
"Copy/Paste Textbox": TextboxNode,
"Sn0w KSampler": SimpleSamplerCustom,
"Filter Tags": FilterTags,
"Generate All Characters": GenerateCharactersNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -56,6 +58,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Copy/Paste Textbox": "Textbox",
"Sn0w KSampler": "Sn0w KSampler",
"Filter Tags": "Filter Tags",
"Generate All Characters": "Generate All Character Images",
}
# Function to check required folder paths
+1 -3
View File
@@ -27,9 +27,7 @@ class CharacterSelectNode:
@classmethod
def initialize(cls):
dir_path = os.path.dirname(os.path.realpath(__file__))
if os.path.basename(dir_path) == "src":
dir_path = os.path.dirname(dir_path)
dir_path = cls.get_base_dir()
cls.load_characters(dir_path)
+162
View File
@@ -0,0 +1,162 @@
import os
import json
import comfy.samplers
import numpy as np
from PIL import Image
from nodes import VAEDecode
from ..sn0w import Logger
from .character_select import CharacterSelectNode
from .simple_ksampler import SimpleSamplerCustom
# File paths
IMAGES_FILE_PATH = "web/images/images.json"
class GenerateCharactersNode:
logger = Logger()
character_select = CharacterSelectNode()
base_dir = character_select.get_base_dir()
json_path = os.path.join(base_dir, IMAGES_FILE_PATH)
characters_dir = os.path.join(os.path.dirname(json_path), "characters")
@classmethod
def initialize(cls):
cls.character_select.check_initialize()
os.makedirs(cls.characters_dir, exist_ok=True)
return cls.character_select.final_character_dict
@classmethod
def get_all_characters(cls):
return cls.initialize()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"vae": ("VAE",),
"add_noise": ("BOOLEAN", {"default": True}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler_name": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "round": 0.01}),
"width": ("INT", {"default": 0, "min": 0, "step": 64}),
"height": ("INT", {"default": 0, "min": 0, "step": 64}),
},
"optional": {
"positive": ("STRING", {"default": ""}),
"negative": ("STRING", {"default": ""}),
},
"hidden": {
"extra_info": "EXTRA_PNGINFO",
"id": "UNIQUE_ID",
},
}
@classmethod
def VALIDATE_INPUTS(cls, input_types):
return True
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("IMAGES",)
FUNCTION = "generate"
CATEGORY = "sn0w"
@classmethod
def load_existing_images(cls):
try:
with open(cls.json_path, "r", encoding="utf-8") as f:
data = json.load(f)
return [img["filename"] for img in data.get("images", [])]
except Exception as e:
cls.logger.log(f"Error loading images.json: {str(e)}", "WARNING")
return []
def generate(
self,
model,
clip,
vae,
add_noise,
noise_seed,
steps,
cfg,
sampler_name,
scheduler_name,
denoise,
width,
height,
**kwargs,
):
simple_sampler = SimpleSamplerCustom()
vae_decode = VAEDecode()
character_dict = self.get_all_characters()
existing_filenames = self.load_existing_images()
orig_positive = kwargs.get("positive", "")
images = []
for character_name in character_dict:
cleaned_name = character_name.split("(")[0].strip().lower()
cleaned_name = cleaned_name.replace(" ", "_")
# Skip if image already exists
if cleaned_name in existing_filenames:
self.logger.log(f"Skipping {cleaned_name} - image already exists", "INFO")
continue
self.logger.log(f"Generating image for character: {character_name}", "INFO")
# Get character details and update positive prompt
char_item = character_dict[character_name]
char_string = f"({char_item['associated_string']}:1.1), "
char_prompt = char_item["prompt"]
positive = f"{char_string}{char_prompt}, {orig_positive}".strip(", ")
kwargs["positive"] = positive
(image, samples, positive_prompt, negative_prompt) = simple_sampler.sample(
model,
clip,
vae,
add_noise,
noise_seed,
steps,
cfg,
sampler_name,
scheduler_name,
denoise,
width,
height,
**kwargs,
)
if samples is not None:
# Decode the tensor into an image using VAE
decoded_image = vae_decode.decode(vae, samples)[0]
images.append(decoded_image[0])
image_path = os.path.join(self.characters_dir, f"{cleaned_name}.png")
# Convert to PIL image and save
i = 255.0 * decoded_image[0].cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
img.save(image_path)
# Add to images.json
with open(self.json_path, "r+", encoding="utf-8") as f:
data = json.load(f)
data["images"].append(
{
"filename": cleaned_name,
"path": f"extensions\\ComfyUI-Sn0w-Scripts\\images\\characters\\{cleaned_name}.png",
}
)
data["count"] = len(data["images"])
f.seek(0)
json.dump(data, f, indent=2)
f.truncate()
return (images,)