import os import subprocess import threading import tkinter as tk from tkinter import filedialog, messagebox, ttk from datetime import datetime, timedelta from concurrent.futures import ThreadPoolExecutor # Versuche tkcalendar zu importieren try: from tkcalendar import DateEntry except ImportError: print("Bitte installiere tkcalendar: pip install tkcalendar") exit() class ExifApp: def __init__(self, root): self.root = root self.root.title("ExifTool - Turbo Sequenzer") self.root.geometry("500x450") self.root.resizable(False, False) # DEIN STANDARDPFAD default_path = r"Z:\PC_WIN_10\Laufwerk_D\Eigene Dateien\Eigene Bilder" # Prüfen, ob der Pfad existiert, sonst leer lassen initial_folder = default_path if os.path.exists(default_path) else "" # Variablen self.folder_var = tk.StringVar(value=initial_folder) self.time_var = tk.StringVar(value="12:00:00") self.offset_var = tk.StringVar(value="5") self.status_var = tk.StringVar(value="Bereit") self.progress_var = tk.DoubleVar() self.setup_ui() def select_folder(self): # Beim Öffnen des Dialogs auch direkt im Standardpfad starten current_val = self.folder_var.get() initial_dir = current_val if os.path.exists(current_val) else None path = filedialog.askdirectory(initialdir=initial_dir) if path: self.folder_var.set(path) def setup_ui(self): frame = tk.Frame(self.root, padx=20, pady=20) frame.pack(fill="both", expand=True) # 1. Ordner tk.Label(frame, text="1. Ordner wählen:", font=("Arial", 10, "bold")).pack(anchor="w") f_frame = tk.Frame(frame) f_frame.pack(fill="x", pady=(5, 15)) tk.Entry(f_frame, textvariable=self.folder_var, state="readonly").pack(side="left", fill="x", expand=True, padx=(0, 10)) tk.Button(f_frame, text="Durchsuchen", command=self.select_folder).pack(side="right") # 2. Datum & Zeit tk.Label(frame, text="2. Start-Zeitpunkt:", font=("Arial", 10, "bold")).pack(anchor="w") dt_frame = tk.Frame(frame) dt_frame.pack(fill="x", pady=(5, 15)) self.cal = DateEntry(dt_frame, width=12, background='darkblue', date_pattern='dd.mm.yyyy') self.cal.pack(side="left", padx=(0, 15)) tk.Label(dt_frame, text="Uhrzeit:").pack(side="left") tk.Entry(dt_frame, textvariable=self.time_var, width=10).pack(side="left", padx=(5, 0)) # 3. Versatz tk.Label(frame, text="3. Zeitversatz (Sekunden):", font=("Arial", 10, "bold")).pack(anchor="w") off_frame = tk.Frame(frame) off_frame.pack(fill="x", pady=(5, 15)) tk.Entry(off_frame, textvariable=self.offset_var, width=8).pack(side="left") # Fortschrittsbalken tk.Label(frame, text="Fortschritt:").pack(anchor="w", pady=(10, 0)) self.progress_bar = ttk.Progressbar(frame, variable=self.progress_var, maximum=100) self.progress_bar.pack(fill="x", pady=(5, 20)) # Start Button self.btn_start = tk.Button(frame, text="Batch-Verarbeitung starten", command=self.start_thread, font=("Arial", 11, "bold"), bg="#2196F3", fg="white", pady=8) self.btn_start.pack(fill="x") # Status tk.Label(frame, textvariable=self.status_var, fg="#666").pack(pady=10) def start_thread(self): # Validierung if not self.folder_var.get(): messagebox.showerror("Fehler", "Kein Ordner gewählt!") return self.btn_start.config(state="disabled") threading.Thread(target=self.run_process, daemon=True).start() def update_exif(self, file_info): filepath, time_str = file_info cmd = ["exiftool", "-overwrite_original", f"-AllDates={time_str}", filepath] try: if os.name == 'nt': subprocess.run(cmd, check=True, creationflags=subprocess.CREATE_NO_WINDOW) else: subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return True except: return False def run_process(self): folder = self.folder_var.get() start_date = self.cal.get_date() try: h, m, s = map(int, self.time_var.get().split(':')) start_dt = datetime.combine(start_date, datetime.min.time()).replace(hour=h, minute=m, second=s) offset = int(self.offset_var.get()) except Exception as e: self.root.after(0, lambda: messagebox.showerror("Fehler", "Ungültige Zeit oder Versatz!")) self.root.after(0, lambda: self.btn_start.config(state="normal")) return valid_ext = ('.jpg', '.jpeg', '.png', '.tif', '.tiff', '.cr2', '.nef', '.arw') files = sorted([os.path.join(folder, f) for f in os.listdir(folder) if f.lower().endswith(valid_ext)]) if not files: self.root.after(0, lambda: messagebox.showinfo("Info", "Keine Bilder gefunden.")) self.root.after(0, lambda: self.btn_start.config(state="normal")) return total = len(files) tasks = [] # Aufgabenliste vorbereiten for i, path in enumerate(files): curr_time = start_dt + timedelta(seconds=i * offset) tasks.append((path, curr_time.strftime("%Y:%m:%d %H:%M:%S"))) # Parallelisierung mit ThreadPool # Nutzt standardmäßig CPU-Kern-Anzahl * 5 (gut für I/O Prozesse wie ExifTool) self.status_var.set(f"Verarbeite {total} Bilder parallel...") completed = 0 with ThreadPoolExecutor() as executor: for result in executor.map(self.update_exif, tasks): completed += 1 prog = (completed / total) * 100 self.progress_var.set(prog) self.status_var.set(f"Bild {completed} von {total} fertig...") self.status_var.set("Abgeschlossen!") self.root.after(0, lambda: messagebox.showinfo("Erfolg", f"Fertig! {total} Bilder wurden aktualisiert.")) self.root.after(0, lambda: self.btn_start.config(state="normal")) self.root.after(0, lambda: self.progress_var.set(0)) if __name__ == "__main__": root = tk.Tk() app = ExifApp(root) root.mainloop()