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.
This commit is contained in:
kk8bit
2026-09-20 20:57:03 +08:00
parent dbb2171274
commit 57c5d9d859
4 changed files with 47 additions and 8 deletions
+4
View File
@@ -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
+8
View File
@@ -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
+25 -3
View File
@@ -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}"
+10 -5
View File
@@ -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):