radiant_ai / services /utilities.py
arshvir's picture
Fix indentation after NBSP cleanup in app.py
073316d
import os
from PIL import Image, ExifTags
class UtilitiesService:
"""
Handles general image utilities:
- Format Conversion (JPG, PNG, WEBP, BMP)
- Resizing & Compression
- Metadata (EXIF) Extraction
"""
def process_request(self, image_path, output_dir, options):
"""
Main entry point.
options: {'action': 'convert'|'resize'|'metadata', ...params}
"""
try:
# check file exists
if not os.path.exists(image_path):
raise FileNotFoundError("Input file missing")
action = options.get('action')
if action == 'metadata':
return True, self._get_metadata(image_path)
elif action == 'convert':
return self._convert_format(image_path, output_dir, options)
elif action == 'resize':
return self._resize_image(image_path, output_dir, options)
else:
raise ValueError(f"Unknown utility action: {action}")
except Exception as e:
print(f"Utility Error: {e}")
return False, str(e)
def _convert_format(self, image_path, output_dir, options):
img = Image.open(image_path)
# Target format
target_format = options.get('format', 'png').lower()
# Handle RGBA to RGB conversion for JPG/BMP which don't support transparency
if target_format in ['jpg', 'jpeg', 'bmp'] and img.mode == 'RGBA':
img = img.convert('RGB')
# Generate new filename
base_name = os.path.splitext(os.path.basename(image_path))[0]
# Clean timestamp prefix if it exists to avoid double timestamps
if '_' in base_name and base_name[0].isdigit():
# rough heuristic to keep name clean
pass
new_filename = f"converted_{base_name}.{target_format}"
output_path = os.path.join(output_dir, new_filename)
# Save
if target_format in ['jpg', 'jpeg']:
img.save(output_path, quality=95)
else:
img.save(output_path)
return True, new_filename
def _resize_image(self, image_path, output_dir, options):
img = Image.open(image_path)
# Get dimensions
try:
# If input is empty string, use original dim
w_opt = options.get('width')
h_opt = options.get('height')
w = int(w_opt) if w_opt else img.width
h = int(h_opt) if h_opt else img.height
except ValueError:
w, h = img.width, img.height
quality = int(options.get('quality', 90))
# Resize (LANCZOS is high quality downsampling filter)
img = img.resize((w, h), Image.Resampling.LANCZOS)
# Generate filename
base_name = os.path.basename(image_path)
output_path = os.path.join(output_dir, f"resized_{base_name}")
# Save with compression if JPEG/WEBP
save_kwargs = {}
if output_path.lower().endswith(('.jpg', '.jpeg', '.webp')):
# Ensure RGB for JPEG
if img.mode == 'RGBA' and output_path.lower().endswith(('.jpg', '.jpeg')):
img = img.convert('RGB')
save_kwargs['quality'] = quality
img.save(output_path, **save_kwargs)
return True, os.path.basename(output_path)
def _get_metadata(self, image_path):
img = Image.open(image_path)
exif_data = {}
# Extract basic info
exif_data['Format'] = img.format
exif_data['Mode'] = img.mode
exif_data['Size'] = f"{img.width} x {img.height}"
# Extract EXIF if available
if hasattr(img, '_getexif') and img._getexif():
for tag, value in img._getexif().items():
if tag in ExifTags.TAGS:
tag_name = ExifTags.TAGS[tag]
# Filter out binary data which isn't JSON serializable
if isinstance(value, bytes):
value = "<Binary Data>"
# Limit long strings
if isinstance(value, str) and len(value) > 100:
value = value[:100] + "..."
exif_data[tag_name] = str(value)
if len(exif_data) <= 3: # Only basic info found
exif_data['Note'] = "No advanced EXIF data found in this image."
return exif_data