UIpdate image script

This commit is contained in:
2024-10-11 12:52:08 +02:00
parent 33054a1f1c
commit e15e337dbe
28 changed files with 1655 additions and 610 deletions

View File

@@ -1,24 +1,43 @@
"""
Module for WooCommerce API interactions and image processing.
"""
import json
import os
import base64
import tempfile
import pprint
from tkinter import messagebox
from cryptography.fernet import Fernet
import requests
from tkinter import messagebox
from woocommerce import API
from cryptography.fernet import Fernet
from utils.image_processing import ImageProcessor
from config.encrypt_config import ConfigEncryptor
from utils.file_operations import FileProcessor
import hashlib
import pprint
CREDENTIALS_FILE = "credentials.json"
# Hardcoded key (replace with your generated key)
KEY = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
def save_active_credential_set(active_set_name):
"""
Update the active credential set in the saved credentials file.
Args:
active_set_name (str): The name of the active credential set.
"""
if not os.path.exists(CREDENTIALS_FILE):
return
with open(CREDENTIALS_FILE, 'r+') as file:
data = json.load(file)
# Find the credential set and mark it as active
for cred in data.get('credentials', []):
cred['active'] = (cred['name'] == active_set_name)
# Rewrite the updated data back to the file
file.seek(0)
json.dump(data, file, indent=4)
file.truncate()
def save_credentials(url, consumer_key, consumer_secret, username, password):
"""
@@ -39,7 +58,7 @@ def save_credentials(url, consumer_key, consumer_secret, username, password):
"password": password,
}
ConfigEncryptor(KEY).save_credentials(credentials)
ConfigEncryptor(KEY).save_credentials(consumer_key, consumer_secret, username, password)
def load_credentials():
@@ -49,37 +68,30 @@ def load_credentials():
Returns:
dict: The decrypted credentials, or None if the file does not exist.
"""
if not os.path.exists("config.enc"):
return None
fernet = Fernet(KEY)
with open("config.enc", "rb") as file:
encrypted = file.read()
decrypted = fernet.decrypt(encrypted).decode()
return json.loads(decrypted).get("credentials")
creds = ConfigEncryptor(KEY).load_credentials()
return creds
def get_wcapi():
"""
Get a WooCommerce API client instance.
Get a WooCommerce API client instance using the active credentials.
Returns:
woocommerce.API: The WooCommerce API client instance, or None if credentials are missing.
"""
credentials = load_credentials()
if not credentials:
messagebox.showerror(
"Error",
"No WooCommerce credentials found. Please set them in the settings.",
)
return None
active_credentials = load_credentials()
pprint.pprint(active_credentials)
return API(
url=credentials["url"],
consumer_key=credentials["consumer_key"],
consumer_secret=credentials["consumer_secret"],
url=active_credentials["url"],
consumer_key=active_credentials["consumer_key"],
consumer_secret=active_credentials["consumer_secret"],
version="wc/v3",
)
def get_product(product_id):
"""
Get a WooCommerce product and download its images.
@@ -95,8 +107,13 @@ def get_product(product_id):
return None
result = wcapi.get(f"products/{product_id}")
image_paths = {}
product = result.json()
return product
def get_images(product, limit = 0):
image_paths = {}
if product.get("images"):
images = product.get("images")
@@ -116,14 +133,19 @@ def get_product(product_id):
print(
f"Image {index + 1}/{len(images)} downloaded and saved: {file_path}"
)
if limit and limit >= index +1:
break
else:
print(f"Failed to download image {index + 1}/{len(images)}")
return image_paths
else:
if product.get("name"):
print(f"No images found for {product.get('name')}")
else:
print("No images found")
return image_paths, product
return []
def upload_image(img_path):
@@ -139,6 +161,8 @@ def upload_image(img_path):
with open(img_path, "rb") as img_file:
data = img_file.read()
file_name = os.path.basename(img_path)
file_name = file_name.replace("", "-")
credentials = load_credentials()
if not credentials:
messagebox.showerror(
@@ -155,7 +179,7 @@ def upload_image(img_path):
"Content-Disposition": f"attachment; filename={file_name}",
"Authorization": f"basic {credentials_base64.decode()}",
}
print(f"Uploading image {img_path}")
try:
res = requests.post(url=url, data=data, headers=headers, timeout=10)
res.raise_for_status()
@@ -180,6 +204,7 @@ def delete_img(image_id):
Args:
image_id (int): The ID of the image to delete.
"""
credentials = load_credentials()
if not credentials:
messagebox.showerror(
@@ -205,27 +230,51 @@ def delete_img(image_id):
print(f"Failed to delete image with ID {image_id}. Error: {res.text}")
def update_product(image_ids, product_id):
def update_product(product_id, new_list, old_list, options):
"""
Update a WooCommerce product with new image IDs.
Update the images and meta data of a WooCommerce product.
Args:
image_ids (list): A list of new image IDs.
product_id (int): The ID of the WooCommerce product.
"""
wcapi = get_wcapi()
if not wcapi:
return
product = wcapi.get(f"products/{product_id}").json()
product["images"] = [{"id": image_id} for image_id in image_ids]
response = wcapi.put(f"products/{product_id}", data=product)
# Prepare the data with images and meta data fields
product_data = {
"images": [{"id": image_id} for image_id in new_list],
"meta_data": [
{
"key": "_image_processed",
"value": options['hash_string']
},
{
"key": "_old_image_ids",
"value": [{"id": image_id} for image_id in old_list]
}
]
}
# Print product data for debugging
print(f"Updating product {product_id} with the following data:")
print(json.dumps(product_data, indent=2))
# Send the update request with images and meta data fields
response = wcapi.put(f"products/{product_id}", data=product_data) # Using 'json' to pass data
if response.status_code == 200:
print(
f"Product with ID {product_id} updated successfully with new image IDs.")
print(f"Product with ID {product_id} updated successfully with new image IDs and meta data.")
else:
print(
f"Failed to update product with ID {product_id}. Error: {response.text}")
print(f"Failed to update product with ID {product_id}. Error: {response.text}")
def process_product_images(options):
@@ -233,36 +282,62 @@ def process_product_images( options):
Process images for a WooCommerce product by resizing and uploading them.
Args:
product_id (int): The ID of the WooCommerce product.
name_template (str): The template for generating image filenames.
canvas_width (int): The width of the canvas for resizing images.
canvas_height (int): The height of the canvas for resizing images.
options (dict): Contains options such as product_id, name_template, canvas_width, canvas_height.
"""
# Concatenate the values into a string
hash_input = f"{options['background_color']}_{options['canvas_height']}_{options['canvas_width']}_{options['image_format']}_{options['image_size']}"
# Create a SHA256 hash from the concatenated string
hash_object = hashlib.sha256(hash_input.encode())
hash_string = hash_object.hexdigest()
options['hash_string'] = hash_string
pprint.pprint(hash_string)
product_id = options.get("product_id")
if not product_id:
print("No product ID")
return
image_paths, product = get_product(product_id)
product = options.get("product")
# Check if the product meta_data contains _image_processed with the current hash
if product['meta_data']:
for meta in product['meta_data']:
if meta['key'] == '_image_processed' and meta['value'] == hash_string:
print(f"Skipping product {product_id}, already processed with the current hash.")
return
image_paths = get_images(product)
if not image_paths:
return
with tempfile.TemporaryDirectory() as temp_output_directory:
print(f"Using temporary directory: {temp_output_directory}")
old_list = []
new_list = []
pprint.pprint ( list(image_paths.values()))
file = FileProcessor()
log = options.get("log_message", None)
for image_id, file_path in image_paths.items():
file = FileProcessor()
img = ImageProcessor()
output_path = file.generate_output_path(temp_output_directory, file_path, options, product)
img.resize_image(file_path, output_path, options)
new_id = upload_image(output_path)
processed = file.process_images([file_path], temp_output_directory, options, log, product)
new_id = upload_image(processed[0])
if new_id:
old_list.append(image_id)
new_list.append(new_id)
update_product(new_list, product_id)
if new_list:
options["image_ids"] = new_list # Store new image IDs in options
update_product(product_id, new_list, old_list, options) # Pass new image IDs here
for old in old_list:
delete_img(old)
print("Temporary files processed and uploaded successfully.")
@@ -294,35 +369,95 @@ def generate_output_path(
)
return os.path.join(temp_output_directory, new_filename + ext)
def process_all_products(options):
def get_first_image_path(product):
images = get_images(product, 1)
# Loop through the dictionary
if images:
for image_id, file_path in images.items():
print(f"Processing Image ID: {image_id}")
print(f"File Path: {file_path}")
return file_path
def get_first_image():
"""
Process images for all WooCommerce products by resizing and uploading them.
Args:
name_template (str): The template for generating image filenames.
canvas_width (int): The width of the canvas for resizing images.
canvas_height (int): The height of the canvas for resizing images.
options (dict): Contains options such as name_template, canvas_width, canvas_height.
"""
wcapi = get_wcapi()
if not wcapi:
return
page = 1
total_products = 0 # Initialize the counter for total products
products = wcapi.get("products", params={"per_page": 5, "page": page}).json()
if not products:
return
for product in products:
total_products += 1 # Update the total count
return get_first_image_path(product)
def search_product(search):
"""
Process images for all WooCommerce products by resizing and uploading them.
Args:
options (dict): Contains options such as name_template, canvas_width, canvas_height.
"""
wcapi = get_wcapi()
if not wcapi:
return
page = 1
total_products = 0 # Initialize the counter for total products
while True:
products = wcapi.get("products", params={
"per_page": 100, "page": page}).json()
products = wcapi.get("products", params={"per_page": 100, "page": page, "search": search}).json()
if not products:
break
return products
def process_all_products(options):
"""
Process images for all WooCommerce products by resizing and uploading them.
Args:
options (dict): Contains options such as name_template, canvas_width, canvas_height.
"""
wcapi = get_wcapi()
if not wcapi:
return
page = 1
total_products = 0 # Initialize the counter for total products
while True:
products = wcapi.get("products", params={"per_page": 100, "page": page}).json()
if not products:
break
product_count = len(products) # Get the count of products on the current page
for product in products:
total_products += 1 # Update the total count
options["product_id"] = product["id"]
process_product_images(
options
)
options["product"] = product
log = options.get("log_message", None)
if log:
if product:
name = product.get("name", "")
log.log_message(f"#{total_products} Processing {name} ") # Log the product name
process_product_images(options)
page += 1
# Log the total number of products processed
log(f"Total products processed: {total_products}")
# Show completion message
messagebox.showinfo(
"Process Complete", "All product images processing is complete."
"Process Complete", f"All product images processing is complete. Total products processed: {total_products}"
)

View File

@@ -1,5 +1,6 @@
from cryptography.fernet import Fernet
import json
import os
class ConfigEncryptor:
@@ -9,51 +10,153 @@ class ConfigEncryptor:
self.fernet = Fernet(self.key)
def encrypt_config(self, data):
"""
Encrypt the given data and save it to a file.
Args:
data (dict): The dictionary containing credentials and options to encrypt and save.
"""
try:
json_data = json.dumps(data)
encrypted_data = self.fernet.encrypt(json_data.encode())
with open(self.filename, "wb") as encrypted_file:
encrypted_file.write(encrypted_data)
print("Credentials saved")
print(f"Encrypted configuration saved to {self.filename}")
except Exception as e:
print(f"Error encrypting config: {e}")
def get_key(self):
"""
Return the encryption key.
Returns:
str: The encryption key as a string.
"""
return self.key.decode()
def save_credentials(self, credentials):
config = self.load_config()
if not config:
config = {"credentials": {}, "options": {}}
config["credentials"] = credentials
"""
Save WooCommerce credentials to the config file, handling multiple credential sets.
Args:
credentials (dict): Dictionary containing WooCommerce credentials.
"""
# Load the existing configuration
config = self.load_config() or {"credentials": [], "options": {}}
# Ensure credentials is a list of dictionaries (if this is the first time saving, initialize it)
if not isinstance(config.get("credentials"), list):
config["credentials"] = []
# Check if the credential with the same 'name' or 'nice_name' already exists and update it
existing_credential = None
for cred in config["credentials"]:
print(credentials)
if cred.get("nice_name") == credentials.get("nice_name"):
existing_credential = cred
break
if existing_credential:
# Update the existing credential set
existing_credential.update(credentials)
else:
# Add new credentials if they don't exist
config["credentials"].append(credentials)
# Set 'active' flag to True for this credential and False for others
for cred in config["credentials"]:
cred['active'] = cred.get("nice_name") == credentials.get("nice_name")
# Encrypt and save the updated config
self.encrypt_config(config)
print(f"Credentials for {credentials.get('nice_name', 'Unnamed')} saved successfully.")
def delete_credentials(self, credentials):
"""
Save WooCommerce credentials to the config file, handling multiple credential sets.
Args:
credentials (dict): Dictionary containing WooCommerce credentials.
"""
# Load the existing configuration
config = self.load_config() or {"credentials": [], "options": {}}
new_config = []
for credi in config["credentials"]:
if credi.get("nice_name") != credentials:
new_config.append(credi)
config["credentials"] = new_config
print(config)
# Encrypt and save the updated config
self.encrypt_config(config)
def save_options(self, options):
config = self.load_config()
if not config:
config = {"credentials": {}, "options": {}}
# Ensure options only contains serializable data
"""
Save options to the config file. Filters out non-serializable data.
Args:
options (dict): Dictionary containing options such as canvas width, height, etc.
"""
config = self.load_config() or {"credentials": {}, "options": {}}
serializable_options = {k: v for k, v in options.items() if self.is_json_serializable(v)}
config["options"] = serializable_options
self.encrypt_config(config)
def load_config(self):
"""
Load and decrypt the config file.
Returns:
dict: Decrypted configuration data containing credentials and options, or None if file not found.
"""
if not os.path.exists(self.filename):
print(f"Config file {self.filename} not found.")
return None
try:
with open(self.filename, "rb") as encrypted_file:
encrypted_data = encrypted_file.read()
decrypted_data = self.fernet.decrypt(encrypted_data).decode()
config = json.loads(decrypted_data)
# Filter only relevant keys
keys_to_return = ["credentials", "options"]
return {key: config[key] for key in keys_to_return if key in config}
except FileNotFoundError:
return config
except Exception as e:
print(f"Error loading or decrypting config: {e}")
return None
def load_credentials(self):
"""
Load the active WooCommerce credentials from the config file.
Returns:
dict: The active WooCommerce credentials if found, otherwise None.
"""
config = self.load_config()
if config:
return config.get("credentials")
# Check if credentials exist and search for the one marked as 'active'
credentials_list = config.get("credentials", [])
if isinstance(credentials_list, list):
for credentials in credentials_list:
if credentials.get("active"):
return credentials
elif isinstance(credentials_list, dict):
return credentials_list
return None
@staticmethod
def is_json_serializable(value):
"""
Check if a value is JSON serializable.
Args:
value: The value to check.
Returns:
bool: True if value is serializable, False otherwise.
"""
try:
json.dumps(value)
return True
@@ -62,7 +165,6 @@ class ConfigEncryptor:
# Define your key here
# Replace with your actual key
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
if __name__ == "__main__":
@@ -84,4 +186,3 @@ if __name__ == "__main__":
}
encryptor = ConfigEncryptor(key)
encryptor.encrypt_config(config_data)

619
controller.py Normal file
View File

@@ -0,0 +1,619 @@
import tempfile
import threading
from utils.file_operations import FileProcessor
from utils.image_processing import ImageProcessor
from ui.options_window import OptionsWindow
from config.encrypt_config import ConfigEncryptor
from api.woocommerce_api import get_first_image
from PIL import Image, ImageTk
from pprint import pformat
from api.woocommerce_api import process_product_images, process_all_products, search_product, get_first_image_path
import customtkinter as ctk
import os
class AppController:
"""
The controller class for managing the overall state and interactions of the application.
"""
def __init__(self, root):
"""
Initialize the AppController.
Args:
root (ctk.CTk): The root CustomTkinter window.
"""
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
self.root = root
self.file = FileProcessor()
self.image = ImageProcessor()
self.menu_bar = None
self.local_processing_tab = None
self.settings_tab = None
self.preview_bar = None
self.log = None
self.canvas_width = 900
self.canvas_height = 900
self.template = "{slug}_{sku}_{width}x{height}"
self.delete_images = False
self.transparent = True
self.background_color = "#000000"
self.image_format = "AUTO"
self.image_size = "contain"
self.config = ConfigEncryptor(key)
self.type = None
self.destination_path = None
self.found_products = None
self.selected_directory = None
self.current_product = 0
self.status = "stopped"
self.load_config()
def load_config(self):
config = self.config.load_config()
if config:
if options := config.get("options"):
self.canvas_width = options.get("canvas_width", 900)
self.canvas_height = options.get("canvas_height", 900)
self.template = options.get("template", "{slug}_{sku}_{width}x{height}")
self.delete_images = options.get("delete_images", False)
self.transparent = options.get("transparent", True)
self.background_color = options.get("background_color", "#000000")
self.image_format = options.get("image_format", "AUTO")
self.image_size = options.get("image_size", "contain")
def set_menu_bar(self, menu_bar):
"""
Set the MenuBar for the application.
Args:
menu_bar (MenuBar): The MenuBar instance.
"""
self.log_message("Init menu bar")
self.menu_bar = menu_bar
def set_log(self, log):
"""
Set the MenuBar for the application.
Args:
menu_bar (MenuBar): The MenuBar instance.
"""
self.log = log
self.log_message("Init Logs")
def set_local_processing_tab(self, local_processing_tab):
"""
Set the LocalProcessingTab for the application.
Args:
local_processing_tab (LocalProcessingTab): The LocalProcessingTab instance.
"""
self.local_processing_tab = local_processing_tab
self.log_message("Init main")
def set_preview_bar(self, preview):
"""
Set the MenuBar for the application.
Args:
menu_bar (MenuBar): The MenuBar instance.
"""
self.preview_bar = preview
self.log_message("Init previews")
def set_info_bar(self, info):
"""
Set the MenuBar for the application.
Args:
menu_bar (MenuBar): The MenuBar instance.
"""
self.info_bar = info
self.log_message("Init info")
def set_settings_tab(self, settings_tab):
"""
Set the SettingsTab for the application.
Args:
settings_tab (SettingsTab): The SettingsTab instance.
"""
self.settings_tab = settings_tab
self.log_message("Init settings")
def show_local_processing_tab(self):
"""
Display the local processing tab.
"""
if self.local_processing_tab:
self.log_message("Show main tab")
self.local_processing_tab.tkraise() # Make sure to raise the correct tab frame
def show_settings_tab(self):
"""
Display the settings tab.
"""
if self.settings_tab:
self.log_message("Show settings tab")
self.settings_tab.tab.tkraise() # Make sure to raise the correct tab frame
def show_local_processing_options(self):
"""
Display the options window in the local processing tab.
"""
if self.local_processing_tab:
self.log_message("Open options")
self.open_options_window()
def log_message(self, obj):
"""
Log a formatted message to the log window using pprint.
Args:
obj (object): The object to format and log.
"""
if self.log:
formatted_message = pformat(obj)
self.log.log_message(formatted_message)
import threading
def start_processing(self):
"""
Start the image processing based on the selected options.
"""
source = self.type
options = self.get_options()
self.log_message(f"Start import source: {source}")
self.status = "started"
self.menu_bar.start_button.configure(fg_color="red", text="Running")
# Wrapper to process and update status after completion
def process_and_update_status(target_func, *args):
try:
# Execute the actual processing function
target_func(*args)
finally:
# Update status to 'stopped' after processing is done
self.status = "stopped"
self.menu_bar.start_button.configure(fg_color="#008000", text="Start")
self.log_message(f"Processing completed for source: {source}")
if source == "directory":
threading.Thread(
target=process_and_update_status, args=(self.file.process_directory_with_logging, options)
).start()
elif source == "product":
threading.Thread(
target=process_and_update_status, args=(process_product_images, options)
).start()
elif source == "file":
threading.Thread(
target=process_and_update_status, args=(self.file.proces_single_image, options)
).start()
elif source == "all_products":
threading.Thread(
target=process_and_update_status, args=(process_all_products, options)
).start()
def update_options(self, text=None):
"""
Update the UI elements based on the selected source type.
"""
self.type = text
self.log_message(f"Update options {text}")
if text:
self.update_info(text)
# if self.local_processing_tab:
# self.local_processing_tab.product_id_button.grid_remove()
# self.local_processing_tab.product_id_entry.grid_remove()
# self.local_processing_tab.additional_name_label.grid_remove()
# self.local_processing_tab.additional_name_entry.grid_remove()
# self.local_processing_tab.browse_button.grid_remove()
# self.local_processing_tab.browse_file_button.grid_remove()
# if self.type == "directory":
# self.local_processing_tab.browse_button.grid()
# elif self.type == "file":
# self.local_processing_tab.browse_button.grid()
# elif self.type == "all_products":
# pass
# elif self.type == "wp_image":
# self.local_processing_tab.product_id_button.grid()
# self.local_processing_tab.product_id_entry.grid()
# elif self.type == "product":
# self.local_processing_tab.product_id_button.grid()
# self.local_processing_tab.product_id_entry.grid()
self.update_previews()
def update_previews(self, before_path=None, after_path=None):
"""
Update the image previews.
Args:
before_path (str, optional): The path to the 'before' image.
after_path (str, optional): The path to the 'after' image.
"""
first_image_path = False
if self.status != "started":
if self.type == "all_products":
first_image_path = get_first_image()
elif self.type == "product" and self.found_products:
first_image_path = get_first_image_path(self.found_products[self.current_product])
else:
print("getting first path")
first_image_path = self.file.get_first_image_path()
if before_path :
before_img = Image.open(before_path)
before_img.thumbnail((200, 200))
before_photo = ImageTk.PhotoImage(before_img)
self.preview_bar.before_image_label.configure(image=before_photo)
self.preview_bar.before_image_label.image = before_photo
dir_name = os.path.basename(before_path)
if len(dir_name) > 35:
dir_name = f"...{dir_name[-35:]}"
self.preview_bar.before_filename_label.configure(text=dir_name)
if after_path:
after_img = Image.open(after_path)
after_img.thumbnail((200, 200))
after_photo = ImageTk.PhotoImage(after_img)
self.preview_bar.after_image_label.configure(image=after_photo)
self.preview_bar.after_image_label.image = after_photo
dir_name = os.path.basename(after_path)
if len(dir_name) > 35:
dir_name = f"...{dir_name[-35:]}"
self.preview_bar.before_filename_label.configure(text=dir_name)
if first_image_path:
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as temp_file:
output_path = temp_file.name
self.image.resize_image(
first_image_path, output_path, self.get_options()
)
before_img = Image.open(first_image_path)
before_img.thumbnail((200, 200))
before_photo = ImageTk.PhotoImage(before_img)
self.preview_bar.before_image_label.configure(image=before_photo)
self.preview_bar.before_image_label.image = before_photo
after_img = Image.open(output_path)
after_img.thumbnail((200, 200))
after_photo = ImageTk.PhotoImage(after_img)
self.preview_bar.after_image_label.configure(image=after_photo)
self.preview_bar.after_image_label.image = after_photo
name = self.file.generate_output_path("/",first_image_path,self.get_options())
dir_name = os.path.basename(name)
if len(dir_name) > 35:
dir_name = f"...{dir_name[-35:]}"
self.preview_bar.after_filename_label.configure(text=dir_name)
dir_name = os.path.basename(first_image_path)
if len(dir_name) > 35:
dir_name = f"...{dir_name[-35:]}"
self.preview_bar.before_filename_label.configure(text=dir_name)
def set_image_preview(self, image_path, label):
"""
Set the image preview for a given label.
Args:
image_path (str): The path to the image file.
label (ctk.CTkLabel): The label to set the image on.
"""
img = Image.open(image_path)
img.thumbnail((150, 150))
photo = ImageTk.PhotoImage(img)
label.configure(image=photo)
label.image = photo
def browse_file_command(self):
"""
Command to browse for a file.
"""
file = self.file.browse_files()
if file:
file_name = os.path.basename(file)
if len(file_name) > 35:
file_name = f"...{file_name[-35:]}"
self.info_bar.selected_button_label.configure(text=file_name)
self.apply_options(self.get_options())
self.update_previews()
def browse_directory_command(self):
"""
Command to browse for a directory.
"""
directory = self.file.browse_directory()
if directory:
dir_name = os.path.basename(directory)
if len(dir_name) > 35:
dir_name = f"...{dir_name[-35:]}"
# self.browse_button.configure(text=dir_name)
self.selected_directory = directory
self.apply_options(self.get_options())
self.update_previews()
def browse_destination_command(self):
"""
Open directory dialog to select a destination directory.
"""
destination_path = self.file.browse_directory()
if destination_path:
self.info_bar.destination_label.configure(text=f"Destination: {destination_path}")
print(f"Selected destination: {destination_path}")
self.destination_path = destination_path
def apply_canvas_size(self):
"""
Apply the canvas size settings and update previews.
"""
self.image.set_canvas_size(self.canvas_width, self.canvas_height)
def apply_image_size(self):
"""
Apply the canvas size settings and update previews.
"""
self.image.set_image_size(self.image_size)
def apply_background_color(self):
"""
Apply the canvas size settings and update previews.
"""
self.image.set_background_color(self.background_color)
def get_options(self) -> dict:
"""
Get the current processing options.
Returns:
dict: The current processing options.
"""
if not self.destination_path:
self.destination_path = False
product = None
product_id = 0
if self.found_products and len(self.found_products) >= self.current_product:
product_id = self.found_products[self.current_product]['id']
product = self.found_products[self.current_product]
options = {
# "selected_directory": self.local_processing_tab.browse_button.cget("text"),
"canvas_width": self.canvas_width,
"canvas_height": self.canvas_height,
"log_message": self.log, # Use the log method from the log_window
"format_log_message": self.log_message,
"update_previews": self.update_previews,
"product_id": product_id,
"product": product,
"template": self.template,
"delete_images": self.delete_images,
"background_color": self.background_color,
"image_format": self.image_format,
"image_size": self.image_size,
"selected_directory": self.selected_directory,
"destination_path" : self.destination_path
}
return options
def open_options_window(self):
"""
Open the options window.
"""
current_options = {
"canvas_width": {
"type": "number",
"label": "Width:",
"default": self.canvas_width,
"min": 1,
"max": 2540,
},
"canvas_height": {
"type": "number",
"label": "Height:",
"default": self.canvas_height,
"min": 1,
"max": 2540,
},
"template": {
"type": "text",
"label": "Filename Template:",
"default": self.template,
},
"delete_images": {
"type": "checkbox",
"label": "Delete image when done",
"default": self.delete_images,
},
"background_color": {
"type": "color",
"label": "Background Color:",
"default": self.background_color,
},
"image_format": {
"type": "dropdown",
"label": "Image Format:",
"options": ["AUTO", "JPEG", "PNG", "GIF", "DZI", "AVIF", "WEBP"],
"default": self.image_format,
},
"image_size": {
"type": "dropdown",
"label": "Image Size:",
"options": ["contain", "cover"],
"default": self.image_size,
},
}
OptionsWindow(self.root, self.apply_options, current_options)
def apply_options(self, options):
"""
Apply the selected options from the options window.
Args:
options (dict): The options to apply.
"""
# if self.log_window:
# self.log_window.clear() # Clear the log window if it exists
self.canvas_width = options["canvas_width"]
self.canvas_height = options["canvas_height"]
self.template = options["template"]
self.delete_images = options["delete_images"]
self.background_color = options["background_color"]
self.image_size = options["image_size"]
self.image_format = options["image_format"]
self.apply_canvas_size()
self.apply_background_color()
self.apply_image_size()
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
self.config.save_options(self.get_options())
self.update_previews()
def process_product(self, input):
self.found_products = search_product(input)
if self.found_products:
count_products = len(self.found_products)
print(f"Found {count_products} products")
print(f"Current product {self.current_product}")
print(self.found_products[self.current_product])
self.info_bar.selected_button_label.configure(text=self.found_products[self.current_product]['name'] + " (id: "+ str(self.found_products[self.current_product]['id']) +")" )
number = self.current_product
number += 1
text = f"Viewing product {number}/{count_products}"
self.info_bar.destination_label.configure(text=text)
self.update_previews()
return self.found_products[self.current_product]
pass
def change_product(self, data):
self.current_product += data
if self.found_products and len(self.found_products) >= self.current_product:
count_products = len(self.found_products)
print(self.found_products[self.current_product])
self.info_bar.selected_button_label.configure(text=self.found_products[self.current_product]['name'] + " (id: "+ str(self.found_products[self.current_product]['id'])+")" )
number = self.current_product
number += 1
text = f"Viewing product {number}/{count_products}"
self.info_bar.destination_label.configure(text=text)
self.update_previews()
pass
def update_info(self, selected_option):
"""
Update the info frame based on the selected option.
Args:
selected_option (str): The currently selected option (e.g., "product", "file", etc.).
"""
# Clear previous description and input fields
if self.info_bar.input_field:
self.info_bar.input_field.grid_forget()
if self.info_bar.input_button:
self.info_bar.input_button.grid_forget()
if self.info_bar.next_button:
self.info_bar.next_button.grid_forget()
if self.info_bar.prev_button:
self.info_bar.prev_button.grid_forget()
if self.info_bar.destination_button:
self.info_bar.destination_button.grid_forget()
if self.info_bar.destination_label:
self.info_bar.destination_label.grid_forget()
display_label = selected_option.replace("_", " ").title()
self.info_bar.selected_button_label.configure(text=display_label)
# Update the description and input fields based on the selected option
if selected_option == "product":
self.info_bar.description_label.configure(text="Search")
self.info_bar.input_field = ctk.CTkEntry(self.info_bar.parent_frame)
self.info_bar.input_field.grid(row=1, column=1, columnspan=2, padx=5, pady=5, sticky="ew")
self.info_bar.input_button = ctk.CTkButton(
self.info_bar.parent_frame,
text="Search product:",
command=lambda: self.process_product(self.info_bar.input_field.get())
)
self.info_bar.input_button.grid(row=1, column=3, padx=5, pady=5, sticky="ew")
self.info_bar.next_button = ctk.CTkButton(
self.info_bar.parent_frame,
text="Next",
command=lambda: self.change_product(1)
)
self.info_bar.next_button.grid(row=2, column=3, padx=5, pady=5, sticky="ew")
self.info_bar.prev_button = ctk.CTkButton(
self.info_bar.parent_frame,
text="Prev",
command=lambda: self.change_product(-1)
)
self.info_bar.prev_button.grid(row=2, column=2, padx=5, pady=5, sticky="ew")
# Destination Directory Label (to show the selected destination)
self.info_bar.destination_label = ctk.CTkLabel(
self.info_bar.parent_frame, text="No products found"
)
self.info_bar.destination_label.grid(row=2, column=0, columnspan=2, padx=5, pady=5, sticky="w")
elif selected_option == "file":
self.info_bar.description_label.configure(text="Choose a file to process:")
# Browse File Button
self.info_bar.input_button = ctk.CTkButton(
self.info_bar.parent_frame,
text="Browse File",
command=self.browse_file_command
)
self.info_bar.input_button.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
# Destination Directory Button
self.info_bar.destination_button = ctk.CTkButton(
self.info_bar.parent_frame,
text="Select Destination",
command=self.browse_destination_command # Command to browse destination directory
)
self.info_bar.destination_button.grid(row=2, column=1, padx=5, pady=5, sticky="ew")
# Destination Directory Label (to show the selected destination)
self.info_bar.destination_label = ctk.CTkLabel(
self.info_bar.parent_frame, text="No destination selected"
)
self.info_bar.destination_label.grid(row=2, column=0, padx=5, pady=5, sticky="w")
elif selected_option == "directory":
self.info_bar.description_label.configure(text="Choose a directory to process:")
# Browse Directory Button
self.info_bar.input_button = ctk.CTkButton(
self.info_bar.parent_frame,
text="Browse Directory",
command=self.browse_directory_command
)
self.info_bar.input_button.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
# Destination Directory Button
self.info_bar.destination_button = ctk.CTkButton(
self.info_bar.parent_frame,
text="Select Destination",
command=self.browse_destination_command # Command to browse destination directory
)
self.info_bar.destination_button.grid(row=2, column=1, padx=5, pady=5, sticky="ew")
# Destination Directory Label (to show the selected destination)
self.info_bar.destination_label = ctk.CTkLabel(
self.info_bar.parent_frame, text="No destination selected"
)
self.info_bar.destination_label.grid(row=2, column=0, padx=5, pady=5, sticky="w")
def run(self):
"""
Run the main event loop.
"""
self.root.mainloop()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

117
main.py
View File

@@ -1,19 +1,28 @@
"""
Main module for the Image Processor application.
"""
from PIL import Image
import customtkinter as ctk
from ui.log_window import LogWindow
from ui.local_processing_tab import LocalProcessingTab
from ui.menu import MenuBar # Import the new MenuBar class
from ui.log_frame import LogWindow
from ui.button_frame import ButtonFrame
from ui.frame_info import InfoFrame
from ui.settings_tab import SettingsTab
from config.decrypt_config import ConfigDecryptor, DECRYPTION_KEY
from config.encrypt_config import ConfigEncryptor
from controller import AppController
from ui.preview_frame import PreviewFrame # Import the new PreviewFrame class
class ImageProcessorApp:
"""
Main application class for the Image Processor.
"""
def __init__(self, root):
"""
Initialize the ImageProcessorApp.
@@ -23,46 +32,82 @@ class ImageProcessorApp:
"""
self.root = root
self.root.title("Image Processor")
self.root.geometry("480x800")
# Create menu frame at the top
menu_frame = ctk.CTkFrame(self.root)
menu_frame.pack(side="top", fill="x")
self.root.geometry("553x800")
local_processing_button = ctk.CTkButton(menu_frame, text="Local Processing", command=self.show_local_processing_tab)
local_processing_button.pack(side="left", padx=5, pady=5)
# Initialize the controller
self.controller = AppController(self.root)
settings_button = ctk.CTkButton(menu_frame, text="Settings", command=self.show_settings_tab)
settings_button.pack(side="left", padx=5, pady=5)
# Create the menu bar
self.menu_bar = MenuBar(self.root, self.controller)
# Create main frame to hold tabs and log window
# Create the main frame to hold tabs, log window, and other sections
main_frame = ctk.CTkFrame(self.root)
main_frame.pack(expand=True, fill="x")
main_frame.pack(expand=True, fill="both") # Ensure the main frame expands both vertically and horizontally
self.tab_parent = ctk.CTkFrame(main_frame)
self.tab_parent.grid(row=0, column=0, sticky="nsew")
# Configure row and column to expand and fill available space
self.root.grid_rowconfigure(0, weight=1) # Ensures the frames expand vertically
self.root.grid_columnconfigure(0, weight=1) # Ensures the frames expand horizontally
self.log_frame = ctk.CTkFrame(main_frame)
self.log_frame.grid(row=1, column=0, sticky="nsew")
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_columnconfigure(0, weight=1)
# Create a master frame to hold all the other frames
self.master_main_frame = ctk.CTkFrame(main_frame)
self.master_main_frame.grid(row=0, column=0, sticky="nsew")
self.master_main_frame.grid_rowconfigure(0, weight=1)
self.master_main_frame.grid_columnconfigure(0, weight=1) # Ensure full-width spanning
# Log Frame (appears at the bottom)
self.log_frame = ctk.CTkFrame(self.master_main_frame)
self.log_frame.grid(row=3, column=0, sticky="ew") # Set sticky to "ew" to expand horizontally
self.log_frame.grid_columnconfigure(0, weight=1)
self.log_window = LogWindow(self.log_frame)
self.controller.set_log(self.log_window)
# Button Frame
self.button_frame = ctk.CTkFrame(self.master_main_frame, height=250)
self.button_frame.grid(row=0, column=0, sticky="nsew") # Set sticky to "ew" to expand horizontally
self.button_frame.grid_columnconfigure(0, weight=1)
self.button_frame = ButtonFrame(self.button_frame, self.controller, None)
self.local_processing_tab = LocalProcessingTab(self.tab_parent, self.log_window)
self.settings_tab = SettingsTab(self.tab_parent)
# Info Frame
self.info_frame = ctk.CTkFrame(self.master_main_frame)
self.info_frame.grid(row=1, column=0, sticky="ew") # Set sticky to "ew" to expand horizontally
self.info_frame.grid_columnconfigure(0, weight=1)
self.info_frame = InfoFrame(self.info_frame)
self.local_processing_tab.tab.grid(row=0, column=0, sticky="nsew")
# Preview Frame
self.preview_frame = ctk.CTkFrame(self.master_main_frame)
self.preview_frame.grid(row=2, column=0, sticky="nsew") # Expand both horizontally and vertically
self.preview_frame.grid_columnconfigure(0, weight=1)
self.preview_frame = PreviewFrame(self.preview_frame) # Initialize the PreviewFrame
# Settings Tab
self.settings_tab = SettingsTab(main_frame, self.controller)
# Register the tabs and preview frame with the controller
self.controller.set_local_processing_tab(self.master_main_frame)
self.controller.set_settings_tab(self.settings_tab)
self.controller.set_preview_bar(self.preview_frame)
self.controller.set_info_bar(self.info_frame)
self.controller.set_menu_bar( self.menu_bar)
# Position the tabs
self.master_main_frame.grid(row=0, column=0, sticky="nsew") # Make sure master_main_frame expands
self.settings_tab.tab.grid(row=0, column=0, sticky="nsew")
self.show_local_processing_tab()
# Show the default tab (Local Processing Tab)
self.controller.update_options()
self.open_local_processing_tab()
def show_local_processing_tab(self):
def open_local_processing_tab(self):
"""
Show the Local Processing tab.
"""
self.local_processing_tab.tab.tkraise()
self.master_main_frame.tkraise()
def show_local_processing_options(self):
"""
Show the Local Processing tab.
"""
self.master_main_frame.open_options_window()
def show_settings_tab(self):
"""
@@ -77,18 +122,26 @@ class ImageProcessorApp:
self.root.mainloop()
if __name__ == "__main__":
try:
decryptor = ConfigEncryptor(DECRYPTION_KEY)
# Load the active credentials
config = decryptor.load_credentials()
print(config)
if config:
wc_url = config["url"]
wc_consumer_key = config["consumer_key"]
wc_consumer_secret = config["consumer_secret"]
wp_username = config["username"]
wp_password = config["password"]
wc_url = config.get("url", "")
wc_consumer_key = config.get("consumer_key", "")
wc_consumer_secret = config.get("consumer_secret", "")
wp_username = config.get("username", "")
wp_password = config.get("password", "")
else:
print("No active credentials found.")
except FileNotFoundError as e:
print(f"File not found: {e}")
except Exception as e:
print(f"An error occurred: {e}")
root = ctk.CTk()
ctk.set_appearance_mode("dark")

View File

@@ -1,25 +1,36 @@
# Import necessary modules
import glob
import os
# -*- mode: python ; coding: utf-8 -*-
# Collect all PNG and JPG images in the ui/images directory
image_files = [(file, "ui/images") for file in glob.glob("ui/images/*.*") if file.endswith(('.png', '.jpg', '.jpeg'))]
block_cipher = None
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
datas=image_files,
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='main',
@@ -29,7 +40,7 @@ exe = EXE(
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,

1
speelgoed-config.enc Normal file
View File

@@ -0,0 +1 @@
gAAAAABm7tsOIxGHffAAnXR8MylfB10oI10SSNuhkirXHBYDu8UcAHDD3S1ADs1NEteVhFUnDZTCKe8AZuFdy5jyMPg9UzoxPL1_7Sbn5_10sfiOyOJHStm-7fx-4WmRrQyaeToAOS0fJYBe_Vg2AGndAB5vu7zPQ_X56jEmNzBzepdMcFtEUMscJB1Aud--p9QAg6KdZSZzmIks8QybBhAEqPifBKPwjxIivY_vx3l7p2lg7D7T1XfWHqufpTYqrDM_FTHy0wKnUJb_sHZCyuD2FNu9WhrqbZ-WYzHRTLEc0TJc1KoICiRHxWHwvZqiG1RCR8HBY1lXXOdYUv-1M-CtFQKD7U5xHKfFHC5wPF-Z682vL3-XJN5QfVXWQ27ua-TCilJF5NUetAXIBuxB2QjrntgugcsW3UxAZivcZk2Ux8hh12FYG3PDE-BUjg4ZK0gIuJ0LpJpUoYnz7CTmHLJ8FhWE1dBJoBAykyxoa5495DJGPuk_farzr9L3UTkfwuxOMkO7cip2rYOFgms8I0yxaBCFag3BdLLez9rIwghouwVzwgxMvHlN7toztQ9C6Dgu6e2bYimkz1JFyvFYO5YZLpX56It1yRaAgs7hwfhK5KOw9HcVEmKr4ipWXZe5rnnblW5NZ3HfsqwuPpjsg29F2g3AwlZlhc-Xl6D53g-2ydEnkhyvCePy7lZmIb_PB-MS4UzBpMFYDGfqRkoaLGNy8s0nelfmZM5i4ZKJPJGtulsIeyyU6rdmwYNf3S6f87CMNLhXYEE0

1
test-config.enc Normal file
View File

@@ -0,0 +1 @@
gAAAAABm7sewlH0ntIM9w6KP4wBUv-nUiW51UId3_8KCRSWM0OptC6n1VnAZukXHo5ZMx1fLf8OzQwWS5LWb0Cwjnauq3U2iB9Ck4DWklNoHNbE2L7T3yGPrxldSnkUU26hpYlELG5kbk5LMr55Y3JOhgsA75VKfv4X0OeAegngLYqfaV0uTA6Xw4WDz6ZDR4lBNpTfEPIPsUmEwuUNgYHCEwyRVEZXXWTK-bIaYhEAojd5Ecn5f09f9-qx_C7zd84335pa28FfrkQIn_-Ms8iosCw_ZOhh7_DRJrCfI5ursfhsbf9_Nk9QMFXPfFgpHx3zcDSslEcTvoMnVQqKgLesvwY_Mg1ISX6ZnDAWIoz0CfPD6jC0LPQXya1nFWSWHEx1FG_mebFrElDLRZO_hHLZFyrOcjuKHSwUKJq58nYUcw0hpmABzN1SUn7ofbp-XlSQpbE8DLH3OeM-WQYqPk0rQpKF19-cGNF6jxK8XzqI7cgONy3SQDEyB7ImKusxKyovF-A7TvGJcL2Nfuiz1zDODHJ771jLUfCO0Ho9GBvmJ3ZXzOn7zYCWjHc4Pt2zO6MCpbkuXVxYgXFLYaWNLE9lVxlT5JZKlKoD2Clg4Fkik1e9eVU7opWhB4DW4rLvdMgtzwCyVp5F-TnoOBsAqwHpD44Q90ZwC0m0BfRd6yt5aedYCeDZ_h5JftCz-_KvASFRXCXJpIwxOa2cStw5qoewv_Ebb5ep9-l4imWODZRVvlbHcB37iHn0ux2vAzGWkCVbV568HiqmH

93
ui/button_frame.py Normal file
View File

@@ -0,0 +1,93 @@
import customtkinter as ctk
from PIL import Image
from tkinter import StringVar
import os
import sys
def resource_path(relative_path):
""" Get the absolute path to a resource, whether we're running in development or a PyInstaller package. """
try:
# PyInstaller stores files in _MEIPASS when built
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, 'ui/images/'+ relative_path +'.png')
class ButtonFrame:
"""
Class for creating and managing the button frame.
"""
def __init__(self, parent_frame, controller, log_window):
"""
Initialize the ButtonFrame.
Args:
parent_frame (ctk.CTkFrame): The parent frame where the buttons will be placed.
controller (AppController): The controller to handle logic and updates.
log_window (LogWindow): The log window to display log messages.
"""
self.parent_frame = parent_frame
self.controller = controller
self.log_window = log_window
# self.log = self.log_window.log_message
self.buttons = {"directory", "file", "wp_image", "product", "all_products"}
self.source_buttons = {}
self.selected_button = StringVar(value="") # To store the selected button
self.setup_ui()
def setup_ui(self):
"""
Set up the UI for the button frame.
"""
row = 0
self.create_buttons(self.parent_frame, self.buttons, self.source_buttons, row)
def create_buttons(self, frame, button_data, button_store, row):
"""
Create buttons from the button_data list and store them in button_store.
"""
col_index = 0
for label in button_data:
path = resource_path(label)
display_label = label.replace("_", " ").title()
icon_path = path
if icon_path:
icon_image = ctk.CTkImage(
light_image=Image.open(icon_path), size=(24, 24)
)
else:
icon_image = None
button = ctk.CTkButton(
frame,
image=icon_image,
text=display_label,
font=("Helvetica", 12, "bold"),
command=lambda l=label, s=button_store: self.set_active_button(l, s),
fg_color="#666666",
hover_color="#0f4d0f",
compound="top",
width=100,
)
button.grid(row=row, column=col_index,columnspan=1, padx=5, pady=5, sticky="ew")
button_store[label] = button
col_index += 1
def set_active_button(self, active_label, button_store):
"""
Set the clicked button to green and the rest to gray for a specific button store.
Also update the description and input fields based on the active button.
"""
if self.controller.status != "started":
for label, button in button_store.items():
if label == active_label:
self.controller.update_options(active_label)
button.configure(fg_color="#008000")
else:
button.configure(fg_color="#666666")

60
ui/frame_info.py Normal file
View File

@@ -0,0 +1,60 @@
# info_frame.py
import customtkinter as ctk
from tkinter import filedialog
class InfoFrame:
"""
Class for managing the info frame where descriptions and input fields are shown.
"""
def __init__(self, parent_frame):
"""
Initialize the InfoFrame.
Args:
parent_frame (ctk.CTkFrame): The parent frame for the info section.
log_window (LogWindow): The log window to display log messages.
"""
self.parent_frame = parent_frame
self.selected_button_label = None
self.description_label = None
self.input_field = None
self.input_button = None
self.prev_button = None
self.next_button = None
self.destination_button = None
self.destination_label = None
self.setup_ui()
def setup_ui(self):
"""
Set up the UI for the info frame.
"""
# Label to display the selected button name
self.selected_button_label = ctk.CTkLabel(
self.parent_frame, text="", font=("Helvetica", 12, "bold")
)
self.selected_button_label.grid(row=0, column=0, columnspan=12, padx=5, pady=5, sticky="w")
# Description label to provide info about the selected button
self.description_label = ctk.CTkLabel(
self.parent_frame, text="", font=("Helvetica", 10)
)
self.description_label.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky="w")
def process_product(self, product_id):
# Handle product processing logic here
self.log(f"Processing product with ID: {product_id}")
def browse_file(self):
# Open file dialog to select a file
file_path = filedialog.askopenfilename()
def browse_directory(self):
# Open directory dialog to select a directory
directory_path = filedialog.askdirectory()

