From 7df83f44b885e592a11b974981c1de70dbec15e3 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Thu, 6 Aug 2026 15:58:18 +0800 Subject: [PATCH] feat(SaveImageLM): add add_loras_to_prompt toggle to restore legacy lora syntax line in metadata --- py/nodes/save_image.py | 19 ++++++++++++--- tests/nodes/test_save_image.py | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/py/nodes/save_image.py b/py/nodes/save_image.py index 92d5ad72..ddc8d2c2 100644 --- a/py/nodes/save_image.py +++ b/py/nodes/save_image.py @@ -252,6 +252,13 @@ class SaveImageLM: "tooltip": "When enabled, embeds generation parameters into the saved image metadata. Disable to skip writing generation metadata.", }, ), + "add_loras_to_prompt": ( + "BOOLEAN", + { + "default": False, + "tooltip": "When enabled, appends the LoRA syntax line (e.g. ) after the positive prompt in the saved metadata.", + }, + ), "add_counter_to_filename": ( "BOOLEAN", { @@ -348,7 +355,7 @@ class SaveImageLM: type_lower = model_type.lower() if model_type else "other" return f"urn:air:{slug}:{type_lower}:civitai:{model_id}@{version_id}" - def format_metadata(self, metadata_dict: dict) -> str: + def format_metadata(self, metadata_dict: dict, add_loras_to_prompt: bool = False) -> str: """Format metadata as A1111-compatible parameters string with Hashes JSON and Civitai resources.""" if not metadata_dict: return "" @@ -458,7 +465,10 @@ class SaveImageLM: scheduler_name = scheduler_mapping.get(scheduler, scheduler) if scheduler else None # Build output lines - lines = [prompt] if prompt else [""] + prompt_line = prompt if prompt else "" + if add_loras_to_prompt and loras_text: + prompt_line = f"{prompt_line}\n{loras_text}" if prompt_line else loras_text + lines = [prompt_line] if prompt_line else [""] if negative_prompt: lines.append(f"Negative prompt: {negative_prompt}") @@ -793,6 +803,7 @@ class SaveImageLM: save_with_metadata=True, add_counter_to_filename=True, save_as_recipe=False, + add_loras_to_prompt=False, ): """Save images with metadata""" results = [] @@ -801,7 +812,7 @@ class SaveImageLM: raw_metadata = get_metadata() metadata_dict = MetadataProcessor.to_dict(raw_metadata, id) - metadata = self.format_metadata(metadata_dict) + metadata = self.format_metadata(metadata_dict, add_loras_to_prompt) # Process filename_prefix with pattern substitution filename_prefix = self.format_filename(filename_prefix, metadata_dict) @@ -943,6 +954,7 @@ class SaveImageLM: save_with_metadata=True, add_counter_to_filename=True, save_as_recipe=False, + add_loras_to_prompt=False, ): """Process and save image with metadata""" # Make sure the output directory exists @@ -974,6 +986,7 @@ class SaveImageLM: save_with_metadata, add_counter_to_filename, save_as_recipe, + add_loras_to_prompt, ) return { diff --git a/tests/nodes/test_save_image.py b/tests/nodes/test_save_image.py index 92e078a2..f4aa6b12 100644 --- a/tests/nodes/test_save_image.py +++ b/tests/nodes/test_save_image.py @@ -86,6 +86,41 @@ def test_save_image_skips_png_parameters_when_metadata_disabled_and_keeps_workfl assert img.info["workflow"] == json.dumps(workflow) +def test_save_image_does_not_append_loras_to_prompt_by_default(monkeypatch, tmp_path): + _configure_save_paths(monkeypatch, tmp_path) + _configure_metadata( + monkeypatch, + {"prompt": "prompt text", "seed": 123, "loras": ""}, + ) + + node = SaveImageLM() + node.save_images([_make_image()], "ComfyUI", "png", id="node-1") + + image_path = tmp_path / "sample_00001_.png" + with Image.open(image_path) as img: + assert ""}, + ) + + node = SaveImageLM() + node.save_images( + [_make_image()], "ComfyUI", "png", id="node-1", add_loras_to_prompt=True + ) + + image_path = tmp_path / "sample_00001_.png" + with Image.open(image_path) as img: + assert img.info["parameters"] == ( + "prompt text\n\nSeed: 123, Version: ComfyUI" + ) + + def test_save_image_skips_jpeg_metadata_when_disabled(monkeypatch, tmp_path): _configure_save_paths(monkeypatch, tmp_path) _configure_metadata(monkeypatch, {"prompt": "prompt text", "seed": 123}) @@ -451,6 +486,14 @@ class TestParameterDefaultConsistency: assert SaveImageLM.save_images.__defaults__[5] == 0 assert SaveImageLM.process_image.__defaults__[7] == 0 + def test_add_loras_to_prompt_defaults_are_consistent(self): + input_types = SaveImageLM.INPUT_TYPES() + optional = input_types["optional"] + + assert optional["add_loras_to_prompt"][1]["default"] is False + assert SaveImageLM.save_images.__defaults__[-1] is False + assert SaveImageLM.process_image.__defaults__[-1] is False + def test_png_does_not_pass_webp_method_or_jpeg_subsampling(monkeypatch, tmp_path): _configure_save_paths(monkeypatch, tmp_path)