Skip to content

Commit e7785c7

Browse files
NathanMOlsonianare
authored andcommitted
Add support for JPEG XL (#242)
1 parent 148b30c commit e7785c7

File tree

6 files changed

+67
-2
lines changed

6 files changed

+67
-2
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ endif
2020
EXIF_PY := $(if $(shell which EXIF.py),EXIF.py,./EXIF.py)
2121

2222
# Find images, support multiple case insensitive extensions and file names with spaces
23-
FIND_IMAGES := find tests/resources -regextype posix-egrep -iregex ".*\.(bmp|gif|heic|heif|jpg|jpeg|png|tiff|webp|arw|avif)" -print0 | LC_COLLATE=C sort -fz | xargs -0
23+
FIND_IMAGES := find tests/resources -regextype posix-egrep -iregex ".*\.(bmp|gif|heic|heif|jpg|jpeg|png|tiff|webp|arw|avif|jxl)" -print0 | LC_COLLATE=C sort -fz | xargs -0
2424

2525

2626
.PHONY: help

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Easy to use Python module to extract Exif metadata from digital image files.
2424

2525
Pure Python, lightweight, no dependencies.
2626

27-
Supported formats: TIFF, JPEG, PNG, Webp, HEIC
27+
Supported formats: TIFF, JPEG, PNG, Webp, HEIC, JPEG XL
2828

2929

3030
Compatibility

exifread/core/find_exif.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from exifread.core.exceptions import ExifNotFound, InvalidExif
77
from exifread.core.heic import HEICExifFinder, find_heic_tiff
88
from exifread.core.jpeg import find_jpeg_exif
9+
from exifread.core.jxl import JXLExifFinder
910
from exifread.core.utils import ord_
1011
from exifread.exif_log import get_logger
1112

@@ -76,6 +77,18 @@ def find_png_exif(fh: BinaryIO, data: bytes) -> Tuple[int, bytes]:
7677
raise ExifNotFound("PNG file does not have exif data.")
7778

7879

80+
def find_jxl_exif(fh: BinaryIO) -> Tuple[int, bytes]:
81+
logger.debug("JPEG XL format recognized in data[0:12]")
82+
83+
fh.seek(0)
84+
jxl = JXLExifFinder(fh)
85+
offset, endian = jxl.find_exif()
86+
if offset > 0:
87+
return offset, endian
88+
89+
raise ExifNotFound("JPEG XL file does not have exif data.")
90+
91+
7992
def determine_type(fh: BinaryIO) -> Tuple[int, bytes, int]:
8093
# by default do not fake an EXIF beginning
8194
fake_exif = 0
@@ -98,6 +111,8 @@ def determine_type(fh: BinaryIO) -> Tuple[int, bytes, int]:
98111
offset, endian, fake_exif = find_jpeg_exif(fh, data, fake_exif)
99112
elif data[0:8] == b"\x89PNG\r\n\x1a\n":
100113
offset, endian = find_png_exif(fh, data)
114+
elif data == b"\0\0\0\x0cJXL\x20\x0d\x0a\x87\x0a":
115+
offset, endian = find_jxl_exif(fh)
101116
else:
102117
raise ExifNotFound("File format not recognized.")
103118
return offset, endian, fake_exif

exifread/core/jxl.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""
2+
Find Exif data in a JPEG XL file
3+
"""
4+
5+
from typing import Tuple
6+
7+
from exifread.core.heic import HEICExifFinder
8+
9+
10+
class JXLExifFinder(HEICExifFinder):
11+
"""Find JPEG XL EXIF tags."""
12+
13+
def find_exif(self) -> Tuple[int, bytes]:
14+
ftyp = self.expect_parse("ftyp")
15+
assert ftyp.major_brand == b"jxl "
16+
assert ftyp.minor_version == 0
17+
exif = self.expect_parse("Exif")
18+
19+
offset = exif.pos + 4
20+
self.file_handle.seek(offset - 8)
21+
assert self.get(8)[:6] == b"Exif\x00\x00"
22+
endian = self.file_handle.read(1)
23+
return offset, endian

tests/resources/dump.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5092,6 +5092,33 @@ Thumbnail YResolution (Ratio): 72
50925092
Opening: tests/resources/jpg/xmp/no_exif.jpg
50935093
No EXIF information found
50945094

5095+
Opening: tests/resources/jxl/test_0001.jxl
5096+
EXIF DateTimeOriginal (ASCII): 2025:08:15 22:25:54
5097+
EXIF FocalLength (Ratio): 14
5098+
EXIF FocalPlaneResolutionUnit (Short): 3
5099+
EXIF FocalPlaneXResolution (Ratio): 3/2500
5100+
EXIF FocalPlaneYResolution (Ratio): 3/2500
5101+
EXIF Make (ASCII): Lab 308
5102+
EXIF MakerNote (ASCII): {}
5103+
EXIF Model (ASCII): Smart Pilot Cam
5104+
EXIF TimeZoneOffset (Signed Short): 0
5105+
GPS GPSAltitude (Ratio): 2019847/1000
5106+
GPS GPSAltitudeRef (Byte): 0
5107+
GPS GPSImgDirection (Ratio): 124223909/1000000
5108+
GPS GPSImgDirectionRef (ASCII): T
5109+
GPS GPSLatitude (Ratio): [525660803/11930465, 0, 0]
5110+
GPS GPSLatitudeRef (ASCII): N
5111+
GPS GPSLongitude (Ratio): [1447334054/11930465, 0, 0]
5112+
GPS GPSLongitudeRef (ASCII): W
5113+
GPS GPSSpeed (Ratio): 50399963/200000
5114+
GPS GPSSpeedRef (ASCII): K
5115+
GPS GPSTrack (Ratio): 34223909/1000000
5116+
GPS GPSTrackRef (ASCII): T
5117+
GPS Tag 0xD000 (Signed Ratio): 40
5118+
GPS Tag 0xD001 (Signed Ratio): 0
5119+
Image ExifOffset (Long): 38
5120+
Image GPSInfo (Long): 220
5121+
50955122
Opening: tests/resources/raw/sony_alpha_a7iii_raw_image.ARW
50965123
EXIF BrightnessValue (Signed Ratio): -3483/1280
50975124
EXIF ColorSpace (Short): sRGB

tests/resources/jxl/test_0001.jxl

85.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)