Miscs
This commit is contained in:
@@ -2,8 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import threading
|
||||
import time
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox
|
||||
from collections.abc import Sequence
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
@@ -23,6 +23,7 @@ from copienator import (
|
||||
workspace_from_target,
|
||||
)
|
||||
from copienator.filesystem import staged_files
|
||||
from copienator.copy_errors import clear_copy_error, mark_copy_error, marked_copy_paths
|
||||
|
||||
DELIMITER_WIDTH = 5
|
||||
DELIMITER_COLOR = (0, 0, 0)
|
||||
@@ -156,6 +157,11 @@ class ImageReviewer:
|
||||
) -> None:
|
||||
self.files = files
|
||||
self.output_dir = output_dir
|
||||
self.workspace = EvaluationWorkspace(output_dir.parent)
|
||||
self.completed = False
|
||||
self.had_errors = False
|
||||
self.stop_prefetch = threading.Event()
|
||||
self.current_result = None
|
||||
self.index = 0
|
||||
self.current_shift = 0
|
||||
self.default_max_per_file = default_max_per_file
|
||||
@@ -174,6 +180,8 @@ class ImageReviewer:
|
||||
self.label_info = tk.Label(self.root, text="", font=("Arial", 12, "bold"))
|
||||
self.label_info.pack(pady=5)
|
||||
self.root.bind("<Return>", self.on_next)
|
||||
self.root.bind("s", self.on_skip)
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||
self.root.bind("n", lambda _event: self.on_shift(50))
|
||||
self.root.bind("N", lambda _event: self.on_shift(100))
|
||||
self.root.bind("t", lambda _event: self.on_shift(-50))
|
||||
@@ -194,20 +202,25 @@ class ImageReviewer:
|
||||
|
||||
def prefetch_worker(self) -> None:
|
||||
processed_index = -1
|
||||
while True:
|
||||
while not self.stop_prefetch.is_set():
|
||||
target = self.index + 1
|
||||
if target < len(self.files) and target != processed_index:
|
||||
get_pdf_pages(self.files[target])
|
||||
try:
|
||||
get_pdf_pages(self.files[target])
|
||||
except Exception:
|
||||
pass # The foreground review reports and flags conversion errors.
|
||||
processed_index = target
|
||||
time.sleep(0.05)
|
||||
self.stop_prefetch.wait(0.05)
|
||||
|
||||
def load_current_image(self) -> None:
|
||||
if self.index >= len(self.files):
|
||||
print("All files processed.")
|
||||
self.root.destroy()
|
||||
self.completed = True
|
||||
self.on_close()
|
||||
return
|
||||
self.is_processing = False
|
||||
self.current_shift = 0
|
||||
self.current_result = None
|
||||
self.trigger_processing(self.files[self.index], self.current_shift)
|
||||
|
||||
def trigger_processing(self, pdf_path: Path, shift: int) -> None:
|
||||
@@ -228,13 +241,13 @@ class ImageReviewer:
|
||||
def check_manual_queue(self, pdf_path: Path) -> None:
|
||||
try:
|
||||
result = self.manual_queue.get_nowait()
|
||||
self.is_processing = False
|
||||
if result is None:
|
||||
print(f"Failed to process {pdf_path.name}, skipping.")
|
||||
self.index += 1
|
||||
self.load_current_image()
|
||||
self._mark_error(pdf_path, "Échec de la conversion pour la découpe des marges")
|
||||
self._advance()
|
||||
else:
|
||||
self.handle_processing_result(result, pdf_path)
|
||||
self.is_processing = False
|
||||
except Empty:
|
||||
self.root.after(100, lambda: self.check_manual_queue(pdf_path))
|
||||
|
||||
@@ -244,7 +257,7 @@ class ImageReviewer:
|
||||
pdf_path: Path,
|
||||
) -> None:
|
||||
self.current_preview = result[0]
|
||||
save_results(result, pdf_path, self.output_dir)
|
||||
self.current_result = result
|
||||
self.update_display(pdf_path.name, result[2])
|
||||
|
||||
def update_display(self, filename: str, schema: dict[str, object]) -> None:
|
||||
@@ -258,7 +271,7 @@ class ImageReviewer:
|
||||
f"[{self.index + 1}/{len(self.files)}] {filename} | "
|
||||
f"Shift: {self.current_shift}px\nFiles: {schema['number_of_files']} | "
|
||||
f"Cols: {schema['columns_per_file']}\n"
|
||||
"Enter: Next | n: +50 | N: +100 | t: -50 | "
|
||||
"Enter: Save and next | s: flag error and skip | n: +50 | N: +100 | t: -50 | "
|
||||
"1: use single column"
|
||||
),
|
||||
fg="black",
|
||||
@@ -272,8 +285,34 @@ class ImageReviewer:
|
||||
self.trigger_processing(self.files[self.index], self.current_shift)
|
||||
|
||||
def on_next(self, _event: object) -> None:
|
||||
if self.is_processing:
|
||||
if self.is_processing or self.current_result is None:
|
||||
return
|
||||
pdf_path = self.files[self.index]
|
||||
try:
|
||||
save_results(self.current_result, pdf_path, self.output_dir)
|
||||
clear_copy_error(self.workspace, pdf_path)
|
||||
except Exception as exc:
|
||||
self._mark_error(pdf_path, f"Échec de l’enregistrement : {exc}")
|
||||
messagebox.showerror("Enregistrement impossible", str(exc), parent=self.root)
|
||||
return
|
||||
self._advance()
|
||||
|
||||
def _mark_error(self, pdf_path: Path, reason: str) -> None:
|
||||
mark_copy_error(self.workspace, pdf_path, reason)
|
||||
self.had_errors = True
|
||||
print(f"[Copie signalée] {pdf_path.name}: {reason}")
|
||||
|
||||
def on_skip(self, _event=None) -> None:
|
||||
if self.is_processing or self.index >= len(self.files):
|
||||
return
|
||||
self._mark_error(self.files[self.index], "Problème repéré pendant la découpe des marges")
|
||||
self._advance()
|
||||
|
||||
def on_close(self) -> None:
|
||||
self.stop_prefetch.set()
|
||||
self.root.destroy()
|
||||
|
||||
def _advance(self) -> None:
|
||||
self.index += 1
|
||||
self.current_shift = 0
|
||||
self.current_max_per_file = self.default_max_per_file
|
||||
@@ -304,23 +343,27 @@ def run(
|
||||
target: Path,
|
||||
*,
|
||||
fullpage: bool = False,
|
||||
marked: bool = False,
|
||||
) -> ExitCode:
|
||||
files = _selected_files(workspace, target)
|
||||
files = marked_copy_paths(workspace) if marked else _selected_files(workspace, target)
|
||||
if not files:
|
||||
print("No PDF files found.")
|
||||
return ExitCode.SUCCESS
|
||||
workspace.cutleft_dir.mkdir(parents=True, exist_ok=True)
|
||||
_get_pdf_pages_cached.cache_clear()
|
||||
ImageReviewer(
|
||||
reviewer = ImageReviewer(
|
||||
files,
|
||||
workspace.cutleft_dir,
|
||||
default_max_per_file=1 if fullpage else 5,
|
||||
)
|
||||
return ExitCode.SUCCESS
|
||||
if not reviewer.completed:
|
||||
return ExitCode.INTERRUPTED
|
||||
return ExitCode.PARTIAL if reviewer.had_errors else ExitCode.SUCCESS
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = target_parser("Interactively crop the label margin from PDF copies")
|
||||
parser.add_argument("--marked", action="store_true", help="Review flagged copies and clear each flag after saving with Enter")
|
||||
parser.add_argument(
|
||||
"--fullpage",
|
||||
action="store_true",
|
||||
@@ -334,11 +377,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
|
||||
def handle(args: argparse.Namespace) -> ExitCode:
|
||||
workspace, target = workspace_from_target(args)
|
||||
return run(workspace, target, fullpage=args.fullpage)
|
||||
return run(workspace, target, fullpage=args.fullpage, marked=args.marked)
|
||||
|
||||
return execute(parser, argv, handle)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user