添加Windows工具,以及分类。
This commit is contained in:
281
Windows/图形切割/ImageSlicerTool.py
Normal file
281
Windows/图形切割/ImageSlicerTool.py
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
图像分割工具
|
||||||
|
将图片切割成指定大小的小块
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from tkinter import Tk, Label, Button, Entry, filedialog, messagebox, Frame, Scrollbar, Listbox, VERTICAL, HORIZONTAL, BOTH, END, StringVar, Radiobutton
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
class ImageSlicerApp:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("图像分割工具")
|
||||||
|
self.root.geometry("600x500")
|
||||||
|
self.root.resizable(True, True)
|
||||||
|
|
||||||
|
# 初始化变量
|
||||||
|
self.source_image_path = ""
|
||||||
|
self.slice_size = 512
|
||||||
|
self.output_dir = os.path.join(os.path.expanduser("~"), "Desktop", "SlicedImages")
|
||||||
|
self.sliced_files = []
|
||||||
|
|
||||||
|
# 创建界面
|
||||||
|
self.create_widgets()
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
# 主框架
|
||||||
|
main_frame = Frame(self.root, padx=20, pady=20)
|
||||||
|
main_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# 源图像选择
|
||||||
|
source_frame = Frame(main_frame, pady=10)
|
||||||
|
source_frame.pack(fill="x")
|
||||||
|
|
||||||
|
Label(source_frame, text="源图像:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||||
|
|
||||||
|
source_path_frame = Frame(source_frame)
|
||||||
|
source_path_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
self.source_entry = Entry(source_path_frame, width=50)
|
||||||
|
self.source_entry.pack(side="left", fill="x", expand=True, padx=5)
|
||||||
|
|
||||||
|
Button(source_path_frame, text="浏览", command=self.browse_source).pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 分割设置
|
||||||
|
settings_frame = Frame(main_frame, pady=10)
|
||||||
|
settings_frame.pack(fill="x")
|
||||||
|
|
||||||
|
Label(settings_frame, text="分割设置:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||||
|
|
||||||
|
# 分割大小
|
||||||
|
size_frame = Frame(settings_frame)
|
||||||
|
size_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
Label(size_frame, text="分割大小:").pack(side="left", padx=5)
|
||||||
|
self.size_entry = Entry(size_frame, width=10)
|
||||||
|
self.size_entry.insert(0, str(self.slice_size))
|
||||||
|
self.size_entry.pack(side="left", padx=5)
|
||||||
|
Label(size_frame, text="x").pack(side="left", padx=5)
|
||||||
|
self.size_entry.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 命名设置
|
||||||
|
naming_frame = Frame(settings_frame)
|
||||||
|
naming_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
Label(naming_frame, text="命名设置:", font=("SimHei", 9, "bold")).pack(anchor="w", padx=5)
|
||||||
|
|
||||||
|
# 前缀
|
||||||
|
prefix_frame = Frame(naming_frame)
|
||||||
|
prefix_frame.pack(fill="x", pady=2, padx=10)
|
||||||
|
Label(prefix_frame, text="前缀:", width=8).pack(side="left", padx=5)
|
||||||
|
self.prefix_entry = Entry(prefix_frame, width=20)
|
||||||
|
self.prefix_entry.insert(0, "slice")
|
||||||
|
self.prefix_entry.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 后缀
|
||||||
|
suffix_frame = Frame(naming_frame)
|
||||||
|
suffix_frame.pack(fill="x", pady=2, padx=10)
|
||||||
|
Label(suffix_frame, text="后缀:", width=8).pack(side="left", padx=5)
|
||||||
|
self.suffix_entry = Entry(suffix_frame, width=20)
|
||||||
|
self.suffix_entry.insert(0, "")
|
||||||
|
self.suffix_entry.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 排序方式
|
||||||
|
sort_frame = Frame(naming_frame)
|
||||||
|
sort_frame.pack(fill="x", pady=2, padx=10)
|
||||||
|
Label(sort_frame, text="排序方式:", width=8).pack(side="left", padx=5)
|
||||||
|
Frame(sort_frame).pack(side="left", padx=5)
|
||||||
|
Label(sort_frame, text="顺序编号 (从左到右,从上到下)").pack(side="left", padx=5)
|
||||||
|
self.sort_var = StringVar(value="sequential")
|
||||||
|
|
||||||
|
# 文件格式
|
||||||
|
format_frame = Frame(naming_frame)
|
||||||
|
format_frame.pack(fill="x", pady=2, padx=10)
|
||||||
|
Label(format_frame, text="文件格式:", width=8).pack(side="left", padx=5)
|
||||||
|
self.format_var = StringVar(value="png")
|
||||||
|
Frame(format_frame).pack(side="left", padx=5)
|
||||||
|
Radiobutton(format_frame, text="PNG", variable=self.format_var, value="png").pack(side="left", padx=5)
|
||||||
|
Radiobutton(format_frame, text="JPG", variable=self.format_var, value="jpg").pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 输出目录
|
||||||
|
output_frame = Frame(settings_frame)
|
||||||
|
output_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
Label(output_frame, text="输出目录:").pack(side="left", padx=5)
|
||||||
|
self.output_entry = Entry(output_frame, width=40)
|
||||||
|
self.output_entry.insert(0, self.output_dir)
|
||||||
|
self.output_entry.pack(side="left", fill="x", expand=True, padx=5)
|
||||||
|
Button(output_frame, text="浏览", command=self.browse_output).pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 执行按钮
|
||||||
|
Button(main_frame, text="执行分割", command=self.slice_image, font=("SimHei", 12, "bold"), height=2, width=20).pack(pady=20)
|
||||||
|
|
||||||
|
# 结果显示
|
||||||
|
result_frame = Frame(main_frame, pady=10)
|
||||||
|
result_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
Label(result_frame, text="分割结果:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||||
|
|
||||||
|
# 结果列表
|
||||||
|
list_frame = Frame(result_frame)
|
||||||
|
list_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
self.result_list = Listbox(list_frame, width=70, height=10)
|
||||||
|
self.result_list.pack(side="left", fill="both", expand=True)
|
||||||
|
|
||||||
|
# 垂直滚动条
|
||||||
|
vscrollbar = Scrollbar(list_frame, orient=VERTICAL, command=self.result_list.yview)
|
||||||
|
vscrollbar.pack(side="right", fill="y")
|
||||||
|
self.result_list.config(yscrollcommand=vscrollbar.set)
|
||||||
|
|
||||||
|
# 水平滚动条
|
||||||
|
hscrollbar = Scrollbar(list_frame, orient=HORIZONTAL, command=self.result_list.xview)
|
||||||
|
hscrollbar.pack(side="bottom", fill="x")
|
||||||
|
self.result_list.config(xscrollcommand=hscrollbar.set)
|
||||||
|
|
||||||
|
def browse_source(self):
|
||||||
|
"""浏览选择源图像"""
|
||||||
|
file_path = filedialog.askopenfilename(
|
||||||
|
title="选择源图像",
|
||||||
|
filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp *.tiff *.gif"), ("All files", "*.*")]
|
||||||
|
)
|
||||||
|
if file_path:
|
||||||
|
self.source_entry.delete(0, END)
|
||||||
|
self.source_entry.insert(0, file_path)
|
||||||
|
self.source_image_path = file_path
|
||||||
|
|
||||||
|
def browse_output(self):
|
||||||
|
"""浏览选择输出目录"""
|
||||||
|
dir_path = filedialog.askdirectory(title="选择输出目录")
|
||||||
|
if dir_path:
|
||||||
|
self.output_entry.delete(0, END)
|
||||||
|
self.output_entry.insert(0, dir_path)
|
||||||
|
self.output_dir = dir_path
|
||||||
|
|
||||||
|
def process_slice(self, img, x, y, width, height, prefix, suffix, index, file_format):
|
||||||
|
"""处理单个图像切片"""
|
||||||
|
# 计算切割区域
|
||||||
|
start_x = x * self.slice_size
|
||||||
|
start_y = y * self.slice_size
|
||||||
|
end_x = min(start_x + self.slice_size, width)
|
||||||
|
end_y = min(start_y + self.slice_size, height)
|
||||||
|
|
||||||
|
# 切割图像
|
||||||
|
slice_img = img.crop((start_x, start_y, end_x, end_y))
|
||||||
|
|
||||||
|
# 生成文件名
|
||||||
|
parts = []
|
||||||
|
if prefix:
|
||||||
|
parts.append(prefix)
|
||||||
|
|
||||||
|
# 顺序编号:使用连续的数字
|
||||||
|
parts.append(f"{index-1}") # 从0开始
|
||||||
|
|
||||||
|
if suffix:
|
||||||
|
parts.append(suffix)
|
||||||
|
filename = "_".join(parts) + f".{file_format}"
|
||||||
|
|
||||||
|
# 保存切割后的图像
|
||||||
|
output_path = os.path.join(self.output_dir, filename)
|
||||||
|
if file_format == "jpg":
|
||||||
|
# 对于JPG格式,需要确保图像模式为RGB
|
||||||
|
if slice_img.mode == "RGBA":
|
||||||
|
# 创建白色背景
|
||||||
|
background = Image.new("RGB", slice_img.size, (255, 255, 255))
|
||||||
|
# 粘贴图像,使用alpha通道作为蒙版
|
||||||
|
background.paste(slice_img, mask=slice_img.split()[3])
|
||||||
|
slice_img = background
|
||||||
|
elif slice_img.mode != "RGB":
|
||||||
|
slice_img = slice_img.convert("RGB")
|
||||||
|
slice_img.save(output_path, "JPEG", quality=95)
|
||||||
|
else:
|
||||||
|
# 对于PNG格式,直接保存
|
||||||
|
slice_img.save(output_path, "PNG")
|
||||||
|
|
||||||
|
# 添加到结果列表
|
||||||
|
self.result_list.insert(END, output_path)
|
||||||
|
self.sliced_files.append(output_path)
|
||||||
|
|
||||||
|
def slice_image(self):
|
||||||
|
"""执行图像分割"""
|
||||||
|
# 获取输入
|
||||||
|
self.source_image_path = self.source_entry.get().strip()
|
||||||
|
self.output_dir = self.output_entry.get().strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.slice_size = int(self.size_entry.get().strip())
|
||||||
|
if self.slice_size <= 0:
|
||||||
|
raise ValueError("分割大小必须大于0")
|
||||||
|
except ValueError as e:
|
||||||
|
messagebox.showerror("错误", f"分割大小设置错误: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 验证源图像
|
||||||
|
if not self.source_image_path:
|
||||||
|
messagebox.showerror("错误", "请选择源图像")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(self.source_image_path):
|
||||||
|
messagebox.showerror("错误", "源图像文件不存在")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建输出目录
|
||||||
|
if not os.path.exists(self.output_dir):
|
||||||
|
try:
|
||||||
|
os.makedirs(self.output_dir)
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("错误", f"无法创建输出目录: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取命名设置
|
||||||
|
prefix = self.prefix_entry.get().strip()
|
||||||
|
suffix = self.suffix_entry.get().strip()
|
||||||
|
sort_order = self.sort_var.get()
|
||||||
|
file_format = self.format_var.get().lower()
|
||||||
|
|
||||||
|
# 禁用PIL的解压炸弹安全限制
|
||||||
|
Image.MAX_IMAGE_PIXELS = None
|
||||||
|
|
||||||
|
# 打开图像
|
||||||
|
with Image.open(self.source_image_path) as img:
|
||||||
|
width, height = img.size
|
||||||
|
|
||||||
|
# 计算分割数量
|
||||||
|
x_count = (width + self.slice_size - 1) // self.slice_size
|
||||||
|
y_count = (height + self.slice_size - 1) // self.slice_size
|
||||||
|
total_slices = x_count * y_count
|
||||||
|
|
||||||
|
# 清空结果列表
|
||||||
|
self.result_list.delete(0, END)
|
||||||
|
self.sliced_files = []
|
||||||
|
|
||||||
|
# 执行分割
|
||||||
|
current = 0
|
||||||
|
|
||||||
|
# 行优先排序 (从上到下,从左到右) - 正确的拼接顺序
|
||||||
|
for y in range(y_count):
|
||||||
|
for x in range(x_count):
|
||||||
|
current += 1
|
||||||
|
self.process_slice(img, x, y, width, height, prefix, suffix, current, file_format)
|
||||||
|
|
||||||
|
# 显示完成消息
|
||||||
|
messagebox.showinfo("完成", f"图像分割完成!\n共生成 {total_slices} 个图像块\n保存路径: {self.output_dir}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("错误", f"分割图像时出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
root = Tk()
|
||||||
|
app = ImageSlicerApp(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
Windows/图形切割/ImageSlicerToolSequential.exe
Normal file
BIN
Windows/图形切割/ImageSlicerToolSequential.exe
Normal file
Binary file not shown.
59
Windows/图形切割/使用说明.md
Normal file
59
Windows/图形切割/使用说明.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# 图像分割工具 - 使用指南
|
||||||
|
|
||||||
|
## 工具简介
|
||||||
|
|
||||||
|
图像分割工具是一款用于将大图切割成多个小块图片的桌面应用程序,适用于游戏美术资源切片、图像处理等场景。
|
||||||
|
|
||||||
|
## 运行方式
|
||||||
|
|
||||||
|
双击 `ImageSlicerToolSequential.exe` 直接运行,或使用 Python 3 运行 `ImageSlicerTool.py`。
|
||||||
|
|
||||||
|
> 注意:运行 Python 版本需要安装 Pillow 库:`pip install Pillow`
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
| 功能 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 图像切割 | 将大图按指定尺寸分割成多个小块 |
|
||||||
|
| 自定义尺寸 | 可设置每个切片的大小 |
|
||||||
|
| 命名设置 | 支持自定义文件名前缀和后缀 |
|
||||||
|
| 输出格式 | 支持 PNG 和 JPG 两种格式 |
|
||||||
|
| 顺序编号 | 按从左到右、从上到下顺序编号 |
|
||||||
|
|
||||||
|
## 使用步骤
|
||||||
|
|
||||||
|
### 1. 选择源图像
|
||||||
|
点击「浏览」按钮,选择需要分割的大图。支持 JPG、PNG、BMP、TIFF、GIF 等常见格式。
|
||||||
|
|
||||||
|
### 2. 设置分割参数
|
||||||
|
- **分割大小**:每个切片块的像素尺寸(默认 512×512)
|
||||||
|
- **前缀**:输出文件名的前缀部分(默认为 `slice`)
|
||||||
|
- **后缀**:输出文件名的后缀部分(可留空)
|
||||||
|
- **文件格式**:选择输出为 PNG 或 JPG 格式
|
||||||
|
|
||||||
|
### 3. 选择输出目录
|
||||||
|
默认输出到桌面 `SlicedImages` 文件夹,点击「浏览」可自定义输出位置。
|
||||||
|
|
||||||
|
### 4. 执行分割
|
||||||
|
点击「执行分割」按钮,工具将自动完成切割并在结果列表中显示所有生成的切片文件。
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
假设有一张 1024×1024 的大图 `background.png`,设置如下:
|
||||||
|
- 分割大小:`512`
|
||||||
|
- 前缀:`tile`
|
||||||
|
- 后缀:留空
|
||||||
|
- 文件格式:PNG
|
||||||
|
|
||||||
|
执行后将在输出目录生成 4 个文件:
|
||||||
|
- `tile_1.png` (左上角 512×512 区域)
|
||||||
|
- `tile_2.png` (右上角 512×512 区域)
|
||||||
|
- `tile_3.png` (左下角 512×512 区域)
|
||||||
|
- `tile_4.png` (右下角 512×512 区域)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 切割顺序为从左到右、从上到下,适合游戏地图_tile_的拼接
|
||||||
|
2. 如果图像尺寸不能被分割大小整除,边缘不完整的块也会被保留
|
||||||
|
3. JPG 格式会将有透明通道的 PNG 图片自动转换为白色背景
|
||||||
|
4. 建议输出到专用文件夹,避免文件混乱
|
||||||
BIN
Windows/批量重命名/BatchRenameTool.exe
Normal file
BIN
Windows/批量重命名/BatchRenameTool.exe
Normal file
Binary file not shown.
324
Windows/批量重命名/BatchRenameTool.py
Normal file
324
Windows/批量重命名/BatchRenameTool.py
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
批量文件重命名工具
|
||||||
|
支持添加文件,按顺序重命名
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import Label, Button, Entry, messagebox, Frame, Scrollbar, Listbox, VERTICAL, HORIZONTAL, END, filedialog
|
||||||
|
|
||||||
|
class BatchRenameApp:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title("批量文件重命名工具")
|
||||||
|
self.root.geometry("750x800")
|
||||||
|
self.root.resizable(True, True)
|
||||||
|
|
||||||
|
# 初始化变量
|
||||||
|
self.file_list = []
|
||||||
|
self.new_prefix = "renamed"
|
||||||
|
self.start_number = 1
|
||||||
|
self.number_digits = 3
|
||||||
|
|
||||||
|
# 创建界面
|
||||||
|
self.create_widgets()
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
# 主框架
|
||||||
|
main_frame = Frame(self.root, padx=20, pady=20)
|
||||||
|
main_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# 提示区域
|
||||||
|
drop_frame = Frame(main_frame, pady=10, bg="#E3F2FD", relief="solid", bd=2)
|
||||||
|
drop_frame.pack(fill="x")
|
||||||
|
|
||||||
|
self.drop_label = Label(drop_frame, text='📁 点击下方"添加文件"按钮选择文件',
|
||||||
|
font=("SimHei", 14, "bold"), bg="#E3F2FD", fg="#1976D2",
|
||||||
|
height=2)
|
||||||
|
self.drop_label.pack(fill="x", expand=True)
|
||||||
|
|
||||||
|
# 按钮区域
|
||||||
|
btn_frame = Frame(main_frame, pady=10)
|
||||||
|
btn_frame.pack(fill="x")
|
||||||
|
|
||||||
|
Button(btn_frame, text="添加文件", command=self.add_files, font=("SimHei", 10), width=12).pack(side="left", padx=5)
|
||||||
|
Button(btn_frame, text="清空列表", command=self.clear_list, font=("SimHei", 10), width=12).pack(side="left", padx=5)
|
||||||
|
Button(btn_frame, text="上移", command=self.move_up, font=("SimHei", 10), width=8).pack(side="left", padx=5)
|
||||||
|
Button(btn_frame, text="下移", command=self.move_down, font=("SimHei", 10), width=8).pack(side="left", padx=5)
|
||||||
|
Button(btn_frame, text="移除选中", command=self.remove_selected, font=("SimHei", 10), width=10).pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 文件列表
|
||||||
|
list_frame = Frame(main_frame, pady=10)
|
||||||
|
list_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
Label(list_frame, text="文件列表 (按顺序重命名):", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||||
|
|
||||||
|
# 列表框容器
|
||||||
|
list_container = Frame(list_frame)
|
||||||
|
list_container.pack(fill="both", expand=True, pady=5)
|
||||||
|
|
||||||
|
# 文件列表
|
||||||
|
self.file_listbox = Listbox(list_container, width=80, height=12, selectmode="single")
|
||||||
|
self.file_listbox.pack(side="left", fill="both", expand=True)
|
||||||
|
|
||||||
|
# 垂直滚动条
|
||||||
|
vscrollbar = Scrollbar(list_container, orient=VERTICAL, command=self.file_listbox.yview)
|
||||||
|
vscrollbar.pack(side="right", fill="y")
|
||||||
|
self.file_listbox.config(yscrollcommand=vscrollbar.set)
|
||||||
|
|
||||||
|
# 水平滚动条
|
||||||
|
hscrollbar = Scrollbar(list_container, orient=HORIZONTAL, command=self.file_listbox.xview)
|
||||||
|
hscrollbar.pack(side="bottom", fill="x")
|
||||||
|
self.file_listbox.config(xscrollcommand=hscrollbar.set)
|
||||||
|
|
||||||
|
# 重命名设置
|
||||||
|
settings_frame = Frame(main_frame, pady=10)
|
||||||
|
settings_frame.pack(fill="x")
|
||||||
|
|
||||||
|
Label(settings_frame, text="重命名设置:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||||
|
|
||||||
|
# 前缀
|
||||||
|
prefix_frame = Frame(settings_frame)
|
||||||
|
prefix_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
Label(prefix_frame, text="新名称前缀:", width=15).pack(side="left", padx=5)
|
||||||
|
self.prefix_entry = Entry(prefix_frame, width=40)
|
||||||
|
self.prefix_entry.insert(0, self.new_prefix)
|
||||||
|
self.prefix_entry.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 起始编号
|
||||||
|
start_frame = Frame(settings_frame)
|
||||||
|
start_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
Label(start_frame, text="起始编号:", width=15).pack(side="left", padx=5)
|
||||||
|
self.start_entry = Entry(start_frame, width=10)
|
||||||
|
self.start_entry.insert(0, str(self.start_number))
|
||||||
|
self.start_entry.pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 编号位数
|
||||||
|
digits_frame = Frame(settings_frame)
|
||||||
|
digits_frame.pack(fill="x", pady=5)
|
||||||
|
|
||||||
|
Label(digits_frame, text="编号位数:", width=15).pack(side="left", padx=5)
|
||||||
|
self.digits_entry = Entry(digits_frame, width=10)
|
||||||
|
self.digits_entry.insert(0, str(self.number_digits))
|
||||||
|
self.digits_entry.pack(side="left", padx=5)
|
||||||
|
Label(digits_frame, text="(例如: 3位 = 001, 002...)").pack(side="left", padx=5)
|
||||||
|
|
||||||
|
# 执行按钮
|
||||||
|
button_frame = Frame(main_frame, pady=15)
|
||||||
|
button_frame.pack(fill="x")
|
||||||
|
|
||||||
|
Button(button_frame, text="预览重命名", command=self.preview_rename, font=("SimHei", 11), width=15).pack(side="left", padx=10)
|
||||||
|
Button(button_frame, text="执行重命名", command=self.execute_rename, font=("SimHei", 11, "bold"), width=15, bg="#4CAF50", fg="white").pack(side="left", padx=10)
|
||||||
|
|
||||||
|
# 结果显示
|
||||||
|
result_frame = Frame(main_frame, pady=10)
|
||||||
|
result_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
Label(result_frame, text="重命名结果:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||||
|
|
||||||
|
# 结果列表
|
||||||
|
result_container = Frame(result_frame)
|
||||||
|
result_container.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
self.result_listbox = Listbox(result_container, width=80, height=8)
|
||||||
|
self.result_listbox.pack(side="left", fill="both", expand=True)
|
||||||
|
|
||||||
|
# 垂直滚动条
|
||||||
|
rvscrollbar = Scrollbar(result_container, orient=VERTICAL, command=self.result_listbox.yview)
|
||||||
|
rvscrollbar.pack(side="right", fill="y")
|
||||||
|
self.result_listbox.config(yscrollcommand=rvscrollbar.set)
|
||||||
|
|
||||||
|
# 水平滚动条
|
||||||
|
rhscrollbar = Scrollbar(result_container, orient=HORIZONTAL, command=self.result_listbox.xview)
|
||||||
|
rhscrollbar.pack(side="bottom", fill="x")
|
||||||
|
self.result_listbox.config(xscrollcommand=rhscrollbar.set)
|
||||||
|
|
||||||
|
def add_files(self):
|
||||||
|
"""通过对话框添加文件"""
|
||||||
|
files = filedialog.askopenfilenames(title="选择文件")
|
||||||
|
if files:
|
||||||
|
for file_path in files:
|
||||||
|
if file_path not in self.file_list:
|
||||||
|
self.file_list.append(file_path)
|
||||||
|
file_name = os.path.basename(file_path)
|
||||||
|
self.file_listbox.insert(END, f"{len(self.file_list)}. {file_name}")
|
||||||
|
|
||||||
|
self.drop_label.config(text=f"📁 已添加 {len(self.file_list)} 个文件")
|
||||||
|
|
||||||
|
def clear_list(self):
|
||||||
|
"""清空文件列表"""
|
||||||
|
self.file_list.clear()
|
||||||
|
self.file_listbox.delete(0, END)
|
||||||
|
self.drop_label.config(text='📁 点击下方"添加文件"按钮选择文件')
|
||||||
|
|
||||||
|
def move_up(self):
|
||||||
|
"""上移选中的文件"""
|
||||||
|
selection = self.file_listbox.curselection()
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
index = selection[0]
|
||||||
|
if index == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 交换列表中的位置
|
||||||
|
self.file_list[index], self.file_list[index-1] = self.file_list[index-1], self.file_list[index]
|
||||||
|
|
||||||
|
# 更新显示
|
||||||
|
self.refresh_listbox()
|
||||||
|
self.file_listbox.selection_set(index-1)
|
||||||
|
|
||||||
|
def move_down(self):
|
||||||
|
"""下移选中的文件"""
|
||||||
|
selection = self.file_listbox.curselection()
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
index = selection[0]
|
||||||
|
if index == len(self.file_list) - 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 交换列表中的位置
|
||||||
|
self.file_list[index], self.file_list[index+1] = self.file_list[index+1], self.file_list[index]
|
||||||
|
|
||||||
|
# 更新显示
|
||||||
|
self.refresh_listbox()
|
||||||
|
self.file_listbox.selection_set(index+1)
|
||||||
|
|
||||||
|
def remove_selected(self):
|
||||||
|
"""移除选中的文件"""
|
||||||
|
selection = self.file_listbox.curselection()
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
index = selection[0]
|
||||||
|
self.file_list.pop(index)
|
||||||
|
self.refresh_listbox()
|
||||||
|
|
||||||
|
if self.file_list:
|
||||||
|
self.drop_label.config(text=f"📁 已添加 {len(self.file_list)} 个文件")
|
||||||
|
else:
|
||||||
|
self.drop_label.config(text='📁 点击下方"添加文件"按钮选择文件')
|
||||||
|
|
||||||
|
def refresh_listbox(self):
|
||||||
|
"""刷新列表框显示"""
|
||||||
|
self.file_listbox.delete(0, END)
|
||||||
|
for i, file_path in enumerate(self.file_list, 1):
|
||||||
|
file_name = os.path.basename(file_path)
|
||||||
|
self.file_listbox.insert(END, f"{i}. {file_name}")
|
||||||
|
|
||||||
|
def format_number(self, num, digits):
|
||||||
|
"""格式化数字,添加前导零"""
|
||||||
|
return str(num).zfill(digits)
|
||||||
|
|
||||||
|
def preview_rename(self):
|
||||||
|
"""预览重命名操作"""
|
||||||
|
if not self.file_list:
|
||||||
|
messagebox.showinfo("提示", "文件列表为空")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.new_prefix = self.prefix_entry.get().strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.start_number = int(self.start_entry.get().strip())
|
||||||
|
self.number_digits = int(self.digits_entry.get().strip())
|
||||||
|
|
||||||
|
if self.start_number < 0:
|
||||||
|
raise ValueError("起始编号不能为负数")
|
||||||
|
if self.number_digits < 1:
|
||||||
|
raise ValueError("编号位数必须大于0")
|
||||||
|
except ValueError as e:
|
||||||
|
messagebox.showerror("错误", f"参数设置错误: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 清空结果列表
|
||||||
|
self.result_listbox.delete(0, END)
|
||||||
|
|
||||||
|
current_num = self.start_number
|
||||||
|
|
||||||
|
for file_path in self.file_list:
|
||||||
|
old_name = os.path.basename(file_path)
|
||||||
|
ext = os.path.splitext(old_name)[1]
|
||||||
|
formatted_num = self.format_number(current_num, self.number_digits)
|
||||||
|
new_name = f"{self.new_prefix}{formatted_num}{ext}"
|
||||||
|
|
||||||
|
self.result_listbox.insert(END, f"{old_name} -> {new_name}")
|
||||||
|
current_num += 1
|
||||||
|
|
||||||
|
messagebox.showinfo("预览", f"预览完成!\n共 {len(self.file_list)} 个文件将被重命名")
|
||||||
|
|
||||||
|
def execute_rename(self):
|
||||||
|
"""执行重命名操作"""
|
||||||
|
if not self.file_list:
|
||||||
|
messagebox.showinfo("提示", "文件列表为空")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.new_prefix = self.prefix_entry.get().strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.start_number = int(self.start_entry.get().strip())
|
||||||
|
self.number_digits = int(self.digits_entry.get().strip())
|
||||||
|
|
||||||
|
if self.start_number < 0:
|
||||||
|
raise ValueError("起始编号不能为负数")
|
||||||
|
if self.number_digits < 1:
|
||||||
|
raise ValueError("编号位数必须大于0")
|
||||||
|
except ValueError as e:
|
||||||
|
messagebox.showerror("错误", f"参数设置错误: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 确认操作
|
||||||
|
confirm = messagebox.askyesno("确认", f"确定要重命名 {len(self.file_list)} 个文件吗?")
|
||||||
|
if not confirm:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 清空结果列表
|
||||||
|
self.result_listbox.delete(0, END)
|
||||||
|
|
||||||
|
success_count = 0
|
||||||
|
skip_count = 0
|
||||||
|
current_num = self.start_number
|
||||||
|
|
||||||
|
for file_path in self.file_list:
|
||||||
|
old_name = os.path.basename(file_path)
|
||||||
|
ext = os.path.splitext(old_name)[1]
|
||||||
|
formatted_num = self.format_number(current_num, self.number_digits)
|
||||||
|
new_name = f"{self.new_prefix}{formatted_num}{ext}"
|
||||||
|
|
||||||
|
# 获取文件所在目录
|
||||||
|
dir_path = os.path.dirname(file_path)
|
||||||
|
new_path = os.path.join(dir_path, new_name)
|
||||||
|
|
||||||
|
if os.path.exists(new_path):
|
||||||
|
if old_name.lower() != new_name.lower():
|
||||||
|
self.result_listbox.insert(END, f"[跳过] 目标已存在: {new_name}")
|
||||||
|
skip_count += 1
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
os.rename(file_path, new_path)
|
||||||
|
self.result_listbox.insert(END, f"[成功] {old_name} -> {new_name}")
|
||||||
|
success_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
self.result_listbox.insert(END, f"[失败] {old_name} - {e}")
|
||||||
|
skip_count += 1
|
||||||
|
|
||||||
|
current_num += 1
|
||||||
|
|
||||||
|
messagebox.showinfo("完成", f"重命名完成!\n成功: {success_count} 个\n失败/跳过: {skip_count} 个")
|
||||||
|
|
||||||
|
# 清空列表
|
||||||
|
self.clear_list()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
root = tk.Tk()
|
||||||
|
app = BatchRenameApp(root)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
65
Windows/批量重命名/使用说明.md
Normal file
65
Windows/批量重命名/使用说明.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# 批量文件重命名工具 - 使用指南
|
||||||
|
|
||||||
|
## 工具简介
|
||||||
|
|
||||||
|
批量文件重命名工具是一款简洁高效的 Windows 桌面应用程序,支持快速对多个文件按顺序进行重命名操作。
|
||||||
|
|
||||||
|
## 运行方式
|
||||||
|
|
||||||
|
双击 `BatchRenameTool.exe` 直接运行,或使用 Python 3 运行 `BatchRenameTool.py`。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
| 功能 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 添加文件 | 支持选择多个文件添加到列表 |
|
||||||
|
| 文件排序 | 通过上移/下移调整文件重命名顺序 |
|
||||||
|
| 自定义前缀 | 可设置新文件名的前缀 |
|
||||||
|
| 起始编号 | 支持设置起始数字 |
|
||||||
|
| 编号位数 | 可自定义编号位数(如3位:001, 002...) |
|
||||||
|
| 预览功能 | 执行前可预览所有文件的命名结果 |
|
||||||
|
| 重命名执行 | 一键批量重命名文件 |
|
||||||
|
|
||||||
|
## 使用步骤
|
||||||
|
|
||||||
|
### 1. 添加文件
|
||||||
|
点击「添加文件」按钮,在弹出的文件选择对话框中选择需要重命名的文件。支持多选。
|
||||||
|
|
||||||
|
### 2. 调整顺序
|
||||||
|
- 选择列表中的文件
|
||||||
|
- 点击「上移」或「下移」调整其在重命名序列中的位置
|
||||||
|
- 点击「移除选中」可删除不想重命名的文件
|
||||||
|
|
||||||
|
### 3. 设置参数
|
||||||
|
- **新名称前缀**:输入新文件名的前缀部分(默认为 `renamed`)
|
||||||
|
- **起始编号**:设置编号的起始数字(默认为 1)
|
||||||
|
- **编号位数**:设置编号的总位数,如设为 3 则显示为 001、002(默认为 3)
|
||||||
|
|
||||||
|
### 4. 预览
|
||||||
|
点击「预览重命名」按钮,查看所有文件的重命名结果,确认无误后再执行。
|
||||||
|
|
||||||
|
### 5. 执行
|
||||||
|
点击绿色的「执行重命名」按钮,确认后开始重命名。
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
假设有以下文件:
|
||||||
|
- `photo.jpg`
|
||||||
|
- `image.png`
|
||||||
|
- `picture.bmp`
|
||||||
|
|
||||||
|
设置参数:
|
||||||
|
- 前缀:`photo_`
|
||||||
|
- 起始编号:`1`
|
||||||
|
- 编号位数:`2`
|
||||||
|
|
||||||
|
预览/执行结果:
|
||||||
|
- `photo.jpg` → `photo_01.jpg`
|
||||||
|
- `image.png` → `photo_02.png`
|
||||||
|
- `picture.bmp` → `photo_03.bmp`
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 执行重命名会直接修改文件系统,无法通过撤销恢复,请务必先使用预览功能确认
|
||||||
|
2. 如果目标文件名已存在,该文件会被跳过
|
||||||
|
3. 编号位数设置过小可能导致编号溢出(如 100+ 个文件但只设 2 位数)
|
||||||
Reference in New Issue
Block a user