first commit
This commit is contained in:
155
.gitignore
vendored
Normal file
155
.gitignore
vendored
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is an error to commit Pipfile.lock with
|
||||||
|
# cross-platform compatibility, and it should be ignored:
|
||||||
|
Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak
|
||||||
|
venv.bak
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Icon?
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Batches
|
||||||
|
*.bat
|
||||||
114
README.md
Normal file
114
README.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# 文件重命名工具
|
||||||
|
|
||||||
|
这是一个功能强大的Python文件重命名工具,支持命令行和图形界面两种使用方式。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 批量文件重命名
|
||||||
|
- 简单字符串替换
|
||||||
|
- 正则表达式替换
|
||||||
|
- 添加前缀/后缀
|
||||||
|
- 自动编号
|
||||||
|
- 大小写转换
|
||||||
|
- 预览模式
|
||||||
|
- 操作撤销
|
||||||
|
- 日志记录
|
||||||
|
|
||||||
|
## 安装要求
|
||||||
|
|
||||||
|
- Python 3.6+
|
||||||
|
- 标准库(无需额外安装第三方包)
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 命令行版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看帮助
|
||||||
|
python file_renamer.py -h
|
||||||
|
|
||||||
|
# 简单替换
|
||||||
|
python file_renamer.py /path/to/directory replace "old_text" "new_text"
|
||||||
|
|
||||||
|
# 正则表达式替换
|
||||||
|
python file_renamer.py /path/to/directory regex "\d+" "#"
|
||||||
|
|
||||||
|
# 添加前缀
|
||||||
|
python file_renamer.py /path/to/directory prefix "PRE_"
|
||||||
|
|
||||||
|
# 添加后缀
|
||||||
|
python file_renamer.py /path/to/directory suffix "_SUF"
|
||||||
|
|
||||||
|
# 自动编号
|
||||||
|
python file_renamer.py /path/to/directory enumerate --prefix="IMG_" --start=1 --digits=3
|
||||||
|
|
||||||
|
# 大小写转换
|
||||||
|
python file_renamer.py /path/to/directory case lower
|
||||||
|
|
||||||
|
# 预览模式(不实际重命名)
|
||||||
|
python file_renamer.py /path/to/directory replace "old" "new" --preview
|
||||||
|
|
||||||
|
# 撤销上次操作
|
||||||
|
python file_renamer.py /path/to/directory undo
|
||||||
|
```
|
||||||
|
|
||||||
|
### 图形界面版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python gui_renamer.py
|
||||||
|
```
|
||||||
|
|
||||||
|
图形界面提供了直观的操作方式,支持以下功能:
|
||||||
|
|
||||||
|
1. 选择目录
|
||||||
|
2. 多种重命名模式切换
|
||||||
|
3. 预览功能
|
||||||
|
4. 实时结果显示
|
||||||
|
5. 撤销操作
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 示例1:批量替换文件名中的日期格式
|
||||||
|
|
||||||
|
假设有一批文件名为 `report_2023-01-01.txt`,想改为 `report_20230101.txt`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python file_renamer.py /path/to/files replace "-" ""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例2:使用正则表达式清理文件名
|
||||||
|
|
||||||
|
移除文件名中的数字:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python file_renamer.py /path/to/files regex "\d+" ""
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例3:为照片文件添加前缀和编号
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python file_renamer.py /path/to/photos enumerate --prefix="PHOTO_" --start=1 --digits=4
|
||||||
|
```
|
||||||
|
|
||||||
|
这会将文件重命名为:`PHOTO_0001.jpg`, `PHOTO_0002.png`, ...
|
||||||
|
|
||||||
|
### 示例4:统一文件名大小写
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python file_renamer.py /path/to/files case lower
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 操作前建议先使用预览模式查看结果
|
||||||
|
2. 工具会自动避免文件名冲突
|
||||||
|
3. 所有操作都会记录到日志文件中
|
||||||
|
4. 支持通过撤销功能回退最后一次操作
|
||||||
|
5. 正则表达式功能需要熟悉基本的正则语法
|
||||||
|
|
||||||
|
## 日志和历史记录
|
||||||
|
|
||||||
|
- 日志文件:`rename_log.txt`
|
||||||
|
- 历史记录:`rename_history.json`
|
||||||
|
|
||||||
|
可以通过历史记录文件查看所有重命名操作,并在需要时手动恢复文件名。
|
||||||
63
example_usage.py
Normal file
63
example_usage.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
文件重命名工具使用示例
|
||||||
|
"""
|
||||||
|
|
||||||
|
from file_renamer import FileRenamer
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""使用示例"""
|
||||||
|
# 创建重命名工具实例
|
||||||
|
renamer = FileRenamer()
|
||||||
|
|
||||||
|
# 设置要处理的目录
|
||||||
|
directory = "./test_files" # 请修改为实际的目录路径
|
||||||
|
|
||||||
|
print("文件重命名工具使用示例")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
# 1. 简单字符串替换
|
||||||
|
print("1. 简单字符串替换:")
|
||||||
|
print("将目录中所有文件名中的 'old' 替换为 'new'")
|
||||||
|
count = renamer.simple_rename(directory, "old", "new", preview=True)
|
||||||
|
print(f"预览模式下将重命名 {count} 个文件")
|
||||||
|
|
||||||
|
# 2. 正则表达式替换
|
||||||
|
print("\n2. 正则表达式替换:")
|
||||||
|
print("将文件名中的数字替换为 # 符号")
|
||||||
|
count = renamer.regex_rename(directory, r"\d+", "#", preview=True)
|
||||||
|
print(f"预览模式下将重命名 {count} 个文件")
|
||||||
|
|
||||||
|
# 3. 添加前缀
|
||||||
|
print("\n3. 添加前缀:")
|
||||||
|
print("为所有文件添加 'PREFIX_' 前缀")
|
||||||
|
count = renamer.add_prefix(directory, "PREFIX_", preview=True)
|
||||||
|
print(f"预览模式下将重命名 {count} 个文件")
|
||||||
|
|
||||||
|
# 4. 添加后缀
|
||||||
|
print("\n4. 添加后缀:")
|
||||||
|
print("为所有文件添加 '_SUFFIX' 后缀")
|
||||||
|
count = renamer.add_suffix(directory, "_SUFFIX", preview=True)
|
||||||
|
print(f"预览模式下将重命名 {count} 个文件")
|
||||||
|
|
||||||
|
# 5. 自动编号
|
||||||
|
print("\n5. 自动编号:")
|
||||||
|
print("为文件添加编号")
|
||||||
|
count = renamer.enumerate_files(directory, prefix="FILE_", start_number=1, digits=3, preview=True)
|
||||||
|
print(f"预览模式下将重命名 {count} 个文件")
|
||||||
|
|
||||||
|
# 6. 大小写转换
|
||||||
|
print("\n6. 大小写转换:")
|
||||||
|
print("将所有文件名转换为小写")
|
||||||
|
count = renamer.change_case(directory, "lower", preview=True)
|
||||||
|
print(f"预览模式下将重命名 {count} 个文件")
|
||||||
|
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print("如需实际执行重命名,请将 preview 参数设为 False")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
422
file_renamer.py
Normal file
422
file_renamer.py
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
文件重命名工具
|
||||||
|
支持批量重命名、正则表达式、预览模式等功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class FileRenamer:
|
||||||
|
def __init__(self, log_file="rename_log.txt"):
|
||||||
|
"""初始化重命名工具"""
|
||||||
|
self.rename_history = []
|
||||||
|
self.setup_logging(log_file)
|
||||||
|
|
||||||
|
def setup_logging(self, log_file):
|
||||||
|
"""设置日志记录"""
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler(log_file, encoding='utf-8'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def add_to_history(self, old_path, new_path):
|
||||||
|
"""添加重命名历史记录"""
|
||||||
|
self.rename_history.append({
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'old_path': old_path,
|
||||||
|
'new_path': new_path
|
||||||
|
})
|
||||||
|
|
||||||
|
def save_history(self, history_file="rename_history.json"):
|
||||||
|
"""保存重命名历史到文件"""
|
||||||
|
try:
|
||||||
|
with open(history_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.rename_history, f, ensure_ascii=False, indent=2)
|
||||||
|
self.logger.info(f"历史记录已保存到 {history_file}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"保存历史记录失败: {e}")
|
||||||
|
|
||||||
|
def load_history(self, history_file="rename_history.json"):
|
||||||
|
"""从文件加载重命名历史"""
|
||||||
|
try:
|
||||||
|
if os.path.exists(history_file):
|
||||||
|
with open(history_file, 'r', encoding='utf-8') as f:
|
||||||
|
self.rename_history = json.load(f)
|
||||||
|
self.logger.info(f"历史记录已从 {history_file} 加载")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"加载历史记录失败: {e}")
|
||||||
|
|
||||||
|
def undo_last_rename(self):
|
||||||
|
"""撤销最后一次重命名操作"""
|
||||||
|
if not self.rename_history:
|
||||||
|
self.logger.warning("没有可撤销的操作")
|
||||||
|
return False
|
||||||
|
|
||||||
|
last_operation = self.rename_history.pop()
|
||||||
|
old_path = last_operation['old_path']
|
||||||
|
new_path = last_operation['new_path']
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not os.path.exists(new_path):
|
||||||
|
self.logger.error(f"文件 {new_path} 不存在,无法撤销")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.rename(new_path, old_path)
|
||||||
|
self.logger.info(f"已撤销重命名: {new_path} -> {old_path}")
|
||||||
|
self.save_history()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"撤销操作失败: {e}")
|
||||||
|
# 如果撤销失败,将操作重新加入历史记录
|
||||||
|
self.rename_history.append(last_operation)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def list_files(self, directory, pattern=None):
|
||||||
|
"""列出目录中的文件"""
|
||||||
|
try:
|
||||||
|
path = Path(directory)
|
||||||
|
if not path.exists():
|
||||||
|
self.logger.error(f"目录 {directory} 不存在")
|
||||||
|
return []
|
||||||
|
|
||||||
|
files = []
|
||||||
|
for item in path.iterdir():
|
||||||
|
if item.is_file():
|
||||||
|
if pattern is None or re.search(pattern, item.name):
|
||||||
|
files.append(item)
|
||||||
|
|
||||||
|
files.sort(key=lambda x: x.name)
|
||||||
|
return files
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"列出文件时出错: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def simple_rename(self, directory, search_pattern, replace_pattern, preview=False):
|
||||||
|
"""简单的字符串替换重命名"""
|
||||||
|
files = self.list_files(directory)
|
||||||
|
if not files:
|
||||||
|
self.logger.warning("目录中没有找到文件")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
renamed_count = 0
|
||||||
|
|
||||||
|
for file_path in files:
|
||||||
|
old_name = file_path.name
|
||||||
|
new_name = old_name.replace(search_pattern, replace_pattern)
|
||||||
|
|
||||||
|
if old_name != new_name:
|
||||||
|
new_path = file_path.parent / new_name
|
||||||
|
|
||||||
|
# 检查目标文件是否已存在
|
||||||
|
if new_path.exists():
|
||||||
|
self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
print(f"[预览] {old_name} -> {new_name}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rename(file_path, new_path)
|
||||||
|
self.add_to_history(str(file_path), str(new_path))
|
||||||
|
self.logger.info(f"已重命名: {old_name} -> {new_name}")
|
||||||
|
renamed_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"重命名 {old_name} 失败: {e}")
|
||||||
|
|
||||||
|
if not preview and renamed_count > 0:
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
|
return renamed_count
|
||||||
|
|
||||||
|
def regex_rename(self, directory, regex_pattern, replace_pattern, preview=False):
|
||||||
|
"""使用正则表达式的重命名"""
|
||||||
|
files = self.list_files(directory)
|
||||||
|
if not files:
|
||||||
|
self.logger.warning("目录中没有找到文件")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
renamed_count = 0
|
||||||
|
try:
|
||||||
|
compiled_pattern = re.compile(regex_pattern)
|
||||||
|
except re.error as e:
|
||||||
|
self.logger.error(f"无效的正则表达式 '{regex_pattern}': {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
for file_path in files:
|
||||||
|
old_name = file_path.name
|
||||||
|
# 使用正则表达式进行替换
|
||||||
|
new_name = compiled_pattern.sub(replace_pattern, old_name)
|
||||||
|
|
||||||
|
if old_name != new_name:
|
||||||
|
new_path = file_path.parent / new_name
|
||||||
|
|
||||||
|
# 检查目标文件是否已存在
|
||||||
|
if new_path.exists():
|
||||||
|
self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
print(f"[预览] {old_name} -> {new_name}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rename(file_path, new_path)
|
||||||
|
self.add_to_history(str(file_path), str(new_path))
|
||||||
|
self.logger.info(f"已重命名: {old_name} -> {new_name}")
|
||||||
|
renamed_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"重命名 {old_name} 失败: {e}")
|
||||||
|
|
||||||
|
if not preview and renamed_count > 0:
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
|
return renamed_count
|
||||||
|
|
||||||
|
def add_prefix(self, directory, prefix, preview=False):
|
||||||
|
"""为文件名添加前缀"""
|
||||||
|
files = self.list_files(directory)
|
||||||
|
if not files:
|
||||||
|
self.logger.warning("目录中没有找到文件")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
renamed_count = 0
|
||||||
|
|
||||||
|
for file_path in files:
|
||||||
|
old_name = file_path.name
|
||||||
|
new_name = prefix + old_name
|
||||||
|
new_path = file_path.parent / new_name
|
||||||
|
|
||||||
|
# 检查目标文件是否已存在
|
||||||
|
if new_path.exists():
|
||||||
|
self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
print(f"[预览] {old_name} -> {new_name}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rename(file_path, new_path)
|
||||||
|
self.add_to_history(str(file_path), str(new_path))
|
||||||
|
self.logger.info(f"已重命名: {old_name} -> {new_name}")
|
||||||
|
renamed_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"重命名 {old_name} 失败: {e}")
|
||||||
|
|
||||||
|
if not preview and renamed_count > 0:
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
|
return renamed_count
|
||||||
|
|
||||||
|
def add_suffix(self, directory, suffix, preview=False):
|
||||||
|
"""为文件名添加后缀(在扩展名之前)"""
|
||||||
|
files = self.list_files(directory)
|
||||||
|
if not files:
|
||||||
|
self.logger.warning("目录中没有找到文件")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
renamed_count = 0
|
||||||
|
|
||||||
|
for file_path in files:
|
||||||
|
old_name = file_path.name
|
||||||
|
name_without_ext = file_path.stem
|
||||||
|
ext = file_path.suffix
|
||||||
|
new_name = name_without_ext + suffix + ext
|
||||||
|
new_path = file_path.parent / new_name
|
||||||
|
|
||||||
|
# 检查目标文件是否已存在
|
||||||
|
if new_path.exists():
|
||||||
|
self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
print(f"[预览] {old_name} -> {new_name}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rename(file_path, new_path)
|
||||||
|
self.add_to_history(str(file_path), str(new_path))
|
||||||
|
self.logger.info(f"已重命名: {old_name} -> {new_name}")
|
||||||
|
renamed_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"重命名 {old_name} 失败: {e}")
|
||||||
|
|
||||||
|
if not preview and renamed_count > 0:
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
|
return renamed_count
|
||||||
|
|
||||||
|
def enumerate_files(self, directory, prefix="", start_number=1, digits=3, preview=False):
|
||||||
|
"""为文件添加序号"""
|
||||||
|
files = self.list_files(directory)
|
||||||
|
if not files:
|
||||||
|
self.logger.warning("目录中没有找到文件")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
renamed_count = 0
|
||||||
|
number = start_number
|
||||||
|
|
||||||
|
for file_path in files:
|
||||||
|
old_name = file_path.name
|
||||||
|
ext = file_path.suffix
|
||||||
|
new_name = f"{prefix}{str(number).zfill(digits)}{ext}"
|
||||||
|
new_path = file_path.parent / new_name
|
||||||
|
|
||||||
|
# 检查目标文件是否已存在
|
||||||
|
if new_path.exists():
|
||||||
|
self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
print(f"[预览] {old_name} -> {new_name}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rename(file_path, new_path)
|
||||||
|
self.add_to_history(str(file_path), str(new_path))
|
||||||
|
self.logger.info(f"已重命名: {old_name} -> {new_name}")
|
||||||
|
renamed_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"重命名 {old_name} 失败: {e}")
|
||||||
|
|
||||||
|
number += 1
|
||||||
|
|
||||||
|
if not preview and renamed_count > 0:
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
|
return renamed_count
|
||||||
|
|
||||||
|
def change_case(self, directory, case_type="lower", preview=False):
|
||||||
|
"""更改文件名大小写"""
|
||||||
|
files = self.list_files(directory)
|
||||||
|
if not files:
|
||||||
|
self.logger.warning("目录中没有找到文件")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
renamed_count = 0
|
||||||
|
|
||||||
|
for file_path in files:
|
||||||
|
old_name = file_path.name
|
||||||
|
|
||||||
|
if case_type == "lower":
|
||||||
|
new_name = old_name.lower()
|
||||||
|
elif case_type == "upper":
|
||||||
|
new_name = old_name.upper()
|
||||||
|
elif case_type == "title":
|
||||||
|
new_name = old_name.title()
|
||||||
|
else:
|
||||||
|
self.logger.error(f"不支持的大小写类型: {case_type}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if old_name != new_name:
|
||||||
|
new_path = file_path.parent / new_name
|
||||||
|
|
||||||
|
# 检查目标文件是否已存在
|
||||||
|
if new_path.exists():
|
||||||
|
self.logger.warning(f"文件 {new_name} 已存在,跳过 {old_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if preview:
|
||||||
|
print(f"[预览] {old_name} -> {new_name}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rename(file_path, new_path)
|
||||||
|
self.add_to_history(str(file_path), str(new_path))
|
||||||
|
self.logger.info(f"已重命名: {old_name} -> {new_name}")
|
||||||
|
renamed_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"重命名 {old_name} 失败: {e}")
|
||||||
|
|
||||||
|
if not preview and renamed_count > 0:
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
|
return renamed_count
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数 - 命令行界面"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="文件重命名工具")
|
||||||
|
parser.add_argument("directory", help="要处理的目录路径")
|
||||||
|
parser.add_argument("-p", "--preview", action="store_true", help="预览模式,不实际重命名文件")
|
||||||
|
|
||||||
|
# 子命令
|
||||||
|
subparsers = parser.add_subparsers(dest="command", help="重命名命令")
|
||||||
|
|
||||||
|
# 简单替换命令
|
||||||
|
replace_parser = subparsers.add_parser("replace", help="简单字符串替换")
|
||||||
|
replace_parser.add_argument("search", help="要查找的字符串")
|
||||||
|
replace_parser.add_argument("replace", help="替换字符串")
|
||||||
|
|
||||||
|
# 正则表达式命令
|
||||||
|
regex_parser = subparsers.add_parser("regex", help="正则表达式替换")
|
||||||
|
regex_parser.add_argument("pattern", help="正则表达式模式")
|
||||||
|
regex_parser.add_argument("replace", help="替换字符串")
|
||||||
|
|
||||||
|
# 添加前缀命令
|
||||||
|
prefix_parser = subparsers.add_parser("prefix", help="添加前缀")
|
||||||
|
prefix_parser.add_argument("prefix", help="前缀字符串")
|
||||||
|
|
||||||
|
# 添加后缀命令
|
||||||
|
suffix_parser = subparsers.add_parser("suffix", help="添加后缀")
|
||||||
|
suffix_parser.add_argument("suffix", help="后缀字符串")
|
||||||
|
|
||||||
|
# 序号命令
|
||||||
|
enum_parser = subparsers.add_parser("enumerate", help="添加序号")
|
||||||
|
enum_parser.add_argument("--prefix", default="", help="序号前缀")
|
||||||
|
enum_parser.add_argument("--start", type=int, default=1, help="起始数字")
|
||||||
|
enum_parser.add_argument("--digits", type=int, default=3, help="数字位数")
|
||||||
|
|
||||||
|
# 大小写命令
|
||||||
|
case_parser = subparsers.add_parser("case", help="更改大小写")
|
||||||
|
case_parser.add_argument("case", choices=["lower", "upper", "title"], help="大小写类型")
|
||||||
|
|
||||||
|
# 撤销命令
|
||||||
|
undo_parser = subparsers.add_parser("undo", help="撤销最后一次重命名")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 创建重命名工具实例
|
||||||
|
renamer = FileRenamer()
|
||||||
|
|
||||||
|
# 加载历史记录
|
||||||
|
renamer.load_history()
|
||||||
|
|
||||||
|
# 执行相应命令
|
||||||
|
if args.command == "undo":
|
||||||
|
renamer.undo_last_rename()
|
||||||
|
elif args.command == "replace":
|
||||||
|
count = renamer.simple_rename(args.directory, args.search, args.replace, args.preview)
|
||||||
|
print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件")
|
||||||
|
elif args.command == "regex":
|
||||||
|
count = renamer.regex_rename(args.directory, args.pattern, args.replace, args.preview)
|
||||||
|
print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件")
|
||||||
|
elif args.command == "prefix":
|
||||||
|
count = renamer.add_prefix(args.directory, args.prefix, args.preview)
|
||||||
|
print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件")
|
||||||
|
elif args.command == "suffix":
|
||||||
|
count = renamer.add_suffix(args.directory, args.suffix, args.preview)
|
||||||
|
print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件")
|
||||||
|
elif args.command == "enumerate":
|
||||||
|
count = renamer.enumerate_files(args.directory, args.prefix, args.start, args.digits, args.preview)
|
||||||
|
print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件")
|
||||||
|
elif args.command == "case":
|
||||||
|
count = renamer.change_case(args.directory, args.case, args.preview)
|
||||||
|
print(f"{'预览' if args.preview else '重命名'}了 {count} 个文件")
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
294
gui_renamer.py
Normal file
294
gui_renamer.py
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
图形界面文件重命名工具
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from file_renamer import FileRenamer
|
||||||
|
|
||||||
|
|
||||||
|
class GuiRenamer:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("文件重命名工具")
|
||||||
|
self.root.geometry("800x600")
|
||||||
|
|
||||||
|
# 创建重命名工具实例
|
||||||
|
self.renamer = FileRenamer()
|
||||||
|
self.renamer.load_history()
|
||||||
|
|
||||||
|
# 当前选择的目录
|
||||||
|
self.current_directory = ""
|
||||||
|
|
||||||
|
# 创建界面
|
||||||
|
self.create_widgets()
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
"""创建界面组件"""
|
||||||
|
# 主框架
|
||||||
|
main_frame = ttk.Frame(self.root, padding="10")
|
||||||
|
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||||
|
|
||||||
|
# 配置网格权重
|
||||||
|
self.root.columnconfigure(0, weight=1)
|
||||||
|
self.root.rowconfigure(0, weight=1)
|
||||||
|
main_frame.columnconfigure(1, weight=1)
|
||||||
|
main_frame.rowconfigure(4, weight=1)
|
||||||
|
|
||||||
|
# 目录选择
|
||||||
|
ttk.Label(main_frame, text="选择目录:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.dir_var = tk.StringVar()
|
||||||
|
self.dir_entry = ttk.Entry(main_frame, textvariable=self.dir_var, width=50)
|
||||||
|
self.dir_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
ttk.Button(main_frame, text="浏览", command=self.browse_directory).grid(row=0, column=2, padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
# 分隔线
|
||||||
|
ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
|
||||||
|
|
||||||
|
# 重命名选项卡
|
||||||
|
self.notebook = ttk.Notebook(main_frame)
|
||||||
|
self.notebook.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||||
|
|
||||||
|
# 简单替换选项卡
|
||||||
|
self.create_replace_tab()
|
||||||
|
|
||||||
|
# 正则表达式选项卡
|
||||||
|
self.create_regex_tab()
|
||||||
|
|
||||||
|
# 添加前缀/后缀选项卡
|
||||||
|
self.create_prefix_suffix_tab()
|
||||||
|
|
||||||
|
# 序号选项卡
|
||||||
|
self.create_enumerate_tab()
|
||||||
|
|
||||||
|
# 大小写选项卡
|
||||||
|
self.create_case_tab()
|
||||||
|
|
||||||
|
# 预览按钮
|
||||||
|
self.preview_btn = ttk.Button(main_frame, text="预览", command=self.preview_rename)
|
||||||
|
self.preview_btn.grid(row=3, column=1, sticky=tk.E, pady=10)
|
||||||
|
|
||||||
|
# 执行按钮
|
||||||
|
self.execute_btn = ttk.Button(main_frame, text="执行重命名", command=self.execute_rename)
|
||||||
|
self.execute_btn.grid(row=3, column=2, padx=(5, 0), pady=10)
|
||||||
|
|
||||||
|
# 结果显示区域
|
||||||
|
result_frame = ttk.LabelFrame(main_frame, text="结果", padding="5")
|
||||||
|
result_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
|
||||||
|
result_frame.columnconfigure(0, weight=1)
|
||||||
|
result_frame.rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
self.result_text = scrolledtext.ScrolledText(result_frame, wrap=tk.WORD, height=15)
|
||||||
|
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||||
|
|
||||||
|
# 撤销按钮
|
||||||
|
self.undo_btn = ttk.Button(main_frame, text="撤销上次操作", command=self.undo_last_rename)
|
||||||
|
self.undo_btn.grid(row=5, column=0, pady=10)
|
||||||
|
|
||||||
|
# 清空结果按钮
|
||||||
|
ttk.Button(main_frame, text="清空结果", command=self.clear_result).grid(row=5, column=2, pady=10)
|
||||||
|
|
||||||
|
def create_replace_tab(self):
|
||||||
|
"""创建简单替换选项卡"""
|
||||||
|
frame = ttk.Frame(self.notebook, padding="10")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="查找:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.replace_search_var = tk.StringVar()
|
||||||
|
ttk.Entry(frame, textvariable=self.replace_search_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="替换为:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.replace_replace_var = tk.StringVar()
|
||||||
|
ttk.Entry(frame, textvariable=self.replace_replace_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
self.notebook.add(frame, text="简单替换")
|
||||||
|
self.replace_frame = frame
|
||||||
|
|
||||||
|
def create_regex_tab(self):
|
||||||
|
"""创建正则表达式选项卡"""
|
||||||
|
frame = ttk.Frame(self.notebook, padding="10")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="正则表达式:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.regex_pattern_var = tk.StringVar()
|
||||||
|
ttk.Entry(frame, textvariable=self.regex_pattern_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="替换为:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.regex_replace_var = tk.StringVar()
|
||||||
|
ttk.Entry(frame, textvariable=self.regex_replace_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
# 正则表达式说明
|
||||||
|
help_text = "常用正则表达式:\n\\d+ 匹配数字\n[a-zA-Z]+ 匹配字母\n.+ 匹配任意字符\n^ 匹配开头\n$ 匹配结尾"
|
||||||
|
ttk.Label(frame, text=help_text, foreground="gray").grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(10, 0))
|
||||||
|
|
||||||
|
self.notebook.add(frame, text="正则表达式")
|
||||||
|
self.regex_frame = frame
|
||||||
|
|
||||||
|
def create_prefix_suffix_tab(self):
|
||||||
|
"""创建前缀/后缀选项卡"""
|
||||||
|
frame = ttk.Frame(self.notebook, padding="10")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="前缀:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.prefix_var = tk.StringVar()
|
||||||
|
ttk.Entry(frame, textvariable=self.prefix_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="后缀:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.suffix_var = tk.StringVar()
|
||||||
|
ttk.Entry(frame, textvariable=self.suffix_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
self.notebook.add(frame, text="前缀/后缀")
|
||||||
|
self.prefix_suffix_frame = frame
|
||||||
|
|
||||||
|
def create_enumerate_tab(self):
|
||||||
|
"""创建序号选项卡"""
|
||||||
|
frame = ttk.Frame(self.notebook, padding="10")
|
||||||
|
frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="前缀:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.enum_prefix_var = tk.StringVar()
|
||||||
|
ttk.Entry(frame, textvariable=self.enum_prefix_var, width=30).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="起始数字:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.enum_start_var = tk.StringVar(value="1")
|
||||||
|
ttk.Entry(frame, textvariable=self.enum_start_var, width=30).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
ttk.Label(frame, text="数字位数:").grid(row=2, column=0, sticky=tk.W, pady=5)
|
||||||
|
self.enum_digits_var = tk.StringVar(value="3")
|
||||||
|
ttk.Entry(frame, textvariable=self.enum_digits_var, width=30).grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=5)
|
||||||
|
|
||||||
|
self.notebook.add(frame, text="添加序号")
|
||||||
|
self.enumerate_frame = frame
|
||||||
|
|
||||||
|
def create_case_tab(self):
|
||||||
|
"""创建大小写选项卡"""
|
||||||
|
frame = ttk.Frame(self.notebook, padding="10")
|
||||||
|
|
||||||
|
ttk.Label(frame, text="选择大小写格式:").grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||||
|
|
||||||
|
self.case_var = tk.StringVar(value="lower")
|
||||||
|
case_frame = ttk.Frame(frame)
|
||||||
|
case_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5)
|
||||||
|
|
||||||
|
ttk.Radiobutton(case_frame, text="小写 (lowercase)", variable=self.case_var, value="lower").pack(anchor=tk.W)
|
||||||
|
ttk.Radiobutton(case_frame, text="大写 (UPPERCASE)", variable=self.case_var, value="upper").pack(anchor=tk.W)
|
||||||
|
ttk.Radiobutton(case_frame, text="首字母大写 (Title Case)", variable=self.case_var, value="title").pack(anchor=tk.W)
|
||||||
|
|
||||||
|
self.notebook.add(frame, text="大小写")
|
||||||
|
self.case_frame = frame
|
||||||
|
|
||||||
|
def browse_directory(self):
|
||||||
|
"""浏览目录"""
|
||||||
|
directory = filedialog.askdirectory()
|
||||||
|
if directory:
|
||||||
|
self.dir_var.set(directory)
|
||||||
|
self.current_directory = directory
|
||||||
|
|
||||||
|
def preview_rename(self):
|
||||||
|
"""预览重命名"""
|
||||||
|
self.perform_rename(preview=True)
|
||||||
|
|
||||||
|
def execute_rename(self):
|
||||||
|
"""执行重命名"""
|
||||||
|
self.perform_rename(preview=False)
|
||||||
|
|
||||||
|
def perform_rename(self, preview=True):
|
||||||
|
"""执行重命名操作"""
|
||||||
|
# 获取当前目录
|
||||||
|
directory = self.dir_var.get().strip()
|
||||||
|
if not directory:
|
||||||
|
messagebox.showerror("错误", "请选择一个目录")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
messagebox.showerror("错误", "选择的目录不存在")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.current_directory = directory
|
||||||
|
|
||||||
|
# 获取当前选中的选项卡
|
||||||
|
current_tab = self.notebook.index(self.notebook.select())
|
||||||
|
|
||||||
|
# 在新线程中执行重命名以避免界面冻结
|
||||||
|
thread = threading.Thread(target=self._perform_rename_thread, args=(current_tab, preview))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _perform_rename_thread(self, tab_index, preview):
|
||||||
|
"""在后台线程中执行重命名"""
|
||||||
|
try:
|
||||||
|
# 根据选项卡索引执行不同的重命名操作
|
||||||
|
if tab_index == 0: # 简单替换
|
||||||
|
search_pattern = self.replace_search_var.get()
|
||||||
|
replace_pattern = self.replace_replace_var.get()
|
||||||
|
count = self.renamer.simple_rename(self.current_directory, search_pattern, replace_pattern, preview)
|
||||||
|
|
||||||
|
elif tab_index == 1: # 正则表达式
|
||||||
|
regex_pattern = self.regex_pattern_var.get()
|
||||||
|
replace_pattern = self.regex_replace_var.get()
|
||||||
|
count = self.renamer.regex_rename(self.current_directory, regex_pattern, replace_pattern, preview)
|
||||||
|
|
||||||
|
elif tab_index == 2: # 前缀/后缀
|
||||||
|
prefix = self.prefix_var.get()
|
||||||
|
suffix = self.suffix_var.get()
|
||||||
|
count = 0
|
||||||
|
if prefix:
|
||||||
|
count += self.renamer.add_prefix(self.current_directory, prefix, preview)
|
||||||
|
if suffix:
|
||||||
|
count += self.renamer.add_suffix(self.current_directory, suffix, preview)
|
||||||
|
|
||||||
|
elif tab_index == 3: # 序号
|
||||||
|
prefix = self.enum_prefix_var.get()
|
||||||
|
try:
|
||||||
|
start = int(self.enum_start_var.get() or "1")
|
||||||
|
digits = int(self.enum_digits_var.get() or "3")
|
||||||
|
count = self.renamer.enumerate_files(self.current_directory, prefix, start, digits, preview)
|
||||||
|
except ValueError:
|
||||||
|
self.show_result("错误: 起始数字或数字位数必须是整数")
|
||||||
|
return
|
||||||
|
|
||||||
|
elif tab_index == 4: # 大小写
|
||||||
|
case_type = self.case_var.get()
|
||||||
|
count = self.renamer.change_case(self.current_directory, case_type, preview)
|
||||||
|
|
||||||
|
# 显示结果
|
||||||
|
action = "预览" if preview else "重命名"
|
||||||
|
self.show_result(f"{action}完成,共处理了 {count} 个文件")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.show_result(f"操作失败: {str(e)}")
|
||||||
|
|
||||||
|
def show_result(self, message):
|
||||||
|
"""在结果区域显示信息"""
|
||||||
|
self.result_text.insert(tk.END, f"{message}\n")
|
||||||
|
self.result_text.see(tk.END)
|
||||||
|
|
||||||
|
def clear_result(self):
|
||||||
|
"""清空结果区域"""
|
||||||
|
self.result_text.delete(1.0, tk.END)
|
||||||
|
|
||||||
|
def undo_last_rename(self):
|
||||||
|
"""撤销上次重命名操作"""
|
||||||
|
try:
|
||||||
|
if self.renamer.undo_last_rename():
|
||||||
|
self.show_result("成功撤销上次重命名操作")
|
||||||
|
else:
|
||||||
|
self.show_result("没有可撤销的操作")
|
||||||
|
except Exception as e:
|
||||||
|
self.show_result(f"撤销操作失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
root = tk.Tk()
|
||||||
|
app = GuiRenamer(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
146
test_renamer.py
Normal file
146
test_renamer.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
文件重命名工具测试脚本
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from file_renamer import FileRenamer
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_files(directory):
|
||||||
|
"""创建测试文件"""
|
||||||
|
test_files = [
|
||||||
|
"test_file_1.txt",
|
||||||
|
"test_file_2.txt",
|
||||||
|
"document_2023-01-01.pdf",
|
||||||
|
"image_001.jpg",
|
||||||
|
"IMAGE_002.JPG",
|
||||||
|
"Report.DOCX"
|
||||||
|
]
|
||||||
|
|
||||||
|
for filename in test_files:
|
||||||
|
filepath = os.path.join(directory, filename)
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(f"这是测试文件 {filename} 的内容")
|
||||||
|
|
||||||
|
|
||||||
|
def test_simple_rename(renamer, test_dir):
|
||||||
|
"""测试简单重命名功能"""
|
||||||
|
print("测试简单重命名功能...")
|
||||||
|
|
||||||
|
# 预览模式
|
||||||
|
count = renamer.simple_rename(test_dir, "test", "example", preview=True)
|
||||||
|
print(f"预览模式重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
# 实际重命名
|
||||||
|
count = renamer.simple_rename(test_dir, "test", "example", preview=False)
|
||||||
|
print(f"实际重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
# 验证结果
|
||||||
|
files = os.listdir(test_dir)
|
||||||
|
renamed_files = [f for f in files if f.startswith("example")]
|
||||||
|
print(f"重命名后的文件: {renamed_files}")
|
||||||
|
|
||||||
|
|
||||||
|
def test_regex_rename(renamer, test_dir):
|
||||||
|
"""测试正则表达式重命名功能"""
|
||||||
|
print("\n测试正则表达式重命名功能...")
|
||||||
|
|
||||||
|
# 预览模式
|
||||||
|
count = renamer.regex_rename(test_dir, r"\d+", "#", preview=True)
|
||||||
|
print(f"预览模式重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
# 实际重命名
|
||||||
|
count = renamer.regex_rename(test_dir, r"\d+", "#", preview=False)
|
||||||
|
print(f"实际重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
|
||||||
|
def test_prefix_suffix(renamer, test_dir):
|
||||||
|
"""测试前缀/后缀功能"""
|
||||||
|
print("\n测试前缀/后缀功能...")
|
||||||
|
|
||||||
|
# 添加前缀
|
||||||
|
count = renamer.add_prefix(test_dir, "PRE_", preview=False)
|
||||||
|
print(f"添加前缀重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
# 添加后缀
|
||||||
|
count = renamer.add_suffix(test_dir, "_SUF", preview=False)
|
||||||
|
print(f"添加后缀重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
|
||||||
|
def test_enumerate(renamer, test_dir):
|
||||||
|
"""测试编号功能"""
|
||||||
|
print("\n测试编号功能...")
|
||||||
|
|
||||||
|
count = renamer.enumerate_files(test_dir, prefix="FILE_", start_number=1, digits=3, preview=False)
|
||||||
|
print(f"编号重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
|
||||||
|
def test_case(renamer, test_dir):
|
||||||
|
"""测试大小写转换功能"""
|
||||||
|
print("\n测试大小写转换功能...")
|
||||||
|
|
||||||
|
# 转换为小写
|
||||||
|
count = renamer.change_case(test_dir, "lower", preview=False)
|
||||||
|
print(f"小写转换重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
# 转换为大写
|
||||||
|
count = renamer.change_case(test_dir, "upper", preview=False)
|
||||||
|
print(f"大写转换重命名了 {count} 个文件")
|
||||||
|
|
||||||
|
|
||||||
|
def test_undo(renamer):
|
||||||
|
"""测试撤销功能"""
|
||||||
|
print("\n测试撤销功能...")
|
||||||
|
|
||||||
|
result = renamer.undo_last_rename()
|
||||||
|
if result:
|
||||||
|
print("成功撤销一次操作")
|
||||||
|
else:
|
||||||
|
print("撤销操作失败或无操作可撤销")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主测试函数"""
|
||||||
|
print("开始测试文件重命名工具...")
|
||||||
|
|
||||||
|
# 创建临时测试目录
|
||||||
|
test_dir = tempfile.mkdtemp(prefix="renamer_test_")
|
||||||
|
print(f"创建测试目录: {test_dir}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建测试文件
|
||||||
|
create_test_files(test_dir)
|
||||||
|
print(f"创建测试文件: {os.listdir(test_dir)}")
|
||||||
|
|
||||||
|
# 创建重命名工具实例
|
||||||
|
renamer = FileRenamer(os.path.join(test_dir, "test_rename_log.txt"))
|
||||||
|
|
||||||
|
# 运行各项测试
|
||||||
|
test_simple_rename(renamer, test_dir)
|
||||||
|
test_regex_rename(renamer, test_dir)
|
||||||
|
test_prefix_suffix(renamer, test_dir)
|
||||||
|
test_enumerate(renamer, test_dir)
|
||||||
|
test_case(renamer, test_dir)
|
||||||
|
test_undo(renamer)
|
||||||
|
|
||||||
|
print("\n测试完成!")
|
||||||
|
print(f"最终文件列表: {os.listdir(test_dir)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"测试过程中出现错误: {e}")
|
||||||
|
finally:
|
||||||
|
# 清理测试目录
|
||||||
|
try:
|
||||||
|
shutil.rmtree(test_dir)
|
||||||
|
print(f"已清理测试目录: {test_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"清理测试目录时出错: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user