#!/usr/bin/env python3 """ EXIF-Entferner für Bilder (Nur Python-Standardbibliotheken) Erstellt einen Überwachungsordner, entfernt EXIF-Daten aus Bildern und speichert bereinigte Versionen auf dem Desktop. """ import os import shutil import time import platform import subprocess from pathlib import Path import tkinter as tk from tkinter import messagebox import threading import struct class ExifRemover: def __init__(self): self.input_folder = Path.home() / "Bilder_Input" self.output_folder = Path.home() / "Desktop" / "Bereinigte_Bilder" self.supported_formats = {'.jpg', '.jpeg', '.png', '.tiff', '.tif', '.bmp'} self.running = False def create_folders(self): """Erstellt die notwendigen Ordner""" try: self.input_folder.mkdir(exist_ok=True) self.output_folder.mkdir(exist_ok=True) print(f"✓ Input-Ordner erstellt: {self.input_folder}") print(f"✓ Output-Ordner erstellt: {self.output_folder}") return True except Exception as e: print(f"✗ Fehler beim Erstellen der Ordner: {e}") return False def remove_exif_data(self, input_path, output_path): """Entfernt EXIF-Daten aus einem JPEG-Bild""" try: if input_path.suffix.lower() in ['.jpg', '.jpeg']: return self.remove_jpeg_exif(input_path, output_path) else: # Für andere Formate: einfach kopieren shutil.copy2(input_path, output_path) return True except Exception as e: print(f"✗ Fehler beim Bearbeiten von {input_path.name}: {e}") return False def remove_jpeg_exif(self, input_path, output_path): """Entfernt EXIF-Daten aus JPEG-Dateien""" try: with open(input_path, 'rb') as f_in: # JPEG Header lesen if f_in.read(2) != b'\xff\xd8': raise ValueError("Nicht gültige JPEG-Datei") with open(output_path, 'wb') as f_out: # JPEG Start Marker schreiben f_out.write(b'\xff\xd8') while True: marker = f_in.read(2) if len(marker) != 2: break if marker[0] != 0xff: break marker_type = marker[1] # EXIF/JFIF Segmente überspringen if marker_type in [0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef]: length_bytes = f_in.read(2) if len(length_bytes) != 2: break length = struct.unpack('>H', length_bytes)[0] f_in.read(length - 2) continue # Alle anderen Segmente beibehalten f_out.write(marker) # Bei Start of Scan (SOS) den Rest der Datei kopieren if marker_type == 0xda: length_bytes = f_in.read(2) if len(length_bytes) != 2: break f_out.write(length_bytes) length = struct.unpack('>H', length_bytes)[0] segment_data = f_in.read(length - 2) f_out.write(segment_data) shutil.copyfileobj(f_in, f_out) break # Für andere Marker die Länge lesen und Daten kopieren if marker_type not in [0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9]: length_bytes = f_in.read(2) if len(length_bytes) != 2: break f_out.write(length_bytes) length = struct.unpack('>H', length_bytes)[0] if length > 2: segment_data = f_in.read(length - 2) f_out.write(segment_data) return True except Exception as e: print(f"✗ JPEG EXIF-Entfernung fehlgeschlagen: {e}") try: shutil.copy2(input_path, output_path) return True except: return False def process_images(self): """Verarbeitet alle Bilder im Input-Ordner""" processed = 0 for file_path in self.input_folder.iterdir(): if file_path.is_file() and file_path.suffix.lower() in self.supported_formats: output_path = self.output_folder / f"clean_{file_path.name}" print(f"🔄 Bearbeite: {file_path.name}") if self.remove_exif_data(file_path, output_path): print(f"✓ Gespeichert: {output_path.name}") try: file_path.unlink() processed += 1 except Exception as e: print(f"⚠ Konnte Originaldatei nicht löschen: {e}") return processed def monitor_folder(self, callback=None): """Überwacht den Input-Ordner kontinuierlich""" print(f"👁 Überwache Ordner: {self.input_folder}") print("📁 Lege Bilder in den Input-Ordner, sie werden automatisch bearbeitet") print("🛑 Drücke Strg+C zum Beenden\n") self.running = True while self.running: try: processed = self.process_images() if processed > 0: message = f"✅ {processed} Bild(er) erfolgreich bearbeitet!" print(message) if callback: callback(message) time.sleep(2) except KeyboardInterrupt: break except Exception as e: print(f"⚠ Fehler bei Überwachung: {e}") time.sleep(5) print("\n👋 Überwachung beendet") def stop_monitoring(self): """Stoppt die Ordnerüberwachung""" self.running = False def open_folder_in_explorer(folder_path): """Öffnet den Ordner betriebssystemunabhängig im Dateimanager""" if platform.system() == "Windows": os.startfile(str(folder_path)) elif platform.system() == "Darwin": # macOS subprocess.Popen(["open", str(folder_path)]) else: # Linux / Unix subprocess.Popen(["xdg-open", str(folder_path)]) class ExifRemoverGUI: def __init__(self): self.remover = ExifRemover() self.monitor_thread = None self.root = tk.Tk() self.root.title("EXIF-Daten Entferner") self.root.geometry("500x300") self.root.resizable(False, False) self.setup_gui() def setup_gui(self): """Erstellt die GUI-Elemente""" title = tk.Label(self.root, text="EXIF-Daten Entferner", font=("Arial", 16, "bold")) title.pack(pady=10) info_text = f"""Input-Ordner: {self.remover.input_folder} Output-Ordner: {self.remover.output_folder} Unterstützte Formate: JPG, PNG, TIFF, BMP (EXIF-Entfernung hauptsächlich für JPG-Dateien)""" info_label = tk.Label(self.root, text=info_text, font=("Arial", 10), justify="left") info_label.pack(pady=10) self.status_label = tk.Label(self.root, text="Bereit zum Starten", font=("Arial", 12), fg="blue") self.status_label.pack(pady=10) button_frame = tk.Frame(self.root) button_frame.pack(pady=20) self.start_button = tk.Button(button_frame, text="Überwachung starten", command=self.start_monitoring, bg="green", fg="white", font=("Arial", 12)) self.start_button.pack(side="left", padx=10) self.stop_button = tk.Button(button_frame, text="Überwachung stoppen", command=self.stop_monitoring, bg="red", fg="white", font=("Arial", 12), state="disabled") self.stop_button.pack(side="left", padx=10) folder_frame = tk.Frame(self.root) folder_frame.pack(pady=10) tk.Button(folder_frame, text="Input-Ordner öffnen", command=self.open_input_folder).pack(side="left", padx=5) tk.Button(folder_frame, text="Output-Ordner öffnen", command=self.open_output_folder).pack(side="left", padx=5) def start_monitoring(self): """Startet die Ordnerüberwachung""" if not self.remover.create_folders(): messagebox.showerror("Fehler", "Konnte Ordner nicht erstellen!") return self.start_button.config(state="disabled") self.stop_button.config(state="normal") self.status_label.config(text="Überwachung läuft...", fg="green") self.monitor_thread = threading.Thread( target=self.remover.monitor_folder, args=(self.update_status,), daemon=True ) self.monitor_thread.start() def stop_monitoring(self): """Stoppt die Ordnerüberwachung""" self.remover.stop_monitoring() self.start_button.config(state="normal") self.stop_button.config(state="disabled") self.status_label.config(text="Überwachung gestoppt", fg="red") def update_status(self, message): """Aktualisiert den Status in der GUI""" self.status_label.config(text=message, fg="green") def open_input_folder(self): """Öffnet den Input-Ordner""" open_folder_in_explorer(self.remover.input_folder) def open_output_folder(self): """Öffnet den Output-Ordner""" open_folder_in_explorer(self.remover.output_folder) def run(self): """Startet die GUI""" self.root.protocol("WM_DELETE_WINDOW", self.on_closing) self.root.mainloop() def on_closing(self): """Wird beim Schließen der App aufgerufen""" if self.monitor_thread and self.monitor_thread.is_alive(): self.remover.stop_monitoring() self.root.destroy() def main(): """Hauptfunktion""" print("🖼️ EXIF-Daten Entferner") print("=" * 50) use_gui = input("GUI verwenden? (j/n, Standard: j): ").strip().lower() if use_gui in ['', 'j', 'ja', 'y', 'yes']: try: app = ExifRemoverGUI() app.run() except ImportError: print("⚠ tkinter nicht verfügbar, verwende Konsolen-Modus") console_mode() else: console_mode() def console_mode(): """Konsolen-Modus ohne GUI""" remover = ExifRemover() if not remover.create_folders(): return try: remover.monitor_folder() except KeyboardInterrupt: print("\n👋 Programm beendet") if __name__ == "__main__": main()