BIN
ui/images/all_products.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
ui/images/cogs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
ui/images/directory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
ui/images/file.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
ui/images/filters.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
ui/images/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
ui/images/product.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
ui/images/save.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
ui/images/trash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
ui/images/wp_image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,423 +0,0 @@
import tempfile
import threading
import customtkinter as ctk
from tkinter.scrolledtext import ScrolledText
from tkinter import StringVar, BooleanVar
from PIL import Image, ImageTk
from utils.file_operations import FileProcessor
from utils.image_processing import ImageProcessor
from api.woocommerce_api import process_product_images, process_all_products
from ui.options_window import OptionsWindow
from pprint import pformat
from config.encrypt_config import ConfigEncryptor
import os
class LocalProcessingTab:
"""
Class for the Local Processing Tab in the Image Processor application.
"""
def __init__(self, tab_parent, log_window):
"""
Initialize the LocalProcessingTab.
Args:
tab_parent (ctk.CTkFrame): The parent frame widget.
log_window (LogWindow): The log window frame.
"""
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
self.log_window = log_window
self.log = self.log_window.log_message
self.tab = ctk.CTkFrame(tab_parent)
self.root = self.tab.winfo_toplevel() # Store the root window reference
self.config = ConfigEncryptor(key)
self.canvas_width = 900
self.canvas_height = 900
self.template = "{slug}_{sku}_{width}x{height}"
self.delete_images = False
self.transparent = True
self.background_color = "#000000"
self.image_format = "AUTO"
self.image_size = "contain"
self.load_config()
self.source_type = StringVar(value="directory")
self.checkbox_var = BooleanVar(value=False)
self.file = FileProcessor()
self.image = ImageProcessor()
# Automatically open the options window with default options
self.setup_ui()
self.update_options()
def load_config(self):
config = self.config.load_config()
if config:
if options := config.get("options"):
self.canvas_width = options.get("canvas_width", 900)
self.canvas_height = options.get("canvas_height", 900)
self.template = options.get("template", "{slug}_{sku}_{width}x{height}")
self.delete_images = options.get("delete_images", False)
self.transparent = options.get("transparent", True)
self.background_color = options.get("background_color", "#000000")
self.image_format = options.get("image_format", "AUTO")
self.image_size = options.get("image_size", "contain")
def setup_ui(self):
"""
Set up the user interface for the tab.
"""
current_row = 0
start_options_frame = ctk.CTkFrame(self.tab, bg_color="gray30")
start_options_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
self.options_button = ctk.CTkButton(
start_options_frame, text="Options", command=self.open_options_window
)
self.options_button.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky="w")
self.button_start = ctk.CTkButton(
start_options_frame, text="Start Processing", command=self.start_processing
)
self.button_start.grid(row=0, column=2, columnspan=2, padx=5, pady=5, sticky="w")
# Image previews section
current_row += 1
# Source selection section
source_frame = ctk.CTkFrame(self.tab, bg_color="gray20")
source_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
source_label = ctk.CTkLabel(source_frame, anchor="w", text="Source Type:")
source_label.grid(row=0, column=0, columnspan=6, padx=5, pady=5, sticky="w")
self.source_dropdown = ctk.CTkComboBox(
source_frame,
variable=self.source_type,
values=["directory", "file", "wp_image", "product", "all_products"],
state="readonly",
command=self.update_options
)
self.source_dropdown.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="w")
self.source_dropdown.bind(
"<<ComboboxSelected>>", lambda e: self.update_options()
)
self.browse_button = ctk.CTkButton(
source_frame, text="Browse directory", command=self.browse_directory_command
)
self.browse_button.grid(row=2, column=0, columnspan=2, padx=5, pady=5, sticky="w")
self.browse_file_button = ctk.CTkButton(
source_frame, text="Browse file", command=self.browse_file_command
)
self.browse_file_button.grid(row=2, column=2, columnspan=2, padx=5, pady=5, sticky="w")
self.product_id_button = ctk.CTkButton(source_frame, text="Get", width=25)
self.product_id_button.grid(row=2, column=4, columnspan=1, padx=5, pady=5, sticky="w")
self.product_id_entry = ctk.CTkEntry(source_frame)
self.product_id_entry.grid(row=2, column=5, columnspan=2, padx=5, pady=5, sticky="w")
self.additional_name_label = ctk.CTkLabel(source_frame, text="Add suffix:")
self.additional_name_label.grid(row=2, column=7, padx=5, pady=5, sticky="w")
self.additional_name_entry = ctk.CTkEntry(source_frame)
self.additional_name_entry.grid(row=2, column=8, padx=5, pady=5, sticky="w")
# Destination selection section
current_row += 1
# destination_frame = ctk.CTkFrame(self.tab, bg_color="gray25")
# destination_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
# destination_label = ctk.CTkLabel(destination_frame, anchor="w", text="Destination Type:")
# destination_label.grid(row=0, column=0, columnspan=6, padx=5, pady=5, sticky="w")
# self.destination_dropdown = ctk.CTkComboBox(
# destination_frame,
# variable=self.source_type,
# values=["auto", "directory", "file", "wp_image", "product"],
# state="readonly",
# command=self.update_options
# )
# self.destination_dropdown.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="w")
# # Start and Options section
# current_row += 1
preview_frame = ctk.CTkFrame(self.tab, bg_color="gray35")
preview_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
self.before_label = ctk.CTkLabel(preview_frame, text="Before:")
self.before_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
self.after_label = ctk.CTkLabel(preview_frame, text="After:")
self.after_label.grid(row=0, column=3, padx=5, pady=5, sticky="w")
self.before_image_label = ctk.CTkLabel(preview_frame, text="")
self.before_image_label.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky="w")
self.after_image_label = ctk.CTkLabel(preview_frame, text="")
self.after_image_label.grid(row=1, column=3, columnspan=3, padx=5, pady=5, sticky="w")
# Configure grid weights to make frames span the full width
self.tab.grid_columnconfigure(0, weight=1)
source_frame.grid_columnconfigure(0, weight=1)
start_options_frame.grid_columnconfigure(0, weight=1)
preview_frame.grid_columnconfigure(0, weight=1)
def update_options(self, text=None):
"""
Update the UI elements based on the selected source type.
"""
self.product_id_button.grid_remove()
self.product_id_entry.grid_remove()
self.additional_name_label.grid_remove()
self.additional_name_entry.grid_remove()
self.browse_button.grid_remove()
self.browse_file_button.grid_remove()
if self.source_type.get() == "directory":
self.browse_button.grid()
elif self.source_type.get() == "product":
self.product_id_button.grid()
self.product_id_entry.grid()
elif self.source_type.get() == "file":
self.browse_file_button.grid()
self.update_previews()
def update_previews(self, before_path=None, after_path=None):
"""
Update the image previews.
Args:
before_path (str, optional): The path to the 'before' image.
after_path (str, optional): The path to the 'after' image.
"""
first_image_path = self.file.get_first_image_path()
if before_path and after_path:
before_img = Image.open(before_path)
before_img.thumbnail((200, 200))
before_photo = ImageTk.PhotoImage(before_img)
self.before_image_label.configure(image=before_photo)
self.before_image_label.image = before_photo
after_img = Image.open(after_path)
after_img.thumbnail((200, 200))
after_photo = ImageTk.PhotoImage(after_img)
self.after_image_label.configure(image=after_photo)
self.after_image_label.image = after_photo
elif first_image_path:
with tempfile.NamedTemporaryFile(
suffix=".jpg", delete=False
) as temp_file:
output_path = temp_file.name
self.image.resize_image(
first_image_path, output_path, self.get_options()
)
before_img = Image.open(first_image_path)
before_img.thumbnail((200, 200))
before_photo = ImageTk.PhotoImage(before_img)
self.before_image_label.configure(image=before_photo)
self.before_image_label.image = before_photo
after_img = Image.open(output_path)
after_img.thumbnail((200, 200))
after_photo = ImageTk.PhotoImage(after_img)
self.after_image_label.configure(image=after_photo)
self.after_image_label.image = after_photo
def set_image_preview(self, image_path, label):
"""
Set the image preview for a given label.
Args:
image_path (str): The path to the image file.
label (ctk.CTkLabel): The label to set the image on.
"""
img = Image.open(image_path)
img.thumbnail((150, 150))
photo = ImageTk.PhotoImage(img)
label.configure(image=photo)
label.image = photo
def browse_file_command(self):
"""
Command to browse for a file.
"""
file = self.file.browse_files()
if file:
file_name = os.path.basename(file)
if len(file_name) > 20:
file_name = f"...{file_name[-20:]}"
self.browse_file_button.configure(text=file_name)
self.apply_options(self.get_options())
self.update_previews()
def browse_directory_command(self):
"""
Command to browse for a directory.
"""
directory = self.file.browse_directory()
if directory:
dir_name = os.path.basename(directory)
if len(dir_name) > 20:
dir_name = f"...{dir_name[-20:]}"
self.browse_button.configure(text=dir_name)
self.apply_options(self.get_options())
self.update_previews()
def apply_canvas_size(self):
"""
Apply the canvas size settings and update previews.
"""
self.image.set_canvas_size(self.canvas_width, self.canvas_height)
def apply_image_size(self):
"""
Apply the canvas size settings and update previews.
"""
self.image.set_image_size(self.image_size)
def apply_background_color(self):
"""
Apply the canvas size settings and update previews.
"""
self.image.set_background_color(self.background_color)
def get_options(self) -> dict:
"""
Get the current processing options.
Returns:
dict: The current processing options.
"""
options = {
"selected_directory": self.browse_button.cget("text"),
"canvas_width": self.canvas_width,
"canvas_height": self.canvas_height,
"log_message": self.log, # Use the log method from the log_window
"format_log_message": self.pprint_log_message,
"update_previews": self.update_previews,
"product_id": self.product_id_entry.get(),
"template": self.template,
"delete_images": self.delete_images,
"background_color": self.background_color,
"image_format": self.image_format,
"image_size": self.image_size,
}
return options
def open_options_window(self):
"""
Open the options window.
"""
current_options = {
"canvas_width": {
"type": "number",
"label": "Width:",
"default": self.canvas_width,
"min": 1,
"max": 2540,
},
"canvas_height": {
"type": "number",
"label": "Height:",
"default": self.canvas_height,
"min": 1,
"max": 2540,
},
"template": {
"type": "text",
"label": "Filename Template:",
"default": self.template,
},
"delete_images": {
"type": "checkbox",
"label": "Delete image when done",
"default": self.delete_images,
},
"background_color": {
"type": "color",
"label": "Background Color:",
"default": self.background_color
},
"image_format": {
"type": "dropdown",
"label": "Image Format:",
"options": ["AUTO", "JPEG", "PNG", "GIF", "DZI"],
"default": self.image_format
},
"image_size": {
"type": "dropdown",
"label": "Image Size:",
"options": ["contain", "cover"],
"default": self.image_size
}
}
OptionsWindow(self.root, self.apply_options, current_options)
def apply_options(self, options):
"""
Apply the selected options from the options window.
Args:
options (dict): The options to apply.
"""
if self.log_window:
self.log_window.clear() # Clear the log window if it exists
self.canvas_width = options["canvas_width"]
self.canvas_height = options["canvas_height"]
self.template = options["template"]
self.delete_images = options["delete_images"]
self.background_color = options["background_color"]
self.image_size = options["image_size"]
self.image_format = options["image_format"]
self.apply_canvas_size()
self.apply_background_color()
self.apply_image_size()
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
self.config.save_options(self.get_options())
self.update_previews()
def pprint_log_message(self, obj):
"""
Log a formatted message to the log window using pprint.
Args:
obj (object): The object to format and log.
"""
formatted_message = pformat(obj)
self.log(formatted_message)
def start_processing(self):
"""
Start the image processing based on the selected options.
"""
source = self.source_type.get()
options = self.get_options()
if source == "directory":
threading.Thread(
target=self.file.process_directory_with_logging, args=(options,)
).start()
elif source == "product":
threading.Thread(
target=process_product_images,
args=(options,)
).start()
elif source == "file":
threading.Thread(
target=self.file.proces_single_image,
args=(options,)
).start()
elif source == "all_products":
threading.Thread(
target=process_all_products,
args=(options,)
).start()
self.update_previews()

