From 40b7ca0782ab95d55d0a5600bfe118b04fcc2ccd Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Tue, 31 Dec 2024 12:06:11 +0100 Subject: [PATCH] Main starts either GUI or TUI. UI split from optima35. --- gui.py | 17 ++- main.py | 15 ++- tui.py | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+), 7 deletions(-) create mode 100644 tui.py diff --git a/gui.py b/gui.py index aeaccd0..710dfb6 100644 --- a/gui.py +++ b/gui.py @@ -2,7 +2,7 @@ import sys import os from datetime import datetime -from optima35 import OPTIMA35 +from optima.optima35 import OPTIMA35 from utils.utility import Utilities from ui.main_window import Ui_MainWindow from ui.exif_handler_window import ExifEditor @@ -37,6 +37,9 @@ class Optima35GUI(QMainWindow, Ui_MainWindow): self.default_ui_layout() self.define_gui_interaction() + if exif_file == "config/exif_example.yaml": + self.change_statusbar("Using example exif...", 10000) + def default_ui_layout(self): self.ui.png_quality_spinBox.setVisible(False) @@ -254,11 +257,19 @@ class Optima35GUI(QMainWindow, Ui_MainWindow): python = sys.executable # Path to the Python interpreter os.execv(python, [python] + sys.argv) -def main(exif_file="local_files/exif.yaml"): +def main(exif_file): app = QtWidgets.QApplication(sys.argv) window = Optima35GUI(exif_file=exif_file) window.show() app.exec() if __name__ == "__main__": - main() + if os.path.isfile("config/exif.yaml"): + exif_file = "config/exif.yaml" + print("Fall back to exif example file...") + elif os.path.isfile("config/exif_example.yaml"): + exif_file = "config/exif_example.yaml" + else: + print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...") + exit() + main(exif_file) diff --git a/main.py b/main.py index cec61da..3c79c78 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,6 @@ -import sys import os -import subprocess from argparse import ArgumentParser -exif_file = "local_files/exif.yaml" + # Mainly from ChatGPT def check_pyside_installed(): try: @@ -17,7 +15,7 @@ def start_gui(): def start_tui(): import tui - tui.main(exif_file) + tui.main(exif_file, tui_settings_file) def main(): parser = ArgumentParser(description="Start the Optima35 application.") @@ -43,4 +41,13 @@ def main(): start_tui() if __name__ == "__main__": + if os.path.isfile("config/exif.yaml"): + exif_file = "config/exif.yaml" + elif os.path.isfile("config/exif_example.yaml"): + exif_file = "config/exif_example.yaml" + print("Fall back to exif example file...") + else: + print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...") + exit() + tui_settings_file = "config/tui_settings.yaml" main() diff --git a/tui.py b/tui.py new file mode 100644 index 0000000..8050ad7 --- /dev/null +++ b/tui.py @@ -0,0 +1,327 @@ +import os +from datetime import datetime +# my packages +from optima.optima35 import OPTIMA35 +from utils.utility import Utilities +from ui.simple_tui import SimpleTUI + +class Optima35TUI(): + def __init__(self, exif_file, settings_file): + self.o = OPTIMA35() + self.u = Utilities() + self.tui = SimpleTUI() + self.exif_file = exif_file + self.exif_data = self.u.read_yaml(exif_file) + self.setting_file = settings_file + self.settings = { + "input_folder": None, + "output_folder": None, + "file_format": None, + "resize": None, + "copy_exif": None, + "contrast": None, + "brightness": None, + "new_file_names": None, + "invert_image_order": False, + "watermark": None, + "gps": None, + "modifications": [], + } + self.settings_to_save = [ + "resize", + "jpg_quality", + "png_compression", + "optimize", + "contrast", + "brightness" + ] + + def process(self): + if "Change EXIF" in self.settings["modifications"]: + self.selected_exif = self.collect_exif_data() + self.check_options() # Get all user selected data + input_folder_valid = os.path.exists(self.o.settings["input_folder"]) + output_folder_valid = os.path.exists(self.o.settings["output_folder"]) + if not input_folder_valid or not output_folder_valid: + print("Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...") + return + + input_folder = self.o.settings["input_folder"] + output_folder = self.o.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.o.settings["new_file_names"] != False: + image_name = self.o.name_images(self.o.settings["new_file_names"], i, len(image_files), self.o.settings["invert_image_order"]) + else: + image_name = os.path.splitext(image_file)[0] + output_path = os.path.join(output_folder, image_name) + self.o.process(input_path, output_path) + self.u.progress_bar(i, len(image_files)) + i += 1 + + def check_options(self): + try: + if "Resize image" in self.settings["modifications"]: + self.o.settings["resize"] = self.settings["resize"] + else: + self.o.settings["resize"] = False + + if "Convert to grayscale" in self.settings["modifications"]: + self.o.settings["grayscale"] = True + else: + self.o.settings["grayscale"] = False + + if "Change contrast" in self.settings["modifications"]: + self.o.settings["contrast"] = self.settings["contrast"] + else: + self.o.settings["contrast"] = False + + if "Change brightness" in self.settings["modifications"]: + self.o.settings["brightness"] = self.settings["brightness"] + else: + self.o.settings["brightness"] = False + + if "Rename images" in self.settings["modifications"]: + self.o.settings["new_file_names"] = self.settings["new_file_names"] + else: + self.o.settings["new_file_names"] = False + + if "Invert image order" in self.settings["modifications"]: + self.o.settings["invert_image_order"] = True + else: + self.o.settings["invert_image_order"] = False + + if "Add Watermark" in self.settings["modifications"]: + self.o.settings["watermark"] = self.settings["watermark"] + else: + self.o.settings["watermark"] = False + + self.o.settings["optimize"] = self.settings["optimize"] + self.o.settings["png_compression"] = self.settings["png_compression"] + self.o.settings["jpg_quality"] = self.settings["jpg_quality"] + + self.o.settings["input_folder"] = self.settings["input_folder"] + self.o.settings["output_folder"] = self.settings["output_folder"] + self.o.settings["file_format"] = self.settings["file_format"] + self.o.settings["font_size"] = 2 # need to add option to select size + + self.o.settings["copy_exif"] = self.settings["copy_exif"] + + if "Change EXIF" in self.settings["modifications"]: #missing + self.o.selected_exif = self.selected_exif # + self.o.settings["own_exif"] = True + if self.settings["gps"] != None: + self.o.settings["gps"] = self.settings["gps"] + else: + self.o.settings["gps"] = False + else: + self.o.settings["own_exif"] = False + + except Exception as e: + print(f"Whoops: {e}") + + def load_or_ask_settings(self): + """Load settings from a YAML file or ask the user if not present or incomplete.""" + try: + if self.read_settings(self.settings_to_save): + for item in self.settings_to_save: + print(f"{item}: {self.settings[item]}") + use_saved = self.tui.yes_no_menu("Use these settings?") + if use_saved: + return + else: + print("No settings found...") + self.ask_for_settings() + except Exception as e: + print(f"Error: {e}") + self.ask_for_settings() + + def ask_for_settings(self): + print("Asking for new settings...\n") + self.settings["resize"] = self.take_input_and_validate(question = "Default resize percentage (below 100 downscale, above upscale): ", accepted_type = int, min_value = 10, max_value = 200) + self.settings["contrast"] = self.take_input_and_validate(question = "Default contrast percentage (negative = decrease, positive = increase): ", accepted_type = int, min_value = -100, max_value = 100) + self.settings["brightness"] = self.take_input_and_validate(question = "Default brighness percentage (negative = decrease, positive = increase): ", accepted_type = int, min_value = -100, max_value = 100) + self.settings["jpg_quality"] = self.take_input_and_validate(question = "JPEG quality (1-100, 80 default): ", accepted_type = int, min_value = 1, max_value = 100) + self.settings["png_compression"] = self.take_input_and_validate(question = "PNG compression level (0-9, 6 default): ", accepted_type = int, min_value = 0, max_value = 9) + self.settings["optimize"] = self.tui.yes_no_menu("Optimize images i.e. compressing?") + + self.write_settings(self.settings_to_save) + + def write_settings(self, keys_to_save): + """"Write self.setting, but only specific values""" + keys = keys_to_save + filtered_settings = {key: self.settings[key] for key in keys if key in self.settings} + self.u.write_yaml(self.setting_file, filtered_settings) + print("New settings saved successfully.") + + def read_settings(self, keys_to_load): + """ + Read settings from the settings file and update self.settings + with the values for specific keys without overwriting existing values. + """ + # First draft by ChatGPT, adjusted to fit my needs. + keys = keys_to_load + if os.path.exists(self.setting_file): + loaded_settings = self.u.read_yaml(self.setting_file) + for key in keys: + if key in loaded_settings: + self.settings[key] = loaded_settings[key] + print("Settings loaded successfully.") + return True + else: + print("Settings file empty.") + return False + + def collect_exif_data(self): + """Collect EXIF data based on user input.""" + user_data = {} + fields = [ + "make", "model", "lens", "iso", "image_description", + "user_comment", "artist", "copyright_info" + ] + for field in fields: + + choise = self.tui.choose_menu(f"Enter {field.replace('_', ' ').title()}", self.exif_data[field]) + user_data[field] = choise + + user_data["software"] = f"{self.o.name} {self.o.version}" + new_date = self.get_date_input() + + if new_date: + user_data["date_time_original"] = new_date + + self.settings["gps"] = self.get_gps_input(user_data) + + return user_data + + def get_gps_input(self, test_exif): + while True: + lat = input("Enter Latitude (xx.xxxxxx): ") + if lat == "": + return False + long = input("Enter Longitude (xx.xxxxxx): ") + try: + self.o.exif_handler.add_geolocation_to_exif(test_exif, float(lat), float(long)) + return [lat, long] + except Exception: + print("Invalid GPS formate, try again...") + + def get_date_input(self): + # Partially chatGPT + while True: + date_input = input("Enter a date (yyyy-mm-dd): ") + if date_input == "": + return None # Skip if input is empty + try: + new_date = datetime.strptime(date_input, "%Y-%m-%d") + return new_date.strftime("%Y:%m:%d 00:00:00") + except ValueError: + print("Invalid date format. Please enter the date in yyyy-mm-dd format.") + + def get_user_settings(self): + """Get initial settings from the user.""" + menu_options = [ + "Resize image", + "Change EXIF", + "Convert to grayscale", + "Change contrast", + "Change brightness", + "Rename images", + "Invert image order", + "Add Watermark" + ] # new option can be added here. + + self.settings["input_folder"] = "local_files/img"#input("Enter path of input folder: ").strip() # Add: check if folder exists. + self.settings["output_folder"] = "local_files/out"#input("Enter path of output folder: ").strip() + self.settings["file_format"] = "jpg"#self.take_input_and_validate(question = "Enter export file format (jpg, png, webp): ", accepted_input = ["jpg", "png", "webp"], accepted_type = str) + self.settings["modifications"] = self.tui.multi_select_menu( + f"\n{self.o.name} v.{self.o.version} \nSelect what you want to do (esc or q to exit)", + menu_options + ) + if "Change EXIF" not in self.settings["modifications"]: + self.settings["copy_exif"] = self.tui.yes_no_menu("Do you want to copy exif info from original file?") + if "Rename images" in self.settings["modifications"]: + self.settings["new_file_names"] = input("What should be the name for the new images? ") # Need + else: + self.settings["new_file_names"] = False + if "Invert image order" in self.settings["modifications"]: + self.settings["invert_image_order"] = True + else: + self.settings["invert_image_order"] = False + if "Add Watermark" in self.settings["modifications"]: + self.settings["watermark"] = input("Enter text for watermark. ") + else: + self.settings["watermark"] = False + + os.makedirs(self.settings["output_folder"], exist_ok = True) + + def take_input_and_validate(self, question, accepted_input = None, accepted_type = str, min_value = None, max_value = None): + """ + Asks the user a question, validates the input, and ensures it matches the specified criteria. + Args: + question (str): The question to ask the user. + accepted_input (list): A list of acceptable inputs (optional for non-numeric types). + accepted_type (type): The expected type of input (e.g., str, int, float). + min_value (int/float): Minimum value for numeric inputs (optional). + max_value (int/float): Maximum value for numeric inputs (optional). + + Returns: + The validated user input. + """ + # Main layout by chatGPT, but modified. + while True: + user_input = input(question).strip() + + try: + # Convert input to the desired type + if accepted_type in [int, float]: + user_input = accepted_type(user_input) + # Validate range for numeric types + if (min_value is not None and user_input < min_value) or (max_value is not None and user_input > max_value): + print(f"Input must be between {min_value} and {max_value}.") + continue + elif accepted_type == str: + # No conversion needed for strings + user_input = str(user_input) + else: + raise ValueError(f"Unsupported type: {accepted_type}") + + # Validate against accepted inputs if provided + if accepted_input is not None and user_input not in accepted_input: + print(f"Invalid input. Must be one of: {', '.join(map(str, accepted_input))}.") + continue + + return user_input # Input is valid + + except ValueError: + print(f"Invalid input. Must be of type {accepted_type.__name__}.") + + def run(self): + """Run the main program.""" + self.load_or_ask_settings() + self.get_user_settings() + self.process() + print("Done") + + + +def main(exif_file, config_file): + app = Optima35TUI(exif_file, config_file) + app.run() + +if __name__ == "__main__": + if os.path.isfile("config/exif.yaml"): + exif_file = "config/exif.yaml" + elif os.path.isfile("config/exif_example.yaml"): + exif_file = "config/exif_example.yaml" + print("Fall back to exif example file...") + else: + print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...") + exit() + main(exif_file, "config/tui_settings.yaml")