mirror of
https://github.com/justUmen/Bjornulf_custom_nodes.git
synced 2026-03-21 12:42:11 -03:00
first commit
This commit is contained in:
11
CUSTOM_STRING.py.txt
Normal file
11
CUSTOM_STRING.py.txt
Normal 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
SaveText/SaveText.txt
Normal file
1
SaveText/SaveText.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
saluts
|
||||||
1
SaveText/SaveText_001.txt
Normal file
1
SaveText/SaveText_001.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
encule
|
||||||
1
SaveText/SaveText_002.txt
Normal file
1
SaveText/SaveText_002.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
connard
|
||||||
0
SaveText/SaveText_003.txt
Normal file
0
SaveText/SaveText_003.txt
Normal file
0
SaveText/SaveText_004.txt
Normal file
0
SaveText/SaveText_004.txt
Normal file
0
SaveText/SaveText_005.txt
Normal file
0
SaveText/SaveText_005.txt
Normal file
86
__init__.py
Normal file
86
__init__.py
Normal 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']
|
||||||
BIN
__pycache__/CUSTOM_STRING.cpython-311.pyc
Normal file
BIN
__pycache__/CUSTOM_STRING.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/__init__.cpython-311.pyc
Normal file
BIN
__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/combine_texts.cpython-311.pyc
Normal file
BIN
__pycache__/combine_texts.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/convert_16to4_channels.cpython-311.pyc
Normal file
BIN
__pycache__/convert_16to4_channels.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/convert_16to8_channels.cpython-311.pyc
Normal file
BIN
__pycache__/convert_16to8_channels.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/create_video.cpython-311.pyc
Normal file
BIN
__pycache__/create_video.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loop_basic_batch.cpython-311.pyc
Normal file
BIN
__pycache__/loop_basic_batch.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loop_float.cpython-311.pyc
Normal file
BIN
__pycache__/loop_float.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loop_integer.cpython-311.pyc
Normal file
BIN
__pycache__/loop_integer.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loop_my_combos_samplers_schedulers.cpython-311.pyc
Normal file
BIN
__pycache__/loop_my_combos_samplers_schedulers.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loop_samplers.cpython-311.pyc
Normal file
BIN
__pycache__/loop_samplers.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loop_schedulers.cpython-311.pyc
Normal file
BIN
__pycache__/loop_schedulers.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loop_texts.cpython-311.pyc
Normal file
BIN
__pycache__/loop_texts.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/ollama.cpython-311.pyc
Normal file
BIN
__pycache__/ollama.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/random_model_clip_vae.cpython-311.pyc
Normal file
BIN
__pycache__/random_model_clip_vae.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/random_texts.cpython-311.pyc
Normal file
BIN
__pycache__/random_texts.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/save_api_image.cpython-311.pyc
Normal file
BIN
__pycache__/save_api_image.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/save_image.cpython-311.pyc
Normal file
BIN
__pycache__/save_image.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/save_text.cpython-311.pyc
Normal file
BIN
__pycache__/save_text.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/save_tmp_image.cpython-311.pyc
Normal file
BIN
__pycache__/save_tmp_image.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/show_float.cpython-311.pyc
Normal file
BIN
__pycache__/show_float.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/show_int.cpython-311.pyc
Normal file
BIN
__pycache__/show_int.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/show_text.cpython-311.pyc
Normal file
BIN
__pycache__/show_text.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/video_pingpong.cpython-311.pyc
Normal file
BIN
__pycache__/video_pingpong.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/write_image_allinone.cpython-311.pyc
Normal file
BIN
__pycache__/write_image_allinone.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/write_image_character.cpython-311.pyc
Normal file
BIN
__pycache__/write_image_character.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/write_image_characters.cpython-311.pyc
Normal file
BIN
__pycache__/write_image_characters.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/write_image_environment.cpython-311.pyc
Normal file
BIN
__pycache__/write_image_environment.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/write_text.cpython-311.pyc
Normal file
BIN
__pycache__/write_text.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/write_texts.cpython-311.pyc
Normal file
BIN
__pycache__/write_texts.cpython-311.pyc
Normal file
Binary file not shown.
46
combine_texts.py
Normal file
46
combine_texts.py
Normal 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
91
create_video.py
Normal 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
21
loop_basic_batch.py
Normal 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
24
loop_float.py
Normal 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
32
loop_integer.py
Normal 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,)
|
||||||
82
loop_my_combos_samplers_schedulers.py
Normal file
82
loop_my_combos_samplers_schedulers.py
Normal 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
23
loop_samplers.py
Normal 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
23
loop_schedulers.py
Normal 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
22
loop_texts.py
Normal 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
28
ollama.py
Normal 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
28
random_checkpoint.py
Normal 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
33
random_model_clip_vae.py
Normal 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
27
random_texts.py
Normal 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
47
save_api_image.py
Normal 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
37
save_text.py
Normal 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
38
save_tmp_image.py
Normal 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
18
show_float.py
Normal 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
18
show_int.py
Normal 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
18
show_text.py
Normal 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
23
video_pingpong.py
Normal 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,)
|
||||||
29
web/js/BJORNULF_TYPES.js.txt
Normal file
29
web/js/BJORNULF_TYPES.js.txt
Normal 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"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
52
web/js/CUSTOM_STRING.js.txt
Normal file
52
web/js/CUSTOM_STRING.js.txt
Normal 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
52
web/js/combine_texts.js
Normal 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
52
web/js/loop_texts.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
65
web/js/random_model_clip_vae.js
Normal file
65
web/js/random_model_clip_vae.js
Normal 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
58
web/js/random_texts.js
Normal 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
76
web/js/show_float.js
Normal 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
76
web/js/show_int.js
Normal 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
76
web/js/show_text.js
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
53
web/js/write_image_characters.js
Normal file
53
web/js/write_image_characters.js
Normal 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
84
write_image_allinone.py
Normal 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
22
write_image_character.py
Normal 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
32
write_image_characters.py
Normal 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,)
|
||||||
27
write_image_environment.py
Normal file
27
write_image_environment.py
Normal 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
19
write_text.py
Normal 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,)}
|
||||||
Reference in New Issue
Block a user