Store accumulator metadata per entry

This commit is contained in:
2026-06-25 06:37:52 +02:00
parent de4ac97665
commit a60258dc4c
2 changed files with 63 additions and 17 deletions
+61 -16
View File
@@ -190,16 +190,32 @@ def _entry_value_summary(value: Any) -> str:
return text[:160] return text[:160]
def _metadata_copy(value: Any) -> Any:
try:
return json.loads(json.dumps(value))
except Exception:
return value
def _entry_metadata(prompt: Any, extra_pnginfo: Any) -> dict[str, Any]:
return {
"prompt": _metadata_copy(prompt),
"extra_pnginfo": _metadata_copy(extra_pnginfo) if isinstance(extra_pnginfo, dict) else {},
}
def _entry_infos(store: list[dict[str, Any]]) -> list[dict[str, Any]]: def _entry_infos(store: list[dict[str, Any]]) -> list[dict[str, Any]]:
entries = [] entries = []
for index, entry in enumerate(store, start=1): for index, entry in enumerate(store, start=1):
image = entry.get("image") image = entry.get("image")
shape = _image_shape(image) shape = _image_shape(image)
has_metadata = "prompt" in entry or "extra_pnginfo" in entry
entries.append( entries.append(
{ {
"index": index, "index": index,
"id": str(entry.get("id") or ""), "id": str(entry.get("id") or ""),
"has_image": image is not None, "has_image": image is not None,
"has_metadata": has_metadata,
"shape": list(shape) if shape is not None else [], "shape": list(shape) if shape is not None else [],
"value": _entry_value_summary(entry.get("value")), "value": _entry_value_summary(entry.get("value")),
} }
@@ -274,12 +290,18 @@ def _metadata(prompt: Any, extra_pnginfo: Any) -> Any:
metadata = PngInfo() metadata = PngInfo()
if prompt is not None: if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt)) metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None: if isinstance(extra_pnginfo, dict):
for key, value in extra_pnginfo.items(): for key, value in extra_pnginfo.items():
metadata.add_text(key, json.dumps(value)) metadata.add_text(key, json.dumps(value))
return metadata return metadata
def _metadata_for_entry(entry: dict[str, Any], fallback_prompt: Any, fallback_extra_pnginfo: Any) -> Any:
prompt = entry["prompt"] if "prompt" in entry else fallback_prompt
extra_pnginfo = entry["extra_pnginfo"] if "extra_pnginfo" in entry else fallback_extra_pnginfo
return _metadata(prompt, extra_pnginfo)
def _image_to_pil(image: Any) -> Any: def _image_to_pil(image: Any) -> Any:
_require_image_saving() _require_image_saving()
tensor = image tensor = image
@@ -312,19 +334,26 @@ def _next_save_counter(folder: str, prefix: str) -> int:
return counter return counter
def _preview_image_results(images: list[Any], preview_limit: int, prompt: Any, extra_pnginfo: Any) -> list[dict[str, str]]: def _preview_image_results(
if not images: entries: list[dict[str, Any]],
preview_limit: int,
prompt: Any,
extra_pnginfo: Any,
) -> list[dict[str, str]]:
image_entries = [entry for entry in entries if entry.get("image") is not None]
if not image_entries:
return [] return []
_require_image_saving() _require_image_saving()
output_dir = folder_paths.get_temp_directory() output_dir = folder_paths.get_temp_directory()
preview_images = images[: max(1, int(preview_limit))] preview_entries = image_entries[: max(1, int(preview_limit))]
first_shape = _image_shape(preview_images[0]) first_shape = _image_shape(preview_entries[0].get("image"))
height, width = (first_shape[0], first_shape[1]) if first_shape else (512, 512) height, width = (first_shape[0], first_shape[1]) if first_shape else (512, 512)
prefix = "SxCPAccumulatorPreview_temp_" + "".join(random.choice("abcdefghijklmnopqrstupvxyz") for _ in range(5)) prefix = "SxCPAccumulatorPreview_temp_" + "".join(random.choice("abcdefghijklmnopqrstupvxyz") for _ in range(5))
full_output_folder, filename, counter, subfolder, _filename_prefix = folder_paths.get_save_image_path(prefix, output_dir, width, height) full_output_folder, filename, counter, subfolder, _filename_prefix = folder_paths.get_save_image_path(prefix, output_dir, width, height)
metadata = _metadata(prompt, extra_pnginfo)
results = [] results = []
for image in preview_images: for entry in preview_entries:
image = entry.get("image")
metadata = _metadata_for_entry(entry, prompt, extra_pnginfo)
file = f"{filename}_{counter:05}_.png" file = f"{filename}_{counter:05}_.png"
_image_to_pil(image).save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=1) _image_to_pil(image).save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=1)
results.append({"filename": file, "subfolder": subfolder, "type": "temp"}) results.append({"filename": file, "subfolder": subfolder, "type": "temp"})
@@ -343,21 +372,23 @@ def _resolve_save_folder(save_path: str) -> str:
def _save_images_to_folder( def _save_images_to_folder(
images: list[Any], entries: list[dict[str, Any]],
save_path: str, save_path: str,
filename_prefix: str, filename_prefix: str,
prompt: Any, prompt: Any,
extra_pnginfo: Any, extra_pnginfo: Any,
) -> list[str]: ) -> list[str]:
if not images: image_entries = [entry for entry in entries if entry.get("image") is not None]
if not image_entries:
return [] return []
folder = _resolve_save_folder(save_path) folder = _resolve_save_folder(save_path)
os.makedirs(folder, exist_ok=True) os.makedirs(folder, exist_ok=True)
prefix = _safe_filename_prefix(filename_prefix) prefix = _safe_filename_prefix(filename_prefix)
counter = _next_save_counter(folder, prefix) counter = _next_save_counter(folder, prefix)
metadata = _metadata(prompt, extra_pnginfo)
saved_paths = [] saved_paths = []
for image in images: for entry in image_entries:
image = entry.get("image")
metadata = _metadata_for_entry(entry, prompt, extra_pnginfo)
file = f"{prefix}_{counter:05}_.png" file = f"{prefix}_{counter:05}_.png"
path = os.path.join(folder, file) path = os.path.join(folder, file)
_image_to_pil(image).save(path, pnginfo=metadata, compress_level=4) _image_to_pil(image).save(path, pnginfo=metadata, compress_level=4)
@@ -642,6 +673,8 @@ class SxCPAccumulator:
"entry_tag": ("STRING", {"default": "", "multiline": False}), "entry_tag": ("STRING", {"default": "", "multiline": False}),
}, },
"hidden": { "hidden": {
"prompt": "PROMPT",
"extra_pnginfo": "EXTRA_PNGINFO",
"unique_id": "UNIQUE_ID", "unique_id": "UNIQUE_ID",
}, },
} }
@@ -681,18 +714,27 @@ class SxCPAccumulator:
return value[image_index] return value[image_index]
return value return value
def _entry_records(self, image: Any, value: Any, entry_id: Any, entry_tag: str, skip_empty: bool) -> list[dict[str, Any]]: def _entry_records(
self,
image: Any,
value: Any,
entry_id: Any,
entry_tag: str,
skip_empty: bool,
metadata: dict[str, Any],
) -> list[dict[str, Any]]:
images = _split_image_value(image) images = _split_image_value(image)
if not images: if not images:
if value is None and skip_empty: if value is None and skip_empty:
return [] return []
return [{"id": self._entry_id(entry_id, entry_tag, 0, 1), "image": None, "value": value}] return [{"id": self._entry_id(entry_id, entry_tag, 0, 1), "image": None, "value": value, **metadata}]
image_count = len(images) image_count = len(images)
return [ return [
{ {
"id": self._entry_id(entry_id, entry_tag, index, image_count), "id": self._entry_id(entry_id, entry_tag, index, image_count),
"image": image_item, "image": image_item,
"value": self._value_for_image(value, index, image_count), "value": self._value_for_image(value, index, image_count),
**metadata,
} }
for index, image_item in enumerate(images) for index, image_item in enumerate(images)
] ]
@@ -748,6 +790,8 @@ class SxCPAccumulator:
value=None, value=None,
entry_id=None, entry_id=None,
entry_tag="", entry_tag="",
prompt=None,
extra_pnginfo=None,
unique_id=None, unique_id=None,
): ):
key = self._store_key(store_key, unique_id) key = self._store_key(store_key, unique_id)
@@ -758,7 +802,8 @@ class SxCPAccumulator:
store.clear() store.clear()
if action not in ("clear", "read"): if action not in ("clear", "read"):
entries = self._entry_records(image, value, entry_id, entry_tag, bool(skip_empty)) metadata = _entry_metadata(prompt, extra_pnginfo)
entries = self._entry_records(image, value, entry_id, entry_tag, bool(skip_empty), metadata)
self._append_or_replace(store, entries, action) self._append_or_replace(store, entries, action)
max_items = max(1, int(max_items)) max_items = max(1, int(max_items))
@@ -855,14 +900,14 @@ class SxCPAccumulatorPreview:
saved_paths: list[str] = [] saved_paths: list[str] = []
save_status = "" save_status = ""
if bool(save_batch) and bool(finished): if bool(save_batch) and bool(finished):
saved_paths = _save_images_to_folder(images, save_path, filename_prefix, prompt, extra_pnginfo) saved_paths = _save_images_to_folder(store, save_path, filename_prefix, prompt, extra_pnginfo)
save_status = f"; saved={len(saved_paths)}" save_status = f"; saved={len(saved_paths)}"
if saved_paths and bool(clear_after_save): if saved_paths and bool(clear_after_save):
store.clear() store.clear()
images = [] images = []
save_status += "; cleared_after_save" save_status += "; cleared_after_save"
preview_images = _preview_image_results(images, preview_limit, prompt, extra_pnginfo) preview_images = _preview_image_results(store, preview_limit, prompt, extra_pnginfo)
entries = _entry_infos(store) entries = _entry_infos(store)
status = _accumulator_status(key, store) status = _accumulator_status(key, store)
if removed: if removed:
+2 -1
View File
@@ -64,7 +64,8 @@ function entryLabel(entry) {
const id = entry?.id ? ` ${entry.id}` : ""; const id = entry?.id ? ` ${entry.id}` : "";
const image = entry?.has_image ? " image" : " value"; const image = entry?.has_image ? " image" : " value";
const shape = Array.isArray(entry?.shape) && entry.shape.length >= 2 ? ` ${entry.shape[1]}x${entry.shape[0]}` : ""; const shape = Array.isArray(entry?.shape) && entry.shape.length >= 2 ? ` ${entry.shape[1]}x${entry.shape[0]}` : "";
return `#${index}${id}${image}${shape}`.trim(); const metadata = entry?.has_metadata ? " metadata" : "";
return `#${index}${id}${image}${shape}${metadata}`.trim();
} }
function setStatus(node, status) { function setStatus(node, status) {