From 57c5d9d859dfb9fc9df94bbe216fcbd6d5a7c360 Mon Sep 17 00:00:00 2001 From: kk8bit Date: Sun, 20 Sep 2026 20:57:03 +0800 Subject: [PATCH] Let batch results keep their original filenames Load Image Folder now also returns the source filenames, one per image, appended to the existing list outputs so they stay index-aligned (an image that fails to load is skipped from all three, otherwise downstream pairing silently shifts). Custom Save Image gained an exact_filename toggle. It always appended a timestamp and an index to the prefix, so feeding it an original name still produced photo_1726824000000_000.png. With the toggle on the prefix is the filename. The name reaching that toggle comes from an input, so it is reduced to a basename before use and can't escape the output directory, and a colliding name gets a _1 suffix rather than overwriting what's there. Closes #24. --- CHANGELOG.md | 4 ++++ README.md | 8 ++++++++ nodes/custom_save_image.py | 28 +++++++++++++++++++++++++--- nodes/load_image_folder.py | 15 ++++++++++----- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a63c5d..16cbd80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [未发布 Unreleased] +- 批量图片载入节点新增 `filenames` 输出(原文件名),配合自定义图像保存节点新增的 `exact_filename` 开关,即可按原文件名保存批处理结果 +- Load Image Folder gained a `filenames` output, and Custom Save Image an `exact_filename` toggle, so batch results can be saved under their original names + ## [0.71.0] - 2026-09-20 - 自定义图标新增开关,关闭后恢复 ComfyUI 默认图标;并把该功能的措辞统一为「浏览器标签页图标」——它一直只改标签页图标,不会替换界面左上角的 ComfyUI 标志 - Added a toggle for the custom icon, which restores ComfyUI's own icon when off, and renamed the feature to "browser tab icon": it only ever changed the tab icon, never the ComfyUI logo in the interface diff --git a/README.md b/README.md index 287ac06..6192093 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ This is a custom node utility package developed for ComfyUI. Initially created f ## 最新更新 / Last Update +### [未发布 Unreleased] +- 批量图片载入节点新增 `filenames` 输出(原文件名),配合自定义图像保存节点新增的 `exact_filename` 开关,即可按原文件名保存批处理结果 +- Load Image Folder gained a `filenames` output, and Custom Save Image an `exact_filename` toggle, so batch results can be saved under their original names + ### [0.71.0] - 2026-09-20 - 自定义图标新增开关,关闭后恢复 ComfyUI 默认图标;并把该功能的措辞统一为「浏览器标签页图标」——它一直只改标签页图标,不会替换界面左上角的 ComfyUI 标志 - Added a toggle for the custom icon, which restores ComfyUI's own icon when off, and renamed the feature to "browser tab icon": it only ever changed the tab icon, never the ComfyUI logo in the interface @@ -158,6 +162,8 @@ https://github.com/user-attachments/assets/b8d1d3f0-04d1-46c5-968a-e433778b73e6 Provide batch loading and batch processing of images. - 支持 image 和 mask 的批量输出。 Support batch output of images and masks. +- 新增 `filenames` 输出,逐张给出图片的原文件名(不含扩展名)。配合 𝙆 Custom Save Image 的 `exact_filename` 开关,即可按原文件名保存处理结果。 + Adds a `filenames` output with each image's original name (without extension). Pair it with the `exact_filename` toggle on 𝙆 Custom Save Image to save results under their original names. ## 无线数据传输节点 Set & Get @@ -264,6 +270,8 @@ https://github.com/user-attachments/assets/b8d1d3f0-04d1-46c5-968a-e433778b73e6 - Provides high-quality JPG saving options with customizable compression quality. - Automatically generates unique filenames to prevent overwriting existing files. - Suitable for workflows requiring precise control over output images. +- `exact_filename` 开关:开启后 `filename_prefix` 就是最终文件名,不再追加时间戳。重名会自动加 `_1`、`_2` 后缀,不会覆盖已有文件。 + `exact_filename`: when on, `filename_prefix` is used as the final filename with no timestamp appended. Existing files are never overwritten — a `_1`, `_2` suffix is added instead. ### 变量使用说明 Variable Usage Notes diff --git a/nodes/custom_save_image.py b/nodes/custom_save_image.py index 8f0d723..595603e 100644 --- a/nodes/custom_save_image.py +++ b/nodes/custom_save_image.py @@ -28,6 +28,11 @@ class CustomSaveImage: "author": ("STRING", {"default": ""}), "copyright_info": ("STRING", {"default": ""}), "save_metadata": ("BOOLEAN", {"default": False}), + "exact_filename": ("BOOLEAN", { + "default": False, + "tooltip": "Use filename_prefix as the exact filename instead of appending a timestamp. " + "Connect the 'filenames' output of Load Image Folder to save with the original names." + }), }, "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, } @@ -38,7 +43,8 @@ class CustomSaveImage: CATEGORY = "KayTool" def save_images(self, images, preview_only=False, filename_prefix="Custom_Save_Image", save_metadata=True, format="PNG", - jpg_quality=95, author="", copyright_info="", color_profile="sRGB IEC61966-2.1", prompt=None, extra_pnginfo=None): + jpg_quality=95, author="", copyright_info="", color_profile="sRGB IEC61966-2.1", exact_filename=False, + prompt=None, extra_pnginfo=None): temp_dir = folder_paths.get_temp_directory() os.makedirs(temp_dir, exist_ok=True) @@ -123,8 +129,11 @@ class CustomSaveImage: }) if not preview_only: - final_filename = f"{base_prefix}_{self.get_unique_filename(idx)}.{format.lower()}" - final_full_path = os.path.join(output_dir, final_filename) + if exact_filename: + final_full_path = self.resolve_exact_path(output_dir, base_prefix, format.lower()) + else: + final_filename = f"{base_prefix}_{self.get_unique_filename(idx)}.{format.lower()}" + final_full_path = os.path.join(output_dir, final_filename) shutil.copy2(temp_full_path, final_full_path) return {"ui": {"images": results}, "status": "Images saved successfully"} @@ -139,6 +148,19 @@ class CustomSaveImage: with open(path, "rb") as f: return f.read() + def resolve_exact_path(self, output_dir, name, ext): + """把 base_prefix 当成完整文件名用,但不允许它逃出 output_dir,也不覆盖已有文件。""" + # 文件名可能来自上游输入,必须剥掉路径成分,挡住 ../ 之类的写法 + name = os.path.basename(name.replace("\\", "/").rstrip("/")).strip() + name = name.lstrip(".") or "Custom_Save_Image" + + candidate = os.path.join(output_dir, f"{name}.{ext}") + counter = 1 + while os.path.exists(candidate): + candidate = os.path.join(output_dir, f"{name}_{counter}.{ext}") + counter += 1 + return candidate + def get_unique_filename(self, idx): import time return f"{int(time.time() * 1000)}_{idx:03d}" diff --git a/nodes/load_image_folder.py b/nodes/load_image_folder.py index 3cda198..378bddd 100644 --- a/nodes/load_image_folder.py +++ b/nodes/load_image_folder.py @@ -21,9 +21,9 @@ class LoadImageFolder: } CATEGORY = "KayTool" - RETURN_TYPES = ("IMAGE", "MASK") - RETURN_NAMES = ("images", "masks") - OUTPUT_IS_LIST = (True, True) + RETURN_TYPES = ("IMAGE", "MASK", "STRING") + RETURN_NAMES = ("images", "masks", "filenames") + OUTPUT_IS_LIST = (True, True, True) FUNCTION = "load_images" OUTPUT_NODE = True @@ -57,6 +57,9 @@ class LoadImageFolder: images = [] masks = [] + # 与 images 严格同序同长:某张图读失败时三者一起跳过, + # 否则下游按索引配对就会张冠李戴。 + filenames = [] for image_path in image_files: @@ -78,6 +81,8 @@ class LoadImageFolder: images.append(image_tensor) masks.append(mask.unsqueeze(0)) + # 不带扩展名:下游保存节点自己决定存成 PNG 还是 JPG + filenames.append(os.path.splitext(os.path.basename(image_path))[0]) except Exception as e: print(f"Error loading {image_path}: {str(e)}") @@ -87,9 +92,9 @@ class LoadImageFolder: if not images: placeholder_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32) placeholder_mask = torch.zeros((1, 64, 64), dtype=torch.float32) - return ([placeholder_image], [placeholder_mask]) + return ([placeholder_image], [placeholder_mask], ["placeholder"]) - return (images, masks) + return (images, masks, filenames) @classmethod def IS_CHANGED(s, path, subfolders):