Two ComfyUI nodes for saving PNGs with either clean (stripped) metadata or realistic, Lightroom-equivalent EXIF + XMP + IPTC + optional GPS that survives forensic inspection.
AI-generated PNGs typically ship with no EXIF/XMP or, worse, partial fake metadata that reads as obviously injected (e.g. camera model but no shutter speed). A real Lightroom export carries 40+ EXIF fields and a develop-history XMP block. This repo's node:
- Strips metadata by default on every save — no matter what — so nothing leaks by accident.
- (planned) Optionally writes a complete, internally consistent set of camera metadata from a curated profile library, with per-image randomized shutter/aperture/ISO/focal-length/timestamps, timezone-aware dates from a selectable location, and optional GPS.
The save node. Always writes PNGs to ComfyUI's output/ directory.
Inputs (planned signature):
| Input | Type | Default | Purpose |
|---|---|---|---|
images |
IMAGE |
— | ComfyUI image batch |
filename_prefix |
STRING |
ComfyUI |
Filename stem + optional subfolder (e.g. bella/img_) |
use_camera_default_filename |
BOOLEAN |
False |
Use the profile's native filename pattern (e.g. IMG_0001_.png). Requires a settings node |
strip_all_metadata |
BOOLEAN |
False |
Kill-switch — force clean output regardless of settings |
debug_console |
BOOLEAN |
False |
Print a per-file summary to the ComfyUI console |
metadata_settings (optional) |
METADATA_SETTINGS |
— | Attach a ControlMetadataSettings node to enable realistic metadata |
Behaviour:
- No
metadata_settingsconnected → clean PNG, all metadata stripped. strip_all_metadata=True→ clean PNG, even if settings are connected.metadata_settingsconnected + kill-switch off → realistic metadata written.
Produces a METADATA_SETTINGS bundle to feed into the save node. No image I/O.
Widget groups:
- Identity — profile dropdown (or
random), location dropdown (orrandom/custom), GPS on/off. - Custom location — lat/lon/tz fields, used only when
location=custom. - Exposure — shutter, aperture, ISO, focal length, capture datetime. Each
accepts
auto(sample from profile) or an explicit value. - Offset — min/max hours between capture and Lightroom save (default 4–168
hours).
DateTimeOriginalis always at least the minimum offset beforeDateTime/xmp:ModifyDate. - Author — creator, copyright, and synthetic body-serial toggle.
- Randomization — scope (per-image vs per-batch), edit intensity
(
light/medium/heavy— controls how much Lightroom develop history is written), and seed.
Ships with a curated set covering common body+lens combinations. The settings node lists them in this order, with iPhone 17 Pro selected by default:
- iPhone 17 Pro Max (back camera)
- iPhone 17 Pro (back camera) — default
- iPhone 15 Pro (back camera)
- Canon EOS-1D Mark IV + EF 100mm f/2.8L Macro IS USM
- Canon EOS 5D Mark IV + EF 24–70mm f/2.8L II USM
- Nikon D850 + AF-S 35mm f/1.4G
- Sony A7R IV + FE 85mm f/1.4 GM
Each profile is a YAML file under data/profiles/. Add your own by dropping a
new YAML + XMP templates into that directory — no code changes required. To
promote a new profile to the top of the dropdown, add its id to
_PROFILE_ORDER in metadata/profiles.py.
data/locations.yaml ships with all major US and UK cities (≈50 US metros
covering every US timezone, ≈20 UK cities across England/Scotland/Wales/NI), a
custom (lat/lon) entry for ad-hoc coordinates, and a random option.
Each location drives:
- Timezone offset on EXIF/XMP timestamps.
- Daylight-hour constraint when sampling
DateTimeOriginal(photos "taken" at 4am read as suspicious). - Optional GPS coordinates (when
write_gps=True), fuzzed ±550m around the city centre.
cd <your ComfyUI>/custom_nodes
git clone <this-repo> comfyui-control-metadata
pip install -r comfyui-control-metadata/requirements.txt # plannedRestart ComfyUI. Both nodes appear under the image category.
Drop the Save Image (Control Metadata) node into any workflow. Any existing
metadata is stripped on save. That's it.
- Add a
ControlMetadataSettingsnode. - Pick a
profile(or leaverandom), alocation(or leaverandom), optionally togglewrite_gps. - Connect its output to the
metadata_settingssocket on the save node. - Queue the prompt. The saved PNG carries a full Lightroom-style metadata
block — open it in Jeffrey's Exif Viewer or run
exiftool -a -G <file>to inspect.
If you want one batch to go out clean even with settings attached, toggle
strip_all_metadata=True on the save node.
- Files are saved as
<prefix>_<counter:05>_.pngwith an auto-incrementing counter based on existing files in the destination folder. filename_prefixmay include subfolders and ComfyUI variables (%year%,%month%,%day%,%width%,%height%).- (planned) Toggling
use_camera_default_filenamereplaces the leaf name with the active profile's native pattern (e.g.IMG_0001_.pngfor Canon,DSC_0001_.pngfor Nikon,DSC00001_.pngfor Sony — each with the correct digit width). The subfolder portion offilename_prefixis preserved.
This release changes the save-node input signature:
| Old input | New equivalent |
|---|---|
remove_any_metadata |
strip_all_metadata (renamed; old input was a no-op anyway) |
replace_metadata_with_photoshop_export |
Attach a ControlMetadataSettings node instead |
Existing workflows will fail to load until the save node is replaced and
reconnected. The node class name (SaveImageCleanMetadata) is unchanged.
- New camera profile: drop a
data/profiles/<id>.yaml+ three templates underdata/profiles/templates/<profile_dir>/{light,medium,heavy}.xmp.tmpl. - New location: add an entry to
data/locations.yamlwithid,label,lat,lon,tz(IANA), anddaylight_hours.
- No maker notes. Camera maker notes (Canon/Nikon/Sony proprietary EXIF blocks) are deliberately omitted. Faking them convincingly requires reverse-engineered encoders with checksums; real pros often strip them via Lightroom's export settings anyway.
- GPS off by default. Missing GPS is less suspicious than a present-but-wrong GPS block — many photographers disable location services. The dropdown makes it easy to turn on when you want it.
- Staggered timestamps.
xmp:CreateDate(shoot time) is always at least 4 hours beforexmp:ModifyDate(Lightroom save) by default — matching a real "shoot, import later" workflow. Develop history entries have intermediate timestamps. - Breaking changes are deliberate. The repo owner is the only user; one-shot reconnection in their workflows is a fine cost for a cleaner API.