我为什么写这个脚本

在日常工作和影音文件整理中,我经常遇到这样的困扰:数百个文件需要按照统一格式重命名,例如:

DVWA-CSRF漏洞详解-part1.mp4
DVWA_CSRF_02.avi
第三讲 csrf.mp4
...

传统解决方案存在三个痛点:

  1. 手动命名效率低下,容易出错
  2. 现有工具难以智能识别内容特征
  3. 需要保持编号连续性和格式统一
    为此,我开发了这个结合深度学习和规则引擎的智能重命名脚本,实现:
    • AI智能理解文件内容主题
    • 自动识别多种编号格式(SXXEYY、连续数字等)
    • 批量处理与冲突解决的一站式方案

Prompt设计艺术

系统级提示词

system_prompt = """您需要帮助用户批量重命名文件,请遵守以下规则:
1. 只需返回文件名主体(不要带扩展名)
2. 命名格式:[主题]_[编号]
3. 编号规则:
   - 优先识别SXXEYY格式(如S01E03)
   - 其次提取两位以上连续数字
   - 最后使用顺序编号
4. 保持编号格式一致性"""

用户提示工程

user_prompt = f"""请为以下文件生成新名称(不要带扩展名):
主题:{theme}
文件列表:
{file_list}

通过结构化输入提升AI理解准确率

输出控制策略

pattern = re.compile(r'\d+\.\s*(.+)$', re.MULTILINE)
matches = pattern.findall(generated)

使用严格的正则表达式确保输出格式合规


完整实现代码

import re
import requests
from pathlib import Path
from typing import List, Dict


class SessionRenamer:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.api_url = "https://api.deepseek.com/v1/chat/completions"
        self.session_messages = []
        self._init_session()

    def _init_session(self):
        """初始化对话上下文"""
        system_prompt = """您需要帮助用户批量重命名文件,请遵守以下规则:
1. 只需返回文件名主体(不要带扩展名)
2. 命名格式:[主题]_[编号]
3. 编号规则:
   - 优先识别SXXEYY格式(如S01E03)
   - 其次提取两位以上连续数字
   - 最后使用顺序编号
4. 保持编号格式一致性"""

        self.session_messages = [{
            "role": "system",
            "content": system_prompt
        }]

    def generate_names(self, filenames: List[str], theme: str) -> Dict[str, str]:
        """生成新文件名主体(不含扩展名)"""
        file_list = "\n".join([f"{i + 1}. {Path(name).stem}" for i, name in enumerate(filenames)])

        user_prompt = f"""请为以下文件生成新名称(不要带扩展名):
主题:{theme}
文件列表:
{file_list}

请按以下格式返回:
1. 新名称1
2. 新名称2
..."""

        self.session_messages.append({"role": "user", "content": user_prompt})

        try:
            response = requests.post(
                self.api_url,
                headers={"Authorization": f"Bearer {self.api_key}"},
                json={
                    "model": "deepseek-chat",
                    "messages": self.session_messages,
                    "temperature": 0.2,
                },
                timeout=20
            ).json()

            generated_text = response['choices'][0]['message']['content']
            return self._parse_output(filenames, generated_text)
        except Exception as e:
            print(f"API调用失败: {str(e)}")
            return {}

    @staticmethod
    def _parse_output(originals: List[str], generated: str) -> Dict[str, str]:
        """解析输出并保留扩展名"""
        pattern = re.compile(r'\d+\.\s*(.+)$', re.MULTILINE)
        matches = pattern.findall(generated)

        mapping = {}
        for i, original in enumerate(originals):
            original_path = Path(original)
            # 保留原始扩展名
            new_stem = matches[i] if i < len(matches) else original_path.stem
            mapping[original] = f"{new_stem}{original_path.suffix}"
        return mapping


class BatchRenamer:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.backup_num = 0
        self.episode_re = re.compile(r'(S\d+E\d+)', re.IGNORECASE)
        self.number_re = re.compile(r'\d{2,}')
        self.auto_mode = False

    def process_folder(self, folder_path: str, theme: str):
        files = [f for f in Path(folder_path).iterdir() if f.is_file()]
        print(f"找到 {len(files)} 个文件")

        # 生成新名称映射
        renamer = SessionRenamer(self.api_key)
        originals = [f.name for f in files]
        name_mapping = renamer.generate_names(originals, theme)

        # 应用重命名
        for filepath in files:
            new_name = name_mapping.get(filepath.name, None)

            # 备用方案
            if not new_name:
                new_name = self._generate_fallback(filepath, theme)

            new_path = self._resolve_conflicts(filepath.parent / new_name)

            print(f"\n原文件: {filepath.name}")
            print(f"新名称: {new_path.name}")

            if self._get_confirmation():
                filepath.rename(new_path)
                print("✓ 已重命名")

    def _generate_fallback(self, filepath: Path, theme: str) -> str:
        """备用命名方案"""
        # 提取剧集编号
        if match := self.episode_re.search(filepath.name):
            return f"{theme}_{match.group(1)}{filepath.suffix}"

        # 提取数字编号
        if match := self.number_re.search(filepath.name):
            return f"{theme}_{match.group()}{filepath.suffix}"

        # 顺序编号
        self.backup_num += 1
        return f"{theme}_{self.backup_num:02d}{filepath.suffix}"

    @staticmethod
    def _resolve_conflicts(path: Path) -> Path:
        """处理文件名冲突"""
        counter = 1
        while path.exists():
            new_stem = f"{path.stem}_{counter}"
            path = path.with_name(f"{new_stem}{path.suffix}")
            counter += 1
        return path

    def _get_confirmation(self) -> bool:
        if self.auto_mode:
            return True
        resp = input("确认?(y/n/a/q): ").lower()
        if resp == 'a':
            self.auto_mode = True
            return True
        if resp == 'q': exit()
        return resp == 'y'


if __name__ == "__main__":
    API_KEY = "你的APIKEY"
    TARGET_FOLDER = r"文件夹地址"

    renamer = BatchRenamer(API_KEY)
    renamer.process_folder(
        folder_path=TARGET_FOLDER,
        theme=input("请输入统一主题名称: ").strip()
    )