#!/usr/bin/env python3 """ Generate test fixture files for metadata parser tests. Each fixture embeds the same workflow and prompt JSON, matching the format the ComfyUI backend uses to write metadata. Prerequisites: source ~/ComfyUI/.venv/bin/activate python3 scripts/generate-embedded-metadata-test-files.py Output: src/scripts/metadata/__fixtures__/ """ import json import os import struct import subprocess import av from PIL import Image from PIL.PngImagePlugin import PngInfo REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) FIXTURES_DIR = os.path.join(REPO_ROOT, 'src', 'scripts', 'metadata', '__fixtures__') WORKFLOW = { 'nodes': [ { 'id': 1, 'type': 'KSampler', 'pos': [100, 100], 'size': [200, 200], } ] } PROMPT = {'1': {'class_type': 'KSampler', 'inputs': {}}} # API-format prompt with bare NaN/Infinity tokens (as Python's json.dumps emits # by default). The NaN variant fixtures omit the workflow field so the loader # must route through prompt-parsing, which trips JSON.parse on bare NaN. PROMPT_NAN = { '1': { 'class_type': 'KSampler', 'inputs': {'cfg': float('nan'), 'denoise': float('inf')}, } } WORKFLOW_JSON = json.dumps(WORKFLOW, separators=(',', ':')) PROMPT_JSON = json.dumps(PROMPT, separators=(',', ':')) PROMPT_NAN_JSON = json.dumps(PROMPT_NAN, separators=(',', ':')) def out(name: str) -> str: return os.path.join(FIXTURES_DIR, name) def report(name: str): size = os.path.getsize(out(name)) print(f' {name} ({size} bytes)') def make_1x1_image() -> Image.Image: return Image.new('RGB', (1, 1), (255, 0, 0)) def build_exif_bytes( workflow_str: str | None = WORKFLOW_JSON, prompt_str: str | None = PROMPT_JSON, ) -> bytes: """Build EXIF bytes matching the backend's tag assignments. Backend: 0x010F (Make) = "workflow:", 0x0110 (Model) = "prompt:" Pass ``None`` to omit a tag. """ img = make_1x1_image() exif = img.getexif() if workflow_str is not None: exif[0x010F] = f'workflow:{workflow_str}' if prompt_str is not None: exif[0x0110] = f'prompt:{prompt_str}' return exif.tobytes() def inject_exif_prefix_in_webp(path: str): """Prepend Exif\\0\\0 to the EXIF chunk in a WEBP file. PIL always strips this prefix, so we re-inject it to test that code path. """ data = bytearray(open(path, 'rb').read()) off = 12 while off < len(data): chunk_type = data[off:off + 4] chunk_len = struct.unpack_from('