Exif情報の一覧化

 写真(JPEG, RAW)のExif情報を一覧にして、かつ重複(現像前のRAWと現像後の複数のJPEGなど)を削除して、Excelファイルに書き出すPythonコード。割と適当。

import os
import re
from glob import glob
from tqdm import tqdm
import pandas as pd
import subprocess
import re

os.chdir(os.path.dirname(os.path.abspath(__file__)))

def get_exif(img_path):
    """ ExifToolを用いて、画像ファイルの全Exif(辞書型)を取得する """
    prog = r"D:\Tools\exiftool\exiftool.exe"
    result = subprocess.run(f'{prog} "{img_path}"',
                            shell=True,
                            check=True,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            universal_newlines=True)
    exif_dict = dict()
    for line in result.stdout.splitlines():
        sp = line.split(":", maxsplit=1)
        key = re.sub(r"\s+$", "", sp[0])
        val = re.sub(r"^\s+", "", sp[1])
        exif_dict[key] = val
    return exif_dict

def main(images, exif_list):
    """ 画像ファイルリストを受け取り、Exif情報をdfとして返す """
    # key=path, value=listの辞書を作成して、あとからdfに変換する
    df_dict = dict()
    for img_path in tqdm(images):
        exif_dict = get_exif(img_path)

        data = []
        for key in exif_list:
            # 出力したいExifだけをdataに格納する
            if key in exif_dict.keys():
                data.append(exif_dict[key])
            else:
                data.append("-")
        df_dict[img_path] = data
    # DataFrameに変換する
    df = pd.DataFrame.from_dict(df_dict, orient="index")
    df.columns = exif_list
    return df

def remove_dup(df):
    """ 重複するデータを削除する(現像前と現像後など) """
    recpl = re.compile("[A-Z_][A-Z0-9]{2}[[A-Z0-9_]\d{4}")
    # 1ループ目でbasenameと優先度、取得する
    for idx in df.index:
        # 現像後"20210101_DP2M0001_2.jpg"みたいなファイル名から
        # "DP2M0001"の部分だけを取得する
        res = recpl.search(idx)
        if res:
            basename =  res.group()
            df.at[idx, "basename"] = basename

            lens_name = str(df.at[idx, "Lens ID"])
            if idx[-4:].lower() == ".jpg":
                # rawとjpgがあった場合、jpgを優先する
                priority = 100
            else:
                priority = 0
            if lens_name.lower() != "nan" and lens_name != "-":
                # レンズ名の入っている方を優先
                priority += 10
            df.at[idx, "priority"] = priority
        else:
            # basenameが取れなかった場合、削除
            df = df.drop(index=idx)

    # 2ループ目で、重複するデータを削除する
    for idx in tqdm(df.index):
        # 既に削除済みの可能性があるため判定
        if idx in df.index:
            basename = df.at[idx, "basename"]
            date = df.at[idx, "Date/Time Original"]
            # ファイル名(拡張子除く)と日付が重複するデータを取得
            df_temp = df[((df["Date/Time Original"]==date) & (df["basename"]==basename))]
            # df_temp = df[(df["Date/Time Original"]==date)]

            if df_temp.shape[0] > 0:
                # priorityで降順ソート
                df_temp_sort = df_temp.sort_values("priority", ascending=False)
                for idx2 in df_temp_sort.index.to_list()[1:]:
                    # priorityが最大のものだけを残して削除
                    df = df.drop(index=idx2)
    
    # 重複判定のためだけに作った列を削除
    df = df.drop(columns=["basename", "priority"])
    return df

if __name__ == "__main__":
    # 対象とする画像ファイルリスト
    files = glob(r"D:\Photo\**\*.jpg", recursive=True)
    files += glob(r"D:\Photo\**\*.raf", recursive=True)
    files += glob(r"D:\Photo\**\*.arw", recursive=True)
    files += glob(r"D:\Photo\**\*.pef", recursive=True)
    files += glob(r"D:\Photo\**\*.x3f", recursive=True)

    # 出力したいExif情報リスト
    exif_list = ["Date/Time Original", 
                 "Exposure Time", 
                 "F Number", 
                 "ISO",
                 "Focal Length", 
                 "Focal Length In 35mm Format", 
                 "Exposure Compensation",
                 "Make", 
                 "Camera Model Name", 
                 "Lens ID", 
                 "Lens Model"]
    
    # いったん全画像ファイルのExifをdfとして取得
    print("get exif data")
    df = main(files, exif_list)

    # 重複するデータを削除
    print("remove duplicated data")
    df = remove_dup(df)

    # Excel形式で出力
    df.to_excel("photo.xlsx")
    print("finished")

 ちなみに、外部プログラムとしてExifTool.exeを1回ずつキックしているので、処理は遅い。

 ライブラリPILでもレンズ名以外は取れるし、ライブラリPyexiv2でも一部のレンズ名は取れて、これらはExifToolよりも数十倍速いが、ExifToolが一番多くの情報を取れる。

 ライブラリPyExifToolを使うとExifTool.exeを開きっぱなしにしてPipeでファイルを渡せるので、外部プログラムとして1回ずつExifTool.exeを起動するよりも10倍近く速く、同じ情報が取れるのだが、なんか安定しない(時々取得できない)。なので、仕方なしに外部プログラムを1回ずつキックしている。ださい。

 遅い。15,000枚弱でちょうど1時間かかった。