From 37831e1c02ed6c4953c90d9acc7bf00f1d3bdc0e Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Tue, 21 Jan 2025 19:05:48 +0000 Subject: [PATCH] feat: BREAKING GPS now must be float --- CHANGELOG.md | 11 ++++++ pip_README.md | 2 ++ pyproject.toml | 4 +-- src/optima35/__main__.py | 1 + src/optima35/core.py | 66 +++++++++++++++++++++++------------ src/optima35/image_handler.py | 12 +++++-- 6 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 pip_README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 360b30c..6e42da3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog ## 0.6.x +### 0.7.0 +- **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) + +### 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). - This version serves as a baseline before potential breaking changes in future updates. 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 = [ 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.") diff --git a/src/optima35/core.py b/src/optima35/core.py index e7e122e..9ff8a53 100644 --- a/src/optima35/core.py +++ b/src/optima35/core.py @@ -6,15 +6,15 @@ 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() 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 @@ -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, # TODO: 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,17 +37,17 @@ 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: 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( - image=processed_img, percent=resize + image=processed_img, percent = resize ) # Watermark @@ -78,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) ) @@ -97,13 +97,35 @@ 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) + + 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 = 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) diff --git a/src/optima35/image_handler.py b/src/optima35/image_handler.py index 33f47a0..10232c2 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): @@ -99,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 = { @@ -197,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)