init image
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/config.enc
|
||||||
|
/__pycache__
|
||||||
|
*/__pycache__
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
/temp
|
||||||
202
api/woocommerce.py
Normal file
202
api/woocommerce.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
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.")
|
||||||
25
decrypt_config.py
Normal file
25
decrypt_config.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from cryptography.fernet import Fernet
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Hardcoded key (replace with your generated key)
|
||||||
|
key = b'u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI='
|
||||||
|
|
||||||
|
def decrypt_config(key):
|
||||||
|
if not os.path.exists("config.enc"):
|
||||||
|
raise FileNotFoundError("The encrypted configuration file 'config.enc' does not exist.")
|
||||||
|
|
||||||
|
fernet = Fernet(key)
|
||||||
|
with open("config.enc", "rb") as encrypted_file:
|
||||||
|
encrypted = encrypted_file.read()
|
||||||
|
decrypted = fernet.decrypt(encrypted).decode()
|
||||||
|
return json.loads(decrypted)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
config = decrypt_config(key)
|
||||||
|
print(config) # Use the decrypted config
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
print(e)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
22
encrypt_config.py
Normal file
22
encrypt_config.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from cryptography.fernet import Fernet
|
||||||
|
|
||||||
|
# Generate a key and print it
|
||||||
|
key = Fernet.generate_key()
|
||||||
|
print(f"Encryption key: {key.decode()}") # Save this key securely
|
||||||
|
|
||||||
|
# Function to encrypt the configuration data
|
||||||
|
def encrypt_config(data, key):
|
||||||
|
fernet = Fernet(key)
|
||||||
|
encrypted = fernet.encrypt(data.encode())
|
||||||
|
with open("config.enc", "wb") as encrypted_file:
|
||||||
|
encrypted_file.write(encrypted)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
config_data = """{
|
||||||
|
"url": "https://yourstore.com",
|
||||||
|
"consumer_key": "ck_yourconsumerkey",
|
||||||
|
"consumer_secret": "cs_yoursecret",
|
||||||
|
"username": "yourusername",
|
||||||
|
"password": "yourpassword"
|
||||||
|
}"""
|
||||||
|
encrypt_config(config_data, key)
|
||||||
38
image_processor.spec
Normal file
38
image_processor.spec
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['main.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='image_processor',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=False,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
47
main.py
Normal file
47
main.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from ui.local_processing_tab import create_tab_local
|
||||||
|
from ui.settings_tab import create_tab_settings
|
||||||
|
from ui.log_window import LogWindow
|
||||||
|
|
||||||
|
|
||||||
|
from decrypt_config import decrypt_config, key
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = decrypt_config(key)
|
||||||
|
wc_url = config['url']
|
||||||
|
wc_consumer_key = config['consumer_key']
|
||||||
|
wc_consumer_secret = config['consumer_secret']
|
||||||
|
wp_username = config['username']
|
||||||
|
wp_password = config['password']
|
||||||
|
|
||||||
|
# Now use these variables to create your WooCommerce API instance
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
print(e)
|
||||||
|
# Handle the missing file case, e.g., by prompting the user to create it
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
# Handle other potential errors
|
||||||
|
log_window = None
|
||||||
|
|
||||||
|
def open_log_window():
|
||||||
|
global log_window
|
||||||
|
if log_window is None or not log_window.winfo_exists():
|
||||||
|
log_window = LogWindow(window)
|
||||||
|
else:
|
||||||
|
log_window.lift()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
window = tk.Tk()
|
||||||
|
window.title("Image Processor")
|
||||||
|
window.geometry("700x400")
|
||||||
|
|
||||||
|
tab_parent = ttk.Notebook(window)
|
||||||
|
|
||||||
|
create_tab_local(tab_parent, "Local Processing", open_log_window)
|
||||||
|
create_tab_settings(tab_parent, "Settings")
|
||||||
|
|
||||||
|
tab_parent.pack(expand=True, fill='both')
|
||||||
|
|
||||||
|
window.mainloop()
|
||||||
38
main.spec
Normal file
38
main.spec
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['main.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='main',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
135
readme.md
Normal file
135
readme.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
Image Processing Application
|
||||||
|
|
||||||
|
This application is a graphical user interface (GUI) tool designed for processing images either from a local directory or from a WooCommerce product catalog. It supports resizing images, renaming them based on customizable templates, and uploading them to a WooCommerce site.
|
||||||
|
Features
|
||||||
|
|
||||||
|
Resize images to specified dimensions.
|
||||||
|
Rename images using customizable templates with placeholders for various attributes.
|
||||||
|
Upload processed images to WooCommerce.
|
||||||
|
Process images from a local directory or WooCommerce products.
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
|
||||||
|
Python 3.10 or later
|
||||||
|
|
||||||
|
Required Python Packages
|
||||||
|
|
||||||
|
You can install the required packages using pip:
|
||||||
|
|
||||||
|
sh
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
ImageMagick
|
||||||
|
|
||||||
|
ImageMagick is required for image processing. You can download and install it from ImageMagick's official website.
|
||||||
|
|
||||||
|
WooCommerce API Credentials
|
||||||
|
|
||||||
|
You need to have WooCommerce API credentials to interact with the WooCommerce site. These include the URL, consumer key, consumer secret, username, and password.
|
||||||
|
|
||||||
|
Setting Up
|
||||||
|
|
||||||
|
Clone the Repository
|
||||||
|
|
||||||
|
Clone this repository to your local machine:
|
||||||
|
|
||||||
|
sh
|
||||||
|
|
||||||
|
git clone https://github.com/your-username/image-processing-app.git
|
||||||
|
cd image-processing-app
|
||||||
|
|
||||||
|
Install Dependencies
|
||||||
|
|
||||||
|
Install the required Python packages:
|
||||||
|
|
||||||
|
sh
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
Configure WooCommerce API Credentials
|
||||||
|
|
||||||
|
Run the application and navigate to the Settings tab to enter and save your WooCommerce API credentials.
|
||||||
|
|
||||||
|
Running the Application
|
||||||
|
|
||||||
|
Run the main application script:
|
||||||
|
|
||||||
|
sh
|
||||||
|
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
Creating an Executable
|
||||||
|
|
||||||
|
To create a standalone executable from this project, you can use PyInstaller. Follow the steps below:
|
||||||
|
|
||||||
|
Install PyInstaller
|
||||||
|
|
||||||
|
You can install PyInstaller using pip:
|
||||||
|
|
||||||
|
sh
|
||||||
|
|
||||||
|
pip install pyinstaller
|
||||||
|
|
||||||
|
Generate the Executable
|
||||||
|
|
||||||
|
Navigate to the project directory and run PyInstaller:
|
||||||
|
|
||||||
|
sh
|
||||||
|
|
||||||
|
pyinstaller --onefile --noconsole main.py
|
||||||
|
|
||||||
|
Find the Executable
|
||||||
|
|
||||||
|
After PyInstaller finishes, you can find the executable in the dist directory.
|
||||||
|
|
||||||
|
File Structure
|
||||||
|
|
||||||
|
main.py: Entry point for the application.
|
||||||
|
api/: Contains the woocommerce.py file for interacting with WooCommerce API.
|
||||||
|
utils/: Contains utility scripts for file operations and image processing.
|
||||||
|
ui/: Contains the UI components for the application.
|
||||||
|
config.enc: Encrypted file storing WooCommerce API credentials.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
|
||||||
|
Create a requirements.txt file with the following content:
|
||||||
|
|
||||||
|
tk
|
||||||
|
Pillow
|
||||||
|
requests
|
||||||
|
woocommerce
|
||||||
|
cryptography
|
||||||
|
pyinstaller
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
Select Source Type
|
||||||
|
Choose between local directory and WooCommerce product.
|
||||||
|
|
||||||
|
Set Canvas Size
|
||||||
|
Specify the dimensions to resize the images.
|
||||||
|
|
||||||
|
Filename Template
|
||||||
|
Define a template for renaming the images using placeholders like {name}, {sku}, {width}, {height}.
|
||||||
|
|
||||||
|
Start Processing
|
||||||
|
Click the "Start Processing" button to process the images. The log will appear in a separate window.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
|
||||||
|
Ensure ImageMagick is correctly installed and added to your system's PATH.
|
||||||
|
The application uses the cryptography library to encrypt WooCommerce API credentials for security.
|
||||||
|
|
||||||
|
Support
|
||||||
|
|
||||||
|
If you encounter any issues or have questions, feel free to open an issue in the repository or contact the maintainer.
|
||||||
|
License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License.
|
||||||
|
Acknowledgements
|
||||||
|
|
||||||
|
ImageMagick
|
||||||
|
WooCommerce
|
||||||
|
|
||||||
|
With this information, you should be able to set up, run, and create an executable for the Image Processing Application. Enjoy processing your images with ease!
|
||||||
204
ui/local_processing_tab.py
Normal file
204
ui/local_processing_tab.py
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from tkinter.scrolledtext import ScrolledText
|
||||||
|
from tkinter import filedialog
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
from utils.file_operations import browse_directory, process_directory_with_logging, get_first_image_path
|
||||||
|
from utils.image_processing import set_canvas_size, resize_image
|
||||||
|
from api.woocommerce import process_product_images, process_all_products
|
||||||
|
|
||||||
|
|
||||||
|
canvas_width = 900
|
||||||
|
canvas_height = 900
|
||||||
|
|
||||||
|
|
||||||
|
def create_tab_local(tab_parent, text, log):
|
||||||
|
tab = ttk.Frame(tab_parent)
|
||||||
|
tab_parent.add(tab, text=text)
|
||||||
|
|
||||||
|
log_window = None
|
||||||
|
|
||||||
|
def create_log_window():
|
||||||
|
nonlocal log_window
|
||||||
|
if log_window and tk.Toplevel.winfo_exists(log_window):
|
||||||
|
return
|
||||||
|
log_window = tk.Toplevel()
|
||||||
|
log_window.title("Processing Log")
|
||||||
|
log_text = ScrolledText(log_window, state='disabled', wrap='word', height=20, width=80)
|
||||||
|
log_text.pack(expand=True, fill='both')
|
||||||
|
|
||||||
|
def log_message(message):
|
||||||
|
log_text.config(state='normal')
|
||||||
|
log_text.insert(tk.END, message + "\n")
|
||||||
|
log_text.see(tk.END)
|
||||||
|
log_text.config(state='disabled')
|
||||||
|
log_text.update_idletasks() # Ensure the GUI updates
|
||||||
|
return log_message
|
||||||
|
|
||||||
|
# Source selection section
|
||||||
|
source_label = tk.Label(tab, text="Source Type:")
|
||||||
|
source_label.grid(row=0, column=0, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
source_type = tk.StringVar(value="local")
|
||||||
|
source_dropdown = ttk.Combobox(tab, textvariable=source_type, values=["local", "product", "all_products"], state="readonly")
|
||||||
|
source_dropdown.grid(row=1, column=0, padx=5, pady=5, sticky='w')
|
||||||
|
source_dropdown.bind("<<ComboboxSelected>>", lambda e: update_source_fields())
|
||||||
|
|
||||||
|
# Local Directory selection section
|
||||||
|
def update_directory():
|
||||||
|
directory = browse_directory()
|
||||||
|
if directory:
|
||||||
|
# Show only the last directory name with ../
|
||||||
|
truncated_directory = f"../{os.path.basename(directory)}"
|
||||||
|
button_browse.config(text=truncated_directory)
|
||||||
|
update_previews()
|
||||||
|
|
||||||
|
button_browse = tk.Button(tab, text="Browse", command=update_directory)
|
||||||
|
button_browse.grid(row=2, column=0, padx=5, pady=5, sticky='nw')
|
||||||
|
|
||||||
|
# WooCommerce Product ID section
|
||||||
|
product_id_label = tk.Label(tab, text="Product ID:")
|
||||||
|
product_id_label.grid(row=2, column=0, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
product_id_entry = tk.Entry(tab)
|
||||||
|
product_id_entry.grid(row=3, column=0, padx=5, pady=5, sticky='w')
|
||||||
|
# SKU section
|
||||||
|
additional_name_label = tk.Label(tab, text="Add suffix:")
|
||||||
|
additional_name_label.grid(row=3, column=1, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
additional_name_entry = tk.Entry(tab)
|
||||||
|
additional_name_entry.grid(row=3, column=2, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
# Template section
|
||||||
|
template_label = tk.Label(tab, text="Filename Template:")
|
||||||
|
template_label.grid(row=3, column=1, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
template_entry = tk.Entry(tab)
|
||||||
|
template_entry.insert(0, "{slug}_{sku}_{width}x{height}")
|
||||||
|
template_entry.grid(row=3, column=2, padx=5, columnspan=2, pady=5, sticky='w')
|
||||||
|
|
||||||
|
|
||||||
|
def update_source_fields():
|
||||||
|
print(source_type.get())
|
||||||
|
if source_type.get() == "local":
|
||||||
|
button_browse.grid()
|
||||||
|
product_id_label.grid_remove()
|
||||||
|
product_id_entry.grid_remove()
|
||||||
|
additional_name_label.grid()
|
||||||
|
additional_name_entry.grid()
|
||||||
|
template_entry.grid_remove()
|
||||||
|
template_label.grid_remove()
|
||||||
|
elif source_type.get() == "product":
|
||||||
|
button_browse.grid_remove()
|
||||||
|
product_id_label.grid()
|
||||||
|
product_id_entry.grid()
|
||||||
|
additional_name_label.grid_remove()
|
||||||
|
additional_name_entry.grid_remove()
|
||||||
|
else:
|
||||||
|
button_browse.grid_remove()
|
||||||
|
product_id_label.grid_remove()
|
||||||
|
product_id_entry.grid_remove()
|
||||||
|
product_id_label.grid()
|
||||||
|
product_id_entry.grid()
|
||||||
|
additional_name_label.grid_remove()
|
||||||
|
additional_name_entry.grid_remove()
|
||||||
|
|
||||||
|
update_source_fields() # Initial call to set correct fields
|
||||||
|
|
||||||
|
# Canvas size section
|
||||||
|
width_label = tk.Label(tab, text="Canvas Width:")
|
||||||
|
width_label.grid(row=0, column=1, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
width_entry = tk.Entry(tab)
|
||||||
|
width_entry.insert(0, "900")
|
||||||
|
width_entry.grid(row=0, column=2, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
height_label = tk.Label(tab, text="Canvas Height:")
|
||||||
|
height_label.grid(row=1, column=1, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
height_entry = tk.Entry(tab)
|
||||||
|
height_entry.insert(0, "900")
|
||||||
|
height_entry.grid(row=1, column=2, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
def apply_canvas_size():
|
||||||
|
global canvas_width, canvas_height
|
||||||
|
canvas_width = int(width_entry.get())
|
||||||
|
canvas_height = int(height_entry.get())
|
||||||
|
set_canvas_size(canvas_width, canvas_height)
|
||||||
|
update_previews()
|
||||||
|
|
||||||
|
button_set_size = tk.Button(tab, text="Save Size", command=apply_canvas_size)
|
||||||
|
button_set_size.grid(row=2, column=1, columnspan=2, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Checkbox for deleting images
|
||||||
|
checkbox_var = tk.BooleanVar()
|
||||||
|
checkbox = tk.Checkbutton(tab, text="Delete image when done", variable=checkbox_var)
|
||||||
|
checkbox.grid(row=0, column=3, columnspan=2, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
# Start Processing button
|
||||||
|
def start_processing():
|
||||||
|
log_message = None # create_log_window()
|
||||||
|
if source_type.get() == "local":
|
||||||
|
process_directory_with_logging(additional_name_entry.get(), checkbox_var.get(), log_message, update_previews)
|
||||||
|
elif source_type.get() == "product":
|
||||||
|
product_id = product_id_entry.get()
|
||||||
|
process_product_images(product_id, template_entry.get(), canvas_width, canvas_height)
|
||||||
|
else:
|
||||||
|
process_all_products(template_entry.get(), canvas_width, canvas_height)
|
||||||
|
|
||||||
|
button_start = tk.Button(tab, text="Start Processing", command=start_processing)
|
||||||
|
button_start.grid(row=1, column=3, columnspan=2, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
# Image previews
|
||||||
|
before_label = tk.Label(tab, text="Before:")
|
||||||
|
before_label.grid(row=5, column=0, columnspan=2, padx=5, pady=5, sticky='w')
|
||||||
|
before_image_label = tk.Label(tab)
|
||||||
|
before_image_label.grid(row=6, columnspan=2, column=0, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
after_label = tk.Label(tab, text="After:")
|
||||||
|
after_label.grid(row=5, columnspan=2, column=2, padx=5, pady=5, sticky='w')
|
||||||
|
after_image_label = tk.Label(tab)
|
||||||
|
after_image_label.grid(row=6, columnspan=2, column=2, padx=5, pady=5, sticky='w')
|
||||||
|
|
||||||
|
def update_previews(before_path=None, after_path=None):
|
||||||
|
if before_path and after_path:
|
||||||
|
before_img = Image.open(before_path)
|
||||||
|
before_img.thumbnail((150, 150))
|
||||||
|
before_photo = ImageTk.PhotoImage(before_img)
|
||||||
|
before_image_label.config(image=before_photo)
|
||||||
|
before_image_label.image = before_photo
|
||||||
|
|
||||||
|
after_img = Image.open(after_path)
|
||||||
|
after_img.thumbnail((150, 150))
|
||||||
|
after_photo = ImageTk.PhotoImage(after_img)
|
||||||
|
after_image_label.config(image=after_photo)
|
||||||
|
after_image_label.image = after_photo
|
||||||
|
else:
|
||||||
|
first_image_path = get_first_image_path()
|
||||||
|
if first_image_path:
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as temp_file:
|
||||||
|
output_path = temp_file.name
|
||||||
|
resize_image(first_image_path, output_path, additional_name_entry.get())
|
||||||
|
before_img = Image.open(first_image_path)
|
||||||
|
before_img.thumbnail((150, 150))
|
||||||
|
before_photo = ImageTk.PhotoImage(before_img)
|
||||||
|
before_image_label.config(image=before_photo)
|
||||||
|
before_image_label.image = before_photo
|
||||||
|
|
||||||
|
after_img = Image.open(output_path)
|
||||||
|
after_img.thumbnail((150, 150))
|
||||||
|
after_photo = ImageTk.PhotoImage(after_img)
|
||||||
|
after_image_label.config(image=after_photo)
|
||||||
|
after_image_label.image = after_photo
|
||||||
|
|
||||||
|
# Configure column weights to allow the log_text to expand
|
||||||
|
tab.columnconfigure(0, weight=1)
|
||||||
|
tab.columnconfigure(1, weight=1)
|
||||||
|
tab.columnconfigure(2, weight=1)
|
||||||
|
tab.columnconfigure(3, weight=1)
|
||||||
|
tab.rowconfigure(6, weight=1)
|
||||||
16
ui/log_window.py
Normal file
16
ui/log_window.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter.scrolledtext import ScrolledText
|
||||||
|
|
||||||
|
class LogWindow(tk.Toplevel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.title("Processing Log")
|
||||||
|
self.geometry("600x400")
|
||||||
|
self.log_text = ScrolledText(self, state='disabled', wrap='word')
|
||||||
|
self.log_text.pack(expand=True, fill='both')
|
||||||
|
|
||||||
|
def log(self, message):
|
||||||
|
self.log_text.config(state='normal')
|
||||||
|
self.log_text.insert(tk.END, message + "\n")
|
||||||
|
self.log_text.see(tk.END)
|
||||||
|
self.log_text.config(state='disabled')
|
||||||
58
ui/settings_tab.py
Normal file
58
ui/settings_tab.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from api.woocommerce import save_credentials, load_credentials
|
||||||
|
|
||||||
|
def create_tab_settings(tab_parent, text):
|
||||||
|
tab = ttk.Frame(tab_parent)
|
||||||
|
tab_parent.add(tab, text=text)
|
||||||
|
|
||||||
|
credentials = load_credentials()
|
||||||
|
url_value = credentials['url'] if credentials else ''
|
||||||
|
consumer_key_value = credentials['consumer_key'] if credentials else ''
|
||||||
|
consumer_secret_value = credentials['consumer_secret'] if credentials else ''
|
||||||
|
username_value = credentials['username'] if credentials else ''
|
||||||
|
password_value = credentials['password'] if credentials else ''
|
||||||
|
|
||||||
|
url_label = tk.Label(tab, text="WooCommerce URL:")
|
||||||
|
url_label.pack(pady=5)
|
||||||
|
|
||||||
|
url_entry = tk.Entry(tab)
|
||||||
|
url_entry.insert(0, url_value)
|
||||||
|
url_entry.pack()
|
||||||
|
|
||||||
|
consumer_key_label = tk.Label(tab, text="Consumer Key:")
|
||||||
|
consumer_key_label.pack(pady=5)
|
||||||
|
|
||||||
|
consumer_key_entry = tk.Entry(tab)
|
||||||
|
consumer_key_entry.insert(0, consumer_key_value)
|
||||||
|
consumer_key_entry.pack()
|
||||||
|
|
||||||
|
consumer_secret_label = tk.Label(tab, text="Consumer Secret:")
|
||||||
|
consumer_secret_label.pack(pady=5)
|
||||||
|
|
||||||
|
consumer_secret_entry = tk.Entry(tab, show="*")
|
||||||
|
consumer_secret_entry.insert(0, consumer_secret_value)
|
||||||
|
consumer_secret_entry.pack()
|
||||||
|
|
||||||
|
username_label = tk.Label(tab, text="Username:")
|
||||||
|
username_label.pack(pady=5)
|
||||||
|
|
||||||
|
username_entry = tk.Entry(tab)
|
||||||
|
username_entry.insert(0, username_value)
|
||||||
|
username_entry.pack()
|
||||||
|
|
||||||
|
password_label = tk.Label(tab, text="Password:")
|
||||||
|
password_label.pack(pady=5)
|
||||||
|
|
||||||
|
password_entry = tk.Entry(tab, show="*")
|
||||||
|
password_entry.insert(0, password_value)
|
||||||
|
password_entry.pack()
|
||||||
|
|
||||||
|
button_save = tk.Button(tab, text="Save Credentials", command=lambda: save_credentials(
|
||||||
|
url_entry.get(),
|
||||||
|
consumer_key_entry.get(),
|
||||||
|
consumer_secret_entry.get(),
|
||||||
|
username_entry.get(),
|
||||||
|
password_entry.get()
|
||||||
|
))
|
||||||
|
button_save.pack(pady=5)
|
||||||
71
utils/file_operations.py
Normal file
71
utils/file_operations.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from tkinter import filedialog, messagebox
|
||||||
|
from utils.image_processing import resize_image
|
||||||
|
|
||||||
|
selected_directory = ""
|
||||||
|
|
||||||
|
def browse_directory():
|
||||||
|
global selected_directory
|
||||||
|
selected_directory = filedialog.askdirectory()
|
||||||
|
return selected_directory
|
||||||
|
|
||||||
|
def get_first_image_path():
|
||||||
|
if not selected_directory:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(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
|
||||||
|
|
||||||
|
def process_directory_with_logging(additional_name, is_checked, log, update_previews):
|
||||||
|
if not selected_directory:
|
||||||
|
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:
|
||||||
|
log("Existing directory removed.")
|
||||||
|
os.makedirs(output_directory, exist_ok=True)
|
||||||
|
if log:
|
||||||
|
log(f"Output directory created: {output_directory}")
|
||||||
|
|
||||||
|
image_paths = []
|
||||||
|
for root, dirs, files in os.walk(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)
|
||||||
|
if log:
|
||||||
|
log(f"Found: {file_path}")
|
||||||
|
if log:
|
||||||
|
log(f"Total images found: {len(image_paths)}")
|
||||||
|
|
||||||
|
for file_path in image_paths:
|
||||||
|
output_path = os.path.join(output_directory, os.path.relpath(file_path, selected_directory))
|
||||||
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
resize_image(file_path, output_path, additional_name)
|
||||||
|
|
||||||
|
# with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as temp_file:
|
||||||
|
# temp_output_path = temp_file.name
|
||||||
|
# resize_image(file_path, temp_output_path, additional_name)
|
||||||
|
# update_previews(file_path, temp_output_path)
|
||||||
|
|
||||||
|
if os.path.exists(file_path) and is_checked:
|
||||||
|
os.remove(file_path)
|
||||||
|
if log:
|
||||||
|
log(f"Processed: {file_path}")
|
||||||
|
|
||||||
|
messagebox.showinfo("Process Complete", "Image processing is complete.")
|
||||||
|
if log:
|
||||||
|
log("Processing complete.")
|
||||||
28
utils/image_processing.py
Normal file
28
utils/image_processing.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from wand.image import Image
|
||||||
|
from wand.color import Color
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
canvas_width = 900
|
||||||
|
canvas_height = 900
|
||||||
|
|
||||||
|
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):
|
||||||
|
with Image(filename=image_path) as img:
|
||||||
|
img.transform(resize=f'{canvas_width}x{canvas_height}>')
|
||||||
|
|
||||||
|
x_offset = int((canvas_width - img.width) / 2)
|
||||||
|
y_offset = int((canvas_height - img.height) / 2)
|
||||||
|
|
||||||
|
with Image(width=canvas_width, height=canvas_height, background=Color('transparent')) as canvas:
|
||||||
|
canvas.composite(img, left=x_offset, top=y_offset)
|
||||||
|
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]
|
||||||
|
output_path = os.path.join(os.path.dirname(output_path), new_filename)
|
||||||
|
canvas.save(filename=output_path)
|
||||||
Reference in New Issue
Block a user