Files

820 lines
30 KiB
Python

import json
from PIL import Image
from PIL.PngImagePlugin import PngInfo
def _save_jpeg_with_user_comment(path, comment: str, prefix: bytes = b"ASCII\x00\x00\x00"):
exif = Image.Exif()
if prefix.startswith(b"UNICODE"):
payload = comment.encode("utf-16le")
else:
payload = comment.encode("utf-8")
exif[37510] = prefix + payload
Image.new("RGB", (32, 24), (20, 40, 60)).save(path, exif=exif)
def _save_png_with_prompt(path, prompt, workflow=None):
pnginfo = PngInfo()
pnginfo.add_text("prompt", json.dumps(prompt))
if workflow is not None:
pnginfo.add_text("workflow", json.dumps(workflow))
Image.new("RGB", (32, 24), (20, 40, 60)).save(path, pnginfo=pnginfo)
def _simple_prompt_graph(positive="original positive prompt", negative="original negative prompt"):
return {
"1": {"class_type": "SaveImage", "inputs": {"images": ["2", 0]}},
"2": {"class_type": "VAEDecode", "inputs": {"samples": ["3", 0]}},
"3": {
"class_type": "KSampler",
"inputs": {
"positive": ["4", 0],
"negative": ["5", 0],
"seed": 111,
"steps": 12,
"cfg": 5.5,
"sampler_name": "euler",
"scheduler": "normal",
},
},
"4": {"class_type": "CLIPTextEncode", "inputs": {"text": positive}},
"5": {"class_type": "CLIPTextEncode", "inputs": {"text": negative}},
}
def _upscale_prompt_graph(reader_image="003.png"):
return {
"22": {
"class_type": "FaceDetailer",
"inputs": {
"image": ["24", 0],
"positive": ["26", 0],
"negative": ["27", 0],
},
},
"24": {
"class_type": "UltimateSDUpscale",
"inputs": {
"image": ["40", 0],
"positive": ["26", 0],
"negative": ["27", 0],
"seed": 222,
"steps": 9,
"cfg": 1.5,
"sampler_name": "Euler",
"scheduler": "normal",
"tile_width": 768,
"seam_fix_mode": "half tile",
},
},
"26": {"class_type": "CLIPTextEncode", "inputs": {"text": ["37", 0]}},
"27": {"class_type": "CLIPTextEncode", "inputs": {"text": ["38", 0]}},
"36": {"class_type": "SmartMetadataReader", "inputs": {"image": reader_image}},
"37": {"class_type": "ShowText|pysssss", "inputs": {"text": ["36", 2]}},
"38": {"class_type": "ShowText|pysssss", "inputs": {"text": ["36", 3]}},
"40": {"class_type": "LoadImage", "inputs": {"image": "source.png"}},
"90": {
"class_type": "GeminiChatNode",
"inputs": {"prompt": "unused Gemini prompt template"},
},
"91": {"class_type": "ShowText|pysssss", "inputs": {"text": ["90", 0]}},
"99": {"class_type": "PreviewImage", "inputs": {"images": ["22", 0]}},
}
def _upscale_workflow(reader_image="003.png"):
return {
"nodes": [
{
"id": 36,
"type": "SmartMetadataReader",
"inputs": {"image": reader_image},
"outputs": [
{"name": "image"},
{"name": "mask"},
{"name": "positive"},
{"name": "negative"},
{"name": "seed"},
{"name": "steps"},
{"name": "cfg"},
{"name": "width"},
{"name": "height"},
{"name": "model_name"},
{"name": "filename"},
{"name": "setting"},
],
},
{
"id": 91,
"type": "ShowText|pysssss",
"inputs": {"text": ["90", 0]},
"widgets_values": ["wrong unused prompt"],
},
]
}
def _llm_showtext_upscale_prompt(show_class="ShowText|pysssss", include_cache=True):
return {
"22": {
"class_type": "FaceDetailer",
"inputs": {"image": ["24", 0], "positive": ["26", 0], "negative": ["27", 0]},
},
"24": {
"class_type": "UltimateSDUpscale",
"inputs": {
"image": ["40", 0],
"positive": ["26", 0],
"negative": ["27", 0],
"seed": 222,
"steps": 9,
"cfg": 1.5,
"sampler_name": "Euler",
"scheduler": "normal",
},
},
"26": {"class_type": "CLIPTextEncode", "inputs": {"text": ["37", 0]}},
"27": {"class_type": "CLIPTextEncode", "inputs": {"text": "bad hands, low quality"}},
"37": {"class_type": show_class, "inputs": {"text": ["90", 0]}},
"40": {"class_type": "LoadImage", "inputs": {"image": "source.png"}},
"90": {
"class_type": "GeminiChatNode",
"inputs": {
"system_instruction": "system instruction should not enter prompt",
"prompt": "Gemini prompt template should not enter prompt",
"api_key": "secret-key",
"model": "gemini-test-model",
},
},
"91": {"class_type": "ShowText|pysssss", "inputs": {"text": ["90", 0]}},
"99": {"class_type": "PreviewImage", "inputs": {"images": ["22", 0]}},
}
def _llm_showtext_workflow(show_class="ShowText|pysssss", cached_text="cached gemini positive prompt"):
widgets_values = [cached_text] if cached_text else []
return {
"nodes": [
{
"id": 37,
"type": show_class,
"inputs": {"text": ["90", 0]},
"widgets_values": widgets_values,
},
{
"id": 90,
"type": "GeminiChatNode",
"inputs": {
"system_instruction": "system instruction should not enter prompt",
"prompt": "Gemini prompt template should not enter prompt",
"api_key": "secret-key",
"model": "gemini-test-model",
},
"widgets_values": [
"Gemini prompt template should not enter prompt",
"system instruction should not enter prompt",
"secret-key",
"gemini-test-model",
],
},
{
"id": 91,
"type": "ShowText|pysssss",
"inputs": {"text": ["90", 0]},
"widgets_values": ["wrong unused prompt"],
},
]
}
def _bundle_for_prompt(prompt, workflow=None, filename="sample.png", width=1200, height=1600):
from smart_metadata_reader.models import MetadataBundle
return MetadataBundle(
filename=filename,
width=width,
height=height,
prompt_raw=json.dumps(prompt),
workflow_raw=json.dumps(workflow) if workflow is not None else None,
parameters_raw=None,
source_format="ComfyUI prompt/workflow",
)
def test_read_metadata_extracts_png_text_chunks_and_dimensions(tmp_path):
from smart_metadata_reader.metadata_reader import read_metadata
image_path = tmp_path / "sample.png"
prompt_graph = {
"1": {
"class_type": "CLIPTextEncode",
"inputs": {"text": "positive trigger"},
}
}
workflow_graph = {
"nodes": [
{
"id": 1,
"type": "CLIPTextEncode",
"inputs": {"text": "positive trigger"},
}
]
}
pnginfo = PngInfo()
pnginfo.add_text("prompt", json.dumps(prompt_graph))
pnginfo.add_text("workflow", json.dumps(workflow_graph))
pnginfo.add_text(
"parameters",
(
"positive\n"
"Negative prompt: negative\n"
"Steps: 20, Sampler: Euler, CFG scale: 7, Seed: 123, Size: 512x768"
),
)
Image.new("RGBA", (64, 32), (255, 0, 0, 128)).save(
image_path, pnginfo=pnginfo
)
bundle = read_metadata(image_path)
assert bundle.filename == "sample.png"
assert bundle.width == 64
assert bundle.height == 32
assert json.loads(bundle.prompt_raw) == prompt_graph
assert json.loads(bundle.workflow_raw) == workflow_graph
assert "Negative prompt: negative" in bundle.parameters_raw
assert bundle.source_format == "ComfyUI prompt/workflow"
def test_load_image_and_mask_returns_batched_image_and_mask_shapes(tmp_path):
from smart_metadata_reader.image_io import load_image_and_mask
image_path = tmp_path / "transparent.png"
Image.new("RGBA", (10, 12), (10, 20, 30, 128)).save(image_path)
image, mask, width, height = load_image_and_mask(image_path)
assert width == 10
assert height == 12
assert tuple(image.shape) == (1, 12, 10, 3)
assert tuple(mask.shape) == (1, 12, 10)
def test_parse_standard_a1111_parameters():
from smart_metadata_reader.a1111_parser import parse_a1111_parameters
parameters = (
"masterpiece, character trigger\n"
"Negative prompt: blurry, low quality\n"
"Steps: 25, Sampler: Euler a, CFG scale: 7.5, Seed: 12345, "
"Size: 768x1024, Model: demo_model"
)
result = parse_a1111_parameters(parameters)
assert result.positive == "masterpiece, character trigger"
assert result.negative == "blurry, low quality"
assert result.steps == 25
assert result.sampler_name == "Euler a"
assert result.cfg == 7.5
assert result.seed == 12345
assert result.width == 768
assert result.height == 1024
assert result.model_name == "demo_model"
assert result.status_message == "A1111 parameters fallback"
assert result.partial_result["source_format"] == "A1111 parameters"
def test_parse_a1111_preserves_multiline_positive_and_negative_prompts():
from smart_metadata_reader.a1111_parser import parse_a1111_parameters
parameters = (
"masterpiece, best quality\n"
"character trigger, detailed eyes\n"
"Negative prompt: low quality\n"
"bad hands, extra fingers\n"
"Steps: 30, Sampler: DPM++ 2M, CFG scale: 6, Seed: 999, Size: 512x768"
)
result = parse_a1111_parameters(parameters)
assert result.positive == "masterpiece, best quality\ncharacter trigger, detailed eyes"
assert result.negative == "low quality\nbad hands, extra fingers"
assert result.steps == 30
assert result.seed == 999
def test_parse_a1111_preserves_lora_triggers_nsfw_and_user_tags():
from smart_metadata_reader.a1111_parser import parse_a1111_parameters
parameters = (
"<lora:role_lora:0.8>, role_lora_trigger, nsfw, nude, user handwritten tag\n"
"Negative prompt: low quality\n"
"Steps: 20, Sampler: Euler, CFG scale: 7, Seed: 101, Size: 640x960"
)
result = parse_a1111_parameters(parameters)
assert result.positive == (
"<lora:role_lora:0.8>, role_lora_trigger, nsfw, nude, user handwritten tag"
)
assert result.negative == "low quality"
def test_parse_a1111_without_negative_prompt_keeps_positive_and_empty_negative():
from smart_metadata_reader.a1111_parser import parse_a1111_parameters
parameters = (
"solo, dramatic lighting\n"
"Steps: 12, Sampler: Euler, CFG scale: 4.5, Seed: 42, Size: 320x448"
)
result = parse_a1111_parameters(parameters)
assert result.positive == "solo, dramatic lighting"
assert result.negative == ""
assert result.steps == 12
assert result.seed == 42
def test_parse_a1111_missing_partial_parameters_uses_defaults():
from smart_metadata_reader.a1111_parser import parse_a1111_parameters
result = parse_a1111_parameters("simple prompt only")
assert result.positive == "simple prompt only"
assert result.negative == ""
assert result.seed == -1
assert result.steps == 0
assert result.cfg == 0.0
assert result.width == 0
assert result.height == 0
assert result.model_name == ""
def test_parse_a1111_scheduler_and_model_hash_metadata():
from smart_metadata_reader.a1111_parser import parse_a1111_parameters
parameters = (
"positive text\n"
"Negative prompt: negative text\n"
"Steps: 25, Sampler: DPM++ 2M, Scheduler: Karras, CFG scale: 7.25, "
"Seed: 123, Size: 768x1024, Model hash: abc123, Model: forge_model"
)
result = parse_a1111_parameters(parameters)
assert result.scheduler == "Karras"
assert result.model_name == "forge_model"
assert result.partial_result["model_hash"] == "abc123"
def test_parse_comfyui_ultimate_sd_upscale_sampler_like_node():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
from smart_metadata_reader.models import MetadataBundle
prompt = {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {"ckpt_name": "base_model.safetensors"},
},
"2": {
"class_type": "LoraLoader",
"inputs": {
"model": ["1", 0],
"clip": ["1", 1],
"lora_name": "upscale_detail.safetensors",
"strength_model": 0.5,
"strength_clip": 0.75,
},
},
"3": {"class_type": "VAELoader", "inputs": {"vae_name": "main_vae.safetensors"}},
"4": {"class_type": "CLIPTextEncode", "inputs": {"text": "role_lora_trigger, 1girl, nsfw"}},
"5": {"class_type": "CLIPTextEncode", "inputs": {"text": "low quality"}},
"6": {
"class_type": "UltimateSDUpscaleNoUpscale",
"inputs": {
"model": ["2", 0],
"clip": ["2", 1],
"vae": ["3", 0],
"positive": ["4", 0],
"negative": ["5", 0],
"seed": 221,
"steps": 9,
"cfg_scale": 1.5,
"sampler": "Euler",
"scheduler_name": "normal",
"tile_width": 768,
"seam_fix_mode": "half tile",
"upscale_model": ["9", 0],
},
},
"7": {"class_type": "SaveImage", "inputs": {"images": ["6", 0]}},
"9": {"class_type": "UpscaleModelLoader", "inputs": {"model_name": "4x.pth"}},
}
bundle = MetadataBundle(
filename="usdu.png",
width=4800,
height=6400,
prompt_raw=json.dumps(prompt),
workflow_raw=None,
parameters_raw=None,
source_format="ComfyUI prompt/workflow",
)
result = parse_metadata_bundle(bundle)
assert result.positive == "role_lora_trigger, 1girl, nsfw"
assert result.negative == "low quality"
assert result.seed == 221
assert result.steps == 9
assert result.cfg == 1.5
assert result.sampler_name == "Euler"
assert result.scheduler == "normal"
assert result.model_name == "base_model.safetensors"
assert result.vae_name == "main_vae.safetensors"
assert [lora.lora_name for lora in result.loras] == ["upscale_detail.safetensors"]
assert "UltimateSDUpscaleNoUpscale 6" in result.debug_trace
assert "tile_width" not in result.positive
assert result.status_message == "OK"
def test_failed_comfyui_parse_includes_output_chain_diagnostics_in_setting():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
from smart_metadata_reader.models import MetadataBundle
prompt = {
"1": {"class_type": "UnsupportedGenerator", "inputs": {"image": ["3", 0]}},
"2": {"class_type": "SaveImage", "inputs": {"images": ["1", 0]}},
"3": {"class_type": "LoadImage", "inputs": {"image": "source.png"}},
}
bundle = MetadataBundle(
filename="unsupported.png",
width=512,
height=768,
prompt_raw=json.dumps(prompt),
workflow_raw=json.dumps({"nodes": []}),
parameters_raw=None,
source_format="ComfyUI prompt/workflow",
)
result = parse_metadata_bundle(bundle)
assert result.status_message == "FAILED"
assert "no supported sampler candidate found" in result.debug_trace
assert "SaveImage 2.images <- UnsupportedGenerator 1" in result.debug_trace
assert "Failure Reason:" in result.setting
assert "final output nodes found: SaveImage 2" in result.setting
def test_read_metadata_extracts_ascii_and_unicode_exif_user_comment(tmp_path):
from smart_metadata_reader.metadata_reader import read_metadata
ascii_path = tmp_path / "ascii.jpeg"
unicode_path = tmp_path / "unicode.jpeg"
_save_jpeg_with_user_comment(ascii_path, "ASCII prompt\nSteps: 9, Seed: 1")
_save_jpeg_with_user_comment(
unicode_path,
"UNICODE prompt\nSteps: 10, Seed: 2",
prefix=b"UNICODE\x00",
)
ascii_bundle = read_metadata(ascii_path)
unicode_bundle = read_metadata(unicode_path)
assert ascii_bundle.user_comment_raw == "ASCII prompt\nSteps: 9, Seed: 1"
assert unicode_bundle.user_comment_raw == "UNICODE prompt\nSteps: 10, Seed: 2"
assert ascii_bundle.source_format == "EXIF UserComment"
assert unicode_bundle.source_format == "EXIF UserComment"
def test_exif_user_comment_civitai_metadata_fallback(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
positive = (
"A green tree frog wearing a tiny straw hat sitting on a lily pad with "
"a fishing rod made from a twig and spider silk."
)
resources = [
{
"type": "checkpoint",
"modelVersionId": 2442439,
"modelName": "Z Image Turbo",
"modelVersionName": "Turbo",
},
{
"type": "lora",
"weight": 0.5,
"modelVersionId": 2460437,
"modelName": "Midjourney Luneva Cinematic Lora",
"modelVersionName": "LORA: R128-5000step",
},
{
"type": "lora",
"weight": 0.8,
"modelVersionId": 2515203,
"modelName": "[ZIT] Detail Slider",
"modelVersionName": "v1.0",
},
]
civitai_metadata = {
"workflow": "txt2img",
"cfgScale": 1,
"steps": 9,
"seed": 2214950711,
"prompt": positive,
"resources": [
{"modelVersionId": 2442439, "strength": 1, "type": "Checkpoint"},
{"modelVersionId": 2460437, "strength": 0.5, "type": "LORA"},
{"modelVersionId": 2515203, "strength": 0.8, "type": "LORA"},
],
}
user_comment = (
f"{positive}\n"
"Steps: 9, Sampler: Euler, CFG scale: 1, Seed: 2214950711, "
"Size: 832x1216, Model type: Z-Image Turbo, "
f"Civitai resources: {json.dumps(resources)}, "
f"Civitai metadata: {json.dumps(civitai_metadata)}"
)
image_path = tmp_path / "civitai.jpeg"
_save_jpeg_with_user_comment(image_path, user_comment, prefix=b"UNICODE\x00")
bundle = read_metadata(image_path)
result = parse_metadata_bundle(bundle)
assert bundle.user_comment_raw.startswith("A green tree frog")
assert result.positive == positive
assert result.negative == ""
assert result.seed == 2214950711
assert result.steps == 9
assert result.cfg == 1.0
assert result.sampler_name == "Euler"
assert result.width == 832
assert result.height == 1216
assert result.model_name == "Z Image Turbo"
assert [(lora.lora_name, lora.strength_model) for lora in result.loras] == [
("Midjourney Luneva Cinematic Lora", 0.5),
("[ZIT] Detail Slider", 0.8),
]
assert result.partial_result["source_format"] == "EXIF UserComment / Civitai metadata"
assert result.status_message == "EXIF UserComment / Civitai metadata fallback"
assert "NO_METADATA" not in result.setting
def test_nested_smart_metadata_reader_outputs_resolve_referenced_image_prompts(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
original_path = tmp_path / "003.png"
_save_png_with_prompt(original_path, _simple_prompt_graph())
upscale_path = tmp_path / "upscaled.png"
_save_png_with_prompt(
upscale_path,
_upscale_prompt_graph(),
workflow=_upscale_workflow(),
)
result = parse_metadata_bundle(read_metadata(upscale_path))
assert result.positive == "original positive prompt"
assert result.negative == "original negative prompt"
assert result.status_message != "FAILED"
assert not (result.status_message == "OK" and result.positive == "" and result.negative == "")
assert "Resolved SmartMetadataReader positive output from referenced image 003.png" in result.debug_trace
assert "Resolved SmartMetadataReader negative output from referenced image 003.png" in result.debug_trace
def test_unconnected_showtext_workflow_cache_does_not_pollute_nested_reader_prompt(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
original_path = tmp_path / "003.png"
_save_png_with_prompt(original_path, _simple_prompt_graph())
upscale_path = tmp_path / "upscaled.png"
_save_png_with_prompt(
upscale_path,
_upscale_prompt_graph(),
workflow=_upscale_workflow(),
)
result = parse_metadata_bundle(read_metadata(upscale_path))
assert result.positive == "original positive prompt"
assert "wrong unused prompt" not in result.positive
assert "wrong unused prompt" not in result.negative
def test_nested_smart_metadata_reader_falls_back_to_current_output_contract_without_workflow_outputs(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
original_path = tmp_path / "003.png"
_save_png_with_prompt(original_path, _simple_prompt_graph())
upscale_path = tmp_path / "upscaled.png"
_save_png_with_prompt(upscale_path, _upscale_prompt_graph())
result = parse_metadata_bundle(read_metadata(upscale_path))
assert result.positive == "original positive prompt"
assert result.negative == "original negative prompt"
def test_nested_smart_metadata_reader_prefers_workflow_output_names_over_indices(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
original_path = tmp_path / "003.png"
_save_png_with_prompt(original_path, _simple_prompt_graph())
prompt = _upscale_prompt_graph()
prompt["37"]["inputs"]["text"] = ["36", 3]
prompt["38"]["inputs"]["text"] = ["36", 2]
workflow = _upscale_workflow()
workflow["nodes"][0]["outputs"][2] = {"name": "negative"}
workflow["nodes"][0]["outputs"][3] = {"name": "positive"}
upscale_path = tmp_path / "upscaled.png"
_save_png_with_prompt(upscale_path, prompt, workflow=workflow)
result = parse_metadata_bundle(read_metadata(upscale_path))
assert result.positive == "original positive prompt"
assert result.negative == "original negative prompt"
def test_nested_smart_metadata_reader_missing_image_returns_partial_unresolved(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
upscale_path = tmp_path / "upscaled.png"
_save_png_with_prompt(
upscale_path,
_upscale_prompt_graph(reader_image="missing.png"),
workflow=_upscale_workflow(reader_image="missing.png"),
)
result = parse_metadata_bundle(read_metadata(upscale_path))
assert result.positive == ""
assert result.negative == ""
assert result.status_message == "PARTIAL"
assert result.confidence <= 0.6
assert result.partial_result["unresolved"][0]["class_type"] == "SmartMetadataReader"
assert result.partial_result["unresolved"][0]["output_index"] == 2
assert result.partial_result["unresolved"][0]["resolved_output_role"] == "positive"
assert result.partial_result["unresolved"][0]["referenced_image_filename"] == "missing.png"
assert "referenced image not found" in result.partial_result["unresolved"][0]["reason"]
assert "Unresolved:" in result.setting
def test_nested_smart_metadata_reader_rejects_unsafe_image_paths(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
for unsafe_path in ("../secret.png", "C:\\Users\\xxx\\secret.png"):
upscale_path = tmp_path / f"upscaled-{unsafe_path[0].encode().hex()}.png"
_save_png_with_prompt(
upscale_path,
_upscale_prompt_graph(reader_image=unsafe_path),
workflow=_upscale_workflow(reader_image=unsafe_path),
)
result = parse_metadata_bundle(read_metadata(upscale_path))
assert result.positive == ""
assert result.status_message == "PARTIAL"
assert result.partial_result["unresolved"][0]["class_type"] == "SmartMetadataReader"
assert "unsafe referenced image path" in result.partial_result["unresolved"][0]["reason"]
def test_nested_smart_metadata_reader_respects_actual_connected_output_role(tmp_path):
from smart_metadata_reader.metadata_reader import parse_metadata_bundle, read_metadata
original_path = tmp_path / "003.png"
_save_png_with_prompt(original_path, _simple_prompt_graph())
prompt = _upscale_prompt_graph()
prompt["37"]["inputs"]["text"] = ["36", 3]
upscale_path = tmp_path / "upscaled.png"
_save_png_with_prompt(upscale_path, prompt, workflow=_upscale_workflow())
result = parse_metadata_bundle(read_metadata(upscale_path))
assert result.positive == "original negative prompt"
assert "SmartMetadataReader output negative used in positive chain" in result.debug_trace
def test_showtext_cache_from_llm_chain_is_used_without_reading_llm_template():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
result = parse_metadata_bundle(
_bundle_for_prompt(
_llm_showtext_upscale_prompt(),
workflow=_llm_showtext_workflow(),
)
)
assert result.positive == "cached gemini positive prompt"
assert result.negative == "bad hands, low quality"
assert "Gemini prompt template" not in result.positive
assert "system instruction" not in result.positive
assert "secret-key" not in result.positive
assert result.status_message != "FAILED"
assert "using cached ShowText text" in result.debug_trace
assert "wrong unused prompt" not in result.positive
def test_showtext_prompt_text_0_on_final_chain_beats_llm_template():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
prompt = _llm_showtext_upscale_prompt()
prompt["37"]["inputs"]["text_0"] = "cached final chain prompt"
result = parse_metadata_bundle(_bundle_for_prompt(prompt))
assert result.positive == "cached final chain prompt"
assert "Gemini prompt template" not in result.positive
assert "system instruction" not in result.positive
assert result.status_message != "FAILED"
def test_showtext_cache_compatible_class_from_llm_chain_is_used():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
result = parse_metadata_bundle(
_bundle_for_prompt(
_llm_showtext_upscale_prompt(show_class="ShowText"),
workflow=_llm_showtext_workflow(show_class="ShowText"),
)
)
assert result.positive == "cached gemini positive prompt"
assert "Gemini prompt template" not in result.positive
def test_showtext_cache_is_single_segment_inside_string_function_chain():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
prompt = _llm_showtext_upscale_prompt()
prompt["26"]["inputs"]["text"] = ["39", 0]
prompt["39"] = {
"class_type": "StringFunction|pysssss",
"inputs": {
"action": "append",
"text_a": "manual prompt",
"text_b": ["37", 0],
"text_c": "preset prompt",
},
}
result = parse_metadata_bundle(
_bundle_for_prompt(prompt, workflow=_llm_showtext_workflow())
)
assert "manual prompt" in result.positive
assert "cached gemini positive prompt" in result.positive
assert "preset prompt" in result.positive
assert "Gemini prompt template" not in result.positive
assert "system instruction" not in result.positive
assert result.positive.index("manual prompt") < result.positive.index("cached gemini positive prompt")
assert result.positive.index("cached gemini positive prompt") < result.positive.index("preset prompt")
def test_conditioning_combine_showtext_cache_does_not_override_other_branches():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
prompt = _llm_showtext_upscale_prompt()
prompt["24"]["inputs"]["positive"] = ["41", 0]
prompt["41"] = {
"class_type": "ConditioningCombine",
"inputs": {"conditioning_1": ["42", 0], "conditioning_2": ["43", 0]},
}
prompt["42"] = {"class_type": "CLIPTextEncode", "inputs": {"text": "branch A manual prompt"}}
prompt["43"] = {"class_type": "CLIPTextEncode", "inputs": {"text": ["37", 0]}}
result = parse_metadata_bundle(
_bundle_for_prompt(prompt, workflow=_llm_showtext_workflow())
)
assert "branch A manual prompt" in result.positive
assert "cached gemini positive prompt" in result.positive
assert "Gemini prompt template" not in result.positive
def test_showtext_missing_cache_with_llm_upstream_returns_partial_unresolved():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
result = parse_metadata_bundle(
_bundle_for_prompt(
_llm_showtext_upscale_prompt(include_cache=False),
workflow=_llm_showtext_workflow(cached_text=""),
)
)
assert result.positive == ""
assert "Gemini prompt template" not in result.positive
assert "system instruction" not in result.positive
assert result.status_message == "PARTIAL"
assert result.confidence <= 0.6
assert result.partial_result["unresolved"][0]["class_type"] == "ShowText|pysssss"
reason = result.partial_result["unresolved"][0]["reason"]
assert "ShowText cache missing on final chain" in reason
assert "upstream LLM runtime output was not embedded in metadata" in reason
assert "Other ShowText caches outside the final chain are ignored" in reason
assert "wrong unused prompt" not in result.positive
assert "Unresolved:" in result.setting