From 1d283eb1c60671dc816d971b2e0cd4eeddbcd7ba Mon Sep 17 00:00:00 2001 From: Kenichiro NOGI Date: Sun, 2 Aug 2026 10:14:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BA=E5=AE=9A=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=AB=E3=83=80=E6=A7=8B=E6=88=90=E3=83=BB=E3=83=AD=E3=83=83?= =?UTF-8?q?=E3=82=AF=E3=83=BB=E3=83=AD=E3=82=B0=E7=B5=B1=E5=90=88=E3=81=AE?= =?UTF-8?q?main()=E3=81=AB=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧CLI引数型(argparse)を廃止し、config.txt駆動の固定フォルダ運用に一本化。 Co-Authored-By: Claude Sonnet 5 --- .../ScanOCR/スクリプト/extract_and_rename.py | 163 +++++++++--------- 1 file changed, 80 insertions(+), 83 deletions(-) diff --git a/PythonProj/ScanOCR/スクリプト/extract_and_rename.py b/PythonProj/ScanOCR/スクリプト/extract_and_rename.py index 8328e960..5a8bcec6 100644 --- a/PythonProj/ScanOCR/スクリプト/extract_and_rename.py +++ b/PythonProj/ScanOCR/スクリプト/extract_and_rename.py @@ -1,24 +1,19 @@ # -*- coding: utf-8 -*- """ -テンプレート(赤枠=氏名, 青枠=日付)を基準に、対象フォルダ内のスキャンPDFから -氏名・日付をOCR抽出し、"output/氏名/YYYYMMDD_氏名.pdf" にリネーム・移動する。 +テンプレート(赤枠=氏名, 青枠=日付)を基準に、"スキャン"フォルダ内のPDFから +氏名・日付をOCR抽出し、"アウトプット/氏名/氏名_YYYYMMDD.pdf" にコピー、 +元ファイルは"成功"(または"失敗")フォルダへ移動する。 -使い方: - python extract_and_rename.py \ - --template template.png \ - --input ./inbox \ - --output ./output \ - --failed ./failed \ - --margin 0.10 +起動は OCR仕分け実行.bat から行う想定(config.txt の固定フォルダ構成に依存)。 必要ライブラリ: opencv-python, pdf2image, pytesseract, pillow, numpy 必要な外部ツール: tesseract-ocr, tesseract-ocr-jpn, poppler-utils(pdftoppm) """ -import argparse +import importlib import re -import shutil -import sys +import time +import traceback from dataclasses import dataclass from pathlib import Path @@ -27,11 +22,13 @@ from pdf2image import convert_from_path from PIL import Image from box_detector import detect_template_fields, to_ratio_box +from config_loader import load_config +from file_ops import copy_with_unique_name, move_with_unique_name, wait_until_stable +from lock_manager import LockAcquisitionError, acquire_lock, release_lock +from logger import append_log DPI = 300 # PDF→画像変換の解像度。テンプレートも同じDPIで作成/スキャンしてください -import importlib - TEMPLATE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp"} @@ -69,13 +66,6 @@ def check_external_tools(tesseract_exe: Path, poppler_exe: Path) -> list[str]: return missing -import time -import traceback - -from file_ops import copy_with_unique_name, move_with_unique_name, wait_until_stable -from logger import append_log - - def key_pressed() -> bool: import msvcrt @@ -231,77 +221,84 @@ def run_queue( append_log(log_dir, "成功", pdf_path.name, name, date, "") -def safe_move(src: Path, dest_dir: Path, filename: str) -> Path: - dest_dir.mkdir(parents=True, exist_ok=True) - dest_path = dest_dir / filename - # 同名ファイルが既にある場合は連番を付けて衝突を回避 - counter = 1 - while dest_path.exists(): - stem = Path(filename).stem - suffix = Path(filename).suffix - dest_path = dest_dir / f"{stem}_{counter}{suffix}" - counter += 1 - shutil.move(str(src), str(dest_path)) - return dest_path +HOME_DIR = Path(__file__).resolve().parent.parent +RUNTIME_FOLDER_KEYS = ( + "テンプレートフォルダ", + "スキャンフォルダ", + "アウトプットフォルダ", + "成功フォルダ", + "失敗フォルダ", + "ログフォルダ", +) -def main(): - parser = argparse.ArgumentParser(description="テンプレート枠を基準にPDFから氏名・日付を抽出してリネーム・移動する") - parser.add_argument("--template", required=True, help="赤枠(氏名)・青枠(日付)付きテンプレート画像") - parser.add_argument("--input", required=True, help="処理対象PDFが入ったフォルダ") - parser.add_argument("--output", required=True, help="成功時の出力先ルートフォルダ(氏名ごとにサブフォルダ作成)") - parser.add_argument("--failed", required=True, help="抽出失敗時の退避先フォルダ") - parser.add_argument("--margin", type=float, default=0.10, help="枠の許容誤差率(デフォルト 0.10 = 10%%)") - args = parser.parse_args() - - template_path = Path(args.template) - input_dir = Path(args.input) - output_dir = Path(args.output) - failed_dir = Path(args.failed) - - if not template_path.exists(): - sys.exit(f"テンプレート画像が見つかりません: {template_path}") - if not input_dir.exists(): - sys.exit(f"入力フォルダが見つかりません: {input_dir}") - - boxes = load_field_boxes(str(template_path)) - print(f"検出フィールド: {[b.label for b in boxes]}") - - pdf_files = sorted(input_dir.glob("*.pdf")) - if not pdf_files: - print("対象PDFが見つかりませんでした。") +def main() -> None: + config_path = HOME_DIR / "config.txt" + try: + config = load_config(config_path) + except Exception as e: + print(f"設定エラー: {e}") + input("何かキーを押すと終了します...") return - results = [] - for pdf_path in pdf_files: - print(f"処理中: {pdf_path.name}") + folders = {key: HOME_DIR / config[key] for key in RUNTIME_FOLDER_KEYS} + for folder in folders.values(): + folder.mkdir(parents=True, exist_ok=True) + + missing_modules = check_dependencies() + if missing_modules: + print(f"配布パッケージが壊れています。不足モジュール: {', '.join(missing_modules)}") + input("何かキーを押すと終了します...") + return + + script_dir = HOME_DIR / config["スクリプトフォルダ"] + tesseract_exe = script_dir / "tools" / "tesseract" / "tesseract.exe" + poppler_exe = script_dir / "tools" / "poppler" / "pdftoppm.exe" + missing_tools = check_external_tools(tesseract_exe, poppler_exe) + if missing_tools: + print(f"配布パッケージが壊れています。不足ファイル: {', '.join(missing_tools)}") + input("何かキーを押すと終了します...") + return + + lock_path = HOME_DIR / ".lock" + try: + acquire_lock(lock_path) + except LockAcquisitionError as e: + print(str(e)) + input("何かキーを押すと終了します...") + return + + try: try: - name, date = process_pdf(pdf_path, boxes, args.margin) - except Exception as e: - print(f" エラー: {e}") - safe_move(pdf_path, failed_dir, pdf_path.name) - results.append((pdf_path.name, None, None, "error", str(e))) - continue + template_path = select_template_file(folders["テンプレートフォルダ"]) + except FileNotFoundError as e: + print(str(e)) + return - if not name or not date: - print(f" 抽出不十分(氏名={name!r}, 日付={date!r})→ failed へ退避") - safe_move(pdf_path, failed_dir, pdf_path.name) - results.append((pdf_path.name, name, date, "failed", "")) - continue + print(f"テンプレート採用: {template_path.name}") + boxes = load_field_boxes(str(template_path)) + print(f"検出フィールド: {[b.label for b in boxes]}") - new_filename = f"{date}_{name}.pdf" - dest_dir = output_dir / name - moved_path = safe_move(pdf_path, dest_dir, new_filename) - print(f" → {moved_path}") - results.append((pdf_path.name, name, date, "success", str(moved_path))) + margin = float(config["margin"]) + stable_wait_sec = float(config["ファイル安定待ち秒"]) + stable_retries = int(config["ファイル安定待ちリトライ回数"]) - # サマリー出力 - print("\n=== 処理結果サマリー ===") - for orig, name, date, status, note in results: - print(f"{status:8s} | {orig:30s} | 氏名={name} 日付={date} | {note}") + run_queue( + folders["スキャンフォルダ"], + folders["アウトプットフォルダ"], + folders["成功フォルダ"], + folders["失敗フォルダ"], + folders["ログフォルダ"], + boxes, + margin, + stable_wait_sec, + stable_retries, + ) + finally: + release_lock(lock_path) - success_count = sum(1 for r in results if r[3] == "success") - print(f"\n合計 {len(results)} 件中 {success_count} 件成功") + print("処理完了。") + input("何かキーを押すと終了します...") if __name__ == "__main__":