Enable AVIF support in image processing and update dependencies

This commit is contained in:
SitiWeb
2026-01-23 16:41:28 +01:00
parent 67c1178c40
commit 551948d828
7 changed files with 139 additions and 3 deletions

55
.github/workflows/build-release.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Build & Release (Windows)
on:
push:
branches: [ main ]
tags:
- 'v*'
workflow_dispatch: {}
permissions:
contents: write
jobs:
build-windows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
shell: pwsh
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install pyinstaller
- name: Build (PyInstaller spec)
shell: pwsh
run: |
pyinstaller --noconfirm --clean main.spec
- name: Upload build artifact (push/dispatch)
if: startsWith(github.ref, 'refs/heads/') || github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: image_processor-windows
path: |
dist/**
- name: Create GitHub Release (tags)
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
generate_release_notes: true
files: |
dist/**

View File

@@ -6,6 +6,12 @@ from ui.options_window import OptionsWindow
from config.encrypt_config import ConfigEncryptor
from api.woocommerce_api import get_first_image
from PIL import Image, ImageTk
# Enable AVIF support for Pillow previews when the optional plugin is installed.
try:
import pillow_avif # type: ignore
except Exception:
pillow_avif = None
from pprint import pformat
from api.woocommerce_api import process_product_images, process_all_products, search_product, get_first_image_path, get_product
import customtkinter as ctk

View File

@@ -6,7 +6,7 @@ a = Analysis(
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hiddenimports=['pillow_avif'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],

View File

@@ -14,7 +14,7 @@ a = Analysis(
pathex=[],
binaries=[],
datas=image_files,
hiddenimports=[],
hiddenimports=['pillow_avif'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],

View File

@@ -12,6 +12,12 @@ Prerequisites
pip install pillow
Optional (AVIF input): Install the Pillow AVIF plugin so previews and conversions can open `.avif` files:
sh
pip install pillow-avif-plugin
Additional Libraries: Ensure you have any additional libraries your utility functions (file_operations, image_processing) depend on.
Application Setup

15
requirements.txt Normal file
View File

@@ -0,0 +1,15 @@
certifi==2026.1.4
cffi==2.0.0
charset-normalizer==3.4.4
cryptography==46.0.3
customtkinter==5.2.2
darkdetect==0.8.0
idna==3.11
packaging==26.0
pillow==12.1.0
pillow-avif-plugin==1.5.5
pycparser==3.0
requests==2.32.5
urllib3==2.6.3
Wand==0.6.13
WooCommerce==3.0.0

View File

@@ -1,7 +1,13 @@
import os
import tempfile
from wand.image import Image
from wand.color import Color
try:
from PIL import Image as PILImage
except Exception: # Pillow is also used elsewhere; keep this optional here.
PILImage = None
class ImageProcessor:
def __init__(self, canvas_width=900, canvas_height=900, background_color="transparent", image_size="fit"):
"""
@@ -48,7 +54,20 @@ class ImageProcessor:
image_path = os.path.normpath(image_path)
output_path = os.path.normpath(output_path)
with Image(filename=image_path) as img:
converted_tmp_path = None
img = None
try:
try:
img = Image(filename=image_path)
except Exception as e:
# Wand/ImageMagick AVIF support depends on the installed ImageMagick build.
# If it can't read AVIF, fall back to Pillow (+ pillow-avif-plugin) and convert to PNG.
if os.path.splitext(image_path)[1].lower() != ".avif":
raise
converted_tmp_path = self._convert_avif_to_temp_png(image_path, log)
img = Image(filename=converted_tmp_path)
self.log_message(f"Opened AVIF via Pillow fallback: {image_path}", log)
self.log_message(f"Original image size: {img.width}x{img.height}", log)
if self.image_size == "contain":
self._contain(img)
@@ -71,6 +90,41 @@ class ImageProcessor:
# Save the image to the final output path
canvas.save(filename=final_output_path)
self.log_message(f"Saved to: {final_output_path}", log)
finally:
try:
if img is not None:
img.close()
finally:
if converted_tmp_path and os.path.exists(converted_tmp_path):
try:
os.remove(converted_tmp_path)
except Exception:
pass
def _convert_avif_to_temp_png(self, image_path, log=None):
if PILImage is None:
raise RuntimeError(
"AVIF input requires Pillow. Install Pillow + pillow-avif-plugin to enable AVIF decoding."
)
try:
import pillow_avif # type: ignore
except Exception:
raise RuntimeError(
"AVIF input requires the optional dependency 'pillow-avif-plugin'. "
"Install it with: pip install pillow-avif-plugin"
)
with PILImage.open(image_path) as im:
# Preserve alpha if present; Wand will composite onto the selected background.
if im.mode not in ("RGB", "RGBA"):
im = im.convert("RGBA")
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
tmp.close()
im.save(tmp.name, format="PNG")
self.log_message(f"Converted AVIF to temporary PNG: {tmp.name}", log)
return tmp.name
def _cover(self, img:Image):