目录
1. 简介功能使用方法2. 运行效果3.相关源码4.总结1. 简介
这款文件整理工具主要用于在文件夹内对文件进行去重和分类存储,尤其适用于处理图片等文件类型。用户可以通过选择源目录、目标目录、重复文件目录以及非重复文件目录,快速完成文件分类和去重工作。工具支持读取 MD5 列表来识别重复文件,并根据文件的修改时间进行分类存储。此外,工具还支持多线程处理,避免界面卡顿,提升用户体验。
功能
1.目录选择和配置:
支持设置基准目录、目标目录、重复文件目录和非重复文件目录。支持通过浏览按钮快速选择文件夹路径。2.文件去重和分类:
使用 MD5 校验码对文件进行去重,判断文件是否重复。如果启用了 MD5 列表,工具会加载先前保存的 MD5 列表,以加速去重过程。根据文件的修改时间,自动将文件按年和月分类存储。3.多线程处理:
采用多线程技术,使得文件处理过程不阻塞主线程,确保界面保持响应。文件的移动操作在后台线程中进行,避免影响 UI 界面的流畅性。4.支持图片文件类型:
支持常见图片格式,如 JPEG、PNG、BMP 和 HEIC。对 HEIC 文件进行特殊处理,提取文件的 EXIF 时间戳,作为文件分类的依据。5.日志记录:
提供实时日志记录功能,记录文件处理的详细过程。支持日志批量更新,减少对 UI 的频繁更新,提高性能。6.MD5 列表管理:
可加载和保存 MD5 列表,记录基准文件的 MD5 值,帮助后续识别重复文件。
使用方法
配置目录:
启动工具后,首先配置四个文件夹路径:基准目录:包含待处理文件的源目录。目标目录:包含待比对的目标文件夹。重复文件目录:存放重复文件的目录。非重复文件目录:存放未重复文件的目录。选择选项:
启用“读取 MD5 列表”选项来加载已保存的 MD5 列表。启用“启用分类存储”选项将文件按年和月进行分类存储。开始处理:
点击“开始处理”按钮,工具会自动扫描源目录和目标目录,识别重复文件并进行分类存储。处理过程中,工具会在日志框中显示详细的操作信息。文件分类与去重:
工具根据文件的 MD5 校验码判断是否为重复文件,重复文件将移动到指定的“重复文件目录”。未重复的文件将按其修改时间(年月)分类,存放到指定的“非重复文件目录”中。完成处理:
文件处理完成后,工具会在日志框中显示相关信息,并自动保存更新的 MD5 列表到“非重复文件目录”中。
2. 运行效果
3.相关源码
import osimport hashlibimport shutilimport jsonimport threadingimport reimport ioimport exifreadimport tkinter as tkfrom tkinter import ttk, filedialog, messageboxfrom datetime import datetimefrom PIL import Imagefrom PIL.ExifTags import TAGSimport pillow_heiffrom pillow_heif import register_heif_openerclass FileOrganizerApp: def __init__(self, root): self.root = root self.root.title("文件整理工具") # 变量初始化 self.base_dir = tk.StringVar() self.target_dir = tk.StringVar() self.duplicate_dir = tk.StringVar() self.unique_dir = tk.StringVar() self.enable_classify = tk.BooleanVar(value=True) self.enable_md5list = tk.BooleanVar(value=False) self.dictfile_name = "base_md5_list.txt" # 创建register_heif_opener模块里的一个类然后再Image.open打开HEIC文件 register_heif_opener() # 创建界面组件 self.create_widgets() def create_widgets(self): # 目录选择部分 dir_frame = ttk.LabelFrame(self.root, text="目录配置") dir_frame.pack(padx=10, pady=5, fill=tk.X) self.create_dir_selector(dir_frame, "基准目录:", self.base_dir, 0) self.create_dir_selector(dir_frame, "目标目录:", self.target_dir, 1) self.create_dir_selector(dir_frame, "重复文件目录:", self.duplicate_dir, 2) self.create_dir_selector(dir_frame, "非重复文件目录:", self.unique_dir, 3) # 选项配置 opt_frame = ttk.LabelFrame(self.root, text="选项配置") opt_frame.pack(padx=10, pady=5, fill=tk.X) ttk.Checkbutton(opt_frame, text="启用分类存储", variable=self.enable_classify).pack(anchor=tk.W) ttk.Checkbutton(opt_frame, text="读取MD5列表", variable=self.enable_md5list).pack(anchor=tk.W) # 操作按钮 btn_frame = ttk.Frame(self.root) btn_frame.pack(padx=10, pady=5, fill=tk.X) ttk.Button(btn_frame, text="开始处理", command=self.start_processing).pack(side=tk.LEFT) # 日志显示 self.log_text = tk.Text(self.root, height=15) self.scrollbar = tk.Scrollbar(root, command=self.log_text.yview) self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.log_text.config(yscrollcommand=self.scrollbar.set) self.log_text.pack(padx=10, pady=5, fill=tk.BOTH, expand=True) def create_dir_selector(self, parent, label, var, row): frame = ttk.Frame(parent) frame.grid(row=row, column=0, sticky="ew", padx=5, pady=2) ttk.Label(frame, text=label).pack(side=tk.LEFT) entry = ttk.Entry(frame, textvariable=var, width=40) entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) ttk.Button(frame, text="浏览...", command=lambda: self.select_directory(var)).pack(side=tk.LEFT) def select_directory(self, var): dir_path = filedialog.askdirectory() if dir_path: var.set(dir_path) def log(self, message): # 批量更新日志,减少频繁刷新UI self.log_text.insert(tk.END, message + "\n") self.root.update_idletasks() def calculate_md5(self, filepath): hash_md5 = hashlib.md5() with open(filepath, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() def save_dict_to_file(self, dictionary, file_path): with open(file_path, 'w', encoding='utf-8') as file: json.dump(dictionary, file, ensure_ascii=False, indent=4) def load_dict_from_file(self, file_path): with open(file_path, 'r', encoding='utf-8') as file: return json.load(file) def get_unique_filename(self, dest_dir, filename): base, ext = os.path.splitext(filename) counter = 1 new_name = filename while os.path.exists(os.path.join(dest_dir, new_name)): new_name = f"{base}_{counter}{ext}" counter += 1 return new_name def is_image(self, file_path): ext = os.path.splitext(file_path)[1].lower() return ext in [".jpg", ".jpeg", ".png", ".bmp", ".heic"] def get_file_time(self, file_path): mtime = os.path.getmtime(file_path) ext = os.path.splitext(file_path)[1].lower() if self.is_image(file_path): if ext == ".heic": mtime = self.get_heic_original(file_path) mtime = self.replace_limited(mtime, ':', '-', 2) dt_object = datetime.strptime(mtime, "%Y-%m-%d %H:%M:%S") mtime = dt_object.timestamp() else: with open(file_path, 'rb') as f: tags = exifread.process_file(f, details=False) if 'EXIF DateTimeOriginal' in tags: mtime = str(tags['EXIF DateTimeOriginal']) mtime = self.replace_limited(mtime, ':', '-', 2) dt_object = datetime.strptime(mtime, "%Y-%m-%d %H:%M:%S") mtime = dt_object.timestamp() return mtime def process_files(self): base_files = {} if self.enable_md5list.get(): base_md5_path = os.path.join(self.base_dir.get(), self.dictfile_name) if os.path.exists(base_md5_path): base_files = self.load_dict_from_file(base_md5_path) self.log(f"基准MD5已加载: {base_md5_path}") else: for root, _, files in os.walk(self.base_dir.get()): for file in files: path = os.path.join(root, file) md5 = self.calculate_md5(path) base_files[md5] = file self.log(f"基准文件已扫描: {path}") for root, _, files in os.walk(self.target_dir.get()): for file in files: src_path = os.path.join(root, file) md5 = self.calculate_md5(src_path) self.log(f"正在处理: {src_path}") if md5 in base_files: dest_dir = self.duplicate_dir.get() dest_path = os.path.join(dest_dir, self.get_unique_filename(dest_dir, file)) shutil.move(src_path, dest_path) self.log(f"重复文件已移动: {dest_path}") else: if self.enable_classify.get(): mtime = self.get_file_time(src_path) date_dir_Y = datetime.fromtimestamp(mtime).strftime("%Y") date_dir_m = datetime.fromtimestamp(mtime).strftime("%m") dest_dir = os.path.join(self.unique_dir.get(), date_dir_Y, date_dir_m) else: dest_dir = self.unique_dir.get() os.makedirs(dest_dir, exist_ok=True) dest_path = os.path.join(dest_dir, self.get_unique_filename(dest_dir, file)) shutil.move(src_path, dest_path) self.log(f"非重复文件已移动: {dest_path}") dest_md5_path = os.path.join(self.unique_dir.get(), self.dictfile_name) self.save_dict_to_file(base_files, dest_md5_path) self.log(f"基准MD5已输出: {dest_md5_path}") def start_processing(self): try: dirs = [ self.base_dir.get(), self.target_dir.get(), self.duplicate_dir.get(), self.unique_dir.get() ] for d in dirs: if not d: raise ValueError("所有目录都必须设置") os.makedirs(d, exist_ok=True) # 启动新线程处理文件 threading.Thread(target=self.process_files, daemon=True).start() messagebox.showinfo("完成", "文件处理已开始,请稍候!") except Exception as e: messagebox.showerror("错误", str(e)) self.log(f"错误发生: {str(e)}")if __name__ == "__main__": root = tk.Tk() app = FileOrganizerApp(root) root.mainloop()
4.总结
这款文件整理工具是一个高效且易于使用的文件去重和分类存储工具。它结合了 MD5 校验和文件时间戳分类,确保用户能够快速识别重复文件并将文件按时间进行合理分类。工具采用了多线程技术,有效避免了长时间操作导致的界面卡顿,提供了流畅的用户体验。通过日志记录和 MD5 列表管理,用户可以清晰地了解文件处理的每一步操作。此外,该工具支持多种常见的图片格式,且对 HEIC 文件进行了专门的处理,使其适应了现代文件管理的需求。