This commit is contained in:
Mr Finchum 2024-12-27 21:44:23 +01:00
parent bc65ae99e5
commit 750ac179d8

145
main.py
View file

@ -1,6 +1,6 @@
import os import os
import re
from datetime import datetime from datetime import datetime
#from debug import my_debugging_tools # Removed for main push
from utility import Utilities from utility import Utilities
from image_handler import ImageProcessor, ExifHandler from image_handler import ImageProcessor, ExifHandler
from tui import SimpleTUI from tui import SimpleTUI
@ -8,8 +8,7 @@ from tui import SimpleTUI
class Optima35: class Optima35:
# The layout of class Optima35 was originally made by ChatGPT, but major adjustments have been made. To remain transparent, I disclose this. # 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): def __init__(self, settings_file, exif_options_file):
self.version = "0.1.0" self.version = "0.1.1"
#self.debugger = my_debugging_tools() # Removed for main push
self.utilities = Utilities() self.utilities = Utilities()
self.image_processor = ImageProcessor() self.image_processor = ImageProcessor()
self.exif_handler = ExifHandler() self.exif_handler = ExifHandler()
@ -27,13 +26,20 @@ class Optima35:
"watermark_text": None, "watermark_text": None,
"modifications": [], "modifications": [],
} }
self.settings_to_save = ["resize_percentage", "jpg_quality", "png_compression", "web_optimize", "contrast_percentage", "brightness_percentage"] self.settings_to_save = [
"resize_percentage",
"jpg_quality",
"png_compression",
"web_optimize",
"contrast_percentage",
"brightness_percentage"
]
self.exif_choices = self.utilities.read_yaml(exif_options_file) self.exif_choices = self.utilities.read_yaml(exif_options_file)
self.setting_file = settings_file self.setting_file = settings_file
def load_or_ask_settings(self): def load_or_ask_settings(self):
"""Load settings from a YAML file or ask the user if not present or incomplete.""" """Load settings from a YAML file or ask the user if not present or incomplete."""
# Partly ChatGPT # Partially ChatGPT
if self.read_settings(self.settings_to_save): if self.read_settings(self.settings_to_save):
for item in self.settings_to_save: for item in self.settings_to_save:
print(f"{item}: {self.settings[item]}") print(f"{item}: {self.settings[item]}")
@ -44,12 +50,12 @@ class Optima35:
print("No settings found...") print("No settings found...")
print("Asking for new settings...\n") print("Asking for new settings...\n")
self.settings["resize_percentage"] = int(input("Default resize percentage: ").strip()) 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"] = int(input("Default contrast percentage: ").strip()) 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"] = int(input("Default brightness percentage: ").strip()) 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["jpg_quality"] = int(input("JPEG quality (1-100): ").strip()) 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"] = int(input("PNG compression level (0-9): ").strip()) 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 for web?") self.settings["web_optimize"] = self.tui.yes_no_menu("Optimize images i.e. compressing?")
self.write_settings(self.settings_to_save) self.write_settings(self.settings_to_save)
@ -57,7 +63,6 @@ class Optima35:
""""Write self.setting, but only specific values""" """"Write self.setting, but only specific values"""
keys = keys_to_save keys = keys_to_save
filtered_settings = {key: self.settings[key] for key in keys if key in self.settings} 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.utilities.write_yaml(self.setting_file, filtered_settings)
print("New settings saved successfully.") print("New settings saved successfully.")
@ -70,7 +75,6 @@ class Optima35:
keys = keys_to_load keys = keys_to_load
if os.path.exists(self.setting_file): if os.path.exists(self.setting_file):
loaded_settings = self.utilities.read_yaml(self.setting_file) loaded_settings = self.utilities.read_yaml(self.setting_file)
for key in keys: for key in keys:
if key in loaded_settings: if key in loaded_settings:
self.settings[key] = loaded_settings[key] self.settings[key] = loaded_settings[key]
@ -92,43 +96,107 @@ class Optima35:
user_data[field] = choise.encode("utf-8") user_data[field] = choise.encode("utf-8")
user_data["software"] = f"OPTIMA-35 {self.version}".encode("utf-8") user_data["software"] = f"OPTIMA-35 {self.version}".encode("utf-8")
user_data["date_time_original"] = datetime.now().strftime("%Y:%m:%d %H:%M:%S").encode("utf-8") new_date = self.get_date_input()
if new_date:
user_data["date_time_original"] = new_date
return user_data return user_data
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): def get_user_settings(self):
"""Get initial settings from the user.""" """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. menu_options = [
self.settings["input_folder"] = input("Enter path of input folder: ").strip() # Add: check if folder exists "Resize image",
"Change EXIF",
"Convert to grayscale",
"Change contrast",
"Change brightness",
"Rename images",
"Invert image order",
"Add Watermark"
] # new option can be added here.
self.settings["input_folder"] = input("Enter path of input folder: ").strip() # Add: check if folder exists.
self.settings["output_folder"] = input("Enter path of output folder: ").strip() self.settings["output_folder"] = input("Enter path of output folder: ").strip()
self.settings["file_format"] = input("Enter export file format (e.g., jpg, png): ").strip() # Add: specific question depending on export file type, like jpg quality and png compression 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( self.settings["modifications"] = self.tui.multi_select_menu(
"Select what you want to do", menu_options "Select what you want to do", menu_options
) )
if "Change EXIF" not in self.settings["modifications"]: 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?") 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"]: if "Rename images" in self.settings["modifications"]:
self.settings["new_file_names"] = input("What should be the name for the new images? ") self.settings["new_file_names"] = input("What should be the name for the new images? ") # Need
if "Invert image order" in self.settings["modifications"]: if "Invert image order" in self.settings["modifications"]:
self.settings["invert_image_order"] = True self.settings["invert_image_order"] = True
if "Add Watermark" in self.settings["modifications"]: if "Add Watermark" in self.settings["modifications"]:
self.settings["watermark_text"] = input("Enter text for watermark. ") self.settings["watermark_text"] = input("Enter text for watermark. ")
os.makedirs(self.settings["output_folder"], exist_ok = True) 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 process_images(self): def process_images(self):
"""Process images based on user settings.""" """Process images based on user settings."""
input_folder = self.settings["input_folder"] input_folder = self.settings["input_folder"]
output_folder = self.settings["output_folder"] output_folder = self.settings["output_folder"]
image_files = [ image_files = [
f for f in os.listdir(input_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg')) f for f in os.listdir(input_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))
] ]
if "Change EXIF" in self.settings["modifications"]: if "Change EXIF" in self.settings["modifications"]:
selected_exif = self.collect_exif_data() selected_exif = self.collect_exif_data()
i = 1 i = 1
for image_file in image_files: for image_file in image_files:
input_path = os.path.join(input_folder, image_file) input_path = os.path.join(input_folder, image_file)
if "Rename images" in self.settings["modifications"]: if "Rename images" in self.settings["modifications"]:
@ -146,6 +214,8 @@ class Optima35:
image = processed_img, percent = self.settings["resize_percentage"], resample = True image = processed_img, percent = self.settings["resize_percentage"], resample = True
) )
elif mod == "Change EXIF" and selected_exif: 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)) exif_data = self.exif_handler.build_exif_dict(selected_exif, self.image_processor.get_image_size(processed_img))
elif mod == "Convert to grayscale": elif mod == "Convert to grayscale":
processed_img = self.image_processor.grayscale(processed_img) processed_img = self.image_processor.grayscale(processed_img)
@ -166,13 +236,17 @@ class Optima35:
# If an error happends it is because the picture does not have exif data # 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.") print("Copying EXIF data selected, but no EXIF data is available in the original image file.")
exif_data = None exif_data = None
else: elif "Change EXIF" not in self.settings["modifications"]:
exif_data = None exif_data = None
self.image_processor.save_image( self.image_processor.save_image(
image = processed_img, path = output_path, exif_data = exif_data, image = processed_img,
file_type = self.settings["file_format"], jpg_quality = self.settings["jpg_quality"], path = output_path,
png_compressing = self.settings["png_compression"], _optimize = self.settings["web_optimize"] 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)) self.utilities.progress_bar(i, len(image_files))
i += 1 i += 1
@ -180,15 +254,30 @@ class Optima35:
def name_images(self, base_name, current_image, total_images, invert): def name_images(self, base_name, current_image, total_images, invert):
""""Returns name, combination of base_name and ending number.""" """"Returns name, combination of base_name and ending number."""
total_digits = len(str(total_images)) total_digits = len(str(total_images))
if invert: if invert:
ending_number = total_images - (current_image - 1) ending_number = total_images - (current_image - 1)
else: else:
ending_number = current_image ending_number = current_image
ending = f"{ending_number:0{total_digits}}" ending = f"{ending_number:0{total_digits}}"
return f"{base_name}_{ending}" 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): def run(self):
"""Run the main program.""" """Run the main program."""
self.load_or_ask_settings() self.load_or_ask_settings()