Files
ImageReducer/app.py

100 lines
3.1 KiB
Python

from bottle import Bottle, response, request, run, static_file, template, BaseRequest
from PIL import Image
import os
import tempfile
import zipfile
from concurrent.futures import ProcessPoolExecutor
from functools import partial
BaseRequest.MEMFILE_MAX = 100 * 1024 * 1024 # Supporte jusqu'à 100 Mo
app = Bottle()
UPLOAD_DIR = tempfile.mkdtemp()
OUTPUT_DIR = os.path.join(UPLOAD_DIR, 'resized')
ZIP_PATH = os.path.join(UPLOAD_DIR, 'resized_images.zip')
os.makedirs(OUTPUT_DIR, exist_ok=True)
def resize_image(filepath, output_dir, ratio):
from PIL import Image # Re-importé dans chaque processus
import os
try:
with Image.open(filepath) as img:
exif_data = img.info.get('exif')
width, height = img.size
new_size = (int(width / ratio), int(height / ratio))
try:
img = img.resize(new_size, Image.LANCZOS)
except ValueError:
img = Image.open(filepath).point(lambda x: x / 256).convert("L")
img = img.resize(new_size, Image.LANCZOS)
name, ext = os.path.splitext(os.path.basename(filepath))
output_path = os.path.join(output_dir, f"{name}_resized{ext}")
img.save(output_path, quality=85, optimize=True, exif=exif_data or [])
print(f"✅ Image enregistrée : {output_path} === ({width, height}) => ({new_size})") # DEBUG
return output_path
except Exception as e:
print(f"❌ Erreur pour {filepath}: {e}")
return None
@app.route('/')
def index():
return template('index.tpl')
@app.post('/upload')
def upload():
files = request.files.getall('files')
ratio = float(request.forms.get('ratio', 2))
if not files:
return "Aucun fichier reçu."
saved_paths = []
for file in files:
filename = os.path.basename(file.filename)
save_path = os.path.join(UPLOAD_DIR, filename)
file.save(save_path, overwrite=True)
saved_paths.append(save_path)
# 🧠 Traitement en parallèle
resize_func = partial(resize_image, output_dir=OUTPUT_DIR, ratio=ratio)
with ProcessPoolExecutor() as executor:
resized_files = list(executor.map(resize_func, saved_paths))
# 📦 Création du ZIP
with zipfile.ZipFile(ZIP_PATH, 'w') as zipf:
for path in resized_files:
if path: # Skip si erreur
zipf.write(path, arcname=os.path.basename(path))
return template(
'result.tpl',
count=len([p for p in resized_files if p]),
ratio=ratio,
images=[os.path.basename(p) for p in resized_files if p]
)
@app.route('/download')
def download():
if not os.path.exists(ZIP_PATH):
return "ZIP non généré ❌"
return static_file('resized_images.zip', root=UPLOAD_DIR, download='images_reduites.zip')
@app.route('/resized/<filename>')
def serve_resized(filename):
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return static_file(filename, root=OUTPUT_DIR)
if __name__ == "__main__":
run(app, host='localhost', port=8080, debug=True)