这个工具适用于以下场景:
应用采用直观的三部分布局:
import os
import shutil
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
import re
from pathlib import Path
class ImageFinderApp:
def __init__(self, root):
self.root = root
self.root.title("图片查找与复制工具")
self.root.geometry("800x600")
self.root.minsize(600, 500)
# 设置中文字体支持
self.font = ('SimHei', 10)
# 创建主框架
self.main_frame = ttk.Frame(root, padding="10")
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 源目录选择
ttk.Label(self.main_frame, text="源目录:", font=self.font).grid(row=0, column=0, sticky=tk.W, pady=5)
self.source_var = tk.StringVar()
ttk.Entry(self.main_frame, textvariable=self.source_var, width=60, font=self.font).grid(row=0, column=1, pady=5, padx=5)
ttk.Button(self.main_frame, text="浏览", command=self.browse_source, width=10).grid(row=0, column=2, pady=5)
# 目标目录选择
ttk.Label(self.main_frame, text="目标目录:", font=self.font).grid(row=1, column=0, sticky=tk.W, pady=5)
self.target_var = tk.StringVar()
ttk.Entry(self.main_frame, textvariable=self.target_var, width=60, font=self.font).grid(row=1, column=1, pady=5, padx=5)
ttk.Button(self.main_frame, text="浏览", command=self.browse_target, width=10).grid(row=1, column=2, pady=5)
# 文件名字段
ttk.Label(self.main_frame, text="输入文件名(每行一个):", font=self.font).grid(row=2, column=0, sticky=tk.W, pady=5)
self.filenames_text = tk.Text(self.main_frame, height=10, width=70, font=self.font)
self.filenames_text.grid(row=3, column=0, columnspan=3, pady=5, sticky=tk.W+tk.E)
# 滚动条
scrollbar = ttk.Scrollbar(self.main_frame, command=self.filenames_text.yview)
scrollbar.grid(row=3, column=3, sticky=tk.N+tk.S)
self.filenames_text.config(yscrollcommand=scrollbar.set)
# 选项
options_frame = ttk.LabelFrame(self.main_frame, text="选项", padding="10")
options_frame.grid(row=4, column=0, columnspan=3, pady=10, sticky=tk.W+tk.E)
self.case_sensitive_var = tk.BooleanVar(value=False)
ttk.Checkbutton(options_frame, text="区分大小写", variable=self.case_sensitive_var, font=self.font).grid(row=0, column=0, padx=10)
self.use_regex_var = tk.BooleanVar(value=False)
ttk.Checkbutton(options_frame, text="使用正则表达式", variable=self.use_regex_var, font=self.font).grid(row=0, column=1, padx=10)
self.overwrite_var = tk.BooleanVar(value=False)
ttk.Checkbutton(options_frame, text="覆盖已存在文件", variable=self.overwrite_var, font=self.font).grid(row=0, column=2, padx=10)
self.search_subfolders_var = tk.BooleanVar(value=True)
ttk.Checkbutton(options_frame, text="搜索子文件夹", variable=self.search_subfolders_var, font=self.font).grid(row=0, column=3, padx=10)
# 图片格式筛选
ttk.Label(options_frame, text="图片格式:", font=self.font).grid(row=1, column=0, sticky=tk.W, pady=5)
self.image_formats = tk.StringVar(value="jpg,jpeg,png,gif,bmp")
ttk.Entry(options_frame, textvariable=self.image_formats, width=40, font=self.font).grid(row=1, column=1, pady=5, padx=5)
ttk.Label(options_frame, text="(逗号分隔)", font=self.font).grid(row=1, column=2, sticky=tk.W, pady=5)
# 状态和进度条
self.status_var = tk.StringVar(value="就绪")
ttk.Label(self.main_frame, textvariable=self.status_var, font=self.font).grid(row=5, column=0, sticky=tk.W, pady=5)
self.progress_var = tk.DoubleVar(value=0)
self.progress_bar = ttk.Progressbar(self.main_frame, variable=self.progress_var, length=100, mode='determinate')
self.progress_bar.grid(row=5, column=1, pady=5, sticky=tk.W+tk.E)
# 结果显示
ttk.Label(self.main_frame, text="结果:", font=self.font).grid(row=6, column=0, sticky=tk.W, pady=5)
self.result_text = tk.Text(self.main_frame, height=10, width=70, font=self.font)
self.result_text.grid(row=7, column=0, columnspan=3, pady=5, sticky=tk.W+tk.E+tk.N+tk.S)
self.result_text.config(state=tk.DISABLED)
# 结果滚动条
result_scrollbar = ttk.Scrollbar(self.main_frame, command=self.result_text.yview)
result_scrollbar.grid(row=7, column=3, sticky=tk.N+tk.S)
self.result_text.config(yscrollcommand=result_scrollbar.set)
# 按钮
button_frame = ttk.Frame(self.main_frame)
button_frame.grid(row=8, column=0, columnspan=3, pady=10, sticky=tk.E)
ttk.Button(button_frame, text="开始搜索", command=self.start_search, width=15).grid(row=0, column=0, padx=5)
ttk.Button(button_frame, text="清空列表", command=self.clear_list, width=15).grid(row=0, column=1, padx=5)
ttk.Button(button_frame, text="退出", command=root.quit, width=15).grid(row=0, column=2, padx=5)
# 配置网格权重,使界面可伸缩
self.main_frame.columnconfigure(1, weight=1)
self.main_frame.rowconfigure(7, weight=1)
# 存储结果
self.search_results = []
self.total_files = 0
self.processed_files = 0
def browse_source(self):
directory = filedialog.askdirectory(title="选择源目录")
if directory:
self.source_var.set(directory)
def browse_target(self):
directory = filedialog.askdirectory(title="选择目标目录")
if directory:
self.target_var.set(directory)
def clear_list(self):
self.filenames_text.delete(1.0, tk.END)
self.result_text.config(state=tk.NORMAL)
self.result_text.delete(1.0, tk.END)
self.result_text.config(state=tk.DISABLED)
self.status_var.set("就绪")
self.progress_var.set(0)
def start_search(self):
# 获取用户输入
source_dir = self.source_var.get()
target_dir = self.target_var.get()
filenames_text = self.filenames_text.get(1.0, tk.END).strip()
# 验证输入
if not source_dir:
messagebox.showerror("错误", "请选择源目录")
return
if not os.path.isdir(source_dir):
messagebox.showerror("错误", "源目录不存在")
return
if not target_dir:
messagebox.showerror("错误", "请选择目标目录")
return
if not filenames_text:
messagebox.showerror("错误", "请输入要搜索的文件名")
return
# 准备文件名列表
filenames = [line.strip() for line in filenames_text.split('\n') if line.strip()]
# 创建目标目录(如果不存在)
os.makedirs(target_dir, exist_ok=True)
# 清空结果显示
self.result_text.config(state=tk.NORMAL)
self.result_text.delete(1.0, tk.END)
self.result_text.config(state=tk.DISABLED)
# 启动搜索线程
self.search_results = []
self.total_files = len(filenames)
self.processed_files = 0
self.progress_var.set(0)
thread = threading.Thread(target=self.search_and_copy_files, args=(source_dir, target_dir, filenames))
thread.daemon = True
thread.start()
def search_and_copy_files(self, source_dir, target_dir, filenames):
# 更新状态
self.update_status("正在搜索文件...")
# 获取图片格式列表
image_extensions = [f".{ext.strip().lower()}" for ext in self.image_formats.get().split(',') if ext.strip()]
# 搜索选项
case_sensitive = self.case_sensitive_var.get()
use_regex = self.use_regex_var.get()
search_subfolders = self.search_subfolders_var.get()
overwrite = self.overwrite_var.get()
found_count = 0
not_found_count = 0
copied_count = 0
skipped_count = 0
# 遍历每个文件名进行搜索
for filename in filenames:
if not case_sensitive:
filename_lower = filename.lower()
else:
filename_lower = filename
# 存储当前文件的搜索结果
found_files = []
# 使用正则表达式搜索
if use_regex:
try:
pattern = re.compile(filename, re.IGNORECASE if not case_sensitive else 0)
# 遍历源目录中的所有文件
if search_subfolders:
for root, _, files in os.walk(source_dir):
for file in files:
if not image_extensions or Path(file).suffix.lower() in image_extensions:
if pattern.search(file):
found_files.append(os.path.join(root, file))
else:
for file in os.listdir(source_dir):
if os.path.isfile(os.path.join(source_dir, file)):
if not image_extensions or Path(file).suffix.lower() in image_extensions:
if pattern.search(file):
found_files.append(os.path.join(source_dir, file))
except re.error as e:
self.update_result(f"错误: 无效的正则表达式 '{filename}': {str(e)}")
not_found_count += 1
self.update_progress()
continue
# 普通文件名搜索
else:
# 遍历源目录中的所有文件
if search_subfolders:
for root, _, files in os.walk(source_dir):
for file in files:
if not image_extensions or Path(file).suffix.lower() in image_extensions:
compare_name = file if case_sensitive else file.lower()
if compare_name == filename_lower:
found_files.append(os.path.join(root, file))
else:
for file in os.listdir(source_dir):
if os.path.isfile(os.path.join(source_dir, file)):
if not image_extensions or Path(file).suffix.lower() in image_extensions:
compare_name = file if case_sensitive else file.lower()
if compare_name == filename_lower:
found_files.append(os.path.join(source_dir, file))
# 处理搜索结果
if found_files:
found_count += 1
for found_file in found_files:
# 构建目标路径
target_file = os.path.join(target_dir, os.path.basename(found_file))
# 检查文件是否已存在
if os.path.exists(target_file):
if overwrite:
try:
shutil.copy2(found_file, target_file)
self.update_result(f"已复制: {found_file} -> {target_file}")
copied_count += 1
except Exception as e:
self.update_result(f"错误: 无法复制 {found_file}: {str(e)}")
else:
self.update_result(f"已跳过: {found_file} (目标文件已存在)")
skipped_count += 1
else:
try:
shutil.copy2(found_file, target_file)
self.update_result(f"已复制: {found_file} -> {target_file}")
copied_count += 1
except Exception as e:
self.update_result(f"错误: 无法复制 {found_file}: {str(e)}")
else:
not_found_count += 1
self.update_result(f"未找到: {filename}")
# 更新进度
self.update_progress()
# 显示最终结果
self.update_status(f"搜索完成。找到: {found_count}, 未找到: {not_found_count}, 已复制: {copied_count}, 已跳过: {skipped_count}")
def update_result(self, message):
self.root.after(0, self._update_result_ui, message)
def _update_result_ui(self, message):
self.result_text.config(state=tk.NORMAL)
self.result_text.insert(tk.END, message + "\n")
self.result_text.see(tk.END)
self.result_text.config(state=tk.DISABLED)
def update_status(self, message):
self.root.after(0, self.status_var.set, message)
def update_progress(self):
self.processed_files += 1
progress = (self.processed_files / self.total_files) * 100
self.root.after(0, self.progress_var.set, progress)
if __name__ == "__main__":
root = tk.Tk()
app = ImageFinderApp(root)
root.mainloop()
这个工具通过简单易用的界面和强大的搜索功能,帮助用户快速从大量图片中找到并复制需要的文件,提高工作效率。