惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Application and Cybersecurity Blog
Application and Cybersecurity Blog
G
GRAHAM CLULEY
P
Privacy International News Feed
L
LINUX DO - 热门话题
C
Cisco Blogs
T
Tor Project blog
AWS News Blog
AWS News Blog
K
Kaspersky official blog
C
Cybersecurity and Infrastructure Security Agency CISA
Cyberwarzone
Cyberwarzone
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
P
Proofpoint News Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
Lohrmann on Cybersecurity
C
CXSECURITY Database RSS Feed - CXSecurity.com
Project Zero
Project Zero
Attack and Defense Labs
Attack and Defense Labs
Latest news
Latest news
The Last Watchdog
The Last Watchdog
Apple Machine Learning Research
Apple Machine Learning Research
Simon Willison's Weblog
Simon Willison's Weblog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Cloudbric
Cloudbric
Last Week in AI
Last Week in AI
S
Security Affairs
Google DeepMind News
Google DeepMind News
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
有赞技术团队
有赞技术团队
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
小众软件
小众软件
P
Palo Alto Networks Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security @ Cisco Blogs
V
V2EX
S
Secure Thoughts
人人都是产品经理
人人都是产品经理
月光博客
月光博客
博客园_首页
T
Troy Hunt's Blog
爱范儿
爱范儿
N
News and Events Feed by Topic
Hugging Face - Blog
Hugging Face - Blog
雷峰网
雷峰网
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Privacy & Cybersecurity Law Blog
V
Vulnerabilities – Threatpost

博客园 - *感悟人生*

PDFtoEXCEL批量处理高保真同步格式 JS 逆向与前端安全加固实战指南 批量处理苹果电脑的HEIF格式转成JPG|PNG 邮件群发系统 注册授权--续 独立授权模块 --可以为你的程序或者工具加上一把锁 音频转换合并切割工具 AI 平台 SQL语句解析 扣代码中,遇到this 应该怎么处理 AST解OB混淆,适用大部分的混淆 Akamai的形成与风控分析:聚焦Akamai 3.0 执行py3o的重启脚本(包含手动执行,以及自动执行的脚本) reduce与map+filter的复杂计算场景 py3o中汇总的计算:sum reduce map 三种形式来处理对比 py3o中数字金额转大写 加密解密基本概念 MJ提示词自动批处理GUI版 uv 在 Python 开发中的常用命令详解 表单和载荷的区别,以及python和js在处理json时的空格问题。 R函数处理异步迭代,在爬虫中的作用。
修改文件重命名(1、默认去掉预览和备份,2、默认当前文件路径)
*感悟人生* · 2025-10-11 · via 博客园 - *感悟人生*
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()