diff --git a/Mango_random_nodes.py b/Mango_random_nodes.py new file mode 100644 index 0000000..09e6a62 --- /dev/null +++ b/Mango_random_nodes.py @@ -0,0 +1,271 @@ +import os +import random +import torch +from PIL import Image, ImageOps +import numpy as np +import cv2 +import time + + +class RandomFilePathNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "directory_path": ("STRING", {"default": ""}), + }, + } + + @classmethod + def IS_CHANGED(cls, **kwargs): + return float("NaN") + + RETURN_TYPES = ("STRING",) + FUNCTION = "get_random_file_path" + CATEGORY = "🥭 芒果节点/文件" + + def get_random_file_path(self, directory_path: str) -> str: + if not os.path.isdir(directory_path): + raise NotADirectoryError( + f"'{directory_path}' is not a valid directory path.") + + files = [] + + # Walk through the directory tree + for root, dirs, files_in_dir in os.walk(directory_path): + for file_name in files_in_dir: + # Build full path to the file + full_file_path = os.path.join(root, file_name) + # Check if the file has a valid extension + files.append(full_file_path) + + if not files: + raise FileNotFoundError( + f"No files found in directory: {directory_path}") + + path = random.choice(files) + return (path,) + + +class RandomImagePathNode: + def __init__(self): + self.current_seed = 0 + self.current_index = 0 + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "directory_path": ("STRING", {"default": ""}), + "sort_mode": (["完全随机" , "顺序循环"],), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + }, + } + + @classmethod + def IS_CHANGED(cls, **kwargs): + return float("NaN") + + RETURN_TYPES = ("IMAGE", "MASK", "STRING") + RETURN_NAMES = ("image", "mask", "text_content") + FUNCTION = "get_random_image_path" + CATEGORY = "🥭 芒果节点/文件" + + def get_random_image_path(self, directory_path, sort_mode, seed) -> tuple: + if not os.path.isdir(directory_path): + raise NotADirectoryError( + f"'{directory_path}' is not a valid directory path.") + + valid_extensions = (".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".webp") + files = [] + + for root, dirs, files_in_dir in os.walk(directory_path): + for file_name in files_in_dir: + full_file_path = os.path.join(root, file_name) + if file_name.lower().endswith(valid_extensions): + files.append(full_file_path) + + if not files: + raise FileNotFoundError( + f"No image files found in directory: {directory_path}") + + # 根据排序模式选择文件 + elif sort_mode == "顺序循环": + files.sort(key=lambda x: os.path.basename(x).lower()) + path = files[self.current_index] + self.current_index = (self.current_index + 1) % len(files) + else: # 完全随机模式 + path = random.choice(files) + + image = Image.open(path) + image = ImageOps.exif_transpose(image) + + # 处理alpha通道 + if image.mode == 'RGBA': + rgb = image.convert('RGB') + alpha = image.split()[3] + + image = np.array(rgb).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + + mask = np.array(alpha).astype(np.float32) / 255.0 + mask = torch.from_numpy(mask)[None,] + else: + image = image.convert('RGB') + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + + mask = torch.ones((1, image.shape[2], image.shape[3]), dtype=torch.float32) + + # 获取对应的txt文件内容 + txt_path = os.path.splitext(path)[0] + '.txt' + text_content = "" + try: + if os.path.exists(txt_path): + with open(txt_path, 'r', encoding='utf-8') as f: + text_content = f.read().strip() + else: + text_content = "No corresponding text file found" + except Exception as e: + text_content = f"Error reading text file: {str(e)}" + + return (image, mask, text_content) + + +video_extensions = ('webm', 'mp4', 'mkv', 'gif') + + +class RandomVideoPathNode: + def __init__(self): + self.current_seed = 0 + self.current_index = 0 + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "directory_path": ("STRING", {"default": ""}), + "sort_mode": (["完全随机", "顺序循环"],), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + }, + } + + @classmethod + def IS_CHANGED(cls, **kwargs): + return float("NaN") + + RETURN_TYPES = ("IMAGE", "STRING", "STRING") + RETURN_NAMES = ("images", "path", "text_content") + FUNCTION = "get_random_video_path" + CATEGORY = "🥭 芒果节点/文件" + + def get_random_video_path(self, directory_path, sort_mode, seed) -> tuple: + if not os.path.isdir(directory_path): + raise NotADirectoryError( + f"'{directory_path}' is not a valid directory path.") + + files = [] + for root, dirs, files_in_dir in os.walk(directory_path): + for file_name in files_in_dir: + full_file_path = os.path.join(root, file_name) + if file_name.lower().endswith(video_extensions): + files.append(full_file_path) + + if not files: + raise FileNotFoundError( + f"No video files found in directory: {directory_path}") + + # 根据排序模式选择文件 + if sort_mode == "顺序循环": + files.sort(key=lambda x: os.path.basename(x).lower()) + path = files[self.current_index] + self.current_index = (self.current_index + 1) % len(files) + else: # 完全随机模式 + random.seed(seed) + path = random.choice(files) + + images = FrameGenerator(path) + + # 获取对应的txt文件内容 + txt_path = os.path.splitext(path)[0] + '.txt' + text_content = "" + try: + if os.path.exists(txt_path): + with open(txt_path, 'r', encoding='utf-8') as f: + text_content = f.read().strip() + else: + text_content = "No corresponding text file found" + except Exception as e: + text_content = f"Error reading text file: {str(e)}" + + return (images, path, text_content) + + +def get_video_frames(video_path): + video_cap = cv2.VideoCapture(video_path) + + if not video_cap.isOpened(): + raise ValueError(f"Could not open video file: {video_path}") + + frames = [] + while True: + ret, frame = video_cap.read() + if not ret: + break + frames.append(frame) + + video_cap.release() + return frames + + +class FrameGenerator: + def __init__(self, video_path): + self.video_path = video_path + self.frames = self._load_frames() + + def _load_frames(self): + video_cap = cv2.VideoCapture(self.video_path) + if not video_cap.isOpened(): + raise ValueError(f"Could not open video file: {self.video_path}") + + frames = [] + while True: + ret, frame = video_cap.read() + if not ret: + break + + # Convert frame from BGR to RGB + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + # Convert frame to a torch tensor and normalize it + frame_tensor = torch.from_numpy(frame).float() / 255.0 + + frames.append(frame_tensor) + + video_cap.release() + return frames + + def __len__(self): + return len(self.frames) + + def __getitem__(self, index): + return self.frames[index] + + def __iter__(self): + return iter(self.frames) + + +NODE_CLASS_MAPPINGS = { + "Random Video Path": RandomVideoPathNode, + "Random Image Path": RandomImagePathNode, + "Random File Path": RandomFilePathNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "Random Video Path": "随机视频 🥭", + "Random Image Path": "随机图片 🥭", + "Random File Path": "随机文件 🥭", +} diff --git a/README.md b/README.md index 06b8b47..517bf2a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,44 @@ -# ComfyUI-Mango-Random-node -comfyui Random read node, can specify the directory, random or sequential read image, text and video content +# 🥭 Mango Random Nodes 🥭 + +Welcome to **Comfyui Mango Random Nodes**! 🚀 This is a random file node tool designed for ComfyUI, making your workflow more flexible and interesting. Whether for creative projects or daily use, our tools help you easily access random files and images. + +## Included Nodes 🌟 + +- 🎲 **Random File Path** + Randomly selects a file from a specified directory and returns the file path as a string. + +- 🖼️ **Random Image Path** + Gets images and their corresponding text content (with the same filename) from a specified folder. Supports two modes: + - Sequential Cycle: Outputs files in order by filename + - Fully Random: Randomly selects and outputs images + +- 🎬 **Random Video Path** + Gets videos and their corresponding text content (with the same filename) from a specified folder. Supports two modes: + - Sequential Cycle: Outputs files in order by filename + - Fully Random: Randomly selects and outputs videos + +## Installation 🛠️ + +### Method 1: Clone Repository to `custom_nodes` Folder + +1. **Clone Repository:** + Open Command Prompt or PowerShell and run: + ```bash + git clone https://github.com/mango-rgb/ComfyUI-Mango-Random-node + ``` + Replace `path/to/comfyui` with your actual ComfyUI installation path. + +2. **Restart ComfyUI:** + Restart the ComfyUI server to load the new nodes. + +### Method 2: Install via ComfyUI Manager + +1. **Open ComfyUI Manager** + +2. **Search for** `Mango Random Nodes` + +3. **Install Nodes:** + Click the `Install` button to install the nodes. + +4. **Restart ComfyUI:** + Restart the ComfyUI server after installation. \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a72574e --- /dev/null +++ b/__init__.py @@ -0,0 +1,20 @@ +# 从当前包的 Mango_random_nodes 模块导入节点相关的映射字典 +from .Mango_random_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +import random + +# 定义一个包含有趣的技术相关随机提示语的列表 +tech_rambling = [ + "Zap zap zoom!", "Sproing-a-ling!", "Flux capacitor charged!", "Circuit party started!", + "Electrons dancing!", "Voltage va-va-voom!", "Capacitor doing the cha-cha!", "Resistor raving!" +] + +# 打印带颜色的欢迎信息,使用随机的技术提示语 +print(f"\033[1;34m[Mango Suite]: 🥭🥭🥭 \033[96m\033[3m{random.choice(tech_rambling)}\033[0m 🥭🥭🥭") +# 打印已激活的节点数量信息 +print(f"\033[1;34m[Mango Suite]:\033[0m Activated \033[96m{len(NODE_CLASS_MAPPINGS)}\033[0m file nodes.") + +# 定义此包可以导出的名称列表 +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] + +# 定义web目录的路径常量 +WEB_DIRECTORY = "./web" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..60e8d38 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "comfyui-mango-random" +description = "🥭 Mango Random Nodes - A collection of random file selection nodes with image mask support" +version = "1.0.0" +license = {file = "LICENSE"} + +[project.urls] +Repository = "https://github.com/mango-rgb/ComfyUI-Mango-Random-node" + +[tool.comfy] +PublisherId = "mango-rgb" +DisplayName = "ComfyUI-Mango-Random" +Icon = "🥭" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..22b32b3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +opencv-python +imageio-ffmpeg