AIService wraps both Anthropic Claude and OpenAI APIs for product content generation. ImageProcessor handles EXIF geo-tagging with company info and GPS coordinates using piexif. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
import base64
|
|
import io
|
|
import logging
|
|
import struct
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ImageProcessor:
|
|
"""Process product images: EXIF geo-tagging, metadata, optimization."""
|
|
|
|
@staticmethod
|
|
def geo_tag_image(image_b64, company_name, address, phone, lat, lng):
|
|
"""Write EXIF metadata with company info and GPS coordinates.
|
|
|
|
Args:
|
|
image_b64: base64-encoded image data
|
|
company_name: company name for Copyright/Artist tags
|
|
address: company address for ImageDescription
|
|
phone: company phone
|
|
lat: GPS latitude (float)
|
|
lng: GPS longitude (float)
|
|
|
|
Returns:
|
|
base64-encoded image with EXIF data
|
|
"""
|
|
try:
|
|
import piexif
|
|
except ImportError:
|
|
_logger.warning("piexif not installed — skipping geo-tagging. Run: pip install piexif")
|
|
return image_b64
|
|
|
|
try:
|
|
image_data = base64.b64decode(image_b64)
|
|
|
|
# Check if it's a JPEG (piexif only works with JPEG)
|
|
if not image_data[:2] == b'\xff\xd8':
|
|
_logger.info("Image is not JPEG — skipping EXIF geo-tagging")
|
|
return image_b64
|
|
|
|
# Try to load existing EXIF
|
|
try:
|
|
exif_dict = piexif.load(image_data)
|
|
except Exception:
|
|
exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "1st": {}}
|
|
|
|
# Set 0th IFD tags
|
|
exif_dict["0th"][piexif.ImageIFD.ImageDescription] = address.encode('utf-8') if address else b''
|
|
exif_dict["0th"][piexif.ImageIFD.Copyright] = (
|
|
f"Copyright {company_name}".encode('utf-8') if company_name else b''
|
|
)
|
|
exif_dict["0th"][piexif.ImageIFD.Artist] = company_name.encode('utf-8') if company_name else b''
|
|
|
|
# Set GPS tags if coordinates provided
|
|
if lat and lng:
|
|
lat_deg = ImageProcessor._decimal_to_dms(abs(lat))
|
|
lng_deg = ImageProcessor._decimal_to_dms(abs(lng))
|
|
|
|
exif_dict["GPS"] = {
|
|
piexif.GPSIFD.GPSLatitudeRef: b'N' if lat >= 0 else b'S',
|
|
piexif.GPSIFD.GPSLatitude: lat_deg,
|
|
piexif.GPSIFD.GPSLongitudeRef: b'E' if lng >= 0 else b'W',
|
|
piexif.GPSIFD.GPSLongitude: lng_deg,
|
|
}
|
|
|
|
exif_bytes = piexif.dump(exif_dict)
|
|
output = io.BytesIO()
|
|
piexif.insert(exif_bytes, image_data, output)
|
|
return base64.b64encode(output.getvalue()).decode('utf-8')
|
|
|
|
except Exception as e:
|
|
_logger.error("Failed to geo-tag image: %s", str(e))
|
|
return image_b64
|
|
|
|
@staticmethod
|
|
def _decimal_to_dms(decimal):
|
|
"""Convert decimal degrees to EXIF DMS format (degrees, minutes, seconds as rationals)."""
|
|
degrees = int(decimal)
|
|
minutes_float = (decimal - degrees) * 60
|
|
minutes = int(minutes_float)
|
|
seconds = int((minutes_float - minutes) * 60 * 10000)
|
|
return (
|
|
(degrees, 1),
|
|
(minutes, 1),
|
|
(seconds, 10000),
|
|
)
|
|
|
|
@staticmethod
|
|
def prepare_wc_image(image_b64, filename, alt_text='', caption='', title='', description=''):
|
|
"""Prepare image data for WooCommerce upload.
|
|
|
|
Returns dict ready for WC product images array, using src as base64 data URL.
|
|
Note: WC REST API v3 accepts image URLs in 'src'. For base64, we need to upload
|
|
via WordPress media endpoint first, then reference by URL.
|
|
"""
|
|
return {
|
|
'name': title or filename,
|
|
'alt': alt_text or '',
|
|
'caption': caption or '',
|
|
'description': description or '',
|
|
# The actual upload will be handled by the wizard
|
|
'_base64': image_b64,
|
|
'_filename': filename,
|
|
}
|