From 1183327e7e09cc95d82feaddc1ffae7fda9a3f64 Mon Sep 17 00:00:00 2001 From: QuBerto Date: Sat, 13 Jul 2024 16:23:11 +0200 Subject: [PATCH] init image --- .gitignore | 6 ++ api/woocommerce.py | 202 ++++++++++++++++++++++++++++++++++++ decrypt_config.py | 25 +++++ encrypt_config.py | 22 ++++ image_processor.spec | 38 +++++++ main.py | 47 +++++++++ main.spec | 38 +++++++ readme.md | 135 ++++++++++++++++++++++++ ui/local_processing_tab.py | 204 +++++++++++++++++++++++++++++++++++++ ui/log_window.py | 16 +++ ui/settings_tab.py | 58 +++++++++++ utils/file_operations.py | 71 +++++++++++++ utils/image_processing.py | 28 +++++ 13 files changed, 890 insertions(+) create mode 100644 .gitignore create mode 100644 api/woocommerce.py create mode 100644 decrypt_config.py create mode 100644 encrypt_config.py create mode 100644 image_processor.spec create mode 100644 main.py create mode 100644 main.spec create mode 100644 readme.md create mode 100644 ui/local_processing_tab.py create mode 100644 ui/log_window.py create mode 100644 ui/settings_tab.py create mode 100644 utils/file_operations.py create mode 100644 utils/image_processing.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c9a365 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/config.enc +/__pycache__ +*/__pycache__ +/build +/dist +/temp \ No newline at end of file diff --git a/api/woocommerce.py b/api/woocommerce.py new file mode 100644 index 0000000..9f93de2 --- /dev/null +++ b/api/woocommerce.py @@ -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.") diff --git a/decrypt_config.py b/decrypt_config.py new file mode 100644 index 0000000..97e858c --- /dev/null +++ b/decrypt_config.py @@ -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}") diff --git a/encrypt_config.py b/encrypt_config.py new file mode 100644 index 0000000..53a1650 --- /dev/null +++ b/encrypt_config.py @@ -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) diff --git a/image_processor.spec b/image_processor.spec new file mode 100644 index 0000000..24882f6 --- /dev/null +++ b/image_processor.spec @@ -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, +) diff --git a/main.py b/main.py new file mode 100644 index 0000000..67f6564 --- /dev/null +++ b/main.py @@ -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() diff --git a/main.spec b/main.spec new file mode 100644 index 0000000..2ba8dd9 --- /dev/null +++ b/main.spec @@ -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, +) diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c288775 --- /dev/null +++ b/readme.md @@ -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! \ No newline at end of file diff --git a/ui/local_processing_tab.py b/ui/local_processing_tab.py new file mode 100644 index 0000000..771a87c --- /dev/null +++ b/ui/local_processing_tab.py @@ -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("<>", 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) \ No newline at end of file diff --git a/ui/log_window.py b/ui/log_window.py new file mode 100644 index 0000000..745990b --- /dev/null +++ b/ui/log_window.py @@ -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') diff --git a/ui/settings_tab.py b/ui/settings_tab.py new file mode 100644 index 0000000..939bad1 --- /dev/null +++ b/ui/settings_tab.py @@ -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) diff --git a/utils/file_operations.py b/utils/file_operations.py new file mode 100644 index 0000000..ba3e455 --- /dev/null +++ b/utils/file_operations.py @@ -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.") diff --git a/utils/image_processing.py b/utils/image_processing.py new file mode 100644 index 0000000..a38a1d2 --- /dev/null +++ b/utils/image_processing.py @@ -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)