131 lines
4.7 KiB
Python
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
|