日常使用 Windows 时,经常需要对文件进行压缩、加密或分卷存储。WinRAR 本身已经提供了比较完善的功能,但如果需要批量处理多个文件和文件夹,操作起来仍然比较繁琐。

因此,我使用 Python 开发了一个简单的批量压缩工具。其并不重新实现压缩算法,而是调用 WinRAR 完成压缩操作。

界面
界面

下载请见 Malvern’s File Server 。

功能介绍

工具基于 Python 标准库中的 tkinter.ttk 构建,主要支持以下功能:

功能说明
批量压缩支持一次选择多个文件或文件夹
压缩等级提供存储、最快、较快、标准、较好、最好六档
文件加密支持设置 RAR 压缩密码
文件名加密可选择同时加密压缩包内部的文件名
恢复记录支持添加 1%、2%、3%、5%、10% 的恢复记录
分卷压缩支持 100 MB、500 MB、1 GB、2 GB、4 GB、4.7 GB、25 GB
覆盖控制可选择覆盖或跳过已经存在的压缩文件
运行日志在窗口右侧显示 WinRAR 输出
Windows 通知压缩完成后可发送系统通知
深浅色模式自动检测 Windows 应用主题并调整界面颜色
高 DPI 适配支持高分辨率显示器
环境检测启动时自动查找 WinRAR

整个工具没有复杂的安装流程,只需要安装 Python 和 WinRAR 即可运行。Windows 通知功能属于可选功能,不会影响主要的压缩操作。

为什么选择 WinRAR?

Python 本身提供了 zipfile、tarfile 等标准库,可以直接创建 ZIP 或 TAR 压缩文件。

但这次开发有两个比较重要的需求:

  1. 支持压缩包加密,尤其是文件名加密。
  2. 支持恢复记录,提高压缩包在发生局部损坏时的可修复性。

WinRAR 的 RAR 格式能够同时满足这些需求,并且提供命令行接口,便于使用 Python 自动化调用。

压缩格式与数据恢复

压缩与纠错实际上是两个不同的概念。

普通压缩主要通过消除数据冗余减小文件体积,而恢复记录则需要额外保存用于修复损坏数据的信息。

如果压缩包中的一部分数据发生损坏,普通压缩文件可能无法正常解压。尤其是在使用固实压缩时,局部损坏可能影响后续多个文件。

WinRAR 提供了恢复记录功能,可以在创建 RAR 压缩包时额外写入一定比例的恢复数据。

例如:

1
WinRAR.exe a -rr3p archive.rar "example"

其中:

  • a:向压缩包添加文件。
  • -rr3p:添加约 3% 的恢复记录。
  • archive.rar:输出压缩包。
  • example:待压缩的文件或文件夹。

需要注意,恢复记录并不意味着压缩包可以承受任意程度的损坏。

实际修复能力取决于损坏的位置、范围以及压缩包结构。恢复记录比例越高,通常需要付出的额外存储空间也越多。

对于需要长期保存的文件,可以适当提高恢复记录比例,但仍然不能用它替代独立备份。

开发环境

本项目使用以下技术:

项目技术
操作系统Windows 10 / Windows 11
编程语言Python 3
图形界面Tkinter.ttk
压缩引擎WinRAR
Windows APIctypes
系统主题检测winreg
后台任务threading
外部程序调用subprocess
Windows 通知winotify(可选)

其中,除了可选的 winotify,其余 Python 模块均属于标准库。

如果需要启用 Windows 通知,可以通过 PowerShell 安装:

1
pip install winotify

如果不安装,程序仍然可以正常运行,只是不发送系统通知。

程序实现原理

整个程序可以分成三个部分:

  1. 使用 tkinter.ttk 创建图形界面。
  2. 根据用户选择生成 WinRAR 命令行参数。
  3. 通过 subprocess 调用 WinRAR,并将输出显示在日志窗口。

自动检测 WinRAR

程序启动时会尝试在常见安装目录中寻找 WinRAR.exe,比如:

1
2
C:\Program Files\WinRAR\WinRAR.exe
C:\Program Files (x86)\WinRAR\WinRAR.exe

同时也会尝试从系统的 PATH 环境变量中寻找 WinRAR。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def find_winrar():
candidates = []

for env_name in ("ProgramFiles", "ProgramFiles(x86)", "ProgramW6432"):
base = os.environ.get(env_name)
if base:
candidates.append(
Path(base) / "WinRAR" / "WinRAR.exe"
)

