Files
photochk/README.md
T

104 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Photochk
フォルダの中から、中身が同じ写真と見た目が似ている写真を探して一覧にする macOS アプリ。
同じ写真のコピー、リサイズ版、メッセージアプリ経由で再圧縮された版、連写のほぼ同じカット —
ファイル名もサイズも違うので Finder では見つからないものを、グループにまとめて見せる。
残す1枚を選んで、ほかをまとめてゴミ箱へ送れる(削除ではないので Finder から戻せる)。
## 使い方
```bash
make run
```
`make build` でビルドのみ、`make test` でテスト。生成物は `build/Build/Products/Debug/Photochk.app`。
配布用に固めるなら `make release`(Release ビルド+`dist/Photochk-1.0.0.zip`)、
`/Applications` に入れるなら `make install`。
> ビルドは ad-hoc 署名(Developer ID 証明書なし)。自分の Mac でビルドして使う分には問題ないが、
> zip を他の Mac に配ると Gatekeeper に止められる。受け取った側は右クリック →「開く」で起動できる。
1. ツールバーの「フォルダを選ぶ」で対象フォルダを指定する
2. 左のグループ一覧から選ぶと、右にそのグループの全枚数が並ぶ
3. 残す1枚以外にチェックを入れる(「一括選択」メニューで「解像度が最も高い1枚を残す」なども選べる)
4. 「N 枚をゴミ箱へ」で移動する
グループから全部を選ぶことはできない。必ず1枚は残る。
各写真には基準(代表)からの距離が出る。代表以外にもバイト単位で中身が同じファイルがある場合は
「同一 A」のように同じ印が付くので、その中のどれを消しても失われるものがないと分かる。
## 検出のしくみ
安い判定から順にかけて候補を絞り込む。
1. **完全同一** — ファイルサイズが同じものだけ SHA-256 を計算して、バイト単位の重複を確定する
2. **知覚ハッシュ (dHash)** — 9×8 のグレースケールに落として隣接ピクセルの明暗を比べ、64bit のハッシュを作る。
ハミング距離が近いペアだけを次に回す
3. **Vision の feature print** — 候補ペアに出てくる画像だけ `GenerateImageFeaturePrintRequest` で
特徴量を計算し、距離で最終判定する
4. **Union-Find** — 「同一」「類似」のペアから連結成分を作ってグループにする
各グループの代表は「解像度が最大 → ファイルサイズが最大 → 撮影が最古」で自動的に選ばれる。
判定の厳しさは3段階。しきい値は実写画像で距離を測って決めた([docs/calibration.md](docs/calibration.md))。
| 段階 | 何が拾えるか |
|---|---|
| 厳しい | コピー・縮小(25% まで)・再圧縮 |
| 標準 | 上記+連写・同一シーンの近いカット |
| ゆるい | 上記+トリミングされた版 |
対応形式は UTType が画像に該当するものすべて。JPEG / PNG / HEIC / TIFF に加えて各社の RAW も扱える。
### 分かっている制限
- **向きが違う写真は別物として扱う。** EXIF の向きを反映した「見た目」で比較しているため、
回転が焼き込まれた版と EXIF で回している版は同じ写真として検出できるが、
実際に 90 度違う向きの写真どうしは検出しない(4方向ぶんの特徴量が必要でコストが4倍になるため)
- 2回目以降のスキャンは `~/Library/Caches/Photochk/fingerprints.plist` のキャッシュで速くなる。
ファイルサイズと更新日時が変わっていれば自動で計算し直す
## 構成
```
Sources/Photochk/
├── Models/ PhotoAsset, SimilarityGroup, ScanSettings
├── Core/ 走査・ハッシュ・特徴量・グルーピング(UI 非依存)
├── Services/ サムネイル生成、ゴミ箱、フォルダのアクセス権
├── ViewModels/ ScanViewModel
└── Views/ SwiftUI
```
外部依存なし。SwiftUI / Vision / CryptoKit / ImageIO / QuickLookThumbnailing のみ。
App Sandbox 有効で、アクセスできるのはユーザーが選んだフォルダだけ。
## 開発
Xcode プロジェクトは XcodeGen で `project.yml` から生成する(リポジトリには含めない)。
```bash
make generate && open Photochk.xcodeproj
```
必要なもの: macOS 15 以降、Xcode 26 以降、`brew install xcodegen`。
### リリースの更新
`tools/release-update.sh` が Gitea のリリースを差し替える(yfoxlogsrv と同じ流儀)。
```bash
make release && ./tools/release-update.sh
```
タグを HEAD へ移動して push し、リリースの添付 zip を最新ビルドに入れ替え、本文の
「ビルド元コミット」を更新する。認証は `git credential`(host=git.yfox.jp)から取るので、
未登録なら実行時に登録コマンドが表示される。リリース自体が未作成の場合はエラーで止まるので、
最初の1本だけ web か gitea MCP の `create_release` で作る。
`Tests/PhotochkTests/SnapshotTests.swift` は `ImageRenderer` で写真タイルなどを実際に描画し、
PNG を一時フォルダに書き出す(パスはテスト出力に出る)。レイアウトを変えたら開いて目で確認できる。
`List` や `NavigationSplitView` は AppKit 由来で `ImageRenderer` では描けないため対象外。