# -*- 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()