Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
*.yml text eol=lf
*.yaml text eol=lf
*.sh text eol=lf
*.lua text eol=lf
*.jsonl text eol=lf
*.xmp text eol=lf
*.gpx text eol=lf
*.png binary
*.jpg binary
*.jpeg binary

6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ jobs:
xcodebuild -version
swift --version

- name: Install Lightroom-compatible Lua test runtime
run: |
brew install micromamba
micromamba create -y -p "$RUNNER_TEMP/rawgeosync-lua51" -c conda-forge lua=5.1
echo "RAWGEOSYNC_LUA=$RUNNER_TEMP/rawgeosync-lua51/bin/lua" >> "$GITHUB_ENV"

- name: Full quality gate
run: ./Scripts/ci.sh

Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ xcuserdata/
*.log
*.env

# Generated location bridge manifests contain private paths and coordinates.
RawGeoSync.locations.jsonl
!**/Tests/Fixtures/RawGeoSync.locations.jsonl
!**/Tests/Fixtures/**/RawGeoSync.locations.jsonl

# User photographs and tracks never belong in source control.
*.nef
*.NEF
Expand All @@ -33,4 +38,3 @@ xcuserdata/
# Synthetic fixtures are explicitly allowed.
!**/Tests/Fixtures/*.gpx
!**/Tests/Fixtures/**/*.gpx

22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

本文件记录面向用户和贡献者的重要变化。版本遵循语义化版本的意图;在 1.0.0 前,MVP 的行为和界面仍可能调整。

## [0.3.0] - 2026-08-12

### 新增

- 新增默认的 Lightroom Classic Catalog Bridge:每个照片根目录只生成一份可校验的位置清单,由纯 Lua 插件批量写入 Catalog。
- Lightroom 插件提供导入预检、精确路径和身份核验、批量复读、离线跳过、来源标记与跨重启整批撤销。
- App 提供插件一键安装/更新,并继续提供传统 XMP Sidecar 兼容模式。

### 性能

- 桥接导出不再执行逐 RAW 完整摘要、逐 XMP ExifTool 复读或逐照片事务清单重写,千张和万张任务按 O(N) 流式处理。

### 隐私

- 位置清单只保存照片根目录相对路径,不保存绝对路径、GPX 路径或完整轨迹;插件无网络且不在照片目录写日志。

### 修复

- GPX 输入现在同时支持通过文件选择器选择、或直接拖入单个 `.gpx` 文件;原有的 GPX 目录递归读取仍然保留。
- 修复 Lightroom 真机环境中普通 `pcall/xpcall` 阻止任务让出,以及目录写锁竞争未等待导致批量导入失败的问题。
- 撤销收据新增 Finder 管理入口;插件不会在未确认的情况下自动删除敏感恢复数据。

## [0.2.0] - 2026-08-10

### 新增
Expand Down
9 changes: 5 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# 参与贡献

感谢你关注 RawGeoSync。项目目前以 macOS 本机离线处理和个人照片工作流为目标,外部贡献应先确认不会改变 RAW 只读、XMP sidecar 输出和隐私边界
感谢你关注 RawGeoSync。项目目前以 macOS 本机离线处理和个人照片工作流为目标,外部贡献应先确认不会改变 RAW 只读、Lightroom Catalog Bridge、XMP 兼容输出和隐私边界

## 开发环境

- macOS 15 或更高版本
- Xcode 26.3 或兼容的 Swift 6 工具链
- 系统 Perl 仅用于运行随项目锁定的 ExifTool 13.59
- Swift 依赖只使用本地 Swift Package,不提交 Homebrew 或 Conda 环境
- Swift 依赖只使用本地 Swift Package;Lua 测试环境放在被忽略的项目 `.local/` conda 环境,不修改 base

首次开发前,确认 `xcode-select -p` 指向完整 Xcode,而不是只安装 Command Line Tools。

Expand Down Expand Up @@ -36,8 +36,9 @@ RawGeoSync 不上传照片、轨迹或坐标。新增网络请求、遥测、反
## 元数据边界