View File

@@ -1,5 +1,5 @@
import customtkinter as ctk
from datetime import datetime
class LogWindow:
def __init__(self, parent):
self.frame = ctk.CTkFrame(parent)
@@ -14,8 +14,18 @@ class LogWindow:
self.log_text.configure(yscrollcommand=self.scrollbar.set)
def log_message(self, message):
"""
Log a message to the log window with the current timestamp.
"""
# Get the current time in the desired format (e.g., HH:MM:SS)
current_time = datetime.now().strftime("%H:%M:%S")
# Prepend the current time to the message
full_message = f"[{current_time}] {message}"
# Log the message
self.log_text.configure(state="normal")
self.log_text.insert(ctk.END, message + "\n")
self.log_text.insert(ctk.END, full_message + "\n")
self.log_text.see(ctk.END)
self.log_text.configure(state="disabled")
self.log_text.update_idletasks()

103
ui/menu.py Normal file
View File

@@ -0,0 +1,103 @@
from PIL import Image
import customtkinter as ctk
import os
import sys
def resource_path(relative_path):
""" Get the absolute path to a resource, whether we're running in development or a PyInstaller package. """
try:
# PyInstaller stores files in _MEIPASS when built
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, 'ui/images/'+ relative_path +'.png')
class MenuBar:
def __init__(self, parent, controller):
"""
Initialize the MenuBar.
Args:
parent (ctk.CTkFrame): The parent frame for the menu.
controller (AppController): The controller instance to manage the app.
"""
self.parent = parent
self.controller = controller
self.setup_ui()
def setup_ui(self):
button_width = 40
icon_size = 24
# Create menu frame
self.menu_frame = ctk.CTkFrame(self.parent)
self.menu_frame.pack(side="top", fill="x")
# Create the buttons with icons
self.create_menu_button(
"house-user-solid",
"#363636",
"",
self.controller.show_local_processing_tab,
button_width,
icon_size,
)
self.create_menu_button(
"filters",
"#363636",
"",
self.controller.show_local_processing_options,
button_width,
icon_size,
)
self.create_menu_button(
"cogs",
"#363636",
"",
self.controller.show_settings_tab,
button_width,
icon_size,
)
self.start_button = self.create_menu_button(
"play",
"#008000",
"Start",
self.controller.start_processing,
button_width,
icon_size,
side="right",
)
def create_menu_button(
self, icon_path, bg_color, text, command, button_width, icon_size, side="left"
):
"""
Create a button with an icon for the menu.
Args:
icon_path (str): Path to the icon.
command (callable): The function to call when the button is pressed.
button_width (int): The width of the button.
icon_size (int): The size of the icon.
side (str): Where to place the button ('left' or 'right').
"""
if icon_path:
path = resource_path(icon_path)
icon_image = ctk.CTkImage(
light_image=Image.open(path), size=(icon_size, icon_size)
)
button = ctk.CTkButton(
self.menu_frame,
image=icon_image,
text=text,
fg_color=bg_color,
command=command,
width=button_width,
)
button.pack(side=side, padx=5, pady=5)
return button

