Main starts either GUI or TUI. UI split from optima35.

This commit is contained in:
Mr Finchum 2024-12-31 12:06:11 +01:00
parent 1285e5a844
commit 40b7ca0782
3 changed files with 352 additions and 7 deletions

17
gui.py
View file

@ -2,7 +2,7 @@ import sys
import os import os
from datetime import datetime from datetime import datetime
from optima35 import OPTIMA35 from optima.optima35 import OPTIMA35
from utils.utility import Utilities from utils.utility import Utilities
from ui.main_window import Ui_MainWindow from ui.main_window import Ui_MainWindow
from ui.exif_handler_window import ExifEditor from ui.exif_handler_window import ExifEditor
@ -37,6 +37,9 @@ class Optima35GUI(QMainWindow, Ui_MainWindow):
self.default_ui_layout() self.default_ui_layout()
self.define_gui_interaction() self.define_gui_interaction()
if exif_file == "config/exif_example.yaml":
self.change_statusbar("Using example exif...", 10000)
def default_ui_layout(self): def default_ui_layout(self):
self.ui.png_quality_spinBox.setVisible(False) self.ui.png_quality_spinBox.setVisible(False)
@ -254,11 +257,19 @@ class Optima35GUI(QMainWindow, Ui_MainWindow):
python = sys.executable # Path to the Python interpreter python = sys.executable # Path to the Python interpreter
os.execv(python, [python] + sys.argv) os.execv(python, [python] + sys.argv)
def main(exif_file="local_files/exif.yaml"): def main(exif_file):
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
window = Optima35GUI(exif_file=exif_file) window = Optima35GUI(exif_file=exif_file)
window.show() window.show()
app.exec() app.exec()
if __name__ == "__main__": if __name__ == "__main__":
main() if os.path.isfile("config/exif.yaml"):
exif_file = "config/exif.yaml"
print("Fall back to exif example file...")
elif os.path.isfile("config/exif_example.yaml"):
exif_file = "config/exif_example.yaml"
else:
print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...")
exit()
main(exif_file)

15
main.py
View file

@ -1,8 +1,6 @@
import sys
import os import os
import subprocess
from argparse import ArgumentParser from argparse import ArgumentParser
exif_file = "local_files/exif.yaml"
# Mainly from ChatGPT # Mainly from ChatGPT
def check_pyside_installed(): def check_pyside_installed():
try: try:
@ -17,7 +15,7 @@ def start_gui():
def start_tui(): def start_tui():
import tui import tui
tui.main(exif_file) tui.main(exif_file, tui_settings_file)
def main(): def main():
parser = ArgumentParser(description="Start the Optima35 application.") parser = ArgumentParser(description="Start the Optima35 application.")
@ -43,4 +41,13 @@ def main():
start_tui() start_tui()
if __name__ == "__main__": if __name__ == "__main__":
if os.path.isfile("config/exif.yaml"):
exif_file = "config/exif.yaml"
elif os.path.isfile("config/exif_example.yaml"):
exif_file = "config/exif_example.yaml"
print("Fall back to exif example file...")
else:
print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...")
exit()
tui_settings_file = "config/tui_settings.yaml"
main() main()

327
tui.py Normal file
View file

