Skip to content

Commit b3f8ba3

Browse files
committed
Bump version
1 parent 596aae6 commit b3f8ba3

File tree

3 files changed

+127
-14
lines changed

3 files changed

+127
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# v1.9.1
2+
3+
- Bugfix: handle network connection error for civitai
4+
15
# v1.9.0
26

37
- Allow multiple comma-separated model names

nodes.py

Lines changed: 122 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
from datetime import datetime
3+
from dataclasses import dataclass
34
from pathlib import Path
45
import json
56
import numpy as np
@@ -62,6 +63,110 @@ def make_filename(filename, width, height, seed, modelname, counter, time_format
6263
filename = make_pathname(filename, width, height, seed, modelname, counter, time_format, sampler_name, steps, cfg, scheduler, denoise, clip_skip)
6364
return get_timestamp(time_format) if filename == "" else filename
6465

66+
@dataclass
67+
class Metadata:
68+
modelname: str
69+
positive: str
70+
negative: str
71+
width: int
72+
height: int
73+
seed: int
74+
steps: int
75+
cfg: float
76+
sampler_name: str
77+
scheduler: str
78+
denoise: float
79+
clip_skip: int
80+
additional_hashes: str
81+
82+
class ImageSaverMetadata:
83+
@classmethod
84+
def INPUT_TYPES(cls):
85+
return {
86+
"optional": {
87+
"modelname": ("STRING", {"default": '', "multiline": False, "tooltip": "model name (can be multiple, separated by commas)"}),
88+
"positive": ("STRING", {"default": 'unknown', "multiline": True, "tooltip": "positive prompt"}),
89+
"negative": ("STRING", {"default": 'unknown', "multiline": True, "tooltip": "negative prompt"}),
90+
"width": ("INT", {"default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "tooltip": "image width"}),
91+
"height": ("INT", {"default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "tooltip": "image height"}),
92+
"seed_value": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "seed"}),
93+
"steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "number of steps"}),
94+
"cfg": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "tooltip": "CFG value"}),
95+
"sampler_name": ("STRING", {"default": '', "multiline": False, "tooltip": "sampler name (as string)"}),
96+
"scheduler": ("STRING", {"default": 'normal', "multiline": False, "tooltip": "scheduler name (as string)"}),
97+
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "tooltip": "denoise value"}),
98+
"clip_skip": ("INT", {"default": 0, "min": -24, "max": 24, "tooltip": "skip last CLIP layers (positive or negative value, 0 for no skip)"}),
99+
"additional_hashes": ("STRING", {"default": "", "multiline": False, "tooltip": "hashes separated by commas, optionally with names. 'Name:HASH' (e.g., 'MyLoRA:FF735FF83F98')\nWith download_civitai_data set to true, weights can be added as well. (e.g., 'HASH:Weight', 'Name:HASH:Weight')"}),
100+
"download_civitai_data": ("BOOLEAN", {"default": True, "tooltip": "Download and cache data from civitai.com to save correct metadata. Allows LoRA weights to be saved to the metadata."}),
101+
"easy_remix": ("BOOLEAN", {"default": True, "tooltip": "Strip LoRAs and simplify 'embedding:path' from the prompt to make the Remix option on civitai.com more seamless."}),
102+
},
103+
}
104+
105+
RETURN_TYPES = ("METADATA",)
106+
RETURN_NAMES = ("metadata",)
107+
OUTPUT_TOOLTIPS = ("metadata for Image Saver Simple",)
108+
FUNCTION = "get_metadata"
109+
CATEGORY = "ImageSaver"
110+
DESCRIPTION = "Prepare metadata for Image Saver Simple"
111+
112+
def get_metadata(
113+
self,
114+
modelname,
115+
positive,
116+
negative,
117+
width,
118+
height,
119+
seed_value,
120+
steps,
121+
cfg,
122+
sampler_name,
123+
scheduler,
124+
denoise,
125+
clip_skip,
126+
additional_hashes="",
127+
download_civitai_data=True,
128+
easy_remix=True,
129+
):
130+
modelname, additional_hashes = ImageSaver.get_multiple_models(modelname, additional_hashes)
131+
metadata = Metadata(modelname, positive, negative, width, height, seed_value, steps, cfg, sampler_name, scheduler, denoise, clip_skip, additional_hashes)
132+
return (metadata,)
133+
134+
class ImageSaverSimple:
135+
@classmethod
136+
def INPUT_TYPES(cls):
137+
return {
138+
"required": {
139+
"images": ("IMAGE", { "tooltip": "image(s) to save"}),
140+
"filename": ("STRING", {"default": '%time_%basemodelname_%seed', "multiline": False, "tooltip": "filename (available variables: %date, %time, %model, %width, %height, %seed, %counter, %sampler_name, %steps, %cfg, %scheduler, %basemodelname, %denoise, %clip_skip)"}),
141+
"path": ("STRING", {"default": '', "multiline": False, "tooltip": "path to save the images (under Comfy's save directory)"}),
142+
"extension": (['png', 'jpeg', 'jpg', 'webp'], { "tooltip": "file extension/type to save image as"}),
143+
"lossless_webp": ("BOOLEAN", {"default": True, "tooltip": "if True, saved WEBP files will be lossless"}),
144+
"quality_jpeg_or_webp": ("INT", {"default": 100, "min": 1, "max": 100, "tooltip": "quality setting of JPEG/WEBP"}),
145+
"optimize_png": ("BOOLEAN", {"default": False, "tooltip": "if True, saved PNG files will be optimized (can reduce file size but is slower)"}),
146+
"embed_workflow": ("BOOLEAN", {"default": True, "tooltip": "if True, embeds the workflow in the saved image files.\nStable for PNG, experimental for WEBP.\nJPEG experimental and only if metadata size is below 65535 bytes"}),
147+
"save_workflow_as_json": ("BOOLEAN", {"default": False, "tooltip": "if True, also saves the workflow as a separate JSON file"}),
148+
},
149+
"optional": {
150+
"metadata": ("METADATA", {"default": None, "tooltip": "metadata to embed in the image"}),
151+
"counter": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "counter"}),
152+
"time_format": ("STRING", {"default": "%Y-%m-%d-%H%M%S", "multiline": False, "tooltip": "timestamp format"}),
153+
},
154+
"hidden": {
155+
"prompt": "PROMPT",
156+
"extra_pnginfo": "EXTRA_PNGINFO",
157+
},
158+
}
159+
160+
RETURN_TYPES = ("STRING","STRING")
161+
RETURN_NAMES = ("hashes","a1111_params")
162+
OUTPUT_TOOLTIPS = ("Comma-separated list of the hashes to chain with other Image Saver additional_hashes","Written parameters to the image metadata")
163+
FUNCTION = "save_files"
164+
165+
OUTPUT_NODE = True
166+
167+
CATEGORY = "ImageSaver"
168+
DESCRIPTION = "Save images with civitai-compatible generation metadata"
169+
65170
class ImageSaver:
66171
@classmethod
67172
def INPUT_TYPES(cls):
@@ -117,6 +222,22 @@ def INPUT_TYPES(cls):
117222
# Match 'anything', 'anything:anything' or 'anything:anything:number' with trimmed white space
118223
re_manual_hash_weights = re.compile(r'^\s*([^:]+?)(?:\s*:\s*([^\s:][^:]*?))?(?:\s*:\s*([-+]?(?:\d+(?:\.\d*)?|\.\d+)))?\s*$')
119224

