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:
80
build_exe.py
80
build_exe.py
@@ -1,63 +1,99 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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 subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).parent)
|
PROJECT_ROOT = Path(__file__).parent
|
||||||
|
DIST_NAME = "信封信息提取系统"
|
||||||
|
|
||||||
|
|
||||||
def build(debug=False):
|
def build(debug=False):
|
||||||
"""使用 PyInstaller 打包"""
|
"""使用 PyInstaller 打包(onedir 模式)"""
|
||||||
|
|
||||||
print("正在打包,请稍候...")
|
print("正在打包,请稍候...")
|
||||||
print(f"工作目录: {PROJECT_ROOT}")
|
print(f"工作目录: {PROJECT_ROOT}")
|
||||||
|
print(f"模式: {'调试(带控制台)' if debug else '正式(无控制台)'}")
|
||||||
print("-" * 50)
|
print("-" * 50)
|
||||||
|
|
||||||
# 使用 Python -m PyInstaller 方式
|
|
||||||
# --paths 将 src 目录添加到 Python 路径,避免导入问题
|
|
||||||
cmd = [
|
cmd = [
|
||||||
sys.executable,
|
sys.executable,
|
||||||
"-m", "PyInstaller",
|
"-m", "PyInstaller",
|
||||||
"--name=信封信息提取系统",
|
f"--name={DIST_NAME}",
|
||||||
"--onefile",
|
"--onedir",
|
||||||
"--clean",
|
|
||||||
"--noconfirm",
|
"--noconfirm",
|
||||||
"--paths=src",
|
"--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:
|
if not debug:
|
||||||
cmd.append("--windowed")
|
cmd.append("--windowed")
|
||||||
|
else:
|
||||||
|
cmd.append("--console")
|
||||||
|
|
||||||
cmd.append("src/desktop.py")
|
cmd.append("src/desktop.py")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(cmd, check=True, cwd=str(PROJECT_ROOT))
|
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:
|
except subprocess.CalledProcessError as e:
|
||||||
print("-" * 50)
|
print("-" * 50)
|
||||||
print(f"打包失败: {e}")
|
print(f"打包失败: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("-" * 50)
|
print("-" * 50)
|
||||||
print("错误: 未找到 PyInstaller")
|
print("错误: 未找到 PyInstaller,请先安装: pip install pyinstaller")
|
||||||
print("请先安装: pip install pyinstaller")
|
|
||||||
sys.exit(1)
|
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__":
|
if __name__ == "__main__":
|
||||||
debug = "--debug" in sys.argv
|
debug = "--debug" in sys.argv
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ def create_offline_ocr(models_base_dir: Path | None = None):
|
|||||||
"""
|
"""
|
||||||
创建 PaddleOCR 2.x 实例(PP-OCRv4 中文)。
|
创建 PaddleOCR 2.x 实例(PP-OCRv4 中文)。
|
||||||
|
|
||||||
首次运行会自动下载模型到 ~/.paddleocr/whl/。
|
- 打包态:使用 models/ 目录下的离线模型(完全离线)
|
||||||
|
- 开发态:首次运行自动下载到 ~/.paddleocr/whl/
|
||||||
"""
|
"""
|
||||||
log = logging.getLogger("post_ocr.ocr")
|
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")
|
log.info("create_offline_ocr: importing paddleocr")
|
||||||
from paddleocr import 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)")
|
log.info("create_offline_ocr: creating PaddleOCR(lang=ch)")
|
||||||
ocr = PaddleOCR(
|
ocr = PaddleOCR(**kwargs)
|
||||||
lang="ch",
|
|
||||||
use_angle_cls=False,
|
|
||||||
show_log=False,
|
|
||||||
)
|
|
||||||
log.info("create_offline_ocr: PaddleOCR created")
|
log.info("create_offline_ocr: PaddleOCR created")
|
||||||
return ocr
|
return ocr
|
||||||
|
|||||||
Reference in New Issue
Block a user