CODEPOOL

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

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

どうも、キヨタです。

プログラミングちゃんと勉強してこなかったので趣味がてら色々作ってみようと思ってPython始めました。 Pythonは初心者なのでお作法とかあまり分からず、とりあえずググりながら書いてみています。

今年の3月に人生初のMacを購入し、子供が寝静まった深夜にニヤニヤしながらカタカタしているのですが、Win機のころ使用していたカメラ画像の取り込みソフトがMacでサポートされておらず写真整理が滞っていました。 そこで記念すべき第1段はカメラからの動画/静止画取り込みを想定した俺得コマンドを作る事にしました。

仕様

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

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

試してみたこと

  • コマンドライン引数の処理 (sys.argv)
  • ファイルパス操作 (pathlib)
  • ファイルのメタ情報の確認 (.stat())
  • 文字列フォーマットの練習 (.format)

使い方と実行結果

使い方はシンプル。
コピー元とコピー先の親ディレクトリをコマンドライン引数で指定します。
-i オプションをつけると静止画のみ、-mをつけると動画のみをコピーします。

> python camera_import.py 

  Error : Too few arguments.
  Usage : camera_import.py [-h | -i | -m] source_dir target_dir

    -h      Show usage.

    -i      Copying image files only.

    -m      Copying movie files only.

> python camera_import.py /Volumes/Source /Volumes/Destination
--------------------------------------------------

  camera_import.py

  Source dir : /Volumes/Source
  Target dir : /Volumes/Destination
  File type  : ('JPG', 'jpg', 'mp4', 'MP4', 'm2ts', 'M2TS')

--------------------------------------------------

   Make directory : /Volumes/Destination/2020/2020-05/2020-05-20

  DSC01052.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  DSC01053.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  DSC01054.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  DSC01055.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  DSC01056.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  DSC01057.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  DSC01058.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  DSC01059.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20
  
  Make directory : /Volumes/Destination/2020/2020-05/2020-05-21

  DSC01071.JPG [2020/05/21] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-21
  DSC01072.JPG [2020/05/21] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-21

...

ソースコード

#!/usr/bin/env python3
#
#   camera_import.py
#
#   Author  : Yuji.Kiyota@gmail.com
#   Create  : 2020/05/22
#

from pathlib import Path
import sys
import shutil
import datetime

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

# Define copying file extentions (case sensitive)
image_ext_tuple = ( 'JPG', 'jpg' )
movie_ext_tuple = ( 'mp4', 'MP4', 'm2ts', 'M2TS' )

# 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' )

# Get file list
src_file_path_list = []
for file_ext in ext_tuple :
    src_file_path_list += list( path_obj_src.glob( '**/*.' + file_ext ) )

# Copy files to YYYY/MM/DD directory
for src_file_path in src_file_path_list :

    # Get file create time as YYYY/MM/DD format
    file_name = src_file_path.name
    create_date = datetime.datetime.fromtimestamp( src_file_path.stat().st_ctime ).strftime( '%Y/%m/%d' )
    date_dir = datetime.datetime.fromtimestamp( src_file_path.stat().st_ctime ).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
    dst_file_path = path_obj_dst_date / file_name
    if dst_file_path.exists() :

        # Judge as 'same file' if target file size is same as source
        if dst_file_path.stat().st_size == src_file_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_file_path, dst_file_path )
        status = '...Copied to ' + str( path_obj_dst_date )

    # Print terminal message
    print( '  {filename} [{ctime}] {stat}'.format( \
        filename = file_name, \
        ctime = create_date, \
        stat = status ) )

まとめと残課題

  • とりあえず思った通りの取り込み動作の実装はできたので良かった
  • shutil.copy2()がコケると処理が止まってしまうので例外処理とか必要っぽい
  • 内部処理を関数化?オブジェクト化?して、直接呼び出しの時のみ引数処理をするような書き方がもっとPythonらしい気がする
  • GooglePhotoにUploadするコマンドも作りたい

今後もマイペースに続けていきたいと思います。