Skip to content

Commit 9347bbf

Browse files
patrickfournierjustinmayer
authored andcommitted
Update ExifImageWidth and ExifImageHeight to their correct values.
If a processed image has the ExifImageWidth or ExifImageHeight tags and the IMAGE_PROCESS_COPY_EXIF_TAGS setting is True, update the tags to the same value as ImageWidth or ImageHeight, respectively. Add tests to verify that: - an image without ExifImageWidth or ExifImageHeight is left untouched - in an image with ExifImageWidth or ExifImageHeight, their values are the same as ImageWidth or ImageHeight.
1 parent 64b1d85 commit 9347bbf

File tree

7 files changed

+109
-19
lines changed

7 files changed

+109
-19
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ IMAGE_PROCESS_COPY_EXIF_TAGS = True
490490
Note that `exiftool` prior to version 12.46 cannot write WebP images, so if you work
491491
with WebP images, you should use version 12.46 or later.
492492

493+
When copying EXIF tags, the plugin will update `ExifImageWidth` and
494+
`ExifImageHeight` tags to reflect the dimensions of the transformed image.
495+
493496
#### Modifying the `class` Attribute of Processed Images
494497

495498
By default, *Image Process* adds the `image-process-<transform>`
@@ -520,9 +523,12 @@ IMAGE_PROCESS_ADD_CLASS = False
520523

521524
## Contributing
522525

523-
Contributions are welcome and much appreciated. Every little bit helps. You can contribute by improving the documentation, adding missing features, and fixing bugs. You can also help out by reviewing and commenting on [existing issues][].
526+
Contributions are welcome and much appreciated. Every little bit helps. You can
527+
contribute by improving the documentation, adding missing features, and fixing bugs.
528+
You can also help out by reviewing and commenting on [existing issues][].
524529

525-
To start contributing to this plugin, review the [Contributing to Pelican][] documentation, beginning with the **Contributing Code** section.
530+
To start contributing to this plugin, review the [Contributing to Pelican][]
531+
documentation, beginning with the **Contributing Code** section.
526532

527533
[existing issues]: https://github.com/pelican-plugins/image-process/issues
528534
[Contributing to Pelican]: https://docs.getpelican.com/en/latest/contribute.html

pelican/plugins/image_process/image_process.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __init__(self):
7575
with contextlib.suppress(LookupError):
7676
codecs.lookup_error("surrogateescape")
7777

78-
with open(os.devnull, "w") as devnull:
78+
with open(os.devnull, "w"):
7979
self.process = subprocess.Popen(
8080
[
8181
"exiftool",
@@ -89,7 +89,7 @@ def __init__(self):
8989
],
9090
stdin=subprocess.PIPE,
9191
stdout=subprocess.PIPE,
92-
stderr=devnull,
92+
stderr=subprocess.STDOUT,
9393
)
9494

9595
def __del__(self):
@@ -101,21 +101,40 @@ def _copy_tags(self, src, dst):
101101
params = (
102102
b"-TagsFromFile",
103103
src.encode(self.encoding, ExifTool.errors),
104-
b'"-all:all>all:all"',
104+
dst.encode(self.encoding, ExifTool.errors),
105+
)
106+
self._send_command(params)
107+
params = (
108+
b"-ExifImageWidth<ImageWidth",
109+
b"-if",
110+
b'"$ExifImageWidth"',
111+
dst.encode(self.encoding, ExifTool.errors),
112+
)
113+
self._send_command(params)
114+
params = (
115+
b"-ExifImageHeight<ImageHeight",
116+
b"-if",
117+
b'"$ExifImageHeight"',
105118
dst.encode(self.encoding, ExifTool.errors),
106119
)
107120
self._send_command(params)
108121
params = (b"-delete_original!", dst.encode(self.encoding, ExifTool.errors))
109122
self._send_command(params)
110123

111124
def _send_command(self, params):
112-
self.process.stdin.write(b"\n".join((*params, b"-j\n", b"-execute\n")))
125+
joined_params = b"\n".join((*params, b"-j\n", b"-execute\n"))
126+
self.process.stdin.write(joined_params)
113127
self.process.stdin.flush()
128+
114129
output = b""
115130
fd = self.process.stdout.fileno()
116131
while not output.strip().endswith(ExifTool.sentinel):
117132
output += os.read(fd, ExifTool.block_size)
118133
exiftool_result = output.strip()[: -len(ExifTool.sentinel)]
134+
135+
logger.debug(
136+
"{} exiftool command: {}".format(LOG_PREFIX, joined_params.decode("utf-8"))
137+
)
119138
logger.debug(
120139
"{} exiftool result: {}".format(LOG_PREFIX, exiftool_result.decode("utf-8"))
121140
)
106 KB
Loading
Binary file not shown.
85.9 KB
Loading
Binary file not shown.

pelican/plugins/image_process/test_image_process.py

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
TEST_DATA.joinpath("exif", f"pelican-bird.{ext}").resolve()
4242
for ext in SUPPORTED_EXIF_IMAGE_FORMATS
4343
]
44+
NOEXIF_TEST_IMAGES = [
45+
TEST_DATA.joinpath("noexif", f"pelican-bird.{ext}").resolve()
46+
for ext in SUPPORTED_EXIF_IMAGE_FORMATS
47+
]
4448
TRANSFORM_RESULTS = TEST_DATA.joinpath("results").resolve()
4549