@ -0,0 +1,327 @@
import os
from datetime import datetime
# my packages
from optima.optima35 import OPTIMA35
from utils.utility import Utilities
from ui.simple_tui import SimpleTUI
class Optima35TUI():
def __init__(self, exif_file, settings_file):
self.o = OPTIMA35()
self.u = Utilities()
self.tui = SimpleTUI()
self.exif_file = exif_file
self.exif_data = self.u.read_yaml(exif_file)
self.setting_file = settings_file
self.settings = {
"input_folder": None,
"output_folder": None,
"file_format": None,
"resize": None,
"copy_exif": None,
"contrast": None,
"brightness": None,
"new_file_names": None,
"invert_image_order": False,
"watermark": None,
"gps": None,
"modifications": [],
}
self.settings_to_save = [
"resize",
"jpg_quality",
"png_compression",
"optimize",
"contrast",
"brightness"
]
def process(self):
if "Change EXIF" in self.settings["modifications"]:
self.selected_exif = self.collect_exif_data()
self.check_options() # Get all user selected data
input_folder_valid = os.path.exists(self.o.settings["input_folder"])
output_folder_valid = os.path.exists(self.o.settings["output_folder"])
if not input_folder_valid or not output_folder_valid:
print("Warning", f"Input location {input_folder_valid}\nOutput folder {output_folder_valid}...")
return
input_folder = self.o.settings["input_folder"]
output_folder = self.o.settings["output_folder"]
image_files = [
f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
]
i = 1
for image_file in image_files:
input_path = os.path.join(input_folder, image_file)
if self.o.settings["new_file_names"] != False:
image_name = self.o.name_images(self.o.settings["new_file_names"], i, len(image_files), self.o.settings["invert_image_order"])
else:
image_name = os.path.splitext(image_file)[0]
output_path = os.path.join(output_folder, image_name)
self.o.process(input_path, output_path)
self.u.progress_bar(i, len(image_files))
i += 1
def check_options(self):
try:
if "Resize image" in self.settings["modifications"]:
self.o.settings["resize"] = self.settings["resize"]
else:
self.o.settings["resize"] = False
if "Convert to grayscale" in self.settings["modifications"]:
self.o.settings["grayscale"] = True
else:
self.o.settings["grayscale"] = False
if "Change contrast" in self.settings["modifications"]:
self.o.settings["contrast"] = self.settings["contrast"]
else:
self.o.settings["contrast"] = False
if "Change brightness" in self.settings["modifications"]:
self.o.settings["brightness"] = self.settings["brightness"]
else:
self.o.settings["brightness"] = False
if "Rename images" in self.settings["modifications"]:
self.o.settings["new_file_names"] = self.settings["new_file_names"]
else:
self.o.settings["new_file_names"] = False
if "Invert image order" in self.settings["modifications"]:
self.o.settings["invert_image_order"] = True
else:
self.o.settings["invert_image_order"] = False
if "Add Watermark" in self.settings["modifications"]:
self.o.settings["watermark"] = self.settings["watermark"]
else:
self.o.settings["watermark"] = False
self.o.settings["optimize"] = self.settings["optimize"]
self.o.settings["png_compression"] = self.settings["png_compression"]
self.o.settings["jpg_quality"] = self.settings["jpg_quality"]
self.o.settings["input_folder"] = self.settings["input_folder"]
self.o.settings["output_folder"] = self.settings["output_folder"]
self.o.settings["file_format"] = self.settings["file_format"]
self.o.settings["font_size"] = 2 # need to add option to select size
self.o.settings["copy_exif"] = self.settings["copy_exif"]
if "Change EXIF" in self.settings["modifications"]: #missing
self.o.selected_exif = self.selected_exif #
self.o.settings["own_exif"] = True
if self.settings["gps"] != None:
self.o.settings["gps"] = self.settings["gps"]
else:
self.o.settings["gps"] = False
else:
self.o.settings["own_exif"] = False
except Exception as e:
print(f"Whoops: {e}")
def load_or_ask_settings(self):
"""Load settings from a YAML file or ask the user if not present or incomplete."""
try:
if self.read_settings(self.settings_to_save):
for item in self.settings_to_save:
print(f"{item}: {self.settings[item]}")
use_saved = self.tui.yes_no_menu("Use these settings?")
if use_saved:
return
else:
print("No settings found...")
self.ask_for_settings()
except Exception as e:
print(f"Error: {e}")
self.ask_for_settings()
def ask_for_settings(self):
print("Asking for new settings...\n")
self.settings["resize"] = self.take_input_and_validate(question = "Default resize percentage (below 100 downscale, above upscale): ", accepted_type = int, min_value = 10, max_value = 200)
self.settings["contrast"] = self.take_input_and_validate(question = "Default contrast percentage (negative = decrease, positive = increase): ", accepted_type = int, min_value = -100, max_value = 100)
self.settings["brightness"] = self.take_input_and_validate(question = "Default brighness percentage (negative = decrease, positive = increase): ", accepted_type = int, min_value = -100, max_value = 100)
self.settings["jpg_quality"] = self.take_input_and_validate(question = "JPEG quality (1-100, 80 default): ", accepted_type = int, min_value = 1, max_value = 100)
self.settings["png_compression"] = self.take_input_and_validate(question = "PNG compression level (0-9, 6 default): ", accepted_type = int, min_value = 0, max_value = 9)
self.settings["optimize"] = self.tui.yes_no_menu("Optimize images i.e. compressing?")
self.write_settings(self.settings_to_save)
def write_settings(self, keys_to_save):
""""Write self.setting, but only specific values"""
keys = keys_to_save
filtered_settings = {key: self.settings[key] for key in keys if key in self.settings}
self.u.write_yaml(self.setting_file, filtered_settings)
print("New settings saved successfully.")
def read_settings(self, keys_to_load):
"""
Read settings from the settings file and update self.settings
with the values for specific keys without overwriting existing values.
"""
# First draft by ChatGPT, adjusted to fit my needs.
keys = keys_to_load
if os.path.exists(self.setting_file):
loaded_settings = self.u.read_yaml(self.setting_file)
for key in keys:
if key in loaded_settings:
self.settings[key] = loaded_settings[key]
print("Settings loaded successfully.")
return True
else:
print("Settings file empty.")
return False
def collect_exif_data(self):
"""Collect EXIF data based on user input."""
user_data = {}
fields = [
"make", "model", "lens", "iso", "image_description",
"user_comment", "artist", "copyright_info"
]
for field in fields:
choise = self.tui.choose_menu(f"Enter {field.replace('_', ' ').title()}", self.exif_data[field])
user_data[field] = choise
user_data["software"] = f"{self.o.name} {self.o.version}"
new_date = self.get_date_input()
if new_date:
user_data["date_time_original"] = new_date
self.settings["gps"] = self.get_gps_input(user_data)
return user_data
def get_gps_input(self, test_exif):
while True:
lat = input("Enter Latitude (xx.xxxxxx): ")
if lat == "":
return False
long = input("Enter Longitude (xx.xxxxxx): ")
try:
self.o.exif_handler.add_geolocation_to_exif(test_exif, float(lat), float(long))
return [lat, long]
except Exception:
print("Invalid GPS formate, try again...")
def get_date_input(self):
# Partially chatGPT
while True:
date_input = input("Enter a date (yyyy-mm-dd): ")
if date_input == "":
return None # Skip if input is empty
try:
new_date = datetime.strptime(date_input, "%Y-%m-%d")
return new_date.strftime("%Y:%m:%d 00:00:00")
except ValueError:
print("Invalid date format. Please enter the date in yyyy-mm-dd format.")
def get_user_settings(self):
"""Get initial settings from the user."""
menu_options = [
"Resize image",
"Change EXIF",
"Convert to grayscale",
"Change contrast",
"Change brightness",
"Rename images",
"Invert image order",
"Add Watermark"
] # new option can be added here.
self.settings["input_folder"] = "local_files/img"#input("Enter path of input folder: ").strip() # Add: check if folder exists.
self.settings["output_folder"] = "local_files/out"#input("Enter path of output folder: ").strip()
self.settings["file_format"] = "jpg"#self.take_input_and_validate(question = "Enter export file format (jpg, png, webp): ", accepted_input = ["jpg", "png", "webp"], accepted_type = str)
self.settings["modifications"] = self.tui.multi_select_menu(
f"\n{self.o.name} v.{self.o.version} \nSelect what you want to do (esc or q to exit)",
menu_options
)
if "Change EXIF" not in self.settings["modifications"]:
self.settings["copy_exif"] = self.tui.yes_no_menu("Do you want to copy exif info from original file?")
if "Rename images" in self.settings["modifications"]:
self.settings["new_file_names"] = input("What should be the name for the new images? ") # Need
else:
self.settings["new_file_names"] = False
if "Invert image order" in self.settings["modifications"]:
self.settings["invert_image_order"] = True
else:
self.settings["invert_image_order"] = False
if "Add Watermark" in self.settings["modifications"]:
self.settings["watermark"] = input("Enter text for watermark. ")
else:
self.settings["watermark"] = False
os.makedirs(self.settings["output_folder"], exist_ok = True)
def take_input_and_validate(self, question, accepted_input = None, accepted_type = str, min_value = None, max_value = None):
"""
Asks the user a question, validates the input, and ensures it matches the specified criteria.
Args:
question (str): The question to ask the user.
accepted_input (list): A list of acceptable inputs (optional for non-numeric types).
accepted_type (type): The expected type of input (e.g., str, int, float).
min_value (int/float): Minimum value for numeric inputs (optional).
max_value (int/float): Maximum value for numeric inputs (optional).
Returns:
The validated user input.
"""
# Main layout by chatGPT, but modified.
while True:
user_input = input(question).strip()
try:
# Convert input to the desired type
if accepted_type in [int, float]:
user_input = accepted_type(user_input)
# Validate range for numeric types
if (min_value is not None and user_input < min_value) or (max_value is not None and user_input > max_value):
print(f"Input must be between {min_value} and {max_value}.")
continue
elif accepted_type == str:
# No conversion needed for strings
user_input = str(user_input)
else:
raise ValueError(f"Unsupported type: {accepted_type}")
# Validate against accepted inputs if provided
if accepted_input is not None and user_input not in accepted_input:
print(f"Invalid input. Must be one of: {', '.join(map(str, accepted_input))}.")
continue
return user_input # Input is valid
except ValueError:
print(f"Invalid input. Must be of type {accepted_type.__name__}.")
def run(self):
"""Run the main program."""
self.load_or_ask_settings()
self.get_user_settings()
self.process()
print("Done")
def main(exif_file, config_file):
app = Optima35TUI(exif_file, config_file)
app.run()
if __name__ == "__main__":
if os.path.isfile("config/exif.yaml"):
exif_file = "config/exif.yaml"
elif os.path.isfile("config/exif_example.yaml"):
exif_file = "config/exif_example.yaml"
print("Fall back to exif example file...")
else:
print("Exif file missing, please ensure an exif file exist in config folder (exif.yaml, or exif_example_yaml)\nExiting...")
exit()
main(exif_file, "config/tui_settings.yaml")