feat: 固定フォルダ構成・ロック・ログ統合のmain()に置き換え

旧CLI引数型(argparse)を廃止し、config.txt駆動の固定フォルダ運用に一本化。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kenichiro NOGI 2026-08-02 10:14:37 +09:00
parent 0a0bb057e2
commit 1d283eb1c6

View File

@ -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__":