Skip to content

feat: capture EXIF, XML/XMP, and JUMBF metadata#673

Open
zone117x wants to merge 10 commits into
libjxl:mainfrom
zone117x:feat/extract-metadata
Open

feat: capture EXIF, XML/XMP, and JUMBF metadata#673
zone117x wants to merge 10 commits into
libjxl:mainfrom
zone117x:feat/extract-metadata

Conversation

@zone117x

@zone117x zone117x commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

Closes #674

Add metadata extraction support for EXIF, XMP, and JUMBF boxes

  • Add opt-in metadata box capture during JXL container parsing for EXIF, XML/XMP, and JUMBF boxes
  • Support both raw and Brotli-compressed (brob) metadata boxes
  • Include configurable size limits per metadata type to prevent memory exhaustion

Example usage:

let file = std::fs::read(std::env::args().nth(1).unwrap())?;
let mut input = file.as_slice();

let mut options = JxlDecoderOptions::default();
options.metadata_capture = JxlMetadataCaptureOptions::capture_all();

let ProcessingResult::Complete { result: mut decoder } =
    JxlDecoder::new(options).process(&mut input)? else { unreachable!() };

// Skip all frames to discover trailing metadata boxes
while decoder.has_more_frames() {
    let ProcessingResult::Complete { result: frame } = decoder.process(&mut input)? else { unreachable!() };
    let ProcessingResult::Complete { result: d } = frame.skip_frame(&mut input)? else { unreachable!() };
    decoder = d;
}

// Print first box of each metadata type as hex
for (label, boxes) in [
    ("EXIF", decoder.exif_boxes()),
    ("XMP", decoder.xmp_boxes()),
    ("JUMBF", decoder.jumbf_boxes()),
] {
    if let Some(first) = boxes.and_then(|b| b.first()) {
        let mut decompressed = Vec::new();
        let data: &[u8] = if first.is_brotli_compressed {
            brotli::Decompressor::new(first.data.as_slice(), 4096).read_to_end(&mut decompressed)?;
            &decompressed
        } else {
            &first.data
        };
        let hex: String = data.iter().map(|b| format!("{b:02x}")).collect();
        println!("{label} ({} bytes): {hex}", data.len());
    }
}

Changes

Default limits:

  • EXIF: 1MB (typical: 10-64KB)
  • XML/XMP: 1MB (typical: 1-100KB)
  • JUMBF: 16MB (can be larger due to C2PA embedded images)

Design notes

  • Metadata (EXIF, XMP, and JUMBF) capture is opt-in, no breaking changes or performance regressions with existing usage of the library, no memory overhead for callers who don't need metadata
  • Brotli decompression is deferred to the caller (marked via is_brotli_compressed flag)
  • Size limits are per-type aggregates

Testing

  • Added test images covering single/multiple boxes, all metadata types, and brotli-compressed variants
  • Tests verify both box capture and content correctness
  • All test .jxl files are based off resources/test/basic.jxl but with metadata added via various tools

CLI

Added metadata extraction (reading) and Brotli compression (writing) capabilities to the JXL CLI tool. This is mostly useful for testing, as most image metadata tools (like exiftool) do not support creating Brotli-compressed metadata.

Example usage:

$ ./jxl_cli '/test/peach.jxl' --compress-metadata '/test/compressed-metadata.jxl'
Compressed metadata boxes:
  Exif: 1160 bytes -> 525 bytes (brotli)
  xml : 7684 bytes -> 1358 bytes (brotli)
