Files
ComfyAssets-ComfyUI_PromptM…/debug_metadata.py
T
Vito Sansevero 6fdfdea89c fix(gallery): enhance metadata parsing for modern ComfyUI workflows
- 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.
2025-08-11 15:15:19 -07:00

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()