说明:有一次自己瞎折腾,然后把服务器相册搞崩了,后来做了备份同步给找了回来,但是相册的时间线全乱了,看起来非常难受。所以就想通过文件夹的形式把照片重新分类
,分类后的结构如下(红色字体为文件夹):
未分类
2023
├── 202301
│ ├── 图片1.jpg
│ ├── 图片2.jpg
│ └── 图片3.jpg
├── 202302
│ ├── 图片4.jpg
│ ├── 图片5.jpg
│ └── 图片6.jpg
└── …
├── …
├── …
└── …
2024
├── 202401
│ ├── 图片1.jpg
│ ├── 图片2.jpg
│ └── 图片3.jpg
├── 202402
│ ├── 图片4.jpg
│ ├── 图片5.jpg
│ └── 图片6.jpg
└── …
├── …
├── …
└── …
import os
import shutil
from PIL import Image
from PIL.ExifTags import TAGS
# 定义支持的照片格式
supported_formats = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff']
# 获取当前脚本文件的绝对路径
script_path = os.path.abspath(__file__)
# 获取当前脚本文件的所在目录的上级目录
directory = os.path.dirname(os.path.dirname(script_path))
# 脚本所在目录的路径下创建程序处理的照片的根文件夹
done_photos_folder = os.path.join(directory, 'done_photos')
os.makedirs(done_photos_folder, exist_ok=True)
# 创建未归类文件夹
unclassified_folder = os.path.join(done_photos_folder, '未归类')
os.makedirs(unclassified_folder, exist_ok=True)
def process_photos(directory_path):
# 遍历目录中的所有文件和目录
for filename in os.listdir(directory_path):
# 构建完整的文件或目录路径
path = os.path.join(directory_path, filename)
if os.path.isfile(path):
# 获取文件的扩展名
file_ext = os.path.splitext(filename)[1].lower()
# 如果文件是照片格式,则读取其信息
if file_ext in supported_formats:
process_photo(path)
elif os.path.isdir(path):
# 处理子目录中的照片
process_photos(path)
def process_photo(file_path):
# 如果file_path包含“done_photos”则不处理
if "done_photos" in file_path or "photo_demo" in file_path:
return
# 打开图片
image = Image.open(file_path)
# 获取图片的Exif信息
exif_data = image._getexif()
# 默认拍摄时间为未归类
capture_time = "未归类"
# 遍历Exif信息
if exif_data:
for tag_id, value in exif_data.items():
# 将标签ID转换为标签名
tag_name = TAGS.get(tag_id, tag_id)
# 如果标签名为DateTimeOriginal,则将拍摄时间赋值给capture_time
if tag_name == 'DateTimeOriginal':
capture_time = value
break
print("照片:", file_path)
print("拍摄时间:", capture_time)
if capture_time != "未归类":
# 提取拍摄年份和月份
year = capture_time[:4]
month = capture_time[5:7]
# 创建年份文件夹
year_folder = os.path.join(done_photos_folder, year)
os.makedirs(year_folder, exist_ok=True)
print("创建年份文件夹:", year_folder)
# 创建月份文件夹
month_folder = os.path.join(year_folder, year + month)
os.makedirs(month_folder, exist_ok=True)
print("创建月份文件夹:", month_folder)
# 构建最终存放照片的文件路径
final_path = os.path.join(month_folder, os.path.basename(file_path))
else:
# 未归类文件的存放路径
final_path = os.path.join(unclassified_folder, os.path.basename(file_path))
try:
# 复制照片到相应的文件夹中
shutil.copy2(file_path, final_path)
print("复制照片到:", final_path)
except Exception as e:
print("无法复制照片:", e)
print()
if __name__ == '__main__':
process_photos(directory)
或者直接使用python脚本执行也可以。打包文件photo_demo下photo_demo.exe
的可执行文件为pyinstaller photo_demo.py
命令打包,不含病毒,如果有报毒请自行斟酌。
注意:代码仅做了简单测试,各位客官按需服用
如果是使用photo_demo打包文件执行,会将photo_demo所在目录作为作为扫描起始目录,即扫描该目录下的所有图片,并在该目录下创建:未分类
、20XX
等分类目录。如果是使用脚本的话请自行调试,比如执行目录改为手动指定等
调整第二版
后代码如下:
调整功能:1. 支持执行处理的文件夹2. 支持是否保留原文件(如果要删除原文件,强烈建议先做备份
)
from pathlib import Path
from PIL import Image
import shutil
from PIL.ExifTags import TAGS
# 定义支持的照片格式
supported_formats = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff'}
# 全局参数
directory = None
del_source = None
move_unrecognized = None
done_photos_folder = None
unclassified_folder = None
unrecognized_folder = None
log_file = None
def process_photos(directory_path):
# 递归处理目录中的所有文件
for item in directory_path.iterdir():
if item.is_dir():
# 如果是目录,则递归处理
process_photos(item)
elif item.is_file() and item.suffix.lower() in supported_formats:
# 如果是文件且属于支持的图片格式,则进行处理
# 符号.开头的文件不处理
if item.name.startswith('.'):
continue
# 如果文件大小为0,直接删除
if item.stat().st_size == 0:
item.unlink()
print(f"删除空文件: {item}")
continue
process_photo(item)
else:
# 不属于支持的图片格式的文件,移动到未识别文件夹
# 符号.开头的文件不处理
if item.name.startswith('.'):
continue
# 如果文件大小为0,不处理
if item.stat().st_size == 0:
continue
move_unrecognized_file(item)
def process_photo(file_path):
# 检查文件路径中是否包含特定的文件夹名称,如果有,则不进行处理
if "done_photos" in file_path.parts or "photo_demo" in file_path.parts:
return
try:
with Image.open(file_path) as image:
exif_data = image._getexif() or {}
except Exception as e:
# 如果打开照片失败,则输出错误信息并返回
print(f"Error opening photo {file_path}: {str(e)}")
return
capture_time = get_capture_time(exif_data)
print("照片:", file_path)
print("拍摄时间:", capture_time)
if capture_time != "未归类":
year, month = capture_time[:4], capture_time[5:7]
final_path = done_photos_folder / year / (year + month)
else:
final_path = unclassified_folder
final_path.mkdir(parents=True, exist_ok=True)
final_photo_path = final_path / file_path.name
if not final_photo_path.exists():
# 将照片复制到最终目标文件夹
shutil.copy2(file_path, final_photo_path)
print("复制照片到:", final_photo_path)
if del_source:
# 如果设置了删除原文件选项,则删除原始文件
file_path.unlink()
print("删除原文件:", file_path)
def get_capture_time(exif_data):
# 从EXIF数据中获取拍摄时间
for tag_id, value in exif_data.items():
if TAGS.get(tag_id, tag_id) == 'DateTimeOriginal':
return value
return "未归类"
def move_unrecognized_file(file_path):
"""移动不支持的文件格式到未识别文件夹"""
# 目标路径为未识别文件夹
target_path = unrecognized_folder / file_path.name
# 移动文件
shutil.copy2(str(file_path), str(target_path))
print(f"复制未识别的文件: {file_path} 到 {target_path}")
if __name__ == '__main__':
# 获取需要处理的文件夹路径
directory = Path(input("请输入需要处理的文件夹路径:"))
# 是否删除已处理的原文件选项
del_source = input("是否删除已处理的原文件y/n?:").lower() == 'y'
# 是否将未处理的文件统一移动到“未识别”中选项
move_unrecognized = input("是否将未处理的文件统一移动到“未识别”中y/n?:").lower() == 'y'
# 设置“已处理照片”文件夹、未归类文件夹、未识别文件夹的路径
done_photos_folder = directory / 'done_photos'
unclassified_folder = done_photos_folder / '未归类'
unrecognized_folder = done_photos_folder / '未识别'
# 确保文件夹存在
done_photos_folder.mkdir(exist_ok=True)
unclassified_folder.mkdir(exist_ok=True)
if move_unrecognized:
unrecognized_folder.mkdir(exist_ok=True)
# 开始处理照片
process_photos(directory)