diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd20fdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +*.pyc diff --git a/CLIPTextEncodeWildcards.py b/CLIPTextEncodeWildcards.py new file mode 100644 index 0000000..1886552 --- /dev/null +++ b/CLIPTextEncodeWildcards.py @@ -0,0 +1,22 @@ +import os, glob, sys +import random +import re +from .wildcards import * +#import wildcards + +print("wildcards_ComfyUI") +#print(os.getcwd()) + +class CLIPTextEncodeWildcards: + @classmethod + def INPUT_TYPES(s): + return {"required": {"text": ("STRING", {"multiline": True}), "clip": ("CLIP", )}} + RETURN_TYPES = ("CONDITIONING",) + FUNCTION = "encode" + + CATEGORY = "conditioning" + + def encode(self, clip, text): + r=run(text) + print(f"encode : {r}") + return ([[clip.encode(r), {}]], ) diff --git a/CheckpointLoaderRandom.py b/CheckpointLoaderRandom.py new file mode 100644 index 0000000..ce4c5a0 --- /dev/null +++ b/CheckpointLoaderRandom.py @@ -0,0 +1,49 @@ +import os +import comfy.sd +from nodes import * +import random +import folder_paths + +cnt=0 +ckpt_name="" +ckpt_path="" + +class CheckpointLoaderRandom: + models_dir = os.path.join(os.getcwd(),"ComfyUI", "models") + ckpt_dir = os.path.join(models_dir, "checkpoints") + cnt=0 + ckpt_name="" + ckpt_path="" + + def __init__(self): + print(f"CheckpointLoaderRandom __init__") + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "max": ("INT", {"default": 10, "min": 0, "max": 0xffffffffffffffff}), + #"ckpt_name": (filter_files_extensions(recursive_search(s.ckpt_dir), supported_ckpt_extensions), ), + } + } + RETURN_TYPES = ("MODEL", "CLIP", "VAE") + FUNCTION = "load_checkpoint" + + CATEGORY = "loaders" + + def load_checkpoint(self, seed, max, output_vae=True, output_clip=True): + global cnt, ckpt_name, ckpt_path + print(f"cnt : { cnt}") + if ckpt_name=="" or cnt>=max : + cnt=0 + ckpt_names= folder_paths.get_filename_list("checkpoints") + #print(f"ckpt_names : { ckpt_names}") + ckpt_name=random.choice(ckpt_names) + print(f"ckpt_name : { ckpt_name}") + ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name) + out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings")) + cnt+=1 + return out + diff --git a/CheckpointLoaderSimpleText.py b/CheckpointLoaderSimpleText.py new file mode 100644 index 0000000..9d77f0e --- /dev/null +++ b/CheckpointLoaderSimpleText.py @@ -0,0 +1,43 @@ +import os +import comfy.sd +from nodes import * +import folder_paths +import random +from .check_name import * + +print(os.getcwd()) + +class CheckpointLoaderSimpleText: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "ckpt_name": ( + "STRING", { + "multiline": False, + "default": random.choice(folder_paths.get_filename_list("checkpoints")) + } + ), + } + } + RETURN_TYPES = ("MODEL", "CLIP", "VAE") + FUNCTION = "load_checkpoint" + + CATEGORY = "loaders" + + def load_checkpoint(self, ckpt_name, output_vae=True, output_clip=True): + ckpt_path =check_name_ckpt(name_split_choice(ckpt_name)) + if ckpt_path is None: + print(f"{ckpt_name} is none") + return + try: + out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings")) + return out + except Exception as e: + print("CheckpointLoaderSimpleText Exception : "+ e) + return + + +#NODE_CLASS_MAPPINGS = { +# "CheckpointLoaderSimpleText": CheckpointLoaderSimpleText, +#} \ No newline at end of file diff --git a/LoraLoaderText.py b/LoraLoaderText.py new file mode 100644 index 0000000..e16d22a --- /dev/null +++ b/LoraLoaderText.py @@ -0,0 +1,52 @@ +import os +import comfy.sd +from nodes import * +import folder_paths + +class LoraLoaderText: + @classmethod + def INPUT_TYPES(s): + return {"required": { "model": ("MODEL",), + "clip": ("CLIP", ), + "lora_name": ("STRING", { + "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node + "default": (folder_paths.get_filename_list("loras"), ) + }), + #"lora_name": (folder_paths.get_filename_list("loras"), ), + "strength_model": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "strength_clip": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + }} + RETURN_TYPES = ("MODEL", "CLIP") + FUNCTION = "load_lora" + + CATEGORY = "loaders" + + def load_lora(self, model, clip, lora_name, strength_model, strength_clip): + + if lora_name is None or lora_name =="": + print("LoraLoaderText No") + return (strength_model, strength_clip) + + if lora_name.endswith('.safetensors') or lora_name.endswith('.ckpt') : + lora_path = folder_paths.get_full_path("loras", lora_name) + + else: + lora_path = folder_paths.get_full_path("loras", lora_name+'.safetensors') + print("LoraLoaderText lora_path : "+ lora_path) + if lora_path is None: + lora_path = folder_paths.get_full_path("loras", lora_name+'.ckpt') + print("LoraLoaderText lora_path : "+ lora_path) + if lora_path is None: + print("LoraLoaderText No : "+ lora_name) + return (strength_model, strength_clip) + + try: + model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora_path, strength_model, strength_clip) + return (model_lora, clip_lora) + except Exception as e: + print("LoraLoaderText Exception : "+ e) + return (strength_model, strength_clip) + +#NODE_CLASS_MAPPINGS = { +# "LoraLoaderText": LoraLoaderText, +#} \ No newline at end of file diff --git a/LoraLoaderTextRandom.py b/LoraLoaderTextRandom.py new file mode 100644 index 0000000..2adbb23 --- /dev/null +++ b/LoraLoaderTextRandom.py @@ -0,0 +1,60 @@ +import os +import comfy.sd +from nodes import * +import folder_paths +import random +from .check_name import * + +class LoraLoaderTextRandom: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "clip": ("CLIP", ), + "lora_name": ("STRING", { + "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node + "default": (folder_paths.get_filename_list("loras"), ) + }), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + #"lora_name": (folder_paths.get_filename_list("loras"), ), + "strength_model_min": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 10.0, "step": 0.01}), + "strength_model_max": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "strength_clip_min": ("FLOAT", {"default": 0.50, "min": 0.0, "max": 10.0, "step": 0.01}), + "strength_clip_max": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + } + } + RETURN_TYPES = ("MODEL", "CLIP") + FUNCTION = "load_lora" + + CATEGORY = "loaders" + + def load_lora(self, + model, + clip, + lora_name, + seed, + strength_model_min, + strength_model_max, + strength_clip_min, + strength_clip_max + ): + + strength_model=random.uniform(min(strength_model_min,strength_model_max),max(strength_model_min,strength_model_max)) + strength_clip=random.uniform(min(strength_clip_min,strength_clip_max),max(strength_clip_min,strength_clip_max)) + + lora_path =check_name_pt("loras",name_split_choice(lora_name)) + if lora_path is None: + print(f"{lora_name} is none") + return (strength_model, strength_clip) + print(f"LoraLoaderTextRandom : {lora_path} ") + try: + model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora_path, strength_model, strength_clip) + return (model_lora, clip_lora) + except Exception as e: + print("LoraLoaderTextRandom Exception : "+ e) + return (strength_model, strength_clip) + +#NODE_CLASS_MAPPINGS = { +# "LoraLoaderTextRandom": LoraLoaderTextRandom, +#} \ No newline at end of file diff --git a/README.md b/README.md index 765e1c1..8e1d4f4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# ComfyUI_node_Lilly +# ComfyUI-node-Lilly diff --git a/Random_Sampler.py b/Random_Sampler.py new file mode 100644 index 0000000..2f6d1c3 --- /dev/null +++ b/Random_Sampler.py @@ -0,0 +1,104 @@ +import os +import nodes +import comfy.samplers +import random +from nodes import common_ksampler + +#wd = os.getcwd() +#print("working directory is ", wd) +# +#filePath = __file__ +#print("This script file path is ", filePath) +# +#absFilePath = os.path.abspath(__file__) +#print("This script absolute path is ", absFilePath) +# +#path, filename = os.path.split(absFilePath) +#print("Script file path is {}, filename is {}".format(path, filename)) + + +class Random_Sampler: + def __init__(self): + print(f"Random_Sampler __init__") + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "positive": ("CONDITIONING", ), + "negative": ("CONDITIONING", ), + "LATENT": ("LATENT", ), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + #"Random": (["enable", "disable"],), + "steps_min": ("INT", {"default": 20, "min": 1,"max": 10000, "step": 1 }), + "steps_max": ("INT", {"default": 30, "min": 1,"max": 10000, "step": 1 }), + "cfg_min": ("FLOAT", {"default": 5.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "cfg_max": ("FLOAT", {"default": 9.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "denoise_min": ("FLOAT", {"default": 0.50, "min": 0.01, "max": 1.0, "step": 0.01}), + "denoise_max": ("FLOAT", {"default": 1.00, "min": 0.01, "max": 1.0, "step": 0.01}), + }, + } + + RETURN_TYPES = ("LATENT",) + FUNCTION = "test" + + OUTPUT_NODE = False + + CATEGORY = "sampling" + + def test(self, + model, + positive, + negative, + LATENT, + sampler_name, + scheduler, + seed, + #Random, + steps_min, + steps_max, + cfg_min, + cfg_max, + denoise_min, + denoise_max, + ): + print(f""" + model : {model} ; + positive : {positive} ; + negative : {negative} ; + LATENT: {LATENT} ; + sampler_name : {sampler_name} ; + scheduler: {scheduler} ; + {seed} ; + + {steps_min} ; + {steps_max} ; + {cfg_min} ; + {cfg_max} ; + {denoise_min} ; + {denoise_max} ; + """) + #if Random == "enable": + # print(f"Random enable") + # return common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise=denoise) + return common_ksampler( + model, + seed, + random.randint( min(steps_min,steps_max), max(steps_min,steps_max) ), + random.randint( int(cfg_min*2) , int(cfg_max*2) ) / 2 , + sampler_name, + scheduler, + positive, + negative, + LATENT, + denoise=random.uniform(min(denoise_min,denoise_max),max(denoise_min,denoise_max)) + ) + #return (LATENT,) + +#NODE_CLASS_MAPPINGS = { +# "Random_Sampler": Random_Sampler +#} \ No newline at end of file diff --git a/VAELoaderDecode.py b/VAELoaderDecode.py new file mode 100644 index 0000000..736b784 --- /dev/null +++ b/VAELoaderDecode.py @@ -0,0 +1,75 @@ +import os +import comfy.sd +from nodes import * +import folder_paths + + +# VAEDecode +# VAELoader +# VAELoaderDecode + +#wd = os.getcwd() +#print("working directory is ", wd) +# +#filePath = __file__ +#print("This script file path is ", filePath) +# +#absFilePath = os.path.abspath(__file__) +#print("This script absolute path is ", absFilePath) +# +#realFilePath = os.path.realpath(__file__) +#print("This script real path is ", realFilePath) +# +#path, filename = os.path.split(absFilePath) +#print("Script file path is {}, filename is {}".format(path, filename)) + +class VAELoaderDecode: + + def __init__(self, device="cpu"): + self.device = device + + #@classmethod + #def INPUT_TYPES(s): + # return {"required": { "samples": ("LATENT", ), "vae": ("VAE", )}} + # + #@classmethod + #def INPUT_TYPES(s): + # return {"required": { "vae_name": (filter_files_extensions(recursive_search(s.vae_dir), supported_pt_extensions), )}} + + @classmethod + def INPUT_TYPES(s): + + return { + "required": { + "samples": ("LATENT", ), + "vae_name": (folder_paths.get_filename_list("vae"), ) + } + } + + RETURN_TYPES = ("IMAGE",) + + FUNCTION = "test" + + CATEGORY = "latent" + + #TODO: scale factor? + #def load_vae(self, vae_name): + # vae_path = os.path.join(self.vae_dir, vae_name) + # vae = comfy.sd.VAE(ckpt_path=vae_path) + # return (vae,) + # + #def decode(self, vae, samples): + # return (vae.decode(samples["samples"]), ) + + def test(self, vae_name, samples): + + t=folder_paths.get_filename_list("vae") + print(f"VAELoaderDecode : {t}") + vae_path = folder_paths.get_full_path("vae", vae_name) + print(f"VAELoaderDecode : {vae_path}") + vae = comfy.sd.VAE(ckpt_path=vae_path) + return (vae.decode(samples["samples"]), ) + +#NODE_CLASS_MAPPINGS = { +# "VAELoaderDecode": VAELoaderDecode, +#} \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..339befb --- /dev/null +++ b/__init__.py @@ -0,0 +1,24 @@ +print("ComfyUI_node_Lilly.__init__") + +import importlib +#from custom_nodes.ComfyUI_node_Lilly import eval(f"{name}") + +NODE_CLASS_MAPPINGS = { +} + +def add(name): + print(f"Load : {name}") + try: + pkg = importlib.import_module(f"custom_nodes.ComfyUI_node_Lilly.{name}") + NODE_CLASS_MAPPINGS[name]=eval(f"pkg.{name}") + print(f"ok : {name}") + except Exception as e: + print(f"Exception : {e}") + +add("CheckpointLoaderRandom") +add("CheckpointLoaderSimpleText") +add("CLIPTextEncodeWildcards") +add("LoraLoaderText") +add("LoraLoaderTextRandom") +add("Random_Sampler") +add("VAELoaderDecode") \ No newline at end of file diff --git a/check_name.py b/check_name.py new file mode 100644 index 0000000..11f5a20 --- /dev/null +++ b/check_name.py @@ -0,0 +1,25 @@ +import os +from folder_paths import supported_ckpt_extensions,supported_pt_extensions +import folder_paths +import random + +def check_name(kind,name,supported_extensions): + for ext in supported_extensions: + if name.lower().endswith(ext): + path = folder_paths.get_full_path(kind, name) + if path is not None: + return path + + for ext in supported_extensions: + path = folder_paths.get_full_path(kind, name+ext) + if path is not None: + return path + +def check_name_ckpt(name): + return check_name("checkpoints",name,supported_ckpt_extensions) + +def check_name_pt(kind,name): + return check_name(kind,name,supported_pt_extensions) + +def name_split_choice(name): + return random.choice(name.split('|')) \ No newline at end of file diff --git a/wildcards.py b/wildcards.py new file mode 100644 index 0000000..a00c8c3 --- /dev/null +++ b/wildcards.py @@ -0,0 +1,109 @@ +import os, glob, sys +import random +import re +#import wildcards + +print("wildcards") +#print(os.getcwd()) + +# ============================================================ + +# 가져올 파일 목록 +card_path=os.path.dirname(__file__)+"\\wildcards\\**\\*.txt" +print(f"wildcards card_path {card_path}") + +# 정규식 +resub = re.compile(r"(\{)([^\{\}]*)(\})") +recard = re.compile(r"(__)(.*?)(__)") + +# 카드 목록 +cards = {} + +# | 로 입력된것중 하나 가져오기 +def sub(match): + m=match.group(2).split("|") + #print(f"sub : {m}") + return random.choice(m) + +# | 로 입력된것중 하나 가져오기 반복 +def sub_loop(text): + bak=text + for i in range(1, 50): + tmp=resub.sub(sub, bak) + if bak==tmp : + return tmp + bak=tmp + return bak + +# 카드 중에서 가져오기 +def card(match): + #print(f"card i : {match.group(2)}") + if match.group(2) in cards : + r=random.choice(cards[match.group(2)]) + else : + r= match.group(2) + #print(f"card r : {r}") + return r + + +# 카드 중에서 가져오기 반복. | 의 것도 처리 +def card_loop(text): + bak=text + for i in range(1, 50): + tmp=recard.sub(card, bak) + #print(f"card l : {bak}") + if bak==tmp : + tmp=sub_loop(tmp) + + if bak==tmp : + #print(f"card le : {bak}") + return tmp + bak=tmp + #print(f"card le : {bak}") + return bak + +# 카드 파일 읽기 +def card_load(): + global cards , card_path + cards = {} + #print(f"path : {path}") + files=glob.glob(card_path, recursive=True) + #print(f"files : {files}") + + for file in files: + basename = os.path.basename(file) + file_name = os.path.splitext(basename)[0] + if not file_name in cards: + cards[file_name]=[] + #print(f"file_name : {file_name}") + f = open(file, 'r', encoding='UTF8') + lines = f.readlines() + for line in lines: + line=line.strip() + # 주석 빈줄 제외 + if line.startswith("#") or len(line)==0: + continue + cards[file_name]+=[line] + #print(f"line : {line}") + print(f"cards : {len(cards)}") + #print(f"cards : {cards.keys()}") + +# 실행기 +def run(text): + + card_load() + + #print(f"text : {text}") + result=card_loop(text) + #print(f"result : {result}") + return result + +# ============================================================ + +# 테스트용 +test="__my__" + +#m = p.sub(sub, test) +#print(m) + +run(test)