Store accumulator metadata per entry
This commit is contained in:
+61
-16
@@ -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:
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user