test_jpg_rot fails with exiftool 13.25 #208

Closed
opened 2025-04-07 01:54:56 +00:00 by dotlambda · 3 comments

After updating exiftool to 13.25, the units property of images seems to be undefined. Maybe this is related to the changelog entry

EXIF X/YResolution, ResolutionUnit and FlashpixVersion are no longer treated as mandatory tags (they were changed to optional in the EXIF 3.0 specification)

___________________ ERROR at setup of test_jpg_rot[internal] ___________________

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ffff6687c20>, _basetemp=PosixPath('/build/pytest-of-nixbld/pytest-0'), _retention_count=3, _retention_policy='all')
tmp_normal_png = PosixPath('/build/pytest-of-nixbld/pytest-0/normal_png0/normal.png')

    @pytest.fixture(scope="session")
    def jpg_rot_img(tmp_path_factory, tmp_normal_png):
        in_img = tmp_path_factory.mktemp("jpg_rot") / "in.jpg"
        subprocess.check_call(CONVERT + [str(tmp_normal_png), str(in_img)])
        subprocess.check_call(
            ["exiftool", "-overwrite_original", "-all=", str(in_img), "-n"]
        )
        subprocess.check_call(
            [
                "exiftool",
                "-overwrite_original",
                "-Orientation=6",
                "-XResolution=96",
                "-YResolution=96",
                "-n",
                str(in_img),
            ]
        )
        identify = json.loads(subprocess.check_output(CONVERT + [str(in_img), "json:"]))
        assert len(identify) == 1
        # somewhere between imagemagick 6.9.7.4 and 6.9.9.34, the json output was
        # put into an array, here we cater for the older version containing just
        # the bare dictionary
        if "image" in identify:
            identify = [identify]
        assert "image" in identify[0]
        assert identify[0]["image"].get("format") == "JPEG", str(identify)
        assert identify[0]["image"].get("mimeType") == "image/jpeg", str(identify)
        assert identify[0]["image"].get("geometry") == {
            "width": 60,
            "height": 60,
            "x": 0,
            "y": 0,
        }, str(identify)
        assert identify[0]["image"].get("resolution") == {"x": 96, "y": 96}
