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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
テンプレート(赤枠=氏名, 青枠=日付)を基準に、対象フォルダ内のスキャンPDFから
|
テンプレート(赤枠=氏名, 青枠=日付)を基準に、"スキャン"フォルダ内のPDFから
|
||||||
氏名・日付をOCR抽出し、"output/氏名/YYYYMMDD_氏名.pdf" にリネーム・移動する。
|
氏名・日付をOCR抽出し、"アウトプット/氏名/氏名_YYYYMMDD.pdf" にコピー、
|
||||||
|
元ファイルは"成功"(または"失敗")フォルダへ移動する。
|
||||||
|
|
||||||
使い方:
|
起動は OCR仕分け実行.bat から行う想定(config.txt の固定フォルダ構成に依存)。
|
||||||
python extract_and_rename.py \
|
|
||||||
--template template.png \
|
|
||||||
--input ./inbox \
|
|
||||||
--output ./output \
|
|
||||||
--failed ./failed \
|
|
||||||
--margin 0.10
|
|
||||||
|
|
||||||
必要ライブラリ: opencv-python, pdf2image, pytesseract, pillow, numpy
|
必要ライブラリ: opencv-python, pdf2image, pytesseract, pillow, numpy
|
||||||
必要な外部ツール: tesseract-ocr, tesseract-ocr-jpn, poppler-utils(pdftoppm)
|
必要な外部ツール: tesseract-ocr, tesseract-ocr-jpn, poppler-utils(pdftoppm)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import importlib
|
||||||
import re
|
import re
|
||||||
import shutil
|
import time
|
||||||
import sys
|
import traceback
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -27,11 +22,13 @@ from pdf2image import convert_from_path
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from box_detector import detect_template_fields, to_ratio_box
|
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で作成/スキャンしてください
|
DPI = 300 # PDF→画像変換の解像度。テンプレートも同じDPIで作成/スキャンしてください
|
||||||
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
TEMPLATE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp"}
|
TEMPLATE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp"}
|
||||||
|
|
||||||
|
|
||||||
@ -69,13 +66,6 @@ def check_external_tools(tesseract_exe: Path, poppler_exe: Path) -> list[str]:
|
|||||||
return missing
|
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:
|
def key_pressed() -> bool:
|
||||||
import msvcrt
|
import msvcrt
|
||||||
|
|
||||||
@ -231,77 +221,84 @@ def run_queue(
|
|||||||
append_log(log_dir, "成功", pdf_path.name, name, date, "")
|
append_log(log_dir, "成功", pdf_path.name, name, date, "")
|
||||||
|
|
||||||
|
|
||||||
def safe_move(src: Path, dest_dir: Path, filename: str) -> Path:
|
HOME_DIR = Path(__file__).resolve().parent.parent
|
||||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
RUNTIME_FOLDER_KEYS = (
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser(description="テンプレート枠を基準にPDFから氏名・日付を抽出してリネーム・移動する")
|
config_path = HOME_DIR / "config.txt"
|
||||||
parser.add_argument("--template", required=True, help="赤枠(氏名)・青枠(日付)付きテンプレート画像")
|
try:
|
||||||
parser.add_argument("--input", required=True, help="処理対象PDFが入ったフォルダ")
|
config = load_config(config_path)
|
||||||
parser.add_argument("--output", required=True, help="成功時の出力先ルートフォルダ(氏名ごとにサブフォルダ作成)")
|
except Exception as e:
|
||||||
parser.add_argument("--failed", required=True, help="抽出失敗時の退避先フォルダ")
|
print(f"設定エラー: {e}")
|
||||||
parser.add_argument("--margin", type=float, default=0.10, help="枠の許容誤差率(デフォルト 0.10 = 10%%)")
|
input("何かキーを押すと終了します...")
|
||||||
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が見つかりませんでした。")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
results = []
|
folders = {key: HOME_DIR / config[key] for key in RUNTIME_FOLDER_KEYS}
|
||||||
for pdf_path in pdf_files:
|
for folder in folders.values():
|
||||||
print(f"処理中: {pdf_path.name}")
|
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:
|
try:
|
||||||
name, date = process_pdf(pdf_path, boxes, args.margin)
|
template_path = select_template_file(folders["テンプレートフォルダ"])
|
||||||
except Exception as e:
|
except FileNotFoundError as e:
|
||||||
print(f" エラー: {e}")
|
print(str(e))
|
||||||
safe_move(pdf_path, failed_dir, pdf_path.name)
|
return
|
||||||
results.append((pdf_path.name, None, None, "error", str(e)))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not name or not date:
|
print(f"テンプレート採用: {template_path.name}")
|
||||||
print(f" 抽出不十分(氏名={name!r}, 日付={date!r})→ failed へ退避")
|
boxes = load_field_boxes(str(template_path))
|
||||||
safe_move(pdf_path, failed_dir, pdf_path.name)
|
print(f"検出フィールド: {[b.label for b in boxes]}")
|
||||||
results.append((pdf_path.name, name, date, "failed", ""))
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_filename = f"{date}_{name}.pdf"
|
margin = float(config["margin"])
|
||||||
dest_dir = output_dir / name
|
stable_wait_sec = float(config["ファイル安定待ち秒"])
|
||||||
moved_path = safe_move(pdf_path, dest_dir, new_filename)
|
stable_retries = int(config["ファイル安定待ちリトライ回数"])
|
||||||
print(f" → {moved_path}")
|
|
||||||
results.append((pdf_path.name, name, date, "success", str(moved_path)))
|
|
||||||
|
|
||||||
# サマリー出力
|
run_queue(
|
||||||
print("\n=== 処理結果サマリー ===")
|
folders["スキャンフォルダ"],
|
||||||
for orig, name, date, status, note in results:
|
folders["アウトプットフォルダ"],
|
||||||
print(f"{status:8s} | {orig:30s} | 氏名={name} 日付={date} | {note}")
|
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("処理完了。")
|
||||||
print(f"\n合計 {len(results)} 件中 {success_count} 件成功")
|
input("何かキーを押すと終了します...")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user