Update files
This commit is contained in:
39
-.pre-commit-config.yaml
Normal file
39
-.pre-commit-config.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# .pre-commit-config.yaml
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.4.0 # Use the latest version
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-json
|
||||||
|
- id: check-added-large-files
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 24.4.2 # Use the latest version
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
- repo: https://github.com/PyCQA/autoflake
|
||||||
|
rev: v2.3.1
|
||||||
|
hooks:
|
||||||
|
|
||||||
|
- id: autoflake
|
||||||
|
args: [--remove-all-unused-imports, --remove-unused-variables]
|
||||||
|
- repo: https://github.com/hhatto/autopep8
|
||||||
|
rev: v2.3.1 # select the tag or revision you want, or run `pre-commit autoupdate`
|
||||||
|
hooks:
|
||||||
|
- id: autopep8
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: pylint
|
||||||
|
name: pylint
|
||||||
|
entry: pylint
|
||||||
|
language: system
|
||||||
|
types: [python]
|
||||||
|
require_serial: true
|
||||||
|
args:
|
||||||
|
[
|
||||||
|
"-rn", # Only display messages
|
||||||
|
"-sn", # Don't display the score
|
||||||
|
]
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,4 +3,4 @@ config.enc
|
|||||||
*/__pycache__
|
*/__pycache__
|
||||||
/build
|
/build
|
||||||
/dist
|
/dist
|
||||||
/temp
|
/temp
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
from cryptography.fernet import Fernet
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import requests
|
|
||||||
import base64
|
|
||||||
from woocommerce import API
|
|
||||||
from tkinter import messagebox
|
|
||||||
import tempfile
|
|
||||||
from utils.image_processing import resize_image
|
|
||||||
import pprint
|
|
||||||
credentials_file = 'credentials.json'
|
|
||||||
|
|
||||||
# Hardcoded key (replace with your generated key)
|
|
||||||
key = b'u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI='
|
|
||||||
|
|
||||||
def save_credentials(url, consumer_key, consumer_secret, username, password):
|
|
||||||
credentials = {
|
|
||||||
'url': url,
|
|
||||||
'consumer_key': consumer_key,
|
|
||||||
'consumer_secret': consumer_secret,
|
|
||||||
'username': username,
|
|
||||||
'password': password
|
|
||||||
}
|
|
||||||
credentials_str = json.dumps(credentials)
|
|
||||||
fernet = Fernet(key)
|
|
||||||
encrypted = fernet.encrypt(credentials_str.encode())
|
|
||||||
with open('config.enc', 'wb') as file:
|
|
||||||
file.write(encrypted)
|
|
||||||
|
|
||||||
def load_credentials():
|
|
||||||
if not os.path.exists('config.enc'):
|
|
||||||
return None
|
|
||||||
fernet = Fernet(key)
|
|
||||||
with open('config.enc', 'rb') as file:
|
|
||||||
encrypted = file.read()
|
|
||||||
decrypted = fernet.decrypt(encrypted).decode()
|
|
||||||
return json.loads(decrypted)
|
|
||||||
|
|
||||||
|
|
||||||
def get_wcapi():
|
|
||||||
credentials = load_credentials()
|
|
||||||
if not credentials:
|
|
||||||
messagebox.showerror("Error", "No WooCommerce credentials found. Please set them in the settings.")
|
|
||||||
return None
|
|
||||||
return API(
|
|
||||||
url=credentials['url'],
|
|
||||||
consumer_key=credentials['consumer_key'],
|
|
||||||
consumer_secret=credentials['consumer_secret'],
|
|
||||||
version="wc/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_product(id):
|
|
||||||
wcapi = get_wcapi()
|
|
||||||
if not wcapi:
|
|
||||||
return None
|
|
||||||
result = wcapi.get("products/"+str(id))
|
|
||||||
|
|
||||||
image_paths = {}
|
|
||||||
product = result.json()
|
|
||||||
if product.get('images'):
|
|
||||||
images = product.get('images')
|
|
||||||
|
|
||||||
if not os.path.exists('temp'):
|
|
||||||
os.makedirs('temp')
|
|
||||||
|
|
||||||
for index, image in enumerate(images):
|
|
||||||
image_url = image.get('src')
|
|
||||||
image_id = image.get('id')
|
|
||||||
response = requests.get(image_url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
file_name = image_url.split('/')[-1]
|
|
||||||
file_path = os.path.join('temp', file_name)
|
|
||||||
image_paths[image_id] = file_path
|
|
||||||
with open(file_path, 'wb') as file:
|
|
||||||
file.write(response.content)
|
|
||||||
print(f"Image {index + 1}/{len(images)} downloaded and saved: {file_path}")
|
|
||||||
else:
|
|
||||||
print(f"Failed to download image {index + 1}/{len(images)}")
|
|
||||||
else:
|
|
||||||
if product.get('name'):
|
|
||||||
print(f"No images found for {product.get('name')}")
|
|
||||||
else:
|
|
||||||
print("No images found")
|
|
||||||
return image_paths, product
|
|
||||||
|
|
||||||
def upload_image(imgPath):
|
|
||||||
data = open(imgPath, 'rb').read()
|
|
||||||
fileName = os.path.basename(imgPath)
|
|
||||||
credentials = load_credentials()
|
|
||||||
if not credentials:
|
|
||||||
messagebox.showerror("Error", "No WordPress credentials found. Please set them in the settings.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
username = credentials['username']
|
|
||||||
password = credentials['password']
|
|
||||||
credentials_base64 = base64.b64encode(f"{username}:{password}".encode())
|
|
||||||
credentials_base64 = base64.b64encode(f"{username}:{password}".encode())
|
|
||||||
url = f"{credentials['url']}/wp-json/wp/v2/media"
|
|
||||||
headers = {
|
|
||||||
'Content-Type': 'image/jpg',
|
|
||||||
'Content-Disposition': f'attachment; filename={fileName}',
|
|
||||||
'Authorization': f'basic {credentials_base64.decode()}'
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
res = requests.post(url=url, data=data, headers=headers)
|
|
||||||
res.raise_for_status() # Raise an HTTPError if the HTTP request returned an unsuccessful status code
|
|
||||||
newDict = res.json()
|
|
||||||
newID = newDict.get('id')
|
|
||||||
link = newDict.get('guid').get("rendered") if newDict.get('guid') else None
|
|
||||||
print(newID, link)
|
|
||||||
return newID if newID else False
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Error uploading image: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def delete_img(image_id):
|
|
||||||
credentials = load_credentials()
|
|
||||||
if not credentials:
|
|
||||||
messagebox.showerror("Error", "No WordPress credentials found. Please set them in the settings.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
url = f"{credentials['url']}/wp-json/wp/v2/media/{image_id}"
|
|
||||||
username = credentials['username']
|
|
||||||
password = credentials['password']
|
|
||||||
credentials_base64 = base64.b64encode(f"{username}:{password}".encode())
|
|
||||||
|
|
||||||
res = requests.delete(url=url,
|
|
||||||
headers={'Authorization': f'basic {credentials_base64.decode()}'},
|
|
||||||
params={'force': 'true'})
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
print(f"Image with ID {image_id} deleted successfully.")
|
|
||||||
else:
|
|
||||||
print(f"Failed to delete image with ID {image_id}. Error: {res.text}")
|
|
||||||
|
|
||||||
def update_product(image_ids, old_image_ids, product_id):
|
|
||||||
wcapi = get_wcapi()
|
|
||||||
if not wcapi:
|
|
||||||
return
|
|
||||||
|
|
||||||
product = wcapi.get(f"products/{product_id}").json()
|
|
||||||
product['images'] = [{'id': image_id} for image_id in image_ids]
|
|
||||||
response = wcapi.put(f"products/{product_id}", data=product)
|
|
||||||
if response.status_code == 200:
|
|
||||||
print(f"Product with ID {product_id} updated successfully with new image IDs.")
|
|
||||||
else:
|
|
||||||
print(f"Failed to update product with ID {product_id}. Error: {response.text}")
|
|
||||||
|
|
||||||
def process_product_images(id, name_template, canvas_width, canvas_height):
|
|
||||||
print(name_template)
|
|
||||||
image_paths, product = get_product(id)
|
|
||||||
if not image_paths:
|
|
||||||
return
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_output_directory:
|
|
||||||
print(f"Using temporary directory: {temp_output_directory}")
|
|
||||||
|
|
||||||
old_list = []
|
|
||||||
new_list = []
|
|
||||||
|
|
||||||
for image_id, file_path in image_paths.items():
|
|
||||||
output_path = generate_output_path(temp_output_directory, file_path, name_template, product, canvas_width, canvas_height)
|
|
||||||
resize_image(file_path, output_path, '')
|
|
||||||
new_id = upload_image(output_path)
|
|
||||||
if new_id:
|
|
||||||
old_list.append(image_id)
|
|
||||||
new_list.append(new_id)
|
|
||||||
|
|
||||||
update_product(new_list, old_list, id)
|
|
||||||
print("Temporary files processed and uploaded successfully.")
|
|
||||||
|
|
||||||
def generate_output_path(temp_output_directory, file_path, template, product, canvas_width, canvas_height):
|
|
||||||
# Generate the new filename based on the template
|
|
||||||
name, ext = os.path.splitext(os.path.basename(file_path))
|
|
||||||
width = canvas_width
|
|
||||||
height = canvas_height
|
|
||||||
sku = product.get('sku', '')
|
|
||||||
slug = product.get('name', '')
|
|
||||||
title = product.get('slug', '')
|
|
||||||
pprint.pprint(product)
|
|
||||||
# Here you can add more attributes to the template if needed
|
|
||||||
new_filename = template.format(name=name, sku=sku, width=width, height=height, slug=slug, title=title)
|
|
||||||
return os.path.join(temp_output_directory, new_filename + ext)
|
|
||||||
|
|
||||||
def process_all_products(name_template):
|
|
||||||
wcapi = get_wcapi()
|
|
||||||
if not wcapi:
|
|
||||||
return
|
|
||||||
|
|
||||||
page = 1
|
|
||||||
while True:
|
|
||||||
products = wcapi.get("products", params={"per_page": 100, "page": page}).json()
|
|
||||||
if not products:
|
|
||||||
break
|
|
||||||
|
|
||||||
for product in products:
|
|
||||||
process_product_images(product['id'], name_template)
|
|
||||||
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
messagebox.showinfo("Process Complete", "All product images processing is complete.")
|
|
||||||
332
api/woocommerce_api.py
Normal file
332
api/woocommerce_api.py
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
"""
|
||||||
|
Module for WooCommerce API interactions and image processing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import tempfile
|
||||||
|
import pprint
|
||||||
|
from tkinter import messagebox
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
import requests
|
||||||
|
from woocommerce import API
|
||||||
|
from utils.image_processing import ImageProcessor
|
||||||
|
|
||||||
|
CREDENTIALS_FILE = "credentials.json"
|
||||||
|
|
||||||
|
# Hardcoded key (replace with your generated key)
|
||||||
|
KEY = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
||||||
|
|
||||||
|
|
||||||
|
def save_credentials(url, consumer_key, consumer_secret, username, password):
|
||||||
|
"""
|
||||||
|
Save WooCommerce and WordPress credentials to an encrypted file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): The base URL for the WooCommerce store.
|
||||||
|
consumer_key (str): The consumer key for WooCommerce API.
|
||||||
|
consumer_secret (str): The consumer secret for WooCommerce API.
|
||||||
|
username (str): The username for WordPress.
|
||||||
|
password (str): The password for WordPress.
|
||||||
|
"""
|
||||||
|
credentials = {
|
||||||
|
"url": url,
|
||||||
|
"consumer_key": consumer_key,
|
||||||
|
"consumer_secret": consumer_secret,
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
credentials_str = json.dumps(credentials)
|
||||||
|
fernet = Fernet(KEY)
|
||||||
|
encrypted = fernet.encrypt(credentials_str.encode())
|
||||||
|
with open("config.enc", "wb") as file:
|
||||||
|
file.write(encrypted)
|
||||||
|
|
||||||
|
|
||||||
|
def load_credentials():
|
||||||
|
"""
|
||||||
|
Load WooCommerce and WordPress credentials from an encrypted file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The decrypted credentials, or None if the file does not exist.
|
||||||
|
"""
|
||||||
|
if not os.path.exists("config.enc"):
|
||||||
|
return None
|
||||||
|
fernet = Fernet(KEY)
|
||||||
|
with open("config.enc", "rb") as file:
|
||||||
|
encrypted = file.read()
|
||||||
|
decrypted = fernet.decrypt(encrypted).decode()
|
||||||
|
return json.loads(decrypted)
|
||||||
|
|
||||||
|
|
||||||
|
def get_wcapi():
|
||||||
|
"""
|
||||||
|
Get a WooCommerce API client instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
woocommerce.API: The WooCommerce API client instance, or None if credentials are missing.
|
||||||
|
"""
|
||||||
|
credentials = load_credentials()
|
||||||
|
if not credentials:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error",
|
||||||
|
"No WooCommerce credentials found. Please set them in the settings.",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return API(
|
||||||
|
url=credentials["url"],
|
||||||
|
consumer_key=credentials["consumer_key"],
|
||||||
|
consumer_secret=credentials["consumer_secret"],
|
||||||
|
version="wc/v3",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_product(product_id):
|
||||||
|
"""
|
||||||
|
Get a WooCommerce product and download its images.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
product_id (int): The ID of the WooCommerce product.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: A dictionary of image paths and the product data.
|
||||||
|
"""
|
||||||
|
wcapi = get_wcapi()
|
||||||
|
if not wcapi:
|
||||||
|
return None
|
||||||
|
result = wcapi.get(f"products/{product_id}")
|
||||||
|
|
||||||
|
image_paths = {}
|
||||||
|
product = result.json()
|
||||||
|
if product.get("images"):
|
||||||
|
images = product.get("images")
|
||||||
|
|
||||||
|
if not os.path.exists("temp"):
|
||||||
|
os.makedirs("temp")
|
||||||
|
|
||||||
|
for index, image in enumerate(images):
|
||||||
|
image_url = image.get("src")
|
||||||
|
image_id = image.get("id")
|
||||||
|
response = requests.get(image_url, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
file_name = image_url.split("/")[-1]
|
||||||
|
file_path = os.path.join("temp", file_name)
|
||||||
|
image_paths[image_id] = file_path
|
||||||
|
with open(file_path, "wb") as file:
|
||||||
|
file.write(response.content)
|
||||||
|
print(
|
||||||
|
f"Image {index + 1}/{len(images)} downloaded and saved: {file_path}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"Failed to download image {index + 1}/{len(images)}")
|
||||||
|
else:
|
||||||
|
if product.get("name"):
|
||||||
|
print(f"No images found for {product.get('name')}")
|
||||||
|
else:
|
||||||
|
print("No images found")
|
||||||
|
return image_paths, product
|
||||||
|
|
||||||
|
|
||||||
|
def upload_image(img_path):
|
||||||
|
"""
|
||||||
|
Upload an image to WordPress.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
img_path (str): The path to the image file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The ID of the uploaded image, or False if the upload failed.
|
||||||
|
"""
|
||||||
|
with open(img_path, "rb") as img_file:
|
||||||
|
data = img_file.read()
|
||||||
|
file_name = os.path.basename(img_path)
|
||||||
|
credentials = load_credentials()
|
||||||
|
if not credentials:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error", "No WordPress credentials found. Please set them in the settings."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
username = credentials["username"]
|
||||||
|
password = credentials["password"]
|
||||||
|
credentials_base64 = base64.b64encode(f"{username}:{password}".encode())
|
||||||
|
url = f"{credentials['url']}/wp-json/wp/v2/media"
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "image/jpg",
|
||||||
|
"Content-Disposition": f"attachment; filename={file_name}",
|
||||||
|
"Authorization": f"basic {credentials_base64.decode()}",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = requests.post(url=url, data=data, headers=headers, timeout=10)
|
||||||
|
res.raise_for_status()
|
||||||
|
response_dict = res.json()
|
||||||
|
new_id = response_dict.get("id")
|
||||||
|
link = (
|
||||||
|
response_dict.get("guid").get("rendered")
|
||||||
|
if response_dict.get("guid")
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
print(new_id, link)
|
||||||
|
return new_id if new_id else False
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error uploading image: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def delete_img(image_id):
|
||||||
|
"""
|
||||||
|
Delete an image from WordPress.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_id (int): The ID of the image to delete.
|
||||||
|
"""
|
||||||
|
credentials = load_credentials()
|
||||||
|
if not credentials:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error", "No WordPress credentials found. Please set them in the settings."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
url = f"{credentials['url']}/wp-json/wp/v2/media/{image_id}"
|
||||||
|
username = credentials["username"]
|
||||||
|
password = credentials["password"]
|
||||||
|
credentials_base64 = base64.b64encode(f"{username}:{password}".encode())
|
||||||
|
|
||||||
|
res = requests.delete(
|
||||||
|
url=url,
|
||||||
|
headers={"Authorization": f"basic {credentials_base64.decode()}"},
|
||||||
|
params={"force": "true"},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
if res.status_code == 200:
|
||||||
|
print(f"Image with ID {image_id} deleted successfully.")
|
||||||
|
else:
|
||||||
|
print(f"Failed to delete image with ID {image_id}. Error: {res.text}")
|
||||||
|
|
||||||
|
|
||||||
|
def update_product(image_ids, product_id):
|
||||||
|
"""
|
||||||
|
Update a WooCommerce product with new image IDs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_ids (list): A list of new image IDs.
|
||||||
|
product_id (int): The ID of the WooCommerce product.
|
||||||
|
"""
|
||||||
|
wcapi = get_wcapi()
|
||||||
|
if not wcapi:
|
||||||
|
return
|
||||||
|
|
||||||
|
product = wcapi.get(f"products/{product_id}").json()
|
||||||
|
product["images"] = [{"id": image_id} for image_id in image_ids]
|
||||||
|
response = wcapi.put(f"products/{product_id}", data=product)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(
|
||||||
|
f"Product with ID {product_id} updated successfully with new image IDs.")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Failed to update product with ID {product_id}. Error: {response.text}")
|
||||||
|
|
||||||
|
|
||||||
|
def process_product_images(product_id, name_template, canvas_width, canvas_height):
|
||||||
|
"""
|
||||||
|
Process images for a WooCommerce product by resizing and uploading them.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
product_id (int): The ID of the WooCommerce product.
|
||||||
|
name_template (str): The template for generating image filenames.
|
||||||
|
canvas_width (int): The width of the canvas for resizing images.
|
||||||
|
canvas_height (int): The height of the canvas for resizing images.
|
||||||
|
"""
|
||||||
|
print(name_template)
|
||||||
|
image_paths, product = get_product(product_id)
|
||||||
|
if not image_paths:
|
||||||
|
return
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as temp_output_directory:
|
||||||
|
print(f"Using temporary directory: {temp_output_directory}")
|
||||||
|
|
||||||
|
old_list = []
|
||||||
|
new_list = []
|
||||||
|
|
||||||
|
for image_id, file_path in image_paths.items():
|
||||||
|
output_path = generate_output_path(
|
||||||
|
temp_output_directory,
|
||||||
|
file_path,
|
||||||
|
name_template,
|
||||||
|
product,
|
||||||
|
canvas_width,
|
||||||
|
canvas_height,
|
||||||
|
)
|
||||||
|
resize_image(file_path, output_path, "")
|
||||||
|
new_id = upload_image(output_path)
|
||||||
|
if new_id:
|
||||||
|
old_list.append(image_id)
|
||||||
|
new_list.append(new_id)
|
||||||
|
|
||||||
|
update_product(new_list, product_id)
|
||||||
|
print("Temporary files processed and uploaded successfully.")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_output_path(
|
||||||
|
temp_output_directory, file_path, template, product, canvas_width, canvas_height
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Generate the output path for resized images based on a template.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
temp_output_directory (str): The path to the temporary output directory.
|
||||||
|
file_path (str): The original file path.
|
||||||
|
template (str): The template for generating the new filename.
|
||||||
|
product (dict): The WooCommerce product data.
|
||||||
|
canvas_width (int): The width of the canvas for resizing images.
|
||||||
|
canvas_height (int): The height of the canvas for resizing images.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated output path.
|
||||||
|
"""
|
||||||
|
name, ext = os.path.splitext(os.path.basename(file_path))
|
||||||
|
width = canvas_width
|
||||||
|
height = canvas_height
|
||||||
|
sku = product.get("sku", "")
|
||||||
|
slug = product.get("name", "")
|
||||||
|
title = product.get("slug", "")
|
||||||
|
pprint.pprint(product)
|
||||||
|
new_filename = template.format(
|
||||||
|
name=name, sku=sku, width=width, height=height, slug=slug, title=title
|
||||||
|
)
|
||||||
|
return os.path.join(temp_output_directory, new_filename + ext)
|
||||||
|
|
||||||
|
|
||||||
|
def process_all_products(name_template, canvas_width, canvas_height):
|
||||||
|
"""
|
||||||
|
Process images for all WooCommerce products by resizing and uploading them.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name_template (str): The template for generating image filenames.
|
||||||
|
canvas_width (int): The width of the canvas for resizing images.
|
||||||
|
canvas_height (int): The height of the canvas for resizing images.
|
||||||
|
"""
|
||||||
|
wcapi = get_wcapi()
|
||||||
|
if not wcapi:
|
||||||
|
return
|
||||||
|
|
||||||
|
page = 1
|
||||||
|
while True:
|
||||||
|
products = wcapi.get("products", params={
|
||||||
|
"per_page": 100, "page": page}).json()
|
||||||
|
if not products:
|
||||||
|
break
|
||||||
|
|
||||||
|
for product in products:
|
||||||
|
process_product_images(
|
||||||
|
product["id"], name_template, canvas_width, canvas_height
|
||||||
|
)
|
||||||
|
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
messagebox.showinfo(
|
||||||
|
"Process Complete", "All product images processing is complete."
|
||||||
|
)
|
||||||
@@ -1,30 +1,63 @@
|
|||||||
from cryptography.fernet import Fernet
|
"""
|
||||||
|
Module for decrypting configuration files using Fernet symmetric encryption.
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
|
||||||
|
|
||||||
class ConfigDecryptor:
|
class ConfigDecryptor:
|
||||||
def __init__(self, key):
|
"""
|
||||||
self.key = key
|
Class to handle decryption of configuration files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, decryption_key):
|
||||||
|
"""
|
||||||
|
Initialize the ConfigDecryptor with a given decryption key.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
decryption_key (bytes): The key to use for decryption.
|
||||||
|
"""
|
||||||
|
self.decryption_key = decryption_key
|
||||||
|
|
||||||
def decrypt(self):
|
def decrypt(self):
|
||||||
|
"""
|
||||||
|
Decrypt the 'config.enc' file and return the configuration data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The decrypted configuration data.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: If the 'config.enc' file does not exist.
|
||||||
|
Exception: If any other error occurs during decryption.
|
||||||
|
"""
|
||||||
if not os.path.exists("config.enc"):
|
if not os.path.exists("config.enc"):
|
||||||
raise FileNotFoundError("The encrypted configuration file 'config.enc' does not exist.")
|
raise FileNotFoundError(
|
||||||
|
"The encrypted configuration file 'config.enc' does not exist."
|
||||||
fernet = Fernet(self.key)
|
)
|
||||||
|
|
||||||
|
fernet = Fernet(self.decryption_key)
|
||||||
with open("config.enc", "rb") as encrypted_file:
|
with open("config.enc", "rb") as encrypted_file:
|
||||||
encrypted = encrypted_file.read()
|
encrypted = encrypted_file.read()
|
||||||
decrypted = fernet.decrypt(encrypted).decode()
|
decrypted = fernet.decrypt(encrypted).decode()
|
||||||
return json.loads(decrypted)
|
return json.loads(decrypted)
|
||||||
|
|
||||||
|
def hello_world(self):
|
||||||
|
"""
|
||||||
|
Placeholder
|
||||||
|
"""
|
||||||
|
return "Hello world"
|
||||||
|
|
||||||
|
|
||||||
# Define your key here
|
# Define your key here
|
||||||
key = b'u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI=' # Replace with your actual key
|
# Replace with your actual key
|
||||||
|
DECRYPTION_KEY = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
key = b'u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI=' # Replace with your actual key
|
decryptor = ConfigDecryptor(DECRYPTION_KEY)
|
||||||
decryptor = ConfigDecryptor(key)
|
|
||||||
try:
|
try:
|
||||||
config = decryptor.decrypt()
|
config = decryptor.decrypt()
|
||||||
print(config)
|
print(config)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
print(e)
|
print(e)
|
||||||
except Exception as e:
|
|
||||||
print(f"An error occurred: {e}")
|
|
||||||
|
|||||||
@@ -1,20 +1,45 @@
|
|||||||
|
"""
|
||||||
|
Module for encrypting configuration files using Fernet symmetric encryption.
|
||||||
|
"""
|
||||||
|
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
|
|
||||||
|
|
||||||
class ConfigEncryptor:
|
class ConfigEncryptor:
|
||||||
|
"""
|
||||||
|
Class to handle encryption of configuration data.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Initialize the ConfigEncryptor with a generated encryption key.
|
||||||
|
"""
|
||||||
self.key = Fernet.generate_key()
|
self.key = Fernet.generate_key()
|
||||||
|
|
||||||
def encrypt_config(self, data):
|
def encrypt_config(self, data):
|
||||||
|
"""
|
||||||
|
Encrypt the configuration data and save it to 'config.enc'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (str): The configuration data to be encrypted.
|
||||||
|
"""
|
||||||
fernet = Fernet(self.key)
|
fernet = Fernet(self.key)
|
||||||
encrypted = fernet.encrypt(data.encode())
|
encrypted = fernet.encrypt(data.encode())
|
||||||
with open("config.enc", "wb") as encrypted_file:
|
with open("config.enc", "wb") as encrypted_file:
|
||||||
encrypted_file.write(encrypted)
|
encrypted_file.write(encrypted)
|
||||||
|
|
||||||
def get_key(self):
|
def get_key(self):
|
||||||
|
"""
|
||||||
|
Get the generated encryption key.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated encryption key as a string.
|
||||||
|
"""
|
||||||
return self.key.decode()
|
return self.key.decode()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
config_data = """
|
CONFIG_DATA = """
|
||||||
{
|
{
|
||||||
"url": "https://yourstore.com",
|
"url": "https://yourstore.com",
|
||||||
"consumer_key": "ck_yourconsumerkey",
|
"consumer_key": "ck_yourconsumerkey",
|
||||||
@@ -25,4 +50,4 @@ if __name__ == "__main__":
|
|||||||
"""
|
"""
|
||||||
encryptor = ConfigEncryptor()
|
encryptor = ConfigEncryptor()
|
||||||
print(f"Encryption key: {encryptor.get_key()}")
|
print(f"Encryption key: {encryptor.get_key()}")
|
||||||
encryptor.encrypt_config(config_data)
|
encryptor.encrypt_config(CONFIG_DATA)
|
||||||
|
|||||||
46
main.py
46
main.py
@@ -1,46 +1,68 @@
|
|||||||
|
"""
|
||||||
|
Main module for the Image Processor application.
|
||||||
|
"""
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from ui.log_window import LogWindow
|
from ui.log_window import LogWindow
|
||||||
from ui.local_processing_tab import LocalProcessingTab
|
from ui.local_processing_tab import LocalProcessingTab
|
||||||
from ui.settings_tab import SettingsTab
|
from ui.settings_tab import SettingsTab
|
||||||
from config.decrypt_config import ConfigDecryptor, key
|
from config.decrypt_config import ConfigDecryptor, DECRYPTION_KEY
|
||||||
|
|
||||||
|
|
||||||
class ImageProcessorApp:
|
class ImageProcessorApp:
|
||||||
|
"""
|
||||||
|
Main application class for the Image Processor.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
|
"""
|
||||||
|
Initialize the ImageProcessorApp.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root (tk.Tk): The root Tkinter window.
|
||||||
|
"""
|
||||||
self.root = root
|
self.root = root
|
||||||
self.root.title("Image Processor")
|
self.root.title("Image Processor")
|
||||||
self.root.geometry("700x400")
|
self.root.geometry("700x400")
|
||||||
|
|
||||||
self.tab_parent = ttk.Notebook(self.root)
|
self.tab_parent = ttk.Notebook(self.root)
|
||||||
self.log_window = None
|
self.log_window = None
|
||||||
|
|
||||||
self.local_processing_tab = LocalProcessingTab(self.tab_parent, "Local Processing", self.open_log_window)
|
self.local_processing_tab = LocalProcessingTab(
|
||||||
|
self.tab_parent, "Local Processing", self.open_log_window
|
||||||
|
)
|
||||||
self.settings_tab = SettingsTab(self.tab_parent, "Settings")
|
self.settings_tab = SettingsTab(self.tab_parent, "Settings")
|
||||||
|
|
||||||
self.tab_parent.pack(expand=True, fill='both')
|
self.tab_parent.pack(expand=True, fill="both")
|
||||||
|
|
||||||
def open_log_window(self):
|
def open_log_window(self):
|
||||||
|
"""
|
||||||
|
Open the log window. If it already exists, bring it to the front.
|
||||||
|
"""
|
||||||
if self.log_window is None or not self.log_window.winfo_exists():
|
if self.log_window is None or not self.log_window.winfo_exists():
|
||||||
self.log_window = LogWindow(self.root)
|
self.log_window = LogWindow(self.root)
|
||||||
else:
|
else:
|
||||||
self.log_window.lift()
|
self.log_window.lift()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
"""
|
||||||
|
Run the Tkinter main loop.
|
||||||
|
"""
|
||||||
self.root.mainloop()
|
self.root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
decryptor = ConfigDecryptor(key)
|
decryptor = ConfigDecryptor(DECRYPTION_KEY)
|
||||||
config = decryptor.decrypt()
|
config = decryptor.decrypt()
|
||||||
wc_url = config['url']
|
wc_url = config["url"]
|
||||||
wc_consumer_key = config['consumer_key']
|
wc_consumer_key = config["consumer_key"]
|
||||||
wc_consumer_secret = config['consumer_secret']
|
wc_consumer_secret = config["consumer_secret"]
|
||||||
wp_username = config['username']
|
wp_username = config["username"]
|
||||||
wp_password = config['password']
|
wp_password = config["password"]
|
||||||
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 = tk.Tk()
|
root = tk.Tk()
|
||||||
app = ImageProcessorApp(root)
|
app = ImageProcessorApp(root)
|
||||||
|
|||||||
@@ -69,4 +69,4 @@ pyinstaller --onefile --windowed main.py
|
|||||||
--windowed: Ensures the console window does not appear when running the GUI application.
|
--windowed: Ensures the console window does not appear when running the GUI application.
|
||||||
|
|
||||||
Locate the Executable:
|
Locate the Executable:
|
||||||
After running the command, you will find the executable in the dist folder within your project directory.
|
After running the command, you will find the executable in the dist folder within your project directory.
|
||||||
|
|||||||
Binary file not shown.
@@ -1,17 +1,18 @@
|
|||||||
from tkinter import Toplevel, Text
|
from tkinter import Toplevel, Text
|
||||||
|
|
||||||
|
|
||||||
class LogWindow(Toplevel):
|
class LogWindow(Toplevel):
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.title("Log Window")
|
self.title("Log Window")
|
||||||
self.geometry("500x300")
|
self.geometry("500x300")
|
||||||
self.text = Text(self)
|
self.text = Text(self)
|
||||||
self.text.pack(expand=True, fill='both')
|
self.text.pack(expand=True, fill="both")
|
||||||
self.protocol("WM_DELETE_WINDOW", self.hide)
|
self.protocol("WM_DELETE_WINDOW", self.hide)
|
||||||
|
|
||||||
def log(self, message):
|
def log(self, message):
|
||||||
self.text.insert('end', message + '\n')
|
self.text.insert("end", message + "\n")
|
||||||
self.text.see('end')
|
self.text.see("end")
|
||||||
|
|
||||||
def hide(self):
|
def hide(self):
|
||||||
self.withdraw()
|
self.withdraw()
|
||||||
|
|||||||
131
ui/options_window.py
Normal file
131
ui/options_window.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
|
||||||
|
class OptionsWindow(tk.Toplevel):
|
||||||
|
def __init__(self, parent, apply_callback, current_options):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.title("Options")
|
||||||
|
self.geometry("400x400")
|
||||||
|
|
||||||
|
self.apply_callback = apply_callback
|
||||||
|
self.options = current_options
|
||||||
|
self.inputs = {}
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
"""
|
||||||
|
Set up the UI components.
|
||||||
|
"""
|
||||||
|
self.row_index = 0
|
||||||
|
for name, details in self.options.items():
|
||||||
|
if details["type"] == "number":
|
||||||
|
self.add_number_input(
|
||||||
|
name,
|
||||||
|
details["label"],
|
||||||
|
details["default"],
|
||||||
|
details["min"],
|
||||||
|
details["max"],
|
||||||
|
)
|
||||||
|
elif details["type"] == "text":
|
||||||
|
self.add_text_input(name, details["label"], details["default"])
|
||||||
|
elif details["type"] == "checkbox":
|
||||||
|
self.add_checkbox(name, details["label"], details["default"])
|
||||||
|
|
||||||
|
self.create_apply_button()
|
||||||
|
|
||||||
|
def add_number_input(self, name, label, default, min_val, max_val):
|
||||||
|
"""
|
||||||
|
Add a number input field.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the input field.
|
||||||
|
label (str): The label for the input field.
|
||||||
|
default (int): The default value.
|
||||||
|
min_val (int): The minimum value.
|
||||||
|
max_val (int): The maximum value.
|
||||||
|
"""
|
||||||
|
lbl = tk.Label(self, text=label)
|
||||||
|
lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
entry = tk.Entry(self)
|
||||||
|
entry.insert(0, str(default))
|
||||||
|
entry.grid(row=self.row_index, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
self.inputs[name] = {
|
||||||
|
"type": "number",
|
||||||
|
"widget": entry,
|
||||||
|
"min": min_val,
|
||||||
|
"max": max_val,
|
||||||
|
}
|
||||||
|
self.row_index += 1
|
||||||
|
|
||||||
|
def add_text_input(self, name, label, default):
|
||||||
|
"""
|
||||||
|
Add a text input field.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the input field.
|
||||||
|
label (str): The label for the input field.
|
||||||
|
default (str): The default value.
|
||||||
|
"""
|
||||||
|
lbl = tk.Label(self, text=label)
|
||||||
|
lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
entry = tk.Entry(self)
|
||||||
|
entry.insert(0, default)
|
||||||
|
entry.grid(row=self.row_index, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
self.inputs[name] = {"type": "text", "widget": entry}
|
||||||
|
self.row_index += 1
|
||||||
|
|
||||||
|
def add_checkbox(self, name, label, default):
|
||||||
|
"""
|
||||||
|
Add a checkbox.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the input field.
|
||||||
|
label (str): The label for the input field.
|
||||||
|
default (bool): The default value.
|
||||||
|
"""
|
||||||
|
var = tk.BooleanVar(value=default)
|
||||||
|
chk = tk.Checkbutton(self, text=label, variable=var)
|
||||||
|
chk.grid(row=self.row_index, column=0,
|
||||||
|
columnspan=2, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
self.inputs[name] = {"type": "checkbox", "variable": var}
|
||||||
|
self.row_index += 1
|
||||||
|
|
||||||
|
def create_apply_button(self):
|
||||||
|
"""
|
||||||
|
Create the apply button.
|
||||||
|
"""
|
||||||
|
apply_button = tk.Button(
|
||||||
|
self, text="Apply", command=self.apply_options)
|
||||||
|
apply_button.grid(row=self.row_index, column=0, columnspan=2, pady=10)
|
||||||
|
|
||||||
|
def apply_options(self):
|
||||||
|
"""
|
||||||
|
Apply the options and call the callback function.
|
||||||
|
"""
|
||||||
|
options = {}
|
||||||
|
for name, details in self.inputs.items():
|
||||||
|
if details["type"] == "number":
|
||||||
|
value = int(details["widget"].get())
|
||||||
|
min_val = details["min"]
|
||||||
|
max_val = details["max"]
|
||||||
|
if min_val <= value <= max_val:
|
||||||
|
options[name] = value
|
||||||
|
else:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error", f"{name} must be between {min_val} and {max_val}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
elif details["type"] == "text":
|
||||||
|
options[name] = details["widget"].get()
|
||||||
|
elif details["type"] == "checkbox":
|
||||||
|
options[name] = details["variable"].get()
|
||||||
|
|
||||||
|
self.apply_callback(options)
|
||||||
|
self.destroy()
|
||||||
Binary file not shown.
@@ -1,69 +1,150 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
from tkinter import filedialog, messagebox
|
from tkinter import filedialog, messagebox
|
||||||
from utils.image_processing import resize_image
|
from utils.image_processing import ImageProcessor
|
||||||
|
|
||||||
selected_directory = ""
|
|
||||||
|
|
||||||
def browse_directory():
|
class FileProcessor:
|
||||||
global selected_directory
|
"""
|
||||||
selected_directory = filedialog.askdirectory()
|
Class to handle file processing operations.
|
||||||
return selected_directory
|
"""
|
||||||
|
|
||||||
def get_first_image_path():
|
def __init__(self):
|
||||||
if not selected_directory:
|
self.selected_directory = ""
|
||||||
|
|
||||||
|
def browse_directory(self):
|
||||||
|
"""
|
||||||
|
Open a dialog to select a directory.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The selected directory path.
|
||||||
|
"""
|
||||||
|
self.selected_directory = filedialog.askdirectory()
|
||||||
|
return self.selected_directory
|
||||||
|
|
||||||
|
def get_first_image_path(self):
|
||||||
|
"""
|
||||||
|
Get the path of the first image in the selected directory.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The path to the first image, or None if no images found.
|
||||||
|
"""
|
||||||
|
if not self.selected_directory:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(self.selected_directory):
|
||||||
|
if "ProcessedImages" in dirs:
|
||||||
|
dirs.remove("ProcessedImages")
|
||||||
|
for file in files:
|
||||||
|
if file.lower().endswith(
|
||||||
|
(".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif")
|
||||||
|
):
|
||||||
|
return os.path.join(root, file)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for root, dirs, files in os.walk(selected_directory):
|
def log_message(self, message, log=None):
|
||||||
if 'ProcessedImages' in dirs:
|
"""
|
||||||
dirs.remove('ProcessedImages')
|
Log a message or print it if no log function is provided.
|
||||||
for file in files:
|
|
||||||
if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif')):
|
|
||||||
return os.path.join(root, file)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def process_directory_with_logging(selected_directory: str, additional_name: str = '', is_checked: bool = False, log = None, update_previews = None):
|
Args:
|
||||||
print(f"is_checked: {is_checked}")
|
message (str): The message to log or print.
|
||||||
if not selected_directory:
|
log (function, optional): The log function to use. Defaults to None.
|
||||||
messagebox.showwarning("No Directory", "Please select a directory.")
|
"""
|
||||||
return
|
|
||||||
if log:
|
|
||||||
log(f"Processing started for directory: {selected_directory}")
|
|
||||||
output_directory = os.path.join(selected_directory, 'ProcessedImages')
|
|
||||||
if os.path.exists(output_directory):
|
|
||||||
shutil.rmtree(output_directory)
|
|
||||||
if log:
|
if log:
|
||||||
log("Existing directory removed.")
|
log(message)
|
||||||
os.makedirs(output_directory, exist_ok=True)
|
else:
|
||||||
if log:
|
print(message)
|
||||||
log(f"Output directory created: {output_directory}")
|
|
||||||
|
|
||||||
image_paths = []
|
def process_directory_with_logging(self, options):
|
||||||
for root, dirs, files in os.walk(selected_directory):
|
"""
|
||||||
if 'ProcessedImages' in dirs:
|
Process images in the selected directory with logging.
|
||||||
dirs.remove('ProcessedImages')
|
|
||||||
for file in files:
|
|
||||||
if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif')):
|
|
||||||
file_path = os.path.join(root, file)
|
|
||||||
image_paths.append(file_path)
|
|
||||||
if log:
|
|
||||||
log(f"Found: {file_path}")
|
|
||||||
if log:
|
|
||||||
log(f"Total images found: {len(image_paths)}")
|
|
||||||
|
|
||||||
for file_path in image_paths:
|
Args:
|
||||||
output_path = os.path.join(output_directory, os.path.relpath(file_path, selected_directory))
|
options (dict): Processing options.
|
||||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
"""
|
||||||
resize_image(file_path, output_path, additional_name)
|
if not self.selected_directory:
|
||||||
|
messagebox.showwarning(
|
||||||
|
"No Directory", "Please select a directory.")
|
||||||
|
return
|
||||||
|
log = options.get("log_message", None)
|
||||||
|
self.log_message(
|
||||||
|
f"Processing started for directory: {self.selected_directory}", log
|
||||||
|
)
|
||||||
|
|
||||||
if os.path.exists(file_path) and is_checked:
|
output_directory = self.create_output_directory(log)
|
||||||
if log:
|
image_paths = self.collect_image_paths(log)
|
||||||
log(f"removing: {file_path}")
|
|
||||||
os.remove(file_path)
|
|
||||||
if log:
|
|
||||||
log(f"Processed: {file_path}")
|
|
||||||
|
|
||||||
messagebox.showinfo("Process Complete", "Image processing is complete.")
|
self.process_images(image_paths, output_directory, options, log)
|
||||||
if log:
|
|
||||||
log("Processing complete.")
|
messagebox.showinfo("Process Complete",
|
||||||
|
"Image processing is complete.")
|
||||||
|
self.log_message("Processing complete.", log)
|
||||||
|
|
||||||
|
def create_output_directory(self, log):
|
||||||
|
"""
|
||||||
|
Create the output directory for processed images.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log (function): The log function to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The path to the output directory.
|
||||||
|
"""
|
||||||
|
output_directory = os.path.join(
|
||||||
|
self.selected_directory, "ProcessedImages")
|
||||||
|
if os.path.exists(output_directory):
|
||||||
|
shutil.rmtree(output_directory)
|
||||||
|
self.log_message("Existing directory removed.", log)
|
||||||
|
os.makedirs(output_directory, exist_ok=True)
|
||||||
|
self.log_message(f"Output directory created: {output_directory}", log)
|
||||||
|
return output_directory
|
||||||
|
|
||||||
|
def collect_image_paths(self, log):
|
||||||
|
"""
|
||||||
|
Collect all image paths in the selected directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log (function): The log function to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: A list of image paths.
|
||||||
|
"""
|
||||||
|
image_paths = []
|
||||||
|
for root, dirs, files in os.walk(self.selected_directory):
|
||||||
|
if "ProcessedImages" in dirs:
|
||||||
|
dirs.remove("ProcessedImages")
|
||||||
|
for file in files:
|
||||||
|
if file.lower().endswith(
|
||||||
|
(".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif")
|
||||||
|
):
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
image_paths.append(file_path)
|
||||||
|
self.log_message(f"Found: {file_path}", log)
|
||||||
|
self.log_message(f"Total images found: {len(image_paths)}", log)
|
||||||
|
return image_paths
|
||||||
|
|
||||||
|
def process_images(self, image_paths, output_directory, options, log):
|
||||||
|
"""
|
||||||
|
Process each image by resizing and saving it to the output directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_paths (list): A list of image paths.
|
||||||
|
output_directory (str): The path to the output directory.
|
||||||
|
options (dict): Processing options.
|
||||||
|
log (function): The log function to use.
|
||||||
|
"""
|
||||||
|
image = ImageProcessor()
|
||||||
|
for file_path in image_paths:
|
||||||
|
output_path = os.path.join(
|
||||||
|
output_directory, os.path.relpath(
|
||||||
|
file_path, self.selected_directory)
|
||||||
|
)
|
||||||
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
image.resize_image(
|
||||||
|
file_path, output_path, options.get("additional_name", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
if os.path.exists(file_path) and options.get("is_checked", False):
|
||||||
|
self.log_message(f"Removing: {file_path}", log)
|
||||||
|
os.remove(file_path)
|
||||||
|
self.log_message(f"Processed: {file_path}", log)
|
||||||
|
|||||||
@@ -2,33 +2,70 @@ import os
|
|||||||
from wand.image import Image
|
from wand.image import Image
|
||||||
from wand.color import Color
|
from wand.color import Color
|
||||||
|
|
||||||
def set_canvas_size(width, height):
|
|
||||||
global canvas_width, canvas_height
|
|
||||||
canvas_width = int(width)
|
|
||||||
canvas_height = int(height)
|
|
||||||
|
|
||||||
def resize_image(image_path, output_path, additional_name):
|
class ImageProcessor:
|
||||||
|
def __init__(
|
||||||
|
self, canvas_width=900, canvas_height=900, background_color="transparent"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize the ImageProcessor with default values.
|
||||||
|
"""
|
||||||
|
self.canvas_width = canvas_width
|
||||||
|
self.canvas_height = canvas_height
|
||||||
|
self.background_color = background_color
|
||||||
|
|
||||||
# Normalize the paths to ensure consistency
|
def set_canvas_size(self, width, height):
|
||||||
image_path = os.path.normpath(image_path)
|
"""
|
||||||
output_path = os.path.normpath(output_path)
|
Set the canvas size.
|
||||||
|
"""
|
||||||
|
self.canvas_width = int(width)
|
||||||
|
self.canvas_height = int(height)
|
||||||
|
|
||||||
with Image(filename=image_path) as img:
|
def set_background_color(self, color):
|
||||||
img.transform(resize=f'{canvas_width}x{canvas_height}>')
|
"""
|
||||||
|
Set the background color.
|
||||||
x_offset = int((canvas_width - img.width) / 2)
|
"""
|
||||||
y_offset = int((canvas_height - img.height) / 2)
|
self.background_color = Color(color)
|
||||||
|
|
||||||
with Image(width=canvas_width, height=canvas_height, background=Color('transparent')) as canvas:
|
def resize_image(self, image_path, output_path, additional_name=None):
|
||||||
canvas.composite(img, left=x_offset, top=y_offset)
|
"""
|
||||||
# Create a new filename
|
Resize and process the image.
|
||||||
new_filename = os.path.splitext(os.path.basename(output_path))[0]
|
"""
|
||||||
if additional_name:
|
# Normalize the paths to ensure consistency
|
||||||
new_filename += " - " + additional_name.strip()
|
image_path = os.path.normpath(image_path)
|
||||||
new_filename += os.path.splitext(output_path)[1]
|
output_path = os.path.normpath(output_path)
|
||||||
# Construct the final output path
|
print(image_path)
|
||||||
final_output_path = os.path.join(os.path.dirname(output_path), new_filename)
|
print(output_path)
|
||||||
# Save the image to the final output path
|
with Image(filename=image_path) as img:
|
||||||
canvas.save(filename=final_output_path)
|
img.transform(resize=f"{self.canvas_width}x{self.canvas_height}>")
|
||||||
print(f"Saved to: {final_output_path}")
|
|
||||||
set_canvas_size(900, 900)
|
x_offset = int((self.canvas_width - img.width) / 2)
|
||||||
|
y_offset = int((self.canvas_height - img.height) / 2)
|
||||||
|
|
||||||
|
with Image(
|
||||||
|
width=self.canvas_width,
|
||||||
|
height=self.canvas_height,
|
||||||
|
background=self.background_color,
|
||||||
|
) as canvas:
|
||||||
|
canvas.composite(img, left=x_offset, top=y_offset)
|
||||||
|
# Create a new filename
|
||||||
|
new_filename = os.path.splitext(
|
||||||
|
os.path.basename(output_path))[0]
|
||||||
|
if additional_name:
|
||||||
|
new_filename += " - " + additional_name.strip()
|
||||||
|
new_filename += os.path.splitext(output_path)[1]
|
||||||
|
# Construct the final output path
|
||||||
|
final_output_path = os.path.join(
|
||||||
|
os.path.dirname(output_path), new_filename
|
||||||
|
)
|
||||||
|
# Save the image to the final output path
|
||||||
|
canvas.save(filename=final_output_path)
|
||||||
|
print(f"Saved to: {final_output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
processor = ImageProcessor()
|
||||||
|
processor.set_canvas_size(900, 900)
|
||||||
|
processor.set_background_color("white")
|
||||||
|
processor.resize_image("input_image.jpg", "output_image.jpg", "example")
|
||||||
|
|||||||
Reference in New Issue
Block a user