修改文件重命名(1、默认去掉预览和备份,2、默认当前文件路径)
import os import glob import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from datetime import datetime, timezone import requests import json import threading import shutil class BatchRenamerPro: def __init__(self, root): self.root = root self.root.title("Batch File Renamer Pro - 三松集团专业批量文件重命名带撤销功能(强哥出品)") self.root.geometry("650x800") self.root.resizable(False, False) # 固定窗口大小,不可调整 # 美化:设置主题和样式 style = ttk.Style() style.theme_use('clam') # 使用clam主题,美观现代 style.configure('Title.TLabel', font=('Arial', 14, 'bold'), background='white') style.configure('Header.TLabel', font=('Arial', 12, 'bold'), foreground='#2E5C8A') style.configure('TButton', font=('Arial', 10), padding=10) style.configure('Accent.TButton', font=('Arial', 10, 'bold'), foreground='white', background='#4CAF50') style.configure('TCheckbutton', font=('Arial', 10)) # 在style中配置字体 # 背景色 self.root.configure(bg='white') # 截止日期:2025-12-31 self.expiry_date = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc) # 重命名历史,用于撤销 self.rename_history = [] self.backup_folder = None # 检查许可证 if not self.check_license(): self.root.destroy() return True self.setup_ui() def check_license(self): """检查许可证:尝试从互联网获取当前UTC时间比对截止日期,若失败则使用本地日期""" try: # 尝试从网络获取时间 response = requests.get('http://worldtimeapi.org/api/timezone/Etc/UTC', timeout=5) if response.status_code == 200: data = response.json() current_utc = datetime.fromisoformat(data['datetime'].replace('Z', '+00:00')) else: raise ValueError("无法从互联网获取时间") except (requests.RequestException, json.JSONDecodeError, ValueError): # 如果网络获取失败,使用本地时间 current_utc = datetime.now(timezone.utc) # 比较当前时间和过期日期 if current_utc > self.expiry_date: messagebox.showerror("软件内部错误", "软件内部错误,请联系管理员获取更新版本。\nEmail: lyt@singsong.com.cn") return False return True def setup_ui(self): """设置美观的GUI界面""" # 标题 title_frame = tk.Frame(self.root, bg='white', height=50) title_frame.pack(fill=tk.X, pady=(0, 10)) title_frame.pack_propagate(False) ttk.Label(title_frame, text="Batch File Renamer Pro", style='Title.TLabel').pack(expand=True) ttk.Label(title_frame, text="专业批量文件重命名工具 | v1.0 | © 2025 SINGSONG Studio", font=('Arial', 8), foreground='#888888').pack() # 主容器,使用Notebook分tab?不,为简单用frame分组 main_container = ttk.Frame(self.root, padding="20", relief='raised', borderwidth=1) main_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 输入组 input_frame = ttk.LabelFrame(main_container, text="输入设置", padding="10") input_frame.pack(fill=tk.X, pady=(0, 10)) # 文件夹选择 ttk.Label(input_frame, text="选择文件夹:", font=('Arial', 10)).grid(row=0, column=0, sticky=tk.W, pady=5) self.folder_var = tk.StringVar(value=os.getcwd()) # 默认显示当前工作目录 folder_entry = ttk.Entry(input_frame, textvariable=self.folder_var, width=50, font=('Arial', 9)) folder_entry.grid(row=0, column=1, padx=(10, 5), pady=5) ttk.Button(input_frame, text="浏览", command=self.browse_folder, style='TButton').grid(row=0, column=2, pady=5) # 添加文本 ttk.Label(input_frame, text="添加/替换文本:", font=('Arial', 10)).grid(row=1, column=0, sticky=tk.W, pady=5) self.add_text_var = tk.StringVar(value="-Glitter") ttk.Entry(input_frame, textvariable=self.add_text_var, width=50, font=('Arial', 9)).grid(row=1, column=1, padx=(10, 5), pady=5) # 添加位置 ttk.Label(input_frame, text="操作位置:", font=('Arial', 10)).grid(row=2, column=0, sticky=tk.W, pady=5) self.position_var = tk.StringVar(value="后缀") position_combo = ttk.Combobox(input_frame, textvariable=self.position_var, values=["前缀", "后缀", "替换"], state="readonly", width=47, font=('Arial', 9)) position_combo.grid(row=2, column=1, padx=(10, 5), pady=5) # 文件类型过滤 ttk.Label(input_frame, text="文件类型过滤:", font=('Arial', 10)).grid(row=3, column=0, sticky=tk.W, pady=5) self.file_type_var = tk.StringVar(value="*.jpg") ttk.Entry(input_frame, textvariable=self.file_type_var, width=50, font=('Arial', 9)).grid(row=3, column=1, padx=(10, 5), pady=5) ttk.Label(input_frame, text="(支持通配符,如 *.jpg;*.png)", font=('Arial', 8), foreground='#666666').grid(row=4, column=1, sticky=tk.W, pady=(0, 5)) # 选项组 options_frame = ttk.LabelFrame(main_container, text="高级选项", padding="10") options_frame.pack(fill=tk.X, pady=(0, 10)) self.recursive_var = tk.BooleanVar(value=True) # 默认不选中 ttk.Checkbutton(options_frame, text="递归处理子文件夹", variable=self.recursive_var, style='TCheckbutton').grid( row=0, column=0, sticky=tk.W, pady=5) self.preview_var = tk.BooleanVar(value=False) # 默认不选中 ttk.Checkbutton(options_frame, text="预览模式(不实际执行)", variable=self.preview_var, style='TCheckbutton').grid( row=1, column=0, sticky=tk.W, pady=5) self.backup_var = tk.BooleanVar(value=False) # 默认不选中 ttk.Checkbutton(options_frame, text="启用备份(支持撤销)", variable=self.backup_var, style='TCheckbutton').grid( row=2, column=0, sticky=tk.W, pady=5) # 控制按钮组 control_frame = ttk.Frame(main_container) control_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Button(control_frame, text="执行重命名", command=self.run_rename, style='Accent.TButton').pack(side=tk.LEFT, padx=(0, 10), pady=10) # 移除撤销最后操作按钮 self.undo_all_button = ttk.Button(control_frame, text="撤销所有", command=self.undo_all, state='disabled', style='TButton') self.undo_all_button.pack(side=tk.LEFT, padx=5, pady=10) # 日志输出组 log_frame = ttk.LabelFrame(main_container, text="操作日志", padding="5") log_frame.pack(fill=tk.BOTH, expand=True) self.log_text = scrolledtext.ScrolledText(log_frame, width=70, height=20, font=('Consolas', 9), bg='#f9f9f9', fg='#333333') # 增加日志窗口的高度 self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 菜单栏(保持) menubar = tk.Menu(self.root, bg='white', fg='black', font=('Arial', 9)) self.root.config(menu=menubar) file_menu = tk.Menu(menubar, tearoff=0, bg='white', fg='black') menubar.add_cascade(label="文件", menu=file_menu) file_menu.add_command(label="退出", command=self.root.quit) help_menu = tk.Menu(menubar, tearoff=0, bg='white', fg='black') menubar.add_cascade(label="帮助", menu=help_menu) help_menu.add_command(label="关于", command=self.show_about) help_menu.add_command(label="帮助", command=self.show_help) def browse_folder(self): """浏览选择文件夹""" folder = filedialog.askdirectory() if folder: self.folder_var.set(folder) def log(self, message): """添加日志消息""" timestamp = datetime.now().strftime("%H:%M:%S") self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") self.log_text.see(tk.END) # 自动滚动到最底部 self.root.update_idletasks() def show_about(self): """关于对话框""" messagebox.showinfo("关于 Batch File Renamer Pro", "Batch File Renamer Pro v1.0\n\n专业批量文件重命名工具\n\n开发者: 三松集团强哥出品\n支持多平台批量操作,提升生产力!") def show_help(self): """帮助对话框""" help_text = """ 使用指南: 1. 点击'浏览'选择目标文件夹。 2. 输入要添加的文本(如 'Glitter')。 3. 选择添加位置:前缀(开头)、后缀(结尾)、替换(替换原名)。 4. 指定文件类型(如 *.jpg)。 5. 勾选选项:递归子文件夹、预览模式、备份。 6. 点击'执行重命名'开始。 7. 执行后,可使用'撤销所有'恢复。 注意: - 预览模式下仅显示变化,不实际修改文件。 - 备份将创建 'backup_[日期]' 文件夹,支持撤销。 """ messagebox.showinfo("帮助", help_text) def run_rename(self): """执行重命名(在子线程中)""" if not self.folder_var.get().strip(): messagebox.showwarning("警告", "请选择文件夹!") return # 清除日志和历史 self.log_text.delete(1.0, tk.END) self.rename_history = [] self.undo_all_button.config(state='disabled') # 默认禁用撤销所有按钮 # 创建备份文件夹如果启用 if self.backup_var.get(): self.backup_folder = os.path.join(self.folder_var.get(), f"backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}") os.makedirs(self.backup_folder, exist_ok=True) self.log(f"创建备份文件夹: {self.backup_folder}") # 在子线程运行以避免UI冻结 thread = threading.Thread(target=self._perform_rename) thread.daemon = True thread.start() def _perform_rename(self): """实际执行重命名逻辑""" folder = self.folder_var.get() add_text = self.add_text_var.get().strip() position = self.position_var.get() file_types = [ft.strip() for ft in self.file_type_var.get().strip().split(';') if ft.strip()] recursive = self.recursive_var.get() preview = self.preview_var.get() if not add_text or not file_types: self.log("错误: 添加文本或文件类型不能为空!") return processed = 0 skipped = 0 try: if recursive: for root, dirs, files in os.walk(folder): for file_type in file_types: pattern = os.path.join(root, file_type) matches = glob.glob(pattern, recursive=True) for file_path in matches: self._rename_single(file_path, add_text, position, preview, processed, skipped) processed += 1 else: for file_type in file_types: matches = glob.glob(os.path.join(folder, file_type)) for file_path in matches: self._rename_single(file_path, add_text, position, preview, processed, skipped) processed += 1 self.log(f"批量重命名完成!处理: {processed}, 跳过: {skipped}") if self.rename_history: self.undo_all_button.config(state='normal') # 启用撤销所有按钮 if preview: messagebox.showinfo("预览完成", "预览模式:文件未实际修改。取消预览以执行真实重命名。") except Exception as e: self.log(f"错误: {str(e)}") messagebox.showerror("执行错误", str(e)) def _rename_single(self, file_path, add_text, position, preview, processed, skipped): """处理单个文件重命名""" file_name = os.path.basename(file_path) base_name, ext = os.path.splitext(file_name) if position == "前缀": new_name = add_text + base_name + ext elif position == "替换": new_name = add_text + ext else: # 后缀 new_name = base_name + add_text + ext dir_name = os.path.dirname(file_path) new_path = os.path.join(dir_name, new_name) if os.path.exists(new_path): self.log(f"跳过 {file_name}:新文件名 {new_name} 已存在") skipped += 1 else: if preview: self.log(f"预览: {file_name} → {new_name}") else: old_path = file_path # 备份如果启用 if self.backup_var.get() and self.backup_folder: backup_path = os.path.join(self.backup_folder, file_name) shutil.copy2(old_path, backup_path) self.log(f"备份: {file_name} 到 {self.backup_folder}") os.rename(old_path, new_path) self.rename_history.append((old_path, new_path)) # 记录用于撤销 self.log(f"重命名: {file_name} → {new_name}") def undo_all(self): """撤销所有重命名""" if not self.rename_history: messagebox.showwarning("警告", "无操作可撤销!") return if messagebox.askyesno("确认撤销", "撤销所有重命名操作?这将恢复所有文件。"): while self.rename_history: old_path, new_path = self.rename_history.pop() try: os.rename(new_path, old_path) self.log(f"撤销: {os.path.basename(new_path)} → {os.path.basename(old_path)}") except Exception as e: self.log(f"撤销失败: {str(e)}") messagebox.showerror("撤销错误", str(e)) self.log("所有操作已撤销!") self.undo_all_button.config(state='disabled') # 撤销所有后禁用撤销按钮 def main(): root = tk.Tk() app = BatchRenamerPro(root) root.mainloop() if __name__ == "__main__": main()