Comfy Metadata
Extract/analyze ComfyUI metadata embedded in output files - PNG tEXt, WebP EXIF, MP4/WebM contain…
by laurigates · MIT
The skill
Extract/analyze ComfyUI metadata embedded in output files - PNG tEXt, WebP EXIF, MP4/WebM container, .latent safetensors. Use when figuring out what prompt/settings produced a file, or comparing runs.
ComfyUI output metadata
Every image/video/audio file ComfyUI emits embeds the API-form prompt and the UI-form workflow that produced it. The encoding varies by file format and by which save-node wrote it (core vs. kijai vs. VHS). This skill is the canonical reference for where the data lives, how to read it back, and a small Python toolkit to do it reliably across every format on disk in this install.
When to Use This Skill
| Use this skill when... | Use instead when... |
|---|---|
| Figuring out what prompt/settings produced an existing output file | Inspecting a live in-graph value during a run -> comfy-debug-preview |
| Scanning, organizing, or comparing a directory of outputs | Auto-arranging the layout of a workflow JSON -> comfy-workflow-layout |
Quick reference
| Format | Saved by | Where the JSON lives | Encoding |
|---|---|---|---|
| PNG still | core SaveImage, kijai PNG path |
tEXt chunks (PIL Image.info["prompt"] and ["workflow"]) |
each value is a JSON string |
| Animated PNG | core SaveAnimatedPNG |
iTXt chunks (same keys) |
each value is a JSON string |
| WebP still + animated | core SaveAnimatedWEBP |
EXIF tags: 0x0110 (Model) holds "prompt:<json>", 0x010F (Make) holds "workflow:<json>", lower tags hold further extra_pnginfo keys |
one EXIF string per key, "key:json" prefix |
| MP4 native | core SaveVideo |
Container metadata, separate keys: prompt, workflow, plus any extra |
each value is a JSON string |
MP4 kijai (WanVideoWrapper_*.mp4) |
WanVideoWrapper.save_video |
Container metadata, single key comment |
one JSON object: {"prompt": "<json>", "workflow": "<json>"} (double-encoded) |
| WebM / Matroska | core SaveVideo; kijai/MMAudio |
Container metadata: per-key (native) or single COMMENT (kijai) |
same patterns as MP4 |
| FLAC / OGG / MP3 / WAV | core SaveAudio |
Container metadata: prompt, extra_pnginfo* keys |
each value is a JSON string |
.latent |
core SaveLatent |
safetensors header metadata: prompt, workflow |
each value is a JSON string |
The library handles all of these uniformly. See REFERENCE.md for code
anchors, exact byte-level details, and edge cases (the _create_webp_metadata
EXIF tag walk, extra_pnginfo keys beyond workflow, the kijai
double-encoded comment format, fp8-scaled safetensors metadata, etc.).
Toolkit
scripts/comfy_meta.py is a single self-contained Python file. It works
as both a library and a CLI with four subcommands. It uses PIL (for
PNG/WebP), PyAV (for MP4/WebM/audio), and safetensors (for
.latent) — all already installed in .venv/.
Run via the project venv:
.venv/bin/python .claude/skills/comfy-metadata/scripts/comfy_meta.py <subcommand> ...
Library use (batch scripts)
For batch work over many files (renaming, indexing, clustering), import comfy_meta directly instead of calling the CLI per file — the sys.path setup and an extract() / summarize() loop are in references/library-use.md.
The UI workflow half is useful too
extract()["workflow"] carries data summarize() drops — save-node filename_prefix widgets, node titles, group names — useful as a classification signal; example in references/library-use.md.
extract — dump the embedded JSON
# Both prompt + workflow as one JSON object on stdout
.venv/bin/python .../comfy_meta.py extract output/WanVideoWrapper_I2V_00001.png
# Just one half (suitable for piping to jq)
.venv/bin/python .../comfy_meta.py extract -k prompt path/to.png | jq .
.venv/bin/python .../comfy_meta.py extract -k workflow path/to.mp4 | jq '.nodes | length'
# Re-import a downloaded JPEG/MP4 back into ComfyUI by saving its workflow:
.venv/bin/python .../comfy_meta.py extract -k workflow some.mp4 > user/default/workflows/2026-05/recovered.json
summary — one-line, analysis-friendly settings
The summarizer walks the API-form prompt and pulls out the fields that actually matter for "what was different between run A and run B": model, text encoders, VAE, every sampler invocation (sampler/scheduler/steps/ cfg/denoise/seed), latent dims, num_frames, every LoRA + strength, and the positive/negative prompt text.
.venv/bin/python .../comfy_meta.py summary output/WanVideoWrapper_I2V_00001.mp4
Output is JSON; use -p for a human-readable two-column print instead.
scan — index a directory into JSONL
Walk a tree (recursively by default) and emit one JSON record per output file. Use this to build a queryable index of every render on disk.
.venv/bin/python .../comfy_meta.py scan output/ -o /tmp/runs.jsonl
# Then analyze with jq:
jq -r '[.path, .summary.steps, .summary.cfg, .summary.sampler] | @tsv' /tmp/runs.jsonl
# Group by sampler+steps+cfg to see what combinations were used:
jq -s 'group_by(.summary.sampler+"|"+(.summary.steps|tostring)+"|"+(.summary.cfg|tostring))
| map({key: .[0].summary | "\(.sampler) steps=\(.steps) cfg=\(.cfg)", count: length})' \
/tmp/runs.jsonl
Files without embedded metadata (e.g. phone photos in the same tree)
get {"path": "...", "error": "no metadata"} so the index still
covers everything.
diff — what changed between two runs
.venv/bin/python .../comfy_meta.py diff a.mp4 b.mp4
Prints a unified diff of the summarized settings. Useful when one of two near-identical workflows produced a better result and you want to see which knob actually moved.
What the summary captures
model: UNETLoader.unet_name / CheckpointLoaderSimple.ckpt_name
/ WanVideoModelLoader.model / Image-Edit's diffusion path
text_encoders [list]: CLIPLoader / DualCLIPLoader / TripleCLIPLoader
/ LoadWanVideoT5TextEncoder / TextEncoderLoaderHiDream …
vae: VAELoader.vae_name / WanVideoVAELoader.model_name
samplers [list]: every KSampler / KSamplerAdvanced / WanVideoSampler /
WanVideoSamplerv2 / SamplerCustomAdvanced — each with
{sampler, scheduler, steps, cfg, denoise, seed, start_step,
end_step, add_noise} as found
latent_dims: width × height from EmptyLatentImage / EmptySD3LatentImage /
EmptyMochiLatentVideo / WanVideoEmptyEmbeds / etc.
num_frames: from WanVideoEmptyEmbeds.num_frames / Empty*Video.length
loras [list]: every LoraLoader / LoraLoaderModelOnly / Power Lora Loader
entry — {name, model_strength, clip_strength}
shift: ModelSamplingAuraFlow / ModelSamplingSD3 shift values
positive [list], negative [list]: CLIPTextEncode-style text inputs, with
the upstream node's title as a hint when present
Heuristic, not exhaustive — but covers ~95% of the workflows on this
install. New node-types missing from the summarizer are still preserved
in the raw prompt half of extract; add them to comfy_meta.py's
SUMMARIZERS registry when a class becomes worth pulling out.
When the toolkit returns "no metadata"
A few cases that look like ComfyUI outputs but lack the JSON:
- ComfyUI launched with
--disable-metadata— the save nodes short-circuit before adding tEXt/EXIF/container tags. - Re-encoded with ffmpeg —
ffmpeg -i in.mp4 -c copy out.mp4does preserve container metadata;-c:v libx264 …(re-encode) typically drops it unless-map_metadata 0is passed. - Re-saved through an image editor (Affinity, Photoshop, GIMP) — most strip tEXt chunks and rewrite EXIF.
- Output from a frontend that bypasses save nodes (custom HTTP pipelines, Hugging Face Spaces wrapping ComfyUI, …).
For the second case, when you mv or cp files between dirs the
metadata is fine — the OS-level operations preserve byte content. Only
re-encoding strips it.
Privacy note
The embedded prompt JSON contains the full positive and negative
text prompts, the exact seed, file paths to LoRAs/checkpoints/VAEs
(which can leak local directory structure like
models/loras/lgates/private_face_v1.safetensors), and sometimes
authoring metadata in extra_pnginfo. Before sharing a ComfyUI output
file publicly, decide whether you want to ship the metadata with it.
To strip metadata in-place (lossless):
- PNG:
oxipng --strip safe file.png(keeps colorspace, strips text) - WebP: re-encode with
cwebp -metadata noneormagick convert in.webp -strip out.webp - MP4/WebM:
ffmpeg -i in.mp4 -c copy -map_metadata -1 out.mp4
Or set --disable-metadata on the ComfyUI server (in comfyui.service)
if you want all future outputs to be metadata-free — but the toolkit
becomes useless then.
Related skills
comfy-workflow-layout— once you've extracted a workflow withextract -k workflow, run it throughscripts/layout_workflow.pyto tidy node positions before importing.comfy-cli—comfy node install-deps <workflow.json>consumes a workflow JSON file; pipeextract -k workflowstraight into a temp file to install the missing custom nodes for an imported workflow.
Use this skill
Connect your agent to the Skill Store MCP server and ask for it by ref: laurigates/comfy-metadata-59323913024.
Details
- Publisher
- laurigates
- Category
- Creative & Media › Image & design
- License
- MIT
- Updated
- 2026-10-19
References
More from laurigates
- Docs Refresh Refresh plugin catalog docs (README, PLUGIN-MAP, d2 diagram) so per-plugin skill/agent counts mat…
- Plugin Authoring Add, modify, or delete a skill or plugin in this repo — frontmatter shape, the seven metadata fil…
- Accessibility Implementation WCAG 2.1/2.2 compliance, ARIA patterns, keyboard nav, focus management, a11y testing.
- Design Tokens Design tokens and CSS custom properties: theme systems, dark mode, component libraries.