All features aviable

This commit is contained in:
Mr Finchum 2024-12-30 21:14:07 +01:00
parent dc3ddd3105
commit 0922830ecb

270
main.py
View file

@ -2,11 +2,13 @@ import sys
import os import os
import re import re
import time import time
import subprocess
from datetime import datetime from datetime import datetime
from utils.utility import Utilities from utils.utility import Utilities
from utils.image_handler import ImageProcessor, ExifHandler from utils.image_handler import ImageProcessor, ExifHandler
from ui.main_window import Ui_MainWindow from ui.main_window import Ui_MainWindow
from ui.exif_handler_window import ExifEditor
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
@ -25,16 +27,16 @@ from PySide6.QtWidgets import (
) )
class Optima35QT6(QMainWindow, Ui_MainWindow): class Optima35QT6(QMainWindow, Ui_MainWindow):
def __init__(self): def __init__(self, exif_file, version):
super(Optima35QT6, self).__init__() super(Optima35QT6, self).__init__()
self.ui = Ui_MainWindow() self.ui = Ui_MainWindow()
self.ui.setupUi(self) self.ui.setupUi(self)
self.define_settings() self.define_settings(exif_file, version)
self.setWindowTitle(f"{self.name} v{self.version}") self.setWindowTitle(f"{self.name} v{self.version}")
self.default_ui_layout() self.default_ui_layout()
self.define_gui_interaction() self.define_gui_interaction()
# GUI
def default_ui_layout(self): def default_ui_layout(self):
self.ui.png_quality_spinBox.setVisible(False) self.ui.png_quality_spinBox.setVisible(False)
@ -44,26 +46,181 @@ class Optima35QT6(QMainWindow, Ui_MainWindow):
self.ui.start_button.clicked.connect(self.process) self.ui.start_button.clicked.connect(self.process)
self.ui.image_type.currentIndexChanged.connect(self.update_quality_options) self.ui.image_type.currentIndexChanged.connect(self.update_quality_options)
self.ui.exif_checkbox.stateChanged.connect(
lambda state: self.handle_checkbox_state(state, 2, self.populate_exif)
)
self.ui.tabWidget.currentChanged.connect(self.on_tab_changed)
self.ui.edit_exif_button.clicked.connect(self.open_exif_editor)
self.ui.restart_button.clicked.connect(self.restart_app)
def open_exif_editor(self):
"""Open the EXIF Editor."""
self.exif_editor = ExifEditor(self.exif_data)
self.exif_editor.exif_data_updated.connect(self.update_exif_data)
self.exif_editor.show()
def update_exif_data(self, updated_exif_data):
"""Update the EXIF data."""
self.exif_data = updated_exif_data
self.populate_exif()
def handle_checkbox_state(self, state, desired_state, action):
"""Perform an action based on the checkbox state and a desired state. Have to use lambda when calling."""
# improved by chatGPT
if state == desired_state:
action()
def on_tab_changed(self, index):
"""Handle tab changes."""
# chatgpt
if index == 1: # EXIF Tab
self.handle_exif_file("read")
elif index == 0: # Main Tab
self.handle_exif_file("write")
def handle_exif_file(self, do):
if do == "read":
self.exif_data = self.utilities.read_yaml(self.exif_file)
elif do == "write":
self.utilities.write_yaml(self.exif_file, self.exif_data)
def populate_exif(self):
# partly chatGPT
# Mapping of EXIF fields to comboboxes in the UI
combo_mapping = {
"make": self.ui.make_comboBox,
"model": self.ui.model_comboBox,
"lens": self.ui.lens_comboBox,
"iso": self.ui.iso_comboBox,
"image_description": self.ui.image_description_comboBox,
"user_comment": self.ui.user_comment_comboBox,
"artist": self.ui.artist_comboBox,
"copyright_info": self.ui.copyright_info_comboBox,
}
self.populate_comboboxes(combo_mapping)
def populate_comboboxes(self, combo_mapping):
"""Populate comboboxes with EXIF data."""
# ChatGPT
for field, comboBox in combo_mapping.items():
comboBox.clear() # Clear existing items
comboBox.addItems(map(str, self.exif_data.get(field, [])))
def update_quality_options(self): def update_quality_options(self):
"""Update visibility of quality settings based on selected format.""" """Update visibility of quality settings based on selected format."""
# ChatGPT
selected_format = self.ui.image_type.currentText() selected_format = self.ui.image_type.currentText()
# Hide all quality settings # Hide all quality settings
self.ui.png_quality_spinBox.setVisible(False) self.ui.png_quality_spinBox.setVisible(False)
self.ui.jpg_quality_spinBox.setVisible(False) self.ui.jpg_quality_spinBox.setVisible(False)
# Show relevant settings # Show relevant settings
if selected_format == "jpg": if selected_format == "jpg":
self.ui.jpg_quality_spinBox.setVisible(True) self.ui.jpg_quality_spinBox.setVisible(True)
elif selected_format == "webp":
self.ui.jpg_quality_spinBox.setVisible(True)
elif selected_format == "png": elif selected_format == "png":
self.ui.png_quality_spinBox.setVisible(True) self.ui.png_quality_spinBox.setVisible(True)
def define_settings(self): def browse_input_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
if folder:
self.ui.input_path.setText(folder)
def browse_output_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Output Folder")
if folder:
self.ui.output_path.setText(folder)
def change_statusbar(self, msg, timeout = 500):
self.ui.statusBar.showMessage(msg, timeout)
def handle_qprogressbar(self, current, total):
progress = int((100 / total) * current)
self.ui.progressBar.setValue(progress)
def check_options(self):
try:
self.settings["input_folder"] = self.ui.input_path.text()
self.settings["output_folder"] = self.ui.output_path.text()
self.settings["file_format"] = self.ui.image_type.currentText()
self.settings["jpg_quality"] = int(self.ui.jpg_quality_spinBox.text())
self.settings["png_compression"] = int(self.ui.png_quality_spinBox.text())
self.settings["invert_image_order"] = self.ui.revert_checkbox.isChecked()
self.settings["grayscale"] = self.ui.grayscale_checkBox.isChecked()
self.settings["copy_exif"] = self.ui.exif_copy_checkBox.isChecked()
self.settings["own_exif"] = self.ui.exif_checkbox.isChecked()
self.settings["font_size"] = self.ui.font_size_comboBox.currentIndex() + 1
self.settings["optimize"] = self.ui.optimize_checkBox.isChecked()
self.settings["own_date"] = self.ui.add_date_checkBox.isChecked()
if self.ui.resize_checkbox.isChecked():
self.settings["resize_percentage"] = int(self.ui.resize_spinBox.text())
if self.ui.brightness_checkbox.isChecked():
self.settings["brightness_percentage"] = int(self.ui.brightness_spinBox.text())
if self.ui.contrast_checkbox.isChecked():
self.settings["contrast_percentage"] = int(self.ui.contrast_spinBox.text())
if self.ui.rename_checkbox.isChecked() and self.ui.filename.text() != "":
self.settings["new_file_names"] = self.ui.filename.text()
if self.ui.watermark_checkbox.isChecked() and self.ui.watermark_lineEdit.text() != "":
self.settings["watermark"] = self.ui.watermark_lineEdit.text()
if self.settings["own_exif"]:
self.selected_exif = self.collect_selected_exif()
if self.ui.add_date_checkBox.isChecked():
self.selected_exif["date_time_original"] = self.get_date()
if self.ui.gps_checkBox.isChecked():
self.settings["gps"] = [self.ui.lat_lineEdit.text(), self.ui.long_lineEdit.text()]
else:
self.settings["gps"] = False
except Exception as e:
print(f"Whoops: {e}")
def get_date(self):
date_input = self.ui.dateEdit.date().toString("yyyy-MM-dd")
new_date = datetime.strptime(date_input, "%Y-%m-%d")
return new_date.strftime("%Y:%m:%d 00:00:00")
def collect_selected_exif(self):
user_data = {}
user_data["make"] = self.ui.make_comboBox.currentText()
user_data["model"] = self.ui.model_comboBox.currentText()
user_data["lens"] = self.ui.lens_comboBox.currentText()
user_data["iso"] = self.ui.iso_comboBox.currentText()
user_data["image_description"] = self.ui.image_description_comboBox.currentText()
user_data["user_comment"] = self.ui.user_comment_comboBox.currentText()
user_data["artist"] = self.ui.artist_comboBox.currentText()
user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText()
user_data["software"] = f"OPTIMA-35 {self.version}"
return user_data
def rebuild_ui(self):
# Define the bash script to execute
print("Rebuild function disabled")
return
bash_script = "rebuild_ui.sh"
os.system(bash_script)
def restart_app(self):
"""Restarts the application."""
self.rebuild_ui()
# chatGPT
python = sys.executable # Path to the Python interpreter
os.execv(python, [python] + sys.argv)
# core
def define_settings(self, exif_file, version):
self.name = "OPTIMA-35" self.name = "OPTIMA-35"
self.version = "0.3.2" self.version = version
self.utilities = Utilities() self.utilities = Utilities()
self.image_processor = ImageProcessor() self.image_processor = ImageProcessor()
self.exif_handler = ExifHandler() self.exif_handler = ExifHandler()
self.exif_file = exif_file
self.settings = { self.settings = {
"input_folder": None, "input_folder": None,
"output_folder": None, "output_folder": None,
@ -78,27 +235,37 @@ class Optima35QT6(QMainWindow, Ui_MainWindow):
"watermark": False, "watermark": False,
"grayscale": False, "grayscale": False,
"jpg_quality": None, "jpg_quality": None,
"png_compression": None "png_compression": None,
"font_size": None,
"optimize": False,
"gps": False
} }
self.exif_data = None self.exif_data = None
def browse_input_folder(self): def modify_timestamp_in_exif(self, exif_data, filename):
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder") """"Takes exif data and adjust time to fit ending of filename."""
if folder: try:
self.ui.input_path.setText(folder) last_tree = filename[-3:len(filename)]
total_seconds = int(re.sub(r'\D+', '', last_tree))
minutes = total_seconds // 60
seconds = total_seconds % 60
time = datetime.strptime(exif_data["date_time_original"], "%Y:%m:%d %H:%M:%S") # change date time string back to an time object for modification
new_time = time.replace(hour=12, minute=minutes, second=seconds)
exif_data["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S")
return exif_data
def browse_output_folder(self): except ValueError:
folder = QFileDialog.getExistingDirectory(self, "Select Output Folder") print("Modifying date went wrong, exiting...")
if folder: exit()
self.ui.output_path.setText(folder)
def process(self): def process(self):
self.check_options() self.ui.start_button.setEnabled(False)
if os.path.exists(self.settings["input_folder"]) and os.path.exists(self.settings["output_folder"]): self.ui.restart_button.setEnabled(False)
print(self.settings) self.check_options() # Get all user selected data
else: input_folder_valid = os.path.exists(self.settings["input_folder"])
print(self.settings) output_folder_valid = os.path.exists(self.settings["output_folder"])
QMessageBox.warning(self, "Warning", "Input and/or output folder invalid...") if not input_folder_valid or not output_folder_valid:
QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
return return
input_folder = self.settings["input_folder"] input_folder = self.settings["input_folder"]
@ -127,8 +294,7 @@ class Optima35QT6(QMainWindow, Ui_MainWindow):
image = processed_img, percent = self.settings["resize_percentage"] image = processed_img, percent = self.settings["resize_percentage"]
) )
if self.settings["watermark"] != False: if self.settings["watermark"] != False:
processed_img = self.image_processor.add_watermark(processed_img, self.settings["watermark"]) processed_img = self.image_processor.add_watermark(processed_img, self.settings["watermark"], int(self.settings["font_size"]))
if self.settings["grayscale"] != False: # There is a problem, if we first to grayscale and then watermark it braeks if self.settings["grayscale"] != False: # There is a problem, if we first to grayscale and then watermark it braeks
processed_img = self.image_processor.grayscale(processed_img) processed_img = self.image_processor.grayscale(processed_img)
if self.settings["brightness_percentage"] != False: # Does the order of brightness and contrast matter? if self.settings["brightness_percentage"] != False: # Does the order of brightness and contrast matter?
@ -136,7 +302,17 @@ class Optima35QT6(QMainWindow, Ui_MainWindow):
if self.settings["contrast_percentage"] != False: # Does the order of brightness and contrast matter? if self.settings["contrast_percentage"] != False: # Does the order of brightness and contrast matter?
processed_img = self.image_processor.change_contrast(processed_img, self.settings["contrast_percentage"]) processed_img = self.image_processor.change_contrast(processed_img, self.settings["contrast_percentage"])
if self.settings["copy_exif"] != False: if self.settings["own_exif"] != False:
selected_exif = self.selected_exif
if "date_time_original" in self.selected_exif:
selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name)
exif_data = self.exif_handler.build_exif_dict(selected_exif, self.image_processor.get_image_size(processed_img))
if self.settings["gps"] != False:
latitude = float(self.settings["gps"][0])
longitude = float(self.settings["gps"][1])
exif_data = self.exif_handler.add_geolocation_to_exif(exif_data, latitude, longitude)
elif self.settings["copy_exif"] == True:
# When copying exif from original, make sure to change Piexel X & Y Dimension to fit new size # When copying exif from original, make sure to change Piexel X & Y Dimension to fit new size
try: try:
og_exif = self.exif_handler.get_exif_info(img) og_exif = self.exif_handler.get_exif_info(img)
@ -144,10 +320,11 @@ class Optima35QT6(QMainWindow, Ui_MainWindow):
exif_data = og_exif exif_data = og_exif
except Exception: except Exception:
# If an error happends it is because the picture does not have exif data # If an error happends it is because the picture does not have exif data
print("Copying EXIF data selected, but no EXIF data is available in the original image file.") self.change_statusbar("Copying EXIF data selected, but no EXIF data is available in the original image file.")
exif_data = None exif_data = None
elif self.settings["copy_exif"] == False: elif self.settings["copy_exif"] == False:
exif_data = None exif_data = None
self.change_statusbar(f"exif data: {exif_data}")
self.image_processor.save_image( self.image_processor.save_image(
image = processed_img, image = processed_img,
@ -156,17 +333,16 @@ class Optima35QT6(QMainWindow, Ui_MainWindow):
file_type = self.settings["file_format"], file_type = self.settings["file_format"],
jpg_quality = self.settings["jpg_quality"], jpg_quality = self.settings["jpg_quality"],
png_compressing = self.settings["png_compression"], png_compressing = self.settings["png_compression"],
optimize = False optimize = self.settings["optimize"]
) )
self.handle_qprogressbar(i, len(image_files)) self.handle_qprogressbar(i, len(image_files))
i += 1 i += 1
QMessageBox.information(self, "Information", "Finished") QMessageBox.information(self, "Information", "Finished")
self.ui.start_button.setEnabled(True)
self.ui.restart_button.setEnabled(True)
self.ui.progressBar.setValue(0) self.ui.progressBar.setValue(0)
def handle_qprogressbar(self, current, total):
progress = int((100 / total) * current)
self.ui.progressBar.setValue(progress)
def name_images(self, base_name, current_image, total_images, invert): def name_images(self, base_name, current_image, total_images, invert):
""""Returns name, combination of base_name and ending number.""" """"Returns name, combination of base_name and ending number."""
total_digits = len(str(total_images)) total_digits = len(str(total_images))
@ -177,39 +353,9 @@ class Optima35QT6(QMainWindow, Ui_MainWindow):
ending = f"{ending_number:0{total_digits}}" ending = f"{ending_number:0{total_digits}}"
return f"{base_name}_{ending}" return f"{base_name}_{ending}"
def check_options(self):
try:
self.settings["input_folder"] = self.ui.input_path.text()
self.settings["output_folder"] = self.ui.output_path.text()
self.settings["file_format"] = self.ui.image_type.currentText()
self.settings["jpg_quality"] = int(self.ui.jpg_quality_spinBox.text())
self.settings["png_compression"] = int(self.ui.png_quality_spinBox.text())
self.settings["invert_image_order"] = self.ui.revert_checkbox.isChecked()
self.settings["grayscale"] = self.ui.grayscale_checkBox.isChecked()
self.settings["copy_exif"] = self.ui.copy_exif_checkBox.isChecked()
self.settings["own_exif"] = self.ui.exif_checkbox.isChecked()
if self.ui.resize_checkbox.isChecked():
self.settings["resize_percentage"] = int(self.ui.resize_spinBox.text())
if self.ui.brightness_checkbox.isChecked():
self.settings["brightness_percentage"] = int(self.ui.brightness_spinBox.text())
if self.ui.contrast_checkbox.isChecked():
self.settings["contrast_percentage"] = int(self.ui.contrast_spinBox.text())
if self.ui.rename_checkbox.isChecked() and self.ui.filename.text() != "":
self.settings["new_file_names"] = self.ui.filename.text()
if self.ui.watermark_checkbox.isChecked() and self.ui.watermark_lineEdit.text() != "":
self.settings["watermark"] = self.ui.watermark_lineEdit.text()
except Exception as e:
print(f"Whoops: {e}")
if __name__ == "__main__": if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
window = Optima35QT6() window = Optima35QT6(exif_file = "local_files/exif.yaml", version = "0.3.4")
window.show() window.show()
app.exec() app.exec()