84
ui/preview_frame.py Normal file
View File

@@ -0,0 +1,84 @@
import customtkinter as ctk
from PIL import Image
class PreviewFrame:
"""
Class to handle the preview frames (Before and After) for image processing.
"""
def __init__(self, parent):
"""
Initialize the PreviewFrame.
Args:
parent (ctk.CTkFrame): The parent frame where the preview frames will be placed.
"""
self.parent = parent
self.setup_ui()
def setup_ui(self):
"""
Set up the user interface for the preview frames.
"""
row = 0
start_row = row
# Creating the main preview frame
preview_frame = ctk.CTkFrame(self.parent, bg_color="gray35")
preview_frame.grid(row=row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
# Ensure the preview_frame expands to the full width
preview_frame.grid_columnconfigure(0, weight=1)
preview_frame.grid_columnconfigure(1, weight=1)
# Creating the "Before" frame
before_frame = ctk.CTkFrame(preview_frame)
before_frame.grid(row=row, column=0, padx=5, pady=5, sticky="ew")
# Adding "Before" label and image label to the before_frame
self.before_label = ctk.CTkLabel(before_frame, text="Before:")
self.before_label.grid(row=row, column=0, padx=5, pady=5, sticky="w")
row += 1
self.before_image_label = ctk.CTkLabel(before_frame, text="", height=175)
self.before_image_label.grid(row=row, column=0, padx=5, pady=5, sticky="w")
row += 1
# Adding a filename label under the "Before" image label
self.before_filename_label = ctk.CTkLabel(before_frame, text="Filename")
self.before_filename_label.grid(row=row, column=0, padx=5, pady=5, sticky="w")
# Creating the "After" frame
after_frame = ctk.CTkFrame(preview_frame)
after_frame.grid(row=start_row, column=1, padx=5, pady=5, sticky="ew")
# Adding "After" label and image label to the after_frame
self.after_label = ctk.CTkLabel(after_frame, text="After:")
self.after_label.grid(row=start_row, column=0, padx=5, pady=5, sticky="w")
start_row += 1
self.after_image_label = ctk.CTkLabel(after_frame, text="", height=175)
self.after_image_label.grid(row=start_row, column=0, padx=5, pady=5, sticky="w")
start_row += 1
# Adding a filename label under the "After" image label
self.after_filename_label = ctk.CTkLabel(after_frame, text="Filename")
self.after_filename_label.grid(row=start_row, column=0, padx=5, pady=5, sticky="w")
# def update_before_image(self, image, filename=""):
# """
# Update the before image and filename label.
# Args:
# image (PIL.Image): The image to display.
# filename (str): The filename to display.
# """
# self.before_image_label.config(image=image)
# self.before_filename_label.config(text=filename)
# def update_after_image(self, image, filename=""):
# """
# Update the after image and filename label.
# Args:
# image (PIL.Image): The image to display.
# filename (str): The filename to display.
# """
# self.after_image_label.config(image=image)
# self.after_filename_label.config(text=filename)

View File

@@ -1,58 +1,234 @@
import customtkinter as ctk
from api.woocommerce_api import save_credentials, load_credentials
from api.woocommerce_api import (
load_credentials,
save_active_credential_set,
)
from config.encrypt_config import ConfigEncryptor
from PIL import Image, ImageTk
import os
import sys
KEY = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
def resource_path(relative_path):
""" Get the absolute path to a resource, whether we're running in development or a PyInstaller package. """
try:
# PyInstaller stores files in _MEIPASS when built
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, 'ui/images/'+ relative_path +'.png')
class SettingsTab:
def __init__(self, tab_parent):
def __init__(self, tab_parent, controller):
self.tab = ctk.CTkFrame(tab_parent)
self.tab.grid(row=0, column=0, sticky="nsew")
self.credentials = load_credentials()
# Initialize an instance of ConfigEncryptor
self.config_encryptor = ConfigEncryptor(KEY) # Ensure you pass any required arguments in the constructor if necessary
config = self.config_encryptor.load_config()
self.credentials_list = []
if config:
self.credentials_list = self.config_encryptor.load_config().get('credentials')
self.active_credential_set = (
self.get_active_credential_set()
) # Fetch active credentials
else:
self.active_credential_set = {
'nice_name': "Default"
}
self.inputs = {}
self.setup_ui()
def setup_ui(self):
if self.credentials:
settings_options = {
"url": {"type": "text", "label": "WooCommerce URL:", "default": self.credentials.get('url', '')},
"consumer_key": {"type": "text", "label": "Consumer Key:", "default": self.credentials.get('consumer_key', '')},
"consumer_secret": {"type": "text", "label": "Consumer Secret:", "default": self.credentials.get('consumer_secret', '')},
"username": {"type": "text", "label": "Username:", "default": self.credentials.get('username', '')},
"password": {"type": "text", "label": "Password:", "default": self.credentials.get('password', ''), "show": "*"}
}
else:
settings_options = {
"url": {"type": "text", "label": "WooCommerce URL:", "default": ""},
"consumer_key": {"type": "text", "label": "Consumer Key:", "default": ""},
"consumer_secret": {"type": "text", "label": "Consumer Secret:", "default": ""},
"username": {"type": "text", "label": "Username:", "default": ""},
"password": {"type": "text", "label": "Password:", "default": "", "show": "*"}
def get_active_credential_set(self):
"""Retrieve active credential set from saved data and convert to new format if needed."""
if isinstance(self.credentials_list, list) and all(
isinstance(cred, dict) for cred in self.credentials_list
):
for cred in self.credentials_list:
if cred.get("active", False):
return cred
return self.credentials_list[0] if self.credentials_list else None
elif isinstance(self.credentials_list, dict):
self.credentials_list = [self.convert_to_new_format(self.credentials_list)]
return self.credentials_list[0]
elif isinstance(self.credentials_list, str):
self.credentials_list = [
self.convert_to_new_format({"url": self.credentials_list})
]
return self.credentials_list[0]
return None
def convert_to_new_format(self, old_credential):
return {
"url": old_credential.get("url", ""),
"consumer_key": old_credential.get("consumer_key", ""),
"consumer_secret": old_credential.get("consumer_secret", ""),
"username": old_credential.get("username", ""),
"password": old_credential.get("password", ""),
"name": old_credential.get("name", "Default Credential Set"),
"nice_name": old_credential.get("nice_name", old_credential.get("url", "Unnamed Credential")),
"active": True,
}
row_index = 0
def setup_ui(self):
# Dropdown to select active credentials
self.credential_var = ctk.StringVar()
self.credential_var.set(
self.active_credential_set.get("nice_name", "Default")
)
credential_options = [
cred.get("nice_name", "Unnamed Credential")
for cred in self.credentials_list
]
dropdown_label = ctk.CTkLabel(self.tab, text="Select Active Credentials:")
dropdown_label.grid(row=0, column=0, padx=5, columnspan=2 , pady=5, sticky="w")
self.credential_dropdown = ctk.CTkComboBox(
self.tab,
variable=self.credential_var,
values=credential_options,
command=self.load_selected_credential,
)
self.credential_dropdown.grid(row=0, column=2, columnspan=2, padx=5, pady=5, sticky="w")
# Show fields for credentials
self.create_credentials_form(self.active_credential_set, row_index=1)
icon_path = resource_path("save")
icon_image = ctk.CTkImage(light_image=Image.open(icon_path), size=(24, 24)) if icon_path else None
save_button = ctk.CTkButton(
self.tab, width=100,fg_color="green",image=icon_image, text="Save", command=self.save_credentials
)
save_button.grid(row=7, column=0, columnspan=1, pady=10)
new_button = ctk.CTkButton(
self.tab, width=100, fg_color="green", image=icon_image, text="New", command=self.add_new_credential_set
)
new_button.grid(row=7, column=1, columnspan=1, pady=10)
# Trash icon for delete button
trash_icon_path = resource_path("trash")
trash_icon_image = ctk.CTkImage(light_image=Image.open(trash_icon_path), size=(24, 24)) if trash_icon_path else None
delete_button = ctk.CTkButton(
self.tab, width=100, fg_color="red", image=trash_icon_image, text="Delete", command=self.delete_selected_credential
)
delete_button.grid(row=7, column=2, columnspan=1, pady=10)
def create_credentials_form(self, credentials, row_index):
settings_options = {
"url": {
"type": "text",
"label": "WooCommerce URL:",
"default": credentials.get("url", ""),
},
"consumer_key": {
"type": "text",
"label": "Consumer Key:",
"default": credentials.get("consumer_key", ""),
},
"consumer_secret": {
"type": "text",
"label": "Consumer Secret:",
"default": credentials.get("consumer_secret", ""),
},
"username": {
"type": "text",
"label": "Username:",
"default": credentials.get("username", ""),
},
"password": {
"type": "text",
"label": "Password:",
"default": credentials.get("password", ""),
"show": "*",
},
"nice_name": {
"type": "text",
"label": "Nice Name:",
"default": credentials.get("nice_name", "Unnamed Credential"),
},
}
self.inputs = {} # Reset inputs for new credentials set
for name, details in settings_options.items():
self.create_setting(name, details, row_index)
row_index += 1
save_button = ctk.CTkButton(self.tab, text="Save Credentials", command=self.save_credentials)
save_button.grid(row=row_index, column=0, columnspan=2, pady=10)
def create_setting(self, name, details, row_index):
"""
Create a setting based on its type.
"""
lbl = ctk.CTkLabel(self.tab, text=details["label"])
lbl.grid(row=row_index, column=0, padx=5, pady=5, sticky="w")
lbl.grid(row=row_index, column=0,columnspan=2, padx=5, pady=5, sticky="w")
if details["type"] == "text":
entry = ctk.CTkEntry(self.tab, show=details.get("show", None))
entry.insert(0, details["default"])
entry.grid(row=row_index, column=1, padx=5, pady=5, sticky="w")
entry.grid(row=row_index, column=2,columnspan=2, padx=5, pady=5, sticky="w")
self.inputs[name] = entry
def load_selected_credential(self, selected_name):
for cred in self.credentials_list:
if cred.get("nice_name", "Unnamed Credential") == selected_name:
self.active_credential_set = cred
self.create_credentials_form(self.active_credential_set, row_index=1)
break
def save_credentials(self):
save_credentials(
self.inputs["url"].get(),
self.inputs["consumer_key"].get(),
self.inputs["consumer_secret"].get(),
self.inputs["username"].get(),
self.inputs["password"].get()
credentials = {
"url": self.inputs["url"].get(),
"consumer_key": self.inputs["consumer_key"].get(),
"consumer_secret": self.inputs["consumer_secret"].get(),
"username": self.inputs["username"].get(),
"password": self.inputs["password"].get(),
"name": self.inputs["nice_name"].get(),
"nice_name": self.inputs["nice_name"].get(),
"active": True,
}
ConfigEncryptor(KEY).save_credentials(credentials)
save_active_credential_set(credentials["name"])
self.credentials_list.append(credentials)
self.credential_dropdown.configure(
values=[cred.get("nice_name", "Unnamed Credential") for cred in self.credentials_list]
)
def add_new_credential_set(self):
self.active_credential_set = {
"url": "",
"consumer_key": "",
"consumer_secret": "",
"username": "",
"password": "",
"name": "New Credential Set",
"nice_name": "New Credential Set",
"active": False,
}
self.create_credentials_form(self.active_credential_set, row_index=1)
self.credential_var.set(self.active_credential_set["nice_name"])
def delete_selected_credential(self):
selected_name = self.credential_var.get()
# Find and remove the selected credential from the list
self.credentials_list = [
cred for cred in self.credentials_list if cred.get("nice_name") != selected_name
]
# Save updated credentials list to storage
ConfigEncryptor(KEY).delete_credentials(selected_name)
# Update the dropdown and form after deletion
if self.credentials_list:
self.active_credential_set = self.credentials_list[0] # Load first available credential
self.credential_var.set(self.active_credential_set["nice_name"])
self.create_credentials_form(self.active_credential_set, row_index=1)
else:
self.active_credential_set = {}
self.create_credentials_form({}, row_index=1) # Clear form if no credentials are left
self.credential_dropdown.configure(
values=[cred.get("nice_name", "Unnamed Credential") for cred in self.credentials_list]
)

View File

@@ -44,8 +44,7 @@ class FileProcessor:
if self.selected_file:
return self.selected_file
return None
if not self.selected_directory:
return None
if self.selected_directory:
for root, dirs, files in os.walk(self.selected_directory):
if "ProcessedImages" in dirs:
@@ -66,7 +65,7 @@ class FileProcessor:
log (function, optional): The log function to use. Defaults to None.
"""
if log:
log(message)
log.log_message(message)
else:
print(message)
@@ -77,6 +76,8 @@ class FileProcessor:
Args:
options (dict): Processing options.
"""
if options.get("selected_directory"):
self.selected_directory = options.get("selected_directory")
if not self.selected_directory:
messagebox.showwarning(
"No Directory", "Please select a directory.")
@@ -85,7 +86,11 @@ class FileProcessor:
self.log_message(
f"Processing started for directory: {self.selected_directory}", log
)
self.log_message(
options, log
)
output_directory = options.get('destination_path')
if not output_directory:
output_directory = self.create_output_directory(log)
image_paths = self.collect_image_paths(log)
@@ -106,7 +111,7 @@ class FileProcessor:
str: The path to the output directory.
"""
output_directory = os.path.join(
self.selected_directory, "ProcessedImages")
self.selected_directory)
if os.path.exists(output_directory):
shutil.rmtree(output_directory)
self.log_message("Existing directory removed.", log)
@@ -138,7 +143,7 @@ class FileProcessor:
self.log_message(f"Total images found: {len(image_paths)}", log)
return image_paths
def process_images(self, image_paths, output_directory, options, log):
def process_images(self, image_paths, output_directory, options, log, product = None):
"""
Process each image by resizing and saving it to the output directory.
@@ -147,32 +152,46 @@ class FileProcessor:
output_directory (str): The path to the output directory.
options (dict): Processing options.
log (function): The log function to use.
Returns:
list: A list of output image paths.
"""
from utils.image_processing import ImageProcessor
processed_images = []
image = ImageProcessor()
image.set_background_color(options.get("background_color", "transparent"))
image.set_image_size(options.get("image_size", "contain"))
image.set_canvas_size( options.get("canvas_width"), options.get("canvas_height"))
format = options.get("image_format")
for file_path in image_paths:
# output_path = os.path.join(
# output_directory, os.path.relpath(
# file_path, self.selected_directory)
# )
output_path = self.generate_output_path(output_directory, file_path, options)
output_path = self.generate_output_path(output_directory, file_path, options, product)
previews = options.get("update_previews")
previews(file_path)
os.makedirs(os.path.dirname(output_path), exist_ok=True)
self.log_message(f"Running: {file_path}", log)
log.log_message(f"Running: {file_path}")
# Check if the image is JPG and set background color accordingly
if file_path.lower().endswith(".jpg") or file_path.lower().endswith(".jpeg"):
image.set_background_color("white")
else:
image.set_background_color(options.get("background_color", "transparent"))
if format == "DZI":
DZI(file_path, output_path, options)
else:
image.resize_image(
file_path, output_path, options
)
image.resize_image(file_path, output_path, options)
# Collect the processed output path
processed_images.append(output_path)
previews(None, output_path)
if os.path.exists(file_path) and options.get("delete_images", False):
self.log_message(f"Removing: {file_path}", log)
os.remove(file_path)
self.log_message(f"Processed: {file_path}", log)
return processed_images
def proces_single_image(self, options):
"""
Process images in the selected directory with logging.
@@ -229,3 +248,5 @@ class FileProcessor:
return os.path.join(output_directory, new_filename + ".jpg")
elif imgf == "DZI":
return os.path.join(output_directory, new_filename + ".dzi")
elif imgf == "WEBP":
return os.path.join(output_directory, new_filename + ".webp")

View File

@@ -119,7 +119,7 @@ class ImageProcessor:
log (function, optional): The log function to use. Defaults to None.
"""
if log:
log(message)
log.log_message(message)
else:
print(message)