Compare commits

...
6 Commits
Author SHA1 Message Date
audioscavenger 61aed6e7e1 release 2.83 fixed PNG save time by fixing compression to 4
modified:   README.md
    	modified:   pyproject.toml
    	modified:   save_image_extended.py
	modified:   requirements.txt
	modified:   save_image_extended.py
2025-01-04 11:57:51 -07:00
audioscavenger 8aa85b1c15 release 2.83 fixed PNG save time by fixing compression to 4
modified:   README.md
	modified:   pyproject.toml
	modified:   save_image_extended.py
2025-01-04 11:55:33 -07:00
audioscavenger a2b9b4ed72 release 2.82 bugfix + CLIP text encode export + tooltips
modified:   README.md
        	modified:   pyproject.toml
        	modified:   save_image_extended.py
    	modified:   README.md
    	modified:   save_image_extended.py
	modified:   README.md
	modified:   save_image_extended.py
2024-12-26 13:42:53 -07:00
audioscavenger f7c06ba306 release 2.82 bugfix + CLIP text encode export
modified:   README.md
    	modified:   pyproject.toml
    	modified:   save_image_extended.py
	modified:   README.md
	modified:   save_image_extended.py
2024-12-26 09:24:39 -07:00
audioscavenger d1d0ddab43 release 2.82 bugfix: removed extra debug lines
modified:   README.md
	modified:   pyproject.toml
	modified:   save_image_extended.py
2024-12-26 08:54:58 -07:00
audioscavenger 4b1d081a2e WIP 2.81 fix for pythongosssss/ComfyUI-Custom-Scripts
modified:   README.md
	modified:   pyproject.toml
	modified:   save_image_extended.py
