Spaces:
Sleeping
Sleeping
| import os | |
| import io | |
| import zipfile | |
| from flask import Flask, render_template, request, send_file, jsonify | |
| from werkzeug.exceptions import RequestEntityTooLarge | |
| from PIL import Image, ImageOps | |
| app = Flask(__name__) | |
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload | |
| def index(): | |
| return render_template('index.html') | |
| def health(): | |
| return jsonify({'status': 'ok'}), 200 | |
| def split_image(): | |
| if 'file' not in request.files: | |
| return jsonify({'error': 'No file uploaded'}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({'error': 'No file selected'}), 400 | |
| try: | |
| # Get parameters | |
| rows = int(request.form.get('rows', 3)) | |
| cols = int(request.form.get('cols', 3)) | |
| out_format = (request.form.get('format') or '').upper() | |
| quality = int(request.form.get('quality', 90)) | |
| square = request.form.get('square', 'false') == 'true' | |
| strict_equal = request.form.get('strict_equal', 'false') == 'true' | |
| # Load image | |
| img = Image.open(file.stream) | |
| # Auto-orient by EXIF if present | |
| try: | |
| img = ImageOps.exif_transpose(img) | |
| except Exception: | |
| pass | |
| # Convert to RGB if necessary (e.g. for RGBA/P images saving as JPEG) | |
| # But we'll try to keep original format if possible, or default to PNG/JPG | |
| format = img.format or 'PNG' | |
| if out_format in ['JPEG', 'PNG', 'WEBP']: | |
| format = out_format | |
| if format not in ['JPEG', 'PNG', 'WEBP']: | |
| format = 'PNG' | |
| # Calculate dimensions | |
| width, height = img.size | |
| # Optional square crop (center) | |
| if square: | |
| side = min(width, height) | |
| left = (width - side) // 2 | |
| top = (height - side) // 2 | |
| img = img.crop((left, top, left + side, top + side)) | |
| width, height = img.size | |
| # Compute base tile size | |
| tile_width = width // cols | |
| tile_height = height // rows | |
| if strict_equal: | |
| # Crop to exact multiple to make all tiles equal | |
| exact_w = tile_width * cols | |
| exact_h = tile_height * rows | |
| left = (width - exact_w) // 2 | |
| top = (height - exact_h) // 2 | |
| img = img.crop((left, top, left + exact_w, top + exact_h)) | |
| width, height = img.size | |
| tile_width = width // cols | |
| tile_height = height // rows | |
| # Create ZIP in memory | |
| memory_file = io.BytesIO() | |
| with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf: | |
| for r in range(rows): | |
| for c in range(cols): | |
| # Crop | |
| left = c * tile_width | |
| upper = r * tile_height | |
| right = left + tile_width | |
| lower = upper + tile_height | |
| # Handle last row/col to include remainder pixels when not strict_equal | |
| if not strict_equal: | |
| if c == cols - 1: | |
| right = width | |
| if r == rows - 1: | |
| lower = height | |
| tile = img.crop((left, upper, right, lower)) | |
| # Save tile to bytes | |
| tile_bytes = io.BytesIO() | |
| save_kwargs = {} | |
| if format == 'JPEG': | |
| if tile.mode in ('RGBA', 'P'): | |
| tile = tile.convert('RGB') | |
| save_kwargs['quality'] = max(1, min(quality, 95)) | |
| save_kwargs['optimize'] = True | |
| save_kwargs['progressive'] = True | |
| elif format == 'WEBP': | |
| save_kwargs['quality'] = max(1, min(quality, 95)) | |
| tile.save(tile_bytes, format=format, **save_kwargs) | |
| tile_bytes.seek(0) | |
| # Add to zip (1-based index) | |
| # Naming convention: {original_name}_{index}.{ext} | |
| # Grid order: 1, 2, 3... | |
| index = r * cols + c + 1 | |
| filename = f"tile_{index:02d}.{format.lower()}" | |
| zf.writestr(filename, tile_bytes.read()) | |
| # Add posting order guide | |
| order_lines = [] | |
| order_lines.append(f"发布顺序指南({rows}x{cols})") | |
| order_lines.append("从左到右,从上到下:") | |
| nums = [f"{i:02d}" for i in range(1, rows * cols + 1)] | |
| for r in range(rows): | |
| row_slice = nums[r * cols:(r + 1) * cols] | |
| order_lines.append(" " + " ".join(row_slice)) | |
| order_lines.append("") | |
| order_lines.append("建议:") | |
| order_lines.append("1. 朋友圈/小红书九宫格:按上述顺序依次选择图片。") | |
| order_lines.append("2. 若平台有排序规则,请关闭自动排序或手动调整。") | |
| zf.writestr("posting_order.txt", "\n".join(order_lines)) | |
| memory_file.seek(0) | |
| return send_file( | |
| memory_file, | |
| mimetype='application/zip', | |
| as_attachment=True, | |
| download_name=f'grid_split_{rows}x{cols}.zip' | |
| ) | |
| except Exception as e: | |
| return jsonify({'error': str(e)}), 500 | |
| def handle_file_too_large(e): | |
| return jsonify({'error': '文件过大,超过限制(最大16MB)'}), 413 | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860) | |