Skip to main content

Read, write, and synchronise EXIF/IPTC/XMP image metadata in JPEG, PNG, and TIFF

Project description

svk-img-metadata

PyPI Tests Changelog License

Read, write, and synchronise descriptive image metadata — EXIF, IPTC-IIM, and XMP — across JPEG, PNG, and TIFF, in mostly-pure Python (Pillow, piexif, defusedxml; no native exiv2/libheif, no ExifTool).

It focuses on the descriptive / rights / location fields you actually curate (caption, creator, copyright, keywords, location, dates, rating…), not camera-technical tags. A single unified model maps each logical field to its correct home in all three standards, so you can read from wherever the value lives and write it back everywhere it belongs.

Installation

pip install svk-img-metadata

Requires Python 3.10+.

Usage

Read

import svk_img_metadata as svk

meta = svk.read("photo.jpg")

print(meta.description)        # a caption (as a LangAlt; str() gives x-default)
print(meta.creator)            # ["Jane Doe"]
print(meta.keywords)           # ["sky", "sea"]
print(meta.city, meta.country)
print(meta.gps)                # GPSCoord(latitude=59.33, longitude=18.06, altitude=None)
print(meta.present_fields())   # which canonical fields are set

Values are merged across the three standards with XMP > IPTC > EXIF precedence. The raw per-standard views are available too (meta.exif, meta.iptc, meta.xmp).

Write

from svk_img_metadata import read
from svk_img_metadata.model import LangAlt, MetaDate, GPSCoord
from datetime import datetime, timezone

meta = read("photo.jpg")
meta.description = LangAlt("Sunset over the archipelago")
meta.creator = ["Jane Doe"]
meta.keywords = ["sunset", "sea"]
meta.copyright = "© 2026 Jane Doe"
meta.date_created = MetaDate(datetime(2026, 7, 12, 20, 30, tzinfo=timezone.utc))
meta.gps = GPSCoord(59.33, 18.06)

meta.save("photo-tagged.jpg")   # or meta.save() to overwrite in place

Canonical fields fan out to every standard that carries them. Pixel data and any metadata the library does not model (unknown EXIF tags, custom XMP namespaces, other Photoshop resources) are preserved. Restrict the carriers with meta.save(path, standards=("xmp",)).

Canonical fields: title, headline, description, creator, creator_title, copyright, rights_usage, credit, source, keywords, date_created, instructions, job_id, sublocation, city, state, country, country_code, gps, rating, label.

Synchronise between standards

Copy one standard's values into the fields the others carry — e.g. populate IPTC/XMP from a camera's EXIF:

meta = read("from-camera.jpg")
meta.sync("exif", targets=["iptc", "xmp"])   # fill empty IPTC/XMP fields from EXIF
meta.sync("exif", overwrite=True)            # or force EXIF to win over the merge precedence
meta.save()

See where the standards currently disagree:

meta.diff_standards()
# {"description": {"exif": LangAlt('old caption'), "xmp": LangAlt('new caption')}}

Export XMP (string or sidecar)

xml = meta.to_xmp()                              # standalone XMP packet as a string
meta.write_xmp_sidecar()                         # -> photo.xmp   (basename convention)
meta.write_xmp_sidecar(naming="fullname")        # -> photo.jpg.xmp

sidecar = svk.read_sidecar("photo.xmp")          # read a .xmp back into the model

Custom XMP namespaces

svk.register_namespace("https://example.com/ns/1.0/", "acme")
meta.xmp.set("acme:ReviewStatus", "approved")
meta.xmp.get("acme:ReviewStatus")                # "approved"
meta.save()                                      # custom property is round-tripped

Validate required metadata

schema = svk.RequiredFields(["description", "creator", "copyright"])
result = meta.validate(schema)
if not result:
    print("missing:", result.missing)           # e.g. ["copyright"]

# require a field in a specific standard, or raise on failure:
svk.RequiredFields([("copyright", "xmp")])
meta.validate(schema, strict=True)               # raises ValidationError if incomplete

Strip metadata (privacy)

meta.strip(fields=["gps"])   # remove location only
meta.strip()                 # remove all modelled fields
meta.save("clean.jpg")

Format support

Format Read Write
JPEG ✅ embedded
PNG ✅ embedded (EXIF + XMP; PNG has no standard IPTC slot)
TIFF ✅ via XMP sidecar (write_xmp_sidecar); embedding not supported
HEIC

For TIFF, save() raises rather than embedding — use meta.write_xmp_sidecar() to write a .xmp alongside the file (the standard sidecar workflow).

Malformed or hostile input always raises a clean svk_img_metadata.MetadataError (never an unhandled crash); XMP is parsed with defusedxml to block XXE and entity-expansion attacks.

Development

Uses uv:

uv sync --group dev      # install project + dev deps
uv run pytest            # run the test suite
uv run ruff check .      # lint

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

svk_img_metadata-0.1.0.tar.gz (39.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

svk_img_metadata-0.1.0-py3-none-any.whl (35.9 kB view details)

Uploaded Python 3

File details

Details for the file svk_img_metadata-0.1.0.tar.gz.

File metadata

  • Download URL: svk_img_metadata-0.1.0.tar.gz
  • Upload date:
  • Size: 39.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for svk_img_metadata-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8f830fb0eb57017ec57dee5ae6045731c3d7c41705927f44ffaee7ca8e07fce6
MD5 d0f7f87021c0d7837b415fa941a2c1dc
BLAKE2b-256 72960d8046812a5cdb7dcf7d24e9ffbb1b433524672926d4870d879dc32978b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for svk_img_metadata-0.1.0.tar.gz:

Publisher: publish.yml on vitlais/svk-img-metadata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file svk_img_metadata-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for svk_img_metadata-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 12d0184f381096489deae19e1cfbb1445f4543dea4c0f9bd7e6df9a1e73a2319
MD5 a548a84ed40a7746bf5bd7eaf660b25f
BLAKE2b-256 d4ad43c889cd15a2e34447f95d711eb73c699318aec1433d69a86e7d687fa905

See more details on using hashes here.

Provenance

The following attestation bundles were made for svk_img_metadata-0.1.0-py3-none-any.whl:

Publisher: publish.yml on vitlais/svk-img-metadata

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page