diff --git a/main.py b/main.py index c77c22c..cec61da 100644 --- a/main.py +++ b/main.py @@ -1,361 +1,46 @@ import sys import os -import re -import time import subprocess -from datetime import datetime -from utils.utility import Utilities -from utils.image_handler import ImageProcessor, ExifHandler +from argparse import ArgumentParser +exif_file = "local_files/exif.yaml" +# Mainly from ChatGPT +def check_pyside_installed(): + try: + import PySide6 # Replace with PySide2 if using that version + return True + except ImportError: + return False -from ui.main_window import Ui_MainWindow -from ui.exif_handler_window import ExifEditor +def start_gui(): + import gui + gui.main(exif_file) -from PySide6 import QtWidgets -from PySide6.QtWidgets import ( - QMessageBox, - QApplication, - QMainWindow, - QWidget, - QVBoxLayout, - QLabel, - QLineEdit, - QPushButton, - QCheckBox, - QFileDialog, - QHBoxLayout, - QSpinBox, -) +def start_tui(): + import tui + tui.main(exif_file) -class Optima35QT6(QMainWindow, Ui_MainWindow): - def __init__(self, exif_file, version): - super(Optima35QT6, self).__init__() - self.ui = Ui_MainWindow() - self.ui.setupUi(self) +def main(): + parser = ArgumentParser(description="Start the Optima35 application.") + parser.add_argument("--tui", action="store_true", help="Start in terminal UI mode.") + args = parser.parse_args() - self.define_settings(exif_file, version) - self.setWindowTitle(f"{self.name} v{self.version}") - self.default_ui_layout() - self.define_gui_interaction() -# GUI - def default_ui_layout(self): - self.ui.png_quality_spinBox.setVisible(False) - - def define_gui_interaction(self): - self.ui.input_folder_button.clicked.connect(self.browse_input_folder) - self.ui.output_folder_button.clicked.connect(self.browse_output_folder) - self.ui.start_button.clicked.connect(self.process) - 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): - """Update visibility of quality settings based on selected format.""" - # ChatGPT - selected_format = self.ui.image_type.currentText() - # Hide all quality settings - self.ui.png_quality_spinBox.setVisible(False) - self.ui.jpg_quality_spinBox.setVisible(False) - # Show relevant settings - if selected_format == "jpg": - self.ui.jpg_quality_spinBox.setVisible(True) - elif selected_format == "webp": - self.ui.jpg_quality_spinBox.setVisible(True) - elif selected_format == "png": - self.ui.png_quality_spinBox.setVisible(True) - - 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") + if args.tui: + print("Starting TUI...") + start_tui() 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.version = version - self.utilities = Utilities() - self.image_processor = ImageProcessor() - self.exif_handler = ExifHandler() - self.exif_file = exif_file - self.settings = { - "input_folder": None, - "output_folder": None, - "file_format": None, - "resize_percentage": False, - "contrast_percentage": False, - "brightness_percentage": False, - "new_file_names": False, - "invert_image_order": False, - "copy_exif": False, - "own_exif": False, - "watermark": False, - "grayscale": False, - "jpg_quality": None, - "png_compression": None, - "font_size": None, - "optimize": False, - "gps": False - } - self.exif_data = None - - def modify_timestamp_in_exif(self, exif_data, filename): - """"Takes exif data and adjust time to fit ending of filename.""" - try: - 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 - - except ValueError: - print("Modifying date went wrong, exiting...") - exit() - - def process(self): - self.ui.start_button.setEnabled(False) - self.ui.restart_button.setEnabled(False) - self.check_options() # Get all user selected data - input_folder_valid = os.path.exists(self.settings["input_folder"]) - output_folder_valid = os.path.exists(self.settings["output_folder"]) - 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 - - input_folder = self.settings["input_folder"] - output_folder = self.settings["output_folder"] - - image_files = [ - f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp")) - ] - - i = 1 - for image_file in image_files: - input_path = os.path.join(input_folder, image_file) - - if self.settings["new_file_names"] != False: - image_name = self.name_images(self.settings["new_file_names"], i, len(image_files), self.settings["invert_image_order"]) - else: - image_name = os.path.splitext(image_file)[0] - - output_path = os.path.join(output_folder, image_name) - - with self.image_processor.open_image(input_path) as img: - processed_img = img - - if self.settings["resize_percentage"] != False: - processed_img = self.image_processor.resize_image( - image = processed_img, percent = self.settings["resize_percentage"] - ) - if self.settings["watermark"] != False: - 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 - processed_img = self.image_processor.grayscale(processed_img) - if self.settings["brightness_percentage"] != False: # Does the order of brightness and contrast matter? - processed_img = self.image_processor.change_brightness(processed_img, self.settings["brightness_percentage"]) - 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"]) - - 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 - try: - og_exif = self.exif_handler.get_exif_info(img) - og_exif["Exif"][40962], og_exif["Exif"][40963] = self.image_processor.get_image_size(processed_img) - exif_data = og_exif - except Exception: - # If an error happends it is because the picture does not have exif data - self.change_statusbar("Copying EXIF data selected, but no EXIF data is available in the original image file.") - exif_data = None - elif self.settings["copy_exif"] == False: - exif_data = None - self.change_statusbar(f"exif data: {exif_data}") - - self.image_processor.save_image( - image = processed_img, - path = output_path, - exif_data = exif_data, - file_type = self.settings["file_format"], - jpg_quality = self.settings["jpg_quality"], - png_compressing = self.settings["png_compression"], - optimize = self.settings["optimize"] - ) - - self.handle_qprogressbar(i, len(image_files)) - i += 1 - QMessageBox.information(self, "Information", "Finished") - self.ui.start_button.setEnabled(True) - self.ui.restart_button.setEnabled(True) - self.ui.progressBar.setValue(0) - - def name_images(self, base_name, current_image, total_images, invert): - """"Returns name, combination of base_name and ending number.""" - total_digits = len(str(total_images)) - if invert: - ending_number = total_images - (current_image - 1) - else: - ending_number = current_image - ending = f"{ending_number:0{total_digits}}" - return f"{base_name}_{ending}" + # Check OS and start GUI if on Windows + if os.name == "nt": + print("Detected Windows. Starting GUI...") + start_gui() + else: + # Non-Windows: Check if PySide is installed + if check_pyside_installed(): + print("PySide detected. Starting GUI...") + start_gui() + else: + print("PySide is not installed. Falling back to TUI...") + start_tui() if __name__ == "__main__": - app = QtWidgets.QApplication(sys.argv) - - window = Optima35QT6(exif_file = "local_files/exif.yaml", version = "0.3.4") - window.show() - app.exec() + main()