rar文件转成zip格式
这几天为了参加一个AI比赛项目,因此在完成项目的过程中难免遇到一些格式转换的问题,既然参加AI比赛,而且国产AI神器DeepSeek的确功能很强,何不体验一下。于是在DeepSeek的帮忙下,这个格式转换工具很快搞定,而且界面很美。下面与大家分享一下。
运行环境
操作系统:mac OS
解释器:Python3.8 +
编译器:Pycharm 2024.1
以下是源码:
import os
import sys
import zipfile
import subprocess
import threading
import queue
import shutil
import uuid
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
class RARConverterApp(tk.Tk):
def __init__(self):
super().__init__()
self.title("高级文件转换工具 v2.0 作者:Bruce_liu 【镇赉融媒】")
self.geometry("800x600")
self.check_dependencies()
self._setup_ui()
self._setup_styles()
self.processing = False
self.queue = queue.Queue()
self.after(100, self.process_queue)
def _setup_styles(self):
"""配置界面样式"""
self.style = ttk.Style()
self.style.theme_use('clam')
# 配置颜色主题
self.style.configure('TButton', padding=6)
self.style.configure('Primary.TButton',
foreground='white',
background='#0078d4',
font=('Helvetica', 10, 'bold'))
self.style.configure('Warning.TButton',
foreground='white',
background='#ffb900')
self.style.configure('Danger.TButton',
foreground='white',
background='#d13438')
def _setup_ui(self):
"""初始化界面组件"""
main_frame = ttk.Frame(self, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 文件选择区域
file_frame = ttk.LabelFrame(main_frame, text=" 文件选择 ")
file_frame.pack(fill=tk.X, pady=5)
self.rar_path = tk.StringVar()
ttk.Entry(file_frame, textvariable=self.rar_path, width=50).pack(side=tk.LEFT, padx=5, expand=True)
ttk.Button(file_frame, text="选择RAR文件", command=self.browse_rar, style='Primary.TButton').pack(side=tk.LEFT)
# 输出设置
output_frame = ttk.LabelFrame(main_frame, text=" 输出设置 ")
output_frame.pack(fill=tk.X, pady=5)
self.zip_path = tk.StringVar()
ttk.Entry(output_frame, textvariable=self.zip_path, width=50).pack(side=tk.LEFT, padx=5, expand=True)
ttk.Button(output_frame, text="保存位置", command=self.browse_zip, style='Primary.TButton').pack(side=tk.LEFT)
# 进度显示
self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, mode='determinate')
self.progress.pack(fill=tk.X, pady=10)
# 日志区域
log_frame = ttk.LabelFrame(main_frame, text=" 操作日志 ")
log_frame.pack(fill=tk.BOTH, expand=True)
self.log_area = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, state=tk.DISABLED)
self.log_area.pack(fill=tk.BOTH, expand=True)
# 控制按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(pady=10)
self.start_btn = ttk.Button(btn_frame, text="开始转换", command=self.start_conversion, style='Primary.TButton')
self.start_btn.pack(side=tk.LEFT, padx=5)
self.cancel_btn = ttk.Button(btn_frame, text="取消转换", command=self.cancel_conversion,
style='Warning.TButton', state=tk.DISABLED)
self.cancel_btn.pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="退出程序", command=self.safe_exit, style='Danger.TButton').pack(side=tk.RIGHT)
def browse_rar(self):
"""选择RAR文件"""
path = filedialog.askopenfilename(
title="选择RAR文件",
filetypes=[("RAR文件", "*.rar"), ("所有文件", "*.*")]
)
if path:
self.rar_path.set(path)
default_zip = os.path.splitext(path)[0] + "_converted.zip"
self.zip_path.set(default_zip)
def browse_zip(self):
"""选择ZIP保存路径"""
path = filedialog.asksaveasfilename(
title="保存ZIP文件",
defaultextension=".zip",
filetypes=[("ZIP文件", "*.zip"), ("所有文件", "*.*")]
)
if path:
self.zip_path.set(path)
def start_conversion(self):
"""启动转换流程"""
if self.processing:
return
if not self._validate_inputs():
return
self.processing = True
self._update_ui_state(starting=True)
self.log("开始转换操作...", "blue")
# 启动转换线程
threading.Thread(target=self.conversion_process, daemon=True).start()
def _validate_inputs(self):
"""验证输入有效性"""
if not os.path.exists(self.rar_path.get()):
messagebox.showerror("错误", "请选择有效的RAR文件")
return False
if not self.zip_path.get().endswith(".zip"):
messagebox.showerror("错误", "输出文件必须是ZIP格式")
return False
return True
def conversion_process(self):
"""执行转换的核心方法"""
temp_dir = None
try:
# 生成唯一临时目录
temp_dir = os.path.join(
os.getcwd(),
f"temp_{uuid.uuid4().hex[:8]}" # 8位随机ID
)
os.makedirs(temp_dir, exist_ok=True)
# 阶段1:解压RAR
self.log("正在解压RAR文件...")
result = subprocess.run(
["unar", "-o", temp_dir, self.rar_path.get()],
capture_output=True,
text=True
)
if result.returncode != 0:
raise RuntimeError(f"解压失败:{result.stderr}")
self.queue.put(("progress", 30))
# 阶段2:创建ZIP
self.log("正在创建ZIP文件...")
with zipfile.ZipFile(
self.zip_path.get(),
'w',
zipfile.ZIP_DEFLATED,
compresslevel=6,
allowZip64=True
) as zipf:
zipf.encoding = 'utf-8'
base_path = os.path.abspath(temp_dir)
all_files = self._get_file_list(temp_dir)
total = len(all_files)
for idx, (full_path, relative_path) in enumerate(all_files, 1):
if not self.processing:
raise InterruptedError("用户取消操作")
zipf.write(full_path, relative_path)
self.queue.put(("progress", 30 + int(70*(idx/total))))
self.queue.put(("log", f"添加文件:{relative_path}"))
# 最终验证
if self._verify_zip(temp_dir):
self.queue.put(("success", "转换完成且通过验证"))
else:
raise RuntimeError("ZIP文件验证失败")
except Exception as e:
self.queue.put(("error", str(e)))
finally:
if temp_dir and os.path.exists(temp_dir):
shutil.rmtree(temp_dir, ignore_errors=True)
self.queue.put(("complete", None))
def _get_file_list(self, root_dir):
"""生成要压缩的文件列表"""
file_list = []
base_path = os.path.abspath(root_dir)
for root, _, files in os.walk(root_dir):
# 跳过隐藏目录
if os.path.basename(root).startswith('.'):
continue
for file in files:
# 过滤系统文件
if file.startswith(('.', '~$')) or file == 'Thumbs.db':
continue
full_path = os.path.join(root, file)
relative_path = os.path.relpath(full_path, base_path)
file_list.append((full_path, relative_path))
return file_list
def _verify_zip(self, original_dir):
"""验证ZIP文件完整性"""
try:
with zipfile.ZipFile(self.zip_path.get(), 'r') as zf:
if zf.testzip() is not None:
return False
# 检查文件数量是否一致
orig_count = len(self._get_file_list(original_dir))
zip_count = len(zf.infolist())
return orig_count == zip_count
except:
return False
def cancel_conversion(self):
"""取消转换操作"""
if messagebox.askyesno("确认取消", "确定要中止当前转换操作吗?"):
self.processing = False
self.log("正在取消操作...", "orange")
def safe_exit(self):
"""安全退出程序"""
if self.processing:
if messagebox.askyesno("退出确认", "转换正在进行中,确定要退出吗?"):
self.processing = False
self.destroy()
else:
self.destroy()
def process_queue(self):
"""处理消息队列"""
try:
while True:
msg_type, content = self.queue.get_nowait()
if msg_type == "progress":
self.progress["value"] = content
elif msg_type == "log":
self.log(content)
elif msg_type == "success":
messagebox.showinfo("成功", content)
self.log(content, "green")
elif msg_type == "error":
messagebox.showerror("错误", content)
self.log(content, "red")
elif msg_type == "complete":
self._update_ui_state(starting=False)
except queue.Empty:
pass
finally:
self.after(100, self.process_queue)
def _update_ui_state(self, starting=True):
"""更新界面状态"""
state = tk.NORMAL if not starting else tk.DISABLED
self.start_btn.config(state=state)
self.cancel_btn.config(state=tk.NORMAL if starting else tk.DISABLED)
self.progress["value"] = 0 if not starting else self.progress["value"]
def log(self, message, color="black"):
"""记录日志信息"""
color_tags = {
"blue": ("blue", "#0078d4"),
"green": ("green", "#107c10"),
"red": ("red", "#d13438"),
"orange": ("orange", "#ff8c00")
}
self.log_area.config(state=tk.NORMAL)
# 配置标签
for tag, (fg, bg) in color_tags.items():
self.log_area.tag_config(tag, foreground=fg)
self.log_area.insert(tk.END, message + "\n", color)
self.log_area.see(tk.END)
self.log_area.config(state=tk.DISABLED)
def check_dependencies(self):
"""检查系统依赖"""
try:
subprocess.run(["unar", "--version"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except (FileNotFoundError, subprocess.CalledProcessError):
messagebox.showerror(
"缺少依赖",
"需要安装unar工具\n\n"
"安装方法:\n"
"macOS: brew install unar\n"
"Linux: sudo apt-get install unar\n"
"Windows: 下载并安装7-Zip"
)
sys.exit(1)
if __name__ == "__main__":
app = RARConverterApp()
app.mainloop()
主要功能特点:
- 现代化界面设计
- 彩色按钮状态指示(蓝色-运行中,黄色-可取消,红色-退出)
- 实时进度条显示
- 带颜色分级的日志系统
- 自适应布局
- 增强稳定性
- 完整的异常处理链
- 临时文件自动清理
- 操作取消确认
- ZIP文件完整性验证
- 性能优化
- 流式文件处理(避免内存溢出)
- 多线程处理保持界面响应
- 智能文件过滤(跳过隐藏文件)
- 易用性改进
- 自动建议输出路径
- 输入有效性验证
- 友好的错误提示
- 系统依赖自动检查
使用说明:
-
安装依赖:
# macOS brew install unar # Ubuntu/Debian sudo apt-get install unar # Windows # 安装7-Zip并添加至系统PATH
-
运行程序:
python converter_gui.py
-
操作流程:
- 点击"选择RAR文件"按钮选择输入文件
- 自动生成默认输出路径(可手动修改)
- 点击"开始转换"启动操作
- 使用"取消转换"按钮可中止操作
- 通过日志区域查看实时进度
当遇到问题时,程序会显示颜色编码的错误信息:
- 蓝色:操作提示
- 绿色:成功信息
- 红色:错误信息
- 橙色:警告信息