From d1db166eb44672421d215d941be13d71e739cb34 Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Tue, 31 Aug 2021 13:51:22 +0200 Subject: [PATCH 1/6] refactor open image so same code is used in all modules --- picframe/config/configuration_example.yaml | 1 + picframe/get_image_meta.py | 34 ++++++++++++++++++---- picframe/image_cache.py | 2 +- picframe/viewer_display.py | 29 ++---------------- test/test_get_image_meta.py | 6 ++-- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/picframe/config/configuration_example.yaml b/picframe/config/configuration_example.yaml index effda0a..2daaf6d 100644 --- a/picframe/config/configuration_example.yaml +++ b/picframe/config/configuration_example.yaml @@ -62,6 +62,7 @@ model: "EXIF FocalLength", "EXIF DateTimeOriginal", "Image Model", + "Image Make", "IPTC Caption/Abstract", "IPTC Object Name", "IPTC Keywords"] diff --git a/picframe/get_image_meta.py b/picframe/get_image_meta.py index 0f98b0b..bb22381 100644 --- a/picframe/get_image_meta.py +++ b/picframe/get_image_meta.py @@ -1,5 +1,6 @@ import exifread import logging +import os from PIL import Image class GetImageMeta: @@ -117,11 +118,32 @@ def get_exif(self, key): def get_size(self): try: # corrupt image file might crash app - width = self.get_exif('EXIF ExifImageWidth') - height = self.get_exif('EXIF ExifImageLength') - if width and height: - return int(width), int(height) - return Image.open(self.__filename).size + return GetImageMeta.get_image_object(self.__filename).size except Exception as e: self.__logger.warning("get_size failed on %s -> %s", self.__filename, e) - return (0, 0) \ No newline at end of file + return (0, 0) + + @staticmethod + def get_image_object(fname): + ext = os.path.splitext(fname)[1].lower() + if ext in ('.heif','.heic'): + try: + import pyheif + + heif_file = pyheif.read(fname) + image = Image.frombytes(heif_file.mode, heif_file.size, heif_file.data, + "raw", heif_file.mode, heif_file.stride) + if image.mode not in ("RGB", "RGBA"): + image = image.convert("RGB") + return image + except: + logger = logging.getLogger("get_image_meta.GetImageMeta") + logger.warning("Failed attempt to convert %s \n** Have you installed pyheif? **", fname) + else: + try: + image = Image.open(fname) + if image.mode not in ("RGB", "RGBA"): # mat system needs RGB or more + image = image.convert("RGB") + except: # for whatever reason + image = None + return image \ No newline at end of file diff --git a/picframe/image_cache.py b/picframe/image_cache.py index d2ed8bd..30323f6 100644 --- a/picframe/image_cache.py +++ b/picframe/image_cache.py @@ -9,7 +9,7 @@ class ImageCache: EXTENSIONS = ['.png','.jpg','.jpeg','.heif','.heic'] EXIF_TO_FIELD = {'EXIF FNumber': 'f_number', - 'EXIF Make': 'make', + 'Image Make': 'make', 'Image Model': 'model', 'EXIF ExposureTime': 'exposure_time', 'EXIF ISOSpeedRatings': 'iso', diff --git a/picframe/viewer_display.py b/picframe/viewer_display.py index f43472a..65b246b 100644 --- a/picframe/viewer_display.py +++ b/picframe/viewer_display.py @@ -8,7 +8,7 @@ import os import numpy as np from PIL import Image, ImageFilter, ImageFile -from picframe import mat_image +from picframe import mat_image, get_image_meta from datetime import datetime # supported display modes for display switch @@ -173,29 +173,6 @@ def clock_is_on(self): def clock_is_on(self, val): self.__show_clock = val - def __check_heif_then_open(self, fname): - ext = os.path.splitext(fname)[1].lower() - if ext in ('.heif','.heic'): - try: - import pyheif - - heif_file = pyheif.read(fname) - image = Image.frombytes(heif_file.mode, heif_file.size, heif_file.data, - "raw", heif_file.mode, heif_file.stride) - if image.mode not in ("RGB", "RGBA"): - image = image.convert("RGB") - return image - except: - self.__logger.warning("Failed attempt to convert %s \n** Have you installed pyheif? **", fname) - else: - try: - image = Image.open(fname) - if image.mode not in ("RGB", "RGBA"): # mat system needs RGB or more - image = image.convert("RGB") - except: # for whatever reason - image = None - return image - # Concatenate the specified images horizontally. Clip the taller # image to the height of the shorter image. def __create_image_pair(self, im1, im2): @@ -272,13 +249,13 @@ def __tex_load(self, pics, size=None): # Load the image(s) and correct their orientation as necessary if pics[0]: - im = self.__check_heif_then_open(pics[0].fname) + im = GetImageMeta.get_image_object(pics[0].fname) if pics[0].orientation != 1: im = self.__orientate_image(im, pics[0].orientation) if im is None: return None if pics[1]: - im2 = self.__check_heif_then_open(pics[1].fname) + im2 = GetImageMeta.get_image_object(pics[1].fname) if pics[1].orientation != 1: im2 = self.__orientate_image(im2, pics[1].orientation) diff --git a/test/test_get_image_meta.py b/test/test_get_image_meta.py index 2b5d6b1..282616a 100644 --- a/test/test_get_image_meta.py +++ b/test/test_get_image_meta.py @@ -69,7 +69,7 @@ def test_exifs_jpg(): assert width == 1920 assert height == 1200 val = exifs.get_exif('Image Make') - assert val == "ILCE-7RM3" + assert val == "SONY" except: pytest.fail("Unexpected exception") @@ -98,8 +98,8 @@ def test_exifs_heic(): assert orientation == 6 width, height = exifs.get_size() - assert width == 4032 - assert height == 3024 + assert height == 4032 + assert width == 3024 f_number = exifs.get_exif('EXIF FNumber') From ea7d07837d1148e9bf92c34cf08f88be6ac51eef Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Wed, 1 Sep 2021 10:39:23 +0200 Subject: [PATCH 2/6] fix heic orientation problem --- picframe/get_image_meta.py | 3 +++ picframe/viewer_display.py | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/picframe/get_image_meta.py b/picframe/get_image_meta.py index bb22381..3e014fe 100644 --- a/picframe/get_image_meta.py +++ b/picframe/get_image_meta.py @@ -92,6 +92,9 @@ def get_location(self): def get_orientation(self): try: + ext = os.path.splitext(self.__filename)[1].lower() + if ext in ('.heif','.heic'): # converting heic to Image object will rotate pic implicitly. So orientation is always 1 + return 1 val = self.__get_if_exist('Image Orientation') if val is not None: return int(val.values[0]) diff --git a/picframe/viewer_display.py b/picframe/viewer_display.py index 65b246b..44d9458 100644 --- a/picframe/viewer_display.py +++ b/picframe/viewer_display.py @@ -249,13 +249,16 @@ def __tex_load(self, pics, size=None): # Load the image(s) and correct their orientation as necessary if pics[0]: - im = GetImageMeta.get_image_object(pics[0].fname) - if pics[0].orientation != 1: - im = self.__orientate_image(im, pics[0].orientation) + im = get_image_meta.GetImageMeta.get_image_object(pics[0].fname) if im is None: return None + if pics[0].orientation != 1: + im = self.__orientate_image(im, pics[0].orientation) + if pics[1]: - im2 = GetImageMeta.get_image_object(pics[1].fname) + im2 = get_image_meta.GetImageMeta.get_image_object(pics[1].fname) + if im2 is None: + return None if pics[1].orientation != 1: im2 = self.__orientate_image(im2, pics[1].orientation) From fdbabf896b96c2a8b2ede37193cbdebf6837ec54 Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Wed, 1 Sep 2021 12:10:30 +0200 Subject: [PATCH 3/6] fix unit test orientation --- test/test_get_image_meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_get_image_meta.py b/test/test_get_image_meta.py index 282616a..e5d9641 100644 --- a/test/test_get_image_meta.py +++ b/test/test_get_image_meta.py @@ -87,7 +87,7 @@ def test_get_orientation(): exifs = GetImageMeta("test/images/test3.HEIC") orientation = exifs.get_orientation() - assert orientation == 6 + assert orientation == 1 # orientation for heic images must be always 1 except: pytest.fail("Unexpected exception") From b34039b1e60fb8cf1addb1e069a716eea14688bf Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Wed, 1 Sep 2021 12:12:13 +0200 Subject: [PATCH 4/6] fix orentation test for heic --- test/test_get_image_meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_get_image_meta.py b/test/test_get_image_meta.py index e5d9641..7204eb6 100644 --- a/test/test_get_image_meta.py +++ b/test/test_get_image_meta.py @@ -95,7 +95,7 @@ def test_exifs_heic(): try: exifs = GetImageMeta("test/images/test3.HEIC") orientation = exifs.get_orientation() - assert orientation == 6 + assert orientation == 1 width, height = exifs.get_size() assert height == 4032 From 2ed18b4181d782b18b12c546e80209f10c8883d2 Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Thu, 2 Sep 2021 11:59:03 +0200 Subject: [PATCH 5/6] Store original orientation in cache. --- picframe/get_image_meta.py | 3 --- picframe/image_cache.py | 3 ++- picframe/viewer_display.py | 10 +++++++--- test/test_get_image_meta.py | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/picframe/get_image_meta.py b/picframe/get_image_meta.py index 3e014fe..bb22381 100644 --- a/picframe/get_image_meta.py +++ b/picframe/get_image_meta.py @@ -92,9 +92,6 @@ def get_location(self): def get_orientation(self): try: - ext = os.path.splitext(self.__filename)[1].lower() - if ext in ('.heif','.heic'): # converting heic to Image object will rotate pic implicitly. So orientation is always 1 - return 1 val = self.__get_if_exist('Image Orientation') if val is not None: return int(val.values[0]) diff --git a/picframe/image_cache.py b/picframe/image_cache.py index 30323f6..1ad0296 100644 --- a/picframe/image_cache.py +++ b/picframe/image_cache.py @@ -454,7 +454,8 @@ def __get_exif_info(self, file_path_name): e['orientation'] = exifs.get_orientation() width, height = exifs.get_size() - if e['orientation'] in (5, 6, 7, 8): + ext = os.path.splitext(file_path_name)[1].lower() + if ext not in ('.heif','.heic') and e['orientation'] in (5, 6, 7, 8): width, height = height, width # swap values e['width'] = width e['height'] = height diff --git a/picframe/viewer_display.py b/picframe/viewer_display.py index 44d9458..d200b42 100644 --- a/picframe/viewer_display.py +++ b/picframe/viewer_display.py @@ -187,7 +187,11 @@ def __create_image_pair(self, im1, im2): dst.paste(im2, (im1.width + sep, 0)) return dst - def __orientate_image(self, im, orientation): + def __orientate_image(self, im, pic): + ext = os.path.splitext(pic.fname)[1].lower() + if ext in ('.heif','.heic'): # heif and heic images are converted to PIL.Image obects and are alway in correct orienation + return im + orientation = pic.orientation if orientation == 2: im = im.transpose(Image.FLIP_LEFT_RIGHT) elif orientation == 3: @@ -253,14 +257,14 @@ def __tex_load(self, pics, size=None): if im is None: return None if pics[0].orientation != 1: - im = self.__orientate_image(im, pics[0].orientation) + im = self.__orientate_image(im, pics[0]) if pics[1]: im2 = get_image_meta.GetImageMeta.get_image_object(pics[1].fname) if im2 is None: return None if pics[1].orientation != 1: - im2 = self.__orientate_image(im2, pics[1].orientation) + im2 = self.__orientate_image(im2, pics[1]) screen_aspect, image_aspect, diff_aspect = self.__get_aspect_diff(size, im.size) diff --git a/test/test_get_image_meta.py b/test/test_get_image_meta.py index 7204eb6..767a4b7 100644 --- a/test/test_get_image_meta.py +++ b/test/test_get_image_meta.py @@ -87,7 +87,7 @@ def test_get_orientation(): exifs = GetImageMeta("test/images/test3.HEIC") orientation = exifs.get_orientation() - assert orientation == 1 # orientation for heic images must be always 1 + assert orientation == 6 except: pytest.fail("Unexpected exception") @@ -95,7 +95,7 @@ def test_exifs_heic(): try: exifs = GetImageMeta("test/images/test3.HEIC") orientation = exifs.get_orientation() - assert orientation == 1 + assert orientation == 6 width, height = exifs.get_size() assert height == 4032 From 617ce856f98a8798bd83428bdb038b9592fe6f5d Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Fri, 3 Sep 2021 14:54:01 +0200 Subject: [PATCH 6/6] make Exif Tag search more flexible --- picframe/get_image_meta.py | 18 +++++++++++++++++- picframe/image_cache.py | 2 +- test/test_get_image_meta.py | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/picframe/get_image_meta.py b/picframe/get_image_meta.py index bb22381..a3d62f8 100644 --- a/picframe/get_image_meta.py +++ b/picframe/get_image_meta.py @@ -103,7 +103,23 @@ def get_orientation(self): def get_exif(self, key): try: - val = self.__get_if_exist(key) + iso_keys = ['EXIF ISOSpeedRatings', 'EXIF PhotographicSensitivity', 'EXIF ISO'] # ISO prior 2.2, ISOSpeedRatings 2.2, PhotographicSensitivity 2.3 + if key in iso_keys: + for iso in iso_keys: + val = self.__get_if_exist(iso) + if val: + break + else: + val = self.__get_if_exist(key) + + if val is None: + grp, tag = key.split(" ", 1) + if grp == "EXIF": + newkey = "Image" + " " + tag + val = self.__get_if_exist(newkey) + elif grp == "Image": + newkey = "EXIF" + " " + tag + val = self.__get_if_exist(newkey) if val is not None: if key == 'EXIF FNumber': val = round(val.values[0].num / val.values[0].den, 1) diff --git a/picframe/image_cache.py b/picframe/image_cache.py index 1ad0296..c03aa62 100644 --- a/picframe/image_cache.py +++ b/picframe/image_cache.py @@ -467,7 +467,7 @@ def __get_exif_info(self, file_path_name): e['exposure_time'] = exifs.get_exif('EXIF ExposureTime') e['iso'] = exifs.get_exif('EXIF ISOSpeedRatings') e['focal_length'] = exifs.get_exif('EXIF FocalLength') - e['rating'] = exifs.get_exif('EXIF Rating') + e['rating'] = exifs.get_exif('Image Rating') e['lens'] = exifs.get_exif('EXIF LensModel') e['exif_datetime'] = None val = exifs.get_exif('EXIF DateTimeOriginal') diff --git a/test/test_get_image_meta.py b/test/test_get_image_meta.py index 767a4b7..f153ecf 100644 --- a/test/test_get_image_meta.py +++ b/test/test_get_image_meta.py @@ -70,6 +70,8 @@ def test_exifs_jpg(): assert height == 1200 val = exifs.get_exif('Image Make') assert val == "SONY" + val = exifs.get_exif('EXIF Make') # This should work as well + assert val == "SONY" except: pytest.fail("Unexpected exception")