backup: WIP Windows avant repart propre (GUI core installer splash spec)

This commit is contained in:
2026-06-05 12:11:21 +02:00
parent 39db675052
commit 1abee3e089
27 changed files with 4628 additions and 775 deletions

View File

@@ -17,6 +17,7 @@ Dépendances : PyMuPDF (pymupdf), Pillow, PyYAML
"""
from __future__ import annotations
import argparse
import io
import json
import math
@@ -31,7 +32,12 @@ from PIL import Image, ImageTk
import fitz # PyMuPDF
import yaml
APP_TITLE = "PDF Mask Designer (Standalone)"
from manual_masking import (
DEFAULT_MASK_OUTPUT_DIRNAME,
DEFAULT_MASK_PREVIEW_DIRNAME,
)
APP_TITLE = "Éditeur de masques PDF"
TEMPLATE_VERSION = 1
# ----------------------------- Data structures -----------------------------
@@ -167,7 +173,16 @@ def apply_template_raster(pdf_in: Path, pdf_out: Path, tpl: Template, dpi: int,
# ----------------------------- GUI ------------------------------
class MaskDesignerApp:
def __init__(self, root: tk.Tk):
def __init__(
self,
root: tk.Tk,
*,
initial_pdf: Optional[Path] = None,
initial_template: Optional[Path] = None,
templates_dir: Optional[Path] = None,
output_dir_name: str = DEFAULT_MASK_OUTPUT_DIRNAME,
preview_dir_name: str = DEFAULT_MASK_PREVIEW_DIRNAME,
):
self.root = root
self.root.title(APP_TITLE)
self.root.geometry("1280x900")
@@ -181,11 +196,18 @@ class MaskDesignerApp:
self.template_name = tk.StringVar(value="template_masks")
self.status = tk.StringVar(value="Prêt.")
self.raster_dpi = tk.IntVar(value=200)
self.templates_dir = templates_dir
self.output_dir_name = output_dir_name
self.preview_dir_name = preview_dir_name
self.is_drawing = False
self.start_xy: Optional[Tuple[int,int]] = None
self._build_ui()
if initial_pdf:
self.open_pdf_path(initial_pdf)
if initial_template:
self.load_template_path(initial_template)
# UI layout
def _build_ui(self):
@@ -228,14 +250,17 @@ class MaskDesignerApp:
def open_pdf(self):
path = filedialog.askopenfilename(filetypes=[("PDF", "*.pdf")])
if not path: return
self.open_pdf_path(Path(path))
def open_pdf_path(self, path: Path):
try:
self.doc = fitz.open(path)
self.doc = fitz.open(str(path))
self.doc_path = Path(path)
self.curr_page = 0
self.masks.clear()
self.template_name.set(self.doc_path.stem + "_template")
self.refresh()
self.status.set(f"PDF ouvert : {Path(path).name}{len(self.doc)} page(s)")
self.status.set(f"PDF ouvert : {self.doc_path.name}{len(self.doc)} page(s)")
except Exception as e:
messagebox.showerror("Erreur", f"Impossible d'ouvrir le PDF : {e}")
@@ -244,7 +269,7 @@ class MaskDesignerApp:
img = page_pix(self.doc, self.curr_page, self.zoom)
# overlay current page masks
rects = self.masks.get(self.curr_page, [])
img_o = draw_overlay(img, rects, 1.0, self.curr_page)
img_o = draw_overlay(img, rects, self.zoom, self.curr_page)
self.curr_image = img_o
self.tk_image = ImageTk.PhotoImage(img_o)
self.canvas.delete("all")
@@ -269,19 +294,25 @@ class MaskDesignerApp:
def on_down(self, ev):
if not self.doc: return
self.is_drawing = True
self.start_xy = (ev.x, ev.y)
self._preview_rect = self.canvas.create_rectangle(ev.x, ev.y, ev.x, ev.y, outline="#000", width=2)
x = self.canvas.canvasx(ev.x)
y = self.canvas.canvasy(ev.y)
self.start_xy = (x, y)
self._preview_rect = self.canvas.create_rectangle(x, y, x, y, outline="#000", width=2)
def on_drag(self, ev):
if not self.doc or not self.is_drawing: return
sx, sy = self.start_xy
self.canvas.coords(self._preview_rect, sx, sy, ev.x, ev.y)
x = self.canvas.canvasx(ev.x)
y = self.canvas.canvasy(ev.y)
self.canvas.coords(self._preview_rect, sx, sy, x, y)
def on_up(self, ev):
if not self.doc or not self.is_drawing: return
self.is_drawing = False
sx, sy = self.start_xy
x0, y0, x1, y1 = rect_norm(sx, sy, ev.x, ev.y)
x = self.canvas.canvasx(ev.x)
y = self.canvas.canvasy(ev.y)
x0, y0, x1, y1 = rect_norm(sx, sy, x, y)
# convert screen px to PDF points
page = self.doc[self.curr_page]
# we rendered with zoom, but here current image is at display resolution (zoom applied in page_pix)
@@ -311,9 +342,12 @@ class MaskDesignerApp:
tpl = self._current_template()
except Exception as e:
messagebox.showwarning("Info", str(e)); return
path = filedialog.asksaveasfilename(defaultextension=".yml",
filetypes=[("YAML", "*.yml *.yaml"), ("JSON", "*.json")],
initialfile=f"{tpl.name}.yml")
path = filedialog.asksaveasfilename(
defaultextension=".yml",
filetypes=[("YAML", "*.yml *.yaml"), ("JSON", "*.json")],
initialdir=str(self._template_initialdir()),
initialfile=f"{tpl.name}.yml",
)
if not path: return
p = Path(path)
try:
@@ -326,8 +360,14 @@ class MaskDesignerApp:
messagebox.showerror("Erreur", f"Impossible d'écrire le template : {e}")
def load_template(self):
path = filedialog.askopenfilename(filetypes=[("YAML/JSON", "*.yml *.yaml *.json")])
path = filedialog.askopenfilename(
filetypes=[("YAML/JSON", "*.yml *.yaml *.json")],
initialdir=str(self._template_initialdir()),
)
if not path: return
self.load_template_path(Path(path))
def load_template_path(self, path: Path):
p = Path(path)
try:
if p.suffix.lower() in (".yml", ".yaml"):
@@ -351,6 +391,14 @@ class MaskDesignerApp:
self.refresh()
self.status.set(f"Masques de la page {self.curr_page+1} supprimés.")
def _template_initialdir(self) -> Path:
if self.templates_dir is not None:
self.templates_dir.mkdir(parents=True, exist_ok=True)
return self.templates_dir
if self.doc_path is not None:
return self.doc_path.parent
return Path.cwd()
# Preview / Apply
def _build_template_from_state(self) -> Optional[Template]:
if not self.doc:
@@ -365,7 +413,7 @@ class MaskDesignerApp:
if not samp: return
for i, s in enumerate(samp[:2], start=1):
pdf_in = Path(s)
out_dir = pdf_in.parent / "masked_preview"
out_dir = pdf_in.parent / self.preview_dir_name
out_dir.mkdir(exist_ok=True)
pdf_out = out_dir / f"{pdf_in.stem}.preview_vector.pdf"
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
@@ -373,7 +421,10 @@ class MaskDesignerApp:
apply_template_vector(pdf_in, pdf_out, tpl, audit)
except Exception as e:
messagebox.showerror("Erreur", f"Prévisualisation vectorielle échouée sur {pdf_in.name} : {e}")
messagebox.showinfo("Prévisualisation", "Terminé (vectoriel). Ouvrez le dossier 'masked_preview'.")
messagebox.showinfo(
"Prévisualisation",
f"Terminé (vectoriel). Ouvrez le dossier '{self.preview_dir_name}'.",
)
def preview_raster(self):
tpl = self._build_template_from_state()
@@ -383,7 +434,7 @@ class MaskDesignerApp:
dpi = int(self.raster_dpi.get())
for i, s in enumerate(samp[:2], start=1):
pdf_in = Path(s)
out_dir = pdf_in.parent / "masked_preview"
out_dir = pdf_in.parent / self.preview_dir_name
out_dir.mkdir(exist_ok=True)
pdf_out = out_dir / f"{pdf_in.stem}.preview_raster.pdf"
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
@@ -391,7 +442,10 @@ class MaskDesignerApp:
apply_template_raster(pdf_in, pdf_out, tpl, dpi, audit)
except Exception as e:
messagebox.showerror("Erreur", f"Prévisualisation raster échouée sur {pdf_in.name} : {e}")
messagebox.showinfo("Prévisualisation", "Terminé (raster). Ouvrez le dossier 'masked_preview'.")
messagebox.showinfo(
"Prévisualisation",
f"Terminé (raster). Ouvrez le dossier '{self.preview_dir_name}'.",
)
def apply_vector_batch(self):
tpl = self._build_template_from_state()
@@ -400,7 +454,7 @@ class MaskDesignerApp:
if not files: return
for s in files:
pdf_in = Path(s)
out_dir = pdf_in.parent / "masked"
out_dir = pdf_in.parent / self.output_dir_name
out_dir.mkdir(exist_ok=True)
pdf_out = out_dir / f"{pdf_in.stem}.masked_vector.pdf"
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
@@ -418,7 +472,7 @@ class MaskDesignerApp:
dpi = int(self.raster_dpi.get())
for s in files:
pdf_in = Path(s)
out_dir = pdf_in.parent / "masked"
out_dir = pdf_in.parent / self.output_dir_name
out_dir.mkdir(exist_ok=True)
pdf_out = out_dir / f"{pdf_in.stem}.masked_raster.pdf"
audit = out_dir / f"{pdf_in.stem}.audit.jsonl"
@@ -430,9 +484,27 @@ class MaskDesignerApp:
# ----------------------------- Main ------------------------------
def main():
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Editeur de masques PDF reutilisables")
parser.add_argument("--pdf", type=Path, help="PDF de reference a ouvrir au demarrage")
parser.add_argument("--template", type=Path, help="Template YAML/JSON a charger au demarrage")
parser.add_argument("--templates-dir", type=Path, help="Dossier par defaut pour sauver/charger les templates")
parser.add_argument("--output-dir-name", default=DEFAULT_MASK_OUTPUT_DIRNAME, help="Nom du dossier de sortie pour l'application des masques")
parser.add_argument("--preview-dir-name", default=DEFAULT_MASK_PREVIEW_DIRNAME, help="Nom du dossier de sortie pour les previsualisations")
return parser
def main(argv: Optional[List[str]] = None):
args = build_arg_parser().parse_args(argv)
root = tk.Tk()
app = MaskDesignerApp(root)
app = MaskDesignerApp(
root,
initial_pdf=args.pdf,
initial_template=args.template,
templates_dir=args.templates_dir,
output_dir_name=args.output_dir_name,
preview_dir_name=args.preview_dir_name,
)
root.mainloop()
if __name__ == "__main__":