Files
Dylan M. Taylor dca2a2cd8e craft-metadata: EXIF, IPTC-IIM, XMP and GPX from LightCraft (#949)
lightcraft-meta at 5b61ce1e, as one crate with a module per format: exif
(read, write, maker-note lens, focus point, tag listing), iptc, xmp (parse,
write, merge), gpx, and the JPEG, PNG, WebP, TIFF, RAF and CR3 containers.
They share Metadata, DateTime, Gps and Orientation.

Changed in the move:
- TIFF comes from craft-tiff; Point, Rect and Orientation are the crate's own.
- The app namespace is a parameter: parse_xmp_with, write_xmp_with and
  MergeRules::namespaces take it, and XmpData no longer has lc_settings.
- The HEIF, PSD and MRW readers stay out, since craft-heif, craft-psd and
  craft-raw read those containers; from_embedded takes the blocks they find.
2026-11-11 19:22:42 -05:00
..

craft-metadata

Photo metadata: EXIF, IPTC-IIM, XMP packets and GPX track logs, the blocks that carry them in image files, and one Metadata model they all read into. From LightCraft (lightcraft-meta). TIFF structure comes from craft-tiff.

One module per format; they share Metadata, DateTime, Gps and Orientation, which is why they are one crate.

Function Purpose
read_exif(&[u8]) -> Metadata A TIFF-structured Exif block: a whole TIFF/DNG/raw file, or a JPEG APP1 payload with or without Exif\0\0. try_read_exif reports header errors, try_read_exif_with takes craft_tiff::ParseOptions.
from_tiff(&Tiff) -> Metadata The same from an already parsed stream.
write_exif(&Metadata) -> Vec<u8> An Exif block for an exported file.
tags::tag_rows, tags::file_tag_rows Every tag with its name and value as text, maker notes included.
focus::read, focus::read_header Where the camera focused, from the maker note (Sony), on the upright photo. read_header reads only the first kilobytes of a file through a callback.
parse_iptc(&[u8]) -> Metadata IIM record 2: title (2:5), keywords (2:25), by-line (2:80), copyright (2:116), caption (2:120), date and time created (2:55, 2:60).
parse_xmp(&str) -> Result<XmpData> Metadata, every property (prefix:name, structs flattened as a:b/c:d) and the structured values. parse_xmp_with also canonicalises the caller's own namespaces.
write_xmp(&Metadata) -> String A complete <?xpacket?> packet. write_xmp_with sets the toolkit name and adds simple properties in the caller's namespace.
merge_xmp(existing, fresh, MergeRules) Updates a packet another program wrote: owned properties are replaced, everything else stays byte for byte.
parse_gpx(&str) -> Result<Tracklog> GPX track points; Tracklog::locate gives the position at a capture time.
embedded(&[u8]) -> Embedded Sniffs JPEG, PNG, WebP, TIFF-based files, Fujifilm RAF and Canon CR3 and returns the Exif, XMP (and extended XMP), ICC and IPTC blocks. jpeg_segments, png_chunks, webp_chunks are the walkers.
cr3::parse_cr3 The Canon CR3 container: CMT1…CMT4, the XMP uuid box, the thumbnail, the tracks.
extract(&[u8]) -> Metadata All of it for a whole file. Precedence: EXIF, then IPTC fills gaps, then XMP overrides the user fields (rating, label, title, caption, artist, copyright, keywords, GPS) and fills the rest. from_embedded does the same for blocks already located.

HEIF, PSD and MRW files are read by craft-heif, craft-psd and craft-raw. Put the blocks they return into an Embedded and call from_embedded.

Capture time

EXIF candidates are tried in order: DateTimeOriginal, DateTimeDigitized, then IFD0 DateTime. A clock with more than three colon-separated components is rejected as a whole, so the next candidate or the XMP supplies the time. ISO 8601 times in XMP need a complete clock. Calendar checks go through chrono. Invalid GPX point times count as untimed.

XMP namespaces

Written: dc, xmp, xmpRights, photoshop, exif, exifEX, tiff, aux, lr, Iptc4xmpCore, and the one namespace the caller passes. Read as well: crs, mwg-rs regions (stArea, stDim). Reading canonicalises prefixes by namespace URI and handles attribute and element forms, rdf:Seq/Bag/Alt (x-default first), rdf:parseType="Resource" and nested-description structs, predefined and numeric entities.

A foreign namespace that uses a recognised prefix cannot supply recognised properties. Its keys are Q{hex-encoded namespace URI}local. Merging keeps the original foreign XML.

Limits

XMP: 64 levels, 200 000 nodes, 1 MiB of flattened keys and 8 MiB of opaque names per packet (XmpError::TooComplex). GPX: 64 levels, 5 000 000 points. CR3: 4096 boxes. JPEG ICC is returned only when its APP2 chunks form a complete, consistent, uniquely numbered sequence. TIFF limits are craft-tiff's. No input panics: proptests feed random bytes to every container walker and random text to the XMP parser, and a sample JPEG is cut at every byte.

Sources

CIPA DC-008 (Exif 2.32), TIFF 6.0, Adobe DNG 1.7 (tag numbers), ISO 16684-1 and the Adobe XMP Specification Parts 1 to 3, IPTC-IIM 4.2 and the IPTC Photo Metadata Standard, the MWG Region Guidelines, ITU-T T.81 (JPEG markers), ICC.1 (APP2 embedding), the PNG Specification 3rd ed. (eXIf, iTXt), the WebP RIFF container specification, ISO/IEC 14496-12 (box structure), GPX 1.1. XML parsing: quick-xml (MIT). No third-party metadata code was consulted.