修改文件重命名(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()

 

posted @ 2025-10-11 17:41  *感悟人生*  阅读(8)  评论(0)    收藏  举报