Pythonで組んだ便利スクリプトです。メモとして残します。

※追記(2023-11-12):Rustで書き直して、日付を印字できる機能を追加したツールを公開しました。

photo-organizer: https://github.com/HamaguRe/photo-organizer

仕様

写真の入ったディレクトリを指定すると、ディレクトリ内の写真のファイル名を“撮影日時 + ハッシュ値”に統一します。

ハッシュ値はファイル名が被らないように付けているだけなので先頭5桁だけ使用し、もし日付情報が得られなかった場合はファイル名をすべてハッシュ値とします。

例えば、2023年1月23日の14時30分に撮影した場合は「2023-01-23_1430_0gh2q.jpg」みたいになります。

また、入れ子になっている複数のディレクトリを一括で処理したい場合もあるので、実行時に-rオプションを付けることでサブディレクトリまで再帰的に処理できるようにしました(python3 change_script.py -rみたいにする)。-rオプションを付けなかった場合は、選択したディレクトリ直下のファイルのみ処理します。

import hashlib
import os, glob, sys
from PIL import Image
from PIL.ExifTags import TAGS
from tkinter import filedialog


def get_exif_of_image(file_path):
    """
    Exifデータを格納した辞書を返す.
    """
    img = Image.open(file_path)

    try:
        exif = img._getexif()
    except AttributeError:
        return {}

    exif_table = {}
    try:    
        for tag_id, value in exif.items():
            tag = TAGS.get(tag_id, tag_id)
            exif_table[tag] = value
    except:
        pass

    return exif_table

# サブディレクトリ含めて再帰的に処理したい場合は,この関数を再帰的に呼び出すこと.
def change_names(dir_path):
    """
    指定したディレクトリ内の画像ファイル(jpg, png)の名前を書き換える.
    """
    # JPGとPNG表記をjpgとpngに統一する
    old_ext = ('.JPG', '.PNG')
    new_ext = ('.jpg', '.png')
    for file_name in os.listdir(dir_path):
        # ファイル名と拡張子を分割
        base_name, ext = os.path.splitext(file_name)
        # 変更前の拡張子に一致する場合、新しい拡張子に変更
        if ext in old_ext:
            new_file_name = base_name + new_ext[old_ext.index(ext)]
            old_file_path = os.path.join(dir_path, file_name)
            new_file_path = os.path.join(dir_path, new_file_name)
            os.rename(old_file_path, new_file_path)


    extensions = ['.jpg', '.png']
    total_nums = []  # 処理する画像枚数を拡張子の順に格納
    # 拡張子ごとのループ
    for ext in extensions:
        file_paths = glob.glob(dir_path + '/*' + ext)
        total_nums.append(len(file_paths))

        # 画像ごとのループ
        for file_path in file_paths:
            # 画像のハッシュ値
            target = open(file_path, "rb")
            target_md5 = hashlib.md5( target.read() ).hexdigest()
            target.close()
            # Exifの日付情報 「'DateTimeOriginal': '2017:12:29 15:13:21'」  YYYY:MM:DD HH:MM:SS
            date_time_original = get_exif_of_image(file_path).get("DateTimeOriginal")
            if date_time_original is None:
                # 時刻情報を持っていなかったら全部ハッシュ値にする
                os.rename(file_path, dir_path + '/' + target_md5 + ext)
            else:
                tmp = date_time_original.split()
                date = tmp[0].replace(":", "-")
                time = tmp[1].replace(":", "")[:4]
                # YYYY-MM-DD_HHMM_md5.jpg
                os.rename(file_path, dir_path + '/' + date + '_' + time + '_' + target_md5[:5] + ext)

    print("------- Result -------")
    for i in range(len(extensions)):
        print(" |{}:{}".format(extensions[i], total_nums[i]), end="")
    print("|\n")


if __name__ == '__main__':
    # ダイアログで処理対象のディレクトリを選択する
    current_dir = os.path.abspath(os.path.dirname(__file__))
    dir_path = filedialog.askdirectory(initialdir=current_dir)

    print("Change names of files in this directory: {}".format(dir_path))
    while True:
        # 確認メッセージ
        check = str(input("Can I start the process? [y/n]:"))
        if check == "y":
            break
        elif check == "n":
            print("Pushed 'n' key...")
            quit()
        else:
            print("Please push the key, 'y' or 'n'.")

    print("processing...")
    change_names(dir_path)

    # コマンドライン引数で -r を指定した場合はサブディレクトリの中身も処理する.
    # サブディレクトリの中に更にサブディレクトリがあっても処理される.
    if len(sys.argv) > 1:
        if sys.argv[1] == "-r":
            for root, dirs, files in os.walk(top=dir_path):
                for dir in dirs:
                    sub_path = os.path.join(root, dir)
                    print("Change names of files in this directory: {}".format(sub_path))
                    change_names(sub_path)

参考サイト