本文目录导读:

我来帮您设计一个按日期分类的图片批量分组工具,以下是完整的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拍摄日期
- 文件名日期识别
- 文件修改日期作为后备
-
灵活的分组方式:
- 按年、年月、年月日分组
- 支持中文日期格式
-
安全可靠:
- 默认复制模式,不破坏原文件
- 自动处理文件名冲突
- 详细的处理日志
-
使用简便:
- 命令行和图形界面两种方式
- 支持批量处理
- 进度显示
选择适合您的版本,按需使用即可!
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播