AppleScriptを使って、選択したファイルを撮影日ごとにフォルダに整理する方法を紹介します。今回は、Finderで選択したファイルをEXIF情報から撮影日を取得し、日付ごとのフォルダに自動で振り分けるAppleScriptを使います。
※要ExifTool
以前書いたものとほぼ同じです。
EXIFを元に撮影日名フォルダにまとめるAppleScript - ぽんハウス BLOG
実はPythonで書き直すために書いていたのですが、このAppleScriptのみのバージョンと速度があまり変わらなかったので、使うのはこちらにしました。
AppleScriptのコード
以下のスクリプトでは、Finderで選択したファイルの撮影日を取得し、それに基づいてファイルを指定したフォルダにコピーします。フォルダ名は「YYMMDD」形式で作成され、同じ日付のファイルは一つのフォルダにまとめられます。
tell application "Finder"
-- Finderで選択されたファイルを取得
set fileObject to selection as alias list
-- ファイルが選択されているか確認
if fileObject is {} then
log "ファイルが選択されていません。"
return
end if
-- コピー先フォルダを選択
set targetDir to (choose folder with prompt "コピー先フォルダを選択してください")'s POSIX path
-- 処理全体の開始時間を記録(保存先フォルダを選択した後)
set totalStartTime to current date
-- すべての選択されたファイルのPOSIXパスを取得してリストに格納
set filePaths to {}
repeat with obj in fileObject
set end of filePaths to quoted form of (POSIX path of obj)
end repeat
-- ファイルパスをスペースで区切って結合
set filesString to ""
repeat with aFile in filePaths
set filesString to filesString & aFile & " "
end repeat
-- exiftoolの開始時間を記録
set exifStartTime to current date
-- exiftoolで全ファイルの撮影日(DateTimeOriginal)をまとめて取得
set exifResult to do shell script "/opt/homebrew/bin/exiftool -T -DateTimeOriginal " & filesString
-- exiftoolの処理終了時間を記録
set exifEndTime to current date
set exifDuration to (exifEndTime - exifStartTime)
log "EXIFデータ取得にかかった時間: " & exifDuration & " 秒"
-- 結果を行単位で分割
set exifList to paragraphs of exifResult
-- 選択されたファイルリストと対応するEXIF日付リストを処理
repeat with i from 1 to count of filePaths
set filePath to item i of filePaths
set obj to quoted form of filePath
set CDR to item i of exifList -- 対応するEXIFデータの行
-- EXIFデータが空でないか確認
if CDR is not "" then
try
-- 取得した日付のフォーマットが "YYYY:MM:DD" の場合、":" を除去
set AppleScript's text item delimiters to ":"
set CDRList to text items of CDR
set fullDate to item 1 of CDRList & item 2 of CDRList & item 3 of CDRList -- "YYYYMMDD" 形式に結合
set AppleScript's text item delimiters to ""
-- 頭の2桁 ("20") を削除し、次の6桁だけを変数に格納 (YYMMDD形式)
set yymmdd to text 3 through 8 of fullDate
-- フォルダ作成の開始時間を記録
set folderStartTime to current date
-- ターゲットフォルダ作成
set destinationFolderPath to targetDir & "/" & yymmdd
do shell script "mkdir -p " & quoted form of destinationFolderPath
-- フォルダ作成の終了時間を記録
set folderEndTime to current date
set folderDuration to (folderEndTime - folderStartTime)
log "フォルダ作成にかかった時間: " & folderDuration & " 秒"
-- ファイルコピーの開始時間を記録
set copyStartTime to current date
-- ファイルを指定されたフォルダに個別にコピー
do shell script "cp " & filePath & " " & quoted form of destinationFolderPath
-- ファイルコピーの終了時間を記録
set copyEndTime to current date
set copyDuration to (copyEndTime - copyStartTime)
log "ファイルコピーにかかった時間: " & copyDuration & " 秒"
on error errMsg
log "エラーが発生しました: " & errMsg
end try
else
log "ファイル" & filePath & "のEXIFデータが見つかりません。"
end if
end repeat
-- 処理全体の終了時間を記録
set totalEndTime to current date
set totalDuration to (totalEndTime - totalStartTime)
log "全体処理にかかった時間: " & totalDuration & " 秒"
end tell
解説
このスクリプトでは、exiftool
を使って各ファイルの撮影日を取得し、その日付に基づいてフォルダを作成し、ファイルを整理しています。フォルダ名は YYMMDD 形式に変換されて保存されます。フォルダが存在しない場合は自動で作成され、ファイルがフォルダにコピーされます。
例えば、撮影日が「2023年9月5日」のファイルは「230905」というフォルダに移動されます。
exiftoolで全ファイルの撮影日(DateTimeOriginal
)をまとめて取得するためには、exiftool
のパスを指定する必要があります。以下のスクリプトでは、/opt/homebrew/bin/exiftool
を使っていますが、環境によっては異なるパスになることがあります。正しいパスはターミナルで以下のコマンドを実行して確認できます:
which exiftool
また、exiftool
をインストールしていない場合は、以下の方法でインストールできます:
- 公式サイトからダウンロード: ExifTool公式サイト
-
Homebrew経由でインストール:
brew install exiftool
set exifResult to do shell script "/opt/homebrew/bin/exiftool -T -DateTimeOriginal " & filesString
フォルダやファイルがすでに存在する場合、以下の動作がデフォルトの挙動です:
フォルダが既に存在する場合:
スクリプトで使用している mkdir -p
コマンドは、指定したフォルダが既に存在している場合でもエラーを発生させず、処理を続けます。既存のフォルダがある場合、そのまま処理が進行し、新しいフォルダが作成されません。
ファイルが既に存在する場合:
スクリプトで使用している cp
コマンドのデフォルトの挙動では、コピー先に同名のファイルが存在すると、そのファイルが上書きされます。上書きを防ぎたい場合は、cp
コマンドに -n
オプション(no clobber)を付けることで、ファイルが既に存在する場合にコピー処理をスキップすることができます。
例えば、ファイルが既に存在している場合に上書きを防ぐには、以下のように変更します:
-- ファイルを指定されたフォルダにコピー、既に存在する場合は上書きしない do shell script "cp -n " & filePath & " " & quoted form of destinationFolderPath
こうすることで、同名のファイルがコピー先に既に存在する場合、ファイルは上書きされず、コピー処理がスキップされます。
一応Python用のコード
GUIはAppleScriptを使い、上のコードと同じく、Finderで選択しているファイルをPythonに渡します。
AppleScriptのみのコードもそうですが、デバッグのための処理時間測定など、いらないコードが入ったままです。
Pythonコードの保存先になるパスとファイル名で
/Users/XXXX/Desktop/exif_date_sorter.py
この部分を書き換えて下さい。
AppleScript
tell application "Finder"
-- Finderで選択されたファイルを取得
set fileObject to selection as alias list
-- ファイルが選択されているか確認
if fileObject is {} then
display dialog "ファイルが選択されていません。" buttons {"OK"} default button "OK"
return
end if
-- すべての選択されたファイルのPOSIXパスを取得してリストに格納
set filePaths to ""
repeat with obj in fileObject
set filePaths to filePaths & quoted form of (POSIX path of obj) & " " -- ファイルパスにスペースを追加
end repeat
-- コピー先フォルダを選択
set targetDir to (choose folder with prompt "コピー先フォルダを選択してください")'s POSIX path
-- Pythonスクリプトを実行し、ファイルパスとコピー先ディレクトリを渡し、結果を受け取る
set pythonScript to "/usr/bin/python3 /Users/XXXX/Desktop/exif_date_sorter.py"
set shellResult to do shell script pythonScript & " " & filePaths & quoted form of targetDir
-- 結果を表示(コンソールの代わりにダイアログで表示)
display dialog shellResult buttons {"OK"} default button "OK"
end tell
Python
import osimport subprocessimport shutilimport sysimport timedef get_exif_dates(file_paths):"""複数ファイルを一度に処理して、各ファイルの撮影日をYYMMDD形式で取得する"""result = subprocess.run(["/opt/homebrew/bin/exiftool", "-T", "-DateTimeOriginal"] + file_paths, # 複数ファイルを渡すcapture_output=True,text=True)exif_data_list = result.stdout.strip().splitlines() # 結果を行ごとに分割exif_dates = []for exif_data in exif_data_list:if exif_data:date_only = exif_data.split(" ")[0] # 日付部分のみ抽出date_parts = date_only.split(":")if len(date_parts) >= 3:yy = date_parts[0][2:] # "20XX"から"XX"部分を取得mm = date_parts[1]dd = date_parts[2]exif_dates.append(yy + mm + dd)else:exif_dates.append(None)else:exif_dates.append(None) # EXIFデータがない場合はNoneを返すreturn exif_datesdef group_files_by_date(file_paths):"""ファイルを撮影日ごとにグループ化"""from collections import defaultdictexif_dates = get_exif_dates(file_paths) # まとめて撮影日を取得grouped_files = defaultdict(list)# 各ファイルの撮影日でグループ化for file_path, exif_date in zip(file_paths, exif_dates):if exif_date:grouped_files[exif_date].append(file_path)else:print(f"EXIFデータが見つかりませんでした: {file_path}")return grouped_filesdef copy_files_to_date_folders(grouped_files, target_dir):"""ファイルを撮影日ごとにフォルダにまとめてコピー"""for exif_date, files in grouped_files.items():destination_folder = os.path.join(target_dir, exif_date)os.makedirs(destination_folder, exist_ok=True)for file_path in files:shutil.copy(file_path, destination_folder)if __name__ == "__main__":# 全体の開始時間を記録total_start_time = time.time()# AppleScriptからファイルパスとターゲットディレクトリを取得file_paths = sys.argv[1:-1] # 最後の要素はターゲットディレクトリtarget_dir = sys.argv[-1]# ファイルを日付ごとにグループ化grouped_files = group_files_by_date(file_paths)# グループごとにファイルをコピーcopy_files_to_date_folders(grouped_files, target_dir)# 全体の終了時間を記録total_end_time = time.time()total_duration = total_end_time - total_start_time# 結果を表示print(f"全体処理にかかった時間: {total_duration:.4f}秒")