続・カメラからのデータ取り込みコマンドを作ってみた
どうも、キヨタです。
先日作成したカメラからのデータ取り込みコマンドに早速不満が出てきたので書き直してみました。
元記事はこちらになります。
データコピーで色々使ってみると撮映日がファイル作成日と異なるケースがあり、コピー先の仕分けが期待と異なる結果となってしまっていました。
そこで、今回は静止画ファイルに限りExifから撮映日を取得してきて保存先の仕分けをするように実装変更しました。
仕様おさらい
- 取り込み元メディアでは指定ディレクトリ以下のサブディレクトリまで探索し、拡張子でマッチして取り込むファイルを決める
- 取り込み先では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式とか便利やん