mirror of
https://github.com/justUmen/Bjornulf_custom_nodes.git
synced 2026-09-20 18:31:27 -03:00
first commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
class CustomStringType:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"value": ("STRING", {"multiline": True})}}
|
||||
|
||||
RETURN_TYPES = ("CUSTOM_STRING",)
|
||||
FUNCTION = "passthrough"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def passthrough(self, value):
|
||||
return (value,)
|
||||
@@ -0,0 +1 @@
|
||||
saluts
|
||||
@@ -0,0 +1 @@
|
||||
encule
|
||||
@@ -0,0 +1 @@
|
||||
connard
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
from .create_video import imgs2vid
|
||||
from .write_text import WriteText
|
||||
from .write_image_environment import WriteImageEnvironment
|
||||
from .write_image_characters import WriteImageCharacters
|
||||
from .write_image_character import WriteImageCharacter
|
||||
from .write_image_allinone import WriteImageAllInOne
|
||||
from .combine_texts import CombineTexts
|
||||
from .loop_texts import LoopTexts
|
||||
from .random_texts import RandomTexts
|
||||
from .random_model_clip_vae import RandomModelClipVae
|
||||
from .video_pingpong import VideoPingPong
|
||||
from .loop_float import LoopFloat
|
||||
from .loop_integer import LoopInteger
|
||||
from .loop_basic_batch import LoopBasicBatch
|
||||
from .loop_samplers import LoopSamplers
|
||||
from .loop_schedulers import LoopSchedulers
|
||||
from .ollama import ollamaLoader
|
||||
from .show_text import ShowText
|
||||
from .show_int import ShowInt
|
||||
from .show_float import ShowFloat
|
||||
from .save_text import SaveText
|
||||
from .save_tmp_image import SaveTmpImage
|
||||
from .save_api_image import SaveApiImage
|
||||
from .loop_my_combos_samplers_schedulers import LoopCombosSamplersSchedulers
|
||||
|
||||
# from .CUSTOM_STRING import CustomStringType
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
# "Bjornulf_CustomStringType": CustomStringType,
|
||||
"Bjornulf_ollamaLoader": ollamaLoader,
|
||||
"Bjornulf_WriteText": WriteText,
|
||||
# "Bjornulf_WriteImageEnvironment": WriteImageEnvironment,
|
||||
# "Bjornulf_WriteImageCharacters": WriteImageCharacters,
|
||||
# "Bjornulf_WriteImageCharacter": WriteImageCharacter,
|
||||
# "Bjornulf_WriteImageAllInOne": WriteImageAllInOne,
|
||||
"Bjornulf_ShowText": ShowText,
|
||||
"Bjornulf_ShowInt": ShowInt,
|
||||
"Bjornulf_ShowFloat": ShowFloat,
|
||||
"Bjornulf_SaveText": SaveText,
|
||||
"Bjornulf_SaveTmpImage": SaveTmpImage,
|
||||
"Bjornulf_SaveApiImage": SaveApiImage,
|
||||
"Bjornulf_CombineTexts": CombineTexts,
|
||||
"Bjornulf_LoopTexts": LoopTexts,
|
||||
"Bjornulf_RandomTexts": RandomTexts,
|
||||
"Bjornulf_RandomModelClipVae": RandomModelClipVae,
|
||||
"Bjornulf_imgs2vid": imgs2vid,
|
||||
"Bjornulf_VideoPingPong": VideoPingPong,
|
||||
"Bjornulf_LoopFloat": LoopFloat,
|
||||
"Bjornulf_LoopInteger": LoopInteger,
|
||||
"Bjornulf_LoopBasicBatch": LoopBasicBatch,
|
||||
"Bjornulf_LoopSamplers": LoopSamplers,
|
||||
"Bjornulf_LoopSchedulers": LoopSchedulers,
|
||||
"Bjornulf_LoopCombosSamplersSchedulers": LoopCombosSamplersSchedulers,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
# "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!",
|
||||
"Bjornulf_ollamaLoader": "🦙 Ollama (Description)",
|
||||
"Bjornulf_ShowText": "👁 Show (Text)",
|
||||
"Bjornulf_ShowInt": "👁 Show (Int)",
|
||||
"Bjornulf_ShowFloat": "👁 Show (Float)",
|
||||
"Bjornulf_SaveTmpImage": "🖼 Save Image (tmp_api.png)",
|
||||
"Bjornulf_SaveApiImage": "🖼 Save Image (API_IMAGES/00001.png...)",
|
||||
"Bjornulf_SaveText": "💾 Save Text", #Make SaveCharacter, SaveLocation, SaveCamera, SaveAction, SaveClothes, SaveEmotion...
|
||||
"Bjornulf_LoadText": "📥 Load Text", #Make LoadCharacter, LoadLocation, LoadCamera, LoadAction, LoadClothes, LoadEmotion...
|
||||
"Bjornulf_WriteText": "✒ Write Text",
|
||||
# "Bjornulf_WriteImageEnvironment": "✒ Write Image Environment",
|
||||
# "Bjornulf_WriteImageCharacters": "✒ Write Image Characters",
|
||||
# "Bjornulf_WriteImageCharacter": "✒ Write Image Character",
|
||||
# "Bjornulf_WriteImageAllInOne": "✒ Write Image All-in-one",
|
||||
"Bjornulf_CombineTexts": "🔗 Combine (Texts)",
|
||||
"Bjornulf_LoopTexts": "♻ Loop (Texts)",
|
||||
"Bjornulf_RandomTexts": "🎲 Random (Texts)",
|
||||
"Bjornulf_RandomModelClipVae": "🎲 Random (Model+Clip+Vae)",
|
||||
"Bjornulf_imgs2vid": "📹 imgs2vid (FFmpeg)",
|
||||
"Bjornulf_VideoPingPong": "📹 video PingPong",
|
||||
"Bjornulf_LoopFloat": "♻ Loop (Float)",
|
||||
"Bjornulf_LoopInteger": "♻ Loop (Integer)",
|
||||
"Bjornulf_LoopBasicBatch": "♻ Loop",
|
||||
"Bjornulf_LoopSamplers": "♻ Loop (All Samplers)",
|
||||
"Bjornulf_LoopSchedulers": "♻ Loop (All Schedulers)",
|
||||
"Bjornulf_LoopCombosSamplersSchedulers": "♻ Loop (My combos Sampler⚔Scheduler)",
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
class CombineTexts:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 10, "step": 1}),
|
||||
"delimiter": (["newline", "comma", "space", "test"], {"default": "newline"}),
|
||||
"text_1": ("STRING", {"forceInput": True}),
|
||||
"text_2": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
"hidden": {
|
||||
**{f"text_{i}": ("STRING", {"forceInput": True}) for i in range(3, 11)}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "combine_texts"
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def combine_texts(self, number_of_inputs, delimiter, **kwargs):
|
||||
def flatten(item):
|
||||
if isinstance(item, str):
|
||||
return item
|
||||
elif isinstance(item, list):
|
||||
return self.get_delimiter(delimiter).join(map(flatten, item))
|
||||
else:
|
||||
return str(item)
|
||||
|
||||
combined_text = self.get_delimiter(delimiter).join([
|
||||
flatten(kwargs[f"text_{i}"])
|
||||
for i in range(1, number_of_inputs + 1)
|
||||
if f"text_{i}" in kwargs
|
||||
])
|
||||
return (combined_text,)
|
||||
|
||||
@staticmethod
|
||||
def get_delimiter(delimiter):
|
||||
if delimiter == "newline":
|
||||
return "\n"
|
||||
elif delimiter == "comma":
|
||||
return ","
|
||||
elif delimiter == "space":
|
||||
return " "
|
||||
else:
|
||||
return "\n"
|
||||
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import torch
|
||||
import subprocess
|
||||
from PIL import Image
|
||||
|
||||
class imgs2vid:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"fps": ("INT", {"default": 30, "min": 1, "max": 60}),
|
||||
"video_name_NO_format": ("STRING", {"default": "output"}),
|
||||
"format": (["mp4", "webm"],),
|
||||
"audio_path": ("STRING", {"default": "/home/umen/6sec.wav"}), # New audio input
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("comment",)
|
||||
FUNCTION = "create_video"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def create_video(self, images, fps, video_name_NO_format, format, audio_path):
|
||||
# Remove any existing extension
|
||||
video_name_NO_format = os.path.splitext(video_name_NO_format)[0]
|
||||
# Add the correct extension
|
||||
output_file = f"{video_name_NO_format}.{format}"
|
||||
temp_dir = "temp_images"
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
# Ensure the output directory exists
|
||||
os.makedirs(os.path.dirname(output_file) if os.path.dirname(output_file) else ".", exist_ok=True)
|
||||
|
||||
# Save the tensor images as PNG files
|
||||
for i, img_tensor in enumerate(images):
|
||||
img = Image.fromarray((img_tensor.cpu().numpy() * 255).astype(np.uint8))
|
||||
if format == "webm":
|
||||
img = img.convert("RGBA") # Ensure alpha channel for WebM
|
||||
img.save(os.path.join(temp_dir, f"frame_{i:04d}.png"))
|
||||
|
||||
# Construct the FFmpeg command based on the selected format
|
||||
if format == "mp4":
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-framerate", str(fps),
|
||||
"-i", os.path.join(temp_dir, "frame_%04d.png"),
|
||||
"-i", str(audio_path),
|
||||
"-crf", "19",
|
||||
"-c:v", "libx264",
|
||||
"-pix_fmt", "yuv420p",
|
||||
output_file
|
||||
]
|
||||
comment = "MP4 format: Widely compatible, efficient compression, no transparency support."
|
||||
elif format == "webm":
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-framerate", str(fps),
|
||||
"-i", os.path.join(temp_dir, "frame_%04d.png"),
|
||||
"-i", str(audio_path),
|
||||
"-crf", "19",
|
||||
"-c:v", "libvpx",
|
||||
"-b:v", "1M", # Set video bitrate
|
||||
"-auto-alt-ref", "0", # Disable auto alt ref
|
||||
"-c:a", "libvorbis",
|
||||
"-pix_fmt", "yuva420p",
|
||||
"-shortest",
|
||||
output_file
|
||||
]
|
||||
comment = "WebM format: Supports transparency, open format, smaller file size, but less compatible than MP4."
|
||||
|
||||
# Run FFmpeg
|
||||
try:
|
||||
subprocess.run(ffmpeg_cmd, check=True)
|
||||
print(f"Video created successfully: {output_file}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error creating video: {e}")
|
||||
finally:
|
||||
# Clean up temporary files
|
||||
for file in os.listdir(temp_dir):
|
||||
os.remove(os.path.join(temp_dir, file))
|
||||
os.rmdir(temp_dir)
|
||||
|
||||
return (comment,)
|
||||
|
||||
# Example usage
|
||||
# images = [torch.rand(256, 256, 3) for _ in range(10)] # Replace with actual image tensors
|
||||
# imgs2vid().create_video(images, 30, "output", "webm", "/home/
|
||||
@@ -0,0 +1,21 @@
|
||||
class LoopBasicBatch:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"nb_loops": ("INT", {"default": 1, "min": 0, "max": 1000, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
FUNCTION = "create_loop_basic_batch"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def create_loop_basic_batch(self, nb_loops):
|
||||
range_values = list()
|
||||
while nb_loops > 0:
|
||||
range_values.append(1)
|
||||
nb_loops -= 1
|
||||
return (range_values,)
|
||||
@@ -0,0 +1,24 @@
|
||||
class LoopFloat:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"from_this": ("FLOAT", {"default": 0.00, "min": 0.00, "max": 1000.00, "step": 0.01}),
|
||||
"to_that": ("FLOAT", {"default": 10.00, "min": 0.00, "max": 1000.00, "step": 0.01}),
|
||||
"jump": ("FLOAT", {"default": 1.00, "min": 0.00, "max": 1000.00, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
FUNCTION = "create_loop_float"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def create_loop_float(self, from_this, to_that, jump):
|
||||
range_values = []
|
||||
current_value = from_this
|
||||
while current_value <= to_that:
|
||||
range_values.append(round(current_value, 2)) # Round to two decimal places
|
||||
current_value += jump
|
||||
return (range_values,)
|
||||
@@ -0,0 +1,32 @@
|
||||
# class AnyType(str):
|
||||
# def __ne__(self, __value: object) -> bool:
|
||||
# return False
|
||||
# any_type = AnyType("*")
|
||||
|
||||
class LoopInteger:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"from_this": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}),
|
||||
"to_that": ("INT", {"default": 10, "min": 0, "max": 1000, "step": 1}),
|
||||
"jump": ("INT", {"default": 1, "min": 0, "max": 1000, "step": 1}),
|
||||
},
|
||||
# "optional": {
|
||||
# "nb_loops": (any_type,)
|
||||
# }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
FUNCTION = "create_loop_integer"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def create_loop_integer(self, from_this, to_that, jump):
|
||||
range_values = list()
|
||||
current_value = from_this
|
||||
while current_value <= to_that:
|
||||
range_values.append(current_value)
|
||||
current_value += jump
|
||||
return (range_values,)
|
||||
@@ -0,0 +1,82 @@
|
||||
import comfy
|
||||
|
||||
class LoopCombosSamplersSchedulers:
|
||||
combinations = [
|
||||
"sgm_uniform/euler", "sgm_uniform/dpm_2", "sgm_uniform/dpmpp_2m", "sgm_uniform/lcm",
|
||||
"sgm_uniform/ddim", "sgm_uniform/uni_pc",
|
||||
|
||||
"normal/ddim", "normal/uni_pc", "normal/euler", "normal/heunpp2", "normal/dpm_2",
|
||||
|
||||
"ddim_uniform/euler", "ddim_uniform/dpm_2", "ddim_uniform/lcm", "ddim_uniform/uni_pc",
|
||||
|
||||
"simple/euler", "simple/heun", "simple/heunpp2", "simple/dpmpp_2m", "simple/lcm", "simple/ipndm", "simple/uni_pc",
|
||||
|
||||
"exponential/dpm_adaptive"
|
||||
]
|
||||
# "normal/uni_pc_bh2", "ddim_uniform/uni_pc_bh2", "simple/uni_pc_bh2", "sgm_uniform/uni_pc_bh2"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
# Generate the list of combinations from specified pairs
|
||||
combi_list = ["ALL 6 COMBINATIONS (sgm_uniform)", "ALL 5 COMBINATIONS (normal)", "ALL 5 COMBINATIONS (ddim_uniform)", "ALL 7 COMBINATIONS (simple)"] + cls.combinations
|
||||
return {
|
||||
"required": {
|
||||
"combination": (combi_list,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (comfy.samplers.KSampler.SAMPLERS, comfy.samplers.KSampler.SCHEDULERS,)
|
||||
RETURN_NAMES = ("sampler_name", "scheduler",)
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
FUNCTION = "create_loop_combination"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def create_loop_combination(self, combination):
|
||||
if combination == "ALL 6 COMBINATIONS (sgm_uniform)":
|
||||
return (["euler", "dpm_2", "dpmpp_2m", "lcm", "ddim", "uni_pc"], "sgm_uniform",) #, "uni_pc_bh2" uni_pc_bh2 is too similar to exist....
|
||||
elif combination == "ALL 5 COMBINATIONS (normal)":
|
||||
return (["ddim", "uni_pc", "euler", "heunpp2", "dpm_2"], "normal",) #, "uni_pc_bh2"
|
||||
elif combination == "ALL 5 COMBINATIONS (ddim_uniform)":
|
||||
return (["euler", "dpm_2", "lcm", "ddim", "uni_pc"], "ddim_uniform",) #, "uni_pc_bh2"
|
||||
elif combination == "ALL 7 COMBINATIONS (simple)":
|
||||
return (["euler", "heun", "heunpp2", "dpmpp_2m", "lcm", "ipndm", "uni_pc"], "simple",) #, "uni_pc_bh2"
|
||||
else:
|
||||
# Split the input and output the selected sampler and scheduler
|
||||
scheduler, sampler = combination.split("/")
|
||||
# return [(sampler, scheduler,)]
|
||||
return ([sampler], scheduler,)
|
||||
|
||||
# + ("exponential", "dpm_adaptive")
|
||||
|
||||
# TESTED GOOD WITH SD3, modelsampling 5 / CFG 3 / 28 steps
|
||||
|
||||
# sgm_uniform + euler
|
||||
# sgm_uniform + dpm_2
|
||||
# sgm_uniform + dpmpp_2m
|
||||
# sgm_uniform + lcm
|
||||
# sgm_uniform + ddim
|
||||
# sgm_uniform + uni_pc
|
||||
# sgm_uniform + uni_pc_bh2
|
||||
|
||||
# normal + ddim
|
||||
# normal + uni_pc
|
||||
# normal + uni_pc_bh2
|
||||
# normal + euler
|
||||
# normal + heunpp2
|
||||
# normal + dpm_2
|
||||
|
||||
# ddim_uniform + euler
|
||||
# ddim_uniform + dpm_2
|
||||
# ddim_uniform + lcm
|
||||
# ddim_uniform + uni_pc
|
||||
# ddim_uniform + uni_pc_bh2
|
||||
|
||||
# simple + euler
|
||||
# simple + heun
|
||||
# simple + heunpp2
|
||||
# simple + dpmpp_2m
|
||||
# simple + lcm
|
||||
# simple + ipndm
|
||||
# simple + uni_pc
|
||||
# simple + uni_pc_bh2
|
||||
|
||||
# exponential + dpm_adaptive
|
||||
@@ -0,0 +1,23 @@
|
||||
import comfy
|
||||
|
||||
class LoopSamplers:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
samplers = ["ALL SAMPLERS"] + list(comfy.samplers.KSampler.SAMPLERS)
|
||||
return {
|
||||
"required": {
|
||||
"sampler_name": (samplers,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (comfy.samplers.KSampler.SAMPLERS,)
|
||||
RETURN_NAMES = ("sampler_name",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "create_loop_sampler"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def create_loop_sampler(self, sampler_name):
|
||||
if sampler_name == "ALL SAMPLERS":
|
||||
return (list(comfy.samplers.KSampler.SAMPLERS),)
|
||||
else:
|
||||
return ([sampler_name],)
|
||||
@@ -0,0 +1,23 @@
|
||||
import comfy
|
||||
|
||||
class LoopSchedulers:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
schedulers = ["ALL SCHEDULERS"] + list(comfy.samplers.KSampler.SCHEDULERS)
|
||||
return {
|
||||
"required": {
|
||||
"scheduler": (schedulers,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (comfy.samplers.KSampler.SCHEDULERS,)
|
||||
RETURN_NAMES = ("scheduler",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "create_loop_scheduler"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def create_loop_scheduler(self, scheduler):
|
||||
if scheduler == "ALL SCHEDULERS":
|
||||
return (list(comfy.samplers.KSampler.SCHEDULERS),)
|
||||
else:
|
||||
return ([scheduler],)
|
||||
@@ -0,0 +1,22 @@
|
||||
class LoopTexts:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 10, "step": 1}),
|
||||
"text_1": ("STRING", {"forceInput": "True"}),
|
||||
"text_2": ("STRING", {"forceInput": "True"}),
|
||||
},
|
||||
"hidden": {
|
||||
**{f"text_{i}": ("STRING", {"forceInput": "True"}) for i in range(3, 11)}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "loop_texts"
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def loop_texts(self, number_of_inputs, **kwargs):
|
||||
text_list = [kwargs[f"text_{i}"] for i in range(1, number_of_inputs + 1) if f"text_{i}" in kwargs]
|
||||
return (text_list,)
|
||||
@@ -0,0 +1,28 @@
|
||||
import ollama
|
||||
from ollama import Client # pip install ollama
|
||||
|
||||
class ollamaLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"user_prompt": ("STRING", {"multiline": True}),
|
||||
# "selected_model": ((), {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("ollama_response",)
|
||||
FUNCTION = "connect_2_ollama"
|
||||
# INPUT_NODE = True # Changed from OUTPUT_NODE to INPUT_NODE
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
# @classmethod
|
||||
def connect_2_ollama(self, user_prompt):
|
||||
keep_alive = 0
|
||||
list_models=ollama.list() #{'models': [{'name': 'dolphin-llama3:latest', 'model': 'dolphin-llama3:latest', 'modified_at': '2024-04-24T06:56:57.498527412+02:00', 'size': 4661235994, 'digest': '613f068e29f863bb900e568f920401b42678efca873d7a7c87b0d6ef4945fadd', 'details': {'parent_model': '', 'format': 'gguf', 'family': 'llama', 'families': ['llama'], 'parameter_size': '8B', 'quantization_level': 'Q4_0'}}]}
|
||||
print(list_models)
|
||||
client = Client(host="http://127.0.0.1:11434")
|
||||
response = client.generate(model="dolphin-llama3", system="I will give you an object, animal, person or landscape, just create details about it : colors, size, clothes, eyes and other physical details or features in 1 sentence.", prompt=user_prompt, keep_alive=str(keep_alive) + "m")
|
||||
print("Ollama response : ", response['response'])
|
||||
return (response['response'],)
|
||||
@@ -0,0 +1,28 @@
|
||||
import random
|
||||
|
||||
class RandomCheckpoint:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 10, "step": 1}),
|
||||
"model_1": ("MODEL", {"forceInput": True}),
|
||||
"clip_1": ("CLIP", {"forceInput": True}),
|
||||
"vae_1": ("VAE", {"forceInput": True}),
|
||||
"model_2": ("MODEL", {"forceInput": True}),
|
||||
"clip_2": ("CLIP", {"forceInput": True}),
|
||||
"vae_2": ("VAE", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
|
||||
FUNCTION = "random_select"
|
||||
|
||||
def random_select(self, number_of_inputs, **kwargs):
|
||||
selected_index = random.randint(1, number_of_inputs)
|
||||
|
||||
selected_model = kwargs[f"model_{selected_index}"]
|
||||
selected_clip = kwargs[f"clip_{selected_index}"]
|
||||
selected_vae = kwargs[f"vae_{selected_index}"]
|
||||
|
||||
return (selected_model, selected_clip, selected_vae)
|
||||
@@ -0,0 +1,33 @@
|
||||
import random
|
||||
|
||||
class RandomModelClipVae:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 10, "step": 1}),
|
||||
"model_1": ("MODEL", {"forceInput": True}),
|
||||
"clip_1": ("CLIP", {"forceInput": True}),
|
||||
"vae_1": ("VAE", {"forceInput": True}),
|
||||
"model_2": ("MODEL", {"forceInput": True}),
|
||||
"clip_2": ("CLIP", {"forceInput": True}),
|
||||
"vae_2": ("VAE", {"forceInput": True}),
|
||||
},
|
||||
"hidden": {
|
||||
**{f"model_{i}": ("MODEL", {"forceInput": True}) for i in range(3, 11)},
|
||||
**{f"clip_{i}": ("CLIP", {"forceInput": True}) for i in range(3, 11)},
|
||||
**{f"vae_{i}": ("VAE", {"forceInput": True}) for i in range(3, 11)}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP", "VAE")
|
||||
FUNCTION = "random_select"
|
||||
|
||||
def random_select(self, number_of_inputs, **kwargs):
|
||||
selected_index = random.randint(1, number_of_inputs)
|
||||
|
||||
selected_model = kwargs[f"model_{selected_index}"]
|
||||
selected_clip = kwargs[f"clip_{selected_index}"]
|
||||
selected_vae = kwargs[f"vae_{selected_index}"]
|
||||
|
||||
return (selected_model, selected_clip, selected_vae)
|
||||
@@ -0,0 +1,27 @@
|
||||
import random
|
||||
|
||||
class RandomTexts:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"number_of_inputs": ("INT", {"default": 2, "min": 2, "max": 30, "step": 1}),
|
||||
"number_of_random": ("INT", {"default": 1, "min": 1, "max": 30, "step": 1}),
|
||||
"text_1": ("STRING", {"forceInput": "True"}),
|
||||
"text_2": ("STRING", {"forceInput": "True"}),
|
||||
"seed": ("INT", {"default": "1"}), #Used with control_after_generate,
|
||||
},
|
||||
"hidden": {
|
||||
**{f"text_{i}": ("STRING", {"forceInput": "True"}) for i in range(3, 31)}
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "random_texts"
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def random_texts(self, number_of_inputs, number_of_random, **kwargs):
|
||||
texts = [kwargs[f"text_{i}"] for i in range(1, number_of_inputs + 1) if f"text_{i}" in kwargs]
|
||||
random_texts = random.sample(texts, min(number_of_random, len(texts)))
|
||||
return (random_texts,)
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
class SaveApiImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION = "save_api_image"
|
||||
RETURN_TYPES = ()
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def save_api_image(self, image):
|
||||
# Ensure the output directory exists
|
||||
os.makedirs("./output/", exist_ok=True)
|
||||
|
||||
# Convert the image from ComfyUI format to PIL Image
|
||||
i = 255. * image.cpu().numpy()
|
||||
if i.ndim > 3:
|
||||
i = np.squeeze(i)
|
||||
if i.ndim == 2:
|
||||
i = i[:, :, np.newaxis]
|
||||
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
|
||||
# Determine the next available filename
|
||||
counter = 1
|
||||
while True:
|
||||
filename = f"./output/api_{counter:05d}.png"
|
||||
if not os.path.exists(filename):
|
||||
break
|
||||
counter += 1
|
||||
|
||||
# Save the image with the determined filename
|
||||
img.save(filename, format="PNG")
|
||||
|
||||
# Write the number of the last image to a text file with leading zeroes
|
||||
with open("./output/api_next_image.txt", "w") as f:
|
||||
f.write(f"api_{counter+1:05d}.png")
|
||||
|
||||
return ()
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
|
||||
class SaveText:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "forceInput": True}),
|
||||
"filename": ("STRING", {"default": "001.txt"})
|
||||
}
|
||||
}
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "save_text"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def save_text(self, text, filename):
|
||||
directory = "custom_nodes/Bjornulf_custom_nodes/SaveText/"
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
base, ext = os.path.splitext(filename)
|
||||
counter = 1
|
||||
new_filename = os.path.join(directory, filename)
|
||||
|
||||
while os.path.exists(new_filename):
|
||||
new_filename = os.path.join(directory, f"{base}_{counter:03d}{ext}")
|
||||
counter += 1
|
||||
|
||||
with open(new_filename, 'w') as file:
|
||||
file.write(text)
|
||||
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
class SaveTmpImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION = "save_image"
|
||||
RETURN_TYPES = ()
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def save_image(self, image):
|
||||
# Ensure the output directory exists
|
||||
# os.makedirs("./output", exist_ok=True)
|
||||
|
||||
# Convert the image from ComfyUI format to PIL Image
|
||||
# Assuming the first two dimensions are extra, and we need to keep the last two
|
||||
i = 255. * image.cpu().numpy()
|
||||
# Reshape the image if it's not in the expected format, remove any leading dimensions of size 1
|
||||
if i.ndim > 3:
|
||||
i = np.squeeze(i)
|
||||
# Ensure the image is 3D (height, width, channels)
|
||||
if i.ndim == 2:
|
||||
i = i[:, :, np.newaxis] # Add a channel dimension if it's missing
|
||||
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
|
||||
# Save the image, overwriting if it exists
|
||||
img.save("./output/tmp_api.png", format="PNG")
|
||||
|
||||
return ()
|
||||
@@ -0,0 +1,18 @@
|
||||
class ShowFloat:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"float_value": ("FLOAT", {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "show_float"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = (True,)
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def show_float(self, float_value):
|
||||
return {"ui": {"text": float_value}}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
class ShowInt:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"int_value": ("INT", {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "show_int"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = (True,)
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def show_int(self, int_value):
|
||||
return {"ui": {"text": int_value}}
|
||||
@@ -0,0 +1,18 @@
|
||||
class ShowText:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text_value": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "show_text"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = (True,)
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def show_text(self, text_value):
|
||||
return {"ui": {"text": text_value}}
|
||||
@@ -0,0 +1,23 @@
|
||||
import torch
|
||||
|
||||
class VideoPingPong:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "pingpong_images"
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def pingpong_images(self, images):
|
||||
if isinstance(images, torch.Tensor):
|
||||
reversed_images = torch.flip(images, [0])
|
||||
combined_images = torch.cat((images, reversed_images[1:]), dim=0)
|
||||
else:
|
||||
reversed_images = images[::-1]
|
||||
combined_images = images + reversed_images[1:]
|
||||
return (combined_images,)
|
||||
@@ -0,0 +1,29 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.CustomBjornulfType",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "Bjornulf_WriteImageCharacters") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
onNodeCreated?.apply(this, arguments);
|
||||
const myInput = this.inputs.find(input => input.name === "BJORNULF_CHARACTER");
|
||||
if (myInput) {
|
||||
myInput.type = "BJORNULF_CHARACTER";
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (nodeData.name === "Bjornulf_WriteImageCharacter") {
|
||||
|
||||
}
|
||||
},
|
||||
async setup(app) {
|
||||
app.registerCustomNodeType("BJORNULF_CHARACTER", (value) => {
|
||||
return {
|
||||
type: "BJORNULF_CHARACTER",
|
||||
data: { value: value || "" },
|
||||
name: "BJORNULF_CHARACTER"
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.CustomStringType",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "Bjornulf_WriteImageAllInOne") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
onNodeCreated?.apply(this, arguments);
|
||||
const locationInput = this.inputs.find(input => input.name === "location");
|
||||
if (locationInput) {
|
||||
locationInput.type = "CUSTOM_STRING";
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
async setup(app) {
|
||||
app.registerCustomNodeType("CUSTOM_STRING", (value) => {
|
||||
return {
|
||||
type: "CustomStringType",
|
||||
data: { value: value || "" },
|
||||
name: "CustomStringType"
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Override the default onConnectionCreated method
|
||||
const originalOnConnectionCreated = LGraphCanvas.prototype.onConnectionCreated;
|
||||
LGraphCanvas.prototype.onConnectionCreated = function(connection, e, node_for_click) {
|
||||
if (node_for_click && node_for_click.type === "WriteImageAllInOne" && connection.targetInput.name === "location") {
|
||||
// Check if the connected node is not already a CustomString
|
||||
if (connection.origin_node.type !== "CustomString") {
|
||||
// Create a new CustomString node
|
||||
const customStringNode = LiteGraph.createNode("CustomString");
|
||||
// Position the new node
|
||||
customStringNode.pos = [connection.origin_node.pos[0] + 200, connection.origin_node.pos[1]];
|
||||
this.graph.add(customStringNode);
|
||||
|
||||
// Connect the new CustomString node
|
||||
connection.origin_node.connect(connection.origin_slot, customStringNode, 0);
|
||||
customStringNode.connect(0, node_for_click, connection.target_slot);
|
||||
|
||||
// Remove the original connection
|
||||
connection.origin_node.disconnectOutput(connection.origin_slot, node_for_click);
|
||||
|
||||
return true; // Prevent the original connection
|
||||
}
|
||||
}
|
||||
return originalOnConnectionCreated.apply(this, arguments);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.CombineTexts",
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass === "Bjornulf_CombineTexts") {
|
||||
const updateInputs = () => {
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (!numInputsWidget) return;
|
||||
|
||||
const numInputs = numInputsWidget.value;
|
||||
|
||||
// Initialize node.inputs if it doesn't exist
|
||||
if (!node.inputs) {
|
||||
node.inputs = [];
|
||||
}
|
||||
|
||||
// Filter existing text inputs
|
||||
const existingInputs = node.inputs.filter(input => input.name.startsWith('text_'));
|
||||
|
||||
// Determine if we need to add or remove inputs
|
||||
if (existingInputs.length < numInputs) {
|
||||
// Add new text inputs if not enough existing
|
||||
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
|
||||
const inputName = `text_${i}`;
|
||||
if (!node.inputs.find(input => input.name === inputName)) {
|
||||
node.addInput(inputName, "STRING");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove excess text inputs if too many
|
||||
node.inputs = node.inputs.filter(input => !input.name.startsWith('text_') || parseInt(input.name.split('_')[1]) <= numInputs);
|
||||
}
|
||||
|
||||
node.setSize(node.computeSize());
|
||||
};
|
||||
|
||||
// Move number_of_inputs to the top initially
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (numInputsWidget) {
|
||||
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
|
||||
numInputsWidget.callback = () => {
|
||||
updateInputs();
|
||||
app.graph.setDirtyCanvas(true);
|
||||
};
|
||||
}
|
||||
|
||||
// Delay the initial update to ensure node is fully initialized
|
||||
setTimeout(updateInputs, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.LoopTexts",
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass === "Bjornulf_LoopTexts") {
|
||||
const updateInputs = () => {
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (!numInputsWidget) return;
|
||||
|
||||
const numInputs = numInputsWidget.value;
|
||||
|
||||
// Initialize node.inputs if it doesn't exist
|
||||
if (!node.inputs) {
|
||||
node.inputs = [];
|
||||
}
|
||||
|
||||
// Filter existing text inputs
|
||||
const existingInputs = node.inputs.filter(input => input.name.startsWith('text_'));
|
||||
|
||||
// Determine if we need to add or remove inputs
|
||||
if (existingInputs.length < numInputs) {
|
||||
// Add new text inputs if not enough existing
|
||||
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
|
||||
const inputName = `text_${i}`;
|
||||
if (!node.inputs.find(input => input.name === inputName)) {
|
||||
node.addInput(inputName, "STRING");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove excess text inputs if too many
|
||||
node.inputs = node.inputs.filter(input => !input.name.startsWith('text_') || parseInt(input.name.split('_')[1]) <= numInputs);
|
||||
}
|
||||
|
||||
node.setSize(node.computeSize());
|
||||
};
|
||||
|
||||
// Move number_of_inputs to the top initially
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (numInputsWidget) {
|
||||
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
|
||||
numInputsWidget.callback = () => {
|
||||
updateInputs();
|
||||
app.graph.setDirtyCanvas(true);
|
||||
};
|
||||
}
|
||||
|
||||
// Delay the initial update to ensure node is fully initialized
|
||||
setTimeout(updateInputs, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.RandomModelClipVae",
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass === "Bjornulf_RandomModelClipVae") {
|
||||
const updateInputs = () => {
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (!numInputsWidget) return;
|
||||
|
||||
const numInputs = numInputsWidget.value;
|
||||
|
||||
// Initialize node.inputs if it doesn't exist
|
||||
if (!node.inputs) {
|
||||
node.inputs = [];
|
||||
}
|
||||
|
||||
// Filter existing model, clip, and vae inputs
|
||||
const existingModelInputs = node.inputs.filter(input => input.name.startsWith('model_'));
|
||||
const existingClipInputs = node.inputs.filter(input => input.name.startsWith('clip_'));
|
||||
const existingVaeInputs = node.inputs.filter(input => input.name.startsWith('vae_'));
|
||||
|
||||
// Determine if we need to add or remove inputs
|
||||
if (existingModelInputs.length < numInputs || existingClipInputs.length < numInputs || existingVaeInputs.length < numInputs) {
|
||||
// Add new model, clip, and vae inputs if not enough existing
|
||||
for (let i = Math.max(existingModelInputs.length, existingClipInputs.length, existingVaeInputs.length) + 1; i <= numInputs; i++) {
|
||||
const modelInputName = `model_${i}`;
|
||||
const clipInputName = `clip_${i}`;
|
||||
const vaeInputName = `vae_${i}`;
|
||||
if (!node.inputs.find(input => input.name === modelInputName)) {
|
||||
node.addInput(modelInputName, "MODEL");
|
||||
}
|
||||
if (!node.inputs.find(input => input.name === clipInputName)) {
|
||||
node.addInput(clipInputName, "CLIP");
|
||||
}
|
||||
if (!node.inputs.find(input => input.name === vaeInputName)) {
|
||||
node.addInput(vaeInputName, "VAE");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove excess model, clip, and vae inputs if too many
|
||||
node.inputs = node.inputs.filter(input =>
|
||||
(!input.name.startsWith('model_') && !input.name.startsWith('clip_') && !input.name.startsWith('vae_')) ||
|
||||
(parseInt(input.name.split('_')[1]) <= numInputs)
|
||||
);
|
||||
}
|
||||
|
||||
node.setSize(node.computeSize());
|
||||
};
|
||||
|
||||
// Move number_of_inputs to the top initially
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (numInputsWidget) {
|
||||
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
|
||||
numInputsWidget.callback = () => {
|
||||
updateInputs();
|
||||
app.graph.setDirtyCanvas(true);
|
||||
};
|
||||
}
|
||||
|
||||
// Delay the initial update to ensure node is fully initialized
|
||||
setTimeout(updateInputs, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.RandomTexts",
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass === "Bjornulf_RandomTexts") {
|
||||
const updateInputs = () => {
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (!numInputsWidget) return;
|
||||
|
||||
const numInputs = numInputsWidget.value;
|
||||
|
||||
// Initialize node.inputs if it doesn't exist
|
||||
if (!node.inputs) {
|
||||
node.inputs = [];
|
||||
}
|
||||
|
||||
// Filter existing text inputs
|
||||
const existingInputs = node.inputs.filter(input => input.name.startsWith('text_'));
|
||||
|
||||
// Determine if we need to add or remove inputs
|
||||
if (existingInputs.length < numInputs) {
|
||||
// Add new text inputs if not enough existing
|
||||
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
|
||||
const inputName = `text_${i}`;
|
||||
if (!node.inputs.find(input => input.name === inputName)) {
|
||||
node.addInput(inputName, "STRING");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove excess text inputs if too many
|
||||
node.inputs = node.inputs.filter(input => !input.name.startsWith('text_') || parseInt(input.name.split('_')[1]) <= numInputs);
|
||||
}
|
||||
|
||||
node.setSize(node.computeSize());
|
||||
};
|
||||
|
||||
// Set seed widget to hidden input
|
||||
const seedWidget = node.widgets.find(w => w.name === "seed");
|
||||
if (seedWidget) {
|
||||
seedWidget.type = "HIDDEN";
|
||||
}
|
||||
|
||||
// Move number_of_inputs to the top initially
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_inputs");
|
||||
if (numInputsWidget) {
|
||||
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
|
||||
numInputsWidget.callback = () => {
|
||||
updateInputs();
|
||||
app.graph.setDirtyCanvas(true);
|
||||
};
|
||||
}
|
||||
|
||||
// Delay the initial update to ensure node is fully initialized
|
||||
setTimeout(updateInputs, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
|
||||
// Styles for the text area
|
||||
const textAreaStyles = {
|
||||
readOnly: true,
|
||||
opacity: 1,
|
||||
padding: '10px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '5px',
|
||||
backgroundColor: '#222',
|
||||
color: 'Lime',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.4',
|
||||
resize: 'vertical',
|
||||
overflowY: 'auto',
|
||||
};
|
||||
|
||||
// Displays input text on a node
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.ShowFloat",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "Bjornulf_ShowFloat") {
|
||||
function createStyledTextArea(text) {
|
||||
const widget = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
|
||||
widget.inputEl.readOnly = true;
|
||||
const textArea = widget.inputEl;
|
||||
|
||||
Object.assign(textArea.style, textAreaStyles);
|
||||
textArea.classList.add('bjornulf-show-text');
|
||||
widget.value = text;
|
||||
return widget;
|
||||
}
|
||||
|
||||
function populate(text) {
|
||||
if (this.widgets) {
|
||||
for (let i = 1; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.();
|
||||
}
|
||||
this.widgets.length = 1;
|
||||
}
|
||||
|
||||
const v = Array.isArray(text) ? text : [text];
|
||||
for (const list of v) {
|
||||
if (list) {
|
||||
createStyledTextArea.call(this, list);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize();
|
||||
if (sz[0] < this.size[0]) sz[0] = this.size[0];
|
||||
if (sz[1] < this.size[1]) sz[1] = this.size[1];
|
||||
this.onResize?.(sz);
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
});
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
|
||||
const onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
onConfigure?.apply(this, arguments);
|
||||
if (this.widgets_values?.length) {
|
||||
populate.call(this, this.widgets_values);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
|
||||
// Styles for the text area
|
||||
const textAreaStyles = {
|
||||
readOnly: true,
|
||||
opacity: 1,
|
||||
padding: '10px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '5px',
|
||||
backgroundColor: '#222',
|
||||
color: 'Lime',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.4',
|
||||
resize: 'vertical',
|
||||
overflowY: 'auto',
|
||||
};
|
||||
|
||||
// Displays input text on a node
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.ShowInt",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "Bjornulf_ShowInt") {
|
||||
function createStyledTextArea(text) {
|
||||
const widget = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
|
||||
widget.inputEl.readOnly = true;
|
||||
const textArea = widget.inputEl;
|
||||
|
||||
Object.assign(textArea.style, textAreaStyles);
|
||||
textArea.classList.add('bjornulf-show-text');
|
||||
widget.value = text;
|
||||
return widget;
|
||||
}
|
||||
|
||||
function populate(text) {
|
||||
if (this.widgets) {
|
||||
for (let i = 1; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.();
|
||||
}
|
||||
this.widgets.length = 1;
|
||||
}
|
||||
|
||||
const v = Array.isArray(text) ? text : [text];
|
||||
for (const list of v) {
|
||||
if (list) {
|
||||
createStyledTextArea.call(this, list);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize();
|
||||
if (sz[0] < this.size[0]) sz[0] = this.size[0];
|
||||
if (sz[1] < this.size[1]) sz[1] = this.size[1];
|
||||
this.onResize?.(sz);
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
});
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
|
||||
const onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
onConfigure?.apply(this, arguments);
|
||||
if (this.widgets_values?.length) {
|
||||
populate.call(this, this.widgets_values);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../scripts/widgets.js";
|
||||
|
||||
// Styles for the text area
|
||||
const textAreaStyles = {
|
||||
readOnly: true,
|
||||
opacity: 1,
|
||||
padding: '10px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '5px',
|
||||
backgroundColor: '#222',
|
||||
color: 'Lime',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontSize: '14px',
|
||||
lineHeight: '1.4',
|
||||
resize: 'vertical',
|
||||
overflowY: 'auto',
|
||||
};
|
||||
|
||||
// Displays input text on a node
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.ShowText",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "Bjornulf_ShowText") {
|
||||
function createStyledTextArea(text) {
|
||||
const widget = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
|
||||
widget.inputEl.readOnly = true;
|
||||
const textArea = widget.inputEl;
|
||||
|
||||
Object.assign(textArea.style, textAreaStyles);
|
||||
textArea.classList.add('bjornulf-show-text');
|
||||
widget.value = text;
|
||||
return widget;
|
||||
}
|
||||
|
||||
function populate(text) {
|
||||
if (this.widgets) {
|
||||
for (let i = 1; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.();
|
||||
}
|
||||
this.widgets.length = 1;
|
||||
}
|
||||
|
||||
const v = Array.isArray(text) ? text : [text];
|
||||
for (const list of v) {
|
||||
if (list) {
|
||||
createStyledTextArea.call(this, list);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize();
|
||||
if (sz[0] < this.size[0]) sz[0] = this.size[0];
|
||||
if (sz[1] < this.size[1]) sz[1] = this.size[1];
|
||||
this.onResize?.(sz);
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
});
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
|
||||
const onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
onConfigure?.apply(this, arguments);
|
||||
if (this.widgets_values?.length) {
|
||||
populate.call(this, this.widgets_values);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Bjornulf.WriteImageCharacters",
|
||||
async nodeCreated(node) {
|
||||
if (node.comfyClass === "Bjornulf_WriteImageCharacters") {
|
||||
const updateInputs = () => {
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_characters");
|
||||
if (!numInputsWidget) return;
|
||||
|
||||
const numInputs = numInputsWidget.value;
|
||||
|
||||
// Initialize node.inputs if it doesn't exist
|
||||
if (!node.inputs) {
|
||||
node.inputs = [];
|
||||
}
|
||||
|
||||
// Filter existing text inputs
|
||||
const existingInputs = node.inputs.filter(input => input.name.startsWith('character_'));
|
||||
|
||||
// Determine if we need to add or remove inputs
|
||||
if (existingInputs.length < numInputs) {
|
||||
// Add new text inputs if not enough existing
|
||||
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
|
||||
const inputName = `character_${i}`;
|
||||
if (!node.inputs.find(input => input.name === inputName)) {
|
||||
// node.addInput(inputName, "STRING");
|
||||
node.addInput(inputName, "BJORNULF_CHARACTER");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove excess text inputs if too many
|
||||
node.inputs = node.inputs.filter(input => !input.name.startsWith('character_') || parseInt(input.name.split('_')[1]) <= numInputs);
|
||||
}
|
||||
|
||||
node.setSize(node.computeSize());
|
||||
};
|
||||
|
||||
// Move number_of_inputs to the top initially
|
||||
const numInputsWidget = node.widgets.find(w => w.name === "number_of_characters");
|
||||
if (numInputsWidget) {
|
||||
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
|
||||
numInputsWidget.callback = () => {
|
||||
updateInputs();
|
||||
app.graph.setDirtyCanvas(true);
|
||||
};
|
||||
}
|
||||
|
||||
// Delay the initial update to ensure node is fully initialized
|
||||
setTimeout(updateInputs, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
class WriteImageAllInOne:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"art_style": (["none", "drawing", "digital art", "photography"],),
|
||||
"location": ("STRING", {"forceInput": "True"}),
|
||||
"lighting": ("STRING", {"multiline": True}),
|
||||
"camera_angle": ("STRING", {"multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"other": ("STRING", {"multiline": True},),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "write_image_allinone"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def write_image_allinone(self, art_style, location, lighting, camera_angle, other=""):
|
||||
text = f"Art Style: {art_style}\n\n"
|
||||
text += f"Location:\n{location}\n\n"
|
||||
text += f"Lighting:\n{lighting}\n\n"
|
||||
text += f"Camera Angle:\n{camera_angle}\n\n"
|
||||
if other:
|
||||
text += f"Other:\n{other}\n\n"
|
||||
return (text,)
|
||||
|
||||
@classmethod
|
||||
def CREATE_CONNECTED_NODES(cls):
|
||||
return [
|
||||
{
|
||||
"class_type": "PrimitiveNode",
|
||||
"inputs": {
|
||||
"value": "drawing",
|
||||
"label": "Art Style",
|
||||
"type": "combo",
|
||||
"options": ["drawing", "digital art", "photography"]
|
||||
},
|
||||
"output": ["art_style"]
|
||||
},
|
||||
{
|
||||
"class_type": "PrimitiveNode",
|
||||
"inputs": {
|
||||
"value": "",
|
||||
"label": "Location",
|
||||
"type": "text",
|
||||
"multiline": True
|
||||
},
|
||||
"output": ["location"]
|
||||
},
|
||||
{
|
||||
"class_type": "PrimitiveNode",
|
||||
"inputs": {
|
||||
"value": "",
|
||||
"label": "Lighting",
|
||||
"type": "text",
|
||||
"multiline": True
|
||||
},
|
||||
"output": ["lighting"]
|
||||
},
|
||||
{
|
||||
"class_type": "PrimitiveNode",
|
||||
"inputs": {
|
||||
"value": "",
|
||||
"label": "Camera Angle",
|
||||
"type": "text",
|
||||
"multiline": True
|
||||
},
|
||||
"output": ["camera_angle"]
|
||||
},
|
||||
{
|
||||
"class_type": "PrimitiveNode",
|
||||
"inputs": {
|
||||
"value": "",
|
||||
"label": "Other",
|
||||
"type": "text",
|
||||
"multiline": True
|
||||
},
|
||||
"output": ["other"]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
class WriteImageCharacter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"description": ("STRING", {"multiline": True}),
|
||||
"action": ("STRING", {"multiline": False}),
|
||||
"emotion": ("STRING", {"multiline": False}),
|
||||
"clothes": ("STRING", {"multiline": False}),
|
||||
},
|
||||
}
|
||||
|
||||
# RETURN_TYPES = ("STRING",)
|
||||
RETURN_TYPES = ("BJORNULF_CHARACTER",)
|
||||
RETURN_NAMES = ("character_details",)
|
||||
FUNCTION = "write_image_character"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def write_image_character(self, description, action, emotion, clothes):
|
||||
text = f"{description}, {action}, {emotion}, {clothes}"
|
||||
return (text,)
|
||||
@@ -0,0 +1,32 @@
|
||||
class WriteImageCharacters:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
hidden_inputs = {}
|
||||
for i in range(2, 6): # Notice the range starts at 2 and ends at 6 to include 5
|
||||
hidden_inputs.update({
|
||||
f"character_{i}": ("BJORNULF_CHARACTER", {"forceInput": True}),
|
||||
# f"character_{i}": ("STRING", {"forceInput": True}),
|
||||
})
|
||||
return {
|
||||
"required": {
|
||||
"number_of_characters": ("INT", {"default": 1, "min": 1, "max": 5, "step": 1}),
|
||||
"character_1": ("BJORNULF_CHARACTER", {"forceInput": True}),
|
||||
# "character_1": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
"optional": {
|
||||
"other": ("STRING", {"multiline": True, "forceInput": True}),
|
||||
},
|
||||
"hidden": hidden_inputs
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "write_image_characters"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def write_image_characters(self, number_of_characters, other="", **kwargs):
|
||||
text = f"Other: {other}\n"
|
||||
for i in range(1, number_of_characters + 1):
|
||||
text += f"{kwargs.get(f'character_{i}', '')}\n"
|
||||
return (text,)
|
||||
@@ -0,0 +1,27 @@
|
||||
class WriteImageEnvironment:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"art_style": (["drawing", "digital art", "photography"],),
|
||||
"location": ("STRING", {"multiline": True}),
|
||||
"lighting": ("STRING", {"multiline": True}),
|
||||
"camera_angle": ("STRING", {"multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"other": ("STRING", {"multiline": True, "forceInput": True},),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "write_image_environment"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def write_image_environment(self, art_style, location, lighting, camera_angle, **kwargs):
|
||||
text = f"Art Style: {art_style}\n\n"
|
||||
text += f"Location:\n{location}\n\n"
|
||||
text += f"Lighting:\n{lighting}\n\n"
|
||||
text += f"Camera Angle:\n{camera_angle}\n\n"
|
||||
return (text,)
|
||||
@@ -0,0 +1,19 @@
|
||||
class WriteText:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True}),
|
||||
}
|
||||
}
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "write_text"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
CATEGORY = "Bjornulf"
|
||||
|
||||
def write_text(self, text):
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
Reference in New Issue
Block a user