Output written to: /test/compressed-metadata.jxl
$ ./jxl_cli '/test/compressed-metadata.jxl' --info --metadata-out '/test/metadata-test'
Image size: 8160x6144
Bit depth: Int { bits_per_sample: 10 }
Orientation: Identity
Preview: none
Extra channels: 0
EXIF: 1 box(es): 525 bytes (brotli)
XMP: 1 box(es): 1358 bytes (brotli)
JUMBF: none
$ exiftool '/test/metadata-test/metadata_exif.exif'
$ exiftool '/test/metadata-test/metadata_exif.exif'
ExifTool Version Number         : 13.44
File Name                       : metadata_exif.exif
Directory                       : /tmp/metadata-test
File Size                       : 1156 bytes
File Modification Date/Time     : 2026:02:07 18:17:12-07:00
File Access Date/Time           : 2026:02:07 18:17:12-07:00
File Inode Change Date/Time     : 2026:02:07 18:17:12-07:00
File Permissions                : -rw-r--r--
File Type                       : EXIF
File Type Extension             : exif
MIME Type                       : application/unknown
Exif Byte Order                 : Big-endian (Motorola, MM)
Make                            : Google
Camera Model Name               : Pixel 9 Pro XL
Orientation                     : Horizontal (normal)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : HDR+ 1.0.854411184nd
Modify Date                     : 2026:02:03 11:54:28
Y Cb Cr Positioning             : Centered
Exposure Time                   : 1/15
F Number                        : 1.7
Exposure Program                : Program AE
ISO                             : 60
Exif Version                    : 0232
Date/Time Original              : 2026:02:03 11:51:22
Create Date                     : 2026:02:03 11:51:22
Offset Time                     : -07:00
Offset Time Original            : -07:00
Offset Time Digitized           : -07:00
Components Configuration        : Y, Cb, Cr, -
Shutter Speed Value             : 1/15
Aperture Value                  : 1.7
Brightness Value                : 1.14
Exposure Compensation           : 0
Max Aperture Value              : 1.7
Subject Distance                : 0.326 m
Metering Mode                   : Other
Flash                           : Off, Did not fire
Focal Length                    : 6.9 mm
Sub Sec Time                    : 057
Sub Sec Time Original           : 057
Sub Sec Time Digitized          : 057
Flashpix Version                : 0100
Color Space                     : Uncalibrated
Exif Image Width                : 8160
Exif Image Height               : 6144
Sensing Method                  : One-chip color area
Scene Type                      : Directly photographed
Custom Rendered                 : Custom
Exposure Mode                   : Auto
White Balance                   : Auto
Digital Zoom Ratio              : 0
Focal Length In 35mm Format     : 24 mm
Scene Capture Type              : Standard
Contrast                        : Normal
Saturation                      : Normal
Sharpness                       : Normal
Subject Distance Range          : Macro
Lens Make                       : Google
Lens Model                      : Pixel 9 Pro XL back camera 6.9mm f/1.68
GPS Version ID                  : 2.2.0.0
GPS Latitude Ref                : North
GPS Longitude Ref               : East
GPS Altitude Ref                : Above Sea Level
GPS Time Stamp                  : 18:49:56
GPS Img Direction Ref           : Magnetic North
GPS Img Direction               : 65
GPS Date Stamp                  : 2026:02:03
Aperture                        : 1.7
Scale Factor To 35 mm Equivalent: 3.5
Shutter Speed                   : 1/15
Create Date                     : 2026:02:03 11:51:22.057-07:00
Date/Time Original              : 2026:02:03 11:51:22.057-07:00
Modify Date                     : 2026:02:03 11:54:28.057-07:00
GPS Altitude                    : 8 m Above Sea Level
GPS Date/Time                   : 2026:02:03 18:49:56Z
GPS Latitude                    : 52 deg 23' 40.55" N
GPS Longitude                   : 4 deg 52' 39.79" E
Circle Of Confusion             : 0.009 mm
Depth Of Field                  : 0.06 m (0.30 - 0.36 m)
Field Of View                   : 73.7 deg
Focal Length                    : 6.9 mm (35 mm equivalent: 24.0 mm)
GPS Position                    : 52 deg 23' 40.55" N, 4 deg 52' 39.79" E
Hyperfocal Distance             : 3.24 m
Light Value                     : 6.2
Lens ID                         : Pixel 9 Pro XL back camera 6.9mm f/1.68

Note: GPS info was scrambled

