Files
slides/app/app.py
2024-03-24 20:09:46 +01:00

227 lines
8.4 KiB
Python
Executable File

import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox
import cv2
import ttkbootstrap as ttk
from convert import differences, load_frames, select_frames
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from params import ParameterTuner
from PIL import Image, ImageTk
from preview import SlidePreview
class SlideConversionApp:
def __init__(self):
self._root = tk.Tk()
self._root.title("PDF Extraktion")
self._root.geometry("1400x1000")
self._frames = None
self._diffs = None
# file selection
self._file_selection = tk.Frame(self._root)
self._file_selection_label = tk.Label(self._file_selection, text="Videodatei:")
self._file_selection_label.pack()
self._file_selection_button = ttk.Button(
self._file_selection, text="Datei auswählen", command=self._select_file
)
self._file_selection_button.pack()
self._file_selection.pack()
self._file = None
ttk.Separator(self._root, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10, padx=10)
# slide selection
self._selection = tk.Frame(self._root)
self._params = ParameterTuner(self._selection)
self._param_threshold = self._params.add("Schwellenwert", 10, 1, 20)
self._params.pack(side=tk.LEFT, padx=10)
self._show_diffs_btn = ttk.Button(self._selection, text="Unterschiede anzeigen", command=self._show_diffs)
self._show_diffs_btn.pack(side=tk.LEFT, padx=10)
self._add_selected_btn = ttk.Button(self._selection, text="Folien hinzufügen", command=self._add_selection)
self._add_selected_btn.pack(side=tk.LEFT, padx=10)
self._selection.pack()
ttk.Separator(self._root, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10, padx=10)
# generated images
self._images = SlidePreview(self._root, columns=6)
self._images.pack(fill=tk.BOTH, padx=10, expand=True)
ttk.Separator(self._root, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10, padx=10)
# buttons
self._buttons = tk.Frame(self._root)
self._buttons.pack(side=tk.RIGHT, pady=(0, 5))
self._button_create_pdf = ttk.Button(self._buttons, text="PDF erstellen", command=self._create_pdf)
self._button_create_pdf.pack(side=tk.RIGHT, padx=10)
self._button_add_frame = ttk.Button(self._buttons, text="Manuell hinzufügen", command=self._add_manually)
self._button_add_frame.pack(side=tk.RIGHT, padx=10)
self._set_interaction_state(tk.DISABLED)
self._root.mainloop()
def _select_file(self):
file = filedialog.askopenfilename(
title="Videodatei auswählen",
filetypes=[
("Videodateien", "*.mp4"),
("Alle Dateien", "*.*"),
],
)
if file is not None and file != "":
file = Path(file)
self._file_selected(file)
def _file_selected(self, file: Path):
# set variables
cap = cv2.VideoCapture(file.as_posix())
self._frames = None
# analyze file
fps = int(cap.get(cv2.CAP_PROP_FPS))
num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# load frames
status = tk.Toplevel(self._root)
status.title("Lade Videoframes ..")
status.grab_set()
status_label = tk.Label(status, text=f"Lade Videoframes (0/{num_frames})")
status_label.pack(padx=10, pady=10)
status_pb = ttk.Progressbar(status, bootstyle="progress", orient=tk.HORIZONTAL, length=200, mode="determinate")
status_pb.pack(padx=10, pady=10)
def callback(text: str, frame_no: int, max_no: int):
status_label.configure(text=f"{text} ({frame_no}/{max_no})")
status_pb.configure(value=int((frame_no / max_no) * 100))
status_label.update()
status_pb.update()
status_pb.update_idletasks()
frames = load_frames(cap, fps, lambda f: callback("Lade Videoframes", f, num_frames))
diffs = differences(frames, lambda f: callback("Berechne Unterschiede", f, len(frames)))
status.grab_release()
status.destroy()
# update values & parameters
self._frames = frames
self._diffs = diffs
self._params.get_slider(self._param_threshold).configure(to=int(max(diffs)))
self._file_selection_button.configure(text=file.name)
self._file = file
self._images.clear()
self._set_interaction_state(tk.NORMAL)
def _set_interaction_state(self, state: str):
elements = [self._button_create_pdf, self._show_diffs_btn, self._add_selected_btn, self._button_add_frame]
self._params.set_state(state)
for element in elements:
element.configure(state=state)
def _show_diffs(self):
if self._diffs is None:
return
diffs = tk.Toplevel(self._root)
diffs.title("Unterschiede zwischen Frames")
diffs.grab_set()
fig = Figure(figsize=(10, 5), dpi=100)
plot = fig.add_subplot(111)
plot.plot(self._diffs)
canvas = FigureCanvasTkAgg(fig, master=diffs)
canvas.draw()
canvas.get_tk_widget().pack()
toolbar = NavigationToolbar2Tk(canvas, diffs)
toolbar.update()
canvas.get_tk_widget().pack()
def _add_selection(self):
self._images.clear()
images = select_frames(self._frames, self._diffs, threshold=self._params.get(self._param_threshold))
for image in images:
self._images.add(image)
def _create_pdf(self):
if len(self._images) == 0:
messagebox.showerror(title="PDF erstellen", message="Keine Folien vorhanden.")
return
file = filedialog.asksaveasfilename(
title="Speichern Unter",
filetypes=[
("PDF", "*.pdf"),
("Alle Dateien", "*.*"),
],
)
if file is not None and file != "":
file = Path(file).with_suffix(".pdf")
else:
return
images = self._images.get_images()
images[0].save(file, "PDF", resolution=100.0, save_all=True, append_images=images[1:])
messagebox.showinfo(title="PDF erstellen", message="Erfolgreich!")
def _add_manually(self):
if self._images._selected_index == -1:
messagebox.showerror(
title="Manuelles Hinzufügen",
message="Wähle zunächst eine Folie zum dahinter einfügen aus",
)
return
frameview = tk.Toplevel(self._root)
frameview.title("Manuell Hinzufügen")
frameview.grab_set()
thumbnail = tk.Label(frameview)
thumbnail.pack(padx=10, pady=5)
def show(frame_no: int):
img = Image.fromarray(self._frames[frame_no][:, :, ::-1])
img.thumbnail(size=(512, 512))
thumbnail.current = ImageTk.PhotoImage(img)
thumbnail.configure(image=thumbnail.current)
show(0)
selected = tk.IntVar(value=0)
selected.trace_add("write", lambda v, i, m: show(selected.get()))
framesel = tk.Frame(frameview)
framesel.pack(padx=10, pady=5)
back_btn = ttk.Button(
framesel,
text="<",
command=lambda: selected.set(0 if selected.get() - 1 < 0 else selected.get() - 1),
)
next_btn = ttk.Button(
framesel,
text=">",
command=lambda: selected.set(
len(self._frames) - 1 if selected.get() + 1 >= len(self._frames) else selected.get() + 1
),
)
slider = ttk.Scale(framesel, from_=0, to=len(self._frames) - 1, variable=selected, length=300)
back_btn.pack(side=tk.LEFT)
slider.pack(side=tk.LEFT, padx=5)
next_btn.pack(side=tk.LEFT)
def add_image():
self._images.insert(
Image.fromarray(self._frames[selected.get()][:, :, ::-1]),
self._images._selected_index + 1,
)
add_btn = ttk.Button(frameview, text="Hinzufügen", command=add_image)
add_btn.pack(pady=5)
if __name__ == "__main__":
app = SlideConversionApp()