Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

mediaferry

カメラの SD カードを NAS に取り込み、分割された動画をつなぎ、Immich へ送るまでを 1 つの画面で行う TrueNAS のカスタムアプリです。

カードを挿すと中身を判別し、NAS のデータセットへコピーします。長い録画がカメラの 都合で分割されていれば 1 本に結合し、RAW+JPEG は Immich 上で 1 枚に束ねます。 撮影日時のずれも、機種ごとの規則に沿って直してから送ります。

SD カード ──► NAS のライブラリ ──► (分かれた動画をつなぐ) ──► Immich
   挿す         画面から取り込む            画面で確認             送信は手動

できること

取り込み カードを挿すと機種を判別してコピーします。初めて見るカードは必ず確認を求めます。一度承認したカードは、次から挿すだけで取り込めます
元の構造を保つ DCIM/100CANON/IMG_0001.JPG のようなカード上の並びをそのまま残すので、NAS を直接開いても辿れます
分かれた動画をつなぐ 長い録画は、カメラが自動で複数のファイルに分けます(DJI Osmo Pocket は約 16 GiB ごと、Canon EOS は約 4 GB ごと)。これを 1 本に結合し、結果を検証します。不合格なら元のまま残して選ばせます
Immich へ送信 送信は常に手動です。ホームの「やること」から 1 手で送れます。複数の Immich へ独立に送れます
重複を作らない 送る前にチェックサムで照合し、既にある写真は上げ直しません(ゴミ箱の中も見ます)
撮影日時の補正 UTC で書かれる機種は、現地の壁時計へ直してから送ります
RAW+JPEG の束ね 同じシャッターで出た 2 枚を Immich のスタックにまとめます
タグ付け 機種ごとのタグを自動で付けます

壊さないことを優先しています。 カードは常に読み取り専用で開き、書き込みません。 Immich にある写真の撮影日時を書き換えるときは、自分が上げたと確かめられた場合を 除いて承認を求めます。

対応している機種

カメラの種類 対象 取り込む拡張子 つなぐ 日時 束ねる タグ
dji-osmo DJI Osmo Pocket 系 MP4 / JPG 約 16 GiB 分割 ファイル名 → 更新時刻。現地時刻へ補正 —— DJI Osmo Pocket 4
canon-eos Canon EOS(カードリーダー経由) JPG / CR2 / MOV 約 4 GB 分割 EXIF → 器の時刻 → 更新時刻。現地時刻へ補正 JPG + CR2 Canon EOS
generic-dcim DCIM を持つ一般的なカード JPG / JPEG / PNG / HEIC / MP4 / MOV / AVI —— EXIF → 更新時刻。補正しない —— ——

機種は設定 › カメラの種類から追加・編集できます。 最初から入っているものは 直接編集できませんが(アプリを更新すると上書きされるため)、複製すれば自分用に 変えられます。ファイル名の規則、対象の拡張子、日時の取り方、つなぐかどうか、 束ね方を YAML で書きます。詳しくは 使い方ガイド。

必要なもの

  • TrueNAS SCALE(Apps → Custom App を使います)
  • 取り込み先のデータセット
  • Immich v3.1.0 と、そのアカウントの API キー
  • USB のカードリーダー、または USB マスストレージとして見えるカメラ

配布イメージがあります(ghcr.io/akashisn/mediaferry と -mountd)。 ビルドは要りません。

対象の Immich は v3.1.0 です。 それより新しい版へ上げるなら、まず tools/immich_probe.py で前提が成り立つかを確かめてください(判定は終了ステータスに返ります)。API の形が 変わっていた場合に直す場所は app/src/mediaferry/adapters/immich.py の 1 ファイルに 閉じています。

使いはじめる

  1. 導入手順 — データセットの用意、TrueNAS への配置、初回の起動
  2. 使い方ガイド — 画面ごとの操作、取り込みから送信まで、困ったとき

導入は「データセットを作る → compose.yaml の <...> を埋める → Custom App に貼る」の 3 段です。 LAN へ公開する前にパスワードを設定してください(既定では本体のループバックに しか出ません)。

画面

ナビゲーションは「ホーム / 写真 / 設定」の 3 つだけです。

画面 何をするところか
ホーム いま挿さっているカード、やること、進行中の作業、送り先ごとの状況、さっき取り込んだもの
写真 取り込んだメディアを日付ごとのグリッドで。絞り込んで選び、まとめて送信
設定 送り先、カメラの種類、作業(つなぐ・日時の確認・接続中のカード)、記録、詳しい設定、リセット

作業のページは設定とホームの下にあり、ナビゲーションの項目を増やしません。

ページ 何をするところか
接続中のカード 挿さっているカード。判別結果と理由、スキャン・取り込み・信頼登録
つなぐ 分かれた動画を探す、つなぐ、検証結果の確認、手での組み立て
日時の確認 先に Immich にあった写真の日時を直してよいかの承認
送る 送り先 → 対象 → 確認の 3 段

進捗と結果はホームが持ちます。送信を始めた後に画面を閉じても、送信は続きます。

Immich の登録と接続の検証、スタックの見送り理由は設定 › 送り先にあります。

設定

環境変数で渡します。MEDIAFERRY_ を頭に付けます(例: MEDIAFERRY_DATA_ROOT)。 「画面から」の欄は、設定 › 詳しい設定で変えられるかどうかです。