- RAW 文件永远不能作为写入目标。
- GPS 写入只能通过同名 XMP sidecar 完成。
- 不要修改 `DateTimeOriginal`,也不要未经用户确认覆盖已有 GPS。
- 默认 GPS 写入通过版本化清单和 Lightroom 插件完成;兼容模式才写同名 XMP sidecar。
- 不要修改 `DateTimeOriginal`。Catalog Bridge 按用户已锁定策略以本次清单覆盖不同 GPS,但必须提供预览和可恢复的整批撤销。
- 禁止 basename 模糊匹配、直接访问 `.lrcat` SQLite、在插件中启动网络/shell/ExifTool/Python,或把绝对照片路径写入清单。
- 更新 ExifTool 时必须同步版本清单、归档 SHA-256、上游许可证说明和真实契约测试。

## Pull Request 检查清单
Expand Down
52 changes: 52 additions & 0 deletions Docs/Decisions/0003-lightroom-catalog-bridge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ADR 0003:以 Lightroom Catalog Bridge 作为默认输出

- 状态:已接受
- 日期:2026-08-12
- 取代范围:补充 ADR 0001;不移除其 XMP 安全边界

## 背景

逐照片 XMP 在大批量任务中同时带来两个问题:照片目录文件数量翻倍,以及写入前后对每张 RAW、XMP 和事务清单进行校验所造成的高延迟。现有 XMP 实现仍适合作为与 Lightroom Catalog 无关的兼容输出,但不适合作为数千张照片的默认路径。

Lightroom Classic SDK 允许插件在 Catalog 写事务内更新原生 GPS 和海拔,而无需改写 RAW。用户已决定默认使用 Catalog Bridge、保留 XMP 兼容模式;桥接清单只包含在 RawGeoSync 中明确勾选且具有最终坐标的照片。

## 决策

RawGeoSync 默认在用户选择的照片根目录生成唯一的 `RawGeoSync.locations.jsonl`。清单使用 UTF-8 JSON Lines,由 header、按相对路径稳定排序的 asset records 和 trailer 组成,并使用 SHA-256 校验记录与整体 payload。

清单只保存相对路径和完成目录身份核验所需的最少信息;禁止绝对路径、basename 模糊匹配、路径大小写归一化和完整 RAW SHA。写入通过同目录临时文件和原子替换完成;相同语义重复导出不改动文件。

配套的纯 Lua Lightroom Classic 插件负责:

- 流式验证清单并按 `manifest parent + relativePath` 精确查找 Catalog 照片;
- 在写入前展示可写、相同、覆盖、离线、缺失和身份冲突数量;
- 默认处理清单全部记录,也允许限制为 Lightroom 当前选择;
- 按用户选择,以新清单覆盖 Catalog 中不同的 GPS;
- 跳过离线或身份无法核验的照片;
- 写入原生 GPS、可选海拔和不含明文坐标的来源 token;
- 批量复读验证,并提供跨 Lightroom 重启仍可用的安全整批撤销。

插件不得解析 GPX、运行 ExifTool/Python/shell、启动网络服务、直接访问 `.lrcat` SQLite,或在照片目录写日志和撤销记录。

现有 XMP 输出继续存在,但桥接实现不得复用 XMP transaction 的逐 RAW 摘要、逐 sidecar 备份/复读和逐条事务清单保存循环。桥接结果只能称为“清单已生成”;只有插件完成 Catalog 复读后才能称为“GPS 已应用并验证”。

## 数据与隐私

清单包含精确位置,权限设为仅当前用户,Git 默认忽略。它不包含绝对照片路径、GPX 路径或完整轨迹。插件日志默认只记录匿名标识、数量和错误类型。

撤销收据位于用户的 Lightroom Application Support 范围,不进入照片目录。收据保存恢复所需的原 GPS/海拔和摘要,因此同样视为敏感数据;清理必须由用户显式执行。

## 兼容边界

