Files
gsinghpal f759bf558f feat: add AI service (Claude + OpenAI) and image processor with EXIF geo-tagging
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>
2026-04-01 14:39:44 -04:00

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,
}