feat: 固定フォルダ構成・ロック・ログ統合のmain()に置き換え
旧CLI引数型(argparse)を廃止し、config.txt駆動の固定フォルダ運用に一本化。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0a0bb057e2
commit
1d283eb1c6
@ -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()
|
||||
def main() -> None:
|
||||
config_path = HOME_DIR / "config.txt"
|
||||
try:
|
||||
config = load_config(config_path)
|
||||
except Exception as e:
|
||||
print(f"設定エラー: {e}")
|
||||
input("何かキーを押すと終了します...")
|
||||
return
|
||||
|
||||
template_path = Path(args.template)
|
||||
input_dir = Path(args.input)
|
||||
output_dir = Path(args.output)
|
||||
failed_dir = Path(args.failed)
|
||||
folders = {key: HOME_DIR / config[key] for key in RUNTIME_FOLDER_KEYS}
|
||||
for folder in folders.values():
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not template_path.exists():
|
||||
sys.exit(f"テンプレート画像が見つかりません: {template_path}")
|
||||
if not input_dir.exists():
|
||||
sys.exit(f"入力フォルダが見つかりません: {input_dir}")
|
||||
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:
|
||||
template_path = select_template_file(folders["テンプレートフォルダ"])
|
||||
except FileNotFoundError as e:
|
||||
print(str(e))
|
||||
return
|
||||
|
||||
print(f"テンプレート採用: {template_path.name}")
|
||||
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が見つかりませんでした。")
|
||||
return
|
||||
margin = float(config["margin"])
|
||||
stable_wait_sec = float(config["ファイル安定待ち秒"])
|
||||
stable_retries = int(config["ファイル安定待ちリトライ回数"])
|
||||
|
||||
results = []
|
||||
for pdf_path in pdf_files:
|
||||
print(f"処理中: {pdf_path.name}")
|
||||
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
|
||||
run_queue(
|
||||
folders["スキャンフォルダ"],
|
||||
folders["アウトプットフォルダ"],
|
||||
folders["成功フォルダ"],
|
||||
folders["失敗フォルダ"],
|
||||
folders["ログフォルダ"],
|
||||
boxes,
|
||||
margin,
|
||||
stable_wait_sec,
|
||||
stable_retries,
|
||||
)
|
||||
finally:
|
||||
release_lock(lock_path)
|
||||
|
||||
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
|
||||
|
||||
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)))
|
||||
|
||||
# サマリー出力
|
||||
print("\n=== 処理結果サマリー ===")
|
||||
for orig, name, date, status, note in results:
|
||||
print(f"{status:8s} | {orig:30s} | 氏名={name} 日付={date} | {note}")
|
||||
|
||||
success_count = sum(1 for r in results if r[3] == "success")
|
||||
print(f"\n合計 {len(results)} 件中 {success_count} 件成功")
|
||||
print("処理完了。")
|
||||
input("何かキーを押すと終了します...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user