path_result = shutil.which("WinRAR.exe")
if path_result:
candidates.append(Path(path_result))

for path in candidates:
if path.is_file():
return str(path)

return None

如果未检测到 WinRAR,程序会询问是否打开官方下载页面,然后退出。

压缩等级

WinRAR 可以通过 -m 参数控制压缩等级,例如:

1
WinRAR.exe a -m5 archive.rar "example"

在 Python 中,通过字典将界面中的中文选项映射为 WinRAR 参数:

1
2
3
4
5
6
7
8
mapping = {
"存储": 0,
"最快": 1,
"较快": 3,
"标准": 5,
"较好": 7,
"最好": 9,
}

其中,-m0 表示不压缩,仅存储文件;-m5 是 RAR5 常用的标准压缩等级。

压缩等级越高,并不意味着所有文件都能明显缩小。

例如,JPEG 图片、MP4 视频以及已经压缩过的文件,通常很难通过再次压缩获得明显的体积收益。

文件与文件名加密

WinRAR 提供了两种相关的密码参数:

1
-pPASSWORD

以及:

1
-hpPASSWORD

两者的区别是:

参数文件内容加密文件名加密
-p是否
-hp是是

因此,程序通过复选框控制是否加密文件名:

1
2
3
4
5
6
7
password = self.password_var.get()

if password:
if self.encrypt_names_var.get():
command.append(f"-hp{password}")
else:
command.append(f"-p{password}")

使用文件名加密后,未输入正确密码的用户通常无法查看压缩包内部的文件列表。

需要注意,当前程序通过命令行参数向 WinRAR 传递密码。在压缩进程运行期间,具有足够权限的本机进程可能读取命令行参数,因此不适合在不可信的共享计算机上处理高敏感密码。

添加恢复记录

恢复记录通过 -rr 参数实现。

程序提供以下选项:

1
2
3
4
5
6
7
RECOVERY_OPTIONS = [
"1%",
"2%",
"3%",
"5%",
"10%",
]

生成命令行参数:

1
2
3
if self.recovery_var.get():
percent = self.recovery_percent_var.get().rstrip("%")
command.append(f"-rr{percent}p")

例如选择 5%,生成:

1
-rr5p

恢复记录可以提高压缩包发生局部损坏后的可修复性,但它并不是独立备份,也不能保证所有损坏都能恢复。

分卷压缩

分卷压缩适合将大型文件拆分成多个较小的压缩文件。

例如,需要将一个大型文件保存到单个文件大小受限的存储设备,或者分批传输到其他计算机时,可以使用分卷压缩。

WinRAR 通过 -v 参数设置分卷大小。

例如:

1
WinRAR.exe a -v1g archive.rar "example"

表示将压缩结果按约 1 GB 的大小分卷。

程序使用字典维护分卷选项:

1
2
3
4
5
6
7
8
9
10
VOLUME_MAP = {
"不分卷": None,
"100 MB": "100m",
"500 MB": "500m",
"1 GB": "1g",
"2 GB": "2g",
"4 GB": "4g",
"4.7 GB(单层 DVD)": "4700m",
"25 GB(蓝光光盘)": "25g",
}

生成参数:

1
2
3
4
volume_size = VOLUME_MAP.get(self.volume_var.get())

if volume_size:
command.append(f"-v{volume_size}")

分卷后的压缩文件通常采用以下命名方式:

1
2
3
archive.part1.rar
archive.part2.rar
archive.part3.rar

最后一个分卷的大小通常小于设定值。

解压时,应将所有分卷放在同一目录中,并从第一个分卷开始解压。

需要注意,分卷本身并不提供数据冗余。如果缺失其中一个分卷,即使其他分卷没有损坏,也可能无法完整恢复原始文件。

防止界面卡死

如果直接在 Tkinter 主线程中执行压缩命令,压缩期间窗口可能无法响应鼠标操作,甚至被 Windows 标记为 “未响应”。

因此,程序使用 threading 将压缩任务放到后台线程执行:

1
2
3
4
5
worker = threading.Thread(
target=self.compression_worker,
daemon=True,
)
worker.start()

实际压缩操作通过 subprocess.Popen() 调用 WinRAR:

1
2
3
4
5
6
7
8
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding="mbcs",
errors="replace",
)

随后读取 WinRAR 输出,并显示到右侧日志区域。

由于 Tkinter 不适合直接从后台线程修改界面控件,程序通过 root.after() 将界面更新操作安排到主线程执行:

