first commit

This commit is contained in:
justumen
2024-07-06 12:05:16 +02:00
commit 643bf5d843
74 changed files with 1553 additions and 0 deletions

11
CUSTOM_STRING.py.txt Normal file
View File

@@ -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,)

1
README.md Normal file
View File

@@ -0,0 +1 @@

1
SaveText/SaveText.txt Normal file
View File

@@ -0,0 +1 @@
saluts

View File

@@ -0,0 +1 @@
encule

View File

@@ -0,0 +1 @@
connard

View File

View File

View File

86
__init__.py Normal file
View File

@@ -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.

46
combine_texts.py Normal file
View File

@@ -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"

91
create_video.py Normal file
View File

@@ -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/

21
loop_basic_batch.py Normal file
View File

@@ -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,)

24
loop_float.py Normal file
View File

@@ -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,)

32
loop_integer.py Normal file
View File

@@ -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,)

View File

@@ -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

23
loop_samplers.py Normal file
View File

@@ -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],)

23
loop_schedulers.py Normal file
View File

@@ -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],)

22
loop_texts.py Normal file
View File

@@ -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,)

28
ollama.py Normal file
View File

@@ -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'],)

28
random_checkpoint.py Normal file
View File

@@ -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)

33
random_model_clip_vae.py Normal file
View File

@@ -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)

27
random_texts.py Normal file
View File

@@ -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,)

47
save_api_image.py Normal file
View File

@@ -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 ()

37
save_text.py Normal file
View File

@@ -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,)}

38
save_tmp_image.py Normal file
View File

@@ -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 ()

18
show_float.py Normal file
View File

@@ -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
show_int.py Normal file
View File

@@ -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}}

18
show_text.py Normal file
View File

@@ -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}}

23
video_pingpong.py Normal file
View File

@@ -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,)

View File

@@ -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"
};
});
}
});

View File

@@ -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);
};

52
web/js/combine_texts.js Normal file
View File

@@ -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);
}
}
});

52
web/js/loop_texts.js Normal file
View File

@@ -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);
}
}
});

View File

@@ -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);
}
}
});

58
web/js/random_texts.js Normal file
View File

@@ -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);
}
}
});

76
web/js/show_float.js Normal file
View File

@@ -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);
}
};
}
},
});

76
web/js/show_int.js Normal file
View File

@@ -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);
}
};
}
},
});

76
web/js/show_text.js Normal file
View File

@@ -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);
}
};
}
},
});

View File

@@ -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);
}
}
});

84
write_image_allinone.py Normal file
View File

@@ -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"]
}
]

22
write_image_character.py Normal file
View File

@@ -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,)

32
write_image_characters.py Normal file
View File

@@ -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,)

View File

@@ -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,)

19
write_text.py Normal file
View File

@@ -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,)}