This commit is contained in:
justumen
2024-07-24 20:44:48 +02:00
parent 3042f85d57
commit afc80ce51c
8 changed files with 138 additions and 29 deletions

View File

@@ -1,4 +1,4 @@
# 🔗 Comfyui : Bjornulf_custom_nodes v0.2 🔗 # 🔗 Comfyui : Bjornulf_custom_nodes v0.3 🔗
# Dependencies # Dependencies
@@ -6,8 +6,9 @@
# 📝 Changelog # 📝 Changelog
## v0.2 - **v0.2 Ollama**: Improve ollama node with system prompt + model selection.
- **Ollama**: Improve ollama node with system prompt + model selection. - **v0.3 Save Image to Folder**: Add a new node : Save image to a chosen folder.
- **v0.3 Save Images**: Add comfyui Metadata / workflow to all my image-related nodes.
# 📝 Nodes descriptions # 📝 Nodes descriptions
@@ -106,7 +107,15 @@ The name will start at `api_00001.png`, then `api_00002.png`, etc...
**Description:** **Description:**
Save image for short-term use : ./output/tmp_api.png ⚠️💣 Save image for short-term use : ./output/tmp_api.png ⚠️💣
## 18 - 🦙 Ollama
## 18 - 🖼 Save image to a chosen folder name
![Save Temporary API](screenshots/save_image_to_folder.png)
**Description:**
Save image in a specific folder : `my_folder/00001.png`, `my_folder/00002.png`, etc...
Also allow multiple nested folders, like for example : `animal/dog/small`.
## 19 - 🦙 Ollama
![Show Text](screenshots/ollama.png) ![Show Text](screenshots/ollama.png)
**Description:** **Description:**
@@ -117,13 +126,13 @@ I recommend using `mistral-nemo` if you can run it, but it's up to you. (Might h
**Description:** **Description:**
Straight forward node to write and show text. Straight forward node to write and show text.
## 18 - 📹 Video Ping Pong ## 20 - 📹 Video Ping Pong
![Video Ping Pong](screenshots/video_pingpong.png) ![Video Ping Pong](screenshots/video_pingpong.png)
**Description:** **Description:**
Create a ping-pong effect from a list of images (from a video) by reversing the playback direction when reaching the last frame. Good for an "infinity loop" effect. Create a ping-pong effect from a list of images (from a video) by reversing the playback direction when reaching the last frame. Good for an "infinity loop" effect.
## 19 - 📹 Images to Video ## 21 - 📹 Images to Video
![Images to Video](screenshots/imgs2video.png) ![Images to Video](screenshots/imgs2video.png)
**Description:** **Description:**

View File

