Compare commits

..
4 Commits
Author SHA1 Message Date
audioscavenger 189bb9997c release 2.75 named_keys
modified:   README.md
	modified:   pyproject.toml
	modified:   save_image_extended.py
	modified:   web/js/shared_utils.js
2024-06-17 21:57:10 -07:00
audioscavenger b0d5dd8aa6 relese 2.74 help_popup
modified:   README.md
	modified:   pyproject.toml
	modified:   save_image_extended.py
2024-06-06 11:41:09 -07:00
audioscavenger 9ec40cbebf release 2.73 ckpt_path
modified:   .gitignore
	modified:   README.md
	modified:   pyproject.toml
	modified:   save_image_extended.py
2024-06-05 15:15:30 -07:00
audioscavenger 8c91af7243 release 2.70 JXL
modified:   README.md
        	modified:   pyproject.toml
        	modified:   requirements.txt
        	modified:   save_image_extended.py
        	modified:   web/js/help_popup.js
    	modified:   README.md
    	modified:   save_image_extended.py
	modified:   README.md
2024-06-01 13:35:08 -07:00
5 changed files with 209 additions and 77 deletions
+2
View File
@@ -6,4 +6,6 @@ commit.*
*.url
*.lnk
desktop.ini
*.code-workspace
__pycache__/
+34 -15
View File
@@ -1,5 +1,5 @@
# 💾 Save Image Extended for ComfyUI
Save as JXL, AVIF, WebP, JPEG, JPEG2000, customize the folder, sub-folders, and filenames of your images!
Save as JXL, AVIF, WebP, JPEG, JPEG2k, customize the folder, sub-folders, and filenames of your images!
Supports those extensions: **JXL AVIF WebP jpg jpeg j2k jp2 png gif tiff bmp**
@@ -37,10 +37,17 @@ Supports those extensions: **JXL AVIF WebP jpg jpeg j2k jp2 png gif tiff bmp**
| `one_counter_per_folder` | Toggles the counter. Either one counter per folder, 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 |
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
- 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
- default it output only the **name** for `ckpt_name` and `control_net_name`
- use `ckpt_path` or `control_net_path` in case you have subfolders for those and want to use them as subfolders
- using `.custom_string` will prevent appending delimiter, the dot will be the delimiter
## Node inputs
@@ -110,18 +117,18 @@ Tested and working with default samplers, Efficiency nodes, UltimateSDUpscale, C
Quality and compression settings: default is 90, 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 |
| --- | --- | --- | --- |
| png | max 9 | PIL | 413111 |
| j2k | n/a | PIL | 395028 |
| jxl | lossless | PIL | 301310 |
| jxl | 90 | PIL | 179210 |
| jpeg | 90 | PIL | 88554 |
| avif | 90 | Imagick | 67272 |
| webp | 90 | Imagick | 64416 |
| webp | 90 | PIL | 64356 |
| avif | 60 | PIL | 47353 |
| avif | 60 | Imagick | 33691 |
| Ext | Compression | Maker | Size | Compression |
| --- | --- | --- | --- | --- |
| png | max 9 | PIL | 413111 | 0% |
| j2k | n/a | PIL | 395028 | 4% |
| jxl | lossless | PIL | 301310 | 27% |
| jxl | 90 | PIL | 179210 | 57% |
| jpeg | 90 | PIL | 88554 | 79% |
| avif | 90 | Imagick | 67272 | 84% |
| webp | 90 | Imagick | 64416 | 84% |
| webp | 90 | PIL | 64356 | 84% |
| avif | 60 | PIL | 47353 | 89% |
| avif | 60 | Imagick | 33691 | 92% |
#
@@ -171,11 +178,23 @@ Once I feel like I don't have time to work on it, I will gladly transfer ownersh
TODO:
- [ ] accept multiple images as input
- [ ] improve get_latest_counter: restarts when user renames files after the counter part.
- [ ] offer to place the counter anywhere, as a key in filename_keys
- [ ] keep same counter if extension changes?
- [ ] files will be out of order if prefixes change... that is expected, but is this what we want?
### release 2.75 💾
- Prefix each value by its key name: prefix-seed=123456-width=1024-etc-0001.avif
### release 2.74 💾
- updated help popup and minor cosmetic fixes
### release 2.73 💾
- added ckpt_path and control_net_path in case you have subfolders for those: default it output only the name
- complete fix of save_images() which allows for use of subfolders in filename_keys
- tried VALIDATE_INPUTS() but all it does is checking 1 single value, no correction
### release 2.70 💾
- added JPEG-XL
- added JPEG2000
+2 -2
View File
@@ -1,7 +1,7 @@
[project]
name = "save-image-extended-comfyui"
description = "Save as AVIF, WebP, JPEG, customize the folder, sub-folders, and filenames of your images!"
version = "2.70"
description = "Save as JXL, AVIF, WebP, JPEG, JPEG2k, customize the folder, sub-folders, and filenames of your images!"
version = "2.75"
license = "LICENCE"
dependencies = ["piexif", "imagecodecs", "pillow", "pillow-avif-plugin", "pillow-jxl-plugin"]
+161 -60
View File
@@ -11,7 +11,7 @@ import pprint
import piexif
import piexif.helper
version = 2.70
version = 2.75
avif_supported = False
jxl_supported = False
@@ -20,10 +20,10 @@ jxl_supported = False
try:
import pillow_avif
except:
print(f"\033[92m[save_image_extended]\033[0m AVIF is not supported. To add it: pip install pillow pillow-avif-plugin\033[0m")
print(f"\033[92m[💾 save_image_extended]\033[0m AVIF is not supported. To add it: pip install pillow pillow-avif-plugin\033[0m")
pass
else:
print(f"\033[92m[save_image_extended] AVIF is supported! Woohoo!\033[0m")
print(f"\033[92m[💾 save_image_extended] AVIF is supported! Woohoo!\033[0m")
avif_supported = True
# Jxl requires jxlpy wheel to be compiled, and a valid MSVC environment, which is complex task
@@ -33,11 +33,11 @@ try:
# from imagecodecs import (jpegxl_encode, jpegxl_decode, jpegxl_check, jpegxl_version, JPEGXL)
import pillow_jxl
except:
print(f"\033[92m[save_image_extended]\033[0m JXL is not supported. To add it: pip install jxlpy\033[0m")
print(f"\033[92m[save_image_extended]\033[0m You will need a valid MSVC env to build the wheel\033[0m")
print(f"\033[92m[💾 save_image_extended]\033[0m JXL is not supported. To add it: pip install jxlpy\033[0m")
print(f"\033[92m[💾 save_image_extended]\033[0m You will need a valid MSVC env to build the wheel\033[0m")
pass
else:
print(f"\033[92m[save_image_extended] JPEGXL is supported! YeePee!\033[0m")
print(f"\033[92m[💾 save_image_extended] JPEGXL is supported! YeePee!\033[0m")
jxl_supported = True
# PIL must be loaded after pillow plugins
@@ -55,15 +55,32 @@ class SaveImageExtended:
OUTPUT_NODE = True
CATEGORY = 'image'
DESCRIPTION = """
### subfolders:
- you can use / or ./ or even ../ to start from parent
- you can also use / as the separator
- if your widget name has subfolders like SDXL/whatnot, you will have subfolders too
### Sample datetime formats: see [man datetime](https://www.man7.org/linux/man-pages/man1/date.1.html)
## Advice
It is strongly advised to enable Badge numbers in the Manager: _#ID Nickname_
### Default behavior
- Workflows with multiple of the same node:
- if you don't specify the node number in your keys like _13.sampler_name_,
- then the highest node number value will be returned
- if your checkpoint/controlnet is in subfolders like SDXL/whatnot, `ckpt_name` will **not** have subfolders
- to get the subfolders from controlnets and checkpoints, use _ckpt_path_ and _control_net_path_
### subfolders
- you can use `/` as the separator, or use keys such as `/name` or `name/` as a folder separator
- you can use `/` or `./` or even `../` anywhere in your keys
- there can be subfolders in _filename\_keys_ as well, altough it will mess the order of your keys
### Datetime formats
See [man datetime](https://www.man7.org/linux/man-pages/man1/date.1.html) for all possible values
- %F = %Y-%m-%d = 2024-05-22
- %H-%M-%S = 09-13-58
- %D = 05/22/24 (subfolders)
### 💾 Prompt Embedded
Prompt and Workflows are embedded in every files except BMP.
They are saved in Exif tags _Make_ [0x010f] and _ImageDescription_ [0x010e].
ComfyUI can only load PNG and WebP at the moment, AVIF is a PR still not merged.
"""
type = 'output'
@@ -97,8 +114,9 @@ class SaveImageExtended:
output_ext = '.webp'
output_exts = ['.webp', '.png', '.jpg', '.jpeg', '.j2k', '.jp2', '.gif', '.tiff', '.bmp']
quality = 90
named_keys = False
print(f"\033[92m[save_image_extended]\033[0m version: {version}\033[0m")
print(f"\033[92m[💾 save_image_extended]\033[0m version: {version}\033[0m")
if jxl_supported:
output_exts.insert(0, '.jxl')
# if pillow_avif not in sys.modules:
@@ -109,10 +127,9 @@ class SaveImageExtended:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.prefix_append = ''
"""
Return a dictionary which contains config for all input fields.
INPUT_TYPES Return a dictionary which contains config for all input fields.
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
The type can be a list for selection.
@@ -127,6 +144,8 @@ class SaveImageExtended:
"""
@classmethod
def INPUT_TYPES(self):
# this is checked by ComfyUI/execution.py: validate_inputs(prompt, item, validated):
# must also define VALIDATE_INPUTS(self) so we can fill in missing inputs when new inputs are added by new versions
return {
'required': {
'images': ('IMAGE', ),
@@ -163,6 +182,7 @@ class SaveImageExtended:
"step": 1,
"display": "silder"
}),
'named_keys': ('BOOLEAN', {'default': self.named_keys}),
},
'optional': {
'positive_text_opt': ('STRING', {'forceInput': True}),
@@ -170,8 +190,27 @@ class SaveImageExtended:
},
'hidden': {'prompt': 'PROMPT', 'extra_pnginfo': 'EXTRA_PNGINFO'},
}
# This class serves no purpose, it can only test 1 element. you always get all errors even if only one element is bad
# @classmethod
# def VALIDATE_INPUTS(self, output_ext, quality, **kwargs):
# print(f"VALIDATE_INPUTS output_ext = x{output_ext}x")
# print(f"VALIDATE_INPUTS quality = x{quality}x")
# if output_ext == None or output_ext == '':
# return "cannot be empty"
# if output_ext not in self.output_exts:
# return "extension invalid"
# else:
# return True
# if quality == None or quality == '' or quality == 0:
# return "cannot be empty or 0"
# return True
# TODO: see how that works and how that can help
# @classmethod
# def IS_CHANGED(self, **kwargs):
# return float("nan")
def get_subfolder_path(self, image_path, output_path):
image_path = Path(image_path).resolve()
output_path = Path(output_path).resolve()
@@ -210,9 +249,19 @@ class SaveImageExtended:
# 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}")
for key, value in prompt.items():
if key in keys_to_find:
found_values[key] = value
# print(f"debug find_keys_recursively: found key={key}")
if key == 'ckpt_name' and 'ckpt_path' in keys_to_find:
value_path = Path(value)
found_values['ckpt_path'] = self.cleanup_fileName(str(value_path.parent))
# print(f"debug find_keys_recursively: ckpt_name={value_path.name} ckpt_path={str(value_path.parent)}")
elif key == 'control_net_name' and 'control_net_path' in keys_to_find:
value_path = Path(value)
found_values['control_net_path'] = self.cleanup_fileName(str(value_path.parent))
else:
found_values[key] = self.cleanup_fileName(value)
elif isinstance(value, dict):
self.find_keys_recursively(value, keys_to_find, found_values)
@@ -261,7 +310,7 @@ 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, timestamp=datetime.now()):
def generate_custom_name(self, keys_to_extract, prefix, delimiter, prompt, timestamp=datetime.now(), named_keys=False):
if '%' in prefix:
custom_name = timestamp.strftime(prefix)
else:
@@ -281,6 +330,7 @@ class SaveImageExtended:
# 'inputs': {'cfg': 1.6,
# 'denoise': 1.0, ...
for key in keys_to_extract:
# empty comma
if not key: continue
value = None
@@ -288,7 +338,7 @@ class SaveImageExtended:
# check if this is a subfolder: starts with ./ or /, can also end with /
# we also exclude datetime formats like "%A %d. %B %Y"
if '/' in key and not '%' in key:
if os.sep in key and not '%' in key:
# key is a subfolder: ./subfolder or ../subfolder or /subfolder
value = key
else:
@@ -305,13 +355,23 @@ class SaveImageExtended:
if node in prompt:
# print(f"debug generate_custom_name: --node.nodeKey = {node}.{nodeKey}")
# splitKey[0] = #node number found in prompt, we will recurse only in that node:
self.find_keys_recursively(prompt[node], [nodeKey], found_values)
if nodeKey == 'ckpt_path':
self.find_keys_recursively(prompt[node], ['ckpt_name', nodeKey], found_values)
elif nodeKey == 'control_net_path':
self.find_keys_recursively(prompt[node], ['control_net_name', nodeKey], found_values)
else:
self.find_keys_recursively(prompt[node], [nodeKey], found_values)
else:
# if splitKey[0] = #num node not found in prompt; #num could have changed or user made a typo
# if splitKey[0] = #num node not found in prompt; #num could have changed or user made a typo. Fallback to normal key search
print(f"SaveImageExtended info: node #{node} not found")
self.find_keys_recursively(prompt, [nodeKey], found_values)
if nodeKey == 'ckpt_path':
self.find_keys_recursively(prompt, ['ckpt_name', nodeKey], found_values)
elif nodeKey == 'control_net_path':
self.find_keys_recursively(prompt, ['control_net_name', nodeKey], found_values)
else:
self.find_keys_recursively(prompt, [nodeKey], found_values)
else:
# key is in the form string.string = fixed string
# key is in the form string.string = fixed string; still, we remove extra stuff and known extensions
value = self.cleanup_fileName(key)
else:
# key is in the form ".string" or "string." or "." - we won't clean that up and keep as is, maybe it's a separator
@@ -323,20 +383,28 @@ class SaveImageExtended:
# 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)
if nodeKey == 'ckpt_path':
self.find_keys_recursively(prompt, ['ckpt_name', nodeKey], found_values)
elif nodeKey == 'control_net_path':
self.find_keys_recursively(prompt, ['control_net_name', nodeKey], found_values)
else:
self.find_keys_recursively(prompt, [nodeKey], found_values)
# is key num.widget_name
# is key subfolder
# at this point we have a nodeKey, or a value, or both
# print(f"debug generate_custom_name: ----key: {nodeKey}")
# print(f"debug generate_custom_name: ----value: {value}")
# 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 value is None:
if nodeKey is not None:
if nodeKey in found_values: value = found_values[nodeKey]
if value is None:
value = nodeKey
else:
value = self.cleanup_fileName(value)
if nodeKey in found_values:
if named_keys:
value = f"{nodeKey}={found_values[nodeKey]}"
else:
value = found_values[nodeKey]
if value is None: value = nodeKey
# at this point, value is not None anymore
# now we analyze each value found and format them accordingly:
@@ -346,14 +414,32 @@ class SaveImageExtended:
# now we build the custom_name:
if isinstance(value, str):
# prefix and keys can very well be subfolders ending or starting with a /
if custom_name.endswith('/'):
if custom_name.endswith(os.sep):
# for subfolders, do not start filename with a delimiter...
delim = ''
else:
# for subfolders in keys, do not clean the filename...
if '/' in value and not value.endswith('/'):
# print(f"debug generate_custom_name: ---------: {os.sep} in value? {nodeKey}={value}")
if os.sep in value and not value.endswith(os.sep):
# print(f"debug generate_custom_name: ---------: folder")
delim= ''
# 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 == '.'
if nodeKey in ['ckpt_path', 'control_net_path']:
# smth_path is most likely placed before smth_name, therefore value cannot be resolved during the previous check
value = found_values[nodeKey]
# and indeed we bypass anything that does not have a subfolder
if value == '.':
continue
else:
# terminate the subfolder with os.sep so it creates an actual subfolder.
# Because find_keys_recursively will only return the path part: 'path' or 'path/subpath'
# Therefore, enclosing this value with os.sep ensure we get a subfolder
value = os.sep + value + os.sep
else:
value = self.cleanup_fileName(value)
# ".string" case
if value.startswith('.'):
delim = ''
@@ -364,7 +450,7 @@ class SaveImageExtended:
custom_name += f"{delim}{value}"
# print(f"debug generate_custom_name: ------custom_name: {custom_name}")
# for each key
return custom_name.strip(delimiter).strip('.').strip('/').strip(delimiter)
return custom_name.strip(delimiter).strip('.').strip(os.sep).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, timestamp=datetime.now()):
@@ -497,9 +583,10 @@ class SaveImageExtended:
# exif[ExifTags.Base.UserComment] = dump
## It seems better to separate the two
# 0x010e: ImageDescription
# 0x010f: Make
# 0x9286: UserComment
# 0x010d: DocumentName Parameters (SD)
# 0x010e: ImageDescription Workflow
# 0x010f: Make Prompt
# 0x9286: UserComment cancelled
# both prompt and workflow must be in IFD close together of that can cause problems for the parseIFD function on import
# https://exiftool.org/TagNames/EXIF.html
# exif[0x9286] = "Prompt: " + json.dumps(metadata['prompt']) # UserComment
@@ -540,9 +627,12 @@ class SaveImageExtended:
return exif_dat
def save_image(self, image_path, img, prompt, save_metadata=save_metadata, extra_pnginfo=None, quality=90):
def save_image(self, image_path, img, prompt, save_metadata=save_metadata, extra_pnginfo=None, quality=quality):
# print(f"debug save_images: image_path={image_path}")
output_ext = os.path.splitext(os.path.basename(image_path))[1]
if quality == 0:
quality = self.quality
# output_ext = os.path.splitext(os.path.basename(image_path))[1]
output_ext = Path(image_path).suffix
metadata = None
kwargs = dict()
@@ -606,18 +696,23 @@ class SaveImageExtended:
positive_text_opt=None,
extra_pnginfo=None,
prompt=None,
quality=75,
quality=quality,
named_keys=named_keys,
):
# print(f"filename_prefix = x{filename_prefix}x")
# print(f"filename_keys = x{filename_keys}x")
# print(f"foldername_prefix = x{foldername_prefix}x")
# print(f"foldername_keys = x{foldername_keys}x")
# print(f"delimiter = x{delimiter}x")
# print(f"save_job_data = x{save_job_data}x")
# print(f"job_data_per_image = x{job_data_per_image}x")
# print(f"output_ext = x{output_ext}x")
# print(f"save_images filename_prefix = x{filename_prefix}x")
# print(f"save_images filename_keys = x{filename_keys}x")
# print(f"save_images foldername_prefix = x{foldername_prefix}x")
# print(f"save_images foldername_keys = x{foldername_keys}x")
# print(f"save_images delimiter = x{delimiter}x")
# print(f"save_images save_job_data = x{save_job_data}x")
# print(f"save_images job_data_per_image = x{job_data_per_image}x")
# print(f"save_images output_ext = x{output_ext}x")
# print(f"save_images quality = x{quality}x")
# bugfix: sometimes on load, quality == 0
if quality == 0: quality = self.quality
# apply default values: we replicate the default save image box
if not filename_prefix and not filename_keys: filename_prefix=self.filename_prefix
if delimiter: delimiter = delimiter[0]
@@ -629,8 +724,8 @@ class SaveImageExtended:
# pprint.pprint(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)
custom_foldername = self.generate_custom_name(foldername_keys_to_extract, foldername_prefix, delimiter, prompt, timestamp, named_keys)
custom_filename = self.generate_custom_name(filename_keys_to_extract, filename_prefix, delimiter, prompt, timestamp, named_keys)
# Get set resolution value
i = 255. * images[0].cpu().numpy()
@@ -639,11 +734,17 @@ class SaveImageExtended:
# Create folders, count images, save images
try:
full_output_folder, filename, _, _, custom_filename = folder_paths.get_save_image_path(custom_filename, self.output_dir, images[0].shape[1], images[0].shape[0])
output_path = os.path.join(full_output_folder, custom_foldername)
# print(f"debug save_images: full_output_folder={full_output_folder}")
# print(f"debug save_images: custom_foldername={custom_foldername}")
# print(f"debug save_images: output_path={output_path}")
# folder_paths.get_save_image_path() is kind of magic, I don't like that. Outpout is wrong anyway, when custom_filename contains folders
# full_output_folder, filename, _, _, custom_filename = folder_paths.get_save_image_path(custom_filename, self.output_dir, images[0].shape[1], images[0].shape[0])
# output_path = os.path.join(full_output_folder, custom_foldername)
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}")
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}")
@@ -654,18 +755,18 @@ class SaveImageExtended:
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
if counter_position == 'last':
file = f'{filename}{delimiter}{counter:0{counter_digits}}{output_ext}'
image_name = f'{filename}{delimiter}{counter:0{counter_digits}}{output_ext}'
else:
file = f'{counter:0{counter_digits}}{delimiter}{filename}{output_ext}'
image_name = f'{counter:0{counter_digits}}{delimiter}{filename}{output_ext}'
image_path = os.path.join(output_path, file)
image_path = os.path.join(output_path, image_name)
self.save_image(image_path, img, prompt, save_metadata, extra_pnginfo, quality)
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', timestamp)
self.save_job_to_json(save_job_data, prompt, filename_prefix, positive_text_opt, negative_text_opt, job_custom_text, resolution, output_path, f'{image_name.removesuffix(output_ext)}.json', timestamp)
subfolder = self.get_subfolder_path(image_path, self.output_dir)
results.append({ 'filename': file, 'subfolder': subfolder, 'type': self.type})
results.append({ 'filename': image_name, 'subfolder': subfolder, 'type': self.type})
counter += 1
if save_job_data != 'disabled' and not job_data_per_image:
+10
View File
@@ -61,6 +61,7 @@ export function getResolver(timeout = 5000) {
}, timeout);
return resolver;
}
export function wait(ms = 16, value) {
if (ms === 16) {
return new Promise((resolve) => {
@@ -73,14 +74,17 @@ export function wait(ms = 16, value) {
}, ms);
});
}
function dec2hex(dec) {
return dec.toString(16).padStart(2, "0");
}
export function generateId(length) {
const arr = new Uint8Array(length / 2);
crypto.getRandomValues(arr);
return Array.from(arr, dec2hex).join("");
}
export function getObjectValue(obj, objKey, def) {
if (!obj || !objKey)
return def;
@@ -92,6 +96,7 @@ export function getObjectValue(obj, objKey, def) {
}
return found;
}
export function setObjectValue(obj, objKey, value, createMissingObjects = true) {
if (!obj || !objKey)
return obj;
@@ -114,14 +119,17 @@ export function setObjectValue(obj, objKey, value, createMissingObjects = true)
}
return obj;
}
export function moveArrayItem(arr, itemOrFrom, to) {
const from = typeof itemOrFrom === "number" ? itemOrFrom : arr.indexOf(itemOrFrom);
arr.splice(to, 0, arr.splice(from, 1)[0]);
}
export function removeArrayItem(arr, itemOrIndex) {
const index = typeof itemOrIndex === "number" ? itemOrIndex : arr.indexOf(itemOrIndex);
arr.splice(index, 1);
}
export function injectCss(href) {
if (document.querySelector(`link[href^="${href}"]`)) {
return Promise.resolve();
@@ -139,6 +147,7 @@ export function injectCss(href) {
document.head.appendChild(link);
});
}
export function injectJs(href) {
if (document.querySelector(`script[src^="${href}"]`)) {
return Promise.resolve();
@@ -155,3 +164,4 @@ export function injectJs(href) {
document.head.appendChild(script);
});
}