Renomate
先给方案、你点头、才动手、随时一键后悔 的安全批量重命名工具(TUI + CLI)。
首个场景是音乐库:把 01 稻香.mp3、周杰伦 - 稻香 [320K].mp3 这类乱七八糟的名字,
统一成 稻香 - 周杰伦 - 魔杰座 - CN.mp3。
1. 安全承诺(不可妥协的四条)
| # | 承诺 | 落地方式 |
|---|---|---|
| 1 | 默认 dry-run:任何写操作都要显式 --yes |
apply 默认只打印计划,不动文件 |
| 2 | 路径黑名单:系统目录、挂载根目录一律拒绝 | safety.is_blocked_path,进入目录时立即生效 |
| 3 | 执行前确认:必须看见并点头 | TUI 确认屏要求输入 APPLY;CLI 需要 --token APPLY |
| 4 | 一键回退:改完随时后悔 | 事务日志 $XDG_DATA_HOME/renomate/journals/*.jsonl + renomate undo |
另外:符号链接、隐藏文件、非白名单类型、只读文件默认全部跳过; 单个文件失败绝不中断整批,且会尽力回滚。
2. 效果预览
┌─ renomate v0.1.0 ── profile: audio ── /mnt/d/Music ────────────────────┐
│ 模板:{title} - {artist} - {album} - {country} │
│ [x] 01 稻香.mp3 → 稻香 - 周杰伦 - 魔杰座 - CN.mp3 │
│ [x] 晴天[320K].mp3 → 晴天 - 周杰伦 - 叶惠美 - CN.mp3 │
│ [ ] unknown.mp3 → 未知标题 - 未知歌手 - 未知专辑 - CN.mp3 ▲缺失 │
│ [!!] CON.mp3 → (Windows 保留名,已拦截) │
│ [??] 重名.mp3 → (冲突未消解,需手工指定) │
│ OK 2 / -- 1 / !! 1 / ?? 1 Space 勾选 Enter 执行 u 回退 q 退出 │
└────────────────────────────────────────────────────────────────────────┘
3. 安装
推荐从 PyPI 安装;想追最新源码或参与开发可从 GitHub 安装(见下方)。
从 PyPI 安装(推荐)
pip install "renomate[audio]" # 含 mutagen 音频标签读取;不要 [audio] 也能跑(仅文件名推断)
从源码安装(开发 / 追最新)
git clone https://github.com/tochikoma777/renomate.git
cd renomate
# 方式 A:用 conda(Python 3.11+)
conda create -n renomate python=3.12 -y
conda activate renomate
pip install -e ".[audio]" # 含 mutagen 音频标签读取;不要 [audio] 也能跑(仅文件名推断)
# 方式 B:用系统/已有的 Python 3.11+(虚拟环境)
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[audio]"
装完验证:
renomate --version # 应输出版本号,如 renomate 0.1.0
renomate --help # 应列出所有子命令
开发 / 跑测试
pip install -e ".[dev,audio]"
pytest -q
conda 一键复现
仓库也提供 environment.yml:
conda env create -f environment.yml && conda activate renomate
mutagen是可选依赖且运行时动态 import:没装也能跑,只是读不出内嵌标签 (自动降级为文件名/目录推断)。音乐库场景建议带[audio]。
4. 30 秒上手
renomate scan ~/Music # 看看扫到了什么
renomate preview ~/Music --preset sorted # 只看方案,绝不改文件
renomate apply ~/Music --yes --token APPLY # 真正执行(默认 dry-run)
renomate undo --last # 后悔了,一键还原
TUI:
renomate tui
不想用预设模板,可换成自定义模板(语法见第 5 节):
renomate preview ~/Music --template "{track|pad:2}. {title} - {artist}"
5. 模板语法速查
| 写法 | 含义 |
|---|---|
{title} |
取字段值 |
{field|filter} |
应用过滤器 |
{field|filter:arg} |
带参过滤器 |
{album|default:未知专辑} |
字段为空时的缺省值 |
{track|pad:2} |
数字补零 → 01 |
{title|upper} / {lower} / {title_case} |
大小写转换 |
{artist|strip_feat} |
去掉 feat. / ft. |
{country_zh} |
国别代码转中文(CN → 中国) |
{album}/{track|pad:2} - {title} |
/ 表示创建子目录 |
渲染流程(顺序固定):取值 → 过滤器 → 逐字段 sanitize → 拼接 → 空折叠 → 长度截断 → 补扩展名。
内置预设:simple / sorted / by_album / with_year([presets] 可自行添加)。
6. 安全机制详解(五道闸门)
| 闸门 | 时机 | 行为 |
|---|---|---|
| 1 路径黑名单 | 进入目录时 | /etc、/mnt/c/Windows、C:\Program Files、挂载根目录 → 拒绝 |
| 2 类型/属性白名单 | 扫描时 | 符号链接、隐藏文件、非白名单扩展名、只读文件 → 跳过并记原因 |
| 3 计划体检 | 生成计划时 | no_op / empty_name / missing:* / path_too_long / name_too_long / cross_device / 冲突 → 跳过或拦截 |
| 4 执行前确认 | 执行前 | TUI 确认屏输入 APPLY;CLI --token APPLY |
| 5 执行期防御 | 执行时 | 两阶段 rename、大小写安全 rename、逐条 try/except |
文件名合规化(Windows/WSL 重点):非法字符替换、控制字符删除、保留设备名
(CON.txt → _CON.txt)、末尾空格与点删除、NFC 归一化、MAX_PATH=260 与 255 字符上限校验。
7. 回退怎么用
renomate journals # 列出所有 session
renomate undo --last # 回退最近一次
renomate undo --session 20261007-153012-8f3a
renomate undo --last --dry-run # 只看会做什么
renomate undo --last --only "稻香*" # 只回退匹配的文件
renomate recover ~/Music # 清理崩溃残留的 .renomate_tmp_*
回退前会校验 size + mtime_ns:与日志不一致就标 needs_manual 并跳过,
绝不基于猜测乱改名。
8. 配置
配置文件:$XDG_CONFIG_HOME/renomate/config.toml(默认 ~/.config/renomate/config.toml)。
renomate config --init # 生成带注释的默认配置
renomate config --show
renomate config --set general.max_batch=8000
renomate profiles # 列出 Profile 与字段定义
要点:
[profiles.audio]:扩展名白名单、字段列表、required_fields、默认模板、Provider 顺序;[presets]:命名预设;[sanitize]:平台(windows/posix)、策略(replace/drop/underscore)、长度上限、替换表;[safety]:额外黑名单、allow_missing_fields、require_confirm、confirm_token;[musicbrainz]:默认关闭(enabled = false),开启后限流 1 req/s 且本地缓存 30 天;[defaults]:字段兜底值(未知标题/未知歌手/CN…)。
目录推断(
pathProvider)只对嵌套文件生效:躺在扫描根目录里的文件不会把~/Downloads这种目录名当成艺术家。
9. 扩展新领域(以 PDF 为例)
- 写 Provider(实现
name/supports/fill):
from renomate.models import FieldSource, FieldValue
from renomate.providers import register_provider
class PdfTagProvider:
name = "pdftag"
def supports(self, profile: str, path) -> bool:
return path.suffix.casefold() == ".pdf"
def fill(self, bundle, cfg) -> None:
bundle.set("title", FieldValue("论文标题", FieldSource.AUDIOTAG, 0.9))
register_provider(PdfTagProvider())
- 注册 Profile(零代码,只改配置):
[profiles.pdf]
extensions = ["pdf"]
fields = ["title", "author", "year"]
required_fields = ["title"]
default_template = "{title} - {author} ({year})"
providers = ["pdftag", "filename", "default"]
- 加测试:
pytest tests/test_providers_pdf.py。核心层(models/template/plan/executor/journal)零改动。
10. WSL / Windows 注意事项
/mnt/c、/mnt/d继承 Windows 的大小写不敏感语义:A.mp3 → a.mp3必须先改到临时名, 本工具已实现(rename_case_safe)。- 盘符/挂载根目录本身(
C:\、/mnt/c)拒绝批量重命名。 - Windows 保留设备名带扩展名同样非法(
CON.txt)。 - 跨设备(EXDEV)不做 move,直接
BLOCKED: cross_device。 - 完整路径按 Windows 传统
MAX_PATH = 260校验。
11. License
本项目 MIT,见 LICENSE。
关于
mutagen:它是 GPL-2.0-or-later,仅作为可选依赖 + 运行时动态 import 使用, 本项目本身不链接、不分发它的代码,仍为 MIT;不安装[audio]额外依赖时完全不会加载它。
12. Roadmap
- 已发布到 PyPI:
pip install "renomate[audio]"即可(见第 3 节)。TestPyPI 同步有预发布验证版。 - PDF / 照片 / 视频 Profile(架构已预留,见第 9 节)
- undo 支持跨目录移动(当前仅同目录重命名)
- 重名智能合并策略(保留更高音质版本)
- 模板条件段
{album?[...]} - TUI 单元格就地编辑
Metadata
Release files for renomate 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| renomate-0.1.0.tar.gz | 92.7 kB | With provenance | Details for renomate-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| renomate-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for renomate-0.1.0-py3-none-any.whl |
Total release size: 165.2 kB
Release files / renomate-0.1.0.tar.gz
| Download URL | renomate-0.1.0.tar.gz |
|---|---|
| Size | 92.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
760ee12e7a22ab167f7f14c4025706d62b99b21cf5a9e3f9279f1612a101014b
|
|
BLAKE2b-256 checksum How to use checksums |
e9220969afd342ce7c6063fed70c8e3a02c38572d75e5b93672c3c151796a7bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / renomate-0.1.0-py3-none-any.whl
| Download URL | renomate-0.1.0-py3-none-any.whl |
|---|---|
| Size | 72.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
809719a2e4fec71c0e049f67fc17e034b1d56e390bc54436a152cd1213f8d5f1
|
|
BLAKE2b-256 checksum How to use checksums |
46bb9828aa72da85f21b6b8c610f7568577278a1f2f84e3b2d5e7f9eede6419d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logSecurity for renomate 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for renomate-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.