python脚本将照片按时间线整理

说明:有一次自己瞎折腾,然后把服务器相册搞崩了,后来做了备份同步给找了回来,但是相册的时间线全乱了,看起来非常难受。所以就想通过文件夹的形式把照片重新分类,分类后的结构如下(红色字体为文件夹):

未分类
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等分类目录。如果是使用脚本的话请自行调试,比如执行目录改为手动指定等

photo_demo打包文件下载

调整第二版后代码如下:
调整功能: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)

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值