158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
"""
|
|
Metadata Factory — Unified metadata cleansing and signing for Easy LoRA Merger.
|
|
|
|
Provides three functions used by all nodes/engines:
|
|
|
|
scrub_metadata(metadata, mode)
|
|
→ Discard / preserve / merge based on mode.
|
|
|
|
sign_metadata(metadata, component, extra_fields)
|
|
→ Add project signature per component type.
|
|
|
|
finalize_metadata(metadata, mode, component, extra_fields)
|
|
→ Shorthand: scrub() + sign() in one call.
|
|
"""
|
|
|
|
import time
|
|
from typing import Dict, Optional, Any
|
|
|
|
from ..config import EASY_LORA_MERGER_VERSION, EASY_LORA_MERGER_DATE
|
|
|
|
# ── Helpers ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def _ensure_dict(metadata: Optional[Dict[str, str]]) -> Dict[str, str]:
|
|
"""Coerce None to empty dict."""
|
|
return dict(metadata) if metadata else {}
|
|
|
|
|
|
def _now_str() -> str:
|
|
"""Current timestamp string used in multiple signatures."""
|
|
return time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
# ── Signature presets per component ───────────────────────────────────────
|
|
|
|
|
|
def _build_signature(component: str, extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
"""Return the base signature dict for *component*, merged with *extra*."""
|
|
sig: Dict[str, str] = {}
|
|
|
|
if component == "merger":
|
|
sig["lora_merge_tool"] = "EasyLoRAMerger-Ariadne"
|
|
sig["merged_date"] = _now_str()
|
|
# merged_key_count expected in extra
|
|
|
|
elif component == "baker":
|
|
sig["baking_tool"] = "EasyLoRAMerger-SmartBaker"
|
|
sig["easy_lora_merger_version"] = EASY_LORA_MERGER_VERSION
|
|
sig["baking_date"] = _now_str()
|
|
# baking_method, baking_strength, baked_lora_source, source_checkpoint,
|
|
# baked_key_count, preserved_key_count, weight_unet/te/clip/vae,
|
|
# metadata_mode expected in extra
|
|
|
|
elif component == "converter":
|
|
sig["conversion_tool"] = "EasyLoRAMerger-LoraStudio"
|
|
sig["easy_lora_merger_version"] = EASY_LORA_MERGER_VERSION
|
|
sig["conversion_date"] = _now_str()
|
|
# target_format, converted_key_count, original_format expected in extra
|
|
|
|
elif component == "extractor":
|
|
sig["extraction_tool"] = "EasyLoRAMerger-Extractor"
|
|
sig["easy_lora_merger_version"] = EASY_LORA_MERGER_VERSION
|
|
sig["extraction_date"] = _now_str()
|
|
# base_checkpoint, tuned_checkpoint, svd_mode, target_rank, etc.
|
|
# expected in extra
|
|
|
|
elif component == "checkpoint_studio":
|
|
sig["modelspec.sai_model_spec"] = "1.0.0"
|
|
sig["modelspec.implementation"] = "musubi_checkpoint_studio"
|
|
sig["modelspec.date"] = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
# modelspec.architecture expected in extra
|
|
|
|
else:
|
|
raise ValueError(f"Unknown metadata component: {component}")
|
|
|
|
if extra:
|
|
sig.update(extra)
|
|
|
|
return sig
|
|
|
|
|
|
# ── Public API ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
def scrub_metadata(
|
|
metadata: Optional[Dict[str, str]],
|
|
mode: str = "none",
|
|
) -> Dict[str, str]:
|
|
"""Cleanse *metadata* according to *mode*.
|
|
|
|
Modes
|
|
-----
|
|
``"none"``
|
|
Discard all original metadata; return empty dict.
|
|
``"preserve_a"`` / ``"preserve_b"``
|
|
Keep original metadata as-is (both modes are symmetrical at
|
|
the single-source level — the a/b distinction only matters
|
|
for two-source merge using MetadataMerger.merge directly).
|
|
``"merge_basic"``
|
|
Return a shallow copy (no conflict resolution needed for a
|
|
single source). When two sources need merging, use
|
|
MetadataMerger.merge directly before calling this function.
|
|
"""
|
|
if metadata is None or mode == "none":
|
|
return {}
|
|
|
|
if mode in ("preserve_a", "preserve_b", "merge_basic"):
|
|
return dict(metadata) # shallow copy
|
|
|
|
raise ValueError(f"Unknown metadata_mode: {mode}")
|
|
|
|
|
|
def sign_metadata(
|
|
metadata: Dict[str, str],
|
|
component: str = "merger",
|
|
extra_fields: Optional[Dict[str, str]] = None,
|
|
) -> Dict[str, str]:
|
|
"""Add project signature to *metadata* for *component*.
|
|
|
|
Parameters
|
|
----------
|
|
metadata
|
|
Base metadata dict (already scrubbed).
|
|
component
|
|
One of ``"merger"``, ``"baker"``, ``"converter"``,
|
|
``"extractor"``, ``"checkpoint_studio"``.
|
|
extra_fields
|
|
Optional additional key/value pairs appended to the signature
|
|
(e.g. component-specific fields like ``baked_key_count``).
|
|
|
|
Returns
|
|
-------
|
|
A new dict with the signature merged on top of *metadata*.
|
|
The signature fields overwrite any colliding keys in *metadata*.
|
|
"""
|
|
base = dict(metadata) # shallow copy
|
|
sig = _build_signature(component, extra=extra_fields)
|
|
# Signature fields take priority (the project's own stamp)
|
|
base.update(sig)
|
|
return base
|
|
|
|
|
|
def finalize_metadata(
|
|
metadata: Optional[Dict[str, str]],
|
|
mode: str = "none",
|
|
component: str = "merger",
|
|
extra_fields: Optional[Dict[str, str]] = None,
|
|
) -> Dict[str, str]:
|
|
"""Convenience: scrub → sign in one call.
|
|
|
|
Equivalent to::
|
|
|
|
scrubbed = scrub_metadata(metadata, mode)
|
|
return sign_metadata(scrubbed, component, extra_fields)
|
|
"""
|
|
scrubbed = scrub_metadata(metadata, mode)
|
|
return sign_metadata(scrubbed, component, extra_fields)
|