2024-12-19 20:21:30 -07:00
4 changed files with 124 additions and 77 deletions
+42 -17
View File
@@ -23,22 +23,22 @@ Supports those extensions: **JXL AVIF WebP jpg jpeg j2k jp2 png gif tiff bmp**
| Attribute | Description |
| --- | --- |
| `filename_prefix` | String prefix added to files. |
| `filename_keys` | Comma separated string with sampler parameters to add to filename. E.g: `sampler_name, scheduler, cfg, denoise` Added to filename in written order. `resolution` also works. `vae_name` `model_name` (upscale model), `ckpt_name` (checkpoint) are others that should work. Here you can try any parameter name of any node. As long as the parameter has the same variable name defined in the `prompt` object they should work. The same applies to `foldername_keys`. |
| `foldername_prefix` | String prefix added to folders. |
| `foldername_keys` | Comma separated string with _sampler_ parameters to add to foldername. Add more subfolders by writing a "./" separated with commas. For example: `sampler_name, ./, ckpt_name` |
| `delimiter` | **now a free field** Delimiter = 1 character, can be anything your file system supports. Windows users should still use "/" for subfolders. |
| `save_job_data` | If enabled, saves information about each job as entries in a `jobs.json` text file, inside the generated folder. Mulitple options for saving `prompt`, `basic data`, `sampler settings`, `loaded models`. |
| `job_data_per_image` | When enabled, saves individual job data files for each image. |
| `job_custom_text` | Custom string to save along with the job data. Right click the node and convert to input to connect with another node. |
| `filename_prefix` | Fixed string prefixed to file name. |
| `filename_keys` | Comma separated string with sampler parameters to add to filename. E.g: `sampler_name, scheduler, cfg, denoise` Added to filename in written order. `resolution` also works. `vae_name` `model_name` (upscale model), `ckpt_name` (checkpoint) are others that should work. Here you can try any parameter name of any node. As long as the parameter has the same variable name defined in the `prompt` object, they should work. The same applies to `foldername_keys`. |
| `foldername_prefix` | Fixed string prefixed to subfolders. |
| `foldername_keys` | Same rules as for `filename_keys`. Create subfolders by using `/` or `../` etc. |
| `delimiter` | **now a free field** Delimiter = any string you like. You can also use `/` to create subfolders. |
| `save_job_data` | Saves information about each job as entries in a `jobs.json` text file, inside the generated subfolder. Multiple options for its content: `prompt`, `basic data`, `sampler settings`, `loaded models`. |
| `job_data_per_image` | Saves individual job data file per image. |
| `job_custom_text` | Custom string to save along with the job data. |
| `save_metadata` | Saves metadata into the image. |
| `counter_digits` | Number of digits used for the image counter. `3` = image_001.png. Will adjust the counter if files are deleted. Looks for the highest number in the folder, does not fill gaps. **Can be disabled** when == 0 |
| `counter_position` | Image counter first or last in the filename. |
| `one_counter_per_folder` | Toggles the counter. Either one counter per folder, or resets when a parameter/prompt changes. |
| `counter_digits` | Number of digits used for the image counter. `3` = image_001.png, based on highest number in the subfolder, ignores gaps. **Can be disabled** when == 0. |
| `counter_position` | Image counter postition: image_001.png or 001_image.png. |
| `one_counter_per_folder` | Toggles one counter per subfolder, or resets when a parameter/prompt changes. |
| `image_preview` | Turns the image preview on and off. |
| `output_ext` | File extension: PNG by default, or WEBP (coming soon). |
| `quality` | Quality for JPEG/JXL/WebP/AVIF/J2K formats; default 90 (AVIF only needs 60 for same results). |
| `named_keys` | Prefix each value by its key name: prefix-seed=123456-width=1024-etc-0001.avif |
| `output_ext` | File extension: WEBP by default, AVIF, PNG, JXL, JPG, etc. |
| `quality` | Quality for JPEG/JXL/WebP/AVIF/J2K formats; Quality is relative to each format. Example: AVIF 60 is same quality as WebP 90. PNG compression is fixed at 4 and not affected by this. PNG compression times skyrocket above level 4 for zero benefits on filesize. |
| `named_keys` | Prefix each value by its key name. Example: prefix-seed=123456-width=1024-cfg=5.0-0001.avif |
- Unknown key names in `filename_keys` and `foldername_keys` are treated as fixed strings
@@ -69,8 +69,8 @@ Supports those extensions: **JXL AVIF WebP jpg jpeg j2k jp2 png gif tiff bmp**
- `images` - The generated images.
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.
- `positive_text_opt` - Optional string saved as `positive_text_opt` in job.json when `save_job_data`=True.
- `negative_text_opt` - Optional string saved as `negative_text_opt` in job.json when `save_job_data`=True.
## Automatic date/time conversion in file/folder names
@@ -129,7 +129,7 @@ Disclaimer: Does not check for illegal characters entered in file or folder name
Tested and working with default samplers, Efficiency nodes, UltimateSDUpscale, ComfyRoll, composer, NegiTools, and 45 other nodes.
#
Quality and compression settings: default is 90, 100 will activate **lossless** for AVIF and WEBP only.
Quality and compression settings: min = 1, default is **90**, max = 100 will activate **lossless** for AVIF and WEBP only.
Quick comparison of size per extension, for the same 512x512 picture, with similar visual quality:
| Ext | Compression | Maker | Size | Compression |
@@ -145,6 +145,20 @@ Quick comparison of size per extension, for the same 512x512 picture, with simil
| avif | 60 | PIL | 47353 | 89% |
| avif | 60 | Imagick | 33691 | 92% |
#
PNG compression 0-9 is fixed at level 4 for the following reason: ***zero** compression benefits above level 4*, while saving time skyrockets. Test results below for a 2560x1440 image on an Intel i7-9700:
| Compression | time | Size |
| --- | --- | --- |
| 0 | 0.5s | 10.6 MB |
| 1 | 0.6s | 4.6 MB |
| 2 | 0.6s | 4.5 MB |
| 3 | 0.6s | 4.4 MB |
| **4** | **0.7s** | **4.0 MB** |
| 5 | 1.1s | 4.0 MB |
| 6 | 1.9s | 4.0 MB |
| 7 | 2.6s | 4.0 MB |
| 9 | 10.5s | 3.9 MB |
#
About extensions WebP AVIF JPEG JXL: ComfyUI can only load PNG and WebP atm... Feel free to ask ComfyUI team to add support for AVIF/jpeg/JXL!
@@ -201,6 +215,17 @@ TODO:
- [x] PIL.Image doesn ot respect compress_level value and always output max 9 compressed images.
- [x] TODO: test import cv2 / OpenCV: https://github.com/python-pillow/Pillow/issues/5986 -> not faster then PIL
### release 2.83 💾
- quality is now also used for PNG compression: 0-90 translates to 0-9 levels of compression
### release 2.82 💾
- bugfix: removed extra debug lines
- improvement: now can extract CLIP text
- improvement: added tooltips
### release 2.81 💾
- fix for pythongosssss/ComfyUI-Custom-Scripts Checkpoint Loader bug and other nodes that store values in a dict
### release 2.80 💾
- tested OpenCV vs PIL for saving images: same speed
- various typos fixed
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "save-image-extended-comfyui"
description = "Save as JXL, AVIF, WebP, JPEG, JPEG2k, customize the folder, sub-folders, and filenames of your images!"
version = "2.80"
version = "2.83"
license = { file = "LICENSE" }
dependencies = ["piexif", "imagecodecs", "pillow", "pillow-avif-plugin", "pillow-jxl-plugin"]
+1 -1
View File
@@ -1,4 +1,4 @@
piexif
# piexif
# jpegxl support: imagecodecs will bring libjxl 0.9.0
imagecodecs
+80 -58
View File
@@ -8,16 +8,18 @@ from datetime import datetime
from pathlib import Path
import folder_paths
import pprint
import piexif
import piexif.helper
# import piexif
# import piexif.helper
# import cv2 # not paster then PIL
# import cv2 # not faster then PIL
version = 2.80
version = 2.83
avif_supported = False
jxl_supported = False
debug = False
# Avif is included in requirements.txt
try:
import pillow_avif
@@ -88,8 +90,6 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
type = 'output'
# BUG: PIL.Image doesn't respect compress_level value and always output max 9 compressed images when optimize_image = True
png_compress_level = 9
avif_quality = 60
webp_quality = 90
jpeg_quality = 90
@@ -97,8 +97,8 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
j2k_quality = 90
# tiff_quality has no impact unless you start meddling with the compressions algorithms
tiff_quality = 90
# optimize_image only works for jpeg, png anf TIFF, with like just 2% reduction in size
optimize_image = False
# optimize_image only works for jpeg, png anf TIFF, with like just 2% reduction in size; not used for PNG as it forces a level 9 compression.
optimize_image = True
filename_prefix = 'ComfyUI'
filename_keys = 'sampler_name, cfg, steps, %F %H-%M-%S'
@@ -117,7 +117,7 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
modelExtensions = ['.safetensors', '.ckpt', '.pt', '.bin', '.pth']
output_ext = '.webp'
output_exts = ['.webp', '.png', '.jpg', '.jpeg', '.j2k', '.jp2', '.gif', '.tiff', '.bmp']
# quality is a lossy compression unused by PNG/tiff/gif
# quality is a lossy compression unused by PNG/tiff/gif but also translated to integers 0-9 for PNG compression level
quality = 90
named_keys = False
@@ -154,44 +154,46 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
return {
'required': {
'images': ('IMAGE', ),
'filename_prefix': ('STRING', {'default': self.filename_prefix, 'multiline': False}),
'filename_keys': ('STRING', {'default': self.filename_keys, 'multiline': True}),
'foldername_prefix': ('STRING', {'default': self.foldername_prefix, 'multiline': False}),
'foldername_keys': ('STRING', {'default': self.foldername_keys, 'multiline': True}),
'delimiter': ('STRING', {'default': self.delimiter, 'multiline': False}),
'filename_prefix': ('STRING', {'default': self.filename_prefix, 'multiline': False, 'tooltip': "Fixed string prefixed to file name"}),
'filename_keys': ('STRING', {'default': self.filename_keys, 'multiline': True, 'tooltip': "Comma separated string with sampler parameters to add to filename. \n* Example: `sampler_name, scheduler, cfg, denoise` Added to filename in written order. \n* Example: also accepts `vae_name` `model_name` (upscale model), `ckpt_name` (checkpoint). \n* `resolution` also works. \n\n* ANY parameter name of any node will work. The same applies to `foldername_keys`"}),
'foldername_prefix': ('STRING', {'default': self.foldername_prefix, 'multiline': False, 'tooltip': "Fixed string prefixed to subfolders"}),
'foldername_keys': ('STRING', {'default': self.foldername_keys, 'multiline': True, 'tooltip': "Same rules as for `filename_keys`. Create subfolders by using `/` or `../` etc"}),
'delimiter': ('STRING', {'default': self.delimiter, 'multiline': False, 'tooltip': "Any string you like. You can also use `/` to create subfolders"}),
'save_job_data': ([
'disabled',
'prompt',
'basic, prompt',
'basic, sampler, prompt',
'basic, models, sampler, prompt'
], {'default': self.save_job_data}),
'job_data_per_image': ('BOOLEAN', {"default": self.job_data_per_image}),
'job_custom_text': ('STRING', {'default': self.job_custom_text, 'multiline': False}),
'save_metadata': ('BOOLEAN', {'default': self.save_metadata}),
], {'default': self.save_job_data, 'tooltip': "Saves information about each job as entries in a `jobs.json` text file, under the generated subfolder. \nMultiple options for its content: `prompt`, `basic data`, `sampler settings`, `loaded models`"}),
'job_data_per_image': ('BOOLEAN', {"default": self.job_data_per_image, 'tooltip': "Saves individual job data file per image"}),
'job_custom_text': ('STRING', {'default': self.job_custom_text, 'multiline': False, 'tooltip': "Custom string to save along with the job data"}),
'save_metadata': ('BOOLEAN', {'default': self.save_metadata, 'tooltip': "Saves metadata into the image"}),
'counter_digits': ('INT', {
"default": self.counter_digits,
"min": 0,
"max": 8,
"step": 1,
"display": "silder"
"display": "silder",
'tooltip': "umber of digits used for the image counter. `3` = image_001.png, based on highest number in the subfolder, ignores gaps. **Can be disabled** when == 0"
}),
'counter_position': (self.counter_positions, {'default': self.counter_position}),
'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}),
'counter_position': (self.counter_positions, {'default': self.counter_position, 'tooltip': "Image counter postition: image_001.png or 001_image.png"}),
'one_counter_per_folder': ('BOOLEAN', {'default': self.one_counter_per_folder, 'tooltip': "Toggles one counter per subfolder, or resets when a parameter/prompt changes"}),
'image_preview': ('BOOLEAN', {'default': self.image_preview, 'tooltip': "Turns the image preview on and off"}),
'output_ext': (self.output_exts, {'default': self.output_ext, 'tooltip': "File extension: WEBP by default, AVIF, PNG, JXL, JPG, etc"}),
'quality': ('INT', {
"default": self.quality,
"min": 1,
"min": 0,
"max": 100,
"step": 1,
"display": "silder"
}),
'named_keys': ('BOOLEAN', {'default': self.named_keys}),
"display": "silder",
'tooltip': "Quality for JPEG/JXL/WebP/AVIF/J2K formats; Quality is relative to each format. \n* Example: AVIF 60 is same quality as WebP 90. \n* PNG compression is fixed at 4 and not affected by this. PNG compression times skyrocket above level 4 for zero benefits on filesize."
}),
'named_keys': ('BOOLEAN', {'default': self.named_keys, 'tooltip': "Prefix each value by its key name. Example: prefix-seed=123456-width=1024-cfg=5.0-0001.avif"}),
},
'optional': {
'positive_text_opt': ('STRING', {'forceInput': True}),
'negative_text_opt': ('STRING', {'forceInput': True}),
'positive_text_opt': ('STRING', {'forceInput': True, 'tooltip': "Optional string saved as `positive_text_opt` in job.json when `save_job_data`=True"}),
'negative_text_opt': ('STRING', {'forceInput': True, 'tooltip': "Optional string saved as `negative_text_opt` in job.json when `save_job_data`=True"}),
},
'hidden': {'prompt': 'PROMPT', 'extra_pnginfo': 'EXTRA_PNGINFO'},
}
@@ -262,17 +264,25 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
# find_keys_recursively is a self-updating recursive method, that will update the dict found_values
def find_keys_recursively(self, prompt={}, keys_to_find=[], found_values={}):
# print(f"debug find_keys_recursively: keys_to_find={keys_to_find} found_values={found_values}")
if debug: print(f"debug find_keys_recursively: keys_to_find={keys_to_find} found_values={found_values}")
for key, value in prompt.items():
if key in keys_to_find:
# print(f"debug find_keys_recursively: found key={key}")
if debug: print(f"debug find_keys_recursively: found key={key}")
if debug: print(f"debug find_keys_recursively: value={value}")
# pythongosssss/ComfyUI-Custom-Scripts stores the value as a dict: value={'content': 'v1-5-pruned-emaonly.safetensors', 'image': 'checkpoints/v1-5-pruned-emaonly.jpg'}
if isinstance(value, dict):
if 'content' in value:
value = value['content']
else:
value = ''
if key in ['ckpt_path','ckpt_name']:
value_path = Path(value)
if 'ckpt_path' in keys_to_find:
found_values['ckpt_path'] = self.cleanup_fileName(str(value_path.parent))
elif 'ckpt_name' in keys_to_find:
found_values['ckpt_name'] = self.cleanup_fileName(str(value_path.name))
# print(f"debug find_keys_recursively: ckpt_name={value_path.name} ckpt_path={str(value_path.parent)}")
if debug: print(f"debug find_keys_recursively: ckpt_name={value_path.name} ckpt_path={str(value_path.parent)}")
elif key in ['control_net_path','control_net_name']:
value_path = Path(value)
if 'control_net_path' in keys_to_find:
@@ -304,7 +314,7 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
def find_parameter_values(self, target_keys, prompt={}, found_values={}):
loras_string = ''
for key, value in prompt.items():
# print(f"debug find_parameter_values: key={key} value={value}")
if debug: print(f"debug find_parameter_values: key={key} value={value}")
if 'loras' in target_keys:
# Match both formats: lora_xx and lora_name_x
if re.match(r'lora(_name)?(_\d+)?', key):
@@ -356,9 +366,9 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
if prompt is not None and keys_to_extract != ['']:
found_values = {}
# print(f"debug generate_custom_name: --prefix: {prefix}")
# print(f"debug generate_custom_name: --keys_to_extract: {keys_to_extract}")
# print(f"debug generate_custom_name: --prompt:")
if debug: print(f"debug generate_custom_name: --prefix: {prefix}")
if debug: print(f"debug generate_custom_name: --keys_to_extract: {keys_to_extract}")
if debug: print(f"debug generate_custom_name: --prompt:")
# now separating numbered keys from non-numbered keys:
# 37.ckpt_name = i want the ckpt_name from node #37
@@ -407,7 +417,7 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
# key has the form num.widget_name like 123.widget_name, we will then look for widget_name value in node #123
node, nodeKey = splitKey[0], splitKey[1]
if node in prompt:
# print(f"debug generate_custom_name: --node.nodeKey = {node}.{nodeKey}")
if debug: print(f"debug generate_custom_name: --node.nodeKey = {node}.{nodeKey}")
# splitKey[0] = #node number found in prompt, we will recurse only in that node:
if nodeKey == 'ckpt_path':
self.find_keys_recursively(prompt[node], ['ckpt_name', nodeKey], found_values)
@@ -452,9 +462,9 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
# value was None
# at this point we have a nodeKey, or a value, or both
# print(f"debug generate_custom_name: ----nodeKey: {nodeKey}")
# print(f"debug generate_custom_name: ----value: {value}")
# print(f"debug generate_custom_name: ----found_values: {found_values}")
if debug: print(f"debug generate_custom_name: ----nodeKey: {nodeKey}")
if debug: print(f"debug generate_custom_name: ----value: {value}")
if debug: print(f"debug generate_custom_name: ----found_values: {found_values}")
if value is None:
if nodeKey is not None:
if nodeKey in found_values:
@@ -467,13 +477,13 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
# at this point, value is not None anymore
# now we analyze each value found and format them accordingly:
# print(f"debug generate_custom_name: ----value: {value}")
if debug: print(f"debug generate_custom_name: ----value: {value}")
# now we build the custom_name:
if isinstance(value, str):
# prefix and keys can very well be subfolders ending or starting with a /
# for subfolders in keys, do not clean the filename...
# print(f"debug generate_custom_name: ---------: / in value? {nodeKey}={value}")
if debug: print(f"debug generate_custom_name: ---------: / in value? {nodeKey}={value}")
# Now process the custom cases ckpt_path and control_net_path; maybe we should do that to `image` as well?
# The recursive function creates ckpt_path and ckpt_name already; we just need to eliminate path if == '.'
@@ -487,18 +497,20 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
value = float(f'{value:.10g}')
custom_name.append(str(value))
# print(f"debug generate_custom_name: ------custom_name: {custom_name}")
if debug: print(f"debug generate_custom_name: ------custom_name: {custom_name}")
# for each key
# remove empty values
custom_name = list(filter(None, custom_name))
# strip each item
custom_name = list(map(str.strip, custom_name))
# join
stringName = delimiter.join(custom_name).replace('/'+delimiter, '/').replace(delimiter+'/', '/').replace(delimiter+'.', '.')
# clean
stringName = stringName.strip(delimiter).strip('/').strip(delimiter).strip('.')
# clean and remove line feeds
stringName = re.sub('\s+',' ',stringName).strip(delimiter).strip('/').strip(delimiter).strip('.')
print(f"debug generate_custom_name: ------custom_name: {custom_name}")
print(f"debug generate_custom_name: ------stringName: {stringName}")
if debug: print(f"debug generate_custom_name: ------custom_name: {custom_name}")
if debug: print(f"debug generate_custom_name: ------stringName: {stringName}")
return re.sub(r'[*?:"<>|]','',stringName).replace('/', os.sep)
@@ -705,7 +717,7 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
# ███ ███ ██ ██ ██ ██ ███████
def writeImage(self, image_path, img, prompt, save_metadata=save_metadata, extra_pnginfo=None, quality=quality):
# print(f"debug writeImage: image_path={image_path}")
if debug: print(f"debug writeImage: image_path={image_path}")
if quality == 0:
quality = self.quality
# output_ext = os.path.splitext(os.path.basename(image_path))[1]
@@ -741,19 +753,29 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
# tiff: no quality
kwargs["optimize"] = self.optimize_image
elif output_ext in ['.png', '.gif']:
# png/gif: no quality
if save_metadata: kwargs["pnginfo"] = self.genMetadataPng(img, prompt, extra_pnginfo)
kwargs["compress_level"] = self.png_compress_level
kwargs["optimize"] = self.optimize_image
# png/gif: no quality, rather we convert quality to compression level in the 0-9 range
old_min = 0
old_max = 90
new_min = 0
new_max = 9
if quality >= 91: quality = 90
png_compress_level = round( ( (quality - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min )
kwargs["compress_level"] = png_compress_level
# BUG: PIL will compress at level 9 when PNG optimize_image = True
# kwargs["optimize"] = self.optimize_image
# elif output_ext in ['.bmp']:
# nothing to add
# img.save(image_path, pnginfo=metadata, compress_level=self.png_compress_level)
# BUG: PIL.Image doesn't respect compress_level value and always output max 9 compressed images when optimize_image = True
# img.save(image_path, pnginfo=metadata, compress_level=png_compress_level)
img.save(image_path, **kwargs)
# Is saving image with OpenCV really faster then PIL? https://github.com/python-pillow/Pillow/issues/5986
# I found that it does not matter for anything smaller then 8k*8k, which Comfy cannot produce anyways.
# compression_level = [cv2.IMWRITE_PNG_COMPRESSION, self.png_compress_level]
# compression_level = [cv2.IMWRITE_PNG_COMPRESSION, png_compress_level]
# image_array = numpy.array(img)
# image_array = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
# cv2.imwrite(image_path.replace('000','cv2'), image_array)
@@ -830,14 +852,14 @@ ComfyUI can only load PNG and WebP at the moment, AVIF is a PR that was sadly dr
output_path = Path(os.path.join(self.output_dir, custom_foldername, custom_filename)).parent
filename = Path(os.path.join(self.output_dir, custom_foldername, custom_filename)).name
# print(f"debug save_images: custom_foldername= {custom_foldername}")
# print(f"debug save_images: custom_filename= {custom_filename}")
# print(f"debug save_images: output_path= {output_path}")
# print(f"debug save_images: filename= {filename}")
if debug: print(f"debug save_images: custom_foldername= {custom_foldername}")
if debug: print(f"debug save_images: custom_filename= {custom_filename}")
if debug: print(f"debug save_images: output_path= {output_path}")
if debug: print(f"debug save_images: filename= {filename}")
os.makedirs(output_path, exist_ok=True)
counter = self.get_latest_counter(one_counter_per_folder, output_path, filename, counter_digits, counter_position, output_ext)
# print(f"debug save_images: counter for {output_ext}: {counter}")
if debug: print(f"debug save_images: counter for {output_ext}: {counter}")
results = list()
for image in images: