added list output for directory crawl and image randomizer + added image blank node
This commit is contained in:
@@ -127,9 +127,11 @@ from .nodes.FL_GPT_Image1_ADV import FL_GPT_Image1_ADV
|
||||
from .nodes.FL_ImageBatch import FL_ImageBatch
|
||||
from .nodes.FL_Hedra_API import FL_Hedra_API
|
||||
from .nodes.FL_RunwayImageAPI import FL_RunwayImageAPI
|
||||
from .nodes.FL_Image_Blank import FL_ImageBlank
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FL_ImageBlank": FL_ImageBlank,
|
||||
"FL_ImageRandomizer": FL_ImageRandomizer,
|
||||
"FL_ImageCaptionSaver": FL_ImageCaptionSaver,
|
||||
"FL_VideoCaptionSaver": FL_VideoCaptionSaver,
|
||||
@@ -265,6 +267,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FL_ImageBlank": "FL Image Blank",
|
||||
"FL_ImageRandomizer": "FL Image Randomizer",
|
||||
"FL_ImageCaptionSaver": "FL Image Caption Saver",
|
||||
"FL_VideoCaptionSaver": "FL Video Caption Saver",
|
||||
|
||||
+44
-23
@@ -17,7 +17,9 @@ class FL_DirectoryCrawl:
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING") # Output a batch of images or list of text contents
|
||||
RETURN_TYPES = ("IMAGE", "STRING", "IMAGE")
|
||||
RETURN_NAMES = ("image_batch", "text_content", "image_list")
|
||||
OUTPUT_IS_LIST = (False, False, True)
|
||||
FUNCTION = "load_batch"
|
||||
CATEGORY = "🏵️Fill Nodes/utility"
|
||||
|
||||
@@ -27,52 +29,71 @@ class FL_DirectoryCrawl:
|
||||
|
||||
file_paths = self.crawl_directories(directory_path, file_type)
|
||||
if not file_paths:
|
||||
raise ValueError(f"No {file_type} found in the specified directory and its subdirectories.")
|
||||
if file_type == "images":
|
||||
return (torch.empty(0), "", []) # Return empty for all if no files
|
||||
else: # text
|
||||
return (torch.empty(0), "", [])
|
||||
|
||||
|
||||
file_paths = file_paths[:max_files] # Limit the number of files
|
||||
|
||||
if file_type == "images":
|
||||
return self.load_image_batch(file_paths)
|
||||
else:
|
||||
return self.load_text_batch(file_paths)
|
||||
batch_tensor, image_list_tensors = self.load_images_data(file_paths)
|
||||
return (batch_tensor, "", image_list_tensors)
|
||||
else: # text
|
||||
text_content = self.load_text_data(file_paths)
|
||||
return (torch.empty(0), text_content, [])
|
||||
|
||||
def load_image_batch(self, image_paths):
|
||||
batch_images = []
|
||||
def load_images_data(self, image_paths):
|
||||
individual_images_np = []
|
||||
individual_image_tensors = []
|
||||
pbar = ProgressBar(len(image_paths))
|
||||
|
||||
if not image_paths:
|
||||
return torch.empty(0), []
|
||||
|
||||
for idx, img_path in enumerate(image_paths):
|
||||
image = Image.open(img_path)
|
||||
image = ImageOps.exif_transpose(image) # Correct orientation
|
||||
image = image.convert("RGB")
|
||||
image_np = np.array(image).astype(np.float32) / 255.0
|
||||
batch_images.append(image_np)
|
||||
individual_images_np.append(image_np)
|
||||
# Create tensor for the list output (B, H, W, C) -> (1, H, W, C)
|
||||
individual_image_tensors.append(torch.from_numpy(image_np)[None,])
|
||||
pbar.update_absolute(idx)
|
||||
|
||||
# Pad images to the largest dimensions
|
||||
max_h = max(img.shape[0] for img in batch_images)
|
||||
max_w = max(img.shape[1] for img in batch_images)
|
||||
# Pad images for batch output
|
||||
max_h = max(img.shape[0] for img in individual_images_np)
|
||||
max_w = max(img.shape[1] for img in individual_images_np)
|
||||
|
||||
padded_images = []
|
||||
for img in batch_images:
|
||||
h, w, c = img.shape
|
||||
padded_images_for_batch = []
|
||||
for img_np in individual_images_np:
|
||||
h, w, c = img_np.shape
|
||||
padded = np.zeros((max_h, max_w, c), dtype=np.float32)
|
||||
padded[:h, :w, :] = img
|
||||
padded_images.append(padded)
|
||||
padded[:h, :w, :] = img_np
|
||||
padded_images_for_batch.append(padded)
|
||||
|
||||
batch_images_np = np.stack(padded_images, axis=0)
|
||||
batch_images_np = np.stack(padded_images_for_batch, axis=0)
|
||||
batch_images_tensor = torch.from_numpy(batch_images_np)
|
||||
|
||||
return (batch_images_tensor, "")
|
||||
return batch_images_tensor, individual_image_tensors
|
||||
|
||||
def load_text_batch(self, text_paths):
|
||||
def load_text_data(self, text_paths):
|
||||
text_contents = []
|
||||
if not text_paths:
|
||||
return ""
|
||||
pbar = ProgressBar(len(text_paths))
|
||||
for idx, txt_path in enumerate(text_paths):
|
||||
with open(txt_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
text_contents.append(content)
|
||||
try:
|
||||
with open(txt_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
text_contents.append(content)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read text file {txt_path}: {e}")
|
||||
text_contents.append(f"Error reading file: {txt_path}")
|
||||
pbar.update_absolute(idx)
|
||||
|
||||
return (torch.zeros(1), "\n---\n".join(text_contents)) # Return empty tensor for IMAGE type
|
||||
return "\n---\n".join(text_contents)
|
||||
|
||||
def crawl_directories(self, directory, file_type):
|
||||
if file_type == "images":
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
class FL_ImageBlank:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"width": ("INT", {"default": 512, "min": 64, "max": 8192, "step": 64}),
|
||||
"height": ("INT", {"default": 512, "min": 64, "max": 8192, "step": 64}),
|
||||
"red": ("INT", {"default": 255, "min": 0, "max": 255, "step": 1}),
|
||||
"green": ("INT", {"default": 255, "min": 0, "max": 255, "step": 1}),
|
||||
"blue": ("INT", {"default": 255, "min": 0, "max": 255, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "create_blank_image"
|
||||
CATEGORY = "🏵️Fill Nodes/Image"
|
||||
|
||||
def create_blank_image(self, width, height, red, green, blue):
|
||||
# Create a 3-channel image (H, W, C)
|
||||
image_np = np.full((height, width, 3), [red, green, blue], dtype=np.uint8)
|
||||
|
||||
# Convert to PIL Image first to handle potential mode issues if needed, then to numpy float32
|
||||
# Though for a simple color, direct numpy to tensor is fine.
|
||||
# image_pil = Image.fromarray(image_np, 'RGB')
|
||||
# image_np_float = np.array(image_pil).astype(np.float32) / 255.0
|
||||
|
||||
image_np_float = image_np.astype(np.float32) / 255.0
|
||||
|
||||
# Convert to tensor and add batch dimension (B, H, W, C)
|
||||
image_tensor = torch.from_numpy(image_np_float)[None,]
|
||||
|
||||
return (image_tensor,)
|
||||
@@ -17,7 +17,9 @@ class FL_ImageRandomizer:
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "PATH")
|
||||
RETURN_TYPES = ("IMAGE", "PATH", "IMAGE")
|
||||
RETURN_NAMES = ("image_batch", "selected_path", "image_list")
|
||||
OUTPUT_IS_LIST = (False, False, True)
|
||||
FUNCTION = "select_media"
|
||||
CATEGORY = "🏵️Fill Nodes/Image"
|
||||
|
||||
@@ -26,11 +28,13 @@ class FL_ImageRandomizer:
|
||||
raise ValueError("Directory path is not provided.")
|
||||
|
||||
if mode == "Image":
|
||||
return self.select_image(directory_path, seed, search_subdirectories)
|
||||
image_tensor, selected_path = self.select_image_data(directory_path, seed, search_subdirectories)
|
||||
return (image_tensor, selected_path, [image_tensor])
|
||||
else: # Video mode
|
||||
return self.select_video_frames(directory_path, seed, search_subdirectories)
|
||||
frames_tensor, selected_path = self.select_video_data(directory_path, seed, search_subdirectories)
|
||||
return (frames_tensor, selected_path, [frames_tensor]) # Video frames are already a batch, but we wrap in list for consistency
|
||||
|
||||
def select_image(self, directory_path, seed, search_subdirectories=False):
|
||||
def select_image_data(self, directory_path, seed, search_subdirectories=False):
|
||||
images = self.load_files(directory_path, search_subdirectories, file_type="image")
|
||||
if not images:
|
||||
raise ValueError("No images found in the specified directory.")
|
||||
@@ -46,9 +50,9 @@ class FL_ImageRandomizer:
|
||||
image_np = np.array(image).astype(np.float32) / 255.0
|
||||
image_tensor = torch.from_numpy(image_np)[None,]
|
||||
|
||||
return (image_tensor, selected_image_path)
|
||||
return image_tensor, selected_image_path
|
||||
|
||||
def select_video_frames(self, directory_path, seed, search_subdirectories=False):
|
||||
def select_video_data(self, directory_path, seed, search_subdirectories=False):
|
||||
videos = self.load_files(directory_path, search_subdirectories, file_type="video")
|
||||
if not videos:
|
||||
raise ValueError("No videos found in the specified directory.")
|
||||
@@ -58,27 +62,21 @@ class FL_ImageRandomizer:
|
||||
|
||||
selected_video_path = videos[selected_index]
|
||||
|
||||
# Open the video file
|
||||
cap = cv2.VideoCapture(selected_video_path)
|
||||
if not cap.isOpened():
|
||||
raise ValueError(f"Could not open video file: {selected_video_path}")
|
||||
|
||||
# Get video properties
|
||||
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
|
||||
if frame_count <= 0:
|
||||
raise ValueError(f"No frames found in video: {selected_video_path}")
|
||||
|
||||
# Extract all frames from the video
|
||||
frames = []
|
||||
success = True
|
||||
while success:
|
||||
success, frame = cap.read()
|
||||
if success:
|
||||
# Convert BGR to RGB (OpenCV uses BGR by default)
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# Normalize
|
||||
frame_np = np.array(frame).astype(np.float32) / 255.0
|
||||
frames.append(frame_np)
|
||||
|
||||
@@ -87,10 +85,9 @@ class FL_ImageRandomizer:
|
||||
if not frames:
|
||||
raise ValueError(f"Failed to extract frames from video: {selected_video_path}")
|
||||
|
||||
# Convert list of frames to tensor with batch dimension
|
||||
frames_tensor = torch.from_numpy(np.stack(frames))
|
||||
|
||||
return (frames_tensor, selected_video_path)
|
||||
return frames_tensor, selected_video_path
|
||||
|
||||
def load_files(self, directory, search_subdirectories=False, file_type="image"):
|
||||
if file_type == "image":
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_fill-nodes"
|
||||
description = "Fill-Nodes is a versatile collection of custom nodes for ComfyUI that extends functionality across multiple domains. Features include advanced image processing (pixelation, slicing, masking), visual effects generation (glitch, halftone, pixel art), comprehensive file handling (PDF creation/extraction, Google Drive integration), AI model interfaces (GPT, DALL-E, Hugging Face), utility nodes for workflow enhancement, and specialized tools for video processing, captioning, and batch operations. The pack provides both practical workflow solutions and creative tools within a unified node collection."
|
||||
version = "1.5.4"
|
||||
version = "1.5.5"
|
||||
license = "LICENSE"
|
||||
dependencies = ["diffusers", "librosa", "sounddevice", "glitch_this", "PyOpenGL", "glfw", "scipy>=1.13.1", "requests", "aiohttp", "moviepy", "matplotlib", "reportlab", "openai", "PyPDF2", "pdf2image", "PyMuPDF", "reportlab", "PyPDF2", "ollama", "kornia", "opencv-python", "gdown", "open_clip_torch", "google-genai"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user