"""Hochgeladene Bilder ohne Metadaten speichern. Fotos vom Handy enthalten in den EXIF-Daten oft den Aufnahmeort (GPS), das Gerät und die Uhrzeit. Die Bilder sind für alle Konten sichtbar, deshalb wird alles außer den Bildpunkten entfernt. Die Drehung aus den EXIF-Daten wird vorher angewendet, damit Hochkant-Fotos richtig stehen bleiben. Nur das Farbprofil (ICC) bleibt erhalten, damit sich die Farben nicht verändern. GIFs bleiben unverändert: Sie haben keine EXIF-/GPS-Daten, und neu speichern würde Animationen verschlechtern. """ import io from PIL import Image, ImageOps from .common import ValidationError # Schutz vor "Dekompressionsbomben" (winzige Datei, riesiges Bild) Image.MAX_IMAGE_PIXELS = 60_000_000 def hat_metadaten(data: bytes) -> bool: """Enthält das Bild EXIF-, XMP- oder Text-Angaben (z. B. GPS, Kamera, Kommentar)?""" try: with Image.open(io.BytesIO(data)) as img: return bool(img.getexif()) or any(k in img.info for k in ("xmp", "XML:com.adobe.xmp", "comment", "exif")) \ or bool(getattr(img, "text", None)) except Exception: # kaputte oder exotische Datei: als "ohne Metadaten" behandeln return False def ohne_metadaten(data: bytes, ext: str) -> bytes: """Bild neu schreiben, ohne EXIF/XMP/Text-Angaben. ext: jpg, png, webp oder gif.""" if ext == "gif": return data try: with Image.open(io.BytesIO(data)) as img: animated = getattr(img, "is_animated", False) icc = img.info.get("icc_profile") out = io.BytesIO() if ext == "webp" and animated: # Animierte WebP: alle Bilder übernehmen, Metadaten fallen weg img.save(out, "WEBP", save_all=True, quality=90, icc_profile=icc, duration=img.info.get("duration"), loop=img.info.get("loop", 0)) return out.getvalue() img = ImageOps.exif_transpose(img) if ext == "jpg": if img.mode not in ("RGB", "L", "CMYK"): img = img.convert("RGB") img.save(out, "JPEG", quality=92, optimize=True, icc_profile=icc) elif ext == "png": img.save(out, "PNG", optimize=True, icc_profile=icc) else: img.save(out, "WEBP", quality=90, icc_profile=icc) return out.getvalue() except Exception: # Pillow wirft je nach Format verschiedene Fehler raise ValidationError("Das Bild konnte nicht gelesen werden oder ist zu groß.")