Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when preparing photos or images for a news wire, publication, photo CMS, or archive — embedding caption, byline, credit, alt text, keywords, copyright or Creative Commons license, AI/synthetic source labeling, and location into a file's IPTC, EXIF, and XMP metadata, batch-tagging a folder of press photos with exiftool, or verifying the C2PA Content Credentials on an inbound image.
.claude/skills/jamditis-photo-metadata/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 167% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 108% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 266% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 227% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 242% | 0% |
Metadata embedded in an image file travels with the file. Photo CMSs (Photo Mechanic, Lightroom, Capture One) and wire intake systems read a photo's caption, credit, and rights from its IPTC and XMP blocks, not from a separate document or the email it arrived in. If the caption, credit, alt text, and license are not inside the file, they are gone the moment the photo is downloaded, forwarded, or re-uploaded.
One exiftool pass writes the EXIF, IPTC, and XMP layers together and leaves every other tag (camera settings, shot time) untouched. Modern software reads XMP first, legacy IPTC-IIM second, EXIF only for date and GPS, so write XMP everywhere and add IIM as a compatibility copy on JPEG/TIFF (HEIC, AVIF, and WebP have no IIM slot at all; see reference.md).
Two things changed since this workflow was "caption, credit, copyright." First, how an image was made now belongs in the metadata: the IPTC Digital Source Type field labels a camera photo versus an AI-generated one, and platforms (Meta, Google) and the EU AI Act increasingly read it. Second, cryptographic provenance (C2PA / "Content Credentials") is arriving on wire images and cameras, a signed layer exiftool can read but not write. Both are covered below.
A capable model already knows the field names. The hard part is not the mechanics, it is the judgment below. Lead with that.
When not to use: editing pixels (this is metadata only); writing alt text for an HTML <img> (use accessibility-compliance); preserving web pages as evidence (use web-archiving); signing a Content Credential (exiftool can't, use c2patool, below).
These are the failures a capable agent makes anyway. They matter more than any tag name.
XMP-iptcCore:AltTextAccessibility and the publishable caption in IPTC:Caption-Abstract. IPTC keeps these deliberately distinct, the caption states facts and is shown on the page; the alt text is read aloud by a screen reader, so do not just copy one into the other. Agents routinely write the caption and skip the alt text.XMP-iptcExt:DigitalSourceType; if it is a straight photo, digitalCapture states that plainly. Do the honest thing and label it; do the diligent thing and, on an inbound file, never strip an existing Digital Source Type or C2PA credential, that erases a disclosure someone made on purpose.-gps:all=) while keeping the caption and credit; keep a full-GPS archival master only where location is editorial evidence. GPS is the single highest-risk tag in the file.Headline, never in City, Caption-Abstract, or the location fields. Partner newsrooms apply their own language; clean structured fields let them.reference.md), so "I embedded it" is not "it arrived."| Role | IPTC (IIM) | XMP | EXIF | |------|-----------|-----|------| | Photographer | By-line | dc:Creator | Artist | | Credit | Credit (org, max 32 chars) | photoshop:Credit (full name / org) | - | | Caption | Caption-Abstract | dc:Description | ImageDescription | | Alt text (short) | - | iptcCore:AltTextAccessibility | - | | Extended description | - | iptcCore:ExtDescrAccessibility (complex images; not the caption) | - | | How it was made | - | iptcExt:DigitalSourceType (full CV URI) | - | | Keywords | Keywords (repeatable) | dc:Subject | - | | Copyright | CopyrightNotice | dc:Rights | Copyright | | License (CC) | - | xmpRights:Marked/WebStatement/UsageTerms, cc:License (legacy) | - | | License / discovery | - | xmpRights:WebStatement, plus:LicensorName/LicensorURL (Google) | - | | Headline | Headline | photoshop:Headline | - | | Location | Sub-location/City/Province-State/Country-* | iptcCore:Location, photoshop:City/State/Country | - | | Date | DateCreated | photoshop:DateCreated | DateTimeOriginal (source of truth) |
Digital Source Type values (fully AI → trainedAlgorithmicMedia, AI-edited → compositeWithTrainedAlgorithmicMedia, straight photo → digitalCapture), the full IPTC controlled vocabulary, the IPTC-IIM byte limits, the PLUS/Google-licensing and Creative Commons field sets, the C2PA tooling, and the AP caption recipe: see reference.md.
bashCAPTION="A crowd holds signs outside the Mercer County Courthouse, Friday, June 19, 2026, in Trenton, N.J. (Dana Rivera/Example News Collective)" ALT="A crowd of people holding handmade signs stands on the steps of a stone courthouse." # how the image was made, a full IPTC CV URI (see reference.md for all values) DST="http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture" exiftool -codedcharacterset=utf8 -overwrite_original -P \ -EXIF:Artist="Dana Rivera" -XMP-dc:Creator="Dana Rivera" -IPTC:By-line="Dana Rivera" \ -IPTC:Credit="Example News Collective" -XMP-photoshop:Credit="Dana Rivera / Example News Collective" \ -IPTC:Caption-Abstract="$CAPTION" -XMP-dc:Description="$CAPTION" -EXIF:ImageDescription="$CAPTION" \ -XMP-iptcCore:AltTextAccessibility="$ALT" \ -XMP-iptcExt:DigitalSourceType="$DST" \ -IPTC:Keywords="protest" -IPTC:Keywords+="Trenton" \ -XMP-dc:Subject="protest" -XMP-dc:Subject+="Trenton" \ -EXIF:Copyright="(c) 2026 Example News Collective. Licensed CC BY 4.0." \ -IPTC:CopyrightNotice="(c) 2026 Example News Collective. CC BY 4.0." \ -XMP-dc:Rights="(c) 2026 Example News Collective. Licensed CC BY 4.0." \ -XMP-xmpRights:Marked=True \ -XMP-xmpRights:WebStatement="https://creativecommons.org/licenses/by/4.0/" \ -XMP-xmpRights:UsageTerms="Licensed CC BY 4.0. Credit: Dana Rivera / Example News Collective." \ -XMP-cc:License="https://creativecommons.org/licenses/by/4.0/" \ -XMP-cc:AttributionName="Dana Rivera / Example News Collective" \ -IPTC:City="Trenton" -IPTC:Province-State="New Jersey" \ -IPTC:Country-PrimaryLocationName="United States" -IPTC:Country-PrimaryLocationCode="USA" \ "-IPTC:DateCreated<EXIF:DateTimeOriginal" "-IPTC:TimeCreated<EXIF:DateTimeOriginal" \ "-XMP-photoshop:DateCreated<EXIF:DateTimeOriginal" \ photo.jpg
-P preserves the file's modification time; drop it if you want the write to touch the timestamp. Extended accessibility descriptions for complex images (charts, infographics) go in XMP-iptcCore:ExtDescrAccessibility, a separate field from the caption, added only when the alt text plus surrounding text can't convey the image.
Then verify from the file (the step agents skip):
bashexiftool -G1 -s -IPTC:By-line -IPTC:Caption-Abstract -XMP-iptcCore:AltTextAccessibility \ -XMP-iptcExt:DigitalSourceType -XMP-cc:License -IPTC:Keywords photo.jpg
XMP-iptcExt:DigitalSourceType records origin from the IPTC controlled vocabulary. The value is a full URI, exiftool does not validate it, so a bare word or a typo is silently accepted and useless. The three every newsroom needs:
bashBASE="http://cv.iptc.org/newscodes/digitalsourcetype" # a straight camera photo, worth stating even for real news images exiftool -XMP-iptcExt:DigitalSourceType="$BASE/digitalCapture" photo.jpg # fully AI-generated (a trained model produced the whole image) exiftool -XMP-iptcExt:DigitalSourceType="$BASE/trainedAlgorithmicMedia" ai.jpg # a real photo edited with generative AI (inpaint / outpaint / generative fill) exiftool -XMP-iptcExt:DigitalSourceType="$BASE/compositeWithTrainedAlgorithmicMedia" edited.jpg
Meta and Google read this field to auto-label AI content, and the EU AI Act's machine-readable-disclosure duty (Article 50, enforcement from August 2026) is pushing it from nice-to-have toward required. IPTC 2025.1 adds companion fields, AISystemUsed, AISystemVersionUsed, AIPromptInformation, AIPromptWriterName (exiftool ≥ 13.40). Full vocabulary and the retired terms to avoid: reference.md.
A Content Credential is a cryptographically signed C2PA manifest bound to the pixels, who made the image, in what tool, and whether AI was involved, increasingly shipped by cameras (Leica M11-P, Nikon Z6III, Sony Alpha) and agencies (AFP, AP, BBC pilots). It is a different layer from IPTC/XMP and answers a different question: not "what does the file claim" but "who signed this, and has it changed since."
exiftool reads it and cannot write or verify it:
bashexiftool -G1 -a -jumbf:all incoming.jpg # report the C2PA/JUMBF manifest (no signature check)
That shows the manifest as data, it does not validate the signature or the signer. For a real check, drop the file into verify.contentauthenticity.org and confirm the signer is the agency you expect. To create a credential, use Adobe/CAI tooling, c2patool (brew install c2patool) or pip install c2pa-python, not exiftool.
Writing metadata to a signed file breaks its credential. A C2PA hard binding hashes the asset, and that hash covers the embedded metadata, so any exiftool write, caption, credit, GPS strip, even the tagging in this skill, leaves the manifest present but invalid. "Never strip the credential" is necessary but not sufficient. On an inbound signed file, either leave the original untouched and do your metadata work on a derivative you will re-sign with c2patool, or accept that the embedded credential no longer validates and say so. Do not embed metadata into a signed original and treat its credential as still good.
Two more cautions worth stating to any newsroom: a valid credential proves a signature and a chain, not that the scene is real (a camera will happily sign a photo of a screen), and most social platforms strip the manifest on upload, so on-platform provenance often survives only via "durable" watermark/fingerprint recovery. See reference.md.
Remove location without touching the caption, credit, copyright, or source type:
bashexiftool -gps:all= "-xmp:GPS*=" -overwrite_original photo.jpg exiftool -a -G1 -gps:all "-xmp:GPS*" photo.jpg # verify, this must print nothing
Use the -xmp:GPS*= wildcard, not just the three main coordinates: destination and image-direction fields (GPSDestLatitude, GPSImgDirection) are also a location and would otherwise survive. Keep the full-GPS file as a locked archival master where coordinates are editorial evidence (geolocation, verification). Publish the stripped copy. embed.py --strip-gps does this for a whole folder after tagging.
To earn the Google Images "Licensable" badge and a working "Get this image" link, set the web statement of rights (the trigger) and the PLUS licensor fields:
bashexiftool -XMP-xmpRights:Marked=True \ -XMP-xmpRights:WebStatement="https://example.org/license/photo123" \ -XMP-plus:LicensorName="Example News" -XMP-plus:LicensorURL="https://example.org/buy/photo123" \ photo.jpg
The web statement is xmpRights:WebStatement, not dc:Rights, a common and costly mix-up. A Creative Commons license routes through the same WebStatement field with the CC deed URL. Details and the full PLUS field set: reference.md.
For a shoot, drive exiftool from a manifest instead of one command per file. embed.py in this directory takes a folder plus a JSON manifest (constant credit, license, licensor, and Digital Source Type fields, then per-image alt text, caption, extended description, keywords, and an optional per-image source-type override), writes tagged copies, reads each one back to confirm the metadata landed, and, with --strip-gps, removes GPS from the copies. It accepts a Digital Source Type shorthand (digitalCapture) or a full URI and refuses anything else rather than embedding a broken value. Run python3 embed.py --help.
| Mistake | Fix | |---------|-----| | Wrote a caption, no alt text | Always write AltTextAccessibility too, they are different fields | | Copied the caption into the alt text (or ExtDescrAccessibility) | IPTC keeps these distinct; write a real screen-reader sentence, keep ExtDescr for complex images only | | By-line/Credit/City silently truncated | Those IIM fields cap at 32 chars; put the full credit in XMP-photoshop:Credit | | Caption states things not in the frame | Describe only what is visible; move unseeable context out | | AI-generated image left unlabeled | Set DigitalSourceType to trainedAlgorithmicMedia (or the right composite value) | | DigitalSourceType set to a bare word | The value must be the full http://cv.iptc.org/... URI; exiftool won't validate it | | Stripped an inbound file's Digital Source Type or C2PA | Never erase a disclosure, preserve provenance on files you receive | | Published with GPS still embedded | Strip with -gps:all= when location could endanger a subject or source | | WebStatement put in dc:Rights | The Google/licensing web statement is xmpRights:WebStatement | | Editorial label in City or caption | Put framing in Headline; keep structured fields neutral | | Assumed the upload kept the metadata | Re-read from the destination; most social platforms strip IPTC/XMP/GPS/C2PA | | Keywords as one comma-joined string | Write repeatable Keywords records (and a dc:Subject list) | | Set a CC license note in plain text only | Add xmpRights:WebStatement (CC deed URL) + xmpRights:Marked |
Embedded metadata is what lets a partner newsroom find a photo, credit it correctly, and republish it under a clear license without ever contacting the photographer. It is also, now, where an image says whether a human or a model made it, and where a signed Content Credential travels. Strip it, and the same photo is an orphaned file, no credit, no license, no provenance.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | pass→pass | 17,744 | 16,104 | -9% | 1 | 1 | 0% | 3,588 | 7,731 | +115% | 0 | 0 | — |
case-01 | fail→pass | 42,617 | 39,825 | -7% | 1 | 1 | 0% | 2,537 | 6,774 | +167% | 0 | 0 | — |
case-03 | fail→pass | 47,776 | 44,835 | -6% | 1 | 1 | 0% | 3,625 | 7,556 | +108% | 0 | 0 | — |
case-04 | fail→fail | 13,639 | 11,585 | -15% | 1 | 1 | 0% | 2,303 | 6,342 | +175% | 0 | 0 | — |
case-05 | fail→fail | 6,697 | 9,384 | +40% | 1 | 1 | 0% | 1,350 | 5,664 | +320% | 0 | 0 | — |
case-06 | fail→pass | 9,215 | 43,303 | +370% | 1 | 1 | 0% | 1,737 | 6,359 | +266% | 0 | 0 | — |
case-07 | fail→fail | 16,113 | 25,591 | +59% | 1 | 1 | 0% | 2,557 | 9,522 | +272% | 0 | 0 | — |
case-14 | fail→pass | 10,929 | 11,136 | +2% | 1 | 1 | 0% | 2,016 | 6,595 | +227% | 0 | 0 | — |
case-08 | fail→pass | 11,567 | 8,390 | -27% | 1 | 1 | 0% | 1,644 | 5,617 | +242% | 0 | 0 | — |
case-09 | pass→pass | 15,322 | 11,779 | -23% | 1 | 1 | 0% | 2,652 | 6,216 | +134% | 0 | 0 | — |
case-10 | fail→pass | 12,067 | 12,031 | -0% | 1 | 1 | 0% | 2,154 | 6,246 | +190% | 0 | 0 | — |
case-11 | pass→pass | 16,536 | 12,278 | -26% | 1 | 1 | 0% | 2,585 | 6,555 | +154% | 0 | 0 | — |
case-12 | pass→pass | 15,789 | 6,051 | -62% | 1 | 1 | 0% | 2,721 | 5,430 | +100% | 0 | 0 | — |
case-13 | pass→pass | 11,346 | 6,886 | -39% | 1 | 1 | 0% | 1,782 | 5,297 | +197% | 0 | 0 | — |
case-15 | pass→pass | 3,240 | 2,860 | -12% | 1 | 1 | 0% | 447 | 4,781 | +970% | 0 | 0 | — |
case-16 | fail→pass | 13,035 | 8,711 | -33% | 1 | 1 | 0% | 2,235 | 5,978 | +167% | 0 | 0 | — |
case-17 | pass→pass | 17,672 | 19,080 | +8% | 1 | 1 | 0% | 3,025 | 7,012 | +132% | 0 | 0 | — |
case-18 | fail→pass | 14,828 | 11,908 | -20% | 1 | 1 | 0% | 2,562 | 5,953 | +132% | 0 | 0 | — |
case-19 | fail→pass | 13,491 | 6,293 | -53% | 1 | 1 | 0% | 2,574 | 5,506 | +114% | 0 | 0 | — |
case-20 | fail→pass | 11,501 | 6,127 | -47% | 1 | 1 | 0% | 2,104 | 5,499 | +161% | 0 | 0 | — |
case-21 | pass→pass | 15,834 | 12,423 | -22% | 1 | 1 | 0% | 2,825 | 6,332 | +124% | 0 | 0 | — |
case-22 | pass→pass | 6,320 | 4,654 | -26% | 1 | 1 | 0% | 833 | 4,896 | +488% | 0 | 0 | — |
case-23 | pass→pass | 6,793 | 4,228 | -38% | 1 | 1 | 0% | 1,186 | 4,834 | +308% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +43 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/21/2026 | +16% |
Other measured skills in the registry, with their headline benchmark lift.