# [メタ情報] # 識別子: DS124の画像動画ファイルをローカルフォルダにコピーする_exe # システム名: DS124カメラアップロード一方向自動同期パイプライン # 技術種別: Python, Zsh, LaunchAgent # 機能名: ds_sync / run_ds_sync / com.XXXXXX.sync_ds124_to_mac # 使用言語: Python 3, Zsh, XML # 状態: 公開用 # [/メタ情報] 要約: DS124(NAS)の共有フォルダ(/Volumes/home/Photos/PhotoLibrary/DSアップロード/)からローカル外付けSSD(/Volumes/NO3_SSD/mybox/カメラアップロード/)へ、写真・動画ファイルを一方向コピー同期するパイプライン。 macOSのTCCセキュリティ制限によるPermissionErrorおよびLaunchAgent直接起動時の権限断絶を防止するため、/bin/zsh ラッパースクリプト(run_ds_sync.sh)を介して毎分(60秒間隔)自動巡回・実行する。 排他制御ロックファイル(/tmp/sync_ds124_to_mac.lock)およびマウント未確認時の安全自律停止ガードを備え、重複コピー防止および拡張子の小文字化処理(.JPG ➔ .jpg)を全自動で行う。 ================================================================================ ■ 1. Python同期メインスクリプト (<任意のフォルダパス>/ds_sync.py) ================================================================================ ```python #!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import shutil import sys from pathlib import Path from dotenv import load_dotenv # ===== 1. 環境設定(.env からの動的ロード) ===== ENV_PATH = Path.home() / "python_scripts" / ".env" load_dotenv(dotenv_path=ENV_PATH) SRC_DIR = os.getenv("DS_SYNC_SRC_DIR") DEST_DIR = os.getenv("DS_SYNC_DEST_DIR") # ===== 2. 起動時自律停止ガード(マウント・設定確認) ===== if not SRC_DIR or not DEST_DIR: print("エラー:必要な環境変数が .env に設定されていません", file=sys.stderr) sys.exit(1) if not os.path.exists(SRC_DIR): print(f"エラー:転送元NASディレクトリが見つかりません(未マウント時自律停止): {SRC_DIR}", file=sys.stderr) sys.exit(1) if not os.path.exists(DEST_DIR): print(f"エラー:転送先ローカルSSDディレクトリが見つかりません: {DEST_DIR}", file=sys.stderr) sys.exit(1) def lowercase_extensions(directory): """コピー先内の大文字拡張子(.JPG, .MOV等)を小文字へ自動整形""" rename_count = 0 for root, _, files in os.walk(directory): for file in files: name, ext = os.path.splitext(file) if ext and ext != ext.lower(): old_path = os.path.join(root, file) new_file = f"{name}{ext.lower()}" new_path = os.path.join(root, new_file) if not os.path.exists(new_path): try: os.rename(old_path, new_path) rename_count += 1 except Exception as e: print(f"[警告] リネーム失敗 ({file}): {e}", file=sys.stderr) return rename_count def main(): print("一方向コピーの巡回を開始します...") existing_files = set(os.listdir(DEST_DIR)) copy_count = 0 for root, _, files in os.walk(SRC_DIR): for file in files: if file.startswith(".") or file.startswith("._"): continue src_file_path = os.path.join(root, file) name, ext = os.path.splitext(file) lower_file = f"{name}{ext.lower()}" if lower_file not in existing_files and file not in existing_files: dest_file_path = os.path.join(DEST_DIR, file) try: shutil.copy2(src_file_path, dest_file_path) print(f"[成功] コピー完了: {file}") copy_count += 1 existing_files.add(file) existing_files.add(lower_file) except Exception as e: print(f"[エラー] コピー失敗 ({file}): {e}", file=sys.stderr) print(f"巡回コピーが完了しました。新規コピー件数: {copy_count}件") print("コピー先の拡張子小文字化処理を開始します...") renamed = lowercase_extensions(DEST_DIR) print(f"拡張子の小文字化が完了しました。変換件数: {renamed}件") if __name__ == "__main__": main() ``` ================================================================================ ■ 2. Zshラッパースクリプト (<任意のフォルダパス>/run_ds_sync.sh) ================================================================================ ```bash #!/bin/zsh # ============================================================================== # DS124カメラアップロード同期 起動ラッパー (TCC権限バイパス&排他ロック管理) # ============================================================================== export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" export LANG="ja_JP.UTF-8" # 二重起動防止ロック処理 LOCKDIR="/tmp/sync_ds124_to_mac.lock" if ! mkdir "$LOCKDIR" 2>/dev/null; then exit 0 fi trap 'rmdir "$LOCKDIR" 2>/dev/null || true' EXIT INT TERM cd <任意のフォルダパス> /usr/bin/python3 <任意のフォルダパス>/ds_sync.py >> /tmp/sync_ds124_to_mac.out 2>&1 ``` ================================================================================ ■ 3. LaunchAgent 設定ファイル (~/Library/LaunchAgents/com.XXXXXX.sync_ds124_to_mac.plist) ================================================================================ ```xml Label com.XXXXXX.sync_ds124_to_mac ProgramArguments /bin/zsh <任意のフォルダパス>/run_ds_sync.sh RunAtLoad StartInterval 60 WorkingDirectory <任意のフォルダパス> StandardOutPath /tmp/sync_ds124_to_mac.out StandardErrorPath /tmp/sync_ds124_to_mac.err ```