@@ -22,6 +22,7 @@ from .save_text import SaveText
from .save_tmp_image import SaveTmpImage from .save_tmp_image import SaveTmpImage
from .save_image_path import SaveImagePath from .save_image_path import SaveImagePath
from .save_api_image import SaveApiImage from .save_api_image import SaveApiImage
from .save_img_to_folder import SaveImageToFolder
from .resize_image import ResizeImage from .resize_image import ResizeImage
from .loop_my_combos_samplers_schedulers import LoopCombosSamplersSchedulers from .loop_my_combos_samplers_schedulers import LoopCombosSamplersSchedulers
@@ -40,6 +41,7 @@ NODE_CLASS_MAPPINGS = {
"Bjornulf_ShowFloat": ShowFloat, "Bjornulf_ShowFloat": ShowFloat,
"Bjornulf_SaveText": SaveText, "Bjornulf_SaveText": SaveText,
"Bjornulf_ResizeImage": ResizeImage, "Bjornulf_ResizeImage": ResizeImage,
"Bjornulf_SaveImageToFolder": SaveImageToFolder,
"Bjornulf_SaveTmpImage": SaveTmpImage, "Bjornulf_SaveTmpImage": SaveTmpImage,
"Bjornulf_SaveImagePath": SaveImagePath, "Bjornulf_SaveImagePath": SaveImagePath,
"Bjornulf_SaveApiImage": SaveApiImage, "Bjornulf_SaveApiImage": SaveApiImage,
@@ -65,6 +67,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"Bjornulf_ShowFloat": "👁 Show (Float)", "Bjornulf_ShowFloat": "👁 Show (Float)",
"Bjornulf_ResizeImage": "📏 Resize Image", "Bjornulf_ResizeImage": "📏 Resize Image",
"Bjornulf_SaveImagePath": "🖼 Save Image (exact path, exact name) ⚠️💣", "Bjornulf_SaveImagePath": "🖼 Save Image (exact path, exact name) ⚠️💣",
"Bjornulf_SaveImageToFolder": "🖼📁 Save Image to a folder",
"Bjornulf_SaveTmpImage": "🖼 Save Image (tmp_api.png) ⚠️💣", "Bjornulf_SaveTmpImage": "🖼 Save Image (tmp_api.png) ⚠️💣",
"Bjornulf_SaveApiImage": "🖼 Save Image (./output/api_00001.png...)", "Bjornulf_SaveApiImage": "🖼 Save Image (./output/api_00001.png...)",
"Bjornulf_SaveText": "💾 Save Text", #Make SaveCharacter, SaveLocation, SaveCamera, SaveAction, SaveClothes, SaveEmotion... "Bjornulf_SaveText": "💾 Save Text", #Make SaveCharacter, SaveLocation, SaveCamera, SaveAction, SaveClothes, SaveEmotion...

View File

@@ -10,15 +10,16 @@ class ResizeImage:
"image": ("IMAGE", {}), "image": ("IMAGE", {}),
"width": ("INT", {"default": 256}), "width": ("INT", {"default": 256}),
"height": ("INT", {"default": 256}), "height": ("INT", {"default": 256}),
} },
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
} }
FUNCTION = "resize_image" FUNCTION = "resize_image"
RETURN_TYPES = ("IMAGE",) RETURN_TYPES = ("IMAGE", "PROMPT", "EXTRA_PNGINFO")
OUTPUT_NODE = True OUTPUT_NODE = True
CATEGORY = "Bjornulf" CATEGORY = "Bjornulf"
def resize_image(self, image, width=256, height=256): def resize_image(self, image, width=256, height=256, prompt=None, extra_pnginfo=None):
# Ensure the input image is on CPU and convert to numpy array # Ensure the input image is on CPU and convert to numpy array
image_np = image.cpu().numpy() image_np = image.cpu().numpy()
@@ -38,7 +39,7 @@ class ResizeImage:
# Stack the resized images back into a batch # Stack the resized images back into a batch
resized_batch = np.stack(resized_images) resized_batch = np.stack(resized_images)
# Convert to torch tensor # Convert to torch tensor
return (torch.from_numpy(resized_batch),) resized_tensor = torch.from_numpy(resized_batch)
else: else:
# If it's a single image, process it directly # If it's a single image, process it directly
# Convert to PIL Image # Convert to PIL Image
@@ -51,4 +52,11 @@ class ResizeImage:
if image.dim() == 4: if image.dim() == 4:
resized_np = np.expand_dims(resized_np, axis=0) resized_np = np.expand_dims(resized_np, axis=0)
# Convert to torch tensor # Convert to torch tensor
return (torch.from_numpy(resized_np),) resized_tensor = torch.from_numpy(resized_np)
# Update metadata if needed
if extra_pnginfo is not None:
extra_pnginfo["resized_width"] = width
extra_pnginfo["resized_height"] = height
return (resized_tensor, prompt, extra_pnginfo)

View File

@@ -1,6 +1,8 @@
import os import os
import numpy as np import numpy as np
from PIL import Image from PIL import Image
import json
from PIL.PngImagePlugin import PngInfo
class SaveApiImage: class SaveApiImage:
@classmethod @classmethod
@@ -8,7 +10,8 @@ class SaveApiImage:
return { return {
"required": { "required": {
"image": ("IMAGE", {"forceInput": True}), "image": ("IMAGE", {"forceInput": True}),
} },
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
} }
FUNCTION = "save_api_image" FUNCTION = "save_api_image"
@@ -16,7 +19,7 @@ class SaveApiImage:
OUTPUT_NODE = True OUTPUT_NODE = True
CATEGORY = "Bjornulf" CATEGORY = "Bjornulf"
def save_api_image(self, image): def save_api_image(self, image, prompt=None, extra_pnginfo=None):
# Ensure the output directory exists # Ensure the output directory exists
os.makedirs("./output/", exist_ok=True) os.makedirs("./output/", exist_ok=True)
@@ -37,11 +40,21 @@ class SaveApiImage:
break break
counter += 1 counter += 1
# Save the image with the determined filename # Prepare metadata
img.save(filename, format="PNG") metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for k, v in extra_pnginfo.items():
metadata.add_text(k, json.dumps(v))
# Save the image with the determined filename and metadata
img.save(filename, format="PNG", pnginfo=metadata)
# Write the number of the last image to a text file with leading zeroes # Write the number of the last image to a text file with leading zeroes
with open("./output/api_next_image.txt", "w") as f: with open("./output/api_next_image.txt", "w") as f:
f.write(f"api_{counter+1:05d}.png") f.write(f"api_{counter+1:05d}.png")
return () print(f"Image saved as: {filename}")
return {"ui": {"images": [{"filename": filename, "type": "output"}]}}

