diff --git a/build_exe.py b/build_exe.py index 75b03e5..c906f49 100644 --- a/build_exe.py +++ b/build_exe.py @@ -1,63 +1,99 @@ #!/usr/bin/env python3 """ -打包脚本 - 将桌面程序打包成独立可执行文件 -使用方法: pip install pyinstaller && python build_exe.py +打包脚本 - 将桌面程序打包成可离线分发的目录包 +使用方法: python build_exe.py [--debug] -调试版本: python build_exe.py --debug +产出: dist/信封信息提取系统/ + ├── 信封信息提取系统.exe + ├── _internal/ (运行时依赖) + └── models/ (OCR 模型,需提前通过 prepare_models.py 准备) """ import subprocess import sys +import shutil from pathlib import Path -PROJECT_ROOT = Path(__file__).parent) +PROJECT_ROOT = Path(__file__).parent +DIST_NAME = "信封信息提取系统" def build(debug=False): - """使用 PyInstaller 打包""" + """使用 PyInstaller 打包(onedir 模式)""" print("正在打包,请稍候...") print(f"工作目录: {PROJECT_ROOT}") + print(f"模式: {'调试(带控制台)' if debug else '正式(无控制台)'}") print("-" * 50) - # 使用 Python -m PyInstaller 方式 - # --paths 将 src 目录添加到 Python 路径,避免导入问题 cmd = [ sys.executable, "-m", "PyInstaller", - "--name=信封信息提取系统", - "--onefile", - "--clean", + f"--name={DIST_NAME}", + "--onedir", "--noconfirm", "--paths=src", + # --- hidden imports --- + "--hidden-import=cv2", + "--hidden-import=PIL", + "--hidden-import=processor", + "--hidden-import=ocr_offline", + "--hidden-import=paddleocr", + "--hidden-import=paddle", + # --- 收集 paddleocr 全部数据(模型配置、字典等) --- + "--collect-all=paddleocr", + # --- 元数据(部分库在运行时通过 importlib.metadata 查版本) --- + "--copy-metadata=paddlepaddle", + "--copy-metadata=paddleocr", ] - # 调试模式:显示控制台窗口,便于查看错误 if not debug: cmd.append("--windowed") + else: + cmd.append("--console") cmd.append("src/desktop.py") try: subprocess.run(cmd, check=True, cwd=str(PROJECT_ROOT)) - print("-" * 50) - print("打包完成!") - exe_path = PROJECT_ROOT / "dist" / "信封信息提取系统.exe" - if exe_path.exists(): - size_mb = exe_path.stat().st_size / 1024 / 1024 - print(f"可执行文件: {exe_path}") - print(f"文件大小: {size_mb:.1f} MB") - else: - print("警告: 未找到输出文件") except subprocess.CalledProcessError as e: print("-" * 50) print(f"打包失败: {e}") sys.exit(1) except FileNotFoundError: print("-" * 50) - print("错误: 未找到 PyInstaller") - print("请先安装: pip install pyinstaller") + print("错误: 未找到 PyInstaller,请先安装: pip install pyinstaller") sys.exit(1) + print("-" * 50) + + # 复制 models/ 到输出目录(与 exe 同级) + dist_dir = PROJECT_ROOT / "dist" / DIST_NAME + models_src = PROJECT_ROOT / "models" + models_dst = dist_dir / "models" + + if models_src.exists() and any(models_src.rglob("*.pdmodel")): + print(f"复制离线模型: {models_src} -> {models_dst}") + if models_dst.exists(): + shutil.rmtree(models_dst) + shutil.copytree(models_src, models_dst) + else: + print("⚠️ 未找到离线模型。请先执行:") + print(" python scripts/prepare_models.py --models-dir models") + print(" 然后重新打包,或手动将 models/ 放到 dist 目录中。") + + # 统计大小 + exe_path = dist_dir / f"{DIST_NAME}.exe" + if exe_path.exists(): + folder_size = sum( + f.stat().st_size for f in dist_dir.rglob("*") if f.is_file() + ) / 1024 / 1024 + print(f"\n打包完成!") + print(f"输出目录: {dist_dir}") + print(f"总大小: {folder_size:.1f} MB") + print(f"\n分发方式: 将 {dist_dir.name}/ 整个文件夹打成 zip 即可。") + else: + print("警告: 未找到输出的 exe 文件") + if __name__ == "__main__": debug = "--debug" in sys.argv diff --git a/src/ocr_offline.py b/src/ocr_offline.py index 2b3ad3f..552773d 100644 --- a/src/ocr_offline.py +++ b/src/ocr_offline.py @@ -73,7 +73,8 @@ def create_offline_ocr(models_base_dir: Path | None = None): """ 创建 PaddleOCR 2.x 实例(PP-OCRv4 中文)。 - 首次运行会自动下载模型到 ~/.paddleocr/whl/。 + - 打包态:使用 models/ 目录下的离线模型(完全离线) + - 开发态:首次运行自动下载到 ~/.paddleocr/whl/ """ log = logging.getLogger("post_ocr.ocr") @@ -83,11 +84,22 @@ def create_offline_ocr(models_base_dir: Path | None = None): log.info("create_offline_ocr: importing paddleocr") from paddleocr import PaddleOCR + # 构建 PaddleOCR 参数 + kwargs = dict(lang="ch", use_angle_cls=False, show_log=False) + + # 如果 models/ 目录存在离线模型,显式指定路径(打包分发场景) + models_dir = models_base_dir or get_models_base_dir() + det_dir = models_dir / "ch_PP-OCRv4_det_infer" + rec_dir = models_dir / "ch_PP-OCRv4_rec_infer" + + if (det_dir / "inference.pdmodel").exists() and (rec_dir / "inference.pdmodel").exists(): + log.info("使用离线模型: %s", models_dir) + kwargs["det_model_dir"] = str(det_dir) + kwargs["rec_model_dir"] = str(rec_dir) + else: + log.info("未找到离线模型,将使用默认路径(可能需要联网下载)") + log.info("create_offline_ocr: creating PaddleOCR(lang=ch)") - ocr = PaddleOCR( - lang="ch", - use_angle_cls=False, - show_log=False, - ) + ocr = PaddleOCR(**kwargs) log.info("create_offline_ocr: PaddleOCR created") return ocr