家族や友人との思い出整理に役立つ写真整理ツールをPythonで書いた

2021-08-21

はじめに

最近、しましまプリントというサービスを使って写真を印刷しています。

妻とワイのスマホやデジカメから写真をPCに取り込むまでは良いんですが整理しづらいなというのが悩みでした。

スマホで手軽に写真を取れるからこそ大量の写真を整理できるソフト無いかなぁと探してました。

写真を日付ごとにフォルダ分けて格納してほしいというのが希望だったのですが無いんですね。

よりリッチな機能が織り込まれた写真整理ソフトはあるのですが求めるものとは違います。

やはり真に求めるものを手に入れるにはDIYしかありません。作っていきましょう。

コード全体

出来上がったコードの全体像はこちらです。 Githubにも上げておきました。(随分前に)

Github:PhotoSorting

# -*- coding: utf-8 -*-
import sys
import os
import re
import shutil
import glob
import datetime
from PIL import Image
from PIL.ExifTags import TAGS
from tqdm import tqdm
import ffmpeg
import json

def get_exif(img,file):
    exif = img._getexif()
    try:
        for id,val in exif.items():
            tg = TAGS.get(id,id)
            if tg == "DateTimeOriginal":
                return val
    except AttributeError:
            if os.path.exists(file) :
                dt = datetime.datetime.fromtimestamp(os.stat(file).st_mtime)
                key = dt.strftime('%Y:%m:%d %H:%M:%S')
                return key
    if os.path.exists(file) :
        dt = datetime.datetime.fromtimestamp(os.stat(file).st_mtime)
        key = dt.strftime('%Y:%m:%d %H:%M:%S')
    return key

def list_files(dir,func):
    for file in dir:
        try:
            img = Image.open(file)
        except:
            continue
        datetimeinfo = func(img,file)
        yield (file, datetimeinfo)
        img.close()

def make_folda(taginfo):
    date_str = re.split(':|\s', taginfo[1])
    year = date_str[0]
    month = date_str[1]
    date = str(date_str[1]) + str(date_str[2])
    time = date_str[3] + '_'+ date_str[4] + '.'+ date_str[5]
    fd_path = '整理済み/写真/' + str(year) +'/'+ str(month)
    datetime = str(year) +'-'+ str(date) + '-' + str(time)
    os.makedirs(fd_path, exist_ok=True )
    return fd_path,datetime

def make_folda_douga(taginfo,datatimes):
    date_str = re.split(':|\s|-|T|\.', datatimes)
    year = date_str[0]
    month = date_str[1]
    date = str(date_str[1]) + str(date_str[2])
    time = date_str[3] + '_'+ date_str[4] + '.'+ date_str[5]
    fd_path = '整理済み/動画/' + str(year) +'/'+ str(month)
    datetimestr = str(year) +'-'+ str(date) + '-' + str(time)
    os.makedirs(fd_path, exist_ok=True )
    return fd_path, datetimestr

def find_metadata_atom(file, name):
       atom = file.find('.//%s//data' % name)
       return atom.get_attribute('data')

def getdatetimeinJSON(json_dict):
    if 'format' not in json_dict.keys():
        return None
    if 'tags' not in json_dict['format'].keys():
        return None
    if 'creation_time' not in json_dict['format']['tags'].keys():
        return None
    return json_dict['format']['tags']['creation_time']

def main():
    id = 0
    print("写真(jpg)の整理中")
    target_path = glob.glob('.\imgs\**\*.webp', recursive=True)
    list_jpg = list_files(target_path,get_exif)
    bar = tqdm(total = len(target_path))
    for taginfo in list_jpg:
        mv_path, datetime = make_folda(taginfo)
        id = id + 1
        new_path = shutil.copy2(taginfo[0], mv_path + '/' +datetime+'_'+str(id) +'.webp')
        bar.update(1)

    print("写真(png)の整理中")
    target_path_png = glob.glob('.\imgs\**\*.webp', recursive=True)
    list_png = list_files(target_path_png,get_exif)
    bar = tqdm(total = len(target_path_png))
    list_png = list_files(target_path_png,get_exif)
    for taginfo in tqdm(list_png):
        mv_path, datetime = make_folda(taginfo)
        id = id + 1
        new_path = shutil.copy2(taginfo[0], mv_path + '/' +datetime+'_'+str(id) +'.webp')
        bar.update(1)

    print("写真(HEIC)の整理中")
    target_path_heic = glob.glob('.\imgs\**\*.heic', recursive=True)
    list_png = list_files(target_path_heic,get_exif)
    bar = tqdm(total = len(target_path_heic))
    list_heic = list_files(target_path_heic,get_exif)
    for taginfo in tqdm(list_heic):
        mv_path, datetime = make_folda(taginfo)
        id = id + 1
        new_path = shutil.copy2(taginfo[0], mv_path + '/' +datetime+'_'+str(id) +'.heic')
        bar.update(1)

    print("動画(mp4)の整理中")
    target_path_mp4 = glob.glob('.\imgs\**\*.mp4', recursive=True)
    for path in tqdm(target_path_mp4):
        video_info = ffmpeg.probe(path)
        json_dict = json.loads(json.dumps(video_info))
        datatimes = getdatetimeinJSON(json_dict)
        mv_path, datetimestr = make_folda_douga(path,datatimes)
        id = id + 1
        new_path = shutil.copy2(path, mv_path + '/' +datetimestr+'_'+str(id) +'.mp4')

    print("動画(mov)の整理中")
    target_path_mov = glob.glob('.\imgs\**\*.mov', recursive=True)
    for path in tqdm(target_path_mov):
        try:
            video_info = ffmpeg.probe(path)
            json_dict = json.loads(json.dumps(video_info))
            datatimes = getdatetimeinJSON(json_dict)
            mv_path, datetimestr = make_folda_douga(path,datatimes)
            id = id + 1
            new_path = shutil.copy2(path, mv_path + '/' +datetimestr+'_'+str(id) +'.mov')
        except:
            print(path)

if __name__ == '__main__':
    main()

準備

pip install ffmpeg
pip install pillow
pip install tqdm

動画から情報取得するためにffmpegを使用しています。 画像は定番のpillowを使い、tqdmは進捗バーを出すのに使っています。

実行画面

動作中の画面はこんな感じです。

実行画面1

年と月ごとに写真を分類して移動させます。

実行画面2 ]

ファイル名は一律で日時から作られるようにしています。

実行画面4

まとめ

ソフトを書いたのが結構前なので自分で書いておきながら内容を忘れてますわ。 やってることは、画像や動画のファイル形式毎にglobを使って取得し一ファイルずつexifに入っている日付情報参照してファイルをコピーしています。

動画のところはエラー処理が甘そうなので気が向いたら更新します。