>       assert identify[0]["image"].get("units") == "PixelsPerInch", str(identify)
E       AssertionError: [{'version': '1.0', 'image': {'name': '/build/pytest-of-nixbld/pytest-0/jpg_rot0/in.jpg', 'baseName': 'in.jpg', 'permissions': 644, 'format': 'JPEG', 'formatDescription': 'Joint Photographic Experts Group JFIF format', 'mimeType': 'image/jpeg', 'class': 'DirectClass', 'geometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'resolution': {'x': 96, 'y': 96}, 'printSize': {'x': 0.625, 'y': 0.625}, 'units': 'Undefined', 'type': 'TrueColor', 'baseType': 'Undefined', 'endianness': 'Undefined', 'colorspace': 'sRGB', 'depth': 8, 'baseDepth': 8, 'channelDepth': {'red': 8, 'green': 8, 'blue': 1}, 'pixels': 3600, 'imageStatistics': {'Overall': {'min': 0, 'max': 255, 'mean': 74.3327, 'median': 1.66667, 'standardDeviation': 105.332, 'kurtosis': -1.00142, 'skewness': 0.915224, 'entropy': 0.561842}}, 'channelStatistics': {'red': {'min': 0, 'max': 255, 'mean': 74.3339, 'median': 2, 'standardDeviation': 105.267, 'kurtosis': -0.998881, 'skewness': 0.916638, 'entropy': 0.570394}, 'green': {'min': 0, 'max': 255, 'mean': 74.4989, 'median': 1, 'standardDeviation': 105.663, 'kurtosis': -1.00709, 'skewness': 0.912291, 'entropy': 0.54539}, 'blue': {'min': 0, 'max': 255, 'mean': 74.1653, 'median': 2, 'standardDeviation': 105.065, 'kurtosis': -0.998276, 'skewness': 0.916744, 'entropy': 0.569742}}, 'renderingIntent': 'Perceptual', 'gamma': 0.454545, 'chromaticity': {'redPrimary': {'x': 0.64, 'y': 0.33}, 'greenPrimary': {'x': 0.3, 'y': 0.6}, 'bluePrimary': {'x': 0.15, 'y': 0.06}, 'whitePrimary': {'x': 0.3127, 'y': 0.329}}, 'matteColor': '#BDBDBDBDBDBD', 'backgroundColor': '#FFFFFFFFFFFF', 'borderColor': '#DFDFDFDFDFDF', 'transparentColor': '#000000000000', 'interlace': 'None', 'intensity': 'Undefined', 'compose': 'Over', 'pageGeometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'dispose': 'Undefined', 'iterations': 0, 'compression': 'JPEG', 'quality': 92, 'orientation': 'RightTop', 'properties': {'exif:YCbCrPositioning': '1', 'jpeg:colorspace': '2', 'jpeg:sampling-factor': '1x1,1x1,1x1', 'signature': '4d03d4a95c2404d895d033f2d4d966ac6d8362be568145c774fe4c10b1c9bb82'}, 'profiles': {'exif': {'length': 84}}, 'tainted': False, 'filesize': '2575B', 'numberPixels': '3600', 'pixelsPerSecond': '14.4009MB', 'userTime': '0.000u', 'elapsedTime': '0:01.000', 'version': 'ImageMagick 7.1.1-47 Q16-HDRI x86_64 c8f4e8cb7:20250329 https://imagemagick.org'}}]
E       assert 'Undefined' == 'PixelsPerInch'
E         
E         - PixelsPerInch
E         + Undefined

src/img2pdf_test.py:1135: AssertionError
---------------------------- Captured stdout setup -----------------------------
    1 image files updated
    1 image files updated
---------------------------- Captured stderr setup -----------------------------
WARNING: The convert command is deprecated in IMv7, use "magick" instead of "convert" or "magick convert"

WARNING: The convert command is deprecated in IMv7, use "magick" instead of "convert" or "magick convert"

___________________ ERROR at setup of test_jpg_rot[pikepdf] ____________________

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ffff6687c20>, _basetemp=PosixPath('/build/pytest-of-nixbld/pytest-0'), _retention_count=3, _retention_policy='all')
tmp_normal_png = PosixPath('/build/pytest-of-nixbld/pytest-0/normal_png0/normal.png')

    @pytest.fixture(scope="session")
    def jpg_rot_img(tmp_path_factory, tmp_normal_png):
        in_img = tmp_path_factory.mktemp("jpg_rot") / "in.jpg"
        subprocess.check_call(CONVERT + [str(tmp_normal_png), str(in_img)])
        subprocess.check_call(
            ["exiftool", "-overwrite_original", "-all=", str(in_img), "-n"]
        )
        subprocess.check_call(
            [
                "exiftool",
                "-overwrite_original",
                "-Orientation=6",
                "-XResolution=96",
                "-YResolution=96",
                "-n",
                str(in_img),
            ]
        )
        identify = json.loads(subprocess.check_output(CONVERT + [str(in_img), "json:"]))
        assert len(identify) == 1
        # somewhere between imagemagick 6.9.7.4 and 6.9.9.34, the json output was
        # put into an array, here we cater for the older version containing just
        # the bare dictionary
        if "image" in identify:
            identify = [identify]
        assert "image" in identify[0]
        assert identify[0]["image"].get("format") == "JPEG", str(identify)
        assert identify[0]["image"].get("mimeType") == "image/jpeg", str(identify)
        assert identify[0]["image"].get("geometry") == {
            "width": 60,
            "height": 60,
            "x": 0,
            "y": 0,
        }, str(identify)
        assert identify[0]["image"].get("resolution") == {"x": 96, "y": 96}
>       assert identify[0]["image"].get("units") == "PixelsPerInch", str(identify)
E       AssertionError: [{'version': '1.0', 'image': {'name': '/build/pytest-of-nixbld/pytest-0/jpg_rot0/in.jpg', 'baseName': 'in.jpg', 'permissions': 644, 'format': 'JPEG', 'formatDescription': 'Joint Photographic Experts Group JFIF format', 'mimeType': 'image/jpeg', 'class': 'DirectClass', 'geometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'resolution': {'x': 96, 'y': 96}, 'printSize': {'x': 0.625, 'y': 0.625}, 'units': 'Undefined', 'type': 'TrueColor', 'baseType': 'Undefined', 'endianness': 'Undefined', 'colorspace': 'sRGB', 'depth': 8, 'baseDepth': 8, 'channelDepth': {'red': 8, 'green': 8, 'blue': 1}, 'pixels': 3600, 'imageStatistics': {'Overall': {'min': 0, 'max': 255, 'mean': 74.3327, 'median': 1.66667, 'standardDeviation': 105.332, 'kurtosis': -1.00142, 'skewness': 0.915224, 'entropy': 0.561842}}, 'channelStatistics': {'red': {'min': 0, 'max': 255, 'mean': 74.3339, 'median': 2, 'standardDeviation': 105.267, 'kurtosis': -0.998881, 'skewness': 0.916638, 'entropy': 0.570394}, 'green': {'min': 0, 'max': 255, 'mean': 74.4989, 'median': 1, 'standardDeviation': 105.663, 'kurtosis': -1.00709, 'skewness': 0.912291, 'entropy': 0.54539}, 'blue': {'min': 0, 'max': 255, 'mean': 74.1653, 'median': 2, 'standardDeviation': 105.065, 'kurtosis': -0.998276, 'skewness': 0.916744, 'entropy': 0.569742}}, 'renderingIntent': 'Perceptual', 'gamma': 0.454545, 'chromaticity': {'redPrimary': {'x': 0.64, 'y': 0.33}, 'greenPrimary': {'x': 0.3, 'y': 0.6}, 'bluePrimary': {'x': 0.15, 'y': 0.06}, 'whitePrimary': {'x': 0.3127, 'y': 0.329}}, 'matteColor': '#BDBDBDBDBDBD', 'backgroundColor': '#FFFFFFFFFFFF', 'borderColor': '#DFDFDFDFDFDF', 'transparentColor': '#000000000000', 'interlace': 'None', 'intensity': 'Undefined', 'compose': 'Over', 'pageGeometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'dispose': 'Undefined', 'iterations': 0, 'compression': 'JPEG', 'quality': 92, 'orientation': 'RightTop', 'properties': {'exif:YCbCrPositioning': '1', 'jpeg:colorspace': '2', 'jpeg:sampling-factor': '1x1,1x1,1x1', 'signature': '4d03d4a95c2404d895d033f2d4d966ac6d8362be568145c774fe4c10b1c9bb82'}, 'profiles': {'exif': {'length': 84}}, 'tainted': False, 'filesize': '2575B', 'numberPixels': '3600', 'pixelsPerSecond': '14.4009MB', 'userTime': '0.000u', 'elapsedTime': '0:01.000', 'version': 'ImageMagick 7.1.1-47 Q16-HDRI x86_64 c8f4e8cb7:20250329 https://imagemagick.org'}}]
E       assert 'Undefined' == 'PixelsPerInch'
E         
E         - PixelsPerInch
E         + Undefined

src/img2pdf_test.py:1135: AssertionError
After updating exiftool to 13.25, the units property of images seems to be undefined. Maybe this is related to the [changelog](https://github.com/exiftool/exiftool/blob/13.25/Changes#L32-L33) entry > EXIF X/YResolution, ResolutionUnit and FlashpixVersion are no longer treated as mandatory tags (they were changed to optional in the EXIF 3.0 specification) ``` ___________________ ERROR at setup of test_jpg_rot[internal] ___________________ tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ffff6687c20>, _basetemp=PosixPath('/build/pytest-of-nixbld/pytest-0'), _retention_count=3, _retention_policy='all') tmp_normal_png = PosixPath('/build/pytest-of-nixbld/pytest-0/normal_png0/normal.png') @pytest.fixture(scope="session") def jpg_rot_img(tmp_path_factory, tmp_normal_png): in_img = tmp_path_factory.mktemp("jpg_rot") / "in.jpg" subprocess.check_call(CONVERT + [str(tmp_normal_png), str(in_img)]) subprocess.check_call( ["exiftool", "-overwrite_original", "-all=", str(in_img), "-n"] ) subprocess.check_call( [ "exiftool", "-overwrite_original", "-Orientation=6", "-XResolution=96", "-YResolution=96", "-n", str(in_img), ] ) identify = json.loads(subprocess.check_output(CONVERT + [str(in_img), "json:"])) assert len(identify) == 1 # somewhere between imagemagick 6.9.7.4 and 6.9.9.34, the json output was # put into an array, here we cater for the older version containing just # the bare dictionary if "image" in identify: identify = [identify] assert "image" in identify[0] assert identify[0]["image"].get("format") == "JPEG", str(identify) assert identify[0]["image"].get("mimeType") == "image/jpeg", str(identify) assert identify[0]["image"].get("geometry") == { "width": 60, "height": 60, "x": 0, "y": 0, }, str(identify) assert identify[0]["image"].get("resolution") == {"x": 96, "y": 96} > assert identify[0]["image"].get("units") == "PixelsPerInch", str(identify) E AssertionError: [{'version': '1.0', 'image': {'name': '/build/pytest-of-nixbld/pytest-0/jpg_rot0/in.jpg', 'baseName': 'in.jpg', 'permissions': 644, 'format': 'JPEG', 'formatDescription': 'Joint Photographic Experts Group JFIF format', 'mimeType': 'image/jpeg', 'class': 'DirectClass', 'geometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'resolution': {'x': 96, 'y': 96}, 'printSize': {'x': 0.625, 'y': 0.625}, 'units': 'Undefined', 'type': 'TrueColor', 'baseType': 'Undefined', 'endianness': 'Undefined', 'colorspace': 'sRGB', 'depth': 8, 'baseDepth': 8, 'channelDepth': {'red': 8, 'green': 8, 'blue': 1}, 'pixels': 3600, 'imageStatistics': {'Overall': {'min': 0, 'max': 255, 'mean': 74.3327, 'median': 1.66667, 'standardDeviation': 105.332, 'kurtosis': -1.00142, 'skewness': 0.915224, 'entropy': 0.561842}}, 'channelStatistics': {'red': {'min': 0, 'max': 255, 'mean': 74.3339, 'median': 2, 'standardDeviation': 105.267, 'kurtosis': -0.998881, 'skewness': 0.916638, 'entropy': 0.570394}, 'green': {'min': 0, 'max': 255, 'mean': 74.4989, 'median': 1, 'standardDeviation': 105.663, 'kurtosis': -1.00709, 'skewness': 0.912291, 'entropy': 0.54539}, 'blue': {'min': 0, 'max': 255, 'mean': 74.1653, 'median': 2, 'standardDeviation': 105.065, 'kurtosis': -0.998276, 'skewness': 0.916744, 'entropy': 0.569742}}, 'renderingIntent': 'Perceptual', 'gamma': 0.454545, 'chromaticity': {'redPrimary': {'x': 0.64, 'y': 0.33}, 'greenPrimary': {'x': 0.3, 'y': 0.6}, 'bluePrimary': {'x': 0.15, 'y': 0.06}, 'whitePrimary': {'x': 0.3127, 'y': 0.329}}, 'matteColor': '#BDBDBDBDBDBD', 'backgroundColor': '#FFFFFFFFFFFF', 'borderColor': '#DFDFDFDFDFDF', 'transparentColor': '#000000000000', 'interlace': 'None', 'intensity': 'Undefined', 'compose': 'Over', 'pageGeometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'dispose': 'Undefined', 'iterations': 0, 'compression': 'JPEG', 'quality': 92, 'orientation': 'RightTop', 'properties': {'exif:YCbCrPositioning': '1', 'jpeg:colorspace': '2', 'jpeg:sampling-factor': '1x1,1x1,1x1', 'signature': '4d03d4a95c2404d895d033f2d4d966ac6d8362be568145c774fe4c10b1c9bb82'}, 'profiles': {'exif': {'length': 84}}, 'tainted': False, 'filesize': '2575B', 'numberPixels': '3600', 'pixelsPerSecond': '14.4009MB', 'userTime': '0.000u', 'elapsedTime': '0:01.000', 'version': 'ImageMagick 7.1.1-47 Q16-HDRI x86_64 c8f4e8cb7:20250329 https://imagemagick.org'}}] E assert 'Undefined' == 'PixelsPerInch' E E - PixelsPerInch E + Undefined src/img2pdf_test.py:1135: AssertionError ---------------------------- Captured stdout setup ----------------------------- 1 image files updated 1 image files updated ---------------------------- Captured stderr setup ----------------------------- WARNING: The convert command is deprecated in IMv7, use "magick" instead of "convert" or "magick convert" WARNING: The convert command is deprecated in IMv7, use "magick" instead of "convert" or "magick convert" ___________________ ERROR at setup of test_jpg_rot[pikepdf] ____________________ tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ffff6687c20>, _basetemp=PosixPath('/build/pytest-of-nixbld/pytest-0'), _retention_count=3, _retention_policy='all') tmp_normal_png = PosixPath('/build/pytest-of-nixbld/pytest-0/normal_png0/normal.png') @pytest.fixture(scope="session") def jpg_rot_img(tmp_path_factory, tmp_normal_png): in_img = tmp_path_factory.mktemp("jpg_rot") / "in.jpg" subprocess.check_call(CONVERT + [str(tmp_normal_png), str(in_img)]) subprocess.check_call( ["exiftool", "-overwrite_original", "-all=", str(in_img), "-n"] ) subprocess.check_call( [ "exiftool", "-overwrite_original", "-Orientation=6", "-XResolution=96", "-YResolution=96", "-n", str(in_img), ] ) identify = json.loads(subprocess.check_output(CONVERT + [str(in_img), "json:"])) assert len(identify) == 1 # somewhere between imagemagick 6.9.7.4 and 6.9.9.34, the json output was # put into an array, here we cater for the older version containing just # the bare dictionary if "image" in identify: identify = [identify] assert "image" in identify[0] assert identify[0]["image"].get("format") == "JPEG", str(identify) assert identify[0]["image"].get("mimeType") == "image/jpeg", str(identify) assert identify[0]["image"].get("geometry") == { "width": 60, "height": 60, "x": 0, "y": 0, }, str(identify) assert identify[0]["image"].get("resolution") == {"x": 96, "y": 96} > assert identify[0]["image"].get("units") == "PixelsPerInch", str(identify) E AssertionError: [{'version': '1.0', 'image': {'name': '/build/pytest-of-nixbld/pytest-0/jpg_rot0/in.jpg', 'baseName': 'in.jpg', 'permissions': 644, 'format': 'JPEG', 'formatDescription': 'Joint Photographic Experts Group JFIF format', 'mimeType': 'image/jpeg', 'class': 'DirectClass', 'geometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'resolution': {'x': 96, 'y': 96}, 'printSize': {'x': 0.625, 'y': 0.625}, 'units': 'Undefined', 'type': 'TrueColor', 'baseType': 'Undefined', 'endianness': 'Undefined', 'colorspace': 'sRGB', 'depth': 8, 'baseDepth': 8, 'channelDepth': {'red': 8, 'green': 8, 'blue': 1}, 'pixels': 3600, 'imageStatistics': {'Overall': {'min': 0, 'max': 255, 'mean': 74.3327, 'median': 1.66667, 'standardDeviation': 105.332, 'kurtosis': -1.00142, 'skewness': 0.915224, 'entropy': 0.561842}}, 'channelStatistics': {'red': {'min': 0, 'max': 255, 'mean': 74.3339, 'median': 2, 'standardDeviation': 105.267, 'kurtosis': -0.998881, 'skewness': 0.916638, 'entropy': 0.570394}, 'green': {'min': 0, 'max': 255, 'mean': 74.4989, 'median': 1, 'standardDeviation': 105.663, 'kurtosis': -1.00709, 'skewness': 0.912291, 'entropy': 0.54539}, 'blue': {'min': 0, 'max': 255, 'mean': 74.1653, 'median': 2, 'standardDeviation': 105.065, 'kurtosis': -0.998276, 'skewness': 0.916744, 'entropy': 0.569742}}, 'renderingIntent': 'Perceptual', 'gamma': 0.454545, 'chromaticity': {'redPrimary': {'x': 0.64, 'y': 0.33}, 'greenPrimary': {'x': 0.3, 'y': 0.6}, 'bluePrimary': {'x': 0.15, 'y': 0.06}, 'whitePrimary': {'x': 0.3127, 'y': 0.329}}, 'matteColor': '#BDBDBDBDBDBD', 'backgroundColor': '#FFFFFFFFFFFF', 'borderColor': '#DFDFDFDFDFDF', 'transparentColor': '#000000000000', 'interlace': 'None', 'intensity': 'Undefined', 'compose': 'Over', 'pageGeometry': {'width': 60, 'height': 60, 'x': 0, 'y': 0}, 'dispose': 'Undefined', 'iterations': 0, 'compression': 'JPEG', 'quality': 92, 'orientation': 'RightTop', 'properties': {'exif:YCbCrPositioning': '1', 'jpeg:colorspace': '2', 'jpeg:sampling-factor': '1x1,1x1,1x1', 'signature': '4d03d4a95c2404d895d033f2d4d966ac6d8362be568145c774fe4c10b1c9bb82'}, 'profiles': {'exif': {'length': 84}}, 'tainted': False, 'filesize': '2575B', 'numberPixels': '3600', 'pixelsPerSecond': '14.4009MB', 'userTime': '0.000u', 'elapsedTime': '0:01.000', 'version': 'ImageMagick 7.1.1-47 Q16-HDRI x86_64 c8f4e8cb7:20250329 https://imagemagick.org'}}] E assert 'Undefined' == 'PixelsPerInch' E E - PixelsPerInch E + Undefined src/img2pdf_test.py:1135: AssertionError ```
Owner

Your analysis is spot on. Since version 13.23, exiftool also removes the ResolutionUnit when calling it with -all=. In Debian I fixed that like this: https://sources.debian.org/src/img2pdf/0.6.0-3/debian/patches/0002-src-img2pdf_test.py-exiftool-all-now-sets-the-unit-t.patch/

--- a/src/img2pdf_test.py
+++ b/src/img2pdf_test.py
@@ -1111,6 +1111,7 @@ def jpg_rot_img(tmp_path_factory, tmp_normal_png):
             "-Orientation=6",
             "-XResolution=96",
             "-YResolution=96",
+            "-ResolutionUnit=2",
             "-n",
             str(in_img),
         ]
Your analysis is spot on. Since version 13.23, exiftool also removes the ResolutionUnit when calling it with `-all=`. In Debian I fixed that like this: https://sources.debian.org/src/img2pdf/0.6.0-3/debian/patches/0002-src-img2pdf_test.py-exiftool-all-now-sets-the-unit-t.patch/ ```patch --- a/src/img2pdf_test.py +++ b/src/img2pdf_test.py @@ -1111,6 +1111,7 @@ def jpg_rot_img(tmp_path_factory, tmp_normal_png): "-Orientation=6", "-XResolution=96", "-YResolution=96", + "-ResolutionUnit=2", "-n", str(in_img), ] ```
Author

Sorry, I should have checked the commits on main: 59132f20f8

Sorry, I should have checked the commits on main: https://gitlab.mister-muffin.de/josch/img2pdf/commit/59132f20f8a40f6ed4e5cd2a3719bf55473ba4d7
Owner

No worries, I also totally forgot that I had this already committed to main which is why I quoted the patch from Debian instead Xd

No worries, I also totally forgot that I had this already committed to main which is why I quoted the patch from Debian instead Xd
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: josch/img2pdf#208
No description provided.