Spaces:
Sleeping
Sleeping
| import os | |
| import zipfile | |
| import io | |
| from flask import Flask, render_template, request, send_file, after_this_request | |
| from PIL import Image, ImageDraw, ImageFont, ImageOps | |
| import tempfile | |
| import shutil | |
| import time | |
| app = Flask(__name__) | |
| app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB limit | |
| def process_image(img, options): | |
| # 1. Strip EXIF (by creating a new image) | |
| if options.get('strip_exif'): | |
| data = list(img.getdata()) | |
| image_without_exif = Image.new(img.mode, img.size) | |
| image_without_exif.putdata(data) | |
| img = image_without_exif | |
| # 2. Resize | |
| resize_mode = options.get('resize_mode') | |
| if resize_mode == 'percentage': | |
| scale = int(options.get('resize_value', 100)) / 100.0 | |
| if scale != 1.0: | |
| new_size = (int(img.width * scale), int(img.height * scale)) | |
| img = img.resize(new_size, Image.Resampling.LANCZOS) | |
| elif resize_mode == 'width': | |
| target_width = int(options.get('resize_value', img.width)) | |
| if target_width != img.width: | |
| ratio = target_width / img.width | |
| new_size = (target_width, int(img.height * ratio)) | |
| img = img.resize(new_size, Image.Resampling.LANCZOS) | |
| # 3. Watermark | |
| watermark_text = options.get('watermark_text') | |
| if watermark_text: | |
| # Create a transparent layer | |
| txt_layer = Image.new('RGBA', img.size, (255, 255, 255, 0)) | |
| draw = ImageDraw.Draw(txt_layer) | |
| # Calculate font size (relative to image width) | |
| fontsize = int(img.width / 20) | |
| if fontsize < 10: fontsize = 10 | |
| try: | |
| # Try to load a default font, otherwise load default | |
| # On Linux/Docker, paths might differ. | |
| # We'll use default bitmap font if TTF not found or just try-except | |
| font = ImageFont.truetype("DejaVuSans.ttf", fontsize) | |
| except IOError: | |
| font = ImageFont.load_default() | |
| # Position: Bottom Right with padding | |
| # Get text size | |
| bbox = draw.textbbox((0, 0), watermark_text, font=font) | |
| textwidth = bbox[2] - bbox[0] | |
| textheight = bbox[3] - bbox[1] | |
| padding = 10 | |
| x = img.width - textwidth - padding | |
| y = img.height - textheight - padding | |
| # Draw semi-transparent text | |
| draw.text((x, y), watermark_text, font=font, fill=(255, 255, 255, 128)) | |
| if img.mode != 'RGBA': | |
| img = img.convert('RGBA') | |
| img = Image.alpha_composite(img, txt_layer) | |
| # 4. Format Conversion handled during save | |
| return img | |
| def index(): | |
| return render_template('index.html') | |
| def process(): | |
| if 'files' not in request.files: | |
| return "No files uploaded", 400 | |
| files = request.files.getlist('files') | |
| if not files or files[0].filename == '': | |
| return "No files selected", 400 | |
| # Get options | |
| target_format = request.form.get('target_format', 'original') | |
| resize_mode = request.form.get('resize_mode', 'none') # none, percentage, width | |
| resize_value = request.form.get('resize_value', 0) | |
| watermark_text = request.form.get('watermark_text', '').strip() | |
| strip_exif = 'strip_exif' in request.form | |
| options = { | |
| 'resize_mode': resize_mode, | |
| 'resize_value': resize_value, | |
| 'watermark_text': watermark_text, | |
| 'strip_exif': strip_exif | |
| } | |
| # Create a temporary directory for processing | |
| temp_dir = tempfile.mkdtemp() | |
| try: | |
| zip_buffer = io.BytesIO() | |
| with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: | |
| for file in files: | |
| if not file.filename: | |
| continue | |
| try: | |
| img = Image.open(file.stream) | |
| # Process | |
| img = process_image(img, options) | |
| # Determine output filename and format | |
| original_name = os.path.splitext(file.filename)[0] | |
| ext = os.path.splitext(file.filename)[1].lower() | |
| if target_format != 'original': | |
| ext = '.' + target_format.lower() | |
| if target_format.lower() == 'jpeg': | |
| img = img.convert('RGB') | |
| elif target_format.lower() == 'png': | |
| # Keep RGBA if possible, or convert if needed | |
| pass | |
| # Save to temp buffer | |
| img_byte_arr = io.BytesIO() | |
| save_format = ext.strip('.').upper() | |
| if save_format == 'JPG': save_format = 'JPEG' | |
| # Handle WEBP/JPEG quality if needed (using default for now) | |
| img.save(img_byte_arr, format=save_format) | |
| # Add to ZIP | |
| zip_file.writestr(f"processed/{original_name}{ext}", img_byte_arr.getvalue()) | |
| except Exception as e: | |
| print(f"Error processing {file.filename}: {e}") | |
| # Optionally add an error log to the zip | |
| zip_file.writestr(f"processed/errors/{file.filename}.txt", str(e)) | |
| zip_buffer.seek(0) | |
| # Cleanup temp dir (we didn't actually write files to disk, just memory, | |
| # but if we did, we'd clean here. The `img_byte_arr` approach avoids disk IO) | |
| return send_file( | |
| zip_buffer, | |
| mimetype='application/zip', | |
| as_attachment=True, | |
| download_name=f'processed_images_{int(time.time())}.zip' | |
| ) | |
| finally: | |
| shutil.rmtree(temp_dir) | |
| if __name__ == '__main__': | |
| port = int(os.environ.get('PORT', 7860)) | |
| app.run(host='0.0.0.0', port=port) | |