diff --git a/debug_metadata.py b/debug_metadata.py new file mode 100644 index 0000000..ba911e4 --- /dev/null +++ b/debug_metadata.py @@ -0,0 +1,213 @@ +#!/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() \ No newline at end of file diff --git a/image_meta_data.png b/image_meta_data.png new file mode 100644 index 0000000..e951534 Binary files /dev/null and b/image_meta_data.png differ diff --git a/test_metadata_file.png b/test_metadata_file.png new file mode 100644 index 0000000..4fb73a6 Binary files /dev/null and b/test_metadata_file.png differ diff --git a/test_metadata_parsing.html b/test_metadata_parsing.html new file mode 100644 index 0000000..6e72c76 --- /dev/null +++ b/test_metadata_parsing.html @@ -0,0 +1,253 @@ + + + + + + Metadata Parsing Test + + + +

PNG Metadata Parsing Test

+

Select the test_metadata_file.png to verify metadata extraction works correctly.

+ + + +
+ + + + \ No newline at end of file diff --git a/web/gallery.html b/web/gallery.html index 5e73781..00a4c61 100644 --- a/web/gallery.html +++ b/web/gallery.html @@ -2014,6 +2014,28 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; sampler = node.inputs.sampler_name || sampler; seed = node.inputs.seed || node.inputs.noise_seed || seed; } + + // New ComfyUI node types for modern workflows + if (node.class_type === 'CFGGuider' && node.inputs && node.inputs.cfg) { + cfgScale = node.inputs.cfg; + } + + if (node.class_type === 'BasicScheduler' && node.inputs && node.inputs.steps) { + steps = node.inputs.steps; + } + + if (node.class_type === 'KSamplerSelect' && node.inputs && node.inputs.sampler_name) { + sampler = node.inputs.sampler_name; + } + + if ((node.class_type === 'RandomNoise' || node.class_type === 'SeedHistory') && node.inputs) { + if (node.inputs.noise_seed) { + // RandomNoise nodes might reference other nodes, resolve if needed + seed = Array.isArray(node.inputs.noise_seed) ? node.inputs.noise_seed[0] : node.inputs.noise_seed; + } else if (node.inputs.seed) { + seed = node.inputs.seed; + } + } } // Process collected text nodes to identify positive/negative prompts diff --git a/web/metadata.html b/web/metadata.html index c8479b0..e4a61e8 100644 --- a/web/metadata.html +++ b/web/metadata.html @@ -235,13 +235,38 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`; } } - // Sampling parameters - if (node.class_type === 'KSampler' && node.inputs) { + // Sampling parameters - check multiple sampler types + if ((node.class_type === 'KSampler' || + node.class_type === 'KSamplerAdvanced' || + node.class_type === 'SamplerCustom' || + node.class_type === 'SamplerCustomAdvanced') && node.inputs) { seed = node.inputs.seed || seed; steps = node.inputs.steps || steps; cfgScale = node.inputs.cfg || cfgScale; sampler = node.inputs.sampler_name || sampler; } + + // New ComfyUI node types for modern workflows + if (node.class_type === 'CFGGuider' && node.inputs && node.inputs.cfg) { + cfgScale = node.inputs.cfg; + } + + if (node.class_type === 'BasicScheduler' && node.inputs && node.inputs.steps) { + steps = node.inputs.steps; + } + + if (node.class_type === 'KSamplerSelect' && node.inputs && node.inputs.sampler_name) { + sampler = node.inputs.sampler_name; + } + + if ((node.class_type === 'RandomNoise' || node.class_type === 'SeedHistory') && node.inputs) { + if (node.inputs.noise_seed) { + // RandomNoise nodes might reference other nodes, resolve if needed + seed = Array.isArray(node.inputs.noise_seed) ? node.inputs.noise_seed[0] : node.inputs.noise_seed; + } else if (node.inputs.seed) { + seed = node.inputs.seed; + } + } } } @@ -280,10 +305,12 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`; } } - // Look for sampler + // Look for sampler nodes - check multiple types const samplerNode = nodes.find(node => node.type === 'KSampler' || - node.type === 'KSamplerAdvanced' + node.type === 'KSamplerAdvanced' || + node.type === 'SamplerCustom' || + node.type === 'SamplerCustomAdvanced' ); if (samplerNode && samplerNode.widgets_values) { seed = samplerNode.widgets_values[0] || seed; @@ -291,6 +318,35 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`; cfgScale = samplerNode.widgets_values[2] || cfgScale; sampler = samplerNode.widgets_values[3] || sampler; } + + // Look for newer node types + if (steps === 'Unknown') { + const schedulerNode = nodes.find(node => node.type === 'BasicScheduler'); + if (schedulerNode && schedulerNode.widgets_values) { + steps = schedulerNode.widgets_values[1]; // steps is usually second parameter + } + } + + if (cfgScale === 'Unknown') { + const cfgNode = nodes.find(node => node.type === 'CFGGuider'); + if (cfgNode && cfgNode.widgets_values) { + cfgScale = cfgNode.widgets_values[0]; // cfg is usually first parameter + } + } + + if (sampler === 'Unknown') { + const samplerSelectNode = nodes.find(node => node.type === 'KSamplerSelect'); + if (samplerSelectNode && samplerSelectNode.widgets_values) { + sampler = samplerSelectNode.widgets_values[0]; // sampler name is usually first parameter + } + } + + if (seed === 'Unknown') { + const noiseNode = nodes.find(node => node.type === 'RandomNoise'); + if (noiseNode && noiseNode.widgets_values) { + seed = noiseNode.widgets_values[0]; // seed is usually first parameter + } + } } // Update the HTML