From 1b255ab42493a5dce7ead54e3d863fbd1278c2f9 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 11:27:17 +0100 Subject: [PATCH 01/12] Adjust for new folder stucture. --- .gitignore | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index b6668dc..3e21bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -local_files/ -debug.* -debug_log/ +test/ +dist/ .ropeproject/ __pycache__/ -config/tui_settings.yaml -config/exif.yaml From a97cc3e2ee0a1482e3a7011c41997cabcd7884e1 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:12:41 +0100 Subject: [PATCH 02/12] Moved to src --- gui.py | 282 ------------ main.py | 44 -- tui.py | 329 -------------- ui/__init__.py | 0 ui/exif_handler_window.py | 87 ---- ui/main_window.py | 559 ----------------------- ui/main_window.ui | 919 -------------------------------------- ui/simple_dialog.py | 30 -- ui/simple_tui.py | 60 --- utils/__init__.py | 0 utils/utility.py | 117 ----- 11 files changed, 2427 deletions(-) delete mode 100644 gui.py delete mode 100644 main.py delete mode 100644 tui.py delete mode 100644 ui/__init__.py delete mode 100644 ui/exif_handler_window.py delete mode 100644 ui/main_window.py delete mode 100644 ui/main_window.ui delete mode 100644 ui/simple_dialog.py delete mode 100644 ui/simple_tui.py delete mode 100644 utils/__init__.py delete mode 100644 utils/utility.py diff --git a/gui.py b/gui.py deleted file mode 100644 index 9f997e7..0000000 --- a/gui.py +++ /dev/null @@ -1,282 +0,0 @@ -import sys -import os -from datetime import datetime - -from optima35.core import OptimaManager -from utils.utility import Utilities -from ui.main_window import Ui_MainWindow -from ui.exif_handler_window import ExifEditor -from ui.simple_dialog import SimpleDialog # Import the SimpleDialog class - -from PySide6 import QtWidgets -from PySide6.QtWidgets import ( - QMessageBox, - QApplication, - QMainWindow, - QWidget, - QVBoxLayout, - QLabel, - QLineEdit, - QPushButton, - QCheckBox, - QFileDialog, - QHBoxLayout, - QSpinBox, -) - -class OptimaLab35(QMainWindow, Ui_MainWindow): - def __init__(self): - super(OptimaLab35, self).__init__() - self.name = "OptimaLab35" - self.version = "0.0.1" - self.ui = Ui_MainWindow() - self.ui.setupUi(self) - self.o = OptimaManager() - self.u = Utilities() - self.u.program_configs() - self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml") - self.available_exif_data = None - self.settings = {} - self.setWindowTitle(f"{self.name} v{self.version}") - self._default_ui_layout() - self._define_gui_interaction() - - self.sd = SimpleDialog() - self._change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000) - - 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.actionInfo.triggered.connect(self._info_window) - - def _info_window(self): - self.sd.show_dialog(f"{self.name} v{self.version}", f"{self.name} is a GUI for {self.o.name} (v{self.o.version})") - - def _process(self): - self.ui.start_button.setEnabled(False) - self._update_settings() # 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.u.append_number_to_name(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) - - self.o.process_image( - image_input_file = input_path, - image_output_file = output_path, - file_type = self.settings["file_format"], - quality = self.settings["jpg_quality"], - compressing = self.settings["png_compression"], - optimize = self.ui.optimize_checkBox.isChecked(), - resize = self.settings["resize"], - watermark = self.settings["watermark"], - font_size = self.settings["font_size"], - grayscale = self.settings["grayscale"], - brightness = self.settings["brightness"], - contrast = self.settings["contrast"], - dict_for_exif = self.user_selected_exif, - gps = self.settings["gps"], - copy_exif = self.settings["copy_exif"]) - self._handle_qprogressbar(i, len(image_files)) - i += 1 - - QMessageBox.information(self, "Information", "Finished") - self.ui.start_button.setEnabled(True) - self.ui.progressBar.setValue(0) - - def _open_exif_editor(self): - """Open the EXIF Editor.""" - self.exif_editor = ExifEditor(self.available_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.""" - 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.available_exif_data = self.u.read_yaml(self.exif_file) - elif do == "write": - self.u.write_yaml(self.exif_file, self.available_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.available_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 _get_checkbox_value(self, checkbox, default=None): - """Helper function to get the value of a checkbox or a default value.""" - return checkbox.isChecked() if checkbox else default - - def _get_spinbox_value(self, spinbox, default=None): - """Helper function to get the value of a spinbox and handle empty input.""" - return int(spinbox.text()) if spinbox.text() else default - - def _get_combobox_value(self, combobox, default=None): - """Helper function to get the value of a combobox.""" - return combobox.currentIndex() + 1 if combobox.currentIndex() != -1 else default - - def _get_text_value(self, lineedit, default=None): - """Helper function to get the value of a text input field.""" - return lineedit.text() if lineedit.text() else default - - def _get_selected_exif(self): - """Collect selected EXIF data and handle date and GPS if necessary.""" - selected_exif = self._collect_selected_exif() if self.ui.exif_checkbox.isChecked() else None - if selected_exif: - if self.ui.add_date_checkBox.isChecked(): - 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"] = None - return selected_exif - - def _update_settings(self): - """Update .settings from all GUI elements.""" - # General settings - self.settings["input_folder"] = self._get_text_value(self.ui.input_path) - self.settings["output_folder"] = self._get_text_value(self.ui.output_path) - self.settings["file_format"] = self.ui.image_type.currentText() - self.settings["jpg_quality"] = self._get_spinbox_value(self.ui.jpg_quality_spinBox) - self.settings["png_compression"] = self._get_spinbox_value(self.ui.png_quality_spinBox) - self.settings["invert_image_order"] = self._get_checkbox_value(self.ui.revert_checkbox) - self.settings["grayscale"] = self._get_checkbox_value(self.ui.grayscale_checkBox) - self.settings["copy_exif"] = self._get_checkbox_value(self.ui.exif_copy_checkBox) - self.settings["own_exif"] = self._get_checkbox_value(self.ui.exif_checkbox) - self.settings["font_size"] = self._get_combobox_value(self.ui.font_size_comboBox) - self.settings["optimize"] = self._get_checkbox_value(self.ui.optimize_checkBox) - self.settings["own_date"] = self._get_checkbox_value(self.ui.add_date_checkBox) - - # Conditional settings with logic - self.settings["resize"] = self._get_spinbox_value(self.ui.resize_spinBox) if self.ui.resize_checkbox.isChecked() else None - self.settings["brightness"] = self._get_spinbox_value(self.ui.brightness_spinBox) if self.ui.brightness_checkbox.isChecked() else None - self.settings["contrast"] = self._get_spinbox_value(self.ui.contrast_spinBox) if self.ui.contrast_checkbox.isChecked() else None - - self.settings["new_file_names"] = self._get_text_value(self.ui.filename, False) if self.ui.rename_checkbox.isChecked() else False - self.settings["watermark"] = self._get_text_value(self.ui.watermark_lineEdit) if self.ui.watermark_checkbox.isChecked() else None - - # Handle EXIF data selection - if self.settings["own_exif"]: - self.user_selected_exif = self._get_selected_exif() - else: - self.user_selected_exif = None - self.settings["gps"] = None - - 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"{self.o.name} {self.o.version}" - return user_data - -def main(): - app = QtWidgets.QApplication(sys.argv) - window = OptimaLab35() - window.show() - app.exec() - -if __name__ == "__main__": - main() diff --git a/main.py b/main.py deleted file mode 100644 index afa2c71..0000000 --- a/main.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -from argparse import ArgumentParser - -# Mainly from ChatGPT -def check_pyside_installed(): - try: - import PySide6 # Replace with PySide2 if using that version - return True - except ImportError: - return False - -def start_gui(): - import gui - gui.main() - -def start_tui(): - import tui - tui.main() - -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() - - if args.tui: - print("Starting TUI...") - start_tui() - return - - # 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__": - main() diff --git a/tui.py b/tui.py deleted file mode 100644 index f1488ed..0000000 --- a/tui.py +++ /dev/null @@ -1,329 +0,0 @@ -import os -from datetime import datetime -# my packages -from optima35.core import OptimaManager -from utils.utility import Utilities -from ui.simple_tui import SimpleTUI - -class Optima35TUI(): - def __init__(self): - self.name = "OptimaLab35-lite" - self.version = "0.0.1" - self.o = OptimaManager() - self.u = Utilities() - self.tui = SimpleTUI() - self.u.program_configs() - self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml") - self.available_exif_data = self.u.read_yaml(self.exif_file) - self.setting_file = os.path.expanduser("~/.config/OptimaLab35/tui_settings.yaml") - 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): - 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: - print("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.u.append_number_to_name(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) - self.o.process_image( - image_input_file = input_path, - image_output_file = output_path, - file_type = self.settings["file_format"], - quality = self.settings["jpg_quality"], - compressing = self.settings["png_compression"], - optimize = self.settings["optimize"], - resize = self.settings["resize"], - watermark = self.settings["watermark"], - font_size = self.settings["font_size"], - grayscale = self.settings["grayscale"], - brightness = self.settings["brightness"], - contrast = self.settings["contrast"], - dict_for_exif = self.selected_exif, - gps = self.settings["gps"], - copy_exif = self.settings["copy_exif"]) - self.u.progress_bar(i, len(image_files)) - i += 1 - - def _check_options(self): - try: - if "Resize image" in self.settings["modifications"]: - self.settings["resize"] = self.settings["resize"] - else: - self.settings["resize"] = None - - if "Convert to grayscale" in self.settings["modifications"]: - self.settings["grayscale"] = True - else: - self.settings["grayscale"] = False - - if "Change contrast" in self.settings["modifications"]: - self.settings["contrast"] = self.settings["contrast"] - else: - self.settings["contrast"] = None - - if "Change brightness" in self.settings["modifications"]: - self.settings["brightness"] = self.settings["brightness"] - else: - self.settings["brightness"] = None - - if "Rename images" in self.settings["modifications"]: - self.settings["new_file_names"] = self.settings["new_file_names"] - 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"] = self.settings["watermark"] - else: - self.settings["watermark"] = None - - self.settings["optimize"] = self.settings["optimize"] - self.settings["png_compression"] = self.settings["png_compression"] - self.settings["jpg_quality"] = self.settings["jpg_quality"] - - self.settings["input_folder"] = self.settings["input_folder"] - self.settings["output_folder"] = self.settings["output_folder"] - self.settings["file_format"] = self.settings["file_format"] - self.settings["font_size"] = 2 # need to add option to select size - - self.settings["copy_exif"] = self.settings["copy_exif"] - - if "Change EXIF" in self.settings["modifications"]: #missing - self.selected_exif = self._collect_exif_data() # - else: - self.selected_exif = None - - 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") - print(f"Settings for {self.name} v{self.version} will be saved {self.setting_file}...") - 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.""" - print(f"Exif file can be found {self.exif_file}...") - 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.available_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 None - 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"] = input("Enter path of input folder: ").strip() # Add: check if folder exists. - self.settings["output_folder"] = input("Enter path of output folder: ").strip() - self.settings["file_format"] = 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.name} v{self.version} for {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(): - app = Optima35TUI() - app.run() - -if __name__ == "__main__": - main() diff --git a/ui/__init__.py b/ui/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ui/exif_handler_window.py b/ui/exif_handler_window.py deleted file mode 100644 index 8564cba..0000000 --- a/ui/exif_handler_window.py +++ /dev/null @@ -1,87 +0,0 @@ -from PySide6.QtCore import Signal -from PySide6.QtWidgets import ( - QMainWindow, QWidget, QVBoxLayout, QComboBox, QListWidget, - QLineEdit, QHBoxLayout, QPushButton, QMessageBox -) -# By ChatGPT -class ExifEditor(QMainWindow): - # Signal to emit the updated EXIF data - exif_data_updated = Signal(dict) - - def __init__(self, exif_data): - super().__init__() - self.exif_data = exif_data - self.current_key = None - - self.setWindowTitle("EXIF Editor") - self.resize(400, 300) - - # Main widget and layout - main_widget = QWidget() - main_layout = QVBoxLayout() - main_widget.setLayout(main_layout) - self.setCentralWidget(main_widget) - - # ComboBox to select lists - self.combo_box = QComboBox() - self.combo_box.addItems(self.exif_data.keys()) - self.combo_box.currentTextChanged.connect(self.load_list) - main_layout.addWidget(self.combo_box) - - # List widget to display items - self.list_widget = QListWidget() - main_layout.addWidget(self.list_widget) - - # Line edit for adding items - self.line_edit = QLineEdit() - self.line_edit.setPlaceholderText("Enter new item...") - main_layout.addWidget(self.line_edit) - - # Buttons: Add, Delete, Cancel - button_layout = QHBoxLayout() - self.add_button = QPushButton("Add") - self.add_button.clicked.connect(self.add_item) - self.delete_button = QPushButton("Delete") - self.delete_button.clicked.connect(self.delete_item) - self.cancel_button = QPushButton("Close") - self.cancel_button.clicked.connect(self.close_editor) - - button_layout.addWidget(self.add_button) - button_layout.addWidget(self.delete_button) - button_layout.addWidget(self.cancel_button) - main_layout.addLayout(button_layout) - - # Load the first list by default - self.load_list(self.combo_box.currentText()) - - def load_list(self, key): - """Load the selected list into the list widget.""" - self.current_key = key - self.list_widget.clear() - if key in self.exif_data: - self.list_widget.addItems(self.exif_data[key]) - - def add_item(self): - """Add a new item to the selected list.""" - new_item = self.line_edit.text().strip() - if new_item: - self.exif_data[self.current_key].append(new_item) - self.list_widget.addItem(new_item) - self.line_edit.clear() - else: - QMessageBox.warning(self, "Warning", "Cannot add an empty item.") - - def delete_item(self): - """Delete the selected item from the list.""" - selected_item = self.list_widget.currentItem() - if selected_item: - item_text = selected_item.text() - self.exif_data[self.current_key].remove(item_text) - self.list_widget.takeItem(self.list_widget.row(selected_item)) - else: - QMessageBox.warning(self, "Warning", "No item selected to delete.") - - def close_editor(self): - """Emit the updated exif_data and close the editor.""" - self.exif_data_updated.emit(self.exif_data) - self.close() diff --git a/ui/main_window.py b/ui/main_window.py deleted file mode 100644 index 890eb7f..0000000 --- a/ui/main_window.py +++ /dev/null @@ -1,559 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'main_window.ui' -## -## Created by: Qt User Interface Compiler version 6.8.1 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient, - QCursor, QFont, QFontDatabase, QGradient, - QIcon, QImage, QKeySequence, QLinearGradient, - QPainter, QPalette, QPixmap, QRadialGradient, - QTransform) -from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateEdit, - QFrame, QGridLayout, QGroupBox, QHBoxLayout, - QLabel, QLineEdit, QMainWindow, QMenu, - QMenuBar, QProgressBar, QPushButton, QSizePolicy, - QSpinBox, QStatusBar, QTabWidget, QVBoxLayout, - QWidget) - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - if not MainWindow.objectName(): - MainWindow.setObjectName(u"MainWindow") - MainWindow.resize(450, 708) - MainWindow.setMinimumSize(QSize(350, 677)) - MainWindow.setMaximumSize(QSize(500, 1000)) - self.actionInfo = QAction(MainWindow) - self.actionInfo.setObjectName(u"actionInfo") - self.centralwidget = QWidget(MainWindow) - self.centralwidget.setObjectName(u"centralwidget") - self.gridLayout = QGridLayout(self.centralwidget) - self.gridLayout.setObjectName(u"gridLayout") - self.tabWidget = QTabWidget(self.centralwidget) - self.tabWidget.setObjectName(u"tabWidget") - self.tabWidget.setMaximumSize(QSize(500, 16777215)) - self.tab_1 = QWidget() - self.tab_1.setObjectName(u"tab_1") - self.verticalLayout_10 = QVBoxLayout(self.tab_1) - self.verticalLayout_10.setObjectName(u"verticalLayout_10") - self.folder_group = QFrame(self.tab_1) - self.folder_group.setObjectName(u"folder_group") - self.folder_group.setMaximumSize(QSize(400, 16777215)) - self.gridLayout_5 = QGridLayout(self.folder_group) - self.gridLayout_5.setObjectName(u"gridLayout_5") - self.input_path = QLineEdit(self.folder_group) - self.input_path.setObjectName(u"input_path") - - self.gridLayout_5.addWidget(self.input_path, 0, 0, 1, 1) - - self.output_path = QLineEdit(self.folder_group) - self.output_path.setObjectName(u"output_path") - - self.gridLayout_5.addWidget(self.output_path, 0, 1, 1, 1) - - self.input_folder_button = QPushButton(self.folder_group) - self.input_folder_button.setObjectName(u"input_folder_button") - - self.gridLayout_5.addWidget(self.input_folder_button, 1, 0, 1, 1) - - self.output_folder_button = QPushButton(self.folder_group) - self.output_folder_button.setObjectName(u"output_folder_button") - - self.gridLayout_5.addWidget(self.output_folder_button, 1, 1, 1, 1) - - - self.verticalLayout_10.addWidget(self.folder_group) - - self.groupBox = QGroupBox(self.tab_1) - self.groupBox.setObjectName(u"groupBox") - self.groupBox.setMaximumSize(QSize(400, 16777215)) - self.gridLayout_4 = QGridLayout(self.groupBox) - self.gridLayout_4.setObjectName(u"gridLayout_4") - self.resize_checkbox = QCheckBox(self.groupBox) - self.resize_checkbox.setObjectName(u"resize_checkbox") - - self.gridLayout_4.addWidget(self.resize_checkbox, 0, 0, 1, 1) - - self.resize_spinBox = QSpinBox(self.groupBox) - self.resize_spinBox.setObjectName(u"resize_spinBox") - self.resize_spinBox.setEnabled(False) - self.resize_spinBox.setMinimum(1) - self.resize_spinBox.setMaximum(200) - self.resize_spinBox.setSingleStep(1) - self.resize_spinBox.setValue(80) - - self.gridLayout_4.addWidget(self.resize_spinBox, 0, 1, 1, 1) - - self.image_type = QComboBox(self.groupBox) - self.image_type.addItem(u"jpg") - self.image_type.addItem(u"png") - self.image_type.addItem(u"webp") - self.image_type.setObjectName(u"image_type") - - self.gridLayout_4.addWidget(self.image_type, 1, 0, 1, 1) - - self.jpg_quality_spinBox = QSpinBox(self.groupBox) - self.jpg_quality_spinBox.setObjectName(u"jpg_quality_spinBox") - self.jpg_quality_spinBox.setMinimum(1) - self.jpg_quality_spinBox.setMaximum(100) - self.jpg_quality_spinBox.setValue(80) - - self.gridLayout_4.addWidget(self.jpg_quality_spinBox, 1, 1, 1, 1) - - self.png_quality_spinBox = QSpinBox(self.groupBox) - self.png_quality_spinBox.setObjectName(u"png_quality_spinBox") - self.png_quality_spinBox.setEnabled(True) - self.png_quality_spinBox.setMinimum(1) - self.png_quality_spinBox.setMaximum(9) - self.png_quality_spinBox.setValue(6) - - self.gridLayout_4.addWidget(self.png_quality_spinBox, 1, 2, 1, 1) - - self.optimize_checkBox = QCheckBox(self.groupBox) - self.optimize_checkBox.setObjectName(u"optimize_checkBox") - - self.gridLayout_4.addWidget(self.optimize_checkBox, 0, 2, 1, 1) - - self.png_quality_spinBox.raise_() - self.resize_checkbox.raise_() - self.resize_spinBox.raise_() - self.image_type.raise_() - self.jpg_quality_spinBox.raise_() - self.optimize_checkBox.raise_() - - self.verticalLayout_10.addWidget(self.groupBox) - - self.groupBox_2 = QGroupBox(self.tab_1) - self.groupBox_2.setObjectName(u"groupBox_2") - self.groupBox_2.setMaximumSize(QSize(400, 16777215)) - self.gridLayout_3 = QGridLayout(self.groupBox_2) - self.gridLayout_3.setObjectName(u"gridLayout_3") - self.watermark_lineEdit = QLineEdit(self.groupBox_2) - self.watermark_lineEdit.setObjectName(u"watermark_lineEdit") - self.watermark_lineEdit.setEnabled(False) - - self.gridLayout_3.addWidget(self.watermark_lineEdit, 3, 0, 1, 3) - - self.brightness_checkbox = QCheckBox(self.groupBox_2) - self.brightness_checkbox.setObjectName(u"brightness_checkbox") - - self.gridLayout_3.addWidget(self.brightness_checkbox, 0, 0, 1, 1) - - self.grayscale_checkBox = QCheckBox(self.groupBox_2) - self.grayscale_checkBox.setObjectName(u"grayscale_checkBox") - - self.gridLayout_3.addWidget(self.grayscale_checkBox, 0, 2, 1, 1) - - self.contrast_spinBox = QSpinBox(self.groupBox_2) - self.contrast_spinBox.setObjectName(u"contrast_spinBox") - self.contrast_spinBox.setEnabled(False) - self.contrast_spinBox.setMinimum(-100) - self.contrast_spinBox.setMaximum(100) - self.contrast_spinBox.setValue(10) - - self.gridLayout_3.addWidget(self.contrast_spinBox, 1, 1, 1, 1) - - self.watermark_checkbox = QCheckBox(self.groupBox_2) - self.watermark_checkbox.setObjectName(u"watermark_checkbox") - - self.gridLayout_3.addWidget(self.watermark_checkbox, 2, 0, 1, 1) - - self.brightness_spinBox = QSpinBox(self.groupBox_2) - self.brightness_spinBox.setObjectName(u"brightness_spinBox") - self.brightness_spinBox.setEnabled(False) - self.brightness_spinBox.setMinimum(-100) - self.brightness_spinBox.setMaximum(100) - self.brightness_spinBox.setValue(-10) - - self.gridLayout_3.addWidget(self.brightness_spinBox, 0, 1, 1, 1) - - self.contrast_checkbox = QCheckBox(self.groupBox_2) - self.contrast_checkbox.setObjectName(u"contrast_checkbox") - - self.gridLayout_3.addWidget(self.contrast_checkbox, 1, 0, 1, 1) - - self.font_size_comboBox = QComboBox(self.groupBox_2) - self.font_size_comboBox.addItem("") - self.font_size_comboBox.addItem("") - self.font_size_comboBox.addItem("") - self.font_size_comboBox.addItem("") - self.font_size_comboBox.addItem("") - self.font_size_comboBox.setObjectName(u"font_size_comboBox") - - self.gridLayout_3.addWidget(self.font_size_comboBox, 2, 1, 1, 1) - - - self.verticalLayout_10.addWidget(self.groupBox_2) - - self.rename_group = QGroupBox(self.tab_1) - self.rename_group.setObjectName(u"rename_group") - self.rename_group.setMaximumSize(QSize(400, 16777215)) - self.gridLayout_6 = QGridLayout(self.rename_group) - self.gridLayout_6.setObjectName(u"gridLayout_6") - self.rename_checkbox = QCheckBox(self.rename_group) - self.rename_checkbox.setObjectName(u"rename_checkbox") - - self.gridLayout_6.addWidget(self.rename_checkbox, 0, 0, 1, 1) - - self.revert_checkbox = QCheckBox(self.rename_group) - self.revert_checkbox.setObjectName(u"revert_checkbox") - - self.gridLayout_6.addWidget(self.revert_checkbox, 0, 1, 1, 1) - - self.filename = QLineEdit(self.rename_group) - self.filename.setObjectName(u"filename") - self.filename.setEnabled(False) - - self.gridLayout_6.addWidget(self.filename, 1, 0, 1, 2) - - - self.verticalLayout_10.addWidget(self.rename_group) - - self.widget_9 = QWidget(self.tab_1) - self.widget_9.setObjectName(u"widget_9") - self.widget_9.setMaximumSize(QSize(400, 50)) - self.horizontalLayout_3 = QHBoxLayout(self.widget_9) - self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") - self.progressBar = QProgressBar(self.widget_9) - self.progressBar.setObjectName(u"progressBar") - self.progressBar.setEnabled(True) - self.progressBar.setValue(0) - - self.horizontalLayout_3.addWidget(self.progressBar) - - self.start_button = QPushButton(self.widget_9) - self.start_button.setObjectName(u"start_button") - self.start_button.setEnabled(True) - - self.horizontalLayout_3.addWidget(self.start_button) - - - self.verticalLayout_10.addWidget(self.widget_9) - - self.tabWidget.addTab(self.tab_1, "") - self.tab_2 = QWidget() - self.tab_2.setObjectName(u"tab_2") - self.verticalLayout_9 = QVBoxLayout(self.tab_2) - self.verticalLayout_9.setObjectName(u"verticalLayout_9") - self.exif_group = QGroupBox(self.tab_2) - self.exif_group.setObjectName(u"exif_group") - self.horizontalLayout = QHBoxLayout(self.exif_group) - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.exif_checkbox = QCheckBox(self.exif_group) - self.exif_checkbox.setObjectName(u"exif_checkbox") - self.exif_checkbox.setEnabled(True) - - self.horizontalLayout.addWidget(self.exif_checkbox) - - self.exif_copy_checkBox = QCheckBox(self.exif_group) - self.exif_copy_checkBox.setObjectName(u"exif_copy_checkBox") - - self.horizontalLayout.addWidget(self.exif_copy_checkBox) - - self.edit_exif_button = QPushButton(self.exif_group) - self.edit_exif_button.setObjectName(u"edit_exif_button") - self.edit_exif_button.setEnabled(False) - - self.horizontalLayout.addWidget(self.edit_exif_button) - - - self.verticalLayout_9.addWidget(self.exif_group) - - self.exif_options_group = QGroupBox(self.tab_2) - self.exif_options_group.setObjectName(u"exif_options_group") - self.exif_options_group.setEnabled(False) - self.gridLayout_7 = QGridLayout(self.exif_options_group) - self.gridLayout_7.setObjectName(u"gridLayout_7") - self.widget_7 = QWidget(self.exif_options_group) - self.widget_7.setObjectName(u"widget_7") - self.verticalLayout_7 = QVBoxLayout(self.widget_7) - self.verticalLayout_7.setObjectName(u"verticalLayout_7") - self.label_7 = QLabel(self.widget_7) - self.label_7.setObjectName(u"label_7") - - self.verticalLayout_7.addWidget(self.label_7) - - self.artist_comboBox = QComboBox(self.widget_7) - self.artist_comboBox.setObjectName(u"artist_comboBox") - - self.verticalLayout_7.addWidget(self.artist_comboBox) - - - self.gridLayout_7.addWidget(self.widget_7, 3, 0, 1, 1) - - self.widget_4 = QWidget(self.exif_options_group) - self.widget_4.setObjectName(u"widget_4") - self.verticalLayout_4 = QVBoxLayout(self.widget_4) - self.verticalLayout_4.setObjectName(u"verticalLayout_4") - self.label_4 = QLabel(self.widget_4) - self.label_4.setObjectName(u"label_4") - - self.verticalLayout_4.addWidget(self.label_4) - - self.iso_comboBox = QComboBox(self.widget_4) - self.iso_comboBox.setObjectName(u"iso_comboBox") - - self.verticalLayout_4.addWidget(self.iso_comboBox) - - - self.gridLayout_7.addWidget(self.widget_4, 1, 1, 1, 1) - - self.widget_6 = QWidget(self.exif_options_group) - self.widget_6.setObjectName(u"widget_6") - self.verticalLayout_6 = QVBoxLayout(self.widget_6) - self.verticalLayout_6.setObjectName(u"verticalLayout_6") - self.label_6 = QLabel(self.widget_6) - self.label_6.setObjectName(u"label_6") - - self.verticalLayout_6.addWidget(self.label_6) - - self.user_comment_comboBox = QComboBox(self.widget_6) - self.user_comment_comboBox.setObjectName(u"user_comment_comboBox") - - self.verticalLayout_6.addWidget(self.user_comment_comboBox) - - - self.gridLayout_7.addWidget(self.widget_6, 2, 1, 1, 1) - - self.widget_2 = QWidget(self.exif_options_group) - self.widget_2.setObjectName(u"widget_2") - self.verticalLayout_2 = QVBoxLayout(self.widget_2) - self.verticalLayout_2.setObjectName(u"verticalLayout_2") - self.label_2 = QLabel(self.widget_2) - self.label_2.setObjectName(u"label_2") - - self.verticalLayout_2.addWidget(self.label_2) - - self.lens_comboBox = QComboBox(self.widget_2) - self.lens_comboBox.setObjectName(u"lens_comboBox") - - self.verticalLayout_2.addWidget(self.lens_comboBox) - - - self.gridLayout_7.addWidget(self.widget_2, 1, 0, 1, 1) - - self.widget_5 = QWidget(self.exif_options_group) - self.widget_5.setObjectName(u"widget_5") - self.verticalLayout_5 = QVBoxLayout(self.widget_5) - self.verticalLayout_5.setObjectName(u"verticalLayout_5") - self.label_5 = QLabel(self.widget_5) - self.label_5.setObjectName(u"label_5") - - self.verticalLayout_5.addWidget(self.label_5) - - self.image_description_comboBox = QComboBox(self.widget_5) - self.image_description_comboBox.setObjectName(u"image_description_comboBox") - - self.verticalLayout_5.addWidget(self.image_description_comboBox) - - - self.gridLayout_7.addWidget(self.widget_5, 2, 0, 1, 1) - - self.widget = QWidget(self.exif_options_group) - self.widget.setObjectName(u"widget") - self.verticalLayout = QVBoxLayout(self.widget) - self.verticalLayout.setObjectName(u"verticalLayout") - self.label = QLabel(self.widget) - self.label.setObjectName(u"label") - - self.verticalLayout.addWidget(self.label) - - self.make_comboBox = QComboBox(self.widget) - self.make_comboBox.setObjectName(u"make_comboBox") - - self.verticalLayout.addWidget(self.make_comboBox) - - - self.gridLayout_7.addWidget(self.widget, 0, 0, 1, 1) - - self.widget_3 = QWidget(self.exif_options_group) - self.widget_3.setObjectName(u"widget_3") - self.verticalLayout_3 = QVBoxLayout(self.widget_3) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") - self.label_3 = QLabel(self.widget_3) - self.label_3.setObjectName(u"label_3") - - self.verticalLayout_3.addWidget(self.label_3) - - self.model_comboBox = QComboBox(self.widget_3) - self.model_comboBox.setObjectName(u"model_comboBox") - - self.verticalLayout_3.addWidget(self.model_comboBox) - - - self.gridLayout_7.addWidget(self.widget_3, 0, 1, 1, 1) - - self.widget_8 = QWidget(self.exif_options_group) - self.widget_8.setObjectName(u"widget_8") - self.verticalLayout_8 = QVBoxLayout(self.widget_8) - self.verticalLayout_8.setObjectName(u"verticalLayout_8") - self.label_8 = QLabel(self.widget_8) - self.label_8.setObjectName(u"label_8") - - self.verticalLayout_8.addWidget(self.label_8) - - self.copyright_info_comboBox = QComboBox(self.widget_8) - self.copyright_info_comboBox.setObjectName(u"copyright_info_comboBox") - - self.verticalLayout_8.addWidget(self.copyright_info_comboBox) - - - self.gridLayout_7.addWidget(self.widget_8, 3, 1, 1, 1) - - - self.verticalLayout_9.addWidget(self.exif_options_group) - - self.gps_groupBox = QGroupBox(self.tab_2) - self.gps_groupBox.setObjectName(u"gps_groupBox") - self.gps_groupBox.setEnabled(False) - self.horizontalLayout_4 = QHBoxLayout(self.gps_groupBox) - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") - self.gps_checkBox = QCheckBox(self.gps_groupBox) - self.gps_checkBox.setObjectName(u"gps_checkBox") - - self.horizontalLayout_4.addWidget(self.gps_checkBox) - - self.lat_lineEdit = QLineEdit(self.gps_groupBox) - self.lat_lineEdit.setObjectName(u"lat_lineEdit") - self.lat_lineEdit.setEnabled(False) - - self.horizontalLayout_4.addWidget(self.lat_lineEdit) - - self.long_lineEdit = QLineEdit(self.gps_groupBox) - self.long_lineEdit.setObjectName(u"long_lineEdit") - self.long_lineEdit.setEnabled(False) - - self.horizontalLayout_4.addWidget(self.long_lineEdit) - - - self.verticalLayout_9.addWidget(self.gps_groupBox) - - self.date_groupBox = QGroupBox(self.tab_2) - self.date_groupBox.setObjectName(u"date_groupBox") - self.date_groupBox.setEnabled(False) - self.horizontalLayout_2 = QHBoxLayout(self.date_groupBox) - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") - self.add_date_checkBox = QCheckBox(self.date_groupBox) - self.add_date_checkBox.setObjectName(u"add_date_checkBox") - - self.horizontalLayout_2.addWidget(self.add_date_checkBox) - - self.dateEdit = QDateEdit(self.date_groupBox) - self.dateEdit.setObjectName(u"dateEdit") - self.dateEdit.setEnabled(False) - self.dateEdit.setDateTime(QDateTime(QDate(2025, 1, 1), QTime(0, 0, 0))) - - self.horizontalLayout_2.addWidget(self.dateEdit) - - - self.verticalLayout_9.addWidget(self.date_groupBox) - - self.tabWidget.addTab(self.tab_2, "") - - self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) - - MainWindow.setCentralWidget(self.centralwidget) - self.statusBar = QStatusBar(MainWindow) - self.statusBar.setObjectName(u"statusBar") - MainWindow.setStatusBar(self.statusBar) - self.menuBar = QMenuBar(MainWindow) - self.menuBar.setObjectName(u"menuBar") - self.menuBar.setGeometry(QRect(0, 0, 450, 27)) - self.menuInfo = QMenu(self.menuBar) - self.menuInfo.setObjectName(u"menuInfo") - MainWindow.setMenuBar(self.menuBar) - - self.menuBar.addAction(self.menuInfo.menuAction()) - self.menuInfo.addAction(self.actionInfo) - - self.retranslateUi(MainWindow) - self.resize_checkbox.toggled.connect(self.resize_spinBox.setEnabled) - self.brightness_checkbox.toggled.connect(self.brightness_spinBox.setEnabled) - self.contrast_checkbox.toggled.connect(self.contrast_spinBox.setEnabled) - self.watermark_checkbox.toggled.connect(self.watermark_lineEdit.setEnabled) - self.rename_checkbox.toggled.connect(self.filename.setEnabled) - self.exif_checkbox.toggled.connect(self.exif_options_group.setEnabled) - self.exif_checkbox.toggled.connect(self.exif_copy_checkBox.setDisabled) - self.exif_copy_checkBox.toggled.connect(self.exif_checkbox.setDisabled) - self.exif_checkbox.toggled.connect(self.edit_exif_button.setEnabled) - self.add_date_checkBox.toggled.connect(self.dateEdit.setEnabled) - self.exif_checkbox.toggled.connect(self.date_groupBox.setEnabled) - self.exif_checkbox.toggled.connect(self.gps_groupBox.setEnabled) - self.gps_checkBox.toggled.connect(self.lat_lineEdit.setEnabled) - self.gps_checkBox.toggled.connect(self.long_lineEdit.setEnabled) - - self.tabWidget.setCurrentIndex(0) - self.font_size_comboBox.setCurrentIndex(2) - - - QMetaObject.connectSlotsByName(MainWindow) - # setupUi - - def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"OPTIMA-35", None)) - self.actionInfo.setText(QCoreApplication.translate("MainWindow", u"Info", None)) - self.input_path.setText("") - self.input_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter input folder", None)) - self.output_path.setText("") - self.output_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter output folder", None)) - self.input_folder_button.setText(QCoreApplication.translate("MainWindow", u"input", None)) - self.output_folder_button.setText(QCoreApplication.translate("MainWindow", u"output", None)) - self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Essential group", None)) - self.resize_checkbox.setText(QCoreApplication.translate("MainWindow", u"Resize", None)) - - self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"optimize", None)) - self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Extra stuff", None)) - self.watermark_lineEdit.setText("") - self.watermark_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter Watermark", None)) - self.brightness_checkbox.setText(QCoreApplication.translate("MainWindow", u"Brightness", None)) - self.grayscale_checkBox.setText(QCoreApplication.translate("MainWindow", u"Grayscale", None)) - self.watermark_checkbox.setText(QCoreApplication.translate("MainWindow", u"Watermark", None)) - self.contrast_checkbox.setText(QCoreApplication.translate("MainWindow", u"Contrast", None)) - self.font_size_comboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Tiny", None)) - self.font_size_comboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Small", None)) - self.font_size_comboBox.setItemText(2, QCoreApplication.translate("MainWindow", u"Normal", None)) - self.font_size_comboBox.setItemText(3, QCoreApplication.translate("MainWindow", u"Large", None)) - self.font_size_comboBox.setItemText(4, QCoreApplication.translate("MainWindow", u"Huge", None)) - - self.font_size_comboBox.setCurrentText(QCoreApplication.translate("MainWindow", u"Normal", None)) - self.rename_group.setTitle(QCoreApplication.translate("MainWindow", u"files", None)) - self.rename_checkbox.setText(QCoreApplication.translate("MainWindow", u"Rename", None)) - self.revert_checkbox.setText(QCoreApplication.translate("MainWindow", u"Revert order", None)) - self.filename.setText("") - self.filename.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter file name", None)) - self.start_button.setText(QCoreApplication.translate("MainWindow", u"Convert", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), QCoreApplication.translate("MainWindow", u"Main", None)) - self.exif_group.setTitle(QCoreApplication.translate("MainWindow", u"EXIF EXPERIMENTAL", None)) - self.exif_checkbox.setText(QCoreApplication.translate("MainWindow", u"own exif", None)) - self.exif_copy_checkBox.setText(QCoreApplication.translate("MainWindow", u"copy exif", None)) - self.edit_exif_button.setText(QCoreApplication.translate("MainWindow", u"edit exif", None)) - self.exif_options_group.setTitle(QCoreApplication.translate("MainWindow", u"Must", None)) - self.label_7.setText(QCoreApplication.translate("MainWindow", u"Artist", None)) - self.label_4.setText(QCoreApplication.translate("MainWindow", u"ISO", None)) - self.label_6.setText(QCoreApplication.translate("MainWindow", u"Scanner", None)) - self.label_2.setText(QCoreApplication.translate("MainWindow", u"Lens", None)) - self.label_5.setText(QCoreApplication.translate("MainWindow", u"Film", None)) - self.label.setText(QCoreApplication.translate("MainWindow", u"Make", None)) - self.make_comboBox.setCurrentText("") - self.make_comboBox.setPlaceholderText("") - self.label_3.setText(QCoreApplication.translate("MainWindow", u"Model", None)) - self.label_8.setText(QCoreApplication.translate("MainWindow", u"Copyright", None)) - self.gps_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"GPS", None)) - self.gps_checkBox.setText(QCoreApplication.translate("MainWindow", u"add gps", None)) - self.lat_lineEdit.setText("") - self.lat_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"latitude [S, N]", None)) - self.long_lineEdit.setText("") - self.long_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"longitude [W, E]", None)) - self.date_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Optional", None)) - self.add_date_checkBox.setText(QCoreApplication.translate("MainWindow", u"add date", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"EXIF", None)) - self.menuInfo.setTitle(QCoreApplication.translate("MainWindow", u"Info", None)) - # retranslateUi - diff --git a/ui/main_window.ui b/ui/main_window.ui deleted file mode 100644 index b75e88f..0000000 --- a/ui/main_window.ui +++ /dev/null @@ -1,919 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 450 - 708 - - - - - 350 - 677 - - - - - 500 - 1000 - - - - OPTIMA-35 - - - - - - - - 500 - 16777215 - - - - 0 - - - - Main - - - - - - - 400 - 16777215 - - - - - - - - - - Enter input folder - - - - - - - - - - Enter output folder - - - - - - - input - - - - - - - output - - - - - - - - - - - 400 - 16777215 - - - - Essential group - - - - - - Resize - - - - - - - false - - - 1 - - - 200 - - - 1 - - - 80 - - - - - - - - jpg - - - - - png - - - - - webp - - - - - - - - 1 - - - 100 - - - 80 - - - - - - - true - - - 1 - - - 9 - - - 6 - - - - - - - optimize - - - - - png_quality_spinBox - resize_checkbox - resize_spinBox - image_type - jpg_quality_spinBox - optimize_checkBox - - - - - - - 400 - 16777215 - - - - Extra stuff - - - - - - false - - - - - - Enter Watermark - - - - - - - Brightness - - - - - - - Grayscale - - - - - - - false - - - -100 - - - 100 - - - 10 - - - - - - - Watermark - - - - - - - false - - - -100 - - - 100 - - - -10 - - - - - - - Contrast - - - - - - - Normal - - - 2 - - - - Tiny - - - - - Small - - - - - Normal - - - - - Large - - - - - Huge - - - - - - - - - - - - 400 - 16777215 - - - - files - - - - - - Rename - - - - - - - Revert order - - - - - - - false - - - - - - Enter file name - - - - - - - - - - - 400 - 50 - - - - - - - true - - - 0 - - - - - - - true - - - Convert - - - - - - - - - - - EXIF - - - - - - EXIF EXPERIMENTAL - - - - - - true - - - own exif - - - - - - - copy exif - - - - - - - false - - - edit exif - - - - - - - - - - false - - - Must - - - - - - - - - Artist - - - - - - - - - - - - - - - - ISO - - - - - - - - - - - - - - - - Scanner - - - - - - - - - - - - - - - - Lens - - - - - - - - - - - - - - - - Film - - - - - - - - - - - - - - - - Make - - - - - - - - - - - - - - - - - - - - - - - Model - - - - - - - - - - - - - - - - Copyright - - - - - - - - - - - - - - - - false - - - GPS - - - - - - add gps - - - - - - - false - - - - - - latitude [S, N] - - - - - - - false - - - - - - longitude [W, E] - - - - - - - - - - false - - - Optional - - - - - - add date - - - - - - - false - - - - 0 - 0 - 0 - 2025 - 1 - 1 - - - - - - - - - - - - - - - - - - 0 - 0 - 450 - 27 - - - - - Info - - - - - - - - Info - - - - - - - resize_checkbox - toggled(bool) - resize_spinBox - setEnabled(bool) - - - 75 - 96 - - - 196 - 118 - - - - - brightness_checkbox - toggled(bool) - brightness_spinBox - setEnabled(bool) - - - 83 - 363 - - - 83 - 399 - - - - - contrast_checkbox - toggled(bool) - contrast_spinBox - setEnabled(bool) - - - 185 - 363 - - - 185 - 399 - - - - - watermark_checkbox - toggled(bool) - watermark_lineEdit - setEnabled(bool) - - - 83 - 435 - - - 237 - 435 - - - - - rename_checkbox - toggled(bool) - filename - setEnabled(bool) - - - 105 - 522 - - - 182 - 560 - - - - - exif_checkbox - toggled(bool) - exif_options_group - setEnabled(bool) - - - 130 - 105 - - - 236 - 328 - - - - - exif_checkbox - toggled(bool) - exif_copy_checkBox - setDisabled(bool) - - - 130 - 105 - - - 332 - 105 - - - - - exif_copy_checkBox - toggled(bool) - exif_checkbox - setDisabled(bool) - - - 332 - 105 - - - 130 - 105 - - - - - exif_checkbox - toggled(bool) - edit_exif_button - setEnabled(bool) - - - 134 - 107 - - - 79 - 170 - - - - - add_date_checkBox - toggled(bool) - dateEdit - setEnabled(bool) - - - 94 - 601 - - - 224 - 602 - - - - - exif_checkbox - toggled(bool) - date_groupBox - setEnabled(bool) - - - 126 - 103 - - - 224 - 589 - - - - - exif_checkbox - toggled(bool) - gps_groupBox - setEnabled(bool) - - - 94 - 103 - - - 224 - 535 - - - - - gps_checkBox - toggled(bool) - lat_lineEdit - setEnabled(bool) - - - 72 - 547 - - - 192 - 547 - - - - - gps_checkBox - toggled(bool) - long_lineEdit - setEnabled(bool) - - - 72 - 547 - - - 344 - 547 - - - - - diff --git a/ui/simple_dialog.py b/ui/simple_dialog.py deleted file mode 100644 index b4a230b..0000000 --- a/ui/simple_dialog.py +++ /dev/null @@ -1,30 +0,0 @@ -from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QLineEdit, QPushButton, QLabel -# ChatGPT -class SimpleDialog(QDialog): - def __init__(self): - super().__init__() - - # Set default properties - self.setGeometry(100, 100, 300, 100) - - # Create the layout - layout = QVBoxLayout() - - # Create the label for the message - self.message_label = QLabel(self) - - # Create the close button - close_button = QPushButton("Close", self) - close_button.clicked.connect(self.close) - - # Add widgets to layout - layout.addWidget(self.message_label) - layout.addWidget(close_button) - - # Set layout for the dialog - self.setLayout(layout) - - def show_dialog(self, title: str, message: str): - self.setWindowTitle(title) # Set the window title - self.message_label.setText(message) # Set the message text - self.exec() # Open the dialog as a modal window diff --git a/ui/simple_tui.py b/ui/simple_tui.py deleted file mode 100644 index f8abd32..0000000 --- a/ui/simple_tui.py +++ /dev/null @@ -1,60 +0,0 @@ -from simple_term_menu import TerminalMenu - -class SimpleTUI: - """TUI parts using library simple_term_menu""" - def __init__(self): - pass - - def choose_menu(self, menu_title, choices): - """ Dynamic function to display content of a list and returnes which was selected.""" - menu_options = choices - menu = TerminalMenu( - menu_entries = menu_options, - title = menu_title, - menu_cursor = "> ", - menu_cursor_style = ("fg_gray", "bold"), - menu_highlight_style = ("bg_gray", "fg_black"), - cycle_cursor = True, - clear_screen = False - ) - menu.show() - return menu.chosen_menu_entry - - def multi_select_menu(self, menu_title, choices): - """ Dynamic function to display content of a list and returnes which was selected.""" - menu_options = choices - menu = TerminalMenu( - menu_entries = menu_options, - title = menu_title, - multi_select=True, - show_multi_select_hint=True, - menu_cursor_style = ("fg_gray", "bold"), - menu_highlight_style = ("bg_gray", "fg_black"), - cycle_cursor = True, - clear_screen = False - ) - menu.show() - choisen_values = menu.chosen_menu_entries - - if choisen_values == None: - print("Exiting...") - exit() - else: - return menu.chosen_menu_entries - - def yes_no_menu(self, message): # oh - menu_options = ["[y] yes", "[n] no"] - menu = TerminalMenu( - menu_entries = menu_options, - title = f"{message}", - menu_cursor = "> ", - menu_cursor_style = ("fg_red", "bold"), - menu_highlight_style = ("bg_gray", "fg_black"), - cycle_cursor = True, - clear_screen = False - ) - menu_entry_index = menu.show() - if menu_entry_index == 0: - return True - elif menu_entry_index == 1: - return False diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/utils/utility.py b/utils/utility.py deleted file mode 100644 index 6dfa17d..0000000 --- a/utils/utility.py +++ /dev/null @@ -1,117 +0,0 @@ -import yaml -import os - -class Utilities: - def __init__(self): - pass - - def read_yaml(self, yaml_file): - try: - with open(yaml_file, "r") as file: - data = yaml.safe_load(file) - return data - except (FileNotFoundError, PermissionError) as e: - print(f"Error loading settings file: {e}") - return - - def write_yaml(self, yaml_file, data): - try: - with open(yaml_file, "w") as file: - yaml.dump(data, file) - except PermissionError as e: - print(f"Error saving setings: {e}") - - def program_configs(self): - """Prepear folder for config and generate default exif if non aviable""" - program_folder = self._ensure_program_folder_exists() - if not os.path.isfile(f"{program_folder}/exif.yaml"): - self._default_exif(f"{program_folder}/exif.yaml") - - def _ensure_program_folder_exists(self): - program_folder = os.path.expanduser("~/.config/OptimaLab35") - print(program_folder) - if not os.path.exists(program_folder): - print("in not, make folder") - os.makedirs(program_folder) - return program_folder - - def _default_exif(self, file): - """Makes a default exif file.""" - print("Making default") - def_exif = { - "artist": [ - "Mr Finchum", - "John Doe" - ], - "copyright_info": [ - "All Rights Reserved", - "CC BY-NC 4.0", - "No Copyright" - ], - "image_description": [ - "ILFORD DELTA 3200", - "ILFORD ILFOCOLOR", - "LomoChrome Turquoise", - "Kodak 200" - ], - "iso": [ - "200", - "400", - "1600", - "3200" - ], - "lens": [ - "Nikon LENS SERIES E 50mm", - "AF NIKKOR 35-70mm", - "Canon FD 50mm f/1.4 S.S.C" - ], - "make": [ - "Nikon", - "Canon" - ], - "model": [ - "FG", - "F50", - "AE-1" - ], - "user_comment": [ - "Scanner.NORITSU-KOKI", - "Scanner.NA" - ] - } - self.write_yaml(file, def_exif) - - def append_number_to_name(self, base_name: str, current_image: int, total_images: int, invert: bool): - """"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}" - - def yes_no(self, str): - """Ask user y/n question""" - while True: - choice = input(f"{str} (y/n): ") - if choice == "y": - return True - elif choice == "n": - return False - else: - print("Not a valid option, try again.") - - def progress_bar(self, current, total, barsize = 50): - if current > total: - print("\033[91mThis bar has exceeded its limits!\033[0m Maybe the current value needs some restraint?") - return - progress = int((barsize / total) * current) - rest = barsize - progress - if rest <= 2: rest = 0 - # Determine the number of digits in total - total_digits = len(str(total)) - # Format current with leading zeros - current_formatted = f"{current:0{total_digits}}" - print(f"{current_formatted}|{progress * '-'}>{rest * ' '}|{total}", end="\r") - if current == total: print("") From 9ff18a7aef9024ee6c02fc305e0429a3916df6a0 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:13:05 +0100 Subject: [PATCH 03/12] Moved all code here + adjusting code to work with new structure. --- src/OptimaLab35/gui.py | 282 +++++++ src/OptimaLab35/main.py | 43 + src/OptimaLab35/tui.py | 329 ++++++++ src/OptimaLab35/ui/__init__.py | 0 src/OptimaLab35/ui/exif_handler_window.py | 87 ++ src/OptimaLab35/ui/main_window.py | 559 +++++++++++++ src/OptimaLab35/ui/main_window.ui | 919 ++++++++++++++++++++++ src/OptimaLab35/ui/simple_dialog.py | 30 + src/OptimaLab35/ui/simple_tui.py | 60 ++ src/OptimaLab35/utils/__init__.py | 0 src/OptimaLab35/utils/utility.py | 117 +++ 11 files changed, 2426 insertions(+) create mode 100644 src/OptimaLab35/gui.py create mode 100644 src/OptimaLab35/main.py create mode 100644 src/OptimaLab35/tui.py create mode 100644 src/OptimaLab35/ui/__init__.py create mode 100644 src/OptimaLab35/ui/exif_handler_window.py create mode 100644 src/OptimaLab35/ui/main_window.py create mode 100644 src/OptimaLab35/ui/main_window.ui create mode 100644 src/OptimaLab35/ui/simple_dialog.py create mode 100644 src/OptimaLab35/ui/simple_tui.py create mode 100644 src/OptimaLab35/utils/__init__.py create mode 100644 src/OptimaLab35/utils/utility.py diff --git a/src/OptimaLab35/gui.py b/src/OptimaLab35/gui.py new file mode 100644 index 0000000..b5ec100 --- /dev/null +++ b/src/OptimaLab35/gui.py @@ -0,0 +1,282 @@ +import sys +import os +from datetime import datetime + +from optima35.core import OptimaManager +from OptimaLab35.utils.utility import Utilities +from OptimaLab35.ui.main_window import Ui_MainWindow +from OptimaLab35.ui.exif_handler_window import ExifEditor +from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class + +from PySide6 import QtWidgets +from PySide6.QtWidgets import ( + QMessageBox, + QApplication, + QMainWindow, + QWidget, + QVBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QCheckBox, + QFileDialog, + QHBoxLayout, + QSpinBox, +) + +class OptimaLab35(QMainWindow, Ui_MainWindow): + def __init__(self): + super(OptimaLab35, self).__init__() + self.name = "OptimaLab35" + self.version = "0.0.3-a1" + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + self.o = OptimaManager() + self.u = Utilities() + self.u.program_configs() + self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml") + self.available_exif_data = None + self.settings = {} + self.setWindowTitle(f"{self.name} v{self.version}") + self._default_ui_layout() + self._define_gui_interaction() + + self.sd = SimpleDialog() + self._change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000) + + 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.actionInfo.triggered.connect(self._info_window) + + def _info_window(self): + self.sd.show_dialog(f"{self.name} v{self.version}", f"{self.name} v{self.version} is a GUI for {self.o.name} (v{self.o.version})") + + def _process(self): + self.ui.start_button.setEnabled(False) + self._update_settings() # 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.u.append_number_to_name(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) + + self.o.process_image( + image_input_file = input_path, + image_output_file = output_path, + file_type = self.settings["file_format"], + quality = self.settings["jpg_quality"], + compressing = self.settings["png_compression"], + optimize = self.ui.optimize_checkBox.isChecked(), + resize = self.settings["resize"], + watermark = self.settings["watermark"], + font_size = self.settings["font_size"], + grayscale = self.settings["grayscale"], + brightness = self.settings["brightness"], + contrast = self.settings["contrast"], + dict_for_exif = self.user_selected_exif, + gps = self.settings["gps"], + copy_exif = self.settings["copy_exif"]) + self._handle_qprogressbar(i, len(image_files)) + i += 1 + + QMessageBox.information(self, "Information", "Finished") + self.ui.start_button.setEnabled(True) + self.ui.progressBar.setValue(0) + + def _open_exif_editor(self): + """Open the EXIF Editor.""" + self.exif_editor = ExifEditor(self.available_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.""" + 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.available_exif_data = self.u.read_yaml(self.exif_file) + elif do == "write": + self.u.write_yaml(self.exif_file, self.available_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.available_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 _get_checkbox_value(self, checkbox, default=None): + """Helper function to get the value of a checkbox or a default value.""" + return checkbox.isChecked() if checkbox else default + + def _get_spinbox_value(self, spinbox, default=None): + """Helper function to get the value of a spinbox and handle empty input.""" + return int(spinbox.text()) if spinbox.text() else default + + def _get_combobox_value(self, combobox, default=None): + """Helper function to get the value of a combobox.""" + return combobox.currentIndex() + 1 if combobox.currentIndex() != -1 else default + + def _get_text_value(self, lineedit, default=None): + """Helper function to get the value of a text input field.""" + return lineedit.text() if lineedit.text() else default + + def _get_selected_exif(self): + """Collect selected EXIF data and handle date and GPS if necessary.""" + selected_exif = self._collect_selected_exif() if self.ui.exif_checkbox.isChecked() else None + if selected_exif: + if self.ui.add_date_checkBox.isChecked(): + 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"] = None + return selected_exif + + def _update_settings(self): + """Update .settings from all GUI elements.""" + # General settings + self.settings["input_folder"] = self._get_text_value(self.ui.input_path) + self.settings["output_folder"] = self._get_text_value(self.ui.output_path) + self.settings["file_format"] = self.ui.image_type.currentText() + self.settings["jpg_quality"] = self._get_spinbox_value(self.ui.jpg_quality_spinBox) + self.settings["png_compression"] = self._get_spinbox_value(self.ui.png_quality_spinBox) + self.settings["invert_image_order"] = self._get_checkbox_value(self.ui.revert_checkbox) + self.settings["grayscale"] = self._get_checkbox_value(self.ui.grayscale_checkBox) + self.settings["copy_exif"] = self._get_checkbox_value(self.ui.exif_copy_checkBox) + self.settings["own_exif"] = self._get_checkbox_value(self.ui.exif_checkbox) + self.settings["font_size"] = self._get_combobox_value(self.ui.font_size_comboBox) + self.settings["optimize"] = self._get_checkbox_value(self.ui.optimize_checkBox) + self.settings["own_date"] = self._get_checkbox_value(self.ui.add_date_checkBox) + + # Conditional settings with logic + self.settings["resize"] = self._get_spinbox_value(self.ui.resize_spinBox) if self.ui.resize_checkbox.isChecked() else None + self.settings["brightness"] = self._get_spinbox_value(self.ui.brightness_spinBox) if self.ui.brightness_checkbox.isChecked() else None + self.settings["contrast"] = self._get_spinbox_value(self.ui.contrast_spinBox) if self.ui.contrast_checkbox.isChecked() else None + + self.settings["new_file_names"] = self._get_text_value(self.ui.filename, False) if self.ui.rename_checkbox.isChecked() else False + self.settings["watermark"] = self._get_text_value(self.ui.watermark_lineEdit) if self.ui.watermark_checkbox.isChecked() else None + + # Handle EXIF data selection + if self.settings["own_exif"]: + self.user_selected_exif = self._get_selected_exif() + else: + self.user_selected_exif = None + self.settings["gps"] = None + + 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"{self.o.name} {self.o.version}" + return user_data + +def main(): + app = QtWidgets.QApplication(sys.argv) + window = OptimaLab35() + window.show() + app.exec() + +if __name__ == "__main__": + main() diff --git a/src/OptimaLab35/main.py b/src/OptimaLab35/main.py new file mode 100644 index 0000000..dad2062 --- /dev/null +++ b/src/OptimaLab35/main.py @@ -0,0 +1,43 @@ +import os +from argparse import ArgumentParser +from OptimaLab35 import gui, tui + +# Mainly from ChatGPT +def check_pyside_installed(): + try: + import PySide6 # Replace with PySide2 if using that version + return True + except ImportError: + return False + +def start_gui(): + gui.main() + +def start_tui(): + tui.main() + +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() + + if args.tui: + print("Starting TUI...") + start_tui() + return + + # 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__": + main() diff --git a/src/OptimaLab35/tui.py b/src/OptimaLab35/tui.py new file mode 100644 index 0000000..d89cd64 --- /dev/null +++ b/src/OptimaLab35/tui.py @@ -0,0 +1,329 @@ +import os +from datetime import datetime +# my packages +from optima35.core import OptimaManager +from OptimaLab35.utils.utility import Utilities +from OptimaLab35.ui.simple_tui import SimpleTUI + +class OptimaLab35_lite(): + def __init__(self): + self.name = "OptimaLab35-lite" + self.version = "0.0.3-a1" + self.o = OptimaManager() + self.u = Utilities() + self.tui = SimpleTUI() + self.u.program_configs() + self.exif_file = os.path.expanduser("~/.config/OptimaLab35/exif.yaml") + self.available_exif_data = self.u.read_yaml(self.exif_file) + self.setting_file = os.path.expanduser("~/.config/OptimaLab35/tui_settings.yaml") + 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): + 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: + print("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.u.append_number_to_name(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) + self.o.process_image( + image_input_file = input_path, + image_output_file = output_path, + file_type = self.settings["file_format"], + quality = self.settings["jpg_quality"], + compressing = self.settings["png_compression"], + optimize = self.settings["optimize"], + resize = self.settings["resize"], + watermark = self.settings["watermark"], + font_size = self.settings["font_size"], + grayscale = self.settings["grayscale"], + brightness = self.settings["brightness"], + contrast = self.settings["contrast"], + dict_for_exif = self.selected_exif, + gps = self.settings["gps"], + copy_exif = self.settings["copy_exif"]) + self.u.progress_bar(i, len(image_files)) + i += 1 + + def _check_options(self): + try: + if "Resize image" in self.settings["modifications"]: + self.settings["resize"] = self.settings["resize"] + else: + self.settings["resize"] = None + + if "Convert to grayscale" in self.settings["modifications"]: + self.settings["grayscale"] = True + else: + self.settings["grayscale"] = False + + if "Change contrast" in self.settings["modifications"]: + self.settings["contrast"] = self.settings["contrast"] + else: + self.settings["contrast"] = None + + if "Change brightness" in self.settings["modifications"]: + self.settings["brightness"] = self.settings["brightness"] + else: + self.settings["brightness"] = None + + if "Rename images" in self.settings["modifications"]: + self.settings["new_file_names"] = self.settings["new_file_names"] + 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"] = self.settings["watermark"] + else: + self.settings["watermark"] = None + + self.settings["optimize"] = self.settings["optimize"] + self.settings["png_compression"] = self.settings["png_compression"] + self.settings["jpg_quality"] = self.settings["jpg_quality"] + + self.settings["input_folder"] = self.settings["input_folder"] + self.settings["output_folder"] = self.settings["output_folder"] + self.settings["file_format"] = self.settings["file_format"] + self.settings["font_size"] = 2 # need to add option to select size + + self.settings["copy_exif"] = self.settings["copy_exif"] + + if "Change EXIF" in self.settings["modifications"]: #missing + self.selected_exif = self._collect_exif_data() # + else: + self.selected_exif = None + + 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") + print(f"Settings for {self.name} v{self.version} will be saved {self.setting_file}...") + 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.""" + print(f"Exif file can be found {self.exif_file}...") + 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.available_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 None + 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"] = input("Enter path of input folder: ").strip() # Add: check if folder exists. + self.settings["output_folder"] = input("Enter path of output folder: ").strip() + self.settings["file_format"] = 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.name} v{self.version} for {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(): + app = OptimaLab35_lite() + app.run() + +if __name__ == "__main__": + main() diff --git a/src/OptimaLab35/ui/__init__.py b/src/OptimaLab35/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/OptimaLab35/ui/exif_handler_window.py b/src/OptimaLab35/ui/exif_handler_window.py new file mode 100644 index 0000000..8564cba --- /dev/null +++ b/src/OptimaLab35/ui/exif_handler_window.py @@ -0,0 +1,87 @@ +from PySide6.QtCore import Signal +from PySide6.QtWidgets import ( + QMainWindow, QWidget, QVBoxLayout, QComboBox, QListWidget, + QLineEdit, QHBoxLayout, QPushButton, QMessageBox +) +# By ChatGPT +class ExifEditor(QMainWindow): + # Signal to emit the updated EXIF data + exif_data_updated = Signal(dict) + + def __init__(self, exif_data): + super().__init__() + self.exif_data = exif_data + self.current_key = None + + self.setWindowTitle("EXIF Editor") + self.resize(400, 300) + + # Main widget and layout + main_widget = QWidget() + main_layout = QVBoxLayout() + main_widget.setLayout(main_layout) + self.setCentralWidget(main_widget) + + # ComboBox to select lists + self.combo_box = QComboBox() + self.combo_box.addItems(self.exif_data.keys()) + self.combo_box.currentTextChanged.connect(self.load_list) + main_layout.addWidget(self.combo_box) + + # List widget to display items + self.list_widget = QListWidget() + main_layout.addWidget(self.list_widget) + + # Line edit for adding items + self.line_edit = QLineEdit() + self.line_edit.setPlaceholderText("Enter new item...") + main_layout.addWidget(self.line_edit) + + # Buttons: Add, Delete, Cancel + button_layout = QHBoxLayout() + self.add_button = QPushButton("Add") + self.add_button.clicked.connect(self.add_item) + self.delete_button = QPushButton("Delete") + self.delete_button.clicked.connect(self.delete_item) + self.cancel_button = QPushButton("Close") + self.cancel_button.clicked.connect(self.close_editor) + + button_layout.addWidget(self.add_button) + button_layout.addWidget(self.delete_button) + button_layout.addWidget(self.cancel_button) + main_layout.addLayout(button_layout) + + # Load the first list by default + self.load_list(self.combo_box.currentText()) + + def load_list(self, key): + """Load the selected list into the list widget.""" + self.current_key = key + self.list_widget.clear() + if key in self.exif_data: + self.list_widget.addItems(self.exif_data[key]) + + def add_item(self): + """Add a new item to the selected list.""" + new_item = self.line_edit.text().strip() + if new_item: + self.exif_data[self.current_key].append(new_item) + self.list_widget.addItem(new_item) + self.line_edit.clear() + else: + QMessageBox.warning(self, "Warning", "Cannot add an empty item.") + + def delete_item(self): + """Delete the selected item from the list.""" + selected_item = self.list_widget.currentItem() + if selected_item: + item_text = selected_item.text() + self.exif_data[self.current_key].remove(item_text) + self.list_widget.takeItem(self.list_widget.row(selected_item)) + else: + QMessageBox.warning(self, "Warning", "No item selected to delete.") + + def close_editor(self): + """Emit the updated exif_data and close the editor.""" + self.exif_data_updated.emit(self.exif_data) + self.close() diff --git a/src/OptimaLab35/ui/main_window.py b/src/OptimaLab35/ui/main_window.py new file mode 100644 index 0000000..890eb7f --- /dev/null +++ b/src/OptimaLab35/ui/main_window.py @@ -0,0 +1,559 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'main_window.ui' +## +## Created by: Qt User Interface Compiler version 6.8.1 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient, + QCursor, QFont, QFontDatabase, QGradient, + QIcon, QImage, QKeySequence, QLinearGradient, + QPainter, QPalette, QPixmap, QRadialGradient, + QTransform) +from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateEdit, + QFrame, QGridLayout, QGroupBox, QHBoxLayout, + QLabel, QLineEdit, QMainWindow, QMenu, + QMenuBar, QProgressBar, QPushButton, QSizePolicy, + QSpinBox, QStatusBar, QTabWidget, QVBoxLayout, + QWidget) + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.resize(450, 708) + MainWindow.setMinimumSize(QSize(350, 677)) + MainWindow.setMaximumSize(QSize(500, 1000)) + self.actionInfo = QAction(MainWindow) + self.actionInfo.setObjectName(u"actionInfo") + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.gridLayout = QGridLayout(self.centralwidget) + self.gridLayout.setObjectName(u"gridLayout") + self.tabWidget = QTabWidget(self.centralwidget) + self.tabWidget.setObjectName(u"tabWidget") + self.tabWidget.setMaximumSize(QSize(500, 16777215)) + self.tab_1 = QWidget() + self.tab_1.setObjectName(u"tab_1") + self.verticalLayout_10 = QVBoxLayout(self.tab_1) + self.verticalLayout_10.setObjectName(u"verticalLayout_10") + self.folder_group = QFrame(self.tab_1) + self.folder_group.setObjectName(u"folder_group") + self.folder_group.setMaximumSize(QSize(400, 16777215)) + self.gridLayout_5 = QGridLayout(self.folder_group) + self.gridLayout_5.setObjectName(u"gridLayout_5") + self.input_path = QLineEdit(self.folder_group) + self.input_path.setObjectName(u"input_path") + + self.gridLayout_5.addWidget(self.input_path, 0, 0, 1, 1) + + self.output_path = QLineEdit(self.folder_group) + self.output_path.setObjectName(u"output_path") + + self.gridLayout_5.addWidget(self.output_path, 0, 1, 1, 1) + + self.input_folder_button = QPushButton(self.folder_group) + self.input_folder_button.setObjectName(u"input_folder_button") + + self.gridLayout_5.addWidget(self.input_folder_button, 1, 0, 1, 1) + + self.output_folder_button = QPushButton(self.folder_group) + self.output_folder_button.setObjectName(u"output_folder_button") + + self.gridLayout_5.addWidget(self.output_folder_button, 1, 1, 1, 1) + + + self.verticalLayout_10.addWidget(self.folder_group) + + self.groupBox = QGroupBox(self.tab_1) + self.groupBox.setObjectName(u"groupBox") + self.groupBox.setMaximumSize(QSize(400, 16777215)) + self.gridLayout_4 = QGridLayout(self.groupBox) + self.gridLayout_4.setObjectName(u"gridLayout_4") + self.resize_checkbox = QCheckBox(self.groupBox) + self.resize_checkbox.setObjectName(u"resize_checkbox") + + self.gridLayout_4.addWidget(self.resize_checkbox, 0, 0, 1, 1) + + self.resize_spinBox = QSpinBox(self.groupBox) + self.resize_spinBox.setObjectName(u"resize_spinBox") + self.resize_spinBox.setEnabled(False) + self.resize_spinBox.setMinimum(1) + self.resize_spinBox.setMaximum(200) + self.resize_spinBox.setSingleStep(1) + self.resize_spinBox.setValue(80) + + self.gridLayout_4.addWidget(self.resize_spinBox, 0, 1, 1, 1) + + self.image_type = QComboBox(self.groupBox) + self.image_type.addItem(u"jpg") + self.image_type.addItem(u"png") + self.image_type.addItem(u"webp") + self.image_type.setObjectName(u"image_type") + + self.gridLayout_4.addWidget(self.image_type, 1, 0, 1, 1) + + self.jpg_quality_spinBox = QSpinBox(self.groupBox) + self.jpg_quality_spinBox.setObjectName(u"jpg_quality_spinBox") + self.jpg_quality_spinBox.setMinimum(1) + self.jpg_quality_spinBox.setMaximum(100) + self.jpg_quality_spinBox.setValue(80) + + self.gridLayout_4.addWidget(self.jpg_quality_spinBox, 1, 1, 1, 1) + + self.png_quality_spinBox = QSpinBox(self.groupBox) + self.png_quality_spinBox.setObjectName(u"png_quality_spinBox") + self.png_quality_spinBox.setEnabled(True) + self.png_quality_spinBox.setMinimum(1) + self.png_quality_spinBox.setMaximum(9) + self.png_quality_spinBox.setValue(6) + + self.gridLayout_4.addWidget(self.png_quality_spinBox, 1, 2, 1, 1) + + self.optimize_checkBox = QCheckBox(self.groupBox) + self.optimize_checkBox.setObjectName(u"optimize_checkBox") + + self.gridLayout_4.addWidget(self.optimize_checkBox, 0, 2, 1, 1) + + self.png_quality_spinBox.raise_() + self.resize_checkbox.raise_() + self.resize_spinBox.raise_() + self.image_type.raise_() + self.jpg_quality_spinBox.raise_() + self.optimize_checkBox.raise_() + + self.verticalLayout_10.addWidget(self.groupBox) + + self.groupBox_2 = QGroupBox(self.tab_1) + self.groupBox_2.setObjectName(u"groupBox_2") + self.groupBox_2.setMaximumSize(QSize(400, 16777215)) + self.gridLayout_3 = QGridLayout(self.groupBox_2) + self.gridLayout_3.setObjectName(u"gridLayout_3") + self.watermark_lineEdit = QLineEdit(self.groupBox_2) + self.watermark_lineEdit.setObjectName(u"watermark_lineEdit") + self.watermark_lineEdit.setEnabled(False) + + self.gridLayout_3.addWidget(self.watermark_lineEdit, 3, 0, 1, 3) + + self.brightness_checkbox = QCheckBox(self.groupBox_2) + self.brightness_checkbox.setObjectName(u"brightness_checkbox") + + self.gridLayout_3.addWidget(self.brightness_checkbox, 0, 0, 1, 1) + + self.grayscale_checkBox = QCheckBox(self.groupBox_2) + self.grayscale_checkBox.setObjectName(u"grayscale_checkBox") + + self.gridLayout_3.addWidget(self.grayscale_checkBox, 0, 2, 1, 1) + + self.contrast_spinBox = QSpinBox(self.groupBox_2) + self.contrast_spinBox.setObjectName(u"contrast_spinBox") + self.contrast_spinBox.setEnabled(False) + self.contrast_spinBox.setMinimum(-100) + self.contrast_spinBox.setMaximum(100) + self.contrast_spinBox.setValue(10) + + self.gridLayout_3.addWidget(self.contrast_spinBox, 1, 1, 1, 1) + + self.watermark_checkbox = QCheckBox(self.groupBox_2) + self.watermark_checkbox.setObjectName(u"watermark_checkbox") + + self.gridLayout_3.addWidget(self.watermark_checkbox, 2, 0, 1, 1) + + self.brightness_spinBox = QSpinBox(self.groupBox_2) + self.brightness_spinBox.setObjectName(u"brightness_spinBox") + self.brightness_spinBox.setEnabled(False) + self.brightness_spinBox.setMinimum(-100) + self.brightness_spinBox.setMaximum(100) + self.brightness_spinBox.setValue(-10) + + self.gridLayout_3.addWidget(self.brightness_spinBox, 0, 1, 1, 1) + + self.contrast_checkbox = QCheckBox(self.groupBox_2) + self.contrast_checkbox.setObjectName(u"contrast_checkbox") + + self.gridLayout_3.addWidget(self.contrast_checkbox, 1, 0, 1, 1) + + self.font_size_comboBox = QComboBox(self.groupBox_2) + self.font_size_comboBox.addItem("") + self.font_size_comboBox.addItem("") + self.font_size_comboBox.addItem("") + self.font_size_comboBox.addItem("") + self.font_size_comboBox.addItem("") + self.font_size_comboBox.setObjectName(u"font_size_comboBox") + + self.gridLayout_3.addWidget(self.font_size_comboBox, 2, 1, 1, 1) + + + self.verticalLayout_10.addWidget(self.groupBox_2) + + self.rename_group = QGroupBox(self.tab_1) + self.rename_group.setObjectName(u"rename_group") + self.rename_group.setMaximumSize(QSize(400, 16777215)) + self.gridLayout_6 = QGridLayout(self.rename_group) + self.gridLayout_6.setObjectName(u"gridLayout_6") + self.rename_checkbox = QCheckBox(self.rename_group) + self.rename_checkbox.setObjectName(u"rename_checkbox") + + self.gridLayout_6.addWidget(self.rename_checkbox, 0, 0, 1, 1) + + self.revert_checkbox = QCheckBox(self.rename_group) + self.revert_checkbox.setObjectName(u"revert_checkbox") + + self.gridLayout_6.addWidget(self.revert_checkbox, 0, 1, 1, 1) + + self.filename = QLineEdit(self.rename_group) + self.filename.setObjectName(u"filename") + self.filename.setEnabled(False) + + self.gridLayout_6.addWidget(self.filename, 1, 0, 1, 2) + + + self.verticalLayout_10.addWidget(self.rename_group) + + self.widget_9 = QWidget(self.tab_1) + self.widget_9.setObjectName(u"widget_9") + self.widget_9.setMaximumSize(QSize(400, 50)) + self.horizontalLayout_3 = QHBoxLayout(self.widget_9) + self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.progressBar = QProgressBar(self.widget_9) + self.progressBar.setObjectName(u"progressBar") + self.progressBar.setEnabled(True) + self.progressBar.setValue(0) + + self.horizontalLayout_3.addWidget(self.progressBar) + + self.start_button = QPushButton(self.widget_9) + self.start_button.setObjectName(u"start_button") + self.start_button.setEnabled(True) + + self.horizontalLayout_3.addWidget(self.start_button) + + + self.verticalLayout_10.addWidget(self.widget_9) + + self.tabWidget.addTab(self.tab_1, "") + self.tab_2 = QWidget() + self.tab_2.setObjectName(u"tab_2") + self.verticalLayout_9 = QVBoxLayout(self.tab_2) + self.verticalLayout_9.setObjectName(u"verticalLayout_9") + self.exif_group = QGroupBox(self.tab_2) + self.exif_group.setObjectName(u"exif_group") + self.horizontalLayout = QHBoxLayout(self.exif_group) + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.exif_checkbox = QCheckBox(self.exif_group) + self.exif_checkbox.setObjectName(u"exif_checkbox") + self.exif_checkbox.setEnabled(True) + + self.horizontalLayout.addWidget(self.exif_checkbox) + + self.exif_copy_checkBox = QCheckBox(self.exif_group) + self.exif_copy_checkBox.setObjectName(u"exif_copy_checkBox") + + self.horizontalLayout.addWidget(self.exif_copy_checkBox) + + self.edit_exif_button = QPushButton(self.exif_group) + self.edit_exif_button.setObjectName(u"edit_exif_button") + self.edit_exif_button.setEnabled(False) + + self.horizontalLayout.addWidget(self.edit_exif_button) + + + self.verticalLayout_9.addWidget(self.exif_group) + + self.exif_options_group = QGroupBox(self.tab_2) + self.exif_options_group.setObjectName(u"exif_options_group") + self.exif_options_group.setEnabled(False) + self.gridLayout_7 = QGridLayout(self.exif_options_group) + self.gridLayout_7.setObjectName(u"gridLayout_7") + self.widget_7 = QWidget(self.exif_options_group) + self.widget_7.setObjectName(u"widget_7") + self.verticalLayout_7 = QVBoxLayout(self.widget_7) + self.verticalLayout_7.setObjectName(u"verticalLayout_7") + self.label_7 = QLabel(self.widget_7) + self.label_7.setObjectName(u"label_7") + + self.verticalLayout_7.addWidget(self.label_7) + + self.artist_comboBox = QComboBox(self.widget_7) + self.artist_comboBox.setObjectName(u"artist_comboBox") + + self.verticalLayout_7.addWidget(self.artist_comboBox) + + + self.gridLayout_7.addWidget(self.widget_7, 3, 0, 1, 1) + + self.widget_4 = QWidget(self.exif_options_group) + self.widget_4.setObjectName(u"widget_4") + self.verticalLayout_4 = QVBoxLayout(self.widget_4) + self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.label_4 = QLabel(self.widget_4) + self.label_4.setObjectName(u"label_4") + + self.verticalLayout_4.addWidget(self.label_4) + + self.iso_comboBox = QComboBox(self.widget_4) + self.iso_comboBox.setObjectName(u"iso_comboBox") + + self.verticalLayout_4.addWidget(self.iso_comboBox) + + + self.gridLayout_7.addWidget(self.widget_4, 1, 1, 1, 1) + + self.widget_6 = QWidget(self.exif_options_group) + self.widget_6.setObjectName(u"widget_6") + self.verticalLayout_6 = QVBoxLayout(self.widget_6) + self.verticalLayout_6.setObjectName(u"verticalLayout_6") + self.label_6 = QLabel(self.widget_6) + self.label_6.setObjectName(u"label_6") + + self.verticalLayout_6.addWidget(self.label_6) + + self.user_comment_comboBox = QComboBox(self.widget_6) + self.user_comment_comboBox.setObjectName(u"user_comment_comboBox") + + self.verticalLayout_6.addWidget(self.user_comment_comboBox) + + + self.gridLayout_7.addWidget(self.widget_6, 2, 1, 1, 1) + + self.widget_2 = QWidget(self.exif_options_group) + self.widget_2.setObjectName(u"widget_2") + self.verticalLayout_2 = QVBoxLayout(self.widget_2) + self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.label_2 = QLabel(self.widget_2) + self.label_2.setObjectName(u"label_2") + + self.verticalLayout_2.addWidget(self.label_2) + + self.lens_comboBox = QComboBox(self.widget_2) + self.lens_comboBox.setObjectName(u"lens_comboBox") + + self.verticalLayout_2.addWidget(self.lens_comboBox) + + + self.gridLayout_7.addWidget(self.widget_2, 1, 0, 1, 1) + + self.widget_5 = QWidget(self.exif_options_group) + self.widget_5.setObjectName(u"widget_5") + self.verticalLayout_5 = QVBoxLayout(self.widget_5) + self.verticalLayout_5.setObjectName(u"verticalLayout_5") + self.label_5 = QLabel(self.widget_5) + self.label_5.setObjectName(u"label_5") + + self.verticalLayout_5.addWidget(self.label_5) + + self.image_description_comboBox = QComboBox(self.widget_5) + self.image_description_comboBox.setObjectName(u"image_description_comboBox") + + self.verticalLayout_5.addWidget(self.image_description_comboBox) + + + self.gridLayout_7.addWidget(self.widget_5, 2, 0, 1, 1) + + self.widget = QWidget(self.exif_options_group) + self.widget.setObjectName(u"widget") + self.verticalLayout = QVBoxLayout(self.widget) + self.verticalLayout.setObjectName(u"verticalLayout") + self.label = QLabel(self.widget) + self.label.setObjectName(u"label") + + self.verticalLayout.addWidget(self.label) + + self.make_comboBox = QComboBox(self.widget) + self.make_comboBox.setObjectName(u"make_comboBox") + + self.verticalLayout.addWidget(self.make_comboBox) + + + self.gridLayout_7.addWidget(self.widget, 0, 0, 1, 1) + + self.widget_3 = QWidget(self.exif_options_group) + self.widget_3.setObjectName(u"widget_3") + self.verticalLayout_3 = QVBoxLayout(self.widget_3) + self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.label_3 = QLabel(self.widget_3) + self.label_3.setObjectName(u"label_3") + + self.verticalLayout_3.addWidget(self.label_3) + + self.model_comboBox = QComboBox(self.widget_3) + self.model_comboBox.setObjectName(u"model_comboBox") + + self.verticalLayout_3.addWidget(self.model_comboBox) + + + self.gridLayout_7.addWidget(self.widget_3, 0, 1, 1, 1) + + self.widget_8 = QWidget(self.exif_options_group) + self.widget_8.setObjectName(u"widget_8") + self.verticalLayout_8 = QVBoxLayout(self.widget_8) + self.verticalLayout_8.setObjectName(u"verticalLayout_8") + self.label_8 = QLabel(self.widget_8) + self.label_8.setObjectName(u"label_8") + + self.verticalLayout_8.addWidget(self.label_8) + + self.copyright_info_comboBox = QComboBox(self.widget_8) + self.copyright_info_comboBox.setObjectName(u"copyright_info_comboBox") + + self.verticalLayout_8.addWidget(self.copyright_info_comboBox) + + + self.gridLayout_7.addWidget(self.widget_8, 3, 1, 1, 1) + + + self.verticalLayout_9.addWidget(self.exif_options_group) + + self.gps_groupBox = QGroupBox(self.tab_2) + self.gps_groupBox.setObjectName(u"gps_groupBox") + self.gps_groupBox.setEnabled(False) + self.horizontalLayout_4 = QHBoxLayout(self.gps_groupBox) + self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.gps_checkBox = QCheckBox(self.gps_groupBox) + self.gps_checkBox.setObjectName(u"gps_checkBox") + + self.horizontalLayout_4.addWidget(self.gps_checkBox) + + self.lat_lineEdit = QLineEdit(self.gps_groupBox) + self.lat_lineEdit.setObjectName(u"lat_lineEdit") + self.lat_lineEdit.setEnabled(False) + + self.horizontalLayout_4.addWidget(self.lat_lineEdit) + + self.long_lineEdit = QLineEdit(self.gps_groupBox) + self.long_lineEdit.setObjectName(u"long_lineEdit") + self.long_lineEdit.setEnabled(False) + + self.horizontalLayout_4.addWidget(self.long_lineEdit) + + + self.verticalLayout_9.addWidget(self.gps_groupBox) + + self.date_groupBox = QGroupBox(self.tab_2) + self.date_groupBox.setObjectName(u"date_groupBox") + self.date_groupBox.setEnabled(False) + self.horizontalLayout_2 = QHBoxLayout(self.date_groupBox) + self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.add_date_checkBox = QCheckBox(self.date_groupBox) + self.add_date_checkBox.setObjectName(u"add_date_checkBox") + + self.horizontalLayout_2.addWidget(self.add_date_checkBox) + + self.dateEdit = QDateEdit(self.date_groupBox) + self.dateEdit.setObjectName(u"dateEdit") + self.dateEdit.setEnabled(False) + self.dateEdit.setDateTime(QDateTime(QDate(2025, 1, 1), QTime(0, 0, 0))) + + self.horizontalLayout_2.addWidget(self.dateEdit) + + + self.verticalLayout_9.addWidget(self.date_groupBox) + + self.tabWidget.addTab(self.tab_2, "") + + self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) + + MainWindow.setCentralWidget(self.centralwidget) + self.statusBar = QStatusBar(MainWindow) + self.statusBar.setObjectName(u"statusBar") + MainWindow.setStatusBar(self.statusBar) + self.menuBar = QMenuBar(MainWindow) + self.menuBar.setObjectName(u"menuBar") + self.menuBar.setGeometry(QRect(0, 0, 450, 27)) + self.menuInfo = QMenu(self.menuBar) + self.menuInfo.setObjectName(u"menuInfo") + MainWindow.setMenuBar(self.menuBar) + + self.menuBar.addAction(self.menuInfo.menuAction()) + self.menuInfo.addAction(self.actionInfo) + + self.retranslateUi(MainWindow) + self.resize_checkbox.toggled.connect(self.resize_spinBox.setEnabled) + self.brightness_checkbox.toggled.connect(self.brightness_spinBox.setEnabled) + self.contrast_checkbox.toggled.connect(self.contrast_spinBox.setEnabled) + self.watermark_checkbox.toggled.connect(self.watermark_lineEdit.setEnabled) + self.rename_checkbox.toggled.connect(self.filename.setEnabled) + self.exif_checkbox.toggled.connect(self.exif_options_group.setEnabled) + self.exif_checkbox.toggled.connect(self.exif_copy_checkBox.setDisabled) + self.exif_copy_checkBox.toggled.connect(self.exif_checkbox.setDisabled) + self.exif_checkbox.toggled.connect(self.edit_exif_button.setEnabled) + self.add_date_checkBox.toggled.connect(self.dateEdit.setEnabled) + self.exif_checkbox.toggled.connect(self.date_groupBox.setEnabled) + self.exif_checkbox.toggled.connect(self.gps_groupBox.setEnabled) + self.gps_checkBox.toggled.connect(self.lat_lineEdit.setEnabled) + self.gps_checkBox.toggled.connect(self.long_lineEdit.setEnabled) + + self.tabWidget.setCurrentIndex(0) + self.font_size_comboBox.setCurrentIndex(2) + + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"OPTIMA-35", None)) + self.actionInfo.setText(QCoreApplication.translate("MainWindow", u"Info", None)) + self.input_path.setText("") + self.input_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter input folder", None)) + self.output_path.setText("") + self.output_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter output folder", None)) + self.input_folder_button.setText(QCoreApplication.translate("MainWindow", u"input", None)) + self.output_folder_button.setText(QCoreApplication.translate("MainWindow", u"output", None)) + self.groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Essential group", None)) + self.resize_checkbox.setText(QCoreApplication.translate("MainWindow", u"Resize", None)) + + self.optimize_checkBox.setText(QCoreApplication.translate("MainWindow", u"optimize", None)) + self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Extra stuff", None)) + self.watermark_lineEdit.setText("") + self.watermark_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter Watermark", None)) + self.brightness_checkbox.setText(QCoreApplication.translate("MainWindow", u"Brightness", None)) + self.grayscale_checkBox.setText(QCoreApplication.translate("MainWindow", u"Grayscale", None)) + self.watermark_checkbox.setText(QCoreApplication.translate("MainWindow", u"Watermark", None)) + self.contrast_checkbox.setText(QCoreApplication.translate("MainWindow", u"Contrast", None)) + self.font_size_comboBox.setItemText(0, QCoreApplication.translate("MainWindow", u"Tiny", None)) + self.font_size_comboBox.setItemText(1, QCoreApplication.translate("MainWindow", u"Small", None)) + self.font_size_comboBox.setItemText(2, QCoreApplication.translate("MainWindow", u"Normal", None)) + self.font_size_comboBox.setItemText(3, QCoreApplication.translate("MainWindow", u"Large", None)) + self.font_size_comboBox.setItemText(4, QCoreApplication.translate("MainWindow", u"Huge", None)) + + self.font_size_comboBox.setCurrentText(QCoreApplication.translate("MainWindow", u"Normal", None)) + self.rename_group.setTitle(QCoreApplication.translate("MainWindow", u"files", None)) + self.rename_checkbox.setText(QCoreApplication.translate("MainWindow", u"Rename", None)) + self.revert_checkbox.setText(QCoreApplication.translate("MainWindow", u"Revert order", None)) + self.filename.setText("") + self.filename.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter file name", None)) + self.start_button.setText(QCoreApplication.translate("MainWindow", u"Convert", None)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), QCoreApplication.translate("MainWindow", u"Main", None)) + self.exif_group.setTitle(QCoreApplication.translate("MainWindow", u"EXIF EXPERIMENTAL", None)) + self.exif_checkbox.setText(QCoreApplication.translate("MainWindow", u"own exif", None)) + self.exif_copy_checkBox.setText(QCoreApplication.translate("MainWindow", u"copy exif", None)) + self.edit_exif_button.setText(QCoreApplication.translate("MainWindow", u"edit exif", None)) + self.exif_options_group.setTitle(QCoreApplication.translate("MainWindow", u"Must", None)) + self.label_7.setText(QCoreApplication.translate("MainWindow", u"Artist", None)) + self.label_4.setText(QCoreApplication.translate("MainWindow", u"ISO", None)) + self.label_6.setText(QCoreApplication.translate("MainWindow", u"Scanner", None)) + self.label_2.setText(QCoreApplication.translate("MainWindow", u"Lens", None)) + self.label_5.setText(QCoreApplication.translate("MainWindow", u"Film", None)) + self.label.setText(QCoreApplication.translate("MainWindow", u"Make", None)) + self.make_comboBox.setCurrentText("") + self.make_comboBox.setPlaceholderText("") + self.label_3.setText(QCoreApplication.translate("MainWindow", u"Model", None)) + self.label_8.setText(QCoreApplication.translate("MainWindow", u"Copyright", None)) + self.gps_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"GPS", None)) + self.gps_checkBox.setText(QCoreApplication.translate("MainWindow", u"add gps", None)) + self.lat_lineEdit.setText("") + self.lat_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"latitude [S, N]", None)) + self.long_lineEdit.setText("") + self.long_lineEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"longitude [W, E]", None)) + self.date_groupBox.setTitle(QCoreApplication.translate("MainWindow", u"Optional", None)) + self.add_date_checkBox.setText(QCoreApplication.translate("MainWindow", u"add date", None)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"EXIF", None)) + self.menuInfo.setTitle(QCoreApplication.translate("MainWindow", u"Info", None)) + # retranslateUi + diff --git a/src/OptimaLab35/ui/main_window.ui b/src/OptimaLab35/ui/main_window.ui new file mode 100644 index 0000000..b75e88f --- /dev/null +++ b/src/OptimaLab35/ui/main_window.ui @@ -0,0 +1,919 @@ + + + MainWindow + + + + 0 + 0 + 450 + 708 + + + + + 350 + 677 + + + + + 500 + 1000 + + + + OPTIMA-35 + + + + + + + + 500 + 16777215 + + + + 0 + + + + Main + + + + + + + 400 + 16777215 + + + + + + + + + + Enter input folder + + + + + + + + + + Enter output folder + + + + + + + input + + + + + + + output + + + + + + + + + + + 400 + 16777215 + + + + Essential group + + + + + + Resize + + + + + + + false + + + 1 + + + 200 + + + 1 + + + 80 + + + + + + + + jpg + + + + + png + + + + + webp + + + + + + + + 1 + + + 100 + + + 80 + + + + + + + true + + + 1 + + + 9 + + + 6 + + + + + + + optimize + + + + + png_quality_spinBox + resize_checkbox + resize_spinBox + image_type + jpg_quality_spinBox + optimize_checkBox + + + + + + + 400 + 16777215 + + + + Extra stuff + + + + + + false + + + + + + Enter Watermark + + + + + + + Brightness + + + + + + + Grayscale + + + + + + + false + + + -100 + + + 100 + + + 10 + + + + + + + Watermark + + + + + + + false + + + -100 + + + 100 + + + -10 + + + + + + + Contrast + + + + + + + Normal + + + 2 + + + + Tiny + + + + + Small + + + + + Normal + + + + + Large + + + + + Huge + + + + + + + + + + + + 400 + 16777215 + + + + files + + + + + + Rename + + + + + + + Revert order + + + + + + + false + + + + + + Enter file name + + + + + + + + + + + 400 + 50 + + + + + + + true + + + 0 + + + + + + + true + + + Convert + + + + + + + + + + + EXIF + + + + + + EXIF EXPERIMENTAL + + + + + + true + + + own exif + + + + + + + copy exif + + + + + + + false + + + edit exif + + + + + + + + + + false + + + Must + + + + + + + + + Artist + + + + + + + + + + + + + + + + ISO + + + + + + + + + + + + + + + + Scanner + + + + + + + + + + + + + + + + Lens + + + + + + + + + + + + + + + + Film + + + + + + + + + + + + + + + + Make + + + + + + + + + + + + + + + + + + + + + + + Model + + + + + + + + + + + + + + + + Copyright + + + + + + + + + + + + + + + + false + + + GPS + + + + + + add gps + + + + + + + false + + + + + + latitude [S, N] + + + + + + + false + + + + + + longitude [W, E] + + + + + + + + + + false + + + Optional + + + + + + add date + + + + + + + false + + + + 0 + 0 + 0 + 2025 + 1 + 1 + + + + + + + + + + + + + + + + + + 0 + 0 + 450 + 27 + + + + + Info + + + + + + + + Info + + + + + + + resize_checkbox + toggled(bool) + resize_spinBox + setEnabled(bool) + + + 75 + 96 + + + 196 + 118 + + + + + brightness_checkbox + toggled(bool) + brightness_spinBox + setEnabled(bool) + + + 83 + 363 + + + 83 + 399 + + + + + contrast_checkbox + toggled(bool) + contrast_spinBox + setEnabled(bool) + + + 185 + 363 + + + 185 + 399 + + + + + watermark_checkbox + toggled(bool) + watermark_lineEdit + setEnabled(bool) + + + 83 + 435 + + + 237 + 435 + + + + + rename_checkbox + toggled(bool) + filename + setEnabled(bool) + + + 105 + 522 + + + 182 + 560 + + + + + exif_checkbox + toggled(bool) + exif_options_group + setEnabled(bool) + + + 130 + 105 + + + 236 + 328 + + + + + exif_checkbox + toggled(bool) + exif_copy_checkBox + setDisabled(bool) + + + 130 + 105 + + + 332 + 105 + + + + + exif_copy_checkBox + toggled(bool) + exif_checkbox + setDisabled(bool) + + + 332 + 105 + + + 130 + 105 + + + + + exif_checkbox + toggled(bool) + edit_exif_button + setEnabled(bool) + + + 134 + 107 + + + 79 + 170 + + + + + add_date_checkBox + toggled(bool) + dateEdit + setEnabled(bool) + + + 94 + 601 + + + 224 + 602 + + + + + exif_checkbox + toggled(bool) + date_groupBox + setEnabled(bool) + + + 126 + 103 + + + 224 + 589 + + + + + exif_checkbox + toggled(bool) + gps_groupBox + setEnabled(bool) + + + 94 + 103 + + + 224 + 535 + + + + + gps_checkBox + toggled(bool) + lat_lineEdit + setEnabled(bool) + + + 72 + 547 + + + 192 + 547 + + + + + gps_checkBox + toggled(bool) + long_lineEdit + setEnabled(bool) + + + 72 + 547 + + + 344 + 547 + + + + + diff --git a/src/OptimaLab35/ui/simple_dialog.py b/src/OptimaLab35/ui/simple_dialog.py new file mode 100644 index 0000000..b4a230b --- /dev/null +++ b/src/OptimaLab35/ui/simple_dialog.py @@ -0,0 +1,30 @@ +from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QLineEdit, QPushButton, QLabel +# ChatGPT +class SimpleDialog(QDialog): + def __init__(self): + super().__init__() + + # Set default properties + self.setGeometry(100, 100, 300, 100) + + # Create the layout + layout = QVBoxLayout() + + # Create the label for the message + self.message_label = QLabel(self) + + # Create the close button + close_button = QPushButton("Close", self) + close_button.clicked.connect(self.close) + + # Add widgets to layout + layout.addWidget(self.message_label) + layout.addWidget(close_button) + + # Set layout for the dialog + self.setLayout(layout) + + def show_dialog(self, title: str, message: str): + self.setWindowTitle(title) # Set the window title + self.message_label.setText(message) # Set the message text + self.exec() # Open the dialog as a modal window diff --git a/src/OptimaLab35/ui/simple_tui.py b/src/OptimaLab35/ui/simple_tui.py new file mode 100644 index 0000000..f8abd32 --- /dev/null +++ b/src/OptimaLab35/ui/simple_tui.py @@ -0,0 +1,60 @@ +from simple_term_menu import TerminalMenu + +class SimpleTUI: + """TUI parts using library simple_term_menu""" + def __init__(self): + pass + + def choose_menu(self, menu_title, choices): + """ Dynamic function to display content of a list and returnes which was selected.""" + menu_options = choices + menu = TerminalMenu( + menu_entries = menu_options, + title = menu_title, + menu_cursor = "> ", + menu_cursor_style = ("fg_gray", "bold"), + menu_highlight_style = ("bg_gray", "fg_black"), + cycle_cursor = True, + clear_screen = False + ) + menu.show() + return menu.chosen_menu_entry + + def multi_select_menu(self, menu_title, choices): + """ Dynamic function to display content of a list and returnes which was selected.""" + menu_options = choices + menu = TerminalMenu( + menu_entries = menu_options, + title = menu_title, + multi_select=True, + show_multi_select_hint=True, + menu_cursor_style = ("fg_gray", "bold"), + menu_highlight_style = ("bg_gray", "fg_black"), + cycle_cursor = True, + clear_screen = False + ) + menu.show() + choisen_values = menu.chosen_menu_entries + + if choisen_values == None: + print("Exiting...") + exit() + else: + return menu.chosen_menu_entries + + def yes_no_menu(self, message): # oh + menu_options = ["[y] yes", "[n] no"] + menu = TerminalMenu( + menu_entries = menu_options, + title = f"{message}", + menu_cursor = "> ", + menu_cursor_style = ("fg_red", "bold"), + menu_highlight_style = ("bg_gray", "fg_black"), + cycle_cursor = True, + clear_screen = False + ) + menu_entry_index = menu.show() + if menu_entry_index == 0: + return True + elif menu_entry_index == 1: + return False diff --git a/src/OptimaLab35/utils/__init__.py b/src/OptimaLab35/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/OptimaLab35/utils/utility.py b/src/OptimaLab35/utils/utility.py new file mode 100644 index 0000000..6dfa17d --- /dev/null +++ b/src/OptimaLab35/utils/utility.py @@ -0,0 +1,117 @@ +import yaml +import os + +class Utilities: + def __init__(self): + pass + + def read_yaml(self, yaml_file): + try: + with open(yaml_file, "r") as file: + data = yaml.safe_load(file) + return data + except (FileNotFoundError, PermissionError) as e: + print(f"Error loading settings file: {e}") + return + + def write_yaml(self, yaml_file, data): + try: + with open(yaml_file, "w") as file: + yaml.dump(data, file) + except PermissionError as e: + print(f"Error saving setings: {e}") + + def program_configs(self): + """Prepear folder for config and generate default exif if non aviable""" + program_folder = self._ensure_program_folder_exists() + if not os.path.isfile(f"{program_folder}/exif.yaml"): + self._default_exif(f"{program_folder}/exif.yaml") + + def _ensure_program_folder_exists(self): + program_folder = os.path.expanduser("~/.config/OptimaLab35") + print(program_folder) + if not os.path.exists(program_folder): + print("in not, make folder") + os.makedirs(program_folder) + return program_folder + + def _default_exif(self, file): + """Makes a default exif file.""" + print("Making default") + def_exif = { + "artist": [ + "Mr Finchum", + "John Doe" + ], + "copyright_info": [ + "All Rights Reserved", + "CC BY-NC 4.0", + "No Copyright" + ], + "image_description": [ + "ILFORD DELTA 3200", + "ILFORD ILFOCOLOR", + "LomoChrome Turquoise", + "Kodak 200" + ], + "iso": [ + "200", + "400", + "1600", + "3200" + ], + "lens": [ + "Nikon LENS SERIES E 50mm", + "AF NIKKOR 35-70mm", + "Canon FD 50mm f/1.4 S.S.C" + ], + "make": [ + "Nikon", + "Canon" + ], + "model": [ + "FG", + "F50", + "AE-1" + ], + "user_comment": [ + "Scanner.NORITSU-KOKI", + "Scanner.NA" + ] + } + self.write_yaml(file, def_exif) + + def append_number_to_name(self, base_name: str, current_image: int, total_images: int, invert: bool): + """"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}" + + def yes_no(self, str): + """Ask user y/n question""" + while True: + choice = input(f"{str} (y/n): ") + if choice == "y": + return True + elif choice == "n": + return False + else: + print("Not a valid option, try again.") + + def progress_bar(self, current, total, barsize = 50): + if current > total: + print("\033[91mThis bar has exceeded its limits!\033[0m Maybe the current value needs some restraint?") + return + progress = int((barsize / total) * current) + rest = barsize - progress + if rest <= 2: rest = 0 + # Determine the number of digits in total + total_digits = len(str(total)) + # Format current with leading zeros + current_formatted = f"{current:0{total_digits}}" + print(f"{current_formatted}|{progress * '-'}>{rest * ' '}|{total}", end="\r") + if current == total: print("") From d800c3b89279d0dce2b91eeef68030757ebdc12b Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:20:04 +0100 Subject: [PATCH 04/12] Deleted because no need of it anymore. --- config/exif_example.yaml | 34 ---------------------------------- requirements.txt | 4 ---- 2 files changed, 38 deletions(-) delete mode 100644 config/exif_example.yaml delete mode 100644 requirements.txt diff --git a/config/exif_example.yaml b/config/exif_example.yaml deleted file mode 100644 index 8833f78..0000000 --- a/config/exif_example.yaml +++ /dev/null @@ -1,34 +0,0 @@ -artist: - - Mr. Finchum - - John Doe -copyright_info: - - All Rights Reserved - - CC BY-NC 4.0 - - No Copyright -image_description: - - ILFORD DELTA 3200 - - ILFORD ILFOCOLOR - - LomoChrome Turquoise - - Kodak 200 -iso: - - "100" - - "200" - - "400" - - "800" - - "1000" - - "1600" - - "3200" -lens: - - Nikon LENS SERIES E 50mm - - AF NIKKOR 35-70mm - - Canon FD 50mm f/1.4 S.S.C -make: - - Nikon - - Canon -model: - - FG - - F50 - - AE-1 -user_comment: - - Scanner.NORITSU-KOKI - - Scanner.NA diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c13d77b..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -pyyaml -optima35 -pyside6 -simple_term_menu From 12c479cedef48f5e83639c624e27f71bff0544ed Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:20:31 +0100 Subject: [PATCH 05/12] Updated to v0.0.4-a1 --- CHANGELOG.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eb4e75..c16fe79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ # Changelog ## 0.0.x +### 0.0.4-a1 +- Refactored project structure, moving all code to the `src` directory. +- Adjusted imports and setup to accommodate the new folder structure. +- Skipped version numbers to `.4` due to PyPI versioning constraints (testing purposes). + ### 0.0.1 - Initial UI-Focused Release -- Forked from OPTIMA35 -- Refactored the project to include only UI elements; all core OPTIMA35 files have been removed. -- Updated the changelog to reflect the changes. -- Integrated with OPTIMA35 from pip, ensuring TUI and GUI functionalities work seamlessly. -- Made initial adjustments to the README for clarity and structure. +- Forked from OPTIMA35. +- Removed core OPTIMA35 files to focus exclusively on UI components. +- Integrated OPTIMA35 functionality via the pip package. +- Ensured both TUI and GUI modes operate seamlessly. +- Revised the README for improved clarity and structure. From 38f37687321af8bfff56a28e5e37a42505696106 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:20:39 +0100 Subject: [PATCH 06/12] Rough update. --- README.md | 72 +++++++++++++++++++------------------------------------ 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 24c56d1..2cb18ee 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,18 @@ # OptimaLab35 -UI for [OPTIMA35](https://gitlab.com/CodeByMrFinchum/optima-35) package, WIP. + +[OptimaLab35](https://gitlab.com/CodeByMrFinchum/OptimaLab35) is UI for [optima35](https://gitlab.com/CodeByMrFinchum/optima35), currently in heavy development. ## Overview -**OPTIMA-35** (**Organizing, Processing, Tweaking Images, and Modifying scanned Analogs from 35mm Film**) is a Python-based project designed to streamline the management and editing of metadata and images from analog photography. While it was created with analog photography in mind, it is versatile enough to handle any type of images. +**OPTIMA35** (**Organizing, Processing, Tweaking Images, and Modifying scanned Analogs from 35mm Film**) is a Python-based project designed to streamline the management and editing of metadata and images from analog photography. While it was created with analog photography in mind, it is versatile enough to handle any type of images. ## Current Status +### Installation +install via pip and all dependencies will be installed (exept simple-term-menu which is used for TUI, but that package is only aviable for linux) +```Bash +pip install OptimaLab35 +``` ### Development and Versioning Notes **OptimaLab35** is currently in an **alpha stage** and under active development. As a result: @@ -18,53 +24,34 @@ While the project follows a semantic versioning structure (major.minor.patch), b **OptimaLab35** supports two modes: **GUI** and **TUI**. - The **GUI** is loaded by default if **PySide6** is available. -- The **TUI** serves as a fallback when **PySide6** is unavailable or can be started explicitly using the `--tui` option with `main.py`. +- The **TUI** serves as a fallback when **PySide6** (PySide6 is a dependency..) is unavailable or can be started explicitly using the `--tui` option with `main.py`. While all features are implemented and functional, the designs of both the GUI and TUI are not yet finalized. Some safety checks are still under development. -### Available Features: - -**Implemented Features:** -- **Image Processing:** - - Resizing - - Renaming with order adjustment - - Grayscale conversion - - Brightness adjustment - - Contrast adjustment -- **EXIF Management:** - - Copy EXIF data - - Add custom EXIF information - - Add GPS data - - Add a date to EXIF - - Remove EXIF -- **Watermarking** +### OptimaLab35 used features from optima35 to: +**Image Processing:** +- Resizing +- Renaming with order adjustment +- Grayscale conversion +- Brightness adjustment +- Contrast adjustment +**EXIF Management:** +- Copy EXIF data +- Add custom EXIF information +- Add GPS data +- Add a date to EXIF +- Remove EXIF +**Watermarking** ### Preview GUI **GUI for OPTIMA-35** with KvArcDark theme on Linux - -**Main windows** - -![main_tab](https://gitlab.com/CodeByMrFinchum/optima-35/-/raw/main/media/main_tab.png){width=40%} - -**Exif tab when opened, settings disabled by default** - -![exif_tab_disabled](https://gitlab.com/CodeByMrFinchum/optima-35/-/raw/main/media/exif_tab_disabled.png){width=40%} - -**Exif tab after enabeling own exif data** - -![exif_tab_enabled](https://gitlab.com/CodeByMrFinchum/optima-35/-/raw/main/media/exif_tab_enabled.png){width=40%} - -**Dialog window to modify exif file without need to open yaml file** - -![exifeditor](https://gitlab.com/CodeByMrFinchum/optima-35/-/raw/main/media/exifeditor.png){width=40%} +OUT OF DATE, comming soon ### Preview TUI **asciinema recording for TUI** - -![demo_gif](https://gitlab.com/CodeByMrFinchum/optima-35/-/raw/main/media/demo_v041.gif) - +OUT OF DATE, comming soon ## Dependencies @@ -75,15 +62,6 @@ While all features are implemented and functional, the designs of both the GUI a - **pyside6**: For the GUI mode. - **simple_term_menu**: For the TUI mode. -### Installing Dependencies - -You can install the dependencies using the requirements file - -Using `pip`: -```bash -pip install -r requirements.txt -``` - # Use of LLMs In the interest of transparency, I disclose that Generative AI (GAI) large language models (LLMs), including OpenAI’s ChatGPT and Ollama models (e.g., OpenCoder and Qwen2.5-coder), have been used to assist in this project. From 18ba2ab0b2f752ae1ed293aa92da846fa1e75414 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:21:43 +0100 Subject: [PATCH 07/12] Used for building pypi package. --- pyproject.toml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b6b355f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "OptimaLab35" +version = "0.0.4-a1" +authors = [{ name = "Mr. Finchum" }] +description = "User interface for OPTIMA35." +readme = "README.md" +requires-python = ">=3.8" +dependencies = ["optima35", "pyside6"] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Operating System :: OS Independent", +] + +[project.urls] +Homepage = "https://gitlab.com/CodeByMrFinchum/OptimaLab35" + +[project.scripts] +OptimaLab35 = "OptimaLab35.main:main" + +# used for when installing package localy. +#[tool.hatch.build.targets.wheel] +#packages = ["src/OptimaLab35"] From a1ccd59bc80f9e760824d61704ef7511c7ea060d Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:22:07 +0100 Subject: [PATCH 08/12] updated version number to 0.0.4-a1 --- src/OptimaLab35/gui.py | 2 +- src/OptimaLab35/tui.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OptimaLab35/gui.py b/src/OptimaLab35/gui.py index b5ec100..9dc3c68 100644 --- a/src/OptimaLab35/gui.py +++ b/src/OptimaLab35/gui.py @@ -28,7 +28,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow): def __init__(self): super(OptimaLab35, self).__init__() self.name = "OptimaLab35" - self.version = "0.0.3-a1" + self.version = "0.0.4-a1" self.ui = Ui_MainWindow() self.ui.setupUi(self) self.o = OptimaManager() diff --git a/src/OptimaLab35/tui.py b/src/OptimaLab35/tui.py index d89cd64..caf4c99 100644 --- a/src/OptimaLab35/tui.py +++ b/src/OptimaLab35/tui.py @@ -8,7 +8,7 @@ from OptimaLab35.ui.simple_tui import SimpleTUI class OptimaLab35_lite(): def __init__(self): self.name = "OptimaLab35-lite" - self.version = "0.0.3-a1" + self.version = "0.0.4-a1" self.o = OptimaManager() self.u = Utilities() self.tui = SimpleTUI() From 1885ddbfefadad7bd1d40c7ba4d8c04df3257d1a Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 12:36:19 +0100 Subject: [PATCH 09/12] Fixed toml to build --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b6b355f..4ac1707 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,5 @@ Homepage = "https://gitlab.com/CodeByMrFinchum/OptimaLab35" [project.scripts] OptimaLab35 = "OptimaLab35.main:main" -# used for when installing package localy. -#[tool.hatch.build.targets.wheel] -#packages = ["src/OptimaLab35"] +[tool.hatch.build.targets.wheel] +packages = ["src/OptimaLab35"] From ffb9e4b8e0a7b05c532ae677fd414e538239a5e1 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 13:45:25 +0100 Subject: [PATCH 10/12] v0.0.4-a2, added __version__ so pypi version is dynamic --- src/OptimaLab35/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/OptimaLab35/__init__.py diff --git a/src/OptimaLab35/__init__.py b/src/OptimaLab35/__init__.py new file mode 100644 index 0000000..f22196d --- /dev/null +++ b/src/OptimaLab35/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.4-a2" From df21f4e4c0c6db5e433a253e1c26ca34bdb59819 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 13:45:59 +0100 Subject: [PATCH 11/12] v0.0.4-a2, added __version__ so pypi version is dynamic --- CHANGELOG.md | 3 +++ pyproject.toml | 5 ++++- src/OptimaLab35/gui.py | 3 ++- src/OptimaLab35/main.py | 2 +- src/OptimaLab35/tui.py | 3 ++- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c16fe79..3863ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## 0.0.x +### 0.0.4-a2 +- Adding __version__ to `__init__.py` so version is automaticly updated in program as well as pypi. + ### 0.0.4-a1 - Refactored project structure, moving all code to the `src` directory. - Adjusted imports and setup to accommodate the new folder structure. diff --git a/pyproject.toml b/pyproject.toml index 4ac1707..afc1b33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "OptimaLab35" -version = "0.0.4-a1" +dynamic = ["version"] authors = [{ name = "Mr. Finchum" }] description = "User interface for OPTIMA35." readme = "README.md" @@ -24,3 +24,6 @@ OptimaLab35 = "OptimaLab35.main:main" [tool.hatch.build.targets.wheel] packages = ["src/OptimaLab35"] + +[tool.hatch.version] +path = "src/OptimaLab35/__init__.py" diff --git a/src/OptimaLab35/gui.py b/src/OptimaLab35/gui.py index 9dc3c68..9b34deb 100644 --- a/src/OptimaLab35/gui.py +++ b/src/OptimaLab35/gui.py @@ -7,6 +7,7 @@ from OptimaLab35.utils.utility import Utilities from OptimaLab35.ui.main_window import Ui_MainWindow from OptimaLab35.ui.exif_handler_window import ExifEditor from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class +from OptimaLab35 import __version__ from PySide6 import QtWidgets from PySide6.QtWidgets import ( @@ -28,7 +29,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow): def __init__(self): super(OptimaLab35, self).__init__() self.name = "OptimaLab35" - self.version = "0.0.4-a1" + self.version = __version__ self.ui = Ui_MainWindow() self.ui.setupUi(self) self.o = OptimaManager() diff --git a/src/OptimaLab35/main.py b/src/OptimaLab35/main.py index dad2062..6892ca8 100644 --- a/src/OptimaLab35/main.py +++ b/src/OptimaLab35/main.py @@ -1,7 +1,7 @@ import os from argparse import ArgumentParser from OptimaLab35 import gui, tui - +from OptimaLab35 import __version__ # Mainly from ChatGPT def check_pyside_installed(): try: diff --git a/src/OptimaLab35/tui.py b/src/OptimaLab35/tui.py index caf4c99..f547abc 100644 --- a/src/OptimaLab35/tui.py +++ b/src/OptimaLab35/tui.py @@ -4,11 +4,12 @@ from datetime import datetime from optima35.core import OptimaManager from OptimaLab35.utils.utility import Utilities from OptimaLab35.ui.simple_tui import SimpleTUI +from OptimaLab35 import __version__ class OptimaLab35_lite(): def __init__(self): self.name = "OptimaLab35-lite" - self.version = "0.0.4-a1" + self.version = __version__ self.o = OptimaManager() self.u = Utilities() self.tui = SimpleTUI() From bbfa4df82233b406c8de281905e537b3bf57a1b1 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 3 Jan 2025 13:51:05 +0100 Subject: [PATCH 12/12] Fixed formation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2cb18ee..241f0e3 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,14 @@ While all features are implemented and functional, the designs of both the GUI a - Grayscale conversion - Brightness adjustment - Contrast adjustment + **EXIF Management:** - Copy EXIF data - Add custom EXIF information - Add GPS data - Add a date to EXIF - Remove EXIF + **Watermarking**