設定 既定 画面から 意味
DATA_ROOT /data —— 取り込み先。ここにライブラリと DB が入ります
BROKER_SOCKET /run/mediaferry/broker.sock —— マウント担当のコンテナとの接続口
SECRET_KEY 未設定 —— Immich の API キーを暗号化する鍵。送り先を 1 つでも作るなら必須(32 バイトを base64)
AUTH_PASSWORD 未設定 —— 設定するとログインが要るようになります。LAN へ出すなら設定してください
BIND_HOST 127.0.0.1 再起動後 待ち受けるアドレス。LAN へ出すときだけ 0.0.0.0 にします
HTTP_PORT 8080 再起動後 待ち受けるポート
TRUSTED_HOSTS 空 再起動後 名前でアクセスするとき、その名前を許可します(IP と localhost は既定で通ります)
AUTO_IMPORT trusted すぐ trusted = 承認済みのカードは挿すだけで取り込む / off = 常に手動
DEFAULT_TIMEZONE 未設定 すぐ 日時の補正に使う地域(例: Asia/Tokyo)。DJI と Canon のカメラの種類を使うなら必須
UPLOAD_TIMEOUT_SECONDS 86400 すぐ 1 回の送信を待つ上限(大きな動画のため既定は長めです)
UPLOAD_MAX_ATTEMPTS 3 すぐ 送信の再試行の上限
LOG_LEVEL info すぐ debug / info / warning / error

「——」の 4 つは TrueNAS のアプリ設定でだけ決められます(DB 自身の置き場所と、 秘密の置き場所なので、アプリの DB には保存しません)。

Immich の URL と API キーは環境変数では設定しません。 画面の「設定 › 送り先」から 登録します(鍵は暗号化して保存され、画面にも API にも出てきません)。

SECRET_KEY は次のように作れます。

python3 -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())"

安全のための作り

  • カードは読み取り専用でしか開きません。 書き込む経路がありません
  • マウントする側とアプリを別のコンテナに分けています。 特権が要るのはマウント側 だけで、アプリ側は権限を落とし、ファイルシステムも読み取り専用で動きます
  • アプリに渡すのは、切り離したツリー由来のディレクトリだけです。 通常のマウントを 渡すと、そこから親ディレクトリへ抜けられてしまうため(実際に抜けられることを 確認したうえでこの方式にしています)
  • カードの判別は、中身を確かめてから確定します。 USB の ID だけでは確定しません (同じ ID の別機種や、他人のカードを誤って取り込む経路になるため)
  • 同じデータの置き場所を 2 つのプロセスに持たせません。 後から起動した側は、 移行も片付けも走らせる前に止まります(重なると、動いている取り込みを「中断」に 倒して作業用ディレクトリを消してしまうため)
  • Immich にある写真の撮影日時を書き換えるときは、自分が上げたと確かめられた場合を 除いて承認を求めます。 タグを既存の写真にも付けるかどうかは、カメラの種類ごとに 選べます
  • Immich から受け取った識別子は、保存する前と使う前の両方で検めます。 侵害された サーバが API キーを識別子として返しても保存されません
  • API キーは暗号化して保存し、画面・API 応答・ログ・エラーメッセージのどこにも出しません

困ったとき

よくあるものは使い方ガイドの「困ったとき」に まとめてあります。分かっていてまだ直していないことは 既知の制限にあります。

  • カードが「対象外」と出る → 「接続中のカード」に判別できなかった理由が出ます
  • 送信が始まらない → 送り先が休止中・退役済みでないか、SECRET_KEY があるかを確認
  • 起動しない → タイムゾーンが要るカメラの種類を使っていて DEFAULT_TIMEZONE が 未設定だと、取り込みを始める前に止めます(誤った時刻で確定させないため)
  • 「同じデータの置き場所を別のプロセスが使っている」と出て起動しない → 同じデータセットを見るアプリが既に動いています。 入れ替えのときは、古い方が 完全に止まってから新しい方を起動してください

ライセンス

Apache License 2.0(LICENSE)


開発する

開発の入口は docs/development.md です(環境の癖、開発 コマンド、テストの作法)。

uv sync --all-packages   # --all-packages が必須(素の sync ではメンバーが入りません)
uv run pytest            # 1,782 件
uv run ruff check . && uv run ruff format --check .

npm --prefix web ci
npm --prefix web test         # 670 件
npm --prefix web run test:e2e # 21 件。実プロセス + 偽ブローカー + 偽 Immich

npm run test:e2e は既定の検査に入っていません。単独で回してください。

既定の pytest から外してあるものが 2 つあります。

uv run pytest -m needs_root     # ユーザ名前空間が使える環境でのみ(1 件)
uv run pytest -m needs_immich   # 実 Immich が要る。書き込むので捨ててよい実例で(4 件)

イメージ

main への push と v* のタグで ghcr.io へ公開されます(latest が動くのは タグのときだけ)。手元でビルドするときはリポジトリの根を文脈にしてください。

docker build -f app/Dockerfile -t mediaferry .
docker build -f mountd/Dockerfile -t mediaferry-mountd .

構成

パッケージ 役割
protocol/ 2 つのコンテナが共有するソケットプロトコル
mountd/ 特権側。USB を読み取り専用でマウントし、切り離したツリーを渡す
app/ 非特権側。取り込み・結合・アップロード・Web UI
web/ 画面(React + TypeScript)

ドキュメントの地図

知りたいこと 見る場所
導入したい docs/setup.md
使い方 docs/user-guide.md
バックアップ docs/backup.md
分かっている制限 docs/known-issues.md
いまどうなっているか(仕様) docs/design.md
なぜそうなっているか(判断と理由) docs/decisions.md
開発する docs/development.md・CLAUDE.md
どう作ったか(実装計画、レビュー記録、実機検証) docs/history/
前提を実機で確かめたい tools/

docs/history/ は開発の記録です。 現在の姿を知るのに読む必要はありません (食い違ったら design.md と decisions.md が正です)。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages