duqing2026's picture
init
8b1c497
Raw
History Blame Contribute Delete
6.04 kB
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
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/process', methods=['POST'])
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)