CODEPOOL

趣味でやるプログラミング関連のMEMO置き場。

続・カメラからのデータ取り込みコマンドを作ってみた

どうも、キヨタです。

先日作成したカメラからのデータ取り込みコマンドに早速不満が出てきたので書き直してみました。

元記事はこちらになります。

codepool.hatenablog.com

データコピーで色々使ってみると撮映日がファイル作成日と異なるケースがあり、コピー先の仕分けが期待と異なる結果となってしまっていました。

そこで、今回は静止画ファイルに限りExifから撮映日を取得してきて保存先の仕分けをするように実装変更しました。

仕様おさらい

f:id:yujikiyota:20200525010427p:plain
カメラからのデータ取り込みコマンドイメージ

  • 取り込み元メディアでは指定ディレクトリ以下のサブディレクトリまで探索し、拡張子でマッチして取り込むファイルを決める
  • 取り込み先ではyyyy/yyyy-mm/yyyy-mm-ddのような撮影日の年月日ベースの階層構造でディレクトリを分けて格納する
  • オプションで静止画のみ/動画のみの取り込みにも対応できるようにする
  • ファイル作成日などのメタデータは保持してコピーしてくる、元ファイルは消さない
  • 取り込み先に同一名のファイルがある場合にはサイズ比較で同一性を確かめてログに吐く

今回の変更点

  • 静止画ファイルはExifから撮映日を取得する Pillow
  • コード冒頭の拡張子リストの大文字/小文字並記回避
  • 要素機能の関数化 def
  • for文制御のフィルタ使用 .filter() lambda
  • コマンドライン引数処理の分離 main()

ソースコード

#!/usr/bin/env python3

import sys
import shutil
import datetime
from pathlib import Path
from PIL import Image
from PIL.ExifTags import TAGS

# Define directory format
dir_format = '%Y/%Y-%m/%Y-%m-%d'

# Define copying file extentions
image_ext_tuple = ('jpg', 'jpeg')
movie_ext_tuple = ('mp4', 'm2ts')


def get_exif_date(img):
    exif = img._getexif()
    try:
        for id, val in exif.items():
            tg = TAGS.get(id, id)
            if tg == "DateTimeOriginal":
                # Convert 'yyyy:mm:dd hh:mm:ss' -> 'yyyy-mm-dd'
                date_val = val.split(' ')
                return date_val[0].replace(':', '-')

    # If there is no exif tags
    except AttributeError:
        return 'NON'

    # If there is no date exif value
    return 'NON'


def copy_media_classify_by_date(path_obj_src, path_obj_dst, dir_format, ext_tuple):
    # Get file list
    src_path_list = list(path_obj_src.glob('**/*'))

    # Copy files to YYYY/MM/DD directory
    for src_path in list(filter(lambda path: path.suffix.replace('.', '').lower() in ext_tuple, src_path_list)):

        # Try to get image taken date
        try:
            img = Image.open(src_path, mode="r")
            taken_date = get_exif_date(img)
        except Exception:
            taken_date = 'NON'

        # Get create date
        create_date = datetime.datetime.fromtimestamp(
            src_path.stat().st_ctime).strftime('%Y-%m-%d')

        # Convert directory path string
        if taken_date == 'NON':
            date_dir = datetime.datetime.fromisoformat(
                create_date).strftime(dir_format)
        else:
            date_dir = datetime.datetime.fromisoformat(
                taken_date).strftime(dir_format)

        # Make YYYY/MM/DD directory if it does not exist
        path_obj_dst_date = path_obj_dst / date_dir
        if not path_obj_dst_date.exists():
            path_obj_dst_date.mkdir(parents=True)
            print('\n  Make directory : {dir}\n'.format(
                dir=str(path_obj_dst_date)))

        # Skip copying if target file is already exist
        file_name = src_path.name
        dst_path = path_obj_dst_date / file_name

        if dst_path.exists():
            # Judge as 'same file' if target file size is same as source
            if dst_path.stat().st_size == src_path.stat().st_size:
                status = '...Skipped : Target file is already exist.'
            else:
                status = '...Skipped : Target file is already exist. (different size)'

        # Copy file with meta-data (copy2)
        else:
            shutil.copy2(src_path, dst_path)
            status = '...Copied to ' + str(path_obj_dst_date)

        # Print terminal message
        if taken_date == 'NON':
            print('  {filename} [{ctime}] {stat}'.format(
                filename=file_name,
                ctime=create_date,
                stat=status))
        else:
            print('  {filename} [{ttime}] {stat}'.format(
                filename=file_name,
                ttime=taken_date,
                stat=status))


def main():
    # Process arguments
    args = sys.argv

    usage = '  Usage : ' + \
        args[0].replace('./', '') + ' [-h | -i | -m] source_dir target_dir'
    usage += """

        -h      Show usage.

        -i      Copying image files only.

        -m      Copying movie files only.
    """

    for arg in args:
        if arg == args[0]:
            pass
        elif arg == '-h' or arg == '--help':
            print('\n' + usage)
        elif arg == '-i':
            ext_tuple = image_ext_tuple
        elif arg == '-m':
            ext_tuple = movie_ext_tuple
        elif 'path_obj_src' not in locals():
            path_obj_src = Path(arg)
        elif 'path_obj_dst' not in locals():
            path_obj_dst = Path(arg)

    if 'ext_tuple' not in locals():
        ext_tuple = image_ext_tuple + movie_ext_tuple

    # Check input exeptions
    if 'path_obj_dst' not in locals():
        print('\n  Error : Too few arguments.')
        print(usage)
        sys.exit(1)

    # Print header
    print('-' * 50 + '\n')
    print('  ' + str(args[0]).replace('./', '') + '\n')
    print('  Source dir : \n    ' + str(path_obj_src))
    print('  Destination root dir : \n    ' + str(path_obj_dst))
    print('  File type : \n    ' + str(ext_tuple) + '\n')
    print('-' * 50 + '\n')

    copy_media_classify_by_date(
        path_obj_src, path_obj_dst, dir_format, ext_tuple)


if __name__ == '__main__':
    main()

結果と所感

  • ちゃんとExifから撮映日を取得することで正しい動作が得られた
  • 要素機能をくくり出し→関数化はもっと修行が必要
  • filterとかlambda式とか便利やん

参考文献

glorificatio.org

it-engineer-lab.com

docs.python.org