Spaces:
Running
Running
| 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 | |