feat: 重写打包脚本,支持PaddleOCR 2.x离线分发

- build_exe.py: 适配2.x,onedir模式,自动复制models/到dist
- ocr_offline.py: 打包态自动检测并使用本地离线模型

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
let5sne.win10
2026-02-14 19:30:27 +08:00
parent bac1818ed0
commit dfbab1b61e
2 changed files with 76 additions and 28 deletions

View File

@@ -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

View File

@@ -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