Merge branch 'feature/enhancing' into 'main'

feat: BREAKING GPS now must be float

See merge request CodeByMrFinchum/optima35!15
This commit is contained in:
Mr Finchum 2025-01-21 19:05:48 +00:00
commit 46d79bf542
6 changed files with 70 additions and 26 deletions

View file

@ -1,6 +1,17 @@
# Changelog # Changelog
## 0.6.x ## 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 ### 0.6.4
- Released a stable-ish version to ensure compatibility with the current GUI in OptimaLab35 (v0.1.0). - 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. - This version serves as a baseline before potential breaking changes in future updates.

2
pip_README.md Normal file
View file

@ -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.

View file

@ -6,8 +6,8 @@ build-backend = "hatchling.build"
name = "optima35" name = "optima35"
dynamic = ["version"] dynamic = ["version"]
authors = [{ name = "Mr. Finchum" }] authors = [{ name = "Mr. Finchum" }]
description = "OPTIMA35 is a package to modify images with pillow and piexif." description = "optima35 is a package to modify images, using pillow and piexif."
readme = "README.md" readme = "pip_README.md"
requires-python = ">=3.8" requires-python = ">=3.8"
dependencies = ["piexif", "pillow"] dependencies = ["piexif", "pillow"]
classifiers = [ classifiers = [

View file

@ -1,6 +1,7 @@
from . import __version__ from . import __version__
# From ChatGPT # From ChatGPT
def main(): 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(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.") print("Please use OptimaLab35 for a UI, run pip install OptimaLab35 and start with OptimaLab35.")

View file

@ -6,15 +6,15 @@ from optima35 import __version__
class OptimaManager: class OptimaManager:
def __init__(self): def __init__(self):
self.name = "OPTIMA35" self.name = "optima35"
self.version = __version__ self.version = __version__
self.image_processor = ImageProcessor() self.image_processor = ImageProcessor()
self.exif_handler = ExifHandler() self.exif_handler = ExifHandler()
def modify_timestamp_in_exif(self, data_for_exif: dict, filename: str): 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.""" """"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)] last_three = filename[-3:len(filename)]
total_seconds = int(re.sub(r'\D+', '', last_tree)) total_seconds = int(re.sub(r'\D+', '', last_three))
minutes = total_seconds // 60 minutes = total_seconds // 60
seconds = 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 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") data_for_exif["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S")
return data_for_exif 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_input_file,
image_output_file, image_output_file,
file_type, file_type = "jpg",
quality, quality = 90,
compressing, compressing = 6,
optimize, optimize = False,
resize = None, resize = None,
watermark = None, watermark = None,
font_size = 2, font_size = 2,
@ -37,17 +37,17 @@ class OptimaManager:
contrast = None, contrast = None,
dict_for_exif = None, dict_for_exif = None,
gps = None, gps = None,
copy_exif = False): copy_exif = False,
save = True):
# Partly optimized by ChatGPT # Partly optimized by ChatGPT
# Open the image file # Open the image file
with self.image_processor.open_image(image_input_file) as img: with self.image_processor.open_image(image_input_file) as img:
processed_img = img processed_img = img
image_name = os.path.basename(image_output_file) # for date adjustment image_name = os.path.basename(image_output_file) # for date adjustment
# Resize # Resize
if resize is not None: if resize is not None:
processed_img = self.image_processor.resize_image( processed_img = self.image_processor.resize_image(
image=processed_img, percent=resize image=processed_img, percent = resize
) )
# Watermark # Watermark
@ -78,7 +78,7 @@ class OptimaManager:
selected_exif = dict_for_exif selected_exif = dict_for_exif
if "date_time_original" in dict_for_exif: if "date_time_original" in dict_for_exif:
selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) 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) selected_exif, self.image_processor.get_image_size(processed_img)
) )
@ -97,13 +97,35 @@ class OptimaManager:
except Exception: except Exception:
print("Copying EXIF data selected, but no EXIF data is available in the original image file.") print("Copying EXIF data selected, but no EXIF data is available in the original image file.")
# Save the processed image if save:
self.image_processor.save_image( # Save the processed image
image = processed_img, self.image_processor.save_image(
path = image_output_file, image = processed_img,
piexif_exif_data = exif_piexif_format, path = image_output_file,
file_type = file_type, piexif_exif_data = exif_piexif_format,
jpg_quality = quality, file_type = file_type,
png_compressing = compressing, jpg_quality = quality,
optimize = optimize 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)

View file

@ -1,4 +1,4 @@
from PIL import Image, ImageDraw, ImageFont, ImageEnhance from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageQt
import piexif import piexif
from fractions import Fraction from fractions import Fraction
@ -91,6 +91,10 @@ class ImageProcessor:
except Exception as e: except Exception as e:
print(f"Failed to save image: {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: class ExifHandler:
"""Function using piexif are here.""" """Function using piexif are here."""
def __init__(self): def __init__(self):
@ -99,7 +103,7 @@ class ExifHandler:
def get_exif_info(self, image): def get_exif_info(self, image):
return(piexif.load(image.info['exif'])) 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.""" """Build a piexif-compatible EXIF dictionary from a dicts."""
# Mostly made by ChatGPT, some adjustment # Mostly made by ChatGPT, some adjustment
zeroth_ifd = { zeroth_ifd = {
@ -197,3 +201,7 @@ class ExifHandler:
return exif_data return exif_data
except Exception as e: except Exception as e:
print(f"Error: {str(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)