Files
06aee09c5c craft-metadata: MPF index reader and writer for gain map JPEGs; XmpData::toolkit (#1007)
* craft-metadata: EXIF, IPTC-IIM, XMP and GPX from LightCraft

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.

* craft-metadata: a RW2 reads ISO and the rest of its Exif from the JpgFromRaw

A RW2's own Exif IFD is a short subset. What it lacks is filled from the Exif
of the embedded JpgFromRaw, then from the ISO tags of the Panasonic IFD0
(0x0017, 0x0037), each only in its recorded field type. 65535 in a SHORT
ISOSpeedRatings is the overflow marker and states no ISO. The private tags
are read only in a RW2 stream, under the caller's parse options.

Ported from storytold/lightcraft#3518.

* craft-metadata: the Exif rating is written, and 65535 reads as rejected

write_exif writes Rating (0x4746), with 0xFFFF for a rejected photo, and
from_tiff reads 65535 as -1 instead of clamping it to 5.

Ported from storytold/lightcraft#1995.

* craft-metadata: one shutter-speed formatter, exact to four digits

format_exposure_time shows a duration within 0.1% of 1/n second as 1/n and
anything else to four significant digits, so 0.3 s, 1/2.8 s and 0.625 s are
no longer rounded to a reciprocal or one decimal. Metadata::exposure_display
and the ExposureTime tag row both use it.

Ported from storytold/lightcraft#1692.

* craft-metadata: JPEG XL containers give their Exif and xml boxes

The first Exif and xml box of a JPEG XL container, plain or Brotli-wrapped
in brob, honouring the Exif box's TIFF header offset. A brob payload is
capped at 32 MiB. A bare codestream has no metadata.

Ported from storytold/lightcraft#3756.

* craft-metadata: XMP Resource structs flatten their attribute fields

A property with rdf:parseType="Resource" may give its fields as attributes
as well as child elements. Only the children were flattened into
XmpData::properties; the attributes now are too, as for a nested
rdf:Description.

Ported from storytold/lightcraft#1226 (the XMP part).

* craft-metadata: a capture date of just a year or a month stays as stated

XMP dates may stop at the year (1983) or the month (1983-04). DateTime now
has a DatePrecision; such a date writes back to XMP as stated and only in
photoshop:DateCreated, and write_exif leaves it out, since an Exif date
cannot say it.

Ported from storytold/lightcraft#3502.

* craft-metadata: Fujifilm in-camera recipes from the maker note

fuji module: the film simulation and the rest of the in-camera recipe (grain,
colour chrome, tone, dynamic range, white balance and its shift, sharpness,
noise reduction, clarity) from a Fujifilm maker note. recipe_from_exif,
recipe_from_bytes (TIFF, JPEG, RAF) and read_recipe_file, which reads only
as far as the metadata. from_embedded fills Metadata::fuji_recipe and
file_tag_rows lists the recipe under Fujifilm.

Ported from storytold/lightcraft#1209.

* craft-metadata: the linear-time scan test compares times, not a fixed bound

The test asserted that 200 000 attributes scan in under 5 s, which failed on
a loaded machine. It now scans 25 000 and 100 000 attributes, takes the best
of three runs of each, and asserts that four times the attributes take less
than ten times as long. Linear scans give about 4; a quadratic duplicate
check gave 18.6 and fails.

* craft-metadata: interleave the linear-time scan test's runs

The best-of-three ratio still failed once under load. The test now alternates
five runs of 50 000 and 200 000 attributes and compares the fastest of each,
so a burst of load slows both sizes. Six runs at load average 55 gave ratios
of 3.9 to 5.4 against the bound of 10.

* craft-metadata: XmpData::toolkit, the x:xmptk of the packet

The name of the program that wrote a packet, from x:xmptk on x:xmpmeta (or
x:xaptk on the older x:xapmeta), whatever prefix the packet binds to
adobe:ns:meta/. write_xmp_with writes XmpWrite::toolkit there, and the test
reads it back, escaped characters included.

* craft-metadata: mpf, the Multi-Picture index of a JPEG

mpf::images lists the images an APP2 MPF index (CIPA DC-007) puts after the
primary image, with their file offsets; the index is read with craft-tiff,
in either byte order, up to 16 entries. mpf::write inserts an index after
the primary's leading APPn segments and appends the other images, with the
primary listed as the representative baseline image: the layout LightCraft's
gain map JPEG writer uses, for any number of images. mpf::markers lists the
marker segments before the first scan, for APPn payloads such as ISO 21496-1
gain map metadata.

---------

Co-authored-by: radialmonster <radial@gmail.com>
Co-authored-by: Sebastian Berschneider <berschi@gmail.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Michael Anderson <1427851+endthestart@users.noreply.github.com>
Co-authored-by: Ilya Dorman <ilya@codeforhumans.dev>
Co-authored-by: Clyde C Brown <clyde.c.brown@pm.me>
Co-authored-by: MohammadTeimori <teimori.mohammad@gmail.com>
2026-11-11 20:28:38 -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.
recipe_from_exif, recipe_from_bytes, read_recipe_file The Fujifilm in-camera recipe (film simulation, grain, colour chrome, tone, white balance shift and the rest) from the maker note of an Exif block, a whole TIFF, JPEG or RAF, or a file read only as far as its metadata.
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, Canon CR3 and JPEG XL containers 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.
jxl::metadata The Exif and xml boxes of a JPEG XL container, plain or Brotli-wrapped in brob.
mpf::images, mpf::write The Multi-Picture Format index (CIPA DC-007) of a JPEG: the images stored after the primary one (a gain map, a preview), and writing an index plus appending the images. mpf::markers lists the marker segments before the first scan.
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. MPF: 16 index entries. CR3 and JPEG XL: 4096 boxes; a brob box inflates to at most 32 MiB. 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.