mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
fix(civitai): improve metadata parsing for nested structures, see #700
- Refactor metadata detection to handle nested "meta" objects - Add support for lowercase "lora:" hash keys - Extract metadata from nested "meta" field when present - Update tests to verify nested metadata parsing - Handle case-insensitive LORA hash detection The changes ensure proper parsing of Civitai image metadata that may be wrapped in nested structures, improving compatibility with different API response formats.
This commit is contained in:
@@ -23,13 +23,34 @@ class CivitaiApiMetadataParser(RecipeMetadataParser):
|
||||
"""
|
||||
if not metadata or not isinstance(metadata, dict):
|
||||
return False
|
||||
|
||||
# Check for key markers specific to Civitai image metadata
|
||||
return any([
|
||||
"resources" in metadata,
|
||||
"civitaiResources" in metadata,
|
||||
"additionalResources" in metadata
|
||||
])
|
||||
|
||||
def has_markers(payload: Dict[str, Any]) -> bool:
|
||||
return any(
|
||||
key in payload
|
||||
for key in (
|
||||
"resources",
|
||||
"civitaiResources",
|
||||
"additionalResources",
|
||||
)
|
||||
)
|
||||
|
||||
if has_markers(metadata):
|
||||
return True
|
||||
|
||||
hashes = metadata.get("hashes")
|
||||
if isinstance(hashes, dict) and any(str(key).lower().startswith("lora:") for key in hashes):
|
||||
return True
|
||||
|
||||
nested_meta = metadata.get("meta")
|
||||
if isinstance(nested_meta, dict):
|
||||
if has_markers(nested_meta):
|
||||
return True
|
||||
|
||||
hashes = nested_meta.get("hashes")
|
||||
if isinstance(hashes, dict) and any(str(key).lower().startswith("lora:") for key in hashes):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def parse_metadata(self, metadata, recipe_scanner=None, civitai_client=None) -> Dict[str, Any]:
|
||||
"""Parse metadata from Civitai image format
|
||||
@@ -45,6 +66,26 @@ class CivitaiApiMetadataParser(RecipeMetadataParser):
|
||||
try:
|
||||
# Get metadata provider instead of using civitai_client directly
|
||||
metadata_provider = await get_default_metadata_provider()
|
||||
|
||||
# Civitai image responses may wrap the actual metadata inside a "meta" key
|
||||
if (
|
||||
isinstance(metadata, dict)
|
||||
and "meta" in metadata
|
||||
and isinstance(metadata["meta"], dict)
|
||||
):
|
||||
inner_meta = metadata["meta"]
|
||||
if any(
|
||||
key in inner_meta
|
||||
for key in (
|
||||
"resources",
|
||||
"civitaiResources",
|
||||
"additionalResources",
|
||||
"hashes",
|
||||
"prompt",
|
||||
"negativePrompt",
|
||||
)
|
||||
):
|
||||
metadata = inner_meta
|
||||
|
||||
# Initialize result structure
|
||||
result = {
|
||||
@@ -62,8 +103,9 @@ class CivitaiApiMetadataParser(RecipeMetadataParser):
|
||||
lora_hashes = {}
|
||||
if "hashes" in metadata and isinstance(metadata["hashes"], dict):
|
||||
for key, hash_value in metadata["hashes"].items():
|
||||
if key.startswith("LORA:"):
|
||||
lora_name = key.replace("LORA:", "")
|
||||
key_str = str(key)
|
||||
if key_str.lower().startswith("lora:"):
|
||||
lora_name = key_str.split(":", 1)[1]
|
||||
lora_hashes[lora_name] = hash_value
|
||||
|
||||
# Extract prompt and negative prompt
|
||||
|
||||
@@ -107,6 +107,12 @@ class RecipeAnalysisService:
|
||||
raise RecipeDownloadError("No image URL found in Civitai response")
|
||||
await self._download_image(image_url, temp_path)
|
||||
metadata = image_info.get("meta") if "meta" in image_info else None
|
||||
if (
|
||||
isinstance(metadata, dict)
|
||||
and "meta" in metadata
|
||||
and isinstance(metadata["meta"], dict)
|
||||
):
|
||||
metadata = metadata["meta"]
|
||||
else:
|
||||
await self._download_image(url, temp_path)
|
||||
|
||||
|
||||
@@ -60,6 +60,46 @@ async def test_parse_metadata_creates_loras_from_hashes(monkeypatch):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_parse_metadata_handles_nested_meta_and_lowercase_hashes(monkeypatch):
|
||||
async def fake_metadata_provider():
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"py.recipes.parsers.civitai_image.get_default_metadata_provider",
|
||||
fake_metadata_provider,
|
||||
)
|
||||
|
||||
parser = CivitaiApiMetadataParser()
|
||||
|
||||
metadata = {
|
||||
"id": 106706587,
|
||||
"meta": {
|
||||
"prompt": "An enigmatic silhouette",
|
||||
"hashes": {
|
||||
"model": "ee75fd24a4",
|
||||
"lora:mj": "de49e1e98c",
|
||||
"LORA:Another_Earth_2": "dc11b64a8b",
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"hash": "ee75fd24a4",
|
||||
"name": "stoiqoNewrealityFLUXSD35_f1DAlphaTwo",
|
||||
"type": "model",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
assert parser.is_metadata_matching(metadata)
|
||||
|
||||
result = await parser.parse_metadata(metadata)
|
||||
|
||||
assert result["gen_params"]["prompt"] == "An enigmatic silhouette"
|
||||
assert {l["name"] for l in result["loras"]} == {"mj", "Another_Earth_2"}
|
||||
assert {l["hash"] for l in result["loras"]} == {"de49e1e98c", "dc11b64a8b"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_parse_metadata_populates_checkpoint_and_rewrites_thumbnails(monkeypatch):
|
||||
checkpoint_info = {
|
||||
|
||||
Reference in New Issue
Block a user