UIpdate image script
@@ -1,24 +1,43 @@
|
|||||||
"""
|
|
||||||
Module for WooCommerce API interactions and image processing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
import tempfile
|
import tempfile
|
||||||
import pprint
|
|
||||||
from tkinter import messagebox
|
|
||||||
from cryptography.fernet import Fernet
|
|
||||||
import requests
|
import requests
|
||||||
|
from tkinter import messagebox
|
||||||
from woocommerce import API
|
from woocommerce import API
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
from utils.image_processing import ImageProcessor
|
from utils.image_processing import ImageProcessor
|
||||||
from config.encrypt_config import ConfigEncryptor
|
from config.encrypt_config import ConfigEncryptor
|
||||||
from utils.file_operations import FileProcessor
|
from utils.file_operations import FileProcessor
|
||||||
|
import hashlib
|
||||||
|
import pprint
|
||||||
CREDENTIALS_FILE = "credentials.json"
|
CREDENTIALS_FILE = "credentials.json"
|
||||||
|
|
||||||
# Hardcoded key (replace with your generated key)
|
# Hardcoded key (replace with your generated key)
|
||||||
KEY = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
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):
|
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,
|
"password": password,
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigEncryptor(KEY).save_credentials(credentials)
|
ConfigEncryptor(KEY).save_credentials(consumer_key, consumer_secret, username, password)
|
||||||
|
|
||||||
|
|
||||||
def load_credentials():
|
def load_credentials():
|
||||||
@@ -49,37 +68,30 @@ def load_credentials():
|
|||||||
Returns:
|
Returns:
|
||||||
dict: The decrypted credentials, or None if the file does not exist.
|
dict: The decrypted credentials, or None if the file does not exist.
|
||||||
"""
|
"""
|
||||||
if not os.path.exists("config.enc"):
|
creds = ConfigEncryptor(KEY).load_credentials()
|
||||||
return None
|
return creds
|
||||||
fernet = Fernet(KEY)
|
|
||||||
with open("config.enc", "rb") as file:
|
|
||||||
encrypted = file.read()
|
|
||||||
decrypted = fernet.decrypt(encrypted).decode()
|
|
||||||
return json.loads(decrypted).get("credentials")
|
|
||||||
|
|
||||||
|
|
||||||
def get_wcapi():
|
def get_wcapi():
|
||||||
"""
|
"""
|
||||||
Get a WooCommerce API client instance.
|
Get a WooCommerce API client instance using the active credentials.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
woocommerce.API: The WooCommerce API client instance, or None if credentials are missing.
|
woocommerce.API: The WooCommerce API client instance, or None if credentials are missing.
|
||||||
"""
|
"""
|
||||||
credentials = load_credentials()
|
active_credentials = load_credentials()
|
||||||
if not credentials:
|
|
||||||
messagebox.showerror(
|
pprint.pprint(active_credentials)
|
||||||
"Error",
|
|
||||||
"No WooCommerce credentials found. Please set them in the settings.",
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
return API(
|
return API(
|
||||||
url=credentials["url"],
|
url=active_credentials["url"],
|
||||||
consumer_key=credentials["consumer_key"],
|
consumer_key=active_credentials["consumer_key"],
|
||||||
consumer_secret=credentials["consumer_secret"],
|
consumer_secret=active_credentials["consumer_secret"],
|
||||||
version="wc/v3",
|
version="wc/v3",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_product(product_id):
|
def get_product(product_id):
|
||||||
"""
|
"""
|
||||||
Get a WooCommerce product and download its images.
|
Get a WooCommerce product and download its images.
|
||||||
@@ -95,8 +107,13 @@ def get_product(product_id):
|
|||||||
return None
|
return None
|
||||||
result = wcapi.get(f"products/{product_id}")
|
result = wcapi.get(f"products/{product_id}")
|
||||||
|
|
||||||
image_paths = {}
|
|
||||||
product = result.json()
|
product = result.json()
|
||||||
|
|
||||||
|
return product
|
||||||
|
|
||||||
|
def get_images(product, limit = 0):
|
||||||
|
image_paths = {}
|
||||||
if product.get("images"):
|
if product.get("images"):
|
||||||
images = product.get("images")
|
images = product.get("images")
|
||||||
|
|
||||||
@@ -116,14 +133,19 @@ def get_product(product_id):
|
|||||||
print(
|
print(
|
||||||
f"Image {index + 1}/{len(images)} downloaded and saved: {file_path}"
|
f"Image {index + 1}/{len(images)} downloaded and saved: {file_path}"
|
||||||
)
|
)
|
||||||
|
if limit and limit >= index +1:
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
print(f"Failed to download image {index + 1}/{len(images)}")
|
print(f"Failed to download image {index + 1}/{len(images)}")
|
||||||
|
|
||||||
|
return image_paths
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if product.get("name"):
|
if product.get("name"):
|
||||||
print(f"No images found for {product.get('name')}")
|
print(f"No images found for {product.get('name')}")
|
||||||
else:
|
else:
|
||||||
print("No images found")
|
print("No images found")
|
||||||
return image_paths, product
|
return []
|
||||||
|
|
||||||
|
|
||||||
def upload_image(img_path):
|
def upload_image(img_path):
|
||||||
@@ -139,6 +161,8 @@ def upload_image(img_path):
|
|||||||
with open(img_path, "rb") as img_file:
|
with open(img_path, "rb") as img_file:
|
||||||
data = img_file.read()
|
data = img_file.read()
|
||||||
file_name = os.path.basename(img_path)
|
file_name = os.path.basename(img_path)
|
||||||
|
file_name = file_name.replace("–", "-")
|
||||||
|
|
||||||
credentials = load_credentials()
|
credentials = load_credentials()
|
||||||
if not credentials:
|
if not credentials:
|
||||||
messagebox.showerror(
|
messagebox.showerror(
|
||||||
@@ -155,7 +179,7 @@ def upload_image(img_path):
|
|||||||
"Content-Disposition": f"attachment; filename={file_name}",
|
"Content-Disposition": f"attachment; filename={file_name}",
|
||||||
"Authorization": f"basic {credentials_base64.decode()}",
|
"Authorization": f"basic {credentials_base64.decode()}",
|
||||||
}
|
}
|
||||||
|
print(f"Uploading image {img_path}")
|
||||||
try:
|
try:
|
||||||
res = requests.post(url=url, data=data, headers=headers, timeout=10)
|
res = requests.post(url=url, data=data, headers=headers, timeout=10)
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
@@ -180,6 +204,7 @@ def delete_img(image_id):
|
|||||||
Args:
|
Args:
|
||||||
image_id (int): The ID of the image to delete.
|
image_id (int): The ID of the image to delete.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
credentials = load_credentials()
|
credentials = load_credentials()
|
||||||
if not credentials:
|
if not credentials:
|
||||||
messagebox.showerror(
|
messagebox.showerror(
|
||||||
@@ -205,64 +230,114 @@ def delete_img(image_id):
|
|||||||
print(f"Failed to delete image with ID {image_id}. Error: {res.text}")
|
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:
|
Args:
|
||||||
image_ids (list): A list of new image IDs.
|
image_ids (list): A list of new image IDs.
|
||||||
product_id (int): The ID of the WooCommerce product.
|
product_id (int): The ID of the WooCommerce product.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
wcapi = get_wcapi()
|
wcapi = get_wcapi()
|
||||||
if not wcapi:
|
if not wcapi:
|
||||||
return
|
return
|
||||||
|
|
||||||
product = wcapi.get(f"products/{product_id}").json()
|
# Prepare the data with images and meta data fields
|
||||||
product["images"] = [{"id": image_id} for image_id in image_ids]
|
product_data = {
|
||||||
response = wcapi.put(f"products/{product_id}", data=product)
|
"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:
|
if response.status_code == 200:
|
||||||
print(
|
print(f"Product with ID {product_id} updated successfully with new image IDs and meta data.")
|
||||||
f"Product with ID {product_id} updated successfully with new image IDs.")
|
|
||||||
else:
|
else:
|
||||||
print(
|
print(f"Failed to update product with ID {product_id}. Error: {response.text}")
|
||||||
f"Failed to update product with ID {product_id}. Error: {response.text}")
|
|
||||||
|
|
||||||
|
|
||||||
def process_product_images( options):
|
|
||||||
|
def process_product_images(options):
|
||||||
"""
|
"""
|
||||||
Process images for a WooCommerce product by resizing and uploading them.
|
Process images for a WooCommerce product by resizing and uploading them.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
product_id (int): The ID of the WooCommerce product.
|
options (dict): Contains options such as product_id, name_template, canvas_width, canvas_height.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 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")
|
product_id = options.get("product_id")
|
||||||
if not product_id:
|
if not product_id:
|
||||||
|
print("No product ID")
|
||||||
return
|
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:
|
if not image_paths:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_output_directory:
|
with tempfile.TemporaryDirectory() as temp_output_directory:
|
||||||
print(f"Using temporary directory: {temp_output_directory}")
|
print(f"Using temporary directory: {temp_output_directory}")
|
||||||
|
|
||||||
old_list = []
|
old_list = []
|
||||||
new_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():
|
for image_id, file_path in image_paths.items():
|
||||||
file = FileProcessor()
|
|
||||||
img = ImageProcessor()
|
processed = file.process_images([file_path], temp_output_directory, options, log, product)
|
||||||
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(processed[0])
|
||||||
new_id = upload_image(output_path)
|
|
||||||
if new_id:
|
if new_id:
|
||||||
old_list.append(image_id)
|
old_list.append(image_id)
|
||||||
new_list.append(new_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.")
|
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)
|
return os.path.join(temp_output_directory, new_filename + ext)
|
||||||
|
|
||||||
|
def get_first_image_path(product):
|
||||||
def process_all_products(options):
|
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.
|
Process images for all WooCommerce products by resizing and uploading them.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name_template (str): The template for generating image filenames.
|
options (dict): Contains options such as name_template, canvas_width, canvas_height.
|
||||||
canvas_width (int): The width of the canvas for resizing images.
|
|
||||||
canvas_height (int): The height of the canvas for resizing images.
|
|
||||||
"""
|
"""
|
||||||
wcapi = get_wcapi()
|
wcapi = get_wcapi()
|
||||||
if not wcapi:
|
if not wcapi:
|
||||||
return
|
return
|
||||||
|
|
||||||
page = 1
|
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:
|
while True:
|
||||||
products = wcapi.get("products", params={
|
products = wcapi.get("products", params={"per_page": 100, "page": page, "search": search}).json()
|
||||||
"per_page": 100, "page": page}).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:
|
if not products:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
product_count = len(products) # Get the count of products on the current page
|
||||||
|
|
||||||
|
|
||||||
for product in products:
|
for product in products:
|
||||||
|
total_products += 1 # Update the total count
|
||||||
options["product_id"] = product["id"]
|
options["product_id"] = product["id"]
|
||||||
process_product_images(
|
options["product"] = product
|
||||||
options
|
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
|
page += 1
|
||||||
|
|
||||||
|
# Log the total number of products processed
|
||||||
|
log(f"Total products processed: {total_products}")
|
||||||
|
|
||||||
|
# Show completion message
|
||||||
messagebox.showinfo(
|
messagebox.showinfo(
|
||||||
"Process Complete", "All product images processing is complete."
|
"Process Complete", f"All product images processing is complete. Total products processed: {total_products}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class ConfigEncryptor:
|
class ConfigEncryptor:
|
||||||
@@ -9,51 +10,153 @@ class ConfigEncryptor:
|
|||||||
self.fernet = Fernet(self.key)
|
self.fernet = Fernet(self.key)
|
||||||
|
|
||||||
def encrypt_config(self, data):
|
def encrypt_config(self, data):
|
||||||
json_data = json.dumps(data)
|
"""
|
||||||
encrypted_data = self.fernet.encrypt(json_data.encode())
|
Encrypt the given data and save it to a file.
|
||||||
with open(self.filename, "wb") as encrypted_file:
|
|
||||||
encrypted_file.write(encrypted_data)
|
Args:
|
||||||
print("Credentials saved")
|
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(f"Encrypted configuration saved to {self.filename}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error encrypting config: {e}")
|
||||||
|
|
||||||
def get_key(self):
|
def get_key(self):
|
||||||
|
"""
|
||||||
|
Return the encryption key.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The encryption key as a string.
|
||||||
|
"""
|
||||||
return self.key.decode()
|
return self.key.decode()
|
||||||
|
|
||||||
def save_credentials(self, credentials):
|
def save_credentials(self, credentials):
|
||||||
config = self.load_config()
|
"""
|
||||||
if not config:
|
Save WooCommerce credentials to the config file, handling multiple credential sets.
|
||||||
config = {"credentials": {}, "options": {}}
|
|
||||||
config["credentials"] = credentials
|
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)
|
self.encrypt_config(config)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def save_options(self, options):
|
def save_options(self, options):
|
||||||
config = self.load_config()
|
"""
|
||||||
if not config:
|
Save options to the config file. Filters out non-serializable data.
|
||||||
config = {"credentials": {}, "options": {}}
|
|
||||||
# Ensure options only contains 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)}
|
serializable_options = {k: v for k, v in options.items() if self.is_json_serializable(v)}
|
||||||
config["options"] = serializable_options
|
config["options"] = serializable_options
|
||||||
self.encrypt_config(config)
|
self.encrypt_config(config)
|
||||||
|
|
||||||
def load_config(self):
|
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:
|
try:
|
||||||
with open(self.filename, "rb") as encrypted_file:
|
with open(self.filename, "rb") as encrypted_file:
|
||||||
encrypted_data = encrypted_file.read()
|
encrypted_data = encrypted_file.read()
|
||||||
decrypted_data = self.fernet.decrypt(encrypted_data).decode()
|
decrypted_data = self.fernet.decrypt(encrypted_data).decode()
|
||||||
config = json.loads(decrypted_data)
|
config = json.loads(decrypted_data)
|
||||||
|
return config
|
||||||
# Filter only relevant keys
|
except Exception as e:
|
||||||
keys_to_return = ["credentials", "options"]
|
print(f"Error loading or decrypting config: {e}")
|
||||||
return {key: config[key] for key in keys_to_return if key in config}
|
|
||||||
except FileNotFoundError:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def load_credentials(self):
|
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()
|
config = self.load_config()
|
||||||
if 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
|
return None
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_json_serializable(value):
|
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:
|
try:
|
||||||
json.dumps(value)
|
json.dumps(value)
|
||||||
return True
|
return True
|
||||||
@@ -62,7 +165,6 @@ class ConfigEncryptor:
|
|||||||
|
|
||||||
|
|
||||||
# Define your key here
|
# Define your key here
|
||||||
# Replace with your actual key
|
|
||||||
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -84,4 +186,3 @@ if __name__ == "__main__":
|
|||||||
}
|
}
|
||||||
encryptor = ConfigEncryptor(key)
|
encryptor = ConfigEncryptor(key)
|
||||||
encryptor.encrypt_config(config_data)
|
encryptor.encrypt_config(config_data)
|
||||||
|
|
||||||
|
|||||||
619
controller.py
Normal 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()
|
||||||
|
Before Width: | Height: | Size: 105 KiB |
117
main.py
@@ -1,19 +1,28 @@
|
|||||||
"""
|
"""
|
||||||
Main module for the Image Processor application.
|
Main module for the Image Processor application.
|
||||||
"""
|
"""
|
||||||
|
from PIL import Image
|
||||||
import customtkinter as ctk
|
import customtkinter as ctk
|
||||||
from ui.log_window import LogWindow
|
from ui.menu import MenuBar # Import the new MenuBar class
|
||||||
from ui.local_processing_tab import LocalProcessingTab
|
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 ui.settings_tab import SettingsTab
|
||||||
from config.decrypt_config import ConfigDecryptor, DECRYPTION_KEY
|
from config.decrypt_config import ConfigDecryptor, DECRYPTION_KEY
|
||||||
from config.encrypt_config import ConfigEncryptor
|
from config.encrypt_config import ConfigEncryptor
|
||||||
|
from controller import AppController
|
||||||
|
|
||||||
|
from ui.preview_frame import PreviewFrame # Import the new PreviewFrame class
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ImageProcessorApp:
|
class ImageProcessorApp:
|
||||||
"""
|
"""
|
||||||
Main application class for the Image Processor.
|
Main application class for the Image Processor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
"""
|
"""
|
||||||
Initialize the ImageProcessorApp.
|
Initialize the ImageProcessorApp.
|
||||||
@@ -23,46 +32,82 @@ class ImageProcessorApp:
|
|||||||
"""
|
"""
|
||||||
self.root = root
|
self.root = root
|
||||||
self.root.title("Image Processor")
|
self.root.title("Image Processor")
|
||||||
self.root.geometry("480x800")
|
self.root.geometry("553x800")
|
||||||
# Create menu frame at the top
|
|
||||||
menu_frame = ctk.CTkFrame(self.root)
|
|
||||||
menu_frame.pack(side="top", fill="x")
|
|
||||||
|
|
||||||
local_processing_button = ctk.CTkButton(menu_frame, text="Local Processing", command=self.show_local_processing_tab)
|
# Initialize the controller
|
||||||
local_processing_button.pack(side="left", padx=5, pady=5)
|
self.controller = AppController(self.root)
|
||||||
|
|
||||||
settings_button = ctk.CTkButton(menu_frame, text="Settings", command=self.show_settings_tab)
|
# Create the menu bar
|
||||||
settings_button.pack(side="left", padx=5, pady=5)
|
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 = 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)
|
# Configure row and column to expand and fill available space
|
||||||
self.tab_parent.grid(row=0, column=0, sticky="nsew")
|
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)
|
# Create a master frame to hold all the other frames
|
||||||
self.log_frame.grid(row=1, column=0, sticky="nsew")
|
self.master_main_frame = ctk.CTkFrame(main_frame)
|
||||||
|
self.master_main_frame.grid(row=0, column=0, sticky="nsew")
|
||||||
main_frame.grid_rowconfigure(0, weight=1)
|
self.master_main_frame.grid_rowconfigure(0, weight=1)
|
||||||
|
self.master_main_frame.grid_columnconfigure(0, weight=1) # Ensure full-width spanning
|
||||||
main_frame.grid_columnconfigure(0, weight=1)
|
|
||||||
|
|
||||||
|
# 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.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)
|
# Info Frame
|
||||||
self.settings_tab = SettingsTab(self.tab_parent)
|
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.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.
|
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):
|
def show_settings_tab(self):
|
||||||
"""
|
"""
|
||||||
@@ -76,19 +121,27 @@ class ImageProcessorApp:
|
|||||||
"""
|
"""
|
||||||
self.root.mainloop()
|
self.root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
decryptor = ConfigEncryptor(DECRYPTION_KEY)
|
decryptor = ConfigEncryptor(DECRYPTION_KEY)
|
||||||
|
# Load the active credentials
|
||||||
config = decryptor.load_credentials()
|
config = decryptor.load_credentials()
|
||||||
|
print(config)
|
||||||
if config:
|
if config:
|
||||||
wc_url = config["url"]
|
wc_url = config.get("url", "")
|
||||||
wc_consumer_key = config["consumer_key"]
|
wc_consumer_key = config.get("consumer_key", "")
|
||||||
wc_consumer_secret = config["consumer_secret"]
|
wc_consumer_secret = config.get("consumer_secret", "")
|
||||||
wp_username = config["username"]
|
wp_username = config.get("username", "")
|
||||||
wp_password = config["password"]
|
wp_password = config.get("password", "")
|
||||||
|
else:
|
||||||
|
print("No active credentials found.")
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print(f"File not found: {e}")
|
print(f"File not found: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
|
||||||
root = ctk.CTk()
|
root = ctk.CTk()
|
||||||
ctk.set_appearance_mode("dark")
|
ctk.set_appearance_mode("dark")
|
||||||
|
|||||||
19
main.spec
@@ -1,25 +1,36 @@
|
|||||||
|
# Import necessary modules
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
# -*- mode: python ; coding: utf-8 -*-
|
# -*- 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(
|
a = Analysis(
|
||||||
['main.py'],
|
['main.py'],
|
||||||
pathex=[],
|
pathex=[],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[],
|
datas=image_files,
|
||||||
hiddenimports=[],
|
hiddenimports=[],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
hooksconfig={},
|
hooksconfig={},
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
excludes=[],
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
noarchive=False,
|
noarchive=False,
|
||||||
optimize=0,
|
|
||||||
)
|
)
|
||||||
pyz = PYZ(a.pure)
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
|
||||||
exe = EXE(
|
exe = EXE(
|
||||||
pyz,
|
pyz,
|
||||||
a.scripts,
|
a.scripts,
|
||||||
a.binaries,
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
a.datas,
|
a.datas,
|
||||||
[],
|
[],
|
||||||
name='main',
|
name='main',
|
||||||
@@ -29,7 +40,7 @@ exe = EXE(
|
|||||||
upx=True,
|
upx=True,
|
||||||
upx_exclude=[],
|
upx_exclude=[],
|
||||||
runtime_tmpdir=None,
|
runtime_tmpdir=None,
|
||||||
console=False,
|
|
||||||
disable_windowed_traceback=False,
|
disable_windowed_traceback=False,
|
||||||
argv_emulation=False,
|
argv_emulation=False,
|
||||||
target_arch=None,
|
target_arch=None,
|
||||||
|
|||||||
1
speelgoed-config.enc
Normal 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
@@ -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
@@ -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
@@ -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
|
After Width: | Height: | Size: 4.0 KiB |
BIN
ui/images/cogs.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
ui/images/directory.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
ui/images/file.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
ui/images/filters.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
ui/images/house-user-solid.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
ui/images/play.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
ui/images/product.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
ui/images/save.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
ui/images/trash.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
ui/images/wp_image.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -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()
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import customtkinter as ctk
|
import customtkinter as ctk
|
||||||
|
from datetime import datetime
|
||||||
class LogWindow:
|
class LogWindow:
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.frame = ctk.CTkFrame(parent)
|
self.frame = ctk.CTkFrame(parent)
|
||||||
@@ -14,8 +14,18 @@ class LogWindow:
|
|||||||
self.log_text.configure(yscrollcommand=self.scrollbar.set)
|
self.log_text.configure(yscrollcommand=self.scrollbar.set)
|
||||||
|
|
||||||
def log_message(self, message):
|
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.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.see(ctk.END)
|
||||||
self.log_text.configure(state="disabled")
|
self.log_text.configure(state="disabled")
|
||||||
self.log_text.update_idletasks()
|
self.log_text.update_idletasks()
|
||||||
103
ui/menu.py
Normal 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
@@ -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)
|
||||||
@@ -1,58 +1,234 @@
|
|||||||
import customtkinter as ctk
|
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:
|
class SettingsTab:
|
||||||
def __init__(self, tab_parent):
|
def __init__(self, tab_parent, controller):
|
||||||
self.tab = ctk.CTkFrame(tab_parent)
|
self.tab = ctk.CTkFrame(tab_parent)
|
||||||
self.tab.grid(row=0, column=0, sticky="nsew")
|
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.inputs = {}
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
def setup_ui(self):
|
def get_active_credential_set(self):
|
||||||
if self.credentials:
|
"""Retrieve active credential set from saved data and convert to new format if needed."""
|
||||||
settings_options = {
|
if isinstance(self.credentials_list, list) and all(
|
||||||
"url": {"type": "text", "label": "WooCommerce URL:", "default": self.credentials.get('url', '')},
|
isinstance(cred, dict) for cred in self.credentials_list
|
||||||
"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', '')},
|
for cred in self.credentials_list:
|
||||||
"username": {"type": "text", "label": "Username:", "default": self.credentials.get('username', '')},
|
if cred.get("active", False):
|
||||||
"password": {"type": "text", "label": "Password:", "default": self.credentials.get('password', ''), "show": "*"}
|
return cred
|
||||||
}
|
return self.credentials_list[0] if self.credentials_list else None
|
||||||
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": "*"}
|
|
||||||
}
|
|
||||||
|
|
||||||
row_index = 0
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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():
|
for name, details in settings_options.items():
|
||||||
self.create_setting(name, details, row_index)
|
self.create_setting(name, details, row_index)
|
||||||
row_index += 1
|
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):
|
def create_setting(self, name, details, row_index):
|
||||||
"""
|
|
||||||
Create a setting based on its type.
|
|
||||||
"""
|
|
||||||
lbl = ctk.CTkLabel(self.tab, text=details["label"])
|
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 = ctk.CTkEntry(self.tab, show=details.get("show", None))
|
entry.insert(0, details["default"])
|
||||||
entry.insert(0, details["default"])
|
entry.grid(row=row_index, column=2,columnspan=2, padx=5, pady=5, sticky="w")
|
||||||
entry.grid(row=row_index, column=1, padx=5, pady=5, sticky="w")
|
self.inputs[name] = entry
|
||||||
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):
|
def save_credentials(self):
|
||||||
save_credentials(
|
credentials = {
|
||||||
self.inputs["url"].get(),
|
"url": self.inputs["url"].get(),
|
||||||
self.inputs["consumer_key"].get(),
|
"consumer_key": self.inputs["consumer_key"].get(),
|
||||||
self.inputs["consumer_secret"].get(),
|
"consumer_secret": self.inputs["consumer_secret"].get(),
|
||||||
self.inputs["username"].get(),
|
"username": self.inputs["username"].get(),
|
||||||
self.inputs["password"].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]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -44,17 +44,16 @@ class FileProcessor:
|
|||||||
if self.selected_file:
|
if self.selected_file:
|
||||||
return self.selected_file
|
return self.selected_file
|
||||||
return None
|
return None
|
||||||
if not self.selected_directory:
|
if self.selected_directory:
|
||||||
return None
|
|
||||||
|
for root, dirs, files in os.walk(self.selected_directory):
|
||||||
for root, dirs, files in os.walk(self.selected_directory):
|
if "ProcessedImages" in dirs:
|
||||||
if "ProcessedImages" in dirs:
|
dirs.remove("ProcessedImages")
|
||||||
dirs.remove("ProcessedImages")
|
for file in files:
|
||||||
for file in files:
|
if file.lower().endswith(
|
||||||
if file.lower().endswith(
|
(".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif")
|
||||||
(".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif")
|
):
|
||||||
):
|
return os.path.join(root, file)
|
||||||
return os.path.join(root, file)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def log_message(self, message, log=None):
|
def log_message(self, message, log=None):
|
||||||
@@ -66,7 +65,7 @@ class FileProcessor:
|
|||||||
log (function, optional): The log function to use. Defaults to None.
|
log (function, optional): The log function to use. Defaults to None.
|
||||||
"""
|
"""
|
||||||
if log:
|
if log:
|
||||||
log(message)
|
log.log_message(message)
|
||||||
else:
|
else:
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
@@ -77,6 +76,8 @@ class FileProcessor:
|
|||||||
Args:
|
Args:
|
||||||
options (dict): Processing options.
|
options (dict): Processing options.
|
||||||
"""
|
"""
|
||||||
|
if options.get("selected_directory"):
|
||||||
|
self.selected_directory = options.get("selected_directory")
|
||||||
if not self.selected_directory:
|
if not self.selected_directory:
|
||||||
messagebox.showwarning(
|
messagebox.showwarning(
|
||||||
"No Directory", "Please select a directory.")
|
"No Directory", "Please select a directory.")
|
||||||
@@ -85,8 +86,12 @@ class FileProcessor:
|
|||||||
self.log_message(
|
self.log_message(
|
||||||
f"Processing started for directory: {self.selected_directory}", log
|
f"Processing started for directory: {self.selected_directory}", log
|
||||||
)
|
)
|
||||||
|
self.log_message(
|
||||||
output_directory = self.create_output_directory(log)
|
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)
|
image_paths = self.collect_image_paths(log)
|
||||||
|
|
||||||
self.process_images(image_paths, output_directory, options, log)
|
self.process_images(image_paths, output_directory, options, log)
|
||||||
@@ -106,7 +111,7 @@ class FileProcessor:
|
|||||||
str: The path to the output directory.
|
str: The path to the output directory.
|
||||||
"""
|
"""
|
||||||
output_directory = os.path.join(
|
output_directory = os.path.join(
|
||||||
self.selected_directory, "ProcessedImages")
|
self.selected_directory)
|
||||||
if os.path.exists(output_directory):
|
if os.path.exists(output_directory):
|
||||||
shutil.rmtree(output_directory)
|
shutil.rmtree(output_directory)
|
||||||
self.log_message("Existing directory removed.", log)
|
self.log_message("Existing directory removed.", log)
|
||||||
@@ -138,7 +143,7 @@ class FileProcessor:
|
|||||||
self.log_message(f"Total images found: {len(image_paths)}", log)
|
self.log_message(f"Total images found: {len(image_paths)}", log)
|
||||||
return image_paths
|
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.
|
Process each image by resizing and saving it to the output directory.
|
||||||
|
|
||||||
@@ -147,31 +152,45 @@ class FileProcessor:
|
|||||||
output_directory (str): The path to the output directory.
|
output_directory (str): The path to the output directory.
|
||||||
options (dict): Processing options.
|
options (dict): Processing options.
|
||||||
log (function): The log function to use.
|
log (function): The log function to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of output image paths.
|
||||||
"""
|
"""
|
||||||
from utils.image_processing import ImageProcessor
|
from utils.image_processing import ImageProcessor
|
||||||
|
processed_images = []
|
||||||
image = ImageProcessor()
|
image = ImageProcessor()
|
||||||
image.set_background_color(options.get("background_color", "transparent"))
|
image.set_background_color(options.get("background_color", "transparent"))
|
||||||
image.set_image_size(options.get("image_size", "contain"))
|
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")
|
format = options.get("image_format")
|
||||||
|
|
||||||
for file_path in image_paths:
|
for file_path in image_paths:
|
||||||
# output_path = os.path.join(
|
output_path = self.generate_output_path(output_directory, file_path, options, product)
|
||||||
# output_directory, os.path.relpath(
|
previews = options.get("update_previews")
|
||||||
# file_path, self.selected_directory)
|
previews(file_path)
|
||||||
# )
|
|
||||||
output_path = self.generate_output_path(output_directory, file_path, options)
|
|
||||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
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":
|
if format == "DZI":
|
||||||
DZI(file_path, output_path, options)
|
DZI(file_path, output_path, options)
|
||||||
else:
|
else:
|
||||||
image.resize_image(
|
image.resize_image(file_path, output_path, options)
|
||||||
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):
|
if os.path.exists(file_path) and options.get("delete_images", False):
|
||||||
self.log_message(f"Removing: {file_path}", log)
|
self.log_message(f"Removing: {file_path}", log)
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
self.log_message(f"Processed: {file_path}", log)
|
self.log_message(f"Processed: {file_path}", log)
|
||||||
|
|
||||||
|
return processed_images
|
||||||
|
|
||||||
|
|
||||||
def proces_single_image(self, options):
|
def proces_single_image(self, options):
|
||||||
"""
|
"""
|
||||||
@@ -229,3 +248,5 @@ class FileProcessor:
|
|||||||
return os.path.join(output_directory, new_filename + ".jpg")
|
return os.path.join(output_directory, new_filename + ".jpg")
|
||||||
elif imgf == "DZI":
|
elif imgf == "DZI":
|
||||||
return os.path.join(output_directory, new_filename + ".dzi")
|
return os.path.join(output_directory, new_filename + ".dzi")
|
||||||
|
elif imgf == "WEBP":
|
||||||
|
return os.path.join(output_directory, new_filename + ".webp")
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class ImageProcessor:
|
|||||||
log (function, optional): The log function to use. Defaults to None.
|
log (function, optional): The log function to use. Defaults to None.
|
||||||
"""
|
"""
|
||||||
if log:
|
if log:
|
||||||
log(message)
|
log.log_message(message)
|
||||||
else:
|
else:
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
|
|||||||