100 lines
3.1 KiB
Python
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)
|