用文件夹名批量重命名工具
用文件夹名批量重命名工具 软件说明书一、软件概述
本软件是一款功能强大的文件批量重命名工具,具有用文件夹名批量重命名的选项,专为需要高效管理大量文件的用户设计。支持多种重命名规则组合,提供实时预览功能,确保操作安全可靠。软件具有直观的图形界面,操作简单便捷,是文件整理、媒体管理、文档归档的理想助手。
二、系统要求
- 操作系统:Windows 7/8/10/11 (64位)
- 运行环境:Python 3.6+ (已内置在安装包中)
- 内存:至少2GB RAM
- 硬盘空间:50MB可用空间
三、主要功能
1. 多种重命名规则组合:
- 添加前缀/后缀
- 插入序列号(可设置起始值和位数)
- 添加日期/时间戳
- 使用父目录名(可设置层级)
- 替换/删除指定文字
- 修改文件扩展名
2. 智能预览功能:
- 实时预览重命名效果
- 自动检测文件名冲突
- 显示完整文件路径
3. 文件管理:
- 支持拖放添加文件
- 文件类型筛选(图片/文档/视频/音频)
- 包含子目录处理
- 删除不需要的文件条目
4. 安全保障:
- 操作前确认提示
- 一键撤销功能
- 自动备份重命名记录
四、界面说明
1. 目录选择区:
- 当前路径显示
- 路径输入框(支持回车确认)
- "前往"和"选择目录"按钮
- 文件树视图(显示目录结构)
2. 重命名规则区:
- 父目录选项(层级选择)
- 前缀/后缀输入框
- 序列号设置(位置、位数、起始值)
- 日期添加选项
- 文件类型筛选
- 替换/删除文字功能
- 扩展名修改
3. 操作按钮区:
- 执行重命名
- 撤销操作
- 删除选中文件
4. 预览区:
- 原始文件名
- 新文件名
- 状态指示(有效/冲突)
- 文件路径
- 右键点击预览表格的原始名称行,弹出菜单:上移、下移、移到首行、移到尾行;
五、高级技巧
1. 批量删除文字:使用删除功能快速移除文件名中不需要的广告词或前缀
2. 媒体文件整理:使用"图片/视频/音频"筛选+日期+序列号,快速整理照片和视频
3. 文档归档:使用父目录名+日期+序列号,创建结构化文件名
4. 多文件夹独立编号:勾选"每文件夹独立编号",为每个文件夹的文件单独编号
5. 自定义日期格式:选择"自定义..."后输入Python日期格式代码(如:%Y%m%d_%H%M%S)
六、注意事项
1. 重名前请确认预览效果,避免误操作
2. 撤销功能仅支持软件内执行的重命名操作
3. 避免在文件名中使用非法字符:\/:*?"|
4. 修改扩展名不会改变文件实际格式,仅修改文件名
5. 大量文件操作时建议先小批量测试
七、常见问题解答
Q1: 为什么预览没有变化?
A: 检查规则是否启用(复选框已勾选),确认输入内容正确,尝试调整规则顺序
Q2: 撤销功能失效怎么办?
A: 1) 确保只撤销最近一次操作 2) 检查备份文件是否被删除 3) 文件已被其他程序占用
Q3: 如何批量添加序号?
A: 启用序列号功能,设置起始值和位数,选择位置(前置/后置)
Q4: 文件名冲突如何解决?
A: 预览区状态列会显示冲突,调整前缀、序列号或删除部分文件条目
Q5: 能否恢复多次操作?
A: 当前仅支持撤销最近一次操作,建议分批操作重要文件
根据提议,新增鼠标右键上下移动位置,新增蓝奏云链接。
下载地址:如有解压密码,请用52pojie
新增蓝奏云下载地址
下载地址.txt(270 Bytes, 下载次数: 268)2025-6-13 14:45 上传
点击文件名下载附件
下载地址 已添加相应功能,请再次试用。
import os
import sys
import json
import datetime
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QPushButton, QFileDialog, QLabel,
QLineEdit, QComboBox, QCheckBox, QSpinBox, QTableView,
QMessageBox, QTreeView, QWidget, QHBoxLayout, QVBoxLayout, QGroupBox,
QProgressDialog, QFileSystemModel, QGridLayout, QFileIconProvider,
QStyledItemDelegate, QAbstractItemView
)
from PyQt5.QtCore import Qt, QDir, QModelIndex, QThread, pyqtSignal, QTimer, QUrl
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QGuiApplication, QIcon
class RenameWorker(QThread):
"""文件重命名的后台工作线程"""
progress_updated = pyqtSignal(int)
rename_finished = pyqtSignal(bool)
error_occurred = pyqtSignal(str)
canceled = pyqtSignal()
def __init__(self, file_list, operations):
super().__init__()
self.file_list = file_list
self.operations = operations
self.backup_list = []
def run(self):
try:
count = len(self.file_list)
for i, file_info in enumerate(self.file_list, start=1):
if self.isInterruptionRequested():
self.canceled.emit()
return
old_path = file_info['path']
new_name = RenamerGUI.generate_preview_name(file_info, self.operations)
new_path = os.path.join(file_info['dir'], new_name)
if old_path == new_path:
continue
if os.path.exists(new_path):
raise FileExistsError(f"文件已存在:{new_name}")
os.rename(old_path, new_path)
self.backup_list.append((old_path, new_path))
self.progress_updated.emit(int(i / count * 100))
self.rename_finished.emit(True)
except Exception as e:
self.error_occurred.emit(str(e))
class CustomLabel(QLabel):
"""自定义标签,用于显示完整路径(鼠标悬停时)"""
def __init__(self, parent=None):
super().__init__(parent)
self.full_path = ""
def setFullPath(self, path):
self.full_path = path
def enterEvent(self, event):
if self.full_path:
self.setText(self.full_path)
def leaveEvent(self, event):
short_path = os.path.basename(self.full_path) if self.full_path else ""
self.setText(f"当前目录: {short_path}")
class MyItemDelegate(QStyledItemDelegate):
"""自定义表格项委托,用于显示文件和文件夹图标"""
def __init__(self, folder_icon, file_icon, parent=None):
super().__init__(parent)
self.folder_icon = folder_icon
self.file_icon = file_icon
def paint(self, painter, option, index):
if index.isValid():
if index.model().fileInfo(index).isDir():
icon = self.folder_icon
else:
icon = self.file_icon
pixmap = icon.pixmap(option.rect.size())
painter.drawPixmap(option.rect, pixmap)
text_rect = option.rect.adjusted(icon.actualSize(option.rect.size()).width(), 0, 0, 0)
painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, index.model().data(index, Qt.DisplayRole))
else:
super().paint(painter, option, index)
class RenamerGUI(QMainWindow):
"""文件批量重命名工具的主窗口类"""
def __init__(self):
super().__init__()
self.current_dir = QDir.homePath()
self.file_list = []
self.backup_info = []
self.backup_file = "rename_backup.json"
self.setAcceptDrops(True)
self.setWindowTitle("文件批量重命名工具 www.52pojie.cn")
self.resize(800, 650)
self.init_ui()
self.load_backup_from_file()
self.preview_timer = None
@staticmethod
def generate_preview_name(file_info, operations):
"""根据操作参数生成预览文件名"""
name = file_info['name']
# 处理文字替换
if operations.get('replace_enabled', False):
old_text = operations.get('replace_old', '')
new_text = operations.get('replace_new', '')
if old_text:
name = name.replace(old_text, new_text)
# 处理文字删除
if operations.get('delete_enabled', False):
delete_text = operations.get('delete_text', '')
if delete_text:
name = name.replace(delete_text, '')
parts = []
# 处理父目录名(可选择前置或后置)
parent_dirs = RenamerGUI.get_parent_dirs(file_info['dir'], operations.get('parent_level', 0))
if operations.get('use_parent', False):
if operations.get('parent_pos') == "前置":
parts.extend(parent_dirs)
# 添加前缀
if operations.get('prefix'):
parts.append(operations['prefix'])
# 添加序列号(可选择前置或后置)
if operations.get('sequence', False):
seq_text = f"{file_info['seq']:0{operations.get('seq_digits', 2)}d}"
if operations.get('seq_pos') == "前置":
parts.append(seq_text)
# 添加处理后的文件名(根据保留原文件名选项)
if operations.get('keep_original', False):
parts.append(name)
# 添加序列号(后置情况)
if operations.get('sequence', False) and operations.get('seq_pos') == "后置":
parts.append(seq_text)
# 添加后缀
if operations.get('suffix'):
parts.append(operations['suffix'])
# 添加日期
if operations.get('date_enabled', False) and operations.get('date_format'):
try:
date_str = datetime.datetime.now().strftime(operations['date_format'])
except ValueError:
date_str = "格式错误"
parts.append(date_str)
# 处理父目录名后置的情况
if operations.get('use_parent', False) and operations.get('parent_pos') == "后置":
parts.extend(parent_dirs)
# 处理扩展名
ext = operations.get('new_ext', file_info['ext'])
if operations.get('change_ext', False) and ext:
if not ext.startswith('.'):
ext = f".{ext}"
else:
ext = file_info['ext']
base = '_'.join(filter(None, parts))
# 组合最终文件名 - 确保总有一个名称部分
if base:
return f"{base}{ext}"
elif name:
return f"{name}{ext}"
else:
return f"{file_info['name']}{ext}"
@staticmethod
def get_parent_dirs(path, levels):
"""获取指定层级的父目录名列表"""
parents = []
for _ in range(levels):
path, dirname = os.path.split(path)
if dirname:
parents.insert(0, dirname)
else:
break
return parents[:levels]
def init_ui(self):
"""初始化用户界面"""
main_widget = QWidget()
self.setCentralWidget(main_widget)
layout = QVBoxLayout(main_widget)
# 目录选择区域
dir_layout = QHBoxLayout()
self.dir_label = CustomLabel()
self.dir_label.setFullPath(self.current_dir)
self.path_input = QLineEdit(self.current_dir)
self.path_input.returnPressed.connect(self.go_to_path)
go_btn = QPushButton("前往")
go_btn.clicked.connect(self.go_to_path)
select_btn = QPushButton("选择目录")
select_btn.clicked.connect(self.select_directory)
dir_layout.addWidget(self.dir_label, 1)
dir_layout.addWidget(self.path_input, 3)
dir_layout.addWidget(go_btn)
dir_layout.addWidget(select_btn)
# 文件树视图
self.file_system_model = QFileSystemModel()
self.file_tree = QTreeView()
self.file_tree.setModel(self.file_system_model)
self.file_tree.clicked.connect(self.on_dir_selected)
self.file_system_model.setRootPath(QDir.rootPath())
icon_provider = QFileIconProvider()
folder_icon = icon_provider.icon(QFileIconProvider.Folder)
file_icon = icon_provider.icon(QFileIconProvider.File)
self.file_tree.setItemDelegate(MyItemDelegate(folder_icon, file_icon))
# 重命名规则设置区域
settings_group = QGroupBox("重命名规则")
settings_layout = QGridLayout(settings_group)
settings_layout.setVerticalSpacing(5)
settings_layout.setHorizontalSpacing(5)
# 第三行设置:父目录、前缀、后缀
row1 = QHBoxLayout()
self.parent_cb = QCheckBox("使用父目录名")
self.parent_level = QSpinBox()
self.parent_level.setRange(1, 5)
self.parent_level.setFixedWidth(40)
self.parent_pos = QComboBox()# 父目录位置选择
self.parent_pos.addItems(["前置", "后置"])
row1.addWidget(self.parent_cb)
row1.addWidget(QLabel("层级"))
row1.addWidget(self.parent_level)
row1.addWidget(QLabel("位置"))
row1.addWidget(self.parent_pos)
row1.addWidget(QLabel("前缀"))
self.prefix_input = QLineEdit()
row1.addWidget(self.prefix_input)
row1.addWidget(QLabel("后缀"))
self.suffix_input = QLineEdit()
row1.addWidget(self.suffix_input)
settings_layout.addLayout(row1, 2, 0, 1, 7)
# 第二行设置:保留原文件名、序列号
row2 = QHBoxLayout()
self.keep_original = QCheckBox("保留原文件名")
self.sequence_cb = QCheckBox("序列号")
self.seq_start = QSpinBox()
self.seq_start.setRange(1, 9999)
self.seq_start.setFixedWidth(40)
self.seq_digits = QSpinBox()
self.seq_digits.setRange(2, 6)
self.seq_digits.setFixedWidth(40)
self.seq_pos = QComboBox()# 序列号位置选择
self.seq_pos.addItems(["前置", "后置"])
self.separate_seq = QCheckBox("每文件夹独立编号")
row2.addWidget(self.keep_original)
row2.addWidget(self.sequence_cb)
row2.addWidget(QLabel("起始"))
row2.addWidget(self.seq_start)
row2.addWidget(QLabel("位数"))
row2.addWidget(self.seq_digits)
row2.addWidget(QLabel("位置"))
row2.addWidget(self.seq_pos)
row2.addWidget(self.separate_seq)
settings_layout.addLayout(row2, 1, 0, 1, 4)
# 第一行设置:文件类型过滤、包含子目录、添加日期格式、修改扩展名
row3 = QHBoxLayout()
# 文件类型过滤
self.file_type = QComboBox()
self.file_type.addItems(["所有文件", "图片", "文档", "视频", "音频", "自定义类型"])
self.file_type.setFixedWidth(120)
row3.addWidget(self.file_type)
# 自定义扩展名输入
self.custom_ext = QLineEdit()
self.custom_ext.setPlaceholderText("如 jpg png")
self.custom_ext.setFixedWidth(100)
row3.addWidget(self.custom_ext)
# 包含子目录
self.recursive = QCheckBox("包含子目录")
row3.addWidget(self.recursive)
# 添加日期功能
self.date_cb = QCheckBox("添加日期")
row3.addWidget(self.date_cb)
# 日期格式
self.date_format = QComboBox()
self.date_format.setFixedWidth(120)
self.date_format.addItems([
"yyyy-mm-dd", "yyyy/mm/dd", "yyyymmdd",
"yy-mm-dd", "yy/mm/dd", "yyyymm",
"yyyy年mm月dd日", "自定义..."
])
self.date_format.setCurrentIndex(2)
self.date_format.currentIndexChanged.connect(self.on_date_format_changed)
row3.addWidget(self.date_format)
# 自定义日期格式输入框
self.custom_date_format = QLineEdit("%Y%m%d")
self.custom_date_format.setFixedWidth(120)
self.custom_date_format.setVisible(False)
row3.addWidget(self.custom_date_format)
# 修改扩展名
self.change_ext_cb = QCheckBox("修改扩展名")
row3.addWidget(self.change_ext_cb)
# 新扩展名输入框
self.new_ext_input = QLineEdit()
self.new_ext_input.setPlaceholderText("如 .png")
self.new_ext_input.setFixedWidth(80)
self.new_ext_input.setEnabled(False)
self.change_ext_cb.toggled.connect(lambda: self.new_ext_input.setEnabled(self.change_ext_cb.isChecked()))
row3.addWidget(self.new_ext_input)
settings_layout.addLayout(row3, 0, 0, 1, 5)
# 第四行设置:替换和删除文字
row4 = QHBoxLayout()
self.replace_enabled = QCheckBox("替换原文件名文字")
self.replace_old = QLineEdit()
self.replace_old.setPlaceholderText("原文件名中要替换的文字")
self.replace_new = QLineEdit()
self.replace_new.setPlaceholderText("替换为")
self.delete_enabled = QCheckBox("删除原文件名文字")
self.delete_text = QLineEdit()
self.delete_text.setPlaceholderText("原文件名中要删除的文字")
row4.addWidget(self.replace_enabled)
row4.addWidget(self.replace_old)
row4.addWidget(QLabel("→"))
row4.addWidget(self.replace_new)
row4.addWidget(self.delete_enabled)
row4.addWidget(self.delete_text)
settings_layout.addLayout(row4, 3, 0, 1, 7)
# 按钮区域
btn_layout = QHBoxLayout()
self.rename_btn = QPushButton("执行重命名")
self.rename_btn.clicked.connect(self.start_rename)
self.undo_btn = QPushButton("撤销")
self.undo_btn.clicked.connect(self.undo_rename)
self.delete_btn = QPushButton("删除选中")
self.delete_btn.clicked.connect(self.delete_selected)
btn_layout.addWidget(self.rename_btn)
btn_layout.addWidget(self.undo_btn)
btn_layout.addWidget(self.delete_btn)
# 预览表格
self.preview_model = QStandardItemModel()
self.preview_table = QTableView()
self.preview_table.setModel(self.preview_model)
self.preview_table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.preview_table.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.preview_model.setHorizontalHeaderLabels(["原始名称", "新名称", "状态", "文件路径"])
# 组装界面
layout.addLayout(dir_layout)
layout.addWidget(self.file_tree, 1)
layout.addWidget(settings_group, 0)
layout.addLayout(btn_layout)
layout.addWidget(self.preview_table, 2)
# 连接信号和槽
self.parent_cb.stateChanged.connect(self.auto_update_preview)
self.parent_level.valueChanged.connect(self.auto_update_preview)
self.parent_pos.currentIndexChanged.connect(self.auto_update_preview)
self.prefix_input.textChanged.connect(self.auto_update_preview)
self.suffix_input.textChanged.connect(self.auto_update_preview)
self.keep_original.toggled.connect(self.auto_update_preview)
self.sequence_cb.toggled.connect(self.auto_update_preview)
self.seq_start.valueChanged.connect(self.auto_update_preview)
self.seq_digits.valueChanged.connect(self.auto_update_preview)
self.seq_pos.currentIndexChanged.connect(self.auto_update_preview)
self.separate_seq.toggled.connect(self.auto_update_preview)
self.date_cb.toggled.connect(self.auto_update_preview)
self.date_format.currentIndexChanged.connect(self.auto_update_preview)
self.custom_date_format.textChanged.connect(self.auto_update_preview)
self.file_type.currentIndexChanged.connect(self.auto_update_preview)
self.custom_ext.textChanged.connect(self.auto_update_preview)
self.recursive.toggled.connect(self.auto_update_preview)
self.change_ext_cb.toggled.connect(self.auto_update_preview)
self.new_ext_input.textChanged.connect(self.auto_update_preview)
self.replace_enabled.toggled.connect(self.auto_update_preview)
self.replace_old.textChanged.connect(self.auto_update_preview)
self.replace_new.textChanged.connect(self.auto_update_preview)
self.delete_enabled.toggled.connect(self.auto_update_preview)
self.delete_text.textChanged.connect(self.auto_update_preview)
self.status_bar = self.statusBar()
self.status_bar.showMessage("就绪")
def go_to_path(self):
"""跳转到用户输入的路径"""
input_path = self.path_input.text()
if os.path.isdir(input_path):
self.current_dir = input_path
self.dir_label.setFullPath(self.current_dir)
self.file_tree.setRootIndex(self.file_system_model.index(self.current_dir))
self.auto_update_preview()
else:
QMessageBox.warning(self, "路径错误", "输入的路径无效,请检查后重试。")
def validate_name(self, name, dir_path):
"""验证文件名是否有效"""
if not name:
return False
invalid_chars = ['\\', '/', ':', '*', '?', '"', '<', '>', '|']
if any(c in name for c in invalid_chars):
return False
return not os.path.exists(os.path.join(dir_path, name))
def auto_update_preview(self, *args):
"""自动更新预览(延迟执行,避免频繁操作)"""
if self.preview_timer:
self.preview_timer.stop()
self.preview_timer = QTimer(self)
self.preview_timer.setSingleShot(True)
self.preview_timer.timeout.connect(self.refresh_preview)
self.preview_timer.start(300)
def refresh_preview(self):
"""刷新预览表格"""
self.scan_files()
self.preview_model.setRowCount(0)
operations = self.get_operations()
seq = self.seq_start.value()
dir_groups = {}
for file_info in self.file_list:
dir_path = file_info['dir']
if dir_path not in dir_groups:
dir_groups = {'seq': seq, 'files': []}
dir_groups['files'].append(file_info)
for dir_path, group in dir_groups.items():
for file_info in group['files']:
file_info['seq'] = group['seq']
group['seq'] += 1
if not self.separate_seq.isChecked():
seq = group['seq']
for file_info in self.file_list:
try:
new_name = self.generate_preview_name(file_info, operations)
status = self.validate_name(new_name, file_info['dir'])
item_old = QStandardItem(file_info['name'] + file_info['ext'])
item_new = QStandardItem(new_name)
item_status = QStandardItem("有效" if status else "冲突")
item_status.setForeground(Qt.red if not status else Qt.black)
item_path = QStandardItem(file_info['path'])
item_path.setToolTip(file_info['path'])
self.preview_model.appendRow()
except Exception as e:
QMessageBox.warning(self, "预览错误", str(e))
self.preview_table.resizeColumnsToContents()
self.status_bar.showMessage(f"预览:{len(self.file_list)} 个文件")
def scan_files(self):
"""扫描目录中的文件"""
self.file_list = []
ext_list = self.get_filter_extensions()
start_seq = self.seq_start.value()
if self.current_dir:
try:
if self.recursive.isChecked():
# 递归模式下,按文件夹分组处理
seq = start_seq
for root, dirs, files in os.walk(self.current_dir):
valid_files = []
for filename in files:
if self.is_valid_file(filename, ext_list):
valid_files.append(filename)
if valid_files:
# 处理当前目录的文件
dir_seq = seq
for filename in valid_files:
base, ext = os.path.splitext(filename)
self.file_list.append({
'dir': root,
'name': base,
'ext': ext,
'path': os.path.join(root, filename),
'seq': dir_seq
})
dir_seq += 1
# 如果启用独立编号,则下一个目录从起始编号开始
if self.separate_seq.isChecked():
seq = start_seq
else:
seq = dir_seq
else:
# 非递归模式
files =
seq = start_seq
for filename in files:
if self.is_valid_file(filename, ext_list):
base, ext = os.path.splitext(filename)
self.file_list.append({
'dir': self.current_dir,
'name': base,
'ext': ext,
'path': os.path.join(self.current_dir, filename),
'seq': seq
})
seq += 1
except Exception as e:
QMessageBox.critical(self, "扫描错误", f"文件扫描失败:{str(e)}")
def is_valid_file(self, filename, ext_list):
"""检查文件是否符合扩展名过滤条件"""
if not ext_list:
return True
ext = os.path.splitext(filename).lower()
return ext in ext_list
def get_filter_extensions(self):
"""获取过滤扩展名列表"""
type_map = {
"图片": [".jpg", ".jpeg", ".png", ".gif", ".bmp"],
"文档": [".doc", ".docx", ".pdf", ".txt", ".xls"],
"视频": [".mp4", ".avi", ".mkv", ".mov"],
"音频": [".mp3", ".wav", ".flac", ".ogg"],
"自定义类型": self.parse_custom_ext()
}
key = self.file_type.currentText()
return type_map.get(key, []) if key != "所有文件" else []
def parse_custom_ext(self):
"""解析自定义扩展名输入"""
raw = self.custom_ext.text().strip().lower()
exts = []
for ext in raw.split():
ext = ext.lstrip('.')
if ext:
exts.append(f".{ext}")
return exts or []
def on_date_format_changed(self, index):
"""日期格式选择变化时的处理"""
if index == len(self.date_format) - 1:
self.custom_date_format.setVisible(True)
else:
self.custom_date_format.setVisible(False)
self.auto_update_preview()
def get_operations(self):
"""获取当前重命名操作参数"""
date_enabled = self.date_cb.isChecked()
if date_enabled:
if self.date_format.currentIndex() == self.date_format.count() - 1:
date_format = self.custom_date_format.text()
else:
date_format = self.get_date_format_string(self.date_format.currentIndex())
else:
date_format = None
replace_enabled = self.replace_enabled.isChecked()
replace_old = self.replace_old.text().strip()
replace_new = self.replace_new.text().strip()
delete_enabled = self.delete_enabled.isChecked()
delete_text = self.delete_text.text().strip()
return {
"use_parent": self.parent_cb.isChecked(),
"parent_level": self.parent_level.value(),
"parent_pos": self.parent_pos.currentText(),
"prefix": self.prefix_input.text(),
"suffix": self.suffix_input.text(),
"keep_original": self.keep_original.isChecked(),
"sequence": self.sequence_cb.isChecked(),
"seq_digits": self.seq_digits.value(),
"seq_pos": self.seq_pos.currentText(),
"date_enabled": date_enabled,
"date_format": date_format,
"change_ext": self.change_ext_cb.isChecked(),
"new_ext": self.new_ext_input.text().strip(),
"replace_enabled": replace_enabled,
"replace_old": replace_old,
"replace_new": replace_new,
"delete_enabled": delete_enabled,
"delete_text": delete_text,
}
def get_date_format_string(self, index):
"""获取日期格式字符串"""
formats = [
"%Y-%m-%d", "%Y/%m/%d", "%Y%m%d",
"%y-%m-%d", "%y/%m/%d", "%Y%m",
"%Y年%m月%d日"
]
return formats
def select_directory(self):
"""选择目录对话框"""
dir_path = QFileDialog.getExistingDirectory(
self, "选择目录", self.current_dir, QFileDialog.ShowDirsOnly
)
if dir_path:
self.current_dir = dir_path
self.dir_label.setFullPath(self.current_dir)
self.file_tree.setRootIndex(self.file_system_model.index(dir_path))
self.auto_update_preview()
def on_dir_selected(self, index):
"""文件树中选择目录时的处理"""
path = self.file_system_model.filePath(index)
if os.path.isdir(path):
self.current_dir = path
self.dir_label.setFullPath(self.current_dir)
self.auto_update_preview()
def start_rename(self):
"""开始重命名操作"""
if not self.file_list:
QMessageBox.warning(self, "警告", "没有文件可重命名")
return
confirm = QMessageBox.question(
self, "确认", f"确定要重命名 {len(self.file_list)} 个文件吗?",
QMessageBox.Yes | QMessageBox.No
)
if confirm != QMessageBox.Yes:
return
self.progress_dialog = QProgressDialog("正在重命名...", "取消", 0, 100, self)
self.progress_dialog.setWindowTitle("请稍候")
self.progress_dialog.setWindowModality(Qt.WindowModal)
self.worker = RenameWorker(self.file_list.copy(), self.get_operations())
self.worker.progress_updated.connect(self.progress_dialog.setValue)
self.worker.rename_finished.connect(self.on_rename_finished)
self.worker.error_occurred.connect(self.show_error)
self.worker.canceled.connect(self.progress_dialog.close)
self.progress_dialog.canceled.connect(self.worker.requestInterruption)
self.worker.start()
def on_rename_finished(self, success):
"""重命名完成后的处理"""
self.progress_dialog.close()
if success:
self.backup_info = self.worker.backup_list
self.save_backup_to_file()
QMessageBox.information(self, "成功", "文件重命名完成")
self.auto_update_preview()
def show_error(self, message):
"""显示错误消息"""
self.progress_dialog.close()
QMessageBox.critical(self, "错误", message)
def undo_rename(self):
"""撤销上次重命名操作"""
if not self.backup_info:
QMessageBox.information(self, "提示", "没有可撤销的操作")
return
confirm = QMessageBox.question(
self, "确认", "确定要撤销上次重命名操作吗?",
QMessageBox.Yes | QMessageBox.No
)
if confirm != QMessageBox.Yes:
return
try:
for old_path, new_path in reversed(self.backup_info):
if os.path.exists(new_path):
os.rename(new_path, old_path)
self.backup_info = []
self.save_backup_to_file()
QMessageBox.information(self, "成功", "重命名已撤销")
self.auto_update_preview()
except Exception as e:
QMessageBox.critical(self, "错误", f"撤销失败: {str(e)}")
def save_backup_to_file(self):
"""保存备份信息到文件"""
try:
with open(self.backup_file, 'w', encoding='utf-8') as f:
json.dump(self.backup_info, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"保存备份失败: {str(e)}")
def load_backup_from_file(self):
"""从文件加载备份信息"""
try:
if os.path.exists(self.backup_file):
with open(self.backup_file, 'r', encoding='utf-8') as f:
self.backup_info = json.load(f)
except Exception as e:
print(f"加载备份失败: {str(e)}")
def delete_selected(self):
"""删除选中的文件条目"""
selected_rows = self.preview_table.selectionModel().selectedRows()
if not selected_rows:
QMessageBox.information(self, "提示", "请先选择要删除的文件")
return
rows_to_delete = sorted(, reverse=True)
for row in rows_to_delete:
if 0 <= row < len(self.file_list):
del self.file_list
self.preview_model.removeRow(row)
self.status_bar.showMessage(f"已删除 {len(rows_to_delete)} 个文件条目")
def dragEnterEvent(self, event):
"""拖放事件处理 - 进入"""
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event):
"""拖放事件处理 - 放下"""
urls = event.mimeData().urls()
file_paths = []
dir_paths = []
for url in urls:
path = url.toLocalFile()
if os.path.isdir(path):
dir_paths.append(path)
elif os.path.isfile(path):
file_paths.append(path)
if dir_paths:
self.current_dir = dir_paths
self.dir_label.setFullPath(self.current_dir)
self.file_tree.setRootIndex(self.file_system_model.index(self.current_dir))
self.auto_update_preview()
elif file_paths:
self.current_dir = os.path.dirname(file_paths) if file_paths else QDir.homePath()
self.file_list = []
ext_list = self.get_filter_extensions()
seq = self.seq_start.value()
for path in file_paths:
filename = os.path.basename(path)
if self.is_valid_file(filename, ext_list):
base, ext = os.path.splitext(filename)
self.file_list.append({
'dir': os.path.dirname(path),
'name': base,
'ext': ext,
'path': path,
'seq': seq
})
seq += 1
self.auto_update_preview()
event.acceptProposedAction()
def excepthook(exctype, value, traceback):
"""全局异常处理"""
error_msg = f"程序崩溃:\n类型: {exctype.__name__}\n信息: {str(value)}"
QMessageBox.critical(None, "致命错误", error_msg)
sys.__excepthook__(exctype, value, traceback)
sys.excepthook = excepthook
if __name__ == '__main__':
"""程序入口点"""
app = QApplication(sys.argv)
window = RenamerGUI()
window.show()
sys.exit(app.exec_()) 大佬好,软件重命名功能齐全,总的很好,能不能加上一个功能:能将预览区文件拖动移动位置,或者设置上移、下移、置顶、置尾右键,以便放置到自已需的地方再重命名。 以前一直使用ACDSee Pro 6这样来批量命名每组图片,有了这个必须试下。 谢谢,这个真实用 刚好需要 谢谢 这个功能实用 一直用拖把重命名工具,这个也很好 感谢分享,下载看看。
页:
[1]
2