feat: enhance A1111MetadataParser to improve metadata extraction and parsing logic. https://github.com/willmiao/ComfyUI-Lora-Manager/issues/148

This commit is contained in:
Will Miao
2025-04-30 19:09:47 +08:00
parent 947c757aa5
commit d308c7ac60

View File

@@ -452,7 +452,7 @@ class A1111MetadataParser(RecipeMetadataParser):
METADATA_MARKER = r'Lora hashes:' METADATA_MARKER = r'Lora hashes:'
LORA_PATTERN = r'<lora:([^:]+):([^>]+)>' LORA_PATTERN = r'<lora:([^:]+):([^>]+)>'
LORA_HASH_PATTERN = r'([^:]+): ([a-f0-9]+)' LORA_HASH_PATTERN = r'([^:]+):\s*([a-fA-F0-9]+)'
def is_metadata_matching(self, user_comment: str) -> bool: def is_metadata_matching(self, user_comment: str) -> bool:
"""Check if the user comment matches the A1111 metadata format""" """Check if the user comment matches the A1111 metadata format"""
@@ -461,51 +461,103 @@ class A1111MetadataParser(RecipeMetadataParser):
async def parse_metadata(self, user_comment: str, recipe_scanner=None, civitai_client=None) -> Dict[str, Any]: async def parse_metadata(self, user_comment: str, recipe_scanner=None, civitai_client=None) -> Dict[str, Any]:
"""Parse metadata from images with A1111 metadata format""" """Parse metadata from images with A1111 metadata format"""
try: try:
# Extract prompt and negative prompt # Initialize metadata with default empty values
parts = user_comment.split('Negative prompt:', 1) metadata = {"prompt": "", "loras": []}
prompt = parts[0].strip()
# Initialize metadata # Check if the user_comment contains prompt and negative prompt
metadata = {"prompt": prompt, "loras": []} if 'Negative prompt:' in user_comment:
# Extract prompt and negative prompt
# Extract negative prompt and parameters parts = user_comment.split('Negative prompt:', 1)
if len(parts) > 1: metadata["prompt"] = parts[0].strip()
negative_and_params = parts[1]
# Extract negative prompt # Extract negative prompt and parameters
if "Steps:" in negative_and_params: if len(parts) > 1:
neg_prompt = negative_and_params.split("Steps:", 1)[0].strip() negative_and_params = parts[1]
metadata["negative_prompt"] = neg_prompt
# Extract negative prompt
# Extract key-value parameters (Steps, Sampler, CFG scale, etc.) param_start = re.search(r'([A-Za-z ]+):', negative_and_params)
param_pattern = r'([A-Za-z ]+): ([^,]+)' if param_start:
params = re.findall(param_pattern, negative_and_params) neg_prompt = negative_and_params[:param_start.start()].strip()
for key, value in params: metadata["negative_prompt"] = neg_prompt
clean_key = key.strip().lower().replace(' ', '_') params_section = negative_and_params[param_start.start():]
metadata[clean_key] = value.strip() else:
params_section = negative_and_params
# Extract parameters from this section
self._extract_parameters(params_section, metadata)
else:
# No prompt/negative prompt - extract parameters directly
self._extract_parameters(user_comment, metadata)
# Extract LoRA information from prompt # Extract LoRA information from prompt if available
lora_weights = {} lora_weights = {}
lora_matches = re.findall(self.LORA_PATTERN, prompt) if metadata["prompt"]:
for lora_name, weights in lora_matches: lora_matches = re.findall(self.LORA_PATTERN, metadata["prompt"])
# Take only the first strength value (before the colon) for lora_name, weights in lora_matches:
weight = weights.split(':')[0] # Take only the first strength value (before the colon)
lora_weights[lora_name.strip()] = float(weight.strip()) weight = weights.split(':')[0]
lora_weights[lora_name.strip()] = float(weight.strip())
# Remove LoRA patterns from prompt
metadata["prompt"] = re.sub(self.LORA_PATTERN, '', prompt).strip() # Remove LoRA patterns from prompt
metadata["prompt"] = re.sub(self.LORA_PATTERN, '', metadata["prompt"]).strip()
# Extract LoRA hashes # Extract LoRA hashes
lora_hashes = {} lora_hashes = {}
if 'Lora hashes:' in user_comment: if 'Lora hashes:' in user_comment:
# Get the LoRA hashes section
lora_hash_section = user_comment.split('Lora hashes:', 1)[1].strip() lora_hash_section = user_comment.split('Lora hashes:', 1)[1].strip()
# Handle various format possibilities
if lora_hash_section.startswith('"'): if lora_hash_section.startswith('"'):
lora_hash_section = lora_hash_section[1:].split('"', 1)[0] # Extract content within quotes
hash_matches = re.findall(self.LORA_HASH_PATTERN, lora_hash_section) quote_match = re.match(r'"([^"]+)"', lora_hash_section)
for lora_name, hash_value in hash_matches: if quote_match:
# Remove any leading comma and space from lora name lora_hash_section = quote_match.group(1)
clean_name = lora_name.strip().lstrip(',').strip()
lora_hashes[clean_name] = hash_value.strip() # Split by commas and parse each LoRA entry
lora_entries = []
current_entry = ""
for part in lora_hash_section.split(','):
# Check if this part contains a colon (indicating a complete entry)
if ':' in part:
if current_entry:
lora_entries.append(current_entry.strip())
current_entry = part.strip()
else:
# This is probably a continuation of the previous entry
current_entry += ',' + part
# Add the last entry if it exists
if current_entry:
lora_entries.append(current_entry.strip())
# Process each entry
for entry in lora_entries:
# Split at the colon to get name and hash
if ':' in entry:
lora_name, hash_value = entry.split(':', 1)
# Clean the values
lora_name = lora_name.strip()
hash_value = hash_value.strip()
# Store in our dictionary
lora_hashes[lora_name] = hash_value
# Alternative backup method using regex if the above parsing fails
if not lora_hashes:
if 'Lora hashes:' in user_comment:
lora_hash_section = user_comment.split('Lora hashes:', 1)[1].strip()
if lora_hash_section.startswith('"'):
# Extract content within quotes if present
quote_match = re.match(r'"([^"]+)"', lora_hash_section)
if quote_match:
lora_hash_section = quote_match.group(1)
# Use regex to find all name:hash pairs
hash_matches = re.findall(self.LORA_HASH_PATTERN, lora_hash_section)
for lora_name, hash_value in hash_matches:
# Clean up name by removing any leading comma and spaces
clean_name = lora_name.strip().lstrip(',').strip()
lora_hashes[clean_name] = hash_value.strip()
# Process LoRAs and collect base models # Process LoRAs and collect base models
base_model_counts = {} base_model_counts = {}
@@ -523,7 +575,7 @@ class A1111MetadataParser(RecipeMetadataParser):
'existsLocally': False, 'existsLocally': False,
'localPath': None, 'localPath': None,
'file_name': lora_name, 'file_name': lora_name,
'hash': hash_value, 'hash': hash_value.lower(), # Ensure hash is lowercase
'thumbnailUrl': '/loras_static/images/no-preview.png', 'thumbnailUrl': '/loras_static/images/no-preview.png',
'baseModel': '', 'baseModel': '',
'size': 0, 'size': 0,
@@ -573,6 +625,15 @@ class A1111MetadataParser(RecipeMetadataParser):
except Exception as e: except Exception as e:
logger.error(f"Error parsing A1111 metadata: {e}", exc_info=True) logger.error(f"Error parsing A1111 metadata: {e}", exc_info=True)
return {"error": str(e), "loras": []} return {"error": str(e), "loras": []}
def _extract_parameters(self, text: str, metadata: Dict[str, Any]) -> None:
"""Extract parameters from text section and populate metadata dict"""
# Extract key-value parameters (Steps, Sampler, CFG scale, etc.)
param_pattern = r'([A-Za-z][A-Za-z0-9 _]+): ([^,]+)(?:,|$)'
params = re.findall(param_pattern, text)
for key, value in params:
clean_key = key.strip().lower().replace(' ', '_')
metadata[clean_key] = value.strip()
class ComfyMetadataParser(RecipeMetadataParser): class ComfyMetadataParser(RecipeMetadataParser):