1
self.root.after(0, append)

这样既能保证窗口正常响应,也可以实时查看压缩进度和错误信息。

Windows 11 深浅色模式适配

Tkinter 是一个跨平台 GUI 库,但传统 ttk 控件并不能完整、自动地适配 Windows 11 的深色模式。

因此,程序采用了系统主题检测与手动设置控件颜色相结合的方案。

检测系统主题

Windows 会在注册表中保存应用主题设置:

1
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize

其中:

1
AppsUseLightTheme = 0

表示深色模式。

1
AppsUseLightTheme = 1

表示浅色模式。

Python 可以使用 winreg 读取这一设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def is_dark_mode():
try:
key_path = (
r"Software\Microsoft\Windows\CurrentVersion"
r"\Themes\Personalize"
)

with winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
key_path
) as key:
value, _ = winreg.QueryValueEx(
key,
"AppsUseLightTheme"
)

return value == 0

except OSError:
return False

程序每隔 500 毫秒检查一次主题设置。

如果用户在 Windows 设置中切换深色或浅色模式,程序也会相应调整界面。

标题栏适配

普通 Tkinter 窗口的标题栏由 Windows 管理。

为了使标题栏也能够跟随系统主题,可以通过 ctypes 调用 Windows DWM API:

1
2
3
4
5
6
7
8
9
def set_dark_titlebar(hwnd, dark):
value = ctypes.c_int(1 if dark else 0)

ctypes.windll.dwmapi.DwmSetWindowAttribute(
ctypes.c_void_p(hwnd),
20,
ctypes.byref(value),
ctypes.sizeof(value),
)

其中,属性编号 20 对应 DWMWA_USE_IMMERSIVE_DARK_MODE。

需要注意,具体效果取决于 Windows 版本。

输入框深色模式白底问题

在实际开发中,我遇到一个问题:

即使已经给 ttk.Entry 设置了深色样式,密码输入框内部仍然可能显示白色背景。

最终的解决方法是将密码输入框改为原生 tk.Entry,并直接控制背景色、文字颜色、光标颜色和选中区域颜色。

1
2
3
4
5
6
7
self.password_entry = tk.Entry(
self.control_frame,
textvariable=self.password_var,
relief="flat",
bd=0,
highlightthickness=1,
)

深色模式下:

1
2
3
4
5
6
7
8
9
self.password_entry.configure(
bg="#2b2b2b",
fg="#ffffff",
insertbackground="#ffffff",
highlightbackground="#555555",
highlightcolor="#60cdff",
selectbackground="#454545",
selectforeground="#ffffff",
)

浅色模式下:

1
2
3
4
5
6
7
8
9
self.password_entry.configure(
bg="#ffffff",
fg="#000000",
insertbackground="#000000",
highlightbackground="#999999",
highlightcolor="#0067c0",
selectbackground="#cce8ff",
selectforeground="#000000",
)

这种方式比单纯修改 ttk 样式更容易控制输入框的实际显示效果。

高 DPI 适配

在高分辨率显示器上,传统 Tkinter 程序可能出现界面模糊或缩放异常。

程序通过 Windows API 设置进程 DPI 感知:

1
ctypes.windll.user32.SetProcessDpiAwarenessContext(ctypes.c_void_p(-4))

其中 -4 对应 Per-Monitor DPI Aware V2。

需要注意,这项设置必须在创建 Tk 窗口之前执行。

另外,程序没有指定自定义字体,而是尽量使用 Tkinter 默认字体,以减少高 DPI 环境下的字体显示问题。

总结

这次开发并没有涉及复杂的压缩算法,而是通过 Python 将 WinRAR 已有的命令行功能整合到一个简单的图形界面中。

开发过程中,比较值得记录的技术点包括:

  • 使用 subprocess 调用外部程序。
  • 使用 threading 避免 Tkinter 界面卡死。
  • 使用 root.after() 实现线程安全的界面更新。
  • 使用 winreg 检测 Windows 应用主题。
  • 使用 ctypes 调用 Windows API。
  • 使用 tk.Entry 解决 ttk 输入框深色模式适配问题。
  • 使用 WinRAR 的命令行参数实现加密、恢复记录和分卷压缩。

网站地图 | 全球节假日日历 | 皮带张力测试 | File Server | 博友圈 | 博客说
Copyright 2022-2026 | Powered by Hexo 8.1.2 & Stellar 1.44.0
总访问量次 |