From 1d43b76bc4adf271aa6c3ba721d56fb14dba4909 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Wed, 8 Jan 2025 15:38:03 +0100 Subject: [PATCH 01/10] Added function to convert image compatible with qlabel. --- CHANGELOG.md | 4 ++++ src/optima35/core.py | 38 +++++++++++++++++++---------------- src/optima35/image_handler.py | 6 +++++- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 360b30c..8dde563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## 0.6.x +### 0.6.5-a +- No breaking changes to backward compatibility yet. +- Updated the `process` function: an image can now be returned in a modified form without saving. It is returned as a Qt image, which is required for the new UI functionality. + ### 0.6.4 - Released a stable-ish version to ensure compatibility with the current GUI in OptimaLab35 (v0.1.0). - This version serves as a baseline before potential breaking changes in future updates. diff --git a/src/optima35/core.py b/src/optima35/core.py index e7e122e..e69cfae 100644 --- a/src/optima35/core.py +++ b/src/optima35/core.py @@ -22,13 +22,13 @@ class OptimaManager: data_for_exif["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S") return data_for_exif - def process_image(self, + def process_image(self, #split into two classes, one for modification for one saving.. image_input_file, image_output_file, - file_type, - quality, - compressing, - optimize, + file_type = "jpg", + quality = 90, + compressing = 6, + optimize = False, resize = None, watermark = None, font_size = 2, @@ -37,7 +37,8 @@ class OptimaManager: contrast = None, dict_for_exif = None, gps = None, - copy_exif = False): + copy_exif = False, + save = True): # Partly optimized by ChatGPT # Open the image file with self.image_processor.open_image(image_input_file) as img: @@ -47,7 +48,7 @@ class OptimaManager: # Resize if resize is not None: processed_img = self.image_processor.resize_image( - image=processed_img, percent=resize + image=processed_img, percent = resize ) # Watermark @@ -97,13 +98,16 @@ class OptimaManager: except Exception: print("Copying EXIF data selected, but no EXIF data is available in the original image file.") - # Save the processed image - self.image_processor.save_image( - image = processed_img, - path = image_output_file, - piexif_exif_data = exif_piexif_format, - file_type = file_type, - jpg_quality = quality, - png_compressing = compressing, - optimize = optimize - ) + if save: + # Save the processed image + self.image_processor.save_image( + image = processed_img, + path = image_output_file, + piexif_exif_data = exif_piexif_format, + file_type = file_type, + jpg_quality = quality, + png_compressing = compressing, + optimize = optimize + ) + else: + return self.image_processor.convert_pil_to_qtimage(processed_img) diff --git a/src/optima35/image_handler.py b/src/optima35/image_handler.py index 33f47a0..c42a66e 100644 --- a/src/optima35/image_handler.py +++ b/src/optima35/image_handler.py @@ -1,4 +1,4 @@ -from PIL import Image, ImageDraw, ImageFont, ImageEnhance +from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageQt import piexif from fractions import Fraction @@ -91,6 +91,10 @@ class ImageProcessor: except Exception as e: print(f"Failed to save image: {e}") + def convert_pil_to_qtimage(self, pillow_image): + qt_image = ImageQt.ImageQt(pillow_image) + return qt_image + class ExifHandler: """Function using piexif are here.""" def __init__(self): From fce4f4de573d99639103e65b10feb53694bca0da Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Thu, 9 Jan 2025 11:32:31 +0100 Subject: [PATCH 02/10] Readme just for pypi, updated toml file. --- pip_README.md | 2 ++ pyproject.toml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 pip_README.md diff --git a/pip_README.md b/pip_README.md new file mode 100644 index 0000000..afe32fa --- /dev/null +++ b/pip_README.md @@ -0,0 +1,2 @@ +Uses pillow and piexif to modify images, see [optima35](https://gitlab.com/CodeByMrFinchum/optima35) gitlab for more information. +Install [OptimaLab35](https://pypi.org/project/OptimaLab35/) in pip for a GUI. diff --git a/pyproject.toml b/pyproject.toml index 2f7e4bf..1ee9c4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "hatchling.build" name = "optima35" dynamic = ["version"] authors = [{ name = "Mr. Finchum" }] -description = "OPTIMA35 is a package to modify images with pillow and piexif." -readme = "README.md" +description = "optima35 is a package to modify images, using pillow and piexif." +readme = "pip_README.md" requires-python = ">=3.8" dependencies = ["piexif", "pillow"] classifiers = [ From 50dce8cb74ffca318c2a61f7838a67b7f8a2d92a Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Thu, 9 Jan 2025 11:32:49 +0100 Subject: [PATCH 03/10] v0.6.5 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dde563..db44468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Changelog ## 0.6.x -### 0.6.5-a +### 0.6.5 / -a - No breaking changes to backward compatibility yet. - Updated the `process` function: an image can now be returned in a modified form without saving. It is returned as a Qt image, which is required for the new UI functionality. +- No change from alpha to *stable* ### 0.6.4 - Released a stable-ish version to ensure compatibility with the current GUI in OptimaLab35 (v0.1.0). From 0b7365e78967fc6fc0cdc369416e83989359412d Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Thu, 9 Jan 2025 11:33:14 +0100 Subject: [PATCH 04/10] Minor change. --- src/optima35/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/optima35/core.py b/src/optima35/core.py index e69cfae..623d274 100644 --- a/src/optima35/core.py +++ b/src/optima35/core.py @@ -6,7 +6,7 @@ from optima35 import __version__ class OptimaManager: def __init__(self): - self.name = "OPTIMA35" + self.name = "optima35" self.version = __version__ self.image_processor = ImageProcessor() self.exif_handler = ExifHandler() @@ -22,7 +22,7 @@ class OptimaManager: data_for_exif["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S") return data_for_exif - def process_image(self, #split into two classes, one for modification for one saving.. + def process_image(self, # TODO: split into two classes, one for modification for one saving.. image_input_file, image_output_file, file_type = "jpg", From bc735b6830536551f2f1bb9dc2e47e158fddcb09 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Sun, 12 Jan 2025 16:56:24 +0100 Subject: [PATCH 05/10] v0.6.6 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db44468..94c7866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## 0.6.x +### 0.6.6 +- Added function to insert exif data into image file (i.e. without modifying image) + ### 0.6.5 / -a - No breaking changes to backward compatibility yet. - Updated the `process` function: an image can now be returned in a modified form without saving. It is returned as a Qt image, which is required for the new UI functionality. From 646083daff8991374267f10c19c9e37696a2c881 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Sun, 12 Jan 2025 16:57:28 +0100 Subject: [PATCH 06/10] Insert exif direclty into image file. --- src/optima35/core.py | 26 ++++++++++++++++++++++---- src/optima35/image_handler.py | 6 +++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/optima35/core.py b/src/optima35/core.py index 623d274..a885a5c 100644 --- a/src/optima35/core.py +++ b/src/optima35/core.py @@ -13,8 +13,8 @@ class OptimaManager: def modify_timestamp_in_exif(self, data_for_exif: dict, filename: str): """"Takes a dict formated for exif use by piexif and adjusts the date_time_original, changing the minutes and seconds to fit the number of the filname.""" - last_tree = filename[-3:len(filename)] - total_seconds = int(re.sub(r'\D+', '', last_tree)) + last_three = filename[-3:len(filename)] + total_seconds = int(re.sub(r'\D+', '', last_three)) minutes = total_seconds // 60 seconds = total_seconds % 60 time = datetime.strptime(data_for_exif["date_time_original"], "%Y:%m:%d %H:%M:%S") # change date time string back to an time object for modification @@ -44,7 +44,6 @@ class OptimaManager: with self.image_processor.open_image(image_input_file) as img: processed_img = img image_name = os.path.basename(image_output_file) # for date adjustment - # Resize if resize is not None: processed_img = self.image_processor.resize_image( @@ -79,7 +78,7 @@ class OptimaManager: selected_exif = dict_for_exif if "date_time_original" in dict_for_exif: selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) - exif_piexif_format = self.exif_handler.build_exif_dict( + exif_piexif_format = self.exif_handler.build_exif_bytes( selected_exif, self.image_processor.get_image_size(processed_img) ) @@ -111,3 +110,22 @@ class OptimaManager: ) else: return self.image_processor.convert_pil_to_qtimage(processed_img) + + def insert_dict_to_image(self, exif_dict, image_path, gps = None): + image_name, ending = os.path.splitext(os.path.basename(image_path)) + img = self.image_processor.open_image(image_path) + selected_exif = exif_dict + if "date_time_original" in exif_dict: + selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) + + exif_piexif_format = self.exif_handler.build_exif_bytes( + selected_exif, self.image_processor.get_image_size(img) + ) + + # GPS data + if gps is not None: + latitude = float(gps[0]) + longitude = float(gps[1]) + exif_piexif_format = self.exif_handler.add_geolocation_to_exif(exif_piexif_format, latitude, longitude) + + self.exif_handler.insert_exif(exif_dict = exif_piexif_format, img_path = image_path) diff --git a/src/optima35/image_handler.py b/src/optima35/image_handler.py index c42a66e..10232c2 100644 --- a/src/optima35/image_handler.py +++ b/src/optima35/image_handler.py @@ -103,7 +103,7 @@ class ExifHandler: def get_exif_info(self, image): return(piexif.load(image.info['exif'])) - def build_exif_dict(self, user_data, imagesize): + def build_exif_bytes(self, user_data, imagesize): """Build a piexif-compatible EXIF dictionary from a dicts.""" # Mostly made by ChatGPT, some adjustment zeroth_ifd = { @@ -201,3 +201,7 @@ class ExifHandler: return exif_data except Exception as e: print(f"Error: {str(e)}") + + def insert_exif(self, exif_dict, img_path): + exif_bytes = piexif.dump(exif_dict) + piexif.insert(exif_bytes, img_path) From 082fd95eaf0001b4021153761dd788672b89061c Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Sun, 12 Jan 2025 16:57:50 +0100 Subject: [PATCH 07/10] Added copyright info --- src/optima35/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/optima35/__main__.py b/src/optima35/__main__.py index e2ca02d..99eafcd 100644 --- a/src/optima35/__main__.py +++ b/src/optima35/__main__.py @@ -1,6 +1,7 @@ from . import __version__ # From ChatGPT def main(): + print("(C) 2024-2025 Mr. Finchum aka CodeByMrFinchum") print(f"optima35 (v{__version__}) is a core library and not intended to be run directly.") print("Please use OptimaLab35 for a UI, run pip install OptimaLab35 and start with OptimaLab35.") From 44d98ef28f74290e08a12b46a5799fea7150a97e Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Thu, 16 Jan 2025 16:56:15 +0100 Subject: [PATCH 08/10] 0.6.7 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c7866..8d58d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## 0.6.x +### 0.6.7 +- **BREAKING CHANGE:** GPS location must now be provided as a float instead of a string. + ### 0.6.6 - Added function to insert exif data into image file (i.e. without modifying image) From 4622136a8ef34e57c681d07640f1f1662b5b9d7c Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Thu, 16 Jan 2025 16:56:37 +0100 Subject: [PATCH 09/10] BREAKING GPS has to be float --- src/optima35/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/optima35/core.py b/src/optima35/core.py index a885a5c..9ff8a53 100644 --- a/src/optima35/core.py +++ b/src/optima35/core.py @@ -124,8 +124,8 @@ class OptimaManager: # GPS data if gps is not None: - latitude = float(gps[0]) - longitude = float(gps[1]) + latitude = gps[0] + longitude = gps[1] exif_piexif_format = self.exif_handler.add_geolocation_to_exif(exif_piexif_format, latitude, longitude) self.exif_handler.insert_exif(exif_dict = exif_piexif_format, img_path = image_path) From 93c3223befd491055dd40e77f599335d2db310f8 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Tue, 21 Jan 2025 20:03:00 +0100 Subject: [PATCH 10/10] v0.7.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d58d80..6e42da3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog ## 0.6.x -### 0.6.7 +### 0.7.0 - **BREAKING CHANGE:** GPS location must now be provided as a float instead of a string. ### 0.6.6