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.
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.
allowed-tools
Bash, Read, Grep, Glob
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/.
For ad-hoc batch work — renaming, indexing, clustering — calling the CLI
once per file is slow. Import comfy_meta directly instead. It has no
package wrapper, so add its dir to sys.path first:
import sys, pathlib
sys.path.insert(0, str(pathlib.Path(".claude/skills/comfy-metadata/scripts")))
import comfy_meta
for p in pathlib.Path("output").iterdir():
ifnot p.is_file():
continue
ex = comfy_meta.extract(p) # {"prompt": <api-dict>, "workflow": <ui-dict>}
prompt = ex.get("prompt")
ifnotisinstance(prompt, dict) ornot prompt:
continue# no embedded metadata
summary = comfy_meta.summarize(prompt)
print(p.name, summary.sampler, summary.scheduler, summary.seed)
extract() returns parsed JSON for both halves; summarize() walks the
API prompt and yields a Summary dataclass. See
scripts/rename_outputs.py for a full example that builds new filenames
from summary.samplers[0] and the source file's mtime.
The UI workflow half is useful too
summarize() covers the API prompt, but extract()["workflow"] (the
UI form) carries data the summarizer doesn't surface — most usefully
save-node widgets. A workflow that wrote itself to a dedicated output
bucket (<bucket>/<date>/…) self-labels its outputs, so the prefix is a
free classification signal:
BUCKET = "<bucket>/"# whatever prefix your install sorts into
ex = comfy_meta.extract(p)
workflow = ex.get("workflow") or {}
for n in workflow.get("nodes", []) or []:
wv = n.get("widgets_values")
# SaveImage / SaveWEBM: list[0] is the filename_prefixifisinstance(wv, list) and wv andisinstance(wv[0], str) and wv[0].startswith(BUCKET):
return BUCKET.rstrip("/")
# VHS_VideoCombine: dict["filename_prefix"]ifisinstance(wv, dict) andstr(wv.get("filename_prefix", "")).startswith(BUCKET):
return BUCKET.rstrip("/")
rename_outputs.py's NSFW classifier combines this self-label signal
with API-prompt asset-name token matching (model / text-encoder / LoRA
names). The same approach works for any other categorisation the UI
workflow encodes that the API prompt strips out: node titles, custom
properties, group names, etc.
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.
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 0 is 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.
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 with
extract -k workflow, run it through scripts/layout_workflow.py to
tidy node positions before importing.
comfy-cli — comfy node install-deps <workflow.json> consumes a
workflow JSON file; pipe extract -k workflow straight into a temp
file to install the missing custom nodes for an imported workflow.