- 首发支持 macOS 15 及 Lightroom Classic 15.4.1 或更高版本。
- 不支持云端版 Lightroom。
- 插件无法通过公开 SDK 可靠读取 Lightroom 的“自动写入 XMP”设置,因此始终提示:启用该选项时,Lightroom 自身仍可能创建 sidecar。
- 已存在的 XMP 不自动删除或迁移。
- 清单没有海拔时保留 Catalog 现有海拔;只有清单明确提供海拔才更新。

## 验收门禁

- Auto XMP 关闭时,大批量任务在照片根目录只增加一份清单,RAW 内容与 mtime 不变。
- 清单导出、插件预检、写入和复读的复杂度为 O(N),不读取完整 RAW 内容。
- 取消或意外失败最终全成或全退;显式撤销不覆盖导入后被用户再次修改的坐标。
- 同名跨目录照片、Unicode 路径、活动目录整体移动、离线照片和损坏清单均有自动或真机测试。
69 changes: 69 additions & 0 deletions Docs/LIGHTROOM_BRIDGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Lightroom Catalog Bridge 使用指南

Catalog Bridge 是 RawGeoSync 的默认输出方式。它让一个照片根目录只增加一份
`RawGeoSync.locations.jsonl`,再由 Lightroom Classic 插件把其中已确认的位置批量写入当前目录。
RAW 文件始终只读;插件也不会直接修改 `.lrcat` 数据库文件。

## 第一次使用

1. 启动 RawGeoSync,保留默认输出“Lightroom Classic 单清单”。
2. 选择或拖入一个 `.gpx` 文件,也可以选择包含多个 GPX 的目录。
3. 选择照片活动根目录。建议选择同时包含 `Z50`、`Z5` 等相机子目录的活动目录,而不是更高层的整个照片库。
4. 完成分析,复核黄色和粗略候选,并勾选本次要交给 Lightroom 的照片。
5. 点击生成清单。照片根目录只会创建或原子更新一个 `RawGeoSync.locations.jsonl`。
6. 在结果页点击“安装/更新 Lightroom 插件”。如果 Lightroom 正在运行,安装后重启一次 Lightroom。
7. 先把对应 RAW 导入 Lightroom Classic,再选择菜单“图库 → 插件增效工具 → RawGeoSync:导入位置清单…”。
8. 选择刚生成的清单,检查预检数量后开始导入。插件默认处理清单全部照片;需要时可限制为 Lightroom 当前选择。

插件按清单父目录和照片相对路径精确查找,不使用文件名模糊匹配。已有不同 GPS 会在预检中明确计数,并由本次已确认清单覆盖;相同坐标跳过,原文件离线、路径缺失或字节数不一致的照片安全跳过。

## 撤销与恢复

一次成功导入会形成 Lightroom 原生撤销记录,同时把恢复所需的敏感事务收据保存在 Lightroom 的 Application Support 目录,而不是照片目录。

- 刚导入后可以使用 Lightroom 的“撤销”;大批任务按 200 张提交,系统撤销可能按批次出现,插件菜单才是整批恢复入口。
- Lightroom 重启后,使用“图库 → 插件增效工具 → RawGeoSync:撤销最近一次导入…”。
- 如果照片的 GPS 在导入后又被用户或其他插件修改,持久撤销会跳过该照片,不覆盖较新的工作。
- 导入中取消、写入失败或复读不一致时,插件自动恢复此前已经提交的批次;若自动恢复受到外部并发修改阻止,收据仍可用于安全补救。

事务收据包含原 GPS 和恢复信息,应和 Catalog 备份一样视为敏感本地数据。不要上传或纳入 Git。

不再需要历史撤销时,可运行“图库 → 插件增效工具 → RawGeoSync:打开撤销收据文件夹…”,在 Finder 中手工清理旧 `.jsonl` 收据。删除收据后无法再用插件恢复对应批次,因此插件不会自动清理。

## 关于 XMP

插件只调用 Lightroom SDK 写入 Catalog。若 Lightroom 偏好设置中启用了“自动将更改写入 XMP”,Lightroom 自身仍可能生成 sidecar;公开 SDK 无法可靠读取或关闭这个选项。希望照片目录始终只有单一清单时,请先在 Lightroom 的“目录设置 → 元数据”中关闭自动写入 XMP。

