387 lines
10 KiB
Python
387 lines
10 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
PDF书签合并工具 - 自动打包脚本
|
|||
|
|
使用PyInstaller将Python程序打包成独立的exe文件
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
import subprocess
|
|||
|
|
import shutil
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
|
|||
|
|
def check_pyinstaller():
|
|||
|
|
"""检查是否安装了PyInstaller"""
|
|||
|
|
try:
|
|||
|
|
subprocess.run([sys.executable, "-c", "import PyInstaller"],
|
|||
|
|
check=True, capture_output=True)
|
|||
|
|
print("✅ PyInstaller 已安装")
|
|||
|
|
return True
|
|||
|
|
except subprocess.CalledProcessError:
|
|||
|
|
print("❌ PyInstaller 未安装")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def install_pyinstaller():
|
|||
|
|
"""安装PyInstaller"""
|
|||
|
|
print("正在安装 PyInstaller...")
|
|||
|
|
try:
|
|||
|
|
subprocess.run([sys.executable, "-m", "pip", "install", "pyinstaller"],
|
|||
|
|
check=True)
|
|||
|
|
print("✅ PyInstaller 安装成功")
|
|||
|
|
return True
|
|||
|
|
except subprocess.CalledProcessError as ex:
|
|||
|
|
print(f"❌ PyInstaller 安装失败: {ex}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def clean_build():
|
|||
|
|
"""清理之前的构建"""
|
|||
|
|
print("🧹 清理之前的构建...")
|
|||
|
|
|
|||
|
|
folders_to_remove = ['build', 'dist', '__pycache__']
|
|||
|
|
files_to_remove = ['SlideCombine.spec']
|
|||
|
|
|
|||
|
|
for folder in folders_to_remove:
|
|||
|
|
if os.path.exists(folder):
|
|||
|
|
shutil.rmtree(folder)
|
|||
|
|
print(f" 已删除文件夹: {folder}")
|
|||
|
|
|
|||
|
|
for file in files_to_remove:
|
|||
|
|
if os.path.exists(file):
|
|||
|
|
os.remove(file)
|
|||
|
|
print(f" 已删除文件: {file}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def create_spec_file():
|
|||
|
|
"""创建PyInstaller配置文件"""
|
|||
|
|
spec_content = '''
|
|||
|
|
# -*- mode: python ; coding: utf-8 -*-
|
|||
|
|
|
|||
|
|
block_cipher = None
|
|||
|
|
|
|||
|
|
a = Analysis(
|
|||
|
|
['slide_combine.py'],
|
|||
|
|
pathex=[],
|
|||
|
|
binaries=[],
|
|||
|
|
datas=[],
|
|||
|
|
hiddenimports=[],
|
|||
|
|
hookspath=[],
|
|||
|
|
hooksconfig={},
|
|||
|
|
runtime_hooks=[],
|
|||
|
|
excludes=[],
|
|||
|
|
win_no_prefer_redirects=False,
|
|||
|
|
win_private_assemblies=False,
|
|||
|
|
cipher=block_cipher,
|
|||
|
|
noarchive=False,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|||
|
|
|
|||
|
|
exe = EXE(
|
|||
|
|
pyz,
|
|||
|
|
a.scripts,
|
|||
|
|
a.binaries,
|
|||
|
|
a.zipfiles,
|
|||
|
|
a.datas,
|
|||
|
|
[],
|
|||
|
|
name='SlideCombine',
|
|||
|
|
debug=False,
|
|||
|
|
bootloader_ignore_signals=False,
|
|||
|
|
strip=False,
|
|||
|
|
upx=True,
|
|||
|
|
upx_exclude=[],
|
|||
|
|
runtime_tmpdir=None,
|
|||
|
|
console=False,
|
|||
|
|
disable_windowed_traceback=False,
|
|||
|
|
argv_emulation=False,
|
|||
|
|
target_arch=None,
|
|||
|
|
codesign_identity=None,
|
|||
|
|
entitlements_file=None,
|
|||
|
|
icon='app.ico', # 图标文件(如果存在)
|
|||
|
|
version='version_info.txt' # 版本信息(如果存在)
|
|||
|
|
)
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
with open('SlideCombine.spec', 'w', encoding='utf-8') as f:
|
|||
|
|
f.write(spec_content)
|
|||
|
|
print("✅ 已创建 SlideCombine.spec 配置文件")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def create_version_info():
|
|||
|
|
"""创建版本信息文件"""
|
|||
|
|
version_info = '''
|
|||
|
|
# UTF-8
|
|||
|
|
#
|
|||
|
|
# For more details about fixed file info 'ffi' see:
|
|||
|
|
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
|
|||
|
|
VSVersionInfo(
|
|||
|
|
ffi=FixedFileInfo(
|
|||
|
|
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
|
|||
|
|
# Set not needed items to zero 0.
|
|||
|
|
filevers=(2,0,0,0),
|
|||
|
|
prodvers=(2,0,0,0),
|
|||
|
|
# Contains a bitmask that specifies the valid bits 'flags'r
|
|||
|
|
mask=0x3f,
|
|||
|
|
# Contains a bitmask that specifies the Boolean attributes of the file.
|
|||
|
|
flags=0x0,
|
|||
|
|
# The operating system for which this file was designed.
|
|||
|
|
# 0x4 - NT and there is no need to change it.
|
|||
|
|
OS=0x4,
|
|||
|
|
# The general type of file.
|
|||
|
|
# 0x1 - the file is an application.
|
|||
|
|
fileType=0x1,
|
|||
|
|
# The function of the file.
|
|||
|
|
# 0x0 - the function is not defined for this fileType
|
|||
|
|
subtype=0x0,
|
|||
|
|
# Creation date and time stamp.
|
|||
|
|
date=(0, 0)
|
|||
|
|
),
|
|||
|
|
kids=[
|
|||
|
|
StringFileInfo(
|
|||
|
|
[
|
|||
|
|
StringTable(
|
|||
|
|
u'080404B0',
|
|||
|
|
[StringStruct(u'CompanyName', u'PDF书签合并工具'),
|
|||
|
|
StringStruct(u'FileDescription', u'PDF书签合并工具 - 用于从PDF文件夹中提取书签信息,与TXT元数据文件合并'),
|
|||
|
|
StringStruct(u'FileVersion', u'2.0.0.0'),
|
|||
|
|
StringStruct(u'InternalName', u'SlideCombine'),
|
|||
|
|
StringStruct(u'LegalCopyright', u'Copyright (C) 2024'),
|
|||
|
|
StringStruct(u'OriginalFilename', u'SlideCombine.exe'),
|
|||
|
|
StringStruct(u'ProductName', u'PDF书签合并工具'),
|
|||
|
|
StringStruct(u'ProductVersion', u'2.0.0.0')])
|
|||
|
|
]),
|
|||
|
|
VarFileInfo([VarStruct(u'Translation', [2052, 1200])])
|
|||
|
|
]
|
|||
|
|
)
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
with open('version_info.txt', 'w', encoding='utf-8') as f:
|
|||
|
|
f.write(version_info)
|
|||
|
|
print("✅ 已创建 version_info.txt 版本信息文件")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_exe():
|
|||
|
|
"""构建exe文件"""
|
|||
|
|
print("🔨 开始构建 exe 文件...")
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
# 构建命令
|
|||
|
|
cmd = [
|
|||
|
|
sys.executable, '-m', 'PyInstaller',
|
|||
|
|
'--clean',
|
|||
|
|
'--onefile',
|
|||
|
|
'--windowed',
|
|||
|
|
'--name=SlideCombine',
|
|||
|
|
'slide_combine.py'
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 如果有图标文件,添加图标参数
|
|||
|
|
if os.path.exists('app.ico'):
|
|||
|
|
cmd.insert(-1, '--icon=app.ico')
|
|||
|
|
print("✅ 已找到图标文件: app.ico")
|
|||
|
|
else:
|
|||
|
|
print("⚠️ 未找到图标文件,将使用默认图标")
|
|||
|
|
|
|||
|
|
print(f"执行命令: {' '.join(cmd)}")
|
|||
|
|
|
|||
|
|
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|||
|
|
print("✅ 构建成功!")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
except subprocess.CalledProcessError as ex:
|
|||
|
|
print(f"❌ 构建失败: {ex}")
|
|||
|
|
if ex.stderr:
|
|||
|
|
print(f"错误信息: {ex.stderr}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def create_release_package():
|
|||
|
|
"""创建发布包"""
|
|||
|
|
print("📦 创建发布包...")
|
|||
|
|
|
|||
|
|
# 检查dist文件夹
|
|||
|
|
dist_path = Path('dist')
|
|||
|
|
if not dist_path.exists():
|
|||
|
|
print("❌ 未找到 dist 文件夹")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 查找生成的exe文件
|
|||
|
|
exe_files = list(dist_path.glob('*.exe'))
|
|||
|
|
if not exe_files:
|
|||
|
|
print("❌ 未找到生成的 exe 文件")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
exe_file = exe_files[0]
|
|||
|
|
print(f"✅ 找到 exe 文件: {exe_file}")
|
|||
|
|
|
|||
|
|
# 创建发布包文件夹
|
|||
|
|
import datetime
|
|||
|
|
version = "2.0.0"
|
|||
|
|
date = datetime.datetime.now().strftime("%Y%m%d")
|
|||
|
|
package_name = f"SlideCombine_v{version}_{date}"
|
|||
|
|
|
|||
|
|
package_path = Path(package_name)
|
|||
|
|
if package_path.exists():
|
|||
|
|
shutil.rmtree(package_path)
|
|||
|
|
|
|||
|
|
package_path.mkdir()
|
|||
|
|
print(f"✅ 创建发布包文件夹: {package_name}")
|
|||
|
|
|
|||
|
|
# 复制exe文件
|
|||
|
|
shutil.copy2(exe_file, package_path / 'SlideCombine.exe')
|
|||
|
|
print("✅ 复制主程序文件")
|
|||
|
|
|
|||
|
|
# 创建使用说明
|
|||
|
|
readme_content = f"""PDF书签合并工具 v{version} 使用说明
|
|||
|
|
=====================================
|
|||
|
|
|
|||
|
|
系统要求:
|
|||
|
|
- Windows 7 SP1 或更高版本
|
|||
|
|
- 无需额外安装软件(绿色软件)
|
|||
|
|
|
|||
|
|
使用方法:
|
|||
|
|
1. 双击运行 SlideCombine.exe
|
|||
|
|
2. 选择三个路径:
|
|||
|
|
- PDF文件夹路径:包含 FreePic2Pdf_bkmk.txt 文件的文件夹
|
|||
|
|
- TXT源文件路径:包含元数据 TXT 文件的路径
|
|||
|
|
- 输出路径:合并后文件的保存位置
|
|||
|
|
3. 点击"开始合并"按钮
|
|||
|
|
4. 等待处理完成
|
|||
|
|
|
|||
|
|
示例目录结构:
|
|||
|
|
PDF文件夹/
|
|||
|
|
├─ CH-875 1-3/FreePic2Pdf_bkmk.txt
|
|||
|
|
├─ CH-875 4-6/FreePic2Pdf_bkmk.txt
|
|||
|
|
|
|||
|
|
TXT源文件/
|
|||
|
|
├─ CH-875 1-3.txt
|
|||
|
|
├─ CH-875 4-6.txt
|
|||
|
|
|
|||
|
|
输出结果:
|
|||
|
|
输出路径/CH-875.txt (合并后的文件)
|
|||
|
|
|
|||
|
|
特点:
|
|||
|
|
- 🚀 运行速度快
|
|||
|
|
- 📦 绿色软件,无需安装
|
|||
|
|
- 🎯 智能文件分组
|
|||
|
|
- 📊 详细处理日志
|
|||
|
|
- 🌍 支持多种文件编码
|
|||
|
|
- 💾 独立程序,无依赖
|
|||
|
|
|
|||
|
|
故障排除:
|
|||
|
|
- 如果程序无法启动,请检查是否有杀毒软件阻止运行
|
|||
|
|
- 确保输入的路径存在且有访问权限
|
|||
|
|
- 查看日志输出了解详细的处理信息
|
|||
|
|
|
|||
|
|
版本信息:
|
|||
|
|
- 程序版本:v{version}
|
|||
|
|
- 构建日期:{date}
|
|||
|
|
- 开发语言:Python
|
|||
|
|
- 界面框架:Tkinter
|
|||
|
|
|
|||
|
|
技术支持:
|
|||
|
|
这是一个绿色软件,解压即用,无需安装任何依赖项。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
with open(package_path / '使用说明.txt', 'w', encoding='utf-8') as f:
|
|||
|
|
f.write(readme_content)
|
|||
|
|
print("✅ 创建使用说明")
|
|||
|
|
|
|||
|
|
# 创建启动脚本
|
|||
|
|
bat_content = '''@echo off
|
|||
|
|
title PDF书签合并工具 v2.0
|
|||
|
|
echo 启动 PDF书签合并工具...
|
|||
|
|
echo.
|
|||
|
|
|
|||
|
|
if exist "SlideCombine.exe" (
|
|||
|
|
start "" "SlideCombine.exe"
|
|||
|
|
echo ✅ 程序已启动
|
|||
|
|
) else (
|
|||
|
|
echo ❌ 错误:未找到 SlideCombine.exe
|
|||
|
|
echo 请确保在正确的目录中运行此脚本
|
|||
|
|
pause
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
timeout /t 2 >nul
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
with open(package_path / '启动程序.bat', 'w', encoding='gbk') as f:
|
|||
|
|
f.write(bat_content)
|
|||
|
|
print("✅ 创建启动脚本")
|
|||
|
|
|
|||
|
|
# 获取文件大小
|
|||
|
|
exe_size = (package_path / 'SlideCombine.exe').stat().st_size
|
|||
|
|
size_mb = exe_size / (1024 * 1024)
|
|||
|
|
size_kb = exe_size / 1024
|
|||
|
|
|
|||
|
|
print(f"""
|
|||
|
|
✅ 发布包创建完成!
|
|||
|
|
📁 发布包位置: {package_path.absolute()}
|
|||
|
|
💾 主程序大小: {size_kb:.1f} KB ({size_mb:.1f} MB)
|
|||
|
|
📋 包含内容:
|
|||
|
|
├─ SlideCombine.exe (主程序)
|
|||
|
|
├─ 使用说明.txt (用户指南)
|
|||
|
|
└─ 启动程序.bat (快捷启动)
|
|||
|
|
|
|||
|
|
🎉 部署说明:
|
|||
|
|
1. 将整个 {package_name} 文件夹复制到目标电脑
|
|||
|
|
2. 双击"启动程序.bat"或直接运行"SlideCombine.exe"
|
|||
|
|
3. 无需安装任何软件,绿色环保
|
|||
|
|
""")
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
"""主函数"""
|
|||
|
|
print("="*50)
|
|||
|
|
print(" PDF书签合并工具 - 自动打包脚本")
|
|||
|
|
print("="*50)
|
|||
|
|
print()
|
|||
|
|
|
|||
|
|
# 检查Python版本
|
|||
|
|
if sys.version_info < (3, 7):
|
|||
|
|
print("❌ 需要 Python 3.7 或更高版本")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
print(f"✅ Python版本: {sys.version}")
|
|||
|
|
|
|||
|
|
# 检查主程序文件
|
|||
|
|
if not os.path.exists('slide_combine.py'):
|
|||
|
|
print("❌ 未找到 slide_combine.py 文件")
|
|||
|
|
print("请确保在项目根目录下运行此脚本")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
# 检查/安装PyInstaller
|
|||
|
|
if not check_pyinstaller():
|
|||
|
|
print("尝试自动安装 PyInstaller...")
|
|||
|
|
if not install_pyinstaller():
|
|||
|
|
print("请手动安装 PyInstaller: pip install pyinstaller")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
# 清理之前的构建
|
|||
|
|
clean_build()
|
|||
|
|
|
|||
|
|
# 创建配置文件
|
|||
|
|
create_spec_file()
|
|||
|
|
create_version_info()
|
|||
|
|
|
|||
|
|
# 构建exe文件
|
|||
|
|
if not build_exe():
|
|||
|
|
print("❌ 构建失败,请检查错误信息")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
# 创建发布包
|
|||
|
|
if not create_release_package():
|
|||
|
|
print("❌ 创建发布包失败")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
print("🎉 打包完成!")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|