diff --git a/PythonProj/ScanOCR/build/build_package.py b/PythonProj/ScanOCR/build/build_package.py new file mode 100644 index 00000000..6e9db456 --- /dev/null +++ b/PythonProj/ScanOCR/build/build_package.py @@ -0,0 +1,161 @@ +# -*- 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, + ) + + +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) + + extract_dir = tmp_dir / "tesseract-extract" + extract_dir.mkdir() + subprocess.run( + ["7z", "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()