225+
@staticmethod
226+
def get_multiple_models(modelname, additional_hashes):
227+
model_names = [m.strip() for m in modelname.split(',')]
228+
modelname = model_names[0] # Use the first model as the primary one
229+
230+
# Process additional model names and add to additional_hashes
231+
for additional_model in model_names[1:]:
232+
additional_ckpt_path = full_checkpoint_path_for(additional_model)
233+
if additional_ckpt_path:
234+
additional_modelhash = get_sha256(additional_ckpt_path)[:10]
235+
# Add to additional_hashes in "name:HASH" format
236+
if additional_hashes:
237+
additional_hashes += ","
238+
additional_hashes += f"{additional_model}:{additional_modelhash}"
239+
return modelname, additional_hashes
240+
120241
def save_files(
121242
self,
122243
images,
@@ -148,19 +269,7 @@ def save_files(
148269
prompt=None,
149270
extra_pnginfo=None,
150271
):
151-
model_names = [m.strip() for m in modelname.split(',')]
152-
modelname = model_names[0] # Use the first model as the primary one
153-
154-
# Process additional model names and add to additional_hashes
155-
for additional_model in model_names[1:]:
156-
additional_ckpt_path = full_checkpoint_path_for(additional_model)
157-
if additional_ckpt_path:
158-
additional_modelhash = get_sha256(additional_ckpt_path)[:10]
159-
# Add to additional_hashes in "name:HASH" format
160-
if additional_hashes:
161-
additional_hashes += ","
162-
additional_hashes += f"{additional_model}:{additional_modelhash}"
163-
272+
modelname, additional_hashes = ImageSaver.get_multiple_models(modelname, additional_hashes)
164273
filename = make_filename(filename, width, height, seed_value, modelname, counter, time_format, sampler_name, steps, cfg, scheduler, denoise, clip_skip)
165274
path = make_pathname(path, width, height, seed_value, modelname, counter, time_format, sampler_name, steps, cfg, scheduler, denoise, clip_skip)
166275

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-image-saver"
33
description = "Save images with generation metadata compatible with Civitai. Works with png, jpeg and webp. Stores LoRAs, models and embeddings hashes for resource recognition."
4-
version = "1.9.0"
4+
version = "1.9.1"
55
license = { file = "LICENSE" }
66
dependencies = ["piexif"]
77

0 commit comments

Comments
 (0)