已有 XMP 不会被自动删除。需要绕过 Lightroom Catalog、与其他软件交换元数据时,可以在 RawGeoSync 输出选项中切回“XMP Sidecar(兼容模式)”。

## 日常启动与更新

日常可从 Finder、Spotlight 或启动台打开 `RawGeoSync.app`,也可以运行:

```sh
open "$HOME/Applications/RawGeoSync.app"
```

源码更新后,在项目目录运行 `./Scripts/build-release.sh` 会重新构建并安全替换本机应用。插件有更新时,打开应用后再次点击“安装/更新 Lightroom 插件”,然后重启 Lightroom。

## 常见问题

### 清单中的照片显示“不在当前目录”

先确认 RAW 已导入当前 Lightroom Catalog。若移动了整个活动目录,先在 Lightroom 使用“查找丢失的文件夹”或“更新文件夹位置”指向新路径,并让清单随目录一起移动;若单独改了 RAW 文件名或目录层级,请回到 RawGeoSync 重新分析并生成清单。

### 导入后地图或元数据面板没有立即刷新

插件会直接复读 Catalog 元数据验证写入。若结果页显示验证成功但界面仍旧,切换照片或重启 Lightroom 后再看;界面缓存不能替代插件复读结果。

### 为什么离线照片不写入

离线状态下只能依赖 Catalog 路径和智能预览,无法核验磁盘文件身份。为避免把坐标写给错误照片,当前版本默认跳过,待原文件联机后重新导入清单即可。

### 清单是否可以分享

不建议。清单包含精确坐标、照片相对路径、拍摄时间、相机型号、可能的机身/内部序列号、快门数、匹配证据摘要及轨迹摘要。权限仅授予当前用户读取;它不上传网络,但仍应按敏感位置与设备身份数据保护。

### 不通过 App,怎样安装独立插件 ZIP

解压 `RawGeoSync-Lightroom-Bridge-<version>.zip`,在 Lightroom 的“文件 → 增效工具管理器…”中点击“添加”,选择解压后的 `RawGeoSync.lrplugin`。确认状态为已安装且版本为 0.3.x 后重启 Lightroom。不要把 Lightroom 指向 ZIP 文件本身。
9 changes: 7 additions & 2 deletions Docs/PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ RawGeoSync 处理照片、拍摄时间和精确位置,这些数据可以还原
| RAW 与照片 | 用户选择的目录 | 读取拍摄时间和已有元数据 | 不复制、不修改 |
| GPX | 用户选择的文件或目录 | 建立时间到位置的候选 | 不保存完整轨迹副本 |
| XMP sidecar | 照片同目录 | 预检、合并和写入 GPS | 用户确认后创建或更新 |
| 位置桥接清单 | 照片根目录 | 把已批准的位置交给 Lightroom 插件 | 默认保留一份,后续导出原子替换 |
| Lightroom Catalog | 用户当前打开的目录 | 保存原生 GPS、海拔和来源 token | 由 Lightroom 管理 |
| 插件撤销收据 | Lightroom Application Support | 跨重启整批恢复 Catalog GPS | 通过“打开撤销收据文件夹…”由用户显式清理 |
| 匹配证据 | 内存 | 解释候选与冲突 | 未写入的分析默认不保存;已写入项在事务清单保留必要证据 |
| 原 XMP 备份 | Application Support | 撤销和崩溃恢复 | 默认最近 10 批且不超过 30 天 |
| 偏好 | UserDefaults | 时区、相机偏移等设置 | 不包含完整轨迹或照片内容 |

RAW 始终只读。分析和 dry-run 不得在 GPX 或照片源目录创建临时文件、缓存、XMP、索引或隐藏文件。写入阶段只允许修改同名 XMP sidecar,并必须经过不可变计划、原子替换和复读验证。
RAW 始终只读。分析和 dry-run 不得在 GPX 或照片源目录创建临时文件、缓存、XMP、索引或隐藏文件。默认导出阶段只允许原子创建或替换一份 `RawGeoSync.locations.jsonl`;兼容模式才允许修改同名 XMP sidecar。

