156 lines
6.3 KiB
Python
156 lines
6.3 KiB
Python
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() |