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() |