本文目录导读:

我来帮您设计一个按日期分类的图片批量分组工具,以下是完整的Python实现方案:
Python脚本实现
import os
import shutil
from datetime import datetime
from PIL import Image
import exifread
import argparse
from pathlib import Path
class ImageDateClassifier:
def __init__(self, source_dir, dest_dir=None, date_format='%Y-%m-%d', copy_mode=True):
"""
初始化图片日期分类器
Args:
source_dir: 源图片目录
dest_dir: 目标目录,默认为源目录下的"按日期分类"文件夹
date_format: 日期格式,如 '%Y' 按年, '%Y-%m' 按月, '%Y-%m-%d' 按日
copy_mode: True为复制模式,False为移动模式
"""
self.source_dir = Path(source_dir)
self.dest_dir = Path(dest_dir) if dest_dir else self.source_dir / "按日期分类"
self.date_format = date_format
self.copy_mode = copy_mode
self.supported_formats = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'}
self.stats = {'total': 0, 'success': 0, 'failed': 0, 'unknown': 0}
def get_date_from_exif(self, image_path):
"""从EXIF信息获取拍摄日期"""
try:
with open(image_path, 'rb') as f:
tags = exifread.process_file(f, details=False)
# 尝试多种EXIF日期字段
date_fields = [
'EXIF DateTimeOriginal',
'EXIF DateTimeDigitized',
'Image DateTime',
'EXIF DateTime'
]
for field in date_fields:
if field in tags:
date_str = str(tags[field])
# 处理常见的日期格式
for fmt in ['%Y:%m:%d %H:%M:%S', '%Y-%m-%d %H:%M:%S', '%Y/%m/%d %H:%M:%S']:
try:
return datetime.strptime(date_str, fmt)
except ValueError:
continue
except Exception as e:
return None
return None
def get_date_from_filename(self, image_path):
"""从文件名提取日期(常见格式)"""
filename = image_path.stem
date_patterns = [
(r'(\d{4})-(\d{2})-(\d{2})', '%Y-%m-%d'),
(r'(\d{4})(\d{2})(\d{2})', '%Y%m%d'),
(r'(\d{4})年(\d{2})月(\d{2})日', '%Y年%m月%d日')
]
import re
for pattern, fmt in date_patterns:
match = re.search(pattern, filename)
if match:
try:
return datetime.strptime(match.group(), fmt)
except ValueError:
continue
return None
def get_file_date(self, image_path):
"""获取文件修改日期作为后备"""
timestamp = os.path.getmtime(image_path)
return datetime.fromtimestamp(timestamp)
def classify_images(self):
"""执行图片分类"""
print(f"开始处理图片...")
print(f"源目录: {self.source_dir}")
print(f"目标目录: {self.dest_dir}")
print(f"模式: {'复制' if self.copy_mode else '移动'}")
print("-" * 50)
# 遍历源目录
for image_path in self.source_dir.rglob('*'):
if image_path.suffix.lower() in self.supported_formats and image_path.is_file():
self.stats['total'] += 1
# 获取日期
date = self.get_date_from_exif(image_path)
if not date:
date = self.get_date_from_filename(image_path)
if not date:
date = self.get_file_date(image_path)
self.stats['unknown'] += 1
folder_name = "未知日期"
else:
folder_name = date.strftime(self.date_format)
# 创建目标文件夹
target_folder = self.dest_dir / folder_name
target_folder.mkdir(parents=True, exist_ok=True)
# 复制或移动文件
target_path = target_folder / image_path.name
# 处理文件名冲突
counter = 1
while target_path.exists():
name_stem = image_path.stem
name_suffix = image_path.suffix
target_path = target_folder / f"{name_stem}_{counter}{name_suffix}"
counter += 1
try:
if self.copy_mode:
shutil.copy2(image_path, target_path)
else:
shutil.move(image_path, target_path)
self.stats['success'] += 1
print(f"✓ {image_path.name} -> {folder_name}/")
except Exception as e:
self.stats['failed'] += 1
print(f"✗ {image_path.name} 处理失败: {e}")
self.print_stats()
def print_stats(self):
"""打印统计信息"""
print("\n" + "=" * 50)
print("处理完成!统计信息:")
print(f"总文件数: {self.stats['total']}")
print(f"成功处理: {self.stats['success']}")
print(f"处理失败: {self.stats['failed']}")
print(f"未知日期(使用文件修改日期): {self.stats['unknown']}")
def main():
parser = argparse.ArgumentParser(description='图片按日期分类工具')
parser.add_argument('source', help='源图片目录路径')
parser.add_argument('-d', '--dest', help='目标目录路径(可选)')
parser.add_argument('-f', '--format', default='%Y-%m-%d',
choices=['%Y', '%Y-%m', '%Y-%m-%d', '%Y年%m月', '%Y年%m月%d日'],
help='日期格式:年、年月、年月日')
parser.add_argument('-m', '--move', action='store_true',
help='移动模式(默认复制模式)')
parser.add_argument('-r', '--recursive', action='store_true',
help='递归处理子目录')
args = parser.parse_args()
classifier = ImageDateClassifier(
source_dir=args.source,
dest_dir=args.dest,
date_format=args.format,
copy_mode=not args.move
)
classifier.classify_images()
if __name__ == '__main__':
main()
使用前安装依赖
# 安装必要的库 pip install pillow exifread
使用方法
基本使用
# 按年份分类 python image_classifier.py /path/to/photos -f '%Y' # 按年月分类 python image_classifier.py /path/to/photos -f '%Y-%m' # 按年月日分类(默认) python image_classifier.py /path/to/photos # 移动到分类目录(不复制) python image_classifier.py /path/to/photos -m # 指定目标目录 python image_classifier.py /path/to/photos -d /path/to/output # 中文日期格式 python image_classifier.py /path/to/photos -f '%Y年%m月'
图形界面版本(Tkinter)
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
class ImageClassifierGUI:
def __init__(self):
self.root = tk.Tk()
self.root.title("图片按日期分类工具")
self.root.geometry("600x500")
self.setup_ui()
def setup_ui(self):
# 源目录选择
tk.Label(self.root, text="源目录:").grid(row=0, column=0, padx=10, pady=10)
self.source_entry = tk.Entry(self.root, width=50)
self.source_entry.grid(row=0, column=1, padx=5)
tk.Button(self.root, text="浏览", command=self.select_source).grid(row=0, column=2)
# 目标目录选择
tk.Label(self.root, text="目标目录:").grid(row=1, column=0, padx=10, pady=10)
self.dest_entry = tk.Entry(self.root, width=50)
self.dest_entry.grid(row=1, column=1, padx=5)
tk.Button(self.root, text="浏览", command=self.select_dest).grid(row=1, column=2)
# 日期格式选择
tk.Label(self.root, text="日期格式:").grid(row=2, column=0, padx=10, pady=10)
self.format_var = tk.StringVar(value="%Y-%m-%d")
format_menu = ttk.Combobox(self.root, textvariable=self.format_var,
values=["%Y", "%Y-%m", "%Y-%m-%d", "%Y年%m月", "%Y年%m月%d日"])
format_menu.grid(row=2, column=1, padx=5, sticky="w")
# 操作模式
self.mode_var = tk.IntVar(value=0)
tk.Radiobutton(self.root, text="复制模式", variable=self.mode_var, value=0).grid(row=3, column=0)
tk.Radiobutton(self.root, text="移动模式", variable=self.mode_var, value=1).grid(row=3, column=1)
# 开始按钮
self.start_btn = tk.Button(self.root, text="开始分类", command=self.start_classify,
bg="green", fg="white", font=("Arial", 12))
self.start_btn.grid(row=4, column=1, pady=20)
# 进度条
self.progress = ttk.Progressbar(self.root, length=400, mode='indeterminate')
self.progress.grid(row=5, column=0, columnspan=3, pady=10)
# 日志显示
self.log_text = tk.Text(self.root, height=15, width=70)
self.log_text.grid(row=6, column=0, columnspan=3, padx=10, pady=10)
# 滚动条
scrollbar = tk.Scrollbar(self.root)
scrollbar.grid(row=6, column=3, sticky="ns")
self.log_text.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.log_text.yview)
def select_source(self):
directory = filedialog.askdirectory()
if directory:
self.source_entry.delete(0, tk.END)
self.source_entry.insert(0, directory)
def select_dest(self):
directory = filedialog.askdirectory()
if directory:
self.dest_entry.delete(0, tk.END)
self.dest_entry.insert(0, directory)
def log(self, message):
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.root.update()
def start_classify(self):
if not self.source_entry.get():
messagebox.showerror("错误", "请选择源目录")
return
self.start_btn.config(state="disabled")
self.progress.start()
# 在新线程中运行
thread = threading.Thread(target=self.run_classifier)
thread.start()
def run_classifier(self):
try:
classifier = ImageDateClassifier(
source_dir=self.source_entry.get(),
dest_dir=self.dest_entry.get() if self.dest_entry.get() else None,
date_format=self.format_var.get(),
copy_mode=(self.mode_var.get() == 0)
)
# 重定向输出到GUI
classifier.log_func = self.log
classifier.classify_images()
self.root.after(0, lambda: messagebox.showinfo("完成", "图片分类完成!"))
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", str(e)))
finally:
self.root.after(0, self.finish)
def finish(self):
self.progress.stop()
self.start_btn.config(state="normal")
def run(self):
self.root.mainloop()
if __name__ == "__main__":
gui = ImageClassifierGUI()
gui.run()
功能特点
-
多源日期提取:
- 优先使用EXIF拍摄日期
- 文件名日期识别
- 文件修改日期作为后备
-
灵活的分组方式:
- 按年、年月、年月日分组
- 支持中文日期格式
-
安全可靠:
- 默认复制模式,不破坏原文件
- 自动处理文件名冲突
- 详细的处理日志
-
使用简便:
- 命令行和图形界面两种方式
- 支持批量处理
- 进度显示
选择适合您的版本,按需使用即可!
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。