$ exiftool '/test/metadata-test/metadata_xmp.xmp'
ExifTool Version Number         : 13.44
File Name                       : metadata_xmp.xmp
Directory                       : /tmp/metadata-test
File Size                       : 7.7 kB
File Modification Date/Time     : 2026:02:07 18:17:12-07:00
File Access Date/Time           : 2026:02:07 18:17:12-07:00
File Inode Change Date/Time     : 2026:02:07 18:17:12-07:00
File Permissions                : -rw-r--r--
File Type                       : XMP
File Type Extension             : xmp
MIME Type                       : application/rdf+xml
XMP Toolkit                     : Image::ExifTool 13.44
Directory Item Length           : 0, 37083
Directory Item Mime             : image/jpeg, image/jpeg
Directory Item Padding          : 0, 0
Directory Item Semantic         : Primary, GainMap
Version                         : 1.0
Subject                         : affordable millennial child, pup
Aperture Value                  : 1.7
Brightness Value                : 1.14
Color Space                     : Uncalibrated
Components Configuration        : Y, Cb, Cr, -
Contrast                        : Normal
Custom Rendered                 : Custom
Digital Zoom Ratio              : 0
Exif Version                    : 0232
Exposure Compensation           : 0
Exposure Mode                   : Auto
Exposure Program                : Program AE
Exposure Time                   : 1/15
F Number                        : 1.7
Flash Fired                     : False
Flash Function                  : False
Flash Mode                      : Off
Flash Red Eye Mode              : False
Flash Return                    : No return detection
Flashpix Version                : 0100
Focal Length                    : 6.9 mm
GPS Altitude Ref                : Above Sea Level
GPS Img Direction               : 65
GPS Img Direction Ref           : Magnetic North
GPS Latitude                    : 52 deg 23' 40.55" N
GPS Longitude                   : 4 deg 52' 39.79" E
GPS Date/Time                   : 2026:02:03 18:49:56+00:00
GPS Version ID                  : 2.2.0.0
ISO                             : 60
Max Aperture Value              : 1.7
Metering Mode                   : Other
Exif Image Width                : 8160
Exif Image Height               : 6144
Saturation                      : Normal
Scene Capture Type              : Standard
Scene Type                      : Directly photographed
Sensing Method                  : One-chip color area
Sharpness                       : Normal
Shutter Speed Value             : 1/15
Subject Distance                : 0.326 m
White Balance                   : Auto
Lens Make                       : Google
Lens Model                      : Pixel 9 Pro XL back camera 6.9mm f/1.68
Photographic Sensitivity        : 60
Date Created                    : 2026:02:03 11:51:22.057
Bits Per Sample                 : 8
Image Height                    : 6144
Image Width                     : 8160
Make                            : Google
Camera Model Name               : Pixel 9 Pro XL
Orientation                     : Horizontal (normal)
Resolution Unit                 : inches
X Resolution                    : 72
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Y Resolution                    : 72
Create Date                     : 2026:02:03 11:51:22.057
Creator Tool                    : HDR+ 1.0.854411184nd
Modify Date                     : 2026:02:03 11:54:28.057
Aperture                        : 1.7
Image Size                      : 8160x6144
Megapixels                      : 50.1
Shutter Speed                   : 1/15
GPS Altitude                    : 8 m Above Sea Level
Flash                           : Off, Did not fire
GPS Latitude Ref                : North
GPS Longitude Ref               : East
Focal Length                    : 6.9 mm
GPS Position                    : 52 deg 23' 40.55" N, 4 deg 52' 39.79" E
Light Value                     : 6.2
Lens ID                         : Pixel 9 Pro XL back camera 6.9mm f/1.68

Note: GPS info was scrambled

@github-actions

github-actions Bot commented Feb 3, 2026

Copy link
Copy Markdown

Benchmark @ f8eea69

MULTI-FILE BENCHMARK RESULTS (4 files)
  CPU architecture: x86_64
  WARNING: System appears noisy: high system load (2.34). Results may be unreliable.
Statistics:
  Confidence:               99.0%
  Max relative error:        3.0%

Comparing: 988ae635 (Base) vs 12c37742 (PR)

File Base (MP/s) PR (MP/s) Δ%
bike.jxl 25.087 25.177 +0.36% ±2.9%
green_queen_modular_e3.jxl 7.907 7.843 -0.81% ±0.4%
green_queen_vardct_e3.jxl 23.058 22.974 -0.36% ±1.2%
sunset_logo.jxl 2.276 2.259 -0.73% ±0.6%

@zone117x zone117x changed the title Feat/extract metadata feat: capture EXIF, XML/XMP, and JUMBF metadata Feb 3, 2026
@zone117x zone117x force-pushed the feat/extract-metadata branch from fc11ec0 to f8eea69 Compare February 11, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support EXIF and XML/XMP extraction

1 participant