diff --git a/comfy_extras/nodes_images.py b/comfy_extras/nodes_images.py index 392aea32c..baf33311a 100644 --- a/comfy_extras/nodes_images.py +++ b/comfy_extras/nodes_images.py @@ -106,7 +106,7 @@ class SaveAnimatedWEBP: def INPUT_TYPES(s): return {"required": {"images": ("IMAGE", ), - "filename_prefix": ("STRING", {"default": "ComfyUI"}), + "filename_prefix": ("STRING", {"default": "%date:yyyy-MM-dd%/ComfyUI"}), "fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}), "lossless": ("BOOLEAN", {"default": True}), "quality": ("INT", {"default": 80, "min": 0, "max": 100}), @@ -171,7 +171,7 @@ class SaveAnimatedPNG: def INPUT_TYPES(s): return {"required": {"images": ("IMAGE", ), - "filename_prefix": ("STRING", {"default": "ComfyUI"}), + "filename_prefix": ("STRING", {"default": "%date:yyyy-MM-dd%/ComfyUI"}), "fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}), "compress_level": ("INT", {"default": 4, "min": 0, "max": 9}) }, diff --git a/folder_paths.py b/folder_paths.py index 9c96540e3..0b242681b 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import re import time import mimetypes import logging @@ -431,45 +432,100 @@ def get_save_image_path(filename_prefix: str, output_dir: str, image_width=0, im prefix = filename[:prefix_len + 1] try: digits = int(filename[prefix_len + 1:].split('_')[0]) - except: + except ValueError: digits = 0 return digits, prefix - def compute_vars(input: str, image_width: int, image_height: int) -> str: - input = input.replace("%width%", str(image_width)) - input = input.replace("%height%", str(image_height)) - now = time.localtime() - input = input.replace("%year%", str(now.tm_year)) - input = input.replace("%month%", str(now.tm_mon).zfill(2)) - input = input.replace("%day%", str(now.tm_mday).zfill(2)) - input = input.replace("%hour%", str(now.tm_hour).zfill(2)) - input = input.replace("%minute%", str(now.tm_min).zfill(2)) - input = input.replace("%second%", str(now.tm_sec).zfill(2)) - return input + def compute_vars(input_str: str, image_width: int, image_height: int) -> str: + date_pattern = re.compile(r'%date:(.+?)%') - if "%" in filename_prefix: - filename_prefix = compute_vars(filename_prefix, image_width, image_height) + def replace_date(match): + date_format = match.group(1) + + dateformat_conversion_map = { + "yyyy": "%Y", + "yy": "%y", + "MM": "%m", + "dd": "%d", + "hh": "%H", + "mm": "%M", + "ss": "%S", + } + + strftime_format = date_format + for old, new in dateformat_conversion_map.items(): + strftime_format = strftime_format.replace(old, new) + + now = time.localtime() + formatted_date = time.strftime(strftime_format, now) + return formatted_date + + input_str = date_pattern.sub(replace_date, input_str) + + now = time.localtime() + input_str = input_str.replace("%year%", str(now.tm_year)) + input_str = input_str.replace("%month%", str(now.tm_mon).zfill(2)) + input_str = input_str.replace("%day%", str(now.tm_mday).zfill(2)) + input_str = input_str.replace("%hour%", str(now.tm_hour).zfill(2)) + input_str = input_str.replace("%minute%", str(now.tm_min).zfill(2)) + input_str = input_str.replace("%second%", str(now.tm_sec).zfill(2)) + + input_str = input_str.replace("%width%", str(image_width)) + input_str = input_str.replace("%height%", str(image_height)) + + return input_str + + filename_prefix = compute_vars(filename_prefix, image_width, image_height) subfolder = os.path.dirname(os.path.normpath(filename_prefix)) filename = os.path.basename(os.path.normpath(filename_prefix)) + if not subfolder and ("%date:" in filename_prefix or "%year%" in filename_prefix or + "%month%" in filename_prefix or "%day%" in filename_prefix or + "%hour%" in filename_prefix or "%minute%" in filename_prefix or + "%second%" in filename_prefix or "ComfyUI" in filename_prefix): + import locale + try: + locale.setlocale(locale.LC_TIME, '') + default_date_format = locale.nl_langinfo(locale.D_FMT).replace("%y", "%Y") + if os.name == 'nt': + default_date_format = default_date_format.replace("/", "-").replace("\\", "-").replace(":","-") + subfolder = time.strftime(default_date_format) + + except (locale.Error, AttributeError): + subfolder = time.strftime("%Y-%m-%d") + if os.name == 'nt': + subfolder = subfolder.replace("/", "-").replace("\\", "-").replace(":","-") + full_output_folder = os.path.join(output_dir, subfolder) if os.path.commonpath((output_dir, os.path.abspath(full_output_folder))) != output_dir: - err = "**** ERROR: Saving image outside the output folder is not allowed." + \ - "\n full_output_folder: " + os.path.abspath(full_output_folder) + \ - "\n output_dir: " + output_dir + \ - "\n commonpath: " + os.path.commonpath((output_dir, os.path.abspath(full_output_folder))) + err = ("**** ERROR: Saving outside the output folder is not allowed." + f"\n full_output_folder: {os.path.abspath(full_output_folder)}" + f"\n output_dir: {output_dir}" + f"\n commonpath: {os.path.commonpath((output_dir, os.path.abspath(full_output_folder)))}") logging.error(err) raise Exception(err) try: - counter = max(filter(lambda a: os.path.normcase(a[1][:-1]) == os.path.normcase(filename) and a[1][-1] == "_", map(map_filename, os.listdir(full_output_folder))))[0] + 1 - except ValueError: - counter = 1 + files = [f for f in os.listdir(full_output_folder) if os.path.isfile(os.path.join(full_output_folder, f))] + mapped_files = [map_filename(f) for f in files] + filtered_files = [ + (digits, prefix) for digits, prefix in mapped_files + if os.path.normcase(prefix[:-1]) == os.path.normcase(filename) and prefix[-1] == "_" + ] + + if filtered_files: + counter = max(digits for digits, _ in filtered_files) + 1 + else: + counter = 1 except FileNotFoundError: os.makedirs(full_output_folder, exist_ok=True) counter = 1 + except OSError as e: + logging.error(f"Error accessing directory: {e}") + raise + return full_output_folder, filename, counter, subfolder, filename_prefix def get_input_subfolders() -> list[str]: diff --git a/nodes.py b/nodes.py index 3fa543294..03a2d5fcd 100644 --- a/nodes.py +++ b/nodes.py @@ -1583,7 +1583,7 @@ class SaveImage: return { "required": { "images": ("IMAGE", {"tooltip": "The images to save."}), - "filename_prefix": ("STRING", {"default": "ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."}) + "filename_prefix": ("STRING", {"default": "%date:yyyy-MM-dd%/ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as '%date:yyyy-MM-dd%', '%year%-%month%-%day%' or %Empty Latent Image.width% to include values from nodes."}) }, "hidden": { "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"