View File

@@ -1,6 +1,8 @@
import os import os
import numpy as np import numpy as np
from PIL import Image from PIL import Image
import json
from PIL.PngImagePlugin import PngInfo
class SaveImagePath: class SaveImagePath:
@classmethod @classmethod
@@ -8,8 +10,9 @@ class SaveImagePath:
return { return {
"required": { "required": {
"image": ("IMAGE", {"forceInput": True}), "image": ("IMAGE", {"forceInput": True}),
"path": ("STRING", {"default":"./output/default.png"}), # Add path input "path": ("STRING", {"default": "./output/default.png"}),
} },
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
} }
FUNCTION = "save_image_path" FUNCTION = "save_image_path"
@@ -17,7 +20,7 @@ class SaveImagePath:
OUTPUT_NODE = True OUTPUT_NODE = True
CATEGORY = "Bjornulf" CATEGORY = "Bjornulf"
def save_image_path(self, image, path): def save_image_path(self, image, path, prompt=None, extra_pnginfo=None):
# Ensure the output directory exists # Ensure the output directory exists
os.makedirs(os.path.dirname(path), exist_ok=True) os.makedirs(os.path.dirname(path), exist_ok=True)
@@ -33,7 +36,17 @@ class SaveImagePath:
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
# Save the image, overwriting if it exists # Create PngInfo object for metadata
img.save(path, format="PNG") metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for k, v in extra_pnginfo.items():
metadata.add_text(k, json.dumps(v))
return () # Save the image with metadata, overwriting if it exists
img.save(path, format="PNG", pnginfo=metadata)
print(f"Image saved as: {path}")
return {"ui": {"images": [{"filename": path, "type": "output"}]}}

50
save_img_to_folder.py Normal file
View File

@@ -0,0 +1,50 @@
import os
import numpy as np
from PIL import Image
import json
from PIL.PngImagePlugin import PngInfo
class SaveImageToFolder:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE", ),
"folder_name": ("STRING", {"default": "my_folder"}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
FUNCTION = "save_image_to_folder"
RETURN_TYPES = ()
OUTPUT_NODE = True
CATEGORY = "Bjornulf"
def save_image_to_folder(self, images, folder_name, prompt=None, extra_pnginfo=None):
output_dir = os.path.join("./output", folder_name)
os.makedirs(output_dir, exist_ok=True)
results = []
for image in images:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for k, v in extra_pnginfo.items():
metadata.add_text(k, json.dumps(v))
counter = 1
while True:
filename = os.path.join(output_dir, f"{counter:05d}.png")
if not os.path.exists(filename):
break
counter += 1
img.save(filename, format="PNG", pnginfo=metadata)
print(f"Image saved as: {filename}")
results.append({"filename": filename})
return {"ui": {"images": results}}

View File

@@ -1,6 +1,8 @@
import os import os
import numpy as np import numpy as np
from PIL import Image from PIL import Image
import json
from PIL.PngImagePlugin import PngInfo
class SaveTmpImage: class SaveTmpImage:
@classmethod @classmethod
@@ -8,7 +10,8 @@ class SaveTmpImage:
return { return {
"required": { "required": {
"image": ("IMAGE", {"forceInput": True}), "image": ("IMAGE", {"forceInput": True}),
} },
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
} }
FUNCTION = "save_image" FUNCTION = "save_image"
@@ -16,12 +19,11 @@ class SaveTmpImage:
OUTPUT_NODE = True OUTPUT_NODE = True
CATEGORY = "Bjornulf" CATEGORY = "Bjornulf"
def save_image(self, image): def save_image(self, image, prompt=None, extra_pnginfo=None):
# Ensure the output directory exists # Ensure the output directory exists
# os.makedirs("./output", exist_ok=True) os.makedirs("./output", exist_ok=True)
# Convert the image from ComfyUI format to PIL Image # 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() i = 255. * image.cpu().numpy()
# Reshape the image if it's not in the expected format, remove any leading dimensions of size 1 # Reshape the image if it's not in the expected format, remove any leading dimensions of size 1
if i.ndim > 3: if i.ndim > 3:
@@ -32,7 +34,18 @@ class SaveTmpImage:
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
# Save the image, overwriting if it exists # Prepare metadata
img.save("./output/tmp_api.png", format="PNG") metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for k, v in extra_pnginfo.items():
metadata.add_text(k, json.dumps(v))
return () # Save the image with metadata, overwriting if it exists
filename = "./output/tmp_api.png"
img.save(filename, format="PNG", pnginfo=metadata)
print(f"Temporary image saved as: {filename}")
return {"ui": {"images": [{"filename": filename, "type": "output"}]}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB