feat: enhance MetaHub chunk processing and add StylePromptEncoder2 node
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
-2
@@ -3,7 +3,7 @@ Compatibility shim for MetaHub Save Node metadata helpers.
|
||||
|
||||
The implementation is kept in metadata_utils_impl.py; this module exposes the
|
||||
same public API expected by the save nodes while pinning the published node
|
||||
version to 1.1.3.
|
||||
version to 1.1.4.
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -11,7 +11,7 @@ try:
|
||||
except ImportError:
|
||||
import metadata_utils_impl as _impl
|
||||
|
||||
METAHUB_SAVE_NODE_VERSION = "1.1.3"
|
||||
METAHUB_SAVE_NODE_VERSION = "1.1.4"
|
||||
_impl.METAHUB_SAVE_NODE_VERSION = METAHUB_SAVE_NODE_VERSION
|
||||
|
||||
for _name in dir(_impl):
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "imagemetahub-comfyui-save"
|
||||
description = "Official companion node for Image MetaHub. Saves A1111/Civitai-compatible parameters plus extended Image MetaHub metadata (workflow JSON, SHA256 model hashes, VRAM peak/total, GPU device, generation time, and steps/sec) for reproducibility and benchmarking."
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
license = {file = "LICENSE"}
|
||||
# classifiers = [
|
||||
# # For OS-independent nodes (works on all operating systems)
|
||||
|
||||
@@ -135,6 +135,75 @@ def test_workflow_extractor_detects_lora_alternate_keys():
|
||||
assert data["lora_list"] == [{"name": "style.safetensors", "weight": 0.65}]
|
||||
|
||||
|
||||
def test_workflow_extractor_detects_rgthree_power_lora_objects():
|
||||
prompt = {
|
||||
"67": {
|
||||
"class_type": "Power Lora Loader (rgthree)",
|
||||
"inputs": {
|
||||
"lora_1": {
|
||||
"on": True,
|
||||
"lora": "Z-Detail-Slider.safetensors",
|
||||
"strength": 0.5325,
|
||||
},
|
||||
"lora_2": {
|
||||
"on": True,
|
||||
"lora": "zy_CinematicShot_zit.safetensors",
|
||||
"strength": 0.6875,
|
||||
},
|
||||
"lora_3": {
|
||||
"on": False,
|
||||
"lora": "disabled.safetensors",
|
||||
"strength": 1.0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
extractor = WorkflowExtractor(prompt)
|
||||
data, _missing = extractor.extract()
|
||||
|
||||
assert data["lora_list"] == [
|
||||
{"name": "Z-Detail-Slider.safetensors", "weight": 0.5325},
|
||||
{"name": "zy_CinematicShot_zit.safetensors", "weight": 0.6875},
|
||||
]
|
||||
|
||||
|
||||
def test_workflow_extractor_reads_style_prompt_encoder_through_basic_guider():
|
||||
prompt = {
|
||||
"61": {
|
||||
"class_type": "SamplerCustomAdvanced",
|
||||
"inputs": {
|
||||
"noise": ["63", 0],
|
||||
"guider": ["101", 0],
|
||||
"sampler": ["108", 0],
|
||||
"sigmas": ["62", 0],
|
||||
"latent_image": ["113", 0],
|
||||
},
|
||||
},
|
||||
"101": {
|
||||
"class_type": "BasicGuider",
|
||||
"inputs": {
|
||||
"model": ["100", 0],
|
||||
"conditioning": ["114", 0],
|
||||
},
|
||||
},
|
||||
"114": {
|
||||
"class_type": "StylePromptEncoder2 //ZImagePowerNodes",
|
||||
"inputs": {
|
||||
"style": "\"Production Photo\"",
|
||||
"text": "A white cat on the roof of a brick house",
|
||||
"clip": ["67", 1],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
extractor = WorkflowExtractor(prompt)
|
||||
data, missing = extractor.extract()
|
||||
|
||||
assert data["positive"] == "A white cat on the roof of a brick house"
|
||||
assert "positive" not in missing
|
||||
|
||||
|
||||
def test_workflow_extractor_resolves_connected_seed_and_prompt_strings():
|
||||
prompt = {
|
||||
"1": {
|
||||
|
||||
+35
-3
@@ -274,8 +274,12 @@ class WorkflowExtractor:
|
||||
if stacked_loras:
|
||||
loras.extend(stacked_loras)
|
||||
continue
|
||||
dynamic_loras = self._extract_dynamic_lora_entries(inputs)
|
||||
if dynamic_loras:
|
||||
loras.extend(dynamic_loras)
|
||||
continue
|
||||
lora_name = self._get_first_literal(inputs, ("lora_name", "lora", "lora_name_1", "lora_1"))
|
||||
if not lora_name:
|
||||
if not isinstance(lora_name, str) or not lora_name.strip():
|
||||
continue
|
||||
strength_model = self._get_first_literal(
|
||||
inputs,
|
||||
@@ -291,6 +295,28 @@ class WorkflowExtractor:
|
||||
|
||||
return loras, has_lora_nodes
|
||||
|
||||
def _extract_dynamic_lora_entries(self, inputs: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
loras: List[Dict[str, Any]] = []
|
||||
for key, raw_lora in inputs.items():
|
||||
if not key.lower().startswith("lora_") or not isinstance(raw_lora, dict):
|
||||
continue
|
||||
if raw_lora.get("on") is False or raw_lora.get("active") is False:
|
||||
continue
|
||||
name = raw_lora.get("lora") or raw_lora.get("lora_name") or raw_lora.get("name")
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
continue
|
||||
weight = self._coerce_float(
|
||||
raw_lora.get("strength")
|
||||
if raw_lora.get("strength") is not None
|
||||
else raw_lora.get("modelStrength")
|
||||
)
|
||||
if weight is None:
|
||||
weight = self._coerce_float(raw_lora.get("strength_model"))
|
||||
if weight is None:
|
||||
weight = 1.0
|
||||
loras.append({"name": name, "weight": float(weight)})
|
||||
return loras
|
||||
|
||||
def _extract_lora_manager_entries(self, inputs: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
raw_loras = inputs.get("loras")
|
||||
if isinstance(raw_loras, dict) and isinstance(raw_loras.get("__value__"), list):
|
||||
@@ -453,7 +479,7 @@ class WorkflowExtractor:
|
||||
class_type = self._class_type(node)
|
||||
if class_type == "ConditioningZeroOut":
|
||||
return ""
|
||||
if class_type in self.CLIP_NODES:
|
||||
if self._is_prompt_encoder(class_type):
|
||||
text = self._get_clip_text(node)
|
||||
if text is not None:
|
||||
texts.append(text)
|
||||
@@ -506,7 +532,7 @@ class WorkflowExtractor:
|
||||
if class_type == "ConditioningZeroOut":
|
||||
return ""
|
||||
|
||||
if class_type in self.CLIP_NODES:
|
||||
if self._is_prompt_encoder(class_type):
|
||||
return self._get_clip_text(node)
|
||||
|
||||
if class_lower == "joinstrings" or "join strings" in class_lower:
|
||||
@@ -663,3 +689,9 @@ class WorkflowExtractor:
|
||||
if class_type in cls.LORA_NODES:
|
||||
return True
|
||||
return "lora" in class_type.lower()
|
||||
|
||||
@classmethod
|
||||
def _is_prompt_encoder(cls, class_type: str) -> bool:
|
||||
if class_type in cls.CLIP_NODES:
|
||||
return True
|
||||
return "promptencoder" in class_type.lower().replace(" ", "")
|
||||
|
||||
Reference in New Issue
Block a user