Compare commits

...
3 Commits
Author SHA1 Message Date
audioscavenger ab2410ba95 release 2.51 datetime
modified:   README.md
    	modified:   save_image_extended.py
	modified:   README.md
	modified:   save_image_extended.py
2024-05-23 11:53:33 -07:00
audioscavenger 6369fb315f release 2.51 datetime
modified:   README.md
	modified:   save_image_extended.py
2024-05-23 11:26:37 -07:00
audioscavenger fc67d2b6b0 release 2.50 JXL
modified:   README.md
    	modified:   assets/save-image-extended-comfyui-example.png
    	modified:   save_image_extended.py
	modified:   README.md
	modified:   save_image_extended.py
2024-05-23 09:41:38 -07:00
3 changed files with 52 additions and 20 deletions
+21 -2
View File
@@ -48,6 +48,7 @@ git clone https://github.com/audioscavenger/save-image-extended-comfyui
```
## Parameters / Usage
| Attribute | Description |
| --- | --- |
| `filename_prefix` | String prefix added to files. |
@@ -67,6 +68,8 @@ git clone https://github.com/audioscavenger/save-image-extended-comfyui
Unknown key names in `filename_keys` and `foldername_keys` are treated as custom strings: if you enter `wrongNumber.attribute`, you will get `attribute` in your filename.
Datetime UNIX format is now included! `%Y-%m-%d` or `%F` etc
## Node inputs
- `images` - The generated images.
@@ -75,9 +78,21 @@ Optional:
- `positive_text_opt` - Optional string input for when using custom nodes for positive prompt text.
- `negative_text_opt` - Optional string input for when using custom nodes for negative prompt text.
## Automatic folder names and date/time in names
## Automatic date/time conversion in file/folder names
Converts [unix datetime formats](https://www.man7.org/linux/man-pages/man1/date.1.html):
| Unix datetime | Example | Comment |
| --- | --- | --- |
| %F or %Y-%m-%d | 2024-05-22 | |
| %D | 05/22/24 | This effectively creates subfolders |
| %F %H-%M-%S | 2024-05-22 09-13-58 | |
| %Y/%V | 2024/21 | year subfolder / ISO week number subfolder |
<p align="center">
<img src="assets/save-image-extended-comfyui-example-2.png" />
</p>
Convert the 'prefix' parameters to inputs (right click in the node and select e.g 'convert foldername_prefix to input'. Then attach the 'Get Date Time String' custom node from JPS to these inputs. This way a new folder name can be automatically generated each time generate is pressed.
#
Disclaimer: Does not check for illegal characters entered in file or folder names. May not be compatible with every other custom node, depending on changes in the `prompt` object.
Tested and working with default samplers, Efficiency nodes, UltimateSDUpscale, ComfyRoll, composer, NegiTools, and 45 other nodes.
@@ -142,8 +157,12 @@ TODO:
- [ ] offer to place the counter anywhere, as a key in filename_keys
- [ ] files can get out of order if prefixes change... that is expected, but is this what we want? another reason to have the counter place anywhere we want
### release 2.51 💾
- added unix datetime formats
### release 2.50 💾
- added JXL support
- added 'BOOLEAN' support
### release 2.46 💾
- bug discovered with *rgthree's Ksampler Config*: using `steps_total` as an input to a Ksampler, will issue the index of the output, instead of the steps value ("\[nodeNum, 0]" instead of steps value). FIX: use `steps_total` instead of `steps`!
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

+31 -18
View File
@@ -44,7 +44,7 @@ original_locale = locale.setlocale(locale.LC_TIME, '')
# class SaveImageExtended -------------------------------------------------------------------------------
class SaveImageExtended:
version = 2.50
version = 2.51
type = 'output'
png_compress_level = 9
@@ -115,9 +115,9 @@ class SaveImageExtended:
'basic, sampler, prompt',
'basic, models, sampler, prompt'
], {'default': self.save_job_data}),
'job_data_per_image': ([False, True], {'default': self.job_data_per_image}),
'job_data_per_image': ('BOOLEAN', {"default": self.job_data_per_image}),
'job_custom_text': ('STRING', {'default': self.job_custom_text, 'multiline': False}),
'save_metadata': ([True, False], {'default': self.save_metadata}),
'save_metadata': ('BOOLEAN', {'default': self.save_metadata}),
'counter_digits': ('INT', {
"default": self.counter_digits,
"min": 1,
@@ -126,8 +126,8 @@ class SaveImageExtended:
"display": "silder"
}),
'counter_position': (self.counter_positions, {'default': self.counter_position}),
'one_counter_per_folder': ([True, False], {'default': self.one_counter_per_folder}),
'image_preview': ([True, False], {'default': self.image_preview}),
'one_counter_per_folder': ('BOOLEAN', {'default': self.one_counter_per_folder}),
'image_preview': ('BOOLEAN', {'default': self.image_preview}),
'output_ext': (self.output_exts, {'default': self.output_ext}),
},
'optional': {
@@ -232,8 +232,12 @@ class SaveImageExtended:
# Fractions and Superscripts '⅔','2²' False True True
# Roman Numerals 'ↁ' False False True
# --------------------------- --------- ----------- --------- -----------
def generate_custom_name(self, keys_to_extract, prefix, delimiter, prompt):
custom_name = prefix
def generate_custom_name(self, keys_to_extract, prefix, delimiter, prompt, timestamp=datetime.now()):
if '%' in prefix:
custom_name = timestamp.strftime(prefix)
else:
custom_name = prefix
if prompt is not None and keys_to_extract != ['']:
found_values = {}
# print(f"debug generate_custom_name: --prefix: {prefix}")
@@ -248,17 +252,21 @@ class SaveImageExtended:
# 'inputs': {'cfg': 1.6,
# 'denoise': 1.0, ...
for key in keys_to_extract:
if not key: continue
value = None
node, nodeKey = None, None
# check if this is a subfolder: starts with ./ or /, can also end with /
if '/' in key:
# we also exclude datetime formats like "%A %d. %B %Y"
if '/' in key and not '%' in key:
# key is a subfolder: ./subfolder or ../subfolder or /subfolder
value = key
else:
splitKey = key.split('.')
# we also exclude cases like "..string" or "sting.string.x" etc
if len(splitKey) == 2:
# we also exclude datetime formats like "%A %d. %B %Y"
if len(splitKey) == 2 and not '%' in key:
# key has the form string.string
if '' not in splitKey:
# key has the form string.string
@@ -280,9 +288,13 @@ class SaveImageExtended:
# key is in the form ".string" or "string." or "." - we won't clean that up and keep as is, maybe it's a separator
value = key
else:
# nodeKey is not a folder, has no dot, or multiple dots, could be a valid key to find, could be a fixed string - keep as is
nodeKey = key
self.find_keys_recursively(prompt, [nodeKey], found_values)
# could be a datetime format
if '%' in key:
value = timestamp.strftime(key)
# nodeKey is not a datetime, folder, has no dot, or multiple dots, could be a valid key to find, could be a fixed string - keep as is
else:
nodeKey = key
self.find_keys_recursively(prompt, [nodeKey], found_values)
# is key num.widget_name
# is key subfolder
@@ -326,7 +338,7 @@ class SaveImageExtended:
return custom_name.strip(delimiter).strip('.').strip('/').strip(delimiter)
def save_job_to_json(self, save_job_data, prompt, filename_prefix, positive_text_opt, negative_text_opt, job_custom_text, resolution, output_path, filename):
def save_job_to_json(self, save_job_data, prompt, filename_prefix, positive_text_opt, negative_text_opt, job_custom_text, resolution, output_path, filename, timestamp=datetime.now()):
prompt_keys_to_save = {}
if 'basic' in save_job_data:
if len(filename_prefix) > 0:
@@ -411,7 +423,7 @@ class SaveImageExtended:
print(f"SaveImageExtended {version} error: The file {json_file_path} is empty or malformed. Initializing with empty data.")
existing_data = {}
timestamp = datetime.now().strftime('%c')
timestamp = timestamp.strftime('%c')
new_entry = {}
new_entry[timestamp] = prompt_keys_to_save
existing_data.update(new_entry)
@@ -568,8 +580,9 @@ class SaveImageExtended:
################################## UNCOMMENT HERE TO SEE THE ENTIRE PROMPT
# pprint.pprint(prompt)
##########################################################################
custom_filename = self.generate_custom_name(filename_keys_to_extract, filename_prefix, delimiter, prompt)
custom_foldername = self.generate_custom_name(foldername_keys_to_extract, foldername_prefix, delimiter, prompt)
timestamp = datetime.now()
custom_filename = self.generate_custom_name(filename_keys_to_extract, filename_prefix, delimiter, prompt, timestamp)
custom_foldername = self.generate_custom_name(foldername_keys_to_extract, foldername_prefix, delimiter, prompt, timestamp)
# Get set resolution value
i = 255. * images[0].cpu().numpy()
@@ -601,14 +614,14 @@ class SaveImageExtended:
self.save_image(image_path, img, prompt, save_metadata, extra_pnginfo)
if save_job_data != 'disabled' and job_data_per_image:
self.save_job_to_json(save_job_data, prompt, filename_prefix, positive_text_opt, negative_text_opt, job_custom_text, resolution, output_path, f'{file.removesuffix(output_ext)}.json')
self.save_job_to_json(save_job_data, prompt, filename_prefix, positive_text_opt, negative_text_opt, job_custom_text, resolution, output_path, f'{file.removesuffix(output_ext)}.json', timestamp)
subfolder = self.get_subfolder_path(image_path, self.output_dir)
results.append({ 'filename': file, 'subfolder': subfolder, 'type': self.type})
counter += 1
if save_job_data != 'disabled' and not job_data_per_image:
self.save_job_to_json(save_job_data, prompt, filename_prefix, positive_text_opt, negative_text_opt, job_custom_text, resolution, output_path, 'jobs.json')
self.save_job_to_json(save_job_data, prompt, filename_prefix, positive_text_opt, negative_text_opt, job_custom_text, resolution, output_path, 'jobs.json', timestamp)
except OSError as e:
print(f"SaveImageExtended {version} error: An error occurred while creating the subfolder or saving the image: {e}")