位置清单包含精确坐标和相对照片路径,属于敏感本地数据。它不保存绝对路径、GPX 路径或完整轨迹,文件权限仅限当前用户。Lightroom 插件不联网、不回写清单,也不在照片目录创建日志或撤销记录。

事务清单为完成撤销、幂等和来源审计,可能包含本地文件引用、GPS、指纹和已选证据;它与 XMP 备份同属敏感本地数据,受相同保留和清理策略约束,不得作为普通诊断日志分享。

Expand All @@ -40,4 +45,4 @@ RAW 始终只读。分析和 dry-run 不得在 GPX 或照片源目录创建临

## 删除、撤销与分享

用户可以撤销仍未被外部程序修改的事务,并可删除 Application Support 中的历史备份。清理备份不会删除原始照片或用户主动保留的 XMP。若怀疑真实位置或照片被误提交,应立即停止分享、从当前分支移除数据并按安全策略报告;仅新增 `.gitignore` 不能清除既有 Git 历史。
用户可以撤销仍未被外部程序修改的 XMP 或 Lightroom Catalog 事务,并可在相应界面删除 Application Support 中的历史备份。清理备份不会删除原始照片、位置清单或用户主动保留的 XMP。若怀疑真实位置或照片被误提交,应立即停止分享、从当前分支移除数据并按安全策略报告;仅新增 `.gitignore` 不能清除既有 Git 历史。
24 changes: 21 additions & 3 deletions Docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,32 @@

Mac App Store 不是当前 MVP 目标。若未来进入 Mac App Store,需要重新设计 App Sandbox、用户选定目录权限、ExifTool helper 和崩溃恢复流程。

## v0.3 Catalog Bridge 门禁

1. 确认 [ADR 0003](Decisions/0003-lightroom-catalog-bridge.md) 与 JSONL schema、App 默认输出和插件实现一致。
2. Swift 与 Lua 共享 golden fixtures 全部通过,仓库策略已扫描 `.lua`,真实生成清单保持 Git ignored。
3. 在独立 Lightroom Catalog 上完成 20 张完整功能样本和 100 张五批次真机样本;完成 1000、5000、10000 条合成复杂度验收。不得使用正式 Catalog 或照片原件。
4. Auto XMP 关闭时证明 RAW 目录只增加一份清单,RAW 内容和 mtime 不变;开启时验证警告与 Lightroom 实际行为一致。
5. 验证原生 Undo、跨重启插件撤销、取消自动回滚、后续编辑冲突保护和重复导入 no-op。
6. 确认插件 ZIP 与 App 内置插件的版本、schema major/minor 和 SHA-256 一致。
7. 发布物同时包含 App ZIP、`RawGeoSync-Lightroom-Bridge-<version>.zip`、校验文件和安装/日常使用说明。

本机构建上述三份发布物:

```sh
./Scripts/package-release.sh 0.3.0
```

输出位于被 Git 忽略的 `.local/release/v0.3.0/`。脚本会核对 App 版本、内置插件与独立插件逐文件一致,再生成 `SHA256SUMS.txt`。

## 发布前数据安全验收

- 分析阶段不产生照片目录写入;
- 写入只创建或更新同名 XMP,NEF SHA-256 不变;
- 已有不同 GPS 默认跳过
- 默认桥接只创建或原子替换单一位置清单;兼容模式才创建或更新同名 XMP,RAW SHA-256 不变;
- Catalog 中已有不同 GPS 会在预览明确计数并按本次清单覆盖;插件撤销收据必须先成功持久化
- 重复运行识别为 already-applied 且不改变 XMP mtime;
- 取消、单项失败和崩溃不会留下半写 XMP;
- 撤销遇到后续 Lightroom 修改时必须拒绝覆盖;
- XMP 和 Catalog 撤销遇到后续 Lightroom 修改时必须拒绝覆盖;
- 强候选冲突、传播循环和跨活动区候选不能自动写入;
- 源无 hacc 时界面和报告都显示 unknown,不生成伪精度;
- 全量 dry-run 的输入目录前后 SHA-256 清单完全一致。
Expand Down
Loading
Loading