diff --git a/.gitignore b/.gitignore index 197fa4e..b6668dc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ debug.* debug_log/ .ropeproject/ __pycache__/ -config/settings.yaml +config/tui_settings.yaml +config/exif.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7420e80..4ad9958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,31 @@ # Changelog -## 0.3.x +## 0.4.x +### 0.4.1: Finished GUI and TUI +- Both **GUI** and **TUI** now fully utilize the `optima35` class for core functionality. +- All planned features are operational and integrated into both interfaces. +- **Next Step**: Bug hunting and optimization. +- The fork `optima-35-tui` has been deleted, as **OPTIMA-35** now includes both **TUI** and **GUI** versions within the same project. +### 0.4.0: Splitting into Classes +- **Code Organization:** + - Core functionality for **Optima-35** is now refactored into `optima35.py` for better separation of logic and reusability. + - The **GUI code** is moved to `gui.py` for a cleaner structure and maintainability. + - The **TUI logic** will be moved into `tui.py`, making it modular and focused. + - The original TUI fork will be deleted to streamline operations. + +- **Main File Enhancements:** + - `main.py` is now the entry point of the application and determines whether to start the GUI or TUI based on: + - Operating system. + - The presence of required dependencies (e.g., PySide for GUI). + - Command-line arguments (`--tui` flag). + +- **Benefits:** + - Clear separation of concerns between GUI, TUI, and core functionalities. + - Improved readability, maintainability, and scalability of the project. + - Easier to test and debug individual components. + +## 0.3.x ### 0.3.4: Features Finalized - Core Features Completed: - All functions are now available, though minor bugs may exist. diff --git a/README.md b/README.md index 058b321..85a20f2 100644 --- a/README.md +++ b/README.md @@ -2,56 +2,90 @@ ## Overview -**OPTIMA-35** (**Organizing, Processing, Tweaking Images and Modifying scanned Analogs from 35mm Film**) is a Python-based project designed to provide a streamlined way to manage and edit metadata and images from analog photography. But can be used for any images. +**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. -This project is a *port* of my earlier work, an collection of [bash script](https://gitlab.com/sf-bashscripts/analogphotography), transitioning functionality to a more modular and maintainable design. +This project replaces my earlier [analogphotography](https://gitlab.com/sf-bashscripts/analogphotography) bash script collection, which has now been archived in favor of OPTIMA-35. -**Please check** if a new branch is available and read the **changelog** to see the progress and current features of the program. The README might sometimes lag behind. - -OPTIMA-35 is evolving! The project is transitioning from a terminal-based user interface (TUI) to a graphical user interface (GUI) using Qt (via PySide6). First TUI version was forked to [OPTIMA-35 TUI](https://gitlab.com/python_projects3802849/optima-35-tui). I intend to keep the TUI version functional since it is usefull for headless setup. - -**GUI for OPTIMA-35 v0.3.4** with KvArcDark theme - -*Last preview until GUI is finished.* +**OPTIMA-35** is a cross-platform program. The **GUI** works on Linux and Windows (tested) and is expected to run on macOS. The **TUI** is currently Linux-only, as its dependency is exclusive to Linux. -![img](https://gitlab.com/python_projects3802849/optima-35/-/raw/main/media/main_tab.png){width=40%} -![img](https://gitlab.com/python_projects3802849/optima-35/-/raw/main/media/exif_tab.png){width=40%} -![img](https://gitlab.com/python_projects3802849/optima-35/-/raw/main/media/exifeditor.png){width=40%} -## **Current Status** -**The README is temporarily outdated** while the GUI version is under development. For the latest updates, please check the **changelog**—I always maintain a detailed log of changes. +## Current Status + +### PLEASE NOTE +The project is under active development. As a result: +- The README may sometimes be outdated. +- I encourage you to check for new branches and read the **CHANGELOG**, which is consistently updated and well-documented. +- Bugs or unforeseen behavior may occur. + +**OPTIMA-35** 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`. + +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: -- Core features: - - resizing - - renaming - - grayscale - - Change brightness - - Change contrast - - Exif management - - Add watermark + +**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** + + +### Preview GUI +**GUI for OPTIMA-35** with KvArcDark theme on Linux + +**Main windows** +![main_tab](https://gitlab.com/python_projects3802849/optima-35/-/raw/main/media/main_tab.png){width=40%} + +**Exif tab when opened, settings disabled by default** +![exif_tab_disabled](https://gitlab.com/python_projects3802849/optima-35/-/raw/main/media/exif_tab_disabled.png){width=40%} + +**Exif tab after enabeling own exif data** +![exif_tab_enabled](https://gitlab.com/python_projects3802849/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/python_projects3802849/optima-35/-/raw/main/media/exifeditor.png){width=40%} + +### Preview TUI +**asciinema recording for TUI** + +![demo_gif](https://gitlab.com/python_projects3802849/optima-35/-/raw/main/media/demo_v041.gif) + ## Dependencies -To run **OPTIMA-35**, the following Python libraries are required: +**OPTIMA-35** has two modes: **GUI** and **TUI**. Each mode has its own set of dependencies, so you don’t need to install TUI dependencies if you only plan to use the GUI (and vice versa). -- **pyyaml**: To handle YAML files for configuration and settings. -- **piexif**: To read, modify, and write EXIF metadata. -- **Pillow**: For image processing. -- **pyside6**: GUI +**Required Dependencies:** +- **pyyaml**: For handling YAML files (configuration and settings). +- **piexif**: For reading, modifying, and writing EXIF metadata. +- **pillow**: For image processing. +- **pyside6**: For the GUI mode. +- **simple_term_menu**: For the TUI mode. ### Installing Dependencies -You can install the dependencies using `pip`: +You can install the dependencies using the respective requirements file for your desired mode (**TUI** or **GUI**). +Using `pip`: ```bash -pip install -r requirements.txt +pip install -r requirements_gui.txt ``` -Alternatively, you can use `conda` or its alternatives (`anaconda`, `mamba`, `micromamba`): - +Alternatively, if you use **conda** or its alternatives (**anaconda**, **mamba**, **micromamba**), run: ```bash -conda install -c conda-forge --file requirements.txt +conda install -c conda-forge --file requirements_gui.txt ``` # Use of LLMs diff --git a/config/exif_example.yaml b/config/exif_example.yaml index 1127702..11b03fe 100644 --- a/config/exif_example.yaml +++ b/config/exif_example.yaml @@ -1,35 +1,34 @@ -# Example file artist: - - Mr. Finchum - - John Doe +- Mr. Finchum +- John Doe copyright_info: - - All Rights Reserved - - CC BY-NC 4.0 - - No Copyright +- All Rights Reserved +- CC BY-NC 4.0 +- No Copyright image_description: - - ILFORD DELTA 3200 - - ILFORD ILFOCOLOR - - LomoChrome Turquoise - - Kodak 200 +- ILFORD DELTA 3200 +- ILFORD ILFOCOLOR +- LomoChrome Turquoise +- Kodak 200 iso: - - "100" - - "200" - - "400" - - "800" - - "1000" - - "1600" - - "3200" +- '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 +- Nikon LENS SERIES E 50mm +- AF NIKKOR 35-70mm +- Canon FD 50mm f/1.4 S.S.C make: - - Nikon - - Canon +- Nikon +- Canon model: - - FG - - F50 - - AE-1 +- FG +- F50 +- AE-1 user_comment: - - Scanner.NORITSU-KOKI - - Scanner.NA +- Scanner.NORITSU-KOKI +- Scanner.NA diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..710dfb6 --- /dev/null +++ b/gui.py @@ -0,0 +1,275 @@ +import sys +import os +from datetime import datetime + +from optima.optima35 import OPTIMA35 +from utils.utility import Utilities +from ui.main_window import Ui_MainWindow +from ui.exif_handler_window import ExifEditor + +from PySide6 import QtWidgets +from PySide6.QtWidgets import ( + QMessageBox, + QApplication, + QMainWindow, + QWidget, + QVBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QCheckBox, + QFileDialog, + QHBoxLayout, + QSpinBox, +) + +class Optima35GUI(QMainWindow, Ui_MainWindow): + def __init__(self, exif_file): + super(Optima35GUI, self).__init__() + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + self.o = OPTIMA35() + self.u = Utilities() + self.exif_file = exif_file + self.exif_data = None + + self.setWindowTitle(f"{self.o.name} {self.o.version}") + self.default_ui_layout() + self.define_gui_interaction() + + if exif_file == "config/exif_example.yaml": + self.change_statusbar("Using example exif...", 10000) + + def default_ui_layout(self): + self.ui.png_quality_spinBox.setVisible(False) + + def define_gui_interaction(self): + self.ui.input_folder_button.clicked.connect(self.browse_input_folder) + self.ui.output_folder_button.clicked.connect(self.browse_output_folder) + self.ui.start_button.clicked.connect(self.process) + self.ui.image_type.currentIndexChanged.connect(self.update_quality_options) + + self.ui.exif_checkbox.stateChanged.connect( + lambda state: self.handle_checkbox_state(state, 2, self.populate_exif) + ) + self.ui.tabWidget.currentChanged.connect(self.on_tab_changed) + self.ui.edit_exif_button.clicked.connect(self.open_exif_editor) + self.ui.restart_button.clicked.connect(self.restart_app) + + def process(self): + #self.ui.start_button.setEnabled(False) + #self.ui.restart_button.setEnabled(False) + self.check_options() # Get all user selected data + input_folder_valid = os.path.exists(self.o.settings["input_folder"]) + output_folder_valid = os.path.exists(self.o.settings["output_folder"]) + if not input_folder_valid or not output_folder_valid: + QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...") + return + + input_folder = self.o.settings["input_folder"] + output_folder = self.o.settings["output_folder"] + + image_files = [ + f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp")) + ] + i = 1 + for image_file in image_files: + input_path = os.path.join(input_folder, image_file) + if self.o.settings["new_file_names"] != False: + image_name = self.o.name_images(self.o.settings["new_file_names"], i, len(image_files), self.o.settings["invert_image_order"]) + else: + image_name = os.path.splitext(image_file)[0] + output_path = os.path.join(output_folder, image_name) + + self.o.process(input_path, output_path) + self.handle_qprogressbar(i, len(image_files)) + i += 1 + + QMessageBox.information(self, "Information", "Finished") + #self.ui.start_button.setEnabled(True) + #self.ui.restart_button.setEnabled(True) + self.ui.progressBar.setValue(0) + + def open_exif_editor(self): + """Open the EXIF Editor.""" + self.exif_editor = ExifEditor(self.exif_data) + self.exif_editor.exif_data_updated.connect(self.update_exif_data) + self.exif_editor.show() + + def update_exif_data(self, updated_exif_data): + """Update the EXIF data.""" + self.exif_data = updated_exif_data + self.populate_exif() + + def handle_checkbox_state(self, state, desired_state, action): + """Perform an action based on the checkbox state and a desired state. Have to use lambda when calling.""" + if state == desired_state: + action() + + def on_tab_changed(self, index): + """Handle tab changes.""" + # chatgpt + if index == 1: # EXIF Tab + self.handle_exif_file("read") + elif index == 0: # Main Tab + self.handle_exif_file("write") + + def handle_exif_file(self, do): + if do == "read": + self.exif_data = self.u.read_yaml(self.exif_file) + elif do == "write": + self.u.write_yaml(self.exif_file, self.exif_data) + + def populate_exif(self): + # partly chatGPT + # Mapping of EXIF fields to comboboxes in the UI + combo_mapping = { + "make": self.ui.make_comboBox, + "model": self.ui.model_comboBox, + "lens": self.ui.lens_comboBox, + "iso": self.ui.iso_comboBox, + "image_description": self.ui.image_description_comboBox, + "user_comment": self.ui.user_comment_comboBox, + "artist": self.ui.artist_comboBox, + "copyright_info": self.ui.copyright_info_comboBox, + } + self.populate_comboboxes(combo_mapping) + + def populate_comboboxes(self, combo_mapping): + """Populate comboboxes with EXIF data.""" + # ChatGPT + for field, comboBox in combo_mapping.items(): + comboBox.clear() # Clear existing items + comboBox.addItems(map(str, self.exif_data.get(field, []))) + + def update_quality_options(self): + """Update visibility of quality settings based on selected format.""" + # ChatGPT + selected_format = self.ui.image_type.currentText() + # Hide all quality settings + self.ui.png_quality_spinBox.setVisible(False) + self.ui.jpg_quality_spinBox.setVisible(False) + # Show relevant settings + if selected_format == "jpg": + self.ui.jpg_quality_spinBox.setVisible(True) + elif selected_format == "webp": + self.ui.jpg_quality_spinBox.setVisible(True) + elif selected_format == "png": + self.ui.png_quality_spinBox.setVisible(True) + + def browse_input_folder(self): + folder = QFileDialog.getExistingDirectory(self, "Select Input Folder") + if folder: + self.ui.input_path.setText(folder) + + def browse_output_folder(self): + folder = QFileDialog.getExistingDirectory(self, "Select Output Folder") + if folder: + self.ui.output_path.setText(folder) + + def change_statusbar(self, msg, timeout = 500): + self.ui.statusBar.showMessage(msg, timeout) + + def handle_qprogressbar(self, current, total): + progress = int((100 / total) * current) + self.ui.progressBar.setValue(progress) + + def check_options(self): + try: + self.o.settings["input_folder"] = self.ui.input_path.text() + self.o.settings["output_folder"] = self.ui.output_path.text() + self.o.settings["file_format"] = self.ui.image_type.currentText() + self.o.settings["jpg_quality"] = int(self.ui.jpg_quality_spinBox.text()) + self.o.settings["png_compression"] = int(self.ui.png_quality_spinBox.text()) + self.o.settings["invert_image_order"] = self.ui.revert_checkbox.isChecked() + self.o.settings["grayscale"] = self.ui.grayscale_checkBox.isChecked() + self.o.settings["copy_exif"] = self.ui.exif_copy_checkBox.isChecked() + self.o.settings["own_exif"] = self.ui.exif_checkbox.isChecked() + self.o.settings["font_size"] = self.ui.font_size_comboBox.currentIndex() + 1 + self.o.settings["optimize"] = self.ui.optimize_checkBox.isChecked() + self.o.settings["own_date"] = self.ui.add_date_checkBox.isChecked() + + if self.ui.resize_checkbox.isChecked(): + self.o.settings["resize"] = int(self.ui.resize_spinBox.text()) + + if self.ui.brightness_checkbox.isChecked(): + self.o.settings["brightness"] = int(self.ui.brightness_spinBox.text()) + + if self.ui.contrast_checkbox.isChecked(): + self.o.settings["contrast"] = int(self.ui.contrast_spinBox.text()) + + if self.ui.rename_checkbox.isChecked(): + if self.ui.filename.text() != "": + self.o.settings["new_file_names"] = self.ui.filename.text() + else: + self.o.settings["new_file_names"] = False + else: + self.o.settings["new_file_names"] = False + + + if self.ui.watermark_checkbox.isChecked(): + if self.ui.watermark_lineEdit.text() != "": + self.o.settings["watermark"] = self.ui.watermark_lineEdit.text() + else: + self.o.settings["watermark"] = False + else: + self.o.settings["watermark"] = False + + if self.o.settings["own_exif"]: + self.o.selected_exif = self.collect_selected_exif() + if self.ui.add_date_checkBox.isChecked(): + self.o.selected_exif["date_time_original"] = self.get_date() + if self.ui.gps_checkBox.isChecked(): + self.o.settings["gps"] = [self.ui.lat_lineEdit.text(), self.ui.long_lineEdit.text()] + else: + self.o.settings["gps"] = False + + except Exception as e: + print(f"Whoops: {e}") + + def get_date(self): + date_input = self.ui.dateEdit.date().toString("yyyy-MM-dd") + new_date = datetime.strptime(date_input, "%Y-%m-%d") + return new_date.strftime("%Y:%m:%d 00:00:00") + + def collect_selected_exif(self): + user_data = {} + user_data["make"] = self.ui.make_comboBox.currentText() + user_data["model"] = self.ui.model_comboBox.currentText() + user_data["lens"] = self.ui.lens_comboBox.currentText() + user_data["iso"] = self.ui.iso_comboBox.currentText() + user_data["image_description"] = self.ui.image_description_comboBox.currentText() + user_data["user_comment"] = self.ui.user_comment_comboBox.currentText() + user_data["artist"] = self.ui.artist_comboBox.currentText() + user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText() + user_data["software"] = f"{self.o.name} {self.o.version}" + return user_data + + def rebuild_ui(self): + # Define the bash script to execute + bash_script = "/home/sam/git/gitlab_public/optima-35/rebuild_ui.sh" + os.system(bash_script) + + def restart_app(self): + """Restarts the application.""" + self.rebuild_ui() + # chatGPT + python = sys.executable # Path to the Python interpreter + os.execv(python, [python] + sys.argv) + +def main(exif_file): + app = QtWidgets.QApplication(sys.argv) + window = Optima35GUI(exif_file=exif_file) + window.show() + app.exec() + +if __name__ == "__main__": + if os.path.isfile("config/exif.yaml"): + exif_file = "config/exif.yaml" + print("Fall back to exif example file...") + elif os.path.isfile("config/exif_example.yaml"): + exif_file = "config/exif_example.yaml" + else: + print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...") + exit() + main(exif_file) diff --git a/main.py b/main.py index c77c22c..3c79c78 100644 --- a/main.py +++ b/main.py @@ -1,361 +1,53 @@ -import sys import os -import re -import time -import subprocess -from datetime import datetime -from utils.utility import Utilities -from utils.image_handler import ImageProcessor, ExifHandler +from argparse import ArgumentParser -from ui.main_window import Ui_MainWindow -from ui.exif_handler_window import ExifEditor +# Mainly from ChatGPT +def check_pyside_installed(): + try: + import PySide6 # Replace with PySide2 if using that version + return True + except ImportError: + return False -from PySide6 import QtWidgets -from PySide6.QtWidgets import ( - QMessageBox, - QApplication, - QMainWindow, - QWidget, - QVBoxLayout, - QLabel, - QLineEdit, - QPushButton, - QCheckBox, - QFileDialog, - QHBoxLayout, - QSpinBox, -) +def start_gui(): + import gui + gui.main(exif_file) -class Optima35QT6(QMainWindow, Ui_MainWindow): - def __init__(self, exif_file, version): - super(Optima35QT6, self).__init__() - self.ui = Ui_MainWindow() - self.ui.setupUi(self) +def start_tui(): + import tui + tui.main(exif_file, tui_settings_file) - self.define_settings(exif_file, version) - self.setWindowTitle(f"{self.name} v{self.version}") - self.default_ui_layout() - self.define_gui_interaction() -# GUI - def default_ui_layout(self): - self.ui.png_quality_spinBox.setVisible(False) +def 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() - def define_gui_interaction(self): - self.ui.input_folder_button.clicked.connect(self.browse_input_folder) - self.ui.output_folder_button.clicked.connect(self.browse_output_folder) - self.ui.start_button.clicked.connect(self.process) - self.ui.image_type.currentIndexChanged.connect(self.update_quality_options) - - self.ui.exif_checkbox.stateChanged.connect( - lambda state: self.handle_checkbox_state(state, 2, self.populate_exif) - ) - - self.ui.tabWidget.currentChanged.connect(self.on_tab_changed) - self.ui.edit_exif_button.clicked.connect(self.open_exif_editor) - self.ui.restart_button.clicked.connect(self.restart_app) - - def open_exif_editor(self): - """Open the EXIF Editor.""" - self.exif_editor = ExifEditor(self.exif_data) - self.exif_editor.exif_data_updated.connect(self.update_exif_data) - self.exif_editor.show() - - def update_exif_data(self, updated_exif_data): - """Update the EXIF data.""" - self.exif_data = updated_exif_data - self.populate_exif() - - def handle_checkbox_state(self, state, desired_state, action): - """Perform an action based on the checkbox state and a desired state. Have to use lambda when calling.""" - # improved by chatGPT - if state == desired_state: - action() - - def on_tab_changed(self, index): - """Handle tab changes.""" - # chatgpt - if index == 1: # EXIF Tab - self.handle_exif_file("read") - elif index == 0: # Main Tab - self.handle_exif_file("write") - - def handle_exif_file(self, do): - if do == "read": - self.exif_data = self.utilities.read_yaml(self.exif_file) - elif do == "write": - self.utilities.write_yaml(self.exif_file, self.exif_data) - - def populate_exif(self): - # partly chatGPT - # Mapping of EXIF fields to comboboxes in the UI - combo_mapping = { - "make": self.ui.make_comboBox, - "model": self.ui.model_comboBox, - "lens": self.ui.lens_comboBox, - "iso": self.ui.iso_comboBox, - "image_description": self.ui.image_description_comboBox, - "user_comment": self.ui.user_comment_comboBox, - "artist": self.ui.artist_comboBox, - "copyright_info": self.ui.copyright_info_comboBox, - } - self.populate_comboboxes(combo_mapping) - - def populate_comboboxes(self, combo_mapping): - """Populate comboboxes with EXIF data.""" - # ChatGPT - for field, comboBox in combo_mapping.items(): - comboBox.clear() # Clear existing items - comboBox.addItems(map(str, self.exif_data.get(field, []))) - - def update_quality_options(self): - """Update visibility of quality settings based on selected format.""" - # ChatGPT - selected_format = self.ui.image_type.currentText() - # Hide all quality settings - self.ui.png_quality_spinBox.setVisible(False) - self.ui.jpg_quality_spinBox.setVisible(False) - # Show relevant settings - if selected_format == "jpg": - self.ui.jpg_quality_spinBox.setVisible(True) - elif selected_format == "webp": - self.ui.jpg_quality_spinBox.setVisible(True) - elif selected_format == "png": - self.ui.png_quality_spinBox.setVisible(True) - - def browse_input_folder(self): - folder = QFileDialog.getExistingDirectory(self, "Select Input Folder") - if folder: - self.ui.input_path.setText(folder) - - def browse_output_folder(self): - folder = QFileDialog.getExistingDirectory(self, "Select Output Folder") - if folder: - self.ui.output_path.setText(folder) - - def change_statusbar(self, msg, timeout = 500): - self.ui.statusBar.showMessage(msg, timeout) - - def handle_qprogressbar(self, current, total): - progress = int((100 / total) * current) - self.ui.progressBar.setValue(progress) - - def check_options(self): - try: - self.settings["input_folder"] = self.ui.input_path.text() - self.settings["output_folder"] = self.ui.output_path.text() - self.settings["file_format"] = self.ui.image_type.currentText() - self.settings["jpg_quality"] = int(self.ui.jpg_quality_spinBox.text()) - self.settings["png_compression"] = int(self.ui.png_quality_spinBox.text()) - self.settings["invert_image_order"] = self.ui.revert_checkbox.isChecked() - self.settings["grayscale"] = self.ui.grayscale_checkBox.isChecked() - self.settings["copy_exif"] = self.ui.exif_copy_checkBox.isChecked() - self.settings["own_exif"] = self.ui.exif_checkbox.isChecked() - self.settings["font_size"] = self.ui.font_size_comboBox.currentIndex() + 1 - self.settings["optimize"] = self.ui.optimize_checkBox.isChecked() - self.settings["own_date"] = self.ui.add_date_checkBox.isChecked() - - if self.ui.resize_checkbox.isChecked(): - self.settings["resize_percentage"] = int(self.ui.resize_spinBox.text()) - - if self.ui.brightness_checkbox.isChecked(): - self.settings["brightness_percentage"] = int(self.ui.brightness_spinBox.text()) - - if self.ui.contrast_checkbox.isChecked(): - self.settings["contrast_percentage"] = int(self.ui.contrast_spinBox.text()) - - if self.ui.rename_checkbox.isChecked() and self.ui.filename.text() != "": - self.settings["new_file_names"] = self.ui.filename.text() - - if self.ui.watermark_checkbox.isChecked() and self.ui.watermark_lineEdit.text() != "": - self.settings["watermark"] = self.ui.watermark_lineEdit.text() - - if self.settings["own_exif"]: - self.selected_exif = self.collect_selected_exif() - if self.ui.add_date_checkBox.isChecked(): - self.selected_exif["date_time_original"] = self.get_date() - if self.ui.gps_checkBox.isChecked(): - self.settings["gps"] = [self.ui.lat_lineEdit.text(), self.ui.long_lineEdit.text()] - else: - self.settings["gps"] = False - - except Exception as e: - print(f"Whoops: {e}") - - def get_date(self): - date_input = self.ui.dateEdit.date().toString("yyyy-MM-dd") - new_date = datetime.strptime(date_input, "%Y-%m-%d") - return new_date.strftime("%Y:%m:%d 00:00:00") - - def collect_selected_exif(self): - user_data = {} - user_data["make"] = self.ui.make_comboBox.currentText() - user_data["model"] = self.ui.model_comboBox.currentText() - user_data["lens"] = self.ui.lens_comboBox.currentText() - user_data["iso"] = self.ui.iso_comboBox.currentText() - user_data["image_description"] = self.ui.image_description_comboBox.currentText() - user_data["user_comment"] = self.ui.user_comment_comboBox.currentText() - user_data["artist"] = self.ui.artist_comboBox.currentText() - user_data["copyright_info"] = self.ui.copyright_info_comboBox.currentText() - user_data["software"] = f"OPTIMA-35 {self.version}" - return user_data - - def rebuild_ui(self): - # Define the bash script to execute - print("Rebuild function disabled") + if args.tui: + print("Starting TUI...") + start_tui() return - bash_script = "rebuild_ui.sh" - os.system(bash_script) - def restart_app(self): - """Restarts the application.""" - self.rebuild_ui() - # chatGPT - python = sys.executable # Path to the Python interpreter - os.execv(python, [python] + sys.argv) - -# core - def define_settings(self, exif_file, version): - self.name = "OPTIMA-35" - self.version = version - self.utilities = Utilities() - self.image_processor = ImageProcessor() - self.exif_handler = ExifHandler() - self.exif_file = exif_file - self.settings = { - "input_folder": None, - "output_folder": None, - "file_format": None, - "resize_percentage": False, - "contrast_percentage": False, - "brightness_percentage": False, - "new_file_names": False, - "invert_image_order": False, - "copy_exif": False, - "own_exif": False, - "watermark": False, - "grayscale": False, - "jpg_quality": None, - "png_compression": None, - "font_size": None, - "optimize": False, - "gps": False - } - self.exif_data = None - - def modify_timestamp_in_exif(self, exif_data, filename): - """"Takes exif data and adjust time to fit ending of filename.""" - try: - last_tree = filename[-3:len(filename)] - total_seconds = int(re.sub(r'\D+', '', last_tree)) - minutes = total_seconds // 60 - seconds = total_seconds % 60 - time = datetime.strptime(exif_data["date_time_original"], "%Y:%m:%d %H:%M:%S") # change date time string back to an time object for modification - new_time = time.replace(hour=12, minute=minutes, second=seconds) - exif_data["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S") - return exif_data - - except ValueError: - print("Modifying date went wrong, exiting...") - exit() - - def process(self): - self.ui.start_button.setEnabled(False) - self.ui.restart_button.setEnabled(False) - self.check_options() # Get all user selected data - input_folder_valid = os.path.exists(self.settings["input_folder"]) - output_folder_valid = os.path.exists(self.settings["output_folder"]) - if not input_folder_valid or not output_folder_valid: - QMessageBox.warning(self, "Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...") - return - - input_folder = self.settings["input_folder"] - output_folder = self.settings["output_folder"] - - image_files = [ - f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp")) - ] - - i = 1 - for image_file in image_files: - input_path = os.path.join(input_folder, image_file) - - if self.settings["new_file_names"] != False: - image_name = self.name_images(self.settings["new_file_names"], i, len(image_files), self.settings["invert_image_order"]) - else: - image_name = os.path.splitext(image_file)[0] - - output_path = os.path.join(output_folder, image_name) - - with self.image_processor.open_image(input_path) as img: - processed_img = img - - if self.settings["resize_percentage"] != False: - processed_img = self.image_processor.resize_image( - image = processed_img, percent = self.settings["resize_percentage"] - ) - if self.settings["watermark"] != False: - processed_img = self.image_processor.add_watermark(processed_img, self.settings["watermark"], int(self.settings["font_size"])) - if self.settings["grayscale"] != False: # There is a problem, if we first to grayscale and then watermark it braeks - processed_img = self.image_processor.grayscale(processed_img) - if self.settings["brightness_percentage"] != False: # Does the order of brightness and contrast matter? - processed_img = self.image_processor.change_brightness(processed_img, self.settings["brightness_percentage"]) - if self.settings["contrast_percentage"] != False: # Does the order of brightness and contrast matter? - processed_img = self.image_processor.change_contrast(processed_img, self.settings["contrast_percentage"]) - - if self.settings["own_exif"] != False: - selected_exif = self.selected_exif - if "date_time_original" in self.selected_exif: - selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) - exif_data = self.exif_handler.build_exif_dict(selected_exif, self.image_processor.get_image_size(processed_img)) - if self.settings["gps"] != False: - latitude = float(self.settings["gps"][0]) - longitude = float(self.settings["gps"][1]) - exif_data = self.exif_handler.add_geolocation_to_exif(exif_data, latitude, longitude) - - elif self.settings["copy_exif"] == True: - # When copying exif from original, make sure to change Piexel X & Y Dimension to fit new size - try: - og_exif = self.exif_handler.get_exif_info(img) - og_exif["Exif"][40962], og_exif["Exif"][40963] = self.image_processor.get_image_size(processed_img) - exif_data = og_exif - except Exception: - # If an error happends it is because the picture does not have exif data - self.change_statusbar("Copying EXIF data selected, but no EXIF data is available in the original image file.") - exif_data = None - elif self.settings["copy_exif"] == False: - exif_data = None - self.change_statusbar(f"exif data: {exif_data}") - - self.image_processor.save_image( - image = processed_img, - path = output_path, - exif_data = exif_data, - file_type = self.settings["file_format"], - jpg_quality = self.settings["jpg_quality"], - png_compressing = self.settings["png_compression"], - optimize = self.settings["optimize"] - ) - - self.handle_qprogressbar(i, len(image_files)) - i += 1 - QMessageBox.information(self, "Information", "Finished") - self.ui.start_button.setEnabled(True) - self.ui.restart_button.setEnabled(True) - self.ui.progressBar.setValue(0) - - def name_images(self, base_name, current_image, total_images, invert): - """"Returns name, combination of base_name and ending number.""" - total_digits = len(str(total_images)) - if invert: - ending_number = total_images - (current_image - 1) - else: - ending_number = current_image - ending = f"{ending_number:0{total_digits}}" - return f"{base_name}_{ending}" + # Check OS and start GUI if on Windows + if os.name == "nt": + print("Detected Windows. Starting GUI...") + start_gui() + else: + # Non-Windows: Check if PySide is installed + if check_pyside_installed(): + print("PySide detected. Starting GUI...") + start_gui() + else: + print("PySide is not installed. Falling back to TUI...") + start_tui() if __name__ == "__main__": - app = QtWidgets.QApplication(sys.argv) - - window = Optima35QT6(exif_file = "local_files/exif.yaml", version = "0.3.4") - window.show() - app.exec() + if os.path.isfile("config/exif.yaml"): + exif_file = "config/exif.yaml" + elif os.path.isfile("config/exif_example.yaml"): + exif_file = "config/exif_example.yaml" + print("Fall back to exif example file...") + else: + print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...") + exit() + tui_settings_file = "config/tui_settings.yaml" + main() diff --git a/media/demo_v041.cast b/media/demo_v041.cast new file mode 100644 index 0000000..8fe36aa --- /dev/null +++ b/media/demo_v041.cast @@ -0,0 +1,372 @@ +{"version": 2, "width": 80, "height": 24, "timestamp": 1735643611, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} +[0.677329, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] +[0.678152, "o", "\u001b]2;sam@potatohead:~/git/gitlab_public/optima-35\u0007\u001b]1;..lic/optima-35\u0007"] +[0.680479, "o", "\u001b]7;file://potatohead/home/sam/git/gitlab_public/optima-35\u0007"] +[0.743392, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[0m\u001b[49m\u001b[39m\u001b[0m\u001b[47m\u001b[38;5;30m \u001b[0m\u001b[38;5;30m\u001b[47m\u001b[47m\u001b[38;5;30m \u001b[0m\u001b[38;5;30m\u001b[47m\u001b[48;5;30m\u001b[37m\u001b[0m\u001b[37m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m  \u001b[1m\u001b[38;5;254m\u001b[48;5;30m\u001b[38;5;255m~\u001b[0m\u001b[38;5;255m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[38;5;250mgi\u001b[0m\u001b[38;5;250m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[38;5;250mg\u001b[0m\u001b[38;5;250m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[1m\u001b[38;5;254m\u001b[48;5;30m\u001b[38;5;255moptima-35\u001b[0m\u001b[38;5;255m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m\u001b[0m\u001b[38;5;254m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m \u001b[0m\u001b[38;5;254m\u001b[48;5;30m\u001b[43m\u001b[38;5;30m\u001b[0m\u001b[38;5;30m\u001b[43m\u001b[43m\u001b[30m  \u001b[30m feature/enhance-modularity \u001b[30m!5 \u001b[30m?2\u001b[0m\u001b[30m\u001b[43m\u001b[43m\u001b[30m \u001b[0m\u001b[30m\u001b[43m\u001b[49m\u001b[33m\u001b[0m\u001b[33m\u001b[49m\u001b[39m \u001b[0m\u001b[49m\u001b[39m\u001b[K\u001b[1C\u001b[0m\u001b[49m\u001b[30m\u001b[0m\u001b[30m\u001b[40m\u001b[32m \u001b[0m\u001b[32m\u001b[40m\u001b[40m\u001b[32m✔\u001b[0m\u001b[32m\u001b[40m\u001b[40m\u001b[32m \u001b[0m\u001b[32m\u001b[40m\u001b[40m\u001b[32m\u001b[34m\u001b[0m\u001b[34m\u001b[40m\u001b[44m\u001b[30m base\u001b[0m\u001b[30m\u001b[44m\u001b[44m\u001b[30m \u001b[0m\u001b[30m\u001b[44m\u001b[44m\u001b[30m \u001b[0m\u001b[30m\u001b[44m\u001b[49m\u001b[39m\u001b[14D"] +[0.744052, "o", "\u001b[?2004h"] +[1.448961, "o", "m\u001b[K"] +[1.45588, "o", "\b\u001b[4mm\u001b[24m"] +[1.456369, "o", "\b\u001b[4mm\u001b[24m\u001b[38;5;244micromamba acti\u001b[38;5;244mv\u001b[38;5;244mate optima35\u001b[39m\u001b[K\u001b[A\u001b[53C"] +[1.524001, "o", "\b\u001b[4mm\u001b[39m\u001b[4mi\u001b[24m"] +[1.538941, "o", "\b\b\u001b[24m\u001b[1m\u001b[31mm\u001b[24m\u001b[1m\u001b[31mi\u001b[0m\u001b[39m"] +[1.64567, "o", "\b\b\u001b[1m\u001b[31mm\u001b[1m\u001b[31mi\u001b[1m\u001b[31mc\u001b[0m\u001b[39m"] +[1.823061, "o", "\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"] +[1.936561, "o", "\b\u001b[1m\u001b[31mr\u001b[1m\u001b[31mo\u001b[0m\u001b[39m"] +[1.941248, "o", "\b\b\b\b\b\u001b[0m\u001b[32mm\u001b[0m\u001b[32mi\u001b[0m\u001b[32mc\u001b[0m\u001b[32mr\u001b[0m\u001b[32mo\u001b[39m"] +[2.275468, "o", "\u001b[39mm\u001b[39ma\u001b[39mm\u001b[39mb\u001b[39ma\u001b[39m \u001b[39ma\u001b[39mc\u001b[39mt\u001b[39miv\u001b[39ma\u001b[39mt\u001b[39me\u001b[39m \u001b[39mo\u001b[39mp\u001b[39mt\u001b[39mi\u001b[39mm\u001b[39ma\u001b[39m3\u001b[39m5"] +[2.283536, "o", "\u001b[A\u001b[56C\u001b[32mo\u001b[32mm\u001b[32ma\u001b[32mm\u001b[32mb\u001b[32ma\u001b[39m\u001b[1B\u001b[62D"] +[2.684344, "o", "\u001b[?2004l\r\r\n"] +[2.686472, "o", "\u001b]2;micromamba activate optima35\u0007\u001b]1;\u0007"] +[2.719122, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] +[2.719308, "o", "\u001b]2;sam@potatohead:~/git/gitlab_public/optima-35\u0007\u001b]1;..lic/optima-35\u0007"] +[2.722384, "o", "\u001b]7;file://potatohead/home/sam/git/gitlab_public/optima-35\u0007"] +[2.8234, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[0m\u001b[49m\u001b[39m\u001b[0m\u001b[47m\u001b[38;5;30m \u001b[0m\u001b[38;5;30m\u001b[47m\u001b[47m\u001b[38;5;30m \u001b[0m\u001b[38;5;30m\u001b[47m\u001b[48;5;30m\u001b[37m\u001b[0m\u001b[37m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m  \u001b[1m\u001b[38;5;254m\u001b[48;5;30m\u001b[38;5;255m~\u001b[0m\u001b[38;5;255m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[38;5;250mgi\u001b[0m\u001b[38;5;250m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[38;5;250mg\u001b[0m\u001b[38;5;250m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[1m\u001b[38;5;254m\u001b[48;5;30m\u001b[38;5;255moptima-35\u001b[0m\u001b[38;5;255m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m\u001b[0m\u001b[38;5;254m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m \u001b[0m\u001b[38;5;254m\u001b[48;5;30m\u001b[43m\u001b[38;5;30m\u001b[0m\u001b[38;5;30m\u001b[43m\u001b[43m\u001b[30m  \u001b[30m feature/enhance-modularity \u001b[30m!5 \u001b[30m?2\u001b[0m\u001b[30m\u001b[43m\u001b[43m\u001b[30m \u001b[0m\u001b[30m\u001b[43m\u001b[49m\u001b[33m\u001b[0m\u001b[33m\u001b[49m\u001b[39m \u001b[0m\u001b[49m\u001b[39m\u001b[K"] +[2.824436, "o", "\u001b[?2004h"] +[3.546197, "o", "p"] +[3.552859, "o", "\b\u001b[1m\u001b[31mp\u001b[0m\u001b[39m"] +[3.553263, "o", "\b\u001b[1m\u001b[31mp\u001b[0m\u001b[39m\u001b[38;5;244myside6-uic ui/\u001b[38;5;244mm\u001b[38;5;244main_window.ui -o ui/main_window.py \u001b[39m\u001b[K\u001b[A\u001b[30C"] +[3.775027, "o", "\b\u001b[1m\u001b[31mp\u001b[1m\u001b[31my\u001b[0m\u001b[39m"] +[3.978262, "o", "\b\b\u001b[1m\u001b[31mp\u001b[1m\u001b[31my\u001b[1m\u001b[31mt\u001b[0m\u001b[39m\u001b[K\u001b[1B\r\u001b[K\u001b[A\u001b[68C"] +[3.996697, "o", "\u001b[38;5;244mhon main.py\u001b[39m\u001b[11D"] +[4.104095, "o", "\b\u001b[1m\u001b[31mt\u001b[1m\u001b[31mh\u001b[0m\u001b[39m"] +[4.51609, "o", "\b\u001b[1m\u001b[31mh\u001b[1m\u001b[31mo\u001b[0m\u001b[39m"] +[4.898228, "o", "\u001b[39mn\u001b[39m \u001b[39mm\u001b[39ma\u001b[39mi\u001b[39mn\u001b[39m.\u001b[39mp\u001b[39my"] +[4.916302, "o", "\u001b[14D\u001b[0m\u001b[32mp\u001b[0m\u001b[32my\u001b[0m\u001b[32mt\u001b[0m\u001b[32mh\u001b[0m\u001b[32mo\u001b[32mn\u001b[39m \u001b[4mm\u001b[4ma\u001b[4mi\u001b[4mn\u001b[4m.\u001b[4mp\u001b[4my\u001b[24m"] +[5.403027, "o", "\b\u001b[4my\u001b[4m \u001b[24m \r\u001b[K"] +[5.409587, "o", "\u001b[A\u001b[78C\u001b[4my\u001b[24m\u001b[24m \u001b[K\r"] +[5.40996, "o", "\u001b[38;5;244m-\u001b[38;5;244m-\u001b[38;5;244mt\u001b[38;5;244mu\u001b[38;5;244mi\u001b[39m\r"] +[5.601688, "o", "\u001b[39m-"] +[5.722974, "o", "\r-\u001b[39m-"] +[6.026401, "o", "\u001b[39mt"] +[6.161384, "o", "\u001b[39mu"] +[6.217467, "o", "\u001b[39mi"] +[6.589968, "o", "\u001b[?2004l\r\r\n"] +[6.59058, "o", "\u001b]2;python main.py --tui\u0007\u001b]1;\u0007"] +[6.617562, "o", "Fall back to exif example file...\r\n"] +[6.622191, "o", "Starting TUI...\r\n"] +[6.716642, "o", "Settings file empty.\r\nNo settings found...\r\nAsking for new settings...\r\n\r\nDefault resize percentage (below 100 downscale, above upscale): "] +[9.164618, "o", "8"] +[9.248071, "o", "0"] +[9.743554, "o", "\r\n"] +[9.743735, "o", "Default contrast percentage (negative = decrease, positive = increase): "] +[10.78209, "o", "1"] +[10.867213, "o", "0"] +[11.206582, "o", "\r\n"] +[11.206816, "o", "Default brighness percentage (negative = decrease, positive = increase): "] +[11.714352, "o", "-"] +[12.073169, "o", "1"] +[12.167016, "o", "0"] +[12.597502, "o", "\r\n"] +[12.597744, "o", "JPEG quality (1-100, 80 default): "] +[13.650373, "o", "8"] +[13.712797, "o", "0"] +[13.941506, "o", "\r\n"] +[13.941706, "o", "PNG compression level (0-9, 6 default): "] +[14.539469, "o", "6"] +[14.796779, "o", "\r\n"] +[14.938909, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[14.942831, "o", "\u001b[A\rOptimize images i.e. compressing? \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b[34my\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \u001b(B\u001b[m\u001b[47m\u001b[30myes\u001b(B\u001b[m \r\n"] +[14.943016, "o", "\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b[34mn\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m no \r\u001b[A"] +[14.946002, "o", "\u001b(B\u001b[m\u001b[31m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\u001b[A"] +[15.919041, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[15.922308, "o", "New settings saved successfully.\r\nEnter path of input folder: "] +[17.083508, "o", "o"] +[17.413375, "o", "\b \b"] +[17.597213, "o", "l"] +[17.763659, "o", "o"] +[17.829166, "o", "c"] +[17.907582, "o", "a"] +[18.018695, "o", "l"] +[18.219882, "o", "_"] +[18.597998, "o", "f"] +[18.684139, "o", "i"] +[18.854542, "o", "l"] +[18.910718, "o", "e"] +[19.116207, "o", "s"] +[19.29677, "o", "/"] +[19.606841, "o", "i"] +[19.67462, "o", "m"] +[19.7635, "o", "g"] +[19.92206, "o", "\r\n"] +[19.922244, "o", "Enter path of output folder: "] +[20.173606, "o", "l"] +[20.321372, "o", "o"] +[20.383433, "o", "c"] +[20.463172, "o", "a"] +[20.555845, "o", "l"] +[20.752791, "o", "_"] +[20.975923, "o", "f"] +[21.051336, "o", "i"] +[21.212242, "o", "l"] +[21.277781, "o", "e"] +[21.473246, "o", "s"] +[21.804064, "o", "/"] +[22.106301, "o", "o"] +[22.201691, "o", "u"] +[22.269246, "o", "t"] +[22.601648, "o", "\r\n"] +[22.601885, "o", "Enter export file format (jpg, png, webp): "] +[24.083726, "o", "j"] +[24.127736, "o", "p"] +[24.264589, "o", "g"] +[24.57916, "o", "\r\n"] +[24.598678, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n\r\n\r\n"] +[24.607614, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mResize image\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n"] +[24.607794, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[24.608348, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[24.616611, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[24.616839, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r"] +[24.617428, "o", "\r\n \r\r\n \r\r\n \r"] +[24.619314, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C"] +[24.619478, "o", "\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[26.660369, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange EXIF\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[26.660559, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[26.66109, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[26.6667, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r"] +[26.666875, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[26.789333, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mConvert to grayscale\u001b(B\u001b[m \r\n"] +[26.789529, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[26.789664, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[26.797578, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[26.798309, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r"] +[26.798751, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[26.929884, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[26.930073, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange contrast\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[26.930199, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[26.937997, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[26.938647, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r"] +[26.939112, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.212356, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[27.212564, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange brightness\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.22141, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[27.221766, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r"] +[27.221985, "o", "\r\n \r\r\n \r"] +[27.222796, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.344641, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[27.345282, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mRename images\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[27.345758, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.354429, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r"] +[27.35514, "o", "\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.616497, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[27.616697, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mInvert image order\u001b(B\u001b[m \r\n"] +[27.617352, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.623229, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r"] +[27.623463, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.778389, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[27.778648, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n"] +[27.779389, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mAdd Watermark\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[27.787995, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[27.788617, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r"] +[27.788866, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[27.789448, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.089642, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[28.089921, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n"] +[28.090473, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n"] +[28.09084, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mAdd Watermark\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.096658, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[28.096769, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r"] +[28.096929, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.244958, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[28.245236, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n"] +[28.245441, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n"] +[28.246239, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mInvert image order\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.254857, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[28.255412, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n"] +[28.255651, "o", " \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[28.255861, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[28.256065, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.538276, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[28.53847, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mRename images\u001b(B\u001b[m \r\n"] +[28.539087, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.547915, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[28.54851, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r"] +[28.549125, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C"] +[28.549609, "o", "\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.635635, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[28.636112, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mRename images\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[28.645078, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[28.645679, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r"] +[28.646141, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C"] +[28.646396, "o", "\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.060214, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[29.060392, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n"] +[29.060994, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mInvert image order\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.066936, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[29.06701, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n"] +[29.06705, "o", " \r"] +[29.067299, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.305548, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n"] +[29.305754, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mInvert image order\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.314491, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[29.314678, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r"] +[29.315449, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.476124, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[29.476376, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n"] +[29.476945, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n"] +[29.477451, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mRename images\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.485916, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[29.486211, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n"] +[29.48687, "o", " \r"] +[29.487248, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.645362, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[29.645617, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n"] +[29.646303, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange brightness\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.655419, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[29.655923, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[29.656398, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r"] +[29.656619, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[29.656796, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.832918, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[29.83311, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange brightness\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.842077, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[29.842286, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r"] +[29.842422, "o", "\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n"] +[29.842576, "o", " \r\r\n \r\r\n \r"] +[29.843329, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[29.843799, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.976699, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[29.976948, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange contrast\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[29.985854, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[29.98636, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[29.986765, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.109772, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[30.109978, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange contrast\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.118884, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[30.119119, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r"] +[30.119814, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.230951, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[30.231215, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mConvert to grayscale\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n"] +[30.231425, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n"] +[30.231688, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.240842, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"] +[30.241119, "o", "\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r"] +[30.241323, "o", "\r\n \r\r\n \r"] +[30.242531, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.360245, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n"] +[30.360539, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mConvert to grayscale\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[30.360826, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.36533, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n \r\r\n"] +[30.365472, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r"] +[30.365943, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.50115, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n"] +[30.501599, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange EXIF\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n"] +[30.502022, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.510559, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[30.510805, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n"] +[30.510971, "o", " \r\r\n \r\r\n \r\r\n \r\r\n \r"] +[30.511911, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.685517, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CResize image \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mChange EXIF\u001b(B\u001b[m \r\n"] +[30.685758, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[30.685952, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.694848, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[30.695365, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r"] +[30.695848, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C"] +[30.696368, "o", "\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.83141, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mResize image\u001b(B\u001b[m \r\n"] +[30.831678, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n"] +[30.831884, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n"] +[30.832089, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n"] +[30.832289, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[30.84096, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r"] +[30.84152, "o", "\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m \u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[30.841772, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[30.841971, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[31.156421, "o", "\u001b[A\u001b[A\u001b[A\r \r\nOPTIMA-35 v.0.4.1 \r\nSelect what you want to do (esc or q to exit) \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mResize image\u001b(B\u001b[m \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange EXIF \r\n"] +[31.156634, "o", "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CConvert to grayscale \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange contrast \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CChange brightness \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CRename images \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CInvert image order \r\n\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[CAdd Watermark \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[31.163125, "o", "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\u001b(B\u001b[m\u001b[33m\u001b[40m\rPress , for multi-selection and to select and accept \r\u001b(B\u001b[m\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\r\n"] +[31.163206, "o", " \r\r\n \r"] +[31.163253, "o", "\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C"] +[31.163314, "o", "\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\n"] +[31.16336, "o", "\r\u001b[C\u001b[C\u001b(B\u001b[m\u001b[37m[\u001b(B\u001b[m\u001b(B\u001b[m\u001b[33m\u001b[1m*\u001b(B\u001b[m\u001b(B\u001b[m\u001b[37m]\u001b(B\u001b[m \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[31.51079, "o", "\u001b[A\u001b[A\u001b[A\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[31.510988, "o", "What should be the name for the new images? "] +[33.593907, "o", "e"] +[33.888461, "o", "\b \b"] +[33.989013, "o", "d"] +[34.134175, "o", "e"] +[34.173323, "o", "m"] +[34.24098, "o", "o"] +[34.728246, "o", "\r\n"] +[34.728423, "o", "Enter text for watermark. "] +[35.633165, "o", "D"] +[35.803621, "o", "e"] +[35.845136, "o", "m"] +[35.914241, "o", "o"] +[36.168864, "o", " "] +[36.853512, "o", "\b \b"] +[37.105764, "o", "\r\n"] +[37.12281, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[37.130101, "o", "\u001b[A\rEnter Make \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mNikon\u001b(B\u001b[m \r\n"] +[37.130252, "o", "\u001b[C\u001b[CCanon \r\u001b[A"] +[37.137636, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\u001b[A"] +[38.366811, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[38.377981, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[38.38132, "o", "\u001b[A\rEnter Model \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mFG\u001b(B\u001b[m \r\n\u001b[C\u001b[CF50 \r\n\u001b[C\u001b[CAE-1 \r\u001b[A\u001b[A"] +[38.384958, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\u001b[A\u001b[A"] +[38.874015, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[M"] +[38.874229, "o", "\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[38.886354, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[38.889292, "o", "\u001b[A\rEnter Lens \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mNikon LENS SERIES E 50mm\u001b(B\u001b[m \r\n\u001b[C\u001b[CAF NIKKOR 35-70mm \r\n"] +[38.889375, "o", "\u001b[C\u001b[CCanon FD 50mm f/1.4 S.S.C \r\u001b[A\u001b[A"] +[38.892151, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\u001b[A\u001b[A"] +[39.594436, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[39.60723, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[39.610311, "o", "\u001b[A\rEnter Iso \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30m100\u001b(B\u001b[m \r\n\u001b[C\u001b[C200 \r\n"] +[39.610499, "o", "\u001b[C\u001b[C400 \r\n\u001b[C\u001b[C800 \r\n\u001b[C\u001b[C1000 \r\n\u001b[C\u001b[C1600 \r\n\u001b[C\u001b[C3200 \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[39.613514, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r"] +[39.613654, "o", "\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[40.285396, "o", "\u001b[A\rEnter Iso \r\n\u001b[C\u001b[C100 \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30m200\u001b(B\u001b[m \r\n\u001b[C\u001b[C400 \r\n\u001b[C\u001b[C800 \r\n"] +[40.285634, "o", "\u001b[C\u001b[C1000 \r\n\u001b[C\u001b[C1600 \r\n\u001b[C\u001b[C3200 \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[40.290088, "o", " \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[40.486366, "o", "\u001b[A\rEnter Iso \r\n\u001b[C\u001b[C100 \r\n\u001b[C\u001b[C200 \r\n"] +[40.486584, "o", "\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30m400\u001b(B\u001b[m \r\n\u001b[C\u001b[C800 \r\n\u001b[C\u001b[C1000 \r\n\u001b[C\u001b[C1600 \r\n\u001b[C\u001b[C3200 \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[40.49292, "o", " \r\r\n \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A"] +[41.095691, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[41.107748, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[41.11136, "o", "\u001b[A\rEnter Image Description \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mILFORD DELTA 3200\u001b(B\u001b[m \r\n\u001b[C\u001b[CILFORD ILFOCOLOR \r\n\u001b[C\u001b[CLomoChrome Turquoise \r\n\u001b[C\u001b[CKodak 200 \r\u001b[A\u001b[A\u001b[A"] +[41.115136, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A"] +[42.008703, "o", "\u001b[A\rEnter Image Description \r\n\u001b[C\u001b[CILFORD DELTA 3200 \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mILFORD ILFOCOLOR\u001b(B\u001b[m \r\n"] +[42.008925, "o", "\u001b[C\u001b[CLomoChrome Turquoise \r\n\u001b[C\u001b[CKodak 200 \r\u001b[A\u001b[A\u001b[A"] +[42.015056, "o", " \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\u001b[A\u001b[A\u001b[A"] +[42.414356, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[42.431029, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[42.43832, "o", "\u001b[A\rEnter User Comment \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mScanner.NORITSU-KOKI\u001b(B\u001b[m \r\n"] +[42.438554, "o", "\u001b[C\u001b[CScanner.NA \r\u001b[A"] +[42.445809, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\u001b[A"] +[43.147181, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[43.1585, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[43.161793, "o", "\u001b[A\rEnter Artist \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mMr. Finchum\u001b(B\u001b[m \r\n"] +[43.16201, "o", "\u001b[C\u001b[CJohn Doe \r\u001b[A"] +[43.16618, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\u001b[A"] +[43.620173, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[?12l\u001b[?25h\u001b[?1l\u001b>"] +[43.631543, "o", "\u001b[?1h\u001b=\u001b[?25l\r\n"] +[43.63482, "o", "\u001b[A\rEnter Copyright Info \r\n\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mAll Rights Reserved\u001b(B\u001b[m \r\n\u001b[C\u001b[CCC BY-NC 4.0 \r\n\u001b[C\u001b[CNo Copyright \r\u001b[A\u001b[A"] +[43.638191, "o", "\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\r\n \r\u001b[A\u001b[A"] +[44.39754, "o", "\u001b[A\rEnter Copyright Info \r\n\u001b[C\u001b[CAll Rights Reserved \r\n"] +[44.397768, "o", "\u001b[C\u001b[C\u001b(B\u001b[m\u001b[47m\u001b[30mCC BY-NC 4.0\u001b(B\u001b[m \r\n\u001b[C\u001b[CNo Copyright \r\u001b[A\u001b[A"] +[44.404144, "o", " \r\r\n\u001b(B\u001b[m\u001b[37m\u001b[1m> \u001b(B\u001b[m\r\r\n \r\u001b[A\u001b[A"] +[44.707673, "o", "\u001b[A\u001b[M\u001b[M\u001b[M\u001b[M"] +[44.707849, "o", "\u001b[?12l\u001b[?25h\u001b[?1l\u001b>Enter a date (yyyy-mm-dd): "] +[45.955851, "o", "2"] +[46.054154, "o", "0"] +[46.404177, "o", "2"] +[46.497714, "o", "4"] +[47.123881, "o", "-"] +[47.416185, "o", "1"] +[47.489983, "o", "2"] +[47.757033, "o", "-"] +[48.21329, "o", "3"] +[48.282472, "o", "1"] +[49.029909, "o", "\r\n"] +[49.036256, "o", "Enter Latitude (xx.xxxxxx): "] +[49.59262, "o", "\r\n"] +[49.773864, "o", "1|------------> |4\r"] +[49.844145, "o", "2|-------------------------> |4\r"] +[49.917126, "o", "3|-------------------------------------> |4\r"] +[49.992149, "o", "4|-------------------------------------------------->|4\r\r\nDone\r\n"] +[50.012799, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] +[50.013073, "o", "\u001b]2;sam@potatohead:~/git/gitlab_public/optima-35\u0007\u001b]1;..lic/optima-35\u0007"] +[50.016341, "o", "\u001b]7;file://potatohead/home/sam/git/gitlab_public/optima-35\u0007"] +[50.047409, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[0m\u001b[49m\u001b[39m\u001b[0m\u001b[47m\u001b[38;5;30m \u001b[0m\u001b[38;5;30m\u001b[47m\u001b[47m\u001b[38;5;30m \u001b[0m\u001b[38;5;30m\u001b[47m\u001b[48;5;30m\u001b[37m\u001b[0m\u001b[37m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m  \u001b[1m\u001b[38;5;254m\u001b[48;5;30m\u001b[38;5;255m~\u001b[0m\u001b[38;5;255m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[38;5;250mgi\u001b[0m\u001b[38;5;250m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[38;5;250mg\u001b[0m\u001b[38;5;250m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m/\u001b[1m\u001b[38;5;254m\u001b[48;5;30m\u001b[38;5;255moptima-35\u001b[0m\u001b[38;5;255m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m\u001b[0m\u001b[38;5;254m\u001b[48;5;30m\u001b[48;5;30m\u001b[38;5;254m \u001b[0m\u001b[38;5;254m\u001b[48;5;30m\u001b[43m\u001b[38;5;30m\u001b[0m\u001b[38;5;30m\u001b[43m\u001b[43m\u001b[30m  \u001b[30m feature/enhance-modularity \u001b[30m!5 \u001b[30m?2\u001b[0m\u001b[30m\u001b[43m\u001b[43m\u001b[30m \u001b[0m\u001b[30m\u001b[43m\u001b[49m\u001b[33m\u001b[0m\u001b[33m\u001b[49m\u001b[39m \u001b[0m\u001b[49m\u001b[39m\u001b[K"] +[50.0481, "o", "\u001b[?2004h"] +[52.228733, "o", "\u001b[?2004l\r\r\n"] diff --git a/media/demo_v041.gif b/media/demo_v041.gif new file mode 100644 index 0000000..d5398b2 Binary files /dev/null and b/media/demo_v041.gif differ diff --git a/media/exif_tab.png b/media/exif_tab.png deleted file mode 100644 index 27f73ee..0000000 Binary files a/media/exif_tab.png and /dev/null differ diff --git a/media/exif_tab_disabled.png b/media/exif_tab_disabled.png new file mode 100644 index 0000000..905d46e Binary files /dev/null and b/media/exif_tab_disabled.png differ diff --git a/media/exif_tab_enabled.png b/media/exif_tab_enabled.png new file mode 100644 index 0000000..6c20ba3 Binary files /dev/null and b/media/exif_tab_enabled.png differ diff --git a/media/main_tab.png b/media/main_tab.png index 611c4e0..b160602 100644 Binary files a/media/main_tab.png and b/media/main_tab.png differ diff --git a/optima/__init__.py b/optima/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/optima/optima35.py b/optima/optima35.py new file mode 100644 index 0000000..8345e3b --- /dev/null +++ b/optima/optima35.py @@ -0,0 +1,113 @@ +import re +import os +from datetime import datetime +# My packages +from utils.image_handler import ImageProcessor, ExifHandler + +class OPTIMA35: + def __init__(self): + self.name = "OPTIMA-35" + self.version = "0.4.1" + self.image_processor = ImageProcessor() + self.exif_handler = ExifHandler() + self.settings = { + "input_folder": None, + "output_folder": None, + "file_format": None, + "resize": False, + "contrast": False, + "brightness": False, + "new_file_names": False, + "invert_image_order": False, + "copy_exif": False, + "own_exif": False, + "watermark": False, + "grayscale": False, + "jpg_quality": None, + "png_compression": None, + "font_size": None, + "optimize": False, + "gps": False + } + self.selected_exif = {} + + def modify_timestamp_in_exif(self, exif_data, filename): + """"Takes exif data and adjust time to fit ending of filename.""" + try: + last_tree = filename[-3:len(filename)] + total_seconds = int(re.sub(r'\D+', '', last_tree)) + minutes = total_seconds // 60 + seconds = total_seconds % 60 + time = datetime.strptime(exif_data["date_time_original"], "%Y:%m:%d %H:%M:%S") # change date time string back to an time object for modification + new_time = time.replace(hour=12, minute=minutes, second=seconds) + exif_data["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S") + return exif_data + + except ValueError: + print("Modifying date went wrong, exiting...") + exit() + + def process(self, image_input_file, image_output_file): + with self.image_processor.open_image(image_input_file) as img: + processed_img = img + image_name = os.path.basename(image_output_file) + + if self.settings["resize"] != False: + processed_img = self.image_processor.resize_image( + image = processed_img, percent = self.settings["resize"] + ) + if self.settings["watermark"] != False: + processed_img = self.image_processor.add_watermark(processed_img, self.settings["watermark"], int(self.settings["font_size"])) + if self.settings["grayscale"] != False: # There is a problem, if we first to grayscale and then watermark it braeks + processed_img = self.image_processor.grayscale(processed_img) + if self.settings["brightness"] != False: # Does the order of brightness and contrast matter? + processed_img = self.image_processor.change_brightness(processed_img, self.settings["brightness"]) + if self.settings["contrast"] != False: # Does the order of brightness and contrast matter? + processed_img = self.image_processor.change_contrast(processed_img, self.settings["contrast"]) + + if self.settings["own_exif"] != False: + selected_exif = self.selected_exif + if "date_time_original" in self.selected_exif: + selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) + exif_data = self.exif_handler.build_exif_dict(selected_exif, self.image_processor.get_image_size(processed_img)) + if self.settings["gps"] != False: + latitude = float(self.settings["gps"][0]) + longitude = float(self.settings["gps"][1]) + exif_data = self.exif_handler.add_geolocation_to_exif(exif_data, latitude, longitude) + + elif self.settings["copy_exif"] == True: + # When copying exif from original, make sure to change Piexel X & Y Dimension to fit new size + try: + og_exif = self.exif_handler.get_exif_info(img) + og_exif["Exif"][40962], og_exif["Exif"][40963] = self.image_processor.get_image_size(processed_img) + exif_data = og_exif + except Exception: + # If an error happends it is because the picture does not have exif data + print("Copying EXIF data selected, but no EXIF data is available in the original image file.") + exif_data = None + elif self.settings["copy_exif"] == False: + exif_data = None + + self.image_processor.save_image( + image = processed_img, + path = image_output_file, + exif_data = exif_data, + file_type = self.settings["file_format"], + jpg_quality = self.settings["jpg_quality"], + png_compressing = self.settings["png_compression"], + optimize = self.settings["optimize"] + ) + + def name_images(self, base_name, current_image, total_images, invert): + """"Returns name, combination of base_name and ending number.""" + total_digits = len(str(total_images)) + if invert: + ending_number = total_images - (current_image - 1) + else: + ending_number = current_image + ending = f"{ending_number:0{total_digits}}" + return f"{base_name}_{ending}" + +if __name__ == "__main__": + print("Please load OPTIMA35 into the ui class...") + exit() diff --git a/requirements.txt b/requirements_gui.txt similarity index 100% rename from requirements.txt rename to requirements_gui.txt diff --git a/requirements_tui.txt b/requirements_tui.txt new file mode 100644 index 0000000..e69e727 --- /dev/null +++ b/requirements_tui.txt @@ -0,0 +1,4 @@ +pyyaml +piexif +pillow +simple_term_menu diff --git a/main_tui.py b/tui.py similarity index 51% rename from main_tui.py rename to tui.py index 07c7537..6a4d7cc 100644 --- a/main_tui.py +++ b/tui.py @@ -1,62 +1,155 @@ import os -import re from datetime import datetime +# my packages +from optima.optima35 import OPTIMA35 from utils.utility import Utilities -from utils.image_handler import ImageProcessor, ExifHandler -from ui.tui import SimpleTUI -# legacy code, will be removed with then next minor version -class Optima35: - # The layout of class Optima35 was originally made by ChatGPT, but major adjustments have been made. To remain transparent, I disclose this. - def __init__(self, settings_file, exif_options_file): - self.name = "OPTIMA-35" - self.version = "0.2.1" - self.utilities = Utilities() - self.image_processor = ImageProcessor() - self.exif_handler = ExifHandler() +from ui.simple_tui import SimpleTUI + +class Optima35TUI(): + def __init__(self, exif_file, settings_file): + self.o = OPTIMA35() + self.u = Utilities() self.tui = SimpleTUI() + self.exif_file = exif_file + self.exif_data = self.u.read_yaml(exif_file) + self.setting_file = settings_file self.settings = { "input_folder": None, "output_folder": None, "file_format": None, - "resize_percentage": None, + "resize": None, "copy_exif": None, - "contrast_percentage": None, - "brightness_percentage": None, + "contrast": None, + "brightness": None, "new_file_names": None, "invert_image_order": False, - "watermark_text": None, + "watermark": None, + "gps": None, "modifications": [], } self.settings_to_save = [ - "resize_percentage", + "resize", "jpg_quality", "png_compression", - "web_optimize", - "contrast_percentage", - "brightness_percentage" + "optimize", + "contrast", + "brightness" ] - self.exif_choices = self.utilities.read_yaml(exif_options_file) - self.setting_file = settings_file + + def process(self): + if "Change EXIF" in self.settings["modifications"]: + self.selected_exif = self.collect_exif_data() + self.check_options() # Get all user selected data + input_folder_valid = os.path.exists(self.o.settings["input_folder"]) + output_folder_valid = os.path.exists(self.o.settings["output_folder"]) + if not input_folder_valid or not output_folder_valid: + print("Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...") + return + + input_folder = self.o.settings["input_folder"] + output_folder = self.o.settings["output_folder"] + + + image_files = [ + f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp")) + ] + i = 1 + for image_file in image_files: + input_path = os.path.join(input_folder, image_file) + if self.o.settings["new_file_names"] != False: + image_name = self.o.name_images(self.o.settings["new_file_names"], i, len(image_files), self.o.settings["invert_image_order"]) + else: + image_name = os.path.splitext(image_file)[0] + output_path = os.path.join(output_folder, image_name) + self.o.process(input_path, output_path) + self.u.progress_bar(i, len(image_files)) + i += 1 + + def check_options(self): + try: + if "Resize image" in self.settings["modifications"]: + self.o.settings["resize"] = self.settings["resize"] + else: + self.o.settings["resize"] = False + + if "Convert to grayscale" in self.settings["modifications"]: + self.o.settings["grayscale"] = True + else: + self.o.settings["grayscale"] = False + + if "Change contrast" in self.settings["modifications"]: + self.o.settings["contrast"] = self.settings["contrast"] + else: + self.o.settings["contrast"] = False + + if "Change brightness" in self.settings["modifications"]: + self.o.settings["brightness"] = self.settings["brightness"] + else: + self.o.settings["brightness"] = False + + if "Rename images" in self.settings["modifications"]: + self.o.settings["new_file_names"] = self.settings["new_file_names"] + else: + self.o.settings["new_file_names"] = False + + if "Invert image order" in self.settings["modifications"]: + self.o.settings["invert_image_order"] = True + else: + self.o.settings["invert_image_order"] = False + + if "Add Watermark" in self.settings["modifications"]: + self.o.settings["watermark"] = self.settings["watermark"] + else: + self.o.settings["watermark"] = False + + self.o.settings["optimize"] = self.settings["optimize"] + self.o.settings["png_compression"] = self.settings["png_compression"] + self.o.settings["jpg_quality"] = self.settings["jpg_quality"] + + self.o.settings["input_folder"] = self.settings["input_folder"] + self.o.settings["output_folder"] = self.settings["output_folder"] + self.o.settings["file_format"] = self.settings["file_format"] + self.o.settings["font_size"] = 2 # need to add option to select size + + self.o.settings["copy_exif"] = self.settings["copy_exif"] + + if "Change EXIF" in self.settings["modifications"]: #missing + self.o.selected_exif = self.selected_exif # + self.o.settings["own_exif"] = True + if self.settings["gps"] != None: + self.o.settings["gps"] = self.settings["gps"] + else: + self.o.settings["gps"] = False + else: + self.o.settings["own_exif"] = False + + except Exception as e: + print(f"Whoops: {e}") def load_or_ask_settings(self): """Load settings from a YAML file or ask the user if not present or incomplete.""" - # Partially ChatGPT - 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...") + try: + if self.read_settings(self.settings_to_save): + for item in self.settings_to_save: + print(f"{item}: {self.settings[item]}") + use_saved = self.tui.yes_no_menu("Use these settings?") + if use_saved: + return + else: + print("No settings found...") + self.ask_for_settings() + except Exception as e: + print(f"Error: {e}") + self.ask_for_settings() + def ask_for_settings(self): print("Asking for new settings...\n") - self.settings["resize_percentage"] = self.take_input_and_validate(question = "Default resize percentage (below 100 downscale, above upscale): ", accepted_type = int, min_value = 1, max_value = 200) - self.settings["contrast_percentage"] = 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_percentage"] = self.take_input_and_validate(question = "Default brighness percentage (negative = decrease, positive = increase): ", accepted_type = int, min_value = -100, max_value = 100) + 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["web_optimize"] = self.tui.yes_no_menu("Optimize images i.e. compressing?") + self.settings["optimize"] = self.tui.yes_no_menu("Optimize images i.e. compressing?") self.write_settings(self.settings_to_save) @@ -64,7 +157,7 @@ class Optima35: """"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.utilities.write_yaml(self.setting_file, filtered_settings) + self.u.write_yaml(self.setting_file, filtered_settings) print("New settings saved successfully.") def read_settings(self, keys_to_load): @@ -75,7 +168,7 @@ class Optima35: # First draft by ChatGPT, adjusted to fit my needs. keys = keys_to_load if os.path.exists(self.setting_file): - loaded_settings = self.utilities.read_yaml(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] @@ -93,17 +186,32 @@ class Optima35: "user_comment", "artist", "copyright_info" ] for field in fields: - choise = self.tui.choose_menu(f"Enter {field.replace('_', ' ').title()}", self.exif_choices[field]) - user_data[field] = choise.encode("utf-8") - user_data["software"] = f"OPTIMA-35 {self.version}".encode("utf-8") + choise = self.tui.choose_menu(f"Enter {field.replace('_', ' ').title()}", self.exif_data[field]) + user_data[field] = choise + + user_data["software"] = f"{self.o.name} {self.o.version}" new_date = self.get_date_input() if new_date: user_data["date_time_original"] = new_date + self.settings["gps"] = self.get_gps_input(user_data) + return user_data + def get_gps_input(self, test_exif): + while True: + lat = input("Enter Latitude (xx.xxxxxx): ") + if lat == "": + return False + long = input("Enter Longitude (xx.xxxxxx): ") + try: + self.o.exif_handler.add_geolocation_to_exif(test_exif, float(lat), float(long)) + return [lat, long] + except Exception: + print("Invalid GPS formate, try again...") + def get_date_input(self): # Partially chatGPT while True: @@ -133,17 +241,24 @@ class Optima35: 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} \nSelect what you want to do (esc or q to exit)", + f"\n{self.o.name} v.{self.o.version} \nSelect what you want to do (esc or q to exit)", menu_options ) if "Change EXIF" not in self.settings["modifications"]: self.settings["copy_exif"] = self.tui.yes_no_menu("Do you want to copy exif info from original file?") if "Rename images" in self.settings["modifications"]: self.settings["new_file_names"] = input("What should be the name for the new images? ") # Need + else: + self.settings["new_file_names"] = False if "Invert image order" in self.settings["modifications"]: self.settings["invert_image_order"] = True + else: + self.settings["invert_image_order"] = False if "Add Watermark" in self.settings["modifications"]: - self.settings["watermark_text"] = input("Enter text for watermark. ") + 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): @@ -187,106 +302,26 @@ class Optima35: except ValueError: print(f"Invalid input. Must be of type {accepted_type.__name__}.") - def process_images(self): - """Process images based on user settings.""" - 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")) - ] - if "Change EXIF" in self.settings["modifications"]: - selected_exif = self.collect_exif_data() - i = 1 - for image_file in image_files: - input_path = os.path.join(input_folder, image_file) - - if "Rename images" in self.settings["modifications"]: - image_name = self.name_images(self.settings["new_file_names"], i, len(image_files), self.settings["invert_image_order"]) - else: - image_name = os.path.splitext(image_file)[0] - - output_path = os.path.join(output_folder, image_name) - - with self.image_processor.open_image(input_path) as img: - processed_img = img - for mod in self.settings["modifications"]: - if mod == "Resize image": - processed_img = self.image_processor.resize_image( - image = processed_img, percent = self.settings["resize_percentage"], resample = True - ) - elif mod == "Change EXIF" and selected_exif: - if "date_time_original" in selected_exif: - selected_exif = self.modify_timestamp_in_exif(selected_exif, image_name) - exif_data = self.exif_handler.build_exif_dict(selected_exif, self.image_processor.get_image_size(processed_img)) - elif mod == "Convert to grayscale": - processed_img = self.image_processor.grayscale(processed_img) - elif mod == "Change contrast": - processed_img = self.image_processor.change_contrast(processed_img, self.settings["contrast_percentage"]) - elif mod == "Change brightness": - processed_img = self.image_processor.change_brightness(processed_img, self.settings["brightness_percentage"]) - elif mod == "Add Watermark": - processed_img = self.image_processor.add_watermark(processed_img, self.settings["watermark_text"]) - - if self.settings["copy_exif"]: - # When copying exif from original, make sure to change Piexel X & Y Dimension to fit new size - try: - og_exif = self.exif_handler.get_exif_info(img) - og_exif["Exif"][40962], og_exif["Exif"][40963] = self.image_processor.get_image_size(processed_img) - exif_data = og_exif - except Exception: - # If an error happends it is because the picture does not have exif data - print("Copying EXIF data selected, but no EXIF data is available in the original image file.") - exif_data = None - elif "Change EXIF" not in self.settings["modifications"]: - exif_data = None - - self.image_processor.save_image( - image = processed_img, - path = output_path, - exif_data = exif_data, - file_type = self.settings["file_format"], - jpg_quality = self.settings["jpg_quality"], - png_compressing = self.settings["png_compression"], - optimize = self.settings["web_optimize"] - ) - self.utilities.progress_bar(i, len(image_files)) - i += 1 - - def name_images(self, base_name, current_image, total_images, invert): - """"Returns name, combination of base_name and ending number.""" - total_digits = len(str(total_images)) - if invert: - ending_number = total_images - (current_image - 1) - else: - ending_number = current_image - ending = f"{ending_number:0{total_digits}}" - return f"{base_name}_{ending}" - - def modify_timestamp_in_exif(self, exif_data, filename): - """"Takes exif data and adjust time to fit ending of filename.""" - try: - last_tree = filename[-3:len(filename)] - total_seconds = int(re.sub(r'\D+', '', last_tree)) - - minutes = total_seconds // 60 - seconds = total_seconds % 60 - time = datetime.strptime(exif_data["date_time_original"], "%Y:%m:%d %H:%M:%S") # change date time string back to an time object for modification - new_time = time.replace(hour=12, minute=minutes, second=seconds) - exif_data["date_time_original"] = new_time.strftime("%Y:%m:%d %H:%M:%S") - return exif_data - - except ValueError: - print("Modifying date went wrong, exiting...") - exit() - def run(self): """Run the main program.""" self.load_or_ask_settings() self.get_user_settings() - self.process_images() + self.process() print("Done") -if __name__ == "__main__": - app = Optima35("config/settings.yaml", "config/exif_options.yaml") + + +def main(exif_file, config_file): + app = Optima35TUI(exif_file, config_file) app.run() + +if __name__ == "__main__": + if os.path.isfile("config/exif.yaml"): + exif_file = "config/exif.yaml" + elif os.path.isfile("config/exif_example.yaml"): + exif_file = "config/exif_example.yaml" + print("Fall back to exif example file...") + else: + print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...") + exit() + main(exif_file, "config/tui_settings.yaml") diff --git a/ui/main_window.py b/ui/main_window.py index 4b9d852..126b11f 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -220,6 +220,7 @@ class Ui_MainWindow(object): self.restart_button = QPushButton(self.widget_9) self.restart_button.setObjectName(u"restart_button") self.restart_button.setEnabled(False) + self.restart_button.setFlat(False) self.horizontalLayout_3.addWidget(self.restart_button) @@ -450,6 +451,7 @@ class Ui_MainWindow(object): 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) @@ -483,6 +485,7 @@ class Ui_MainWindow(object): self.tabWidget.setCurrentIndex(0) self.font_size_comboBox.setCurrentIndex(2) + self.restart_button.setDefault(False) QMetaObject.connectSlotsByName(MainWindow) @@ -490,9 +493,9 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"OPTIMA-35", None)) - self.input_path.setText(QCoreApplication.translate("MainWindow", u"local_files/img", None)) + self.input_path.setText("") self.input_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Enter input folder", None)) - self.output_path.setText(QCoreApplication.translate("MainWindow", u"local_files/out", 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)) diff --git a/ui/main_window.ui b/ui/main_window.ui index abf83f3..9c0ba37 100644 --- a/ui/main_window.ui +++ b/ui/main_window.ui @@ -55,7 +55,7 @@ - local_files/img + Enter input folder @@ -65,7 +65,7 @@ - local_files/out + Enter output folder @@ -373,6 +373,12 @@ Restart + + false + + + false + @@ -654,6 +660,16 @@ false + + + 0 + 0 + 0 + 2025 + 1 + 1 + + diff --git a/ui/tui.py b/ui/simple_tui.py similarity index 100% rename from ui/tui.py rename to ui/simple_tui.py