feat: BREAKING GPS now must be float
This commit is contained in:
parent
8e56565b29
commit
37831e1c02
6 changed files with 70 additions and 26 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -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
2
pip_README.md
Normal 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.
|
|
@ -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 = [
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
||||||
|
|
|
@ -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,13 +37,13 @@ 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(
|
||||||
|
@ -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,6 +97,7 @@ 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.")
|
||||||
|
|
||||||
|
if save:
|
||||||
# Save the processed image
|
# Save the processed image
|
||||||
self.image_processor.save_image(
|
self.image_processor.save_image(
|
||||||
image = processed_img,
|
image = processed_img,
|
||||||
|
@ -107,3 +108,24 @@ class OptimaManager:
|
||||||
png_compressing = compressing,
|
png_compressing = compressing,
|
||||||
optimize = optimize
|
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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue