Files
2026-09-29 19:55:26 +02:00

131 lines
4.7 KiB
Python

import threading
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from pathlib import Path
from PIL import Image, ImageTk
import cv2
from core.image_engine import ImageSwapEngine
from ui.components import create_path_row
from ui.theme import BG_DARK, BG_PANEL, BG_PREVIEW, FG_TEXT, FG_MUTED, FONT_NORMAL
class ImageTab:
"""UI tab for batch and single image face swapping."""
def __init__(self, notebook, config_vars, swapper, on_save_config=None, log_fn=print):
self.notebook = notebook
self.vars = config_vars
self.swapper = swapper
self.on_save_config = on_save_config
self.log = log_fn
self.engine = ImageSwapEngine(swapper=self.swapper, log_fn=self.log)
self.stop_requested = False
self.is_running = False
self.tk_preview = None
self.tab = tk.Frame(notebook, bg=BG_DARK, padx=12, pady=10)
notebook.add(self.tab, text="Bilder")
self._build_ui()
def _build_ui(self):
# Input directory
create_path_row(
self.tab, "2 EINGABE-ORDNER - Bilder die geswappt werden sollen",
self.vars["input_dir"], self._pick_indir, on_change=self.on_save_config
)
# Output directory
create_path_row(
self.tab, "3 AUSGABE-ORDNER - Zielort fuer fertige Bilder",
self.vars["output_dir"], self._pick_outdir, on_change=self.on_save_config
)
# Preview area
preview_box = tk.Frame(self.tab, bg=BG_PREVIEW, height=220)
preview_box.pack(fill="both", expand=True, padx=4, pady=(12, 6))
preview_box.pack_propagate(False)
self.lbl_preview = tk.Label(
preview_box, bg=BG_PREVIEW, fg=FG_MUTED, font=FONT_NORMAL,
text="Vorschau (Zeigt das zuletzt bearbeitete Bild)"
)
self.lbl_preview.pack(fill="both", expand=True)
def _pick_indir(self):
d = filedialog.askdirectory(title="Eingabe-Ordner waehlen", initialdir=self.vars["input_dir"].get())
if d:
self.vars["input_dir"].set(d)
if self.on_save_config:
self.on_save_config()
def _pick_outdir(self):
d = filedialog.askdirectory(title="Ausgabe-Ordner waehlen", initialdir=self.vars["output_dir"].get())
if d:
self.vars["output_dir"].set(d)
if self.on_save_config:
self.on_save_config()
def update_preview(self, cv_bgr_img):
"""Update preview image in UI."""
if cv_bgr_img is None:
return
try:
h, w = cv_bgr_img.shape[:2]
scale = min(400.0 / max(w, 1), 220.0 / max(h, 1))
nw = max(1, int(w * scale))
nh = max(1, int(h * scale))
resized = cv2.resize(cv_bgr_img, (nw, nh), interpolation=cv2.INTER_AREA)
rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(rgb)
self.tk_preview = ImageTk.PhotoImage(pil_img)
self.lbl_preview.configure(image=self.tk_preview, text="")
except Exception:
pass
def run_batch(self, on_progress=None, on_finished=None):
"""Start batch processing in background thread."""
if self.is_running:
return
self.is_running = True
self.stop_requested = False
source = self.vars["source"].get().strip()
indir = self.vars["input_dir"].get().strip()
outdir = self.vars["output_dir"].get().strip()
if not all([source, indir, outdir]):
messagebox.showerror("Fehler", "Bitte Zielgesicht, Eingabe- und Ausgabeordner ausfuellen.")
self.is_running = False
if on_finished:
on_finished()
return
def _worker():
def _progress(done, total, preview_img, filename):
if preview_img is not None:
self.tab.after(0, self.update_preview, preview_img)
if on_progress:
on_progress(done, total, filename)
try:
self.engine.process_batch(
source_path=source,
input_dir=indir,
output_dir=outdir,
progress_cb=_progress,
cancel_check=lambda: self.stop_requested
)
except Exception as e:
self.log(f"BILDER-FEHLER: {e}")
self.tab.after(0, lambda: messagebox.showerror("Fehler", str(e)))
finally:
self.is_running = False
if on_finished:
self.tab.after(0, on_finished)
threading.Thread(target=_worker, daemon=True).start()
def request_stop(self):
self.stop_requested = True