- Add support for new ComfyUI node types in gallery metadata extraction: - CFGGuider for CFG scale values - BasicScheduler for steps parameter - KSamplerSelect for sampler selection - RandomNoise/SeedHistory for seed values - Update both main gallery (/gallery) and metadata viewer (/metadata.html) - Fix metadata display issues where technical parameters showed as "Unknown" - Add comprehensive test image and debug tools for validation - Maintain backward compatibility with legacy KSampler nodes This resolves metadata parsing issues with modern ComfyUI workflows that use the newer node architecture for sampling and generation.
213 lines
9.1 KiB
Python
213 lines
9.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Debug script to extract and analyze PNG metadata from the test image
|
|
to understand what should be displayed in the gallery views.
|
|
"""
|
|
|
|
import json
|
|
from PIL import Image
|
|
from PIL.PngImagePlugin import PngInfo
|
|
|
|
def extract_png_metadata(image_path):
|
|
"""Extract PNG metadata chunks from an image file."""
|
|
try:
|
|
with Image.open(image_path) as img:
|
|
metadata = {}
|
|
|
|
# Extract text chunks
|
|
if hasattr(img, 'text'):
|
|
for key, value in img.text.items():
|
|
metadata[key] = value
|
|
print(f"Found text chunk: {key} = {value[:100]}{'...' if len(value) > 100 else ''}")
|
|
|
|
# Try to extract PNG info
|
|
if hasattr(img, 'info'):
|
|
print(f"\nImage info keys: {list(img.info.keys())}")
|
|
for key, value in img.info.items():
|
|
if isinstance(value, str):
|
|
print(f"Info {key}: {value[:100]}{'...' if len(value) > 100 else ''}")
|
|
|
|
return metadata
|
|
|
|
except Exception as e:
|
|
print(f"Error reading PNG metadata: {e}")
|
|
return {}
|
|
|
|
def parse_comfyui_data(metadata):
|
|
"""Parse ComfyUI workflow and prompt data from PNG metadata."""
|
|
workflow_data = None
|
|
prompt_data = None
|
|
|
|
# Look for workflow data
|
|
workflow_fields = ['workflow', 'Workflow', 'comfy', 'ComfyUI']
|
|
for field in workflow_fields:
|
|
if field in metadata:
|
|
try:
|
|
workflow_data = json.loads(metadata[field])
|
|
print(f"\nFound workflow data in field '{field}'")
|
|
if isinstance(workflow_data, dict) and 'nodes' in workflow_data:
|
|
print(f"Workflow has {len(workflow_data['nodes'])} nodes")
|
|
break
|
|
except json.JSONDecodeError as e:
|
|
print(f"Failed to parse workflow field '{field}': {e}")
|
|
|
|
# Look for prompt data
|
|
prompt_fields = ['prompt', 'Prompt', 'parameters', 'Parameters']
|
|
for field in prompt_fields:
|
|
if field in metadata:
|
|
try:
|
|
# Clean up NaN values that might break JSON parsing
|
|
cleaned_json = metadata[field]
|
|
cleaned_json = cleaned_json.replace(': NaN', ': null')
|
|
cleaned_json = cleaned_json.replace(':NaN', ':null')
|
|
cleaned_json = cleaned_json.replace('NaN', 'null')
|
|
|
|
prompt_data = json.loads(cleaned_json)
|
|
print(f"\nFound prompt data in field '{field}'")
|
|
if isinstance(prompt_data, dict):
|
|
print(f"Prompt data has {len(prompt_data)} nodes")
|
|
break
|
|
except json.JSONDecodeError as e:
|
|
print(f"Failed to parse prompt field '{field}': {e}")
|
|
|
|
return workflow_data, prompt_data
|
|
|
|
def extract_generation_params(workflow_data, prompt_data):
|
|
"""Extract generation parameters from ComfyUI data."""
|
|
params = {
|
|
'checkpoint': 'Unknown',
|
|
'positive_prompt': 'No prompt found',
|
|
'negative_prompt': 'No negative prompt found',
|
|
'steps': 'Unknown',
|
|
'cfg_scale': 'Unknown',
|
|
'sampler': 'Unknown',
|
|
'seed': 'Unknown'
|
|
}
|
|
|
|
# Parse prompt data first (more reliable)
|
|
if prompt_data:
|
|
print("\nAnalyzing prompt data...")
|
|
for node_id, node in prompt_data.items():
|
|
node_type = node.get('class_type', '')
|
|
inputs = node.get('inputs', {})
|
|
|
|
print(f" Node {node_id}: {node_type}")
|
|
if inputs:
|
|
print(f" Inputs: {list(inputs.keys())}")
|
|
|
|
if node_type == 'CheckpointLoaderSimple' and 'ckpt_name' in inputs:
|
|
params['checkpoint'] = inputs['ckpt_name']
|
|
print(f" Found checkpoint: {params['checkpoint']}")
|
|
|
|
elif node_type == 'PromptManager' and 'text' in inputs:
|
|
params['positive_prompt'] = inputs['text']
|
|
print(f" Found PromptManager text: {params['positive_prompt'][:100]}...")
|
|
|
|
elif node_type == 'CLIPTextEncode' and 'text' in inputs:
|
|
text = inputs['text'].lower()
|
|
if any(neg_word in text for neg_word in ['bad anatomy', 'unfinished', 'censored', 'negative', 'embedding:']):
|
|
params['negative_prompt'] = inputs['text']
|
|
print(f" Found negative prompt: {params['negative_prompt'][:100]}...")
|
|
elif params['positive_prompt'] == 'No prompt found':
|
|
params['positive_prompt'] = inputs['text']
|
|
print(f" Found positive prompt: {params['positive_prompt'][:100]}...")
|
|
|
|
elif 'sampler' in node_type.lower() or node_type in ['KSampler', 'SamplerCustomAdvanced', 'DetailDaemonSamplerNode']:
|
|
if 'seed' in inputs:
|
|
params['seed'] = inputs['seed']
|
|
if 'steps' in inputs:
|
|
params['steps'] = inputs['steps']
|
|
if 'cfg' in inputs:
|
|
params['cfg_scale'] = inputs['cfg']
|
|
if 'sampler_name' in inputs:
|
|
params['sampler'] = inputs['sampler_name']
|
|
if 'scheduler' in inputs:
|
|
params['scheduler'] = inputs['scheduler']
|
|
print(f" Found {node_type}: seed={inputs.get('seed', 'N/A')}, steps={inputs.get('steps', 'N/A')}, cfg={inputs.get('cfg', 'N/A')}, sampler={inputs.get('sampler_name', 'N/A')}")
|
|
|
|
# Also check for BasicScheduler which might have steps
|
|
elif node_type == 'BasicScheduler' and 'steps' in inputs:
|
|
if params['steps'] == 'Unknown':
|
|
params['steps'] = inputs['steps']
|
|
print(f" Found BasicScheduler with steps: {params['steps']}")
|
|
|
|
# Check for CFG values
|
|
elif node_type == 'CFGGuider' and 'cfg' in inputs:
|
|
if params['cfg_scale'] == 'Unknown':
|
|
params['cfg_scale'] = inputs['cfg']
|
|
print(f" Found CFGGuider with cfg: {params['cfg_scale']}")
|
|
|
|
# Check for seed in various nodes
|
|
elif 'seed' in inputs:
|
|
if params['seed'] == 'Unknown':
|
|
params['seed'] = inputs['seed']
|
|
print(f" Found {node_type} with seed: {params['seed']}")
|
|
elif 'noise_seed' in inputs:
|
|
if params['seed'] == 'Unknown':
|
|
params['seed'] = inputs['noise_seed']
|
|
print(f" Found {node_type} with noise_seed: {params['seed']}")
|
|
|
|
# Check for noise/seed generators
|
|
elif node_type in ['RandomNoise', 'EmptyLatentImage'] and 'seed' in inputs:
|
|
if params['seed'] == 'Unknown':
|
|
params['seed'] = inputs['seed']
|
|
print(f" Found {node_type} with seed: {params['seed']}")
|
|
|
|
# Fallback to workflow data if needed
|
|
if workflow_data and workflow_data.get('nodes'):
|
|
print("\nAnalyzing workflow data...")
|
|
for node in workflow_data['nodes']:
|
|
node_type = node.get('type', '')
|
|
widgets = node.get('widgets_values', [])
|
|
|
|
if node_type == 'CheckpointLoaderSimple' and widgets:
|
|
if params['checkpoint'] == 'Unknown':
|
|
params['checkpoint'] = widgets[0]
|
|
print(f" Found checkpoint in workflow: {params['checkpoint']}")
|
|
|
|
elif node_type == 'PromptManager' and widgets:
|
|
if params['positive_prompt'] == 'No prompt found':
|
|
params['positive_prompt'] = widgets[0]
|
|
print(f" Found PromptManager in workflow: {params['positive_prompt'][:100]}...")
|
|
|
|
elif node_type == 'KSampler' and len(widgets) >= 4:
|
|
if params['seed'] == 'Unknown':
|
|
params['seed'] = widgets[0]
|
|
params['steps'] = widgets[1]
|
|
params['cfg_scale'] = widgets[2]
|
|
params['sampler'] = widgets[3]
|
|
print(f" Found KSampler in workflow: seed={params['seed']}, steps={params['steps']}")
|
|
|
|
return params
|
|
|
|
def main():
|
|
print("=== PNG Metadata Debug Tool ===\n")
|
|
|
|
image_path = "test_metadata_file.png"
|
|
print(f"Analyzing: {image_path}")
|
|
|
|
# Extract raw metadata
|
|
metadata = extract_png_metadata(image_path)
|
|
print(f"\nFound {len(metadata)} metadata fields:")
|
|
for key in metadata.keys():
|
|
print(f" - {key}")
|
|
|
|
# Parse ComfyUI data
|
|
workflow_data, prompt_data = parse_comfyui_data(metadata)
|
|
|
|
# Extract generation parameters
|
|
params = extract_generation_params(workflow_data, prompt_data)
|
|
|
|
print("\n=== EXTRACTED PARAMETERS ===")
|
|
for key, value in params.items():
|
|
print(f"{key}: {value}")
|
|
|
|
print("\n=== RAW METADATA FIELDS ===")
|
|
for key, value in metadata.items():
|
|
if len(value) < 200:
|
|
print(f"{key}: {value}")
|
|
else:
|
|
print(f"{key}: {value[:200]}... (truncated)")
|
|
|
|
if __name__ == "__main__":
|
|
main() |