実機実行時、7-ZipはインストールされているがPATHに7zコマンドが無くFileNotFoundErrorで失敗したため、 C:\Program Files\7-Zip\7z.exe 等へのフォールバック探索を追加。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
181 lines
5.4 KiB
Python
181 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
配布パッケージ生成スクリプト。
|
||
|
||
開発者PC上で1回実行すると、dist/ScanOCR/ 配下に
|
||
Python embeddable版・依存pipパッケージ・tesseract-ocr・poppler一式を含む
|
||
配布用フォルダを生成する。
|
||
|
||
前提:
|
||
- ネットワーク接続必須
|
||
- 7-Zip(7zコマンド)がPATHに通っていること(tesseractインストーラー展開用)
|
||
|
||
使い方:
|
||
python build/build_package.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import shutil
|
||
import subprocess
|
||
import tempfile
|
||
import urllib.request
|
||
import zipfile
|
||
from pathlib import Path
|
||
|
||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||
DIST_DIR = REPO_ROOT / "dist" / "ScanOCR"
|
||
|
||
PYTHON_VERSION = "3.11.9"
|
||
PYTHON_EMBED_URL = (
|
||
f"https://www.python.org/ftp/python/{PYTHON_VERSION}/"
|
||
f"python-{PYTHON_VERSION}-embed-amd64.zip"
|
||
)
|
||
GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
|
||
PIP_PACKAGES = ["opencv-python", "pdf2image", "pytesseract", "pillow", "numpy"]
|
||
|
||
TESSERACT_VERSION = "5.4.0.20240606"
|
||
TESSERACT_INSTALLER_URL = (
|
||
"https://github.com/UB-Mannheim/tesseract/releases/download/"
|
||
f"v{TESSERACT_VERSION}/tesseract-ocr-w64-setup-{TESSERACT_VERSION}.exe"
|
||
)
|
||
|
||
POPPLER_VERSION = "24.02.0-0"
|
||
POPPLER_ZIP_URL = (
|
||
"https://github.com/oschwartz10612/poppler-windows/releases/download/"
|
||
f"v{POPPLER_VERSION}/Release-{POPPLER_VERSION}.zip"
|
||
)
|
||
|
||
CODE_FILES = (
|
||
"extract_and_rename.py",
|
||
"box_detector.py",
|
||
"config_loader.py",
|
||
"file_ops.py",
|
||
"lock_manager.py",
|
||
"logger.py",
|
||
)
|
||
|
||
RUNTIME_FOLDERS = ("テンプレート", "スキャン", "アウトプット", "成功", "失敗", "ログ")
|
||
|
||
|
||
def download(url: str, dest: Path) -> None:
|
||
print(f"ダウンロード: {url}")
|
||
urllib.request.urlretrieve(url, dest)
|
||
|
||
|
||
def prepare_dist_dir() -> None:
|
||
if DIST_DIR.exists():
|
||
shutil.rmtree(DIST_DIR)
|
||
DIST_DIR.mkdir(parents=True)
|
||
|
||
|
||
def copy_code_files() -> Path:
|
||
script_dst = DIST_DIR / "スクリプト"
|
||
script_dst.mkdir(parents=True)
|
||
for name in CODE_FILES:
|
||
shutil.copy2(REPO_ROOT / "スクリプト" / name, script_dst / name)
|
||
shutil.copy2(REPO_ROOT / "OCR仕分け実行.bat", DIST_DIR / "OCR仕分け実行.bat")
|
||
shutil.copy2(REPO_ROOT / "config.txt", DIST_DIR / "config.txt")
|
||
return script_dst
|
||
|
||
|
||
def setup_python(script_dst: Path, tmp_dir: Path) -> None:
|
||
python_dir = script_dst / "python"
|
||
python_dir.mkdir(parents=True)
|
||
|
||
zip_path = tmp_dir / "python-embed.zip"
|
||
download(PYTHON_EMBED_URL, zip_path)
|
||
with zipfile.ZipFile(zip_path) as zf:
|
||
zf.extractall(python_dir)
|
||
|
||
pth_files = list(python_dir.glob("python*._pth"))
|
||
if not pth_files:
|
||
raise RuntimeError("python*._pth が見つかりません(embeddable zipの構成が変わった可能性)")
|
||
pth_path = pth_files[0]
|
||
content = pth_path.read_text(encoding="utf-8")
|
||
content = content.replace("#import site", "import site")
|
||
pth_path.write_text(content, encoding="utf-8")
|
||
|
||
get_pip_path = tmp_dir / "get-pip.py"
|
||
download(GET_PIP_URL, get_pip_path)
|
||
python_exe = python_dir / "python.exe"
|
||
subprocess.run([str(python_exe), str(get_pip_path)], check=True)
|
||
subprocess.run(
|
||
[str(python_exe), "-m", "pip", "install", *PIP_PACKAGES],
|
||
check=True,
|
||
)
|
||
|
||
|
||
SEVEN_ZIP_FALLBACK_PATHS = (
|
||
r"C:\Program Files\7-Zip\7z.exe",
|
||
r"C:\Program Files (x86)\7-Zip\7z.exe",
|
||
)
|
||
|
||
|
||
def find_7zip_exe() -> str:
|
||
found = shutil.which("7z")
|
||
if found:
|
||
return found
|
||
for candidate in SEVEN_ZIP_FALLBACK_PATHS:
|
||
if Path(candidate).exists():
|
||
return candidate
|
||
raise RuntimeError(
|
||
"7-Zip(7z.exe)が見つかりません。PATHに通すか、標準的な場所にインストールしてください。"
|
||
)
|
||
|
||
|
||
def setup_tesseract(script_dst: Path, tmp_dir: Path) -> None:
|
||
tools_dst = script_dst / "tools" / "tesseract"
|
||
tools_dst.mkdir(parents=True)
|
||
|
||
installer_path = tmp_dir / "tesseract-setup.exe"
|
||
download(TESSERACT_INSTALLER_URL, installer_path)
|
||
|
||
seven_zip_exe = find_7zip_exe()
|
||
extract_dir = tmp_dir / "tesseract-extract"
|
||
extract_dir.mkdir()
|
||
subprocess.run(
|
||
[seven_zip_exe, "x", str(installer_path), f"-o{extract_dir}", "-y"],
|
||
check=True,
|
||
)
|
||
for item in extract_dir.iterdir():
|
||
shutil.move(str(item), str(tools_dst / item.name))
|
||
|
||
|
||
def setup_poppler(script_dst: Path, tmp_dir: Path) -> None:
|
||
tools_parent = script_dst / "tools"
|
||
tools_parent.mkdir(parents=True, exist_ok=True)
|
||
tools_dst = tools_parent / "poppler"
|
||
|
||
zip_path = tmp_dir / "poppler.zip"
|
||
download(POPPLER_ZIP_URL, zip_path)
|
||
extract_dir = tmp_dir / "poppler-extract"
|
||
with zipfile.ZipFile(zip_path) as zf:
|
||
zf.extractall(extract_dir)
|
||
|
||
inner_items = list(extract_dir.iterdir())
|
||
source_dir = inner_items[0] if len(inner_items) == 1 else extract_dir
|
||
shutil.move(str(source_dir), str(tools_dst))
|
||
|
||
|
||
def create_runtime_folders() -> None:
|
||
for name in RUNTIME_FOLDERS:
|
||
(DIST_DIR / name).mkdir(parents=True, exist_ok=True)
|
||
|
||
|
||
def main() -> None:
|
||
prepare_dist_dir()
|
||
script_dst = copy_code_files()
|
||
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
tmp_dir = Path(tmp)
|
||
setup_python(script_dst, tmp_dir)
|
||
setup_tesseract(script_dst, tmp_dir)
|
||
setup_poppler(script_dst, tmp_dir)
|
||
|
||
create_runtime_folders()
|
||
print(f"配布パッケージ生成完了: {DIST_DIR}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|