4650
# Register all supported transforms.
@@ -708,30 +712,35 @@ def test_copy_exif_tags(tmp_path, image_path, copy_tags):
708712
return
709713

710714
# A few EXIF tags to test for.
711-
exif_tags = [
712-
"Artist",
713-
"Creator",
714-
"Title",
715-
"Description",
716-
"Subject",
717-
"Rating",
715+
exif_tags = ["Artist", "Creator", "Title", "Description", "Subject", "Rating"]
716+
717+
size_tags = [
718718
"ExifImageWidth",
719+
"ExifImageHeight",
720+
"ImageWidth",
721+
"ImageHeight",
719722
]
720723

721724
settings = get_settings(IMAGE_PROCESS_COPY_EXIF_TAGS=copy_tags)
722725

723-
transform_id = "grayscale"
724-
transform_params = ["grayscale"]
726+
# Use a transform that changes image dimensions.
727+
transform_id = "scale_in"
728+
transform_params = ["scale_in 200 250 False"]
725729
image_name = image_path.name
726730
destination_path = tmp_path.joinpath(transform_id, image_name)
727731

728732
expected_results = subprocess.run(
729733
["exiftool", "-json", image_path], stdout=subprocess.PIPE, check=False
730734
)
731735
expected_tags = json.loads(expected_results.stdout)[0]
732-
for tag in exif_tags:
736+
for tag in exif_tags + size_tags:
733737
assert tag in expected_tags
734738

739+
assert "ExifImageWidth" in expected_tags
740+
assert "ExifImageHeight" in expected_tags
741+
assert expected_tags["ExifImageWidth"] == expected_tags["ImageWidth"]
742+
assert expected_tags["ExifImageHeight"] == expected_tags["ImageHeight"]
743+
735744
if copy_tags:
736745
ExifTool.start_exiftool()
737746
process_image((str(image_path), str(destination_path), transform_params), settings)
@@ -745,12 +754,68 @@ def test_copy_exif_tags(tmp_path, image_path, copy_tags):
745754
assert actual_results.returncode == 0
746755

747756
actual_tags = json.loads(actual_results.stdout)[0]
748-
for tag in exif_tags:
757+
for tag in exif_tags + size_tags:
749758
if copy_tags:
750759
assert tag in actual_tags
751-
assert expected_tags[tag] == actual_tags[tag]
752760
else:
753-
assert tag not in actual_tags
761+
assert tag in {"ImageWidth", "ImageHeight"} or tag not in actual_tags
762+
763+
if copy_tags:
764+
for tag in exif_tags:
765+
assert expected_tags[tag] == actual_tags[tag]
766+
767+
# Dimensions should differ due to the transform.
768+
assert expected_tags["ExifImageWidth"] != actual_tags["ExifImageWidth"]
769+
assert expected_tags["ImageWidth"] != actual_tags["ImageWidth"]
770+
assert expected_tags["ExifImageHeight"] != actual_tags["ExifImageHeight"]
771+
assert expected_tags["ImageHeight"] != actual_tags["ImageHeight"]
772+
773+
# Check that the exif size tags are equal in the real size tags.
774+
assert actual_tags["ExifImageWidth"] == actual_tags["ImageWidth"]
775+
assert actual_tags["ExifImageHeight"] == actual_tags["ImageHeight"]
776+
777+
778+
@pytest.mark.parametrize("image_path", NOEXIF_TEST_IMAGES)
779+
def test_copy_exif_tags_does_not_add_exif_dims_tags(tmp_path, image_path):
780+
if shutil.which("exiftool") is None:
781+
warnings.warn(
782+
"EXIF tags copying will not be tested because the exiftool program could "
783+
"not be found. Please install exiftool and make sure it is in your path.",
784+
stacklevel=2,
785+
)
786+
return
787+
788+
settings = get_settings(IMAGE_PROCESS_COPY_EXIF_TAGS=True)
789+
790+
transform_id = "scale_in"
791+
transform_params = ["scale_in 200 250 False"]
792+
image_name = image_path.name
793+
destination_path = tmp_path.joinpath(transform_id, image_name)
794+
795+
expected_results = subprocess.run(
796+
["exiftool", "-json", image_path], stdout=subprocess.PIPE, check=False
797+
)
798+
expected_tags = json.loads(expected_results.stdout)[0]
799+
800+
# We want to verify that dimension tags are not added
801+
# if they are not initially present.
802+
assert "ExifImageWidth" not in expected_tags
803+
assert "ExifImageHeight" not in expected_tags
804+
805+
ExifTool.start_exiftool()
806+
process_image((str(image_path), str(destination_path), transform_params), settings)
807+
ExifTool.stop_exiftool()
808+
809+
actual_results = subprocess.run(
810+
["exiftool", "-json", destination_path], stdout=subprocess.PIPE, check=False
811+
)
812+
813+
assert actual_results.returncode == 0
814+
815+
actual_tags = json.loads(actual_results.stdout)[0]
816+
817+
assert "ExifImageWidth" not in actual_tags
818+
assert "ExifImageHeight" not in actual_tags
754819

755820

756821
def test_try_open_image():

0 commit comments

Comments
 (0)