这是一个面向摄影工作流的自动化处理工具集,目前提供两大核心功能:
- GPS 修正 (
geotag):在照片从相机同步到电脑后,结合移动设备导出的GPX轨迹,为照片批量补充写入 GPS 坐标。 - 照片归档整理 (
organize-by-date):根据照片元数据中的拍摄日期,对照片及其配套文件(RAW、JPG、XMP 等)进行按日期的归档与规范化重命名。
项目当前采用 Go CLI + exiftool 的方式工作,重点解决“导入电脑后的元数据补充与文件整理”环节,不扩展成完整的 DAM 或后期管理系统。
- 将同 basename 的配套文件视为一个拍摄单元,至少包含
RAW + JPG,并可带XMP、ACR、WAV等 companion files。 - 以
RAW作为元数据权威源。 - 使用照片自身的
DateTimeOriginal + OffsetTimeOriginal与GPX轨迹进行匹配。 - 先为
RAW写入 GPS,再把GPS:all同步到同名JPG,并在存在XMP时同步到XMP。 - 按照片原始拍摄日期归档到
Processed/YYYY/MMDD/。 - 所有用户可见日志和输出统一使用中文。
GPS/
├── cmd/photools/ # 统一 CLI 工具入口
├── internal/ # 内部核心逻辑
│ ├── exiftool/ # ExifTool 交互与元数据操作
│ ├── geotag/ # GPS 修正业务逻辑
│ └── organizer/ # 资产发现与归档整理逻辑
├── Inbox/ # 相机同步到电脑后的待处理照片
├── GPX/ # 移动设备导出的轨迹文件
├── Processed/ # 按拍摄日期归档后的照片
├── Logs/ # 中文日志
└── CORE_CONSTRAINTS.md # 项目核心约束
- Go
1.26.2 - ExifTool
先确认命令可用:
go version
exiftool -ver- 把待处理照片放进
Inbox/ - 把轨迹文件放进
GPX/
工具默认使用基础目录:
$HOME/Pictures/GPS
默认运行:
go run ./cmd/photools geotag按拍摄日期整理指定目录:
go run ./cmd/photools organize-by-date -source-dir /path/to/source -target-dir /path/to/output指定基础目录:
go run ./cmd/photools geotag -base-dir /Users/你的用户名/Pictures/GPS指定时间偏移:
go run ./cmd/photools geotag -geosync +00:00:05指定 RAW 扩展名集合:
go run ./cmd/photools geotag -raw-exts nef,cr3,arw,dng指定并发处理数:
go run ./cmd/photools geotag -workers 4- 同 basename 的文件会被识别为一个拍摄单元。
RAW + JPG是进入 GPS 修正流程的最小核心组合。- 只有
RAW没有JPG时,任务会记录“等待配对 JPG”,不会提前归档。 - 只有
JPG没有RAW时,任务会跳过,不单独处理。 - 同 basename 的
XMP、ACR、WAV等 companion files 会和主文件一起归档。
Inbox中的资产组会按 basename 分组后并发处理。- 并发粒度是“拍摄单元”,不是单个文件。
- 同一拍摄单元内部仍按固定顺序执行:
RAW->JPG->XMP-> 归档。 - 可通过
-workers控制并发数,默认使用当前机器的 CPU 核心数。
- 默认信任照片中的
DateTimeOriginal和OffsetTimeOriginal。 - 默认
geosync=0。 - 如相机时钟有误差,可通过
-geosync传入偏移值。 - 多个
GPX文件会逐个传给exiftool,不会拼成单一参数。
- GPS 先写入
RAW。 - 工具不会只看
exiftool退出码,还会二次校验RAW是否真的出现GPSPosition。 RAW成功后,再把GPS:all同步到JPG。- 如果同 basename 存在
XMP,会继续把 GPS 同步到XMP。 - 同步后会再次校验
JPG和XMP是否存在有效 GPS。 - 如果照片时间在当前
GPX中找不到可用插值,整组文件会继续保留在Inbox/,不会被错误归档。
成功处理后,同 basename 的整组文件将被提取拍摄日期,进行规范化重命名,并归档到 Processed/YYYY/MMDD/ 目录下。
重命名规则会在原文件编号前自动插入 YYYY-MM-DD 格式的日期。扩展名通常保持不变(视具体动作而定)。
归档成果示例:
假设 Inbox/ 有两组拍摄于 2025-10-06 的资产,执行整理或 geotag 后,你的 Processed/ 目录将呈现如下结构:
Processed/
└── 2025/
└── 1006/
├── DSC_2025-10-06_1010.NEF # 主 RAW 文件
├── DSC_2025-10-06_1010.JPG # 同步过 GPS 的 JPG
├── DSC_2025-10-06_1010.xmp # 附加的 XMP 配置文件
├── DSC_2025-10-06_1011_edit.NEF # 带有自定义后缀的文件
└── DSC_2025-10-06_1011_edit.JPG
organize-by-date是独立子命令,不依赖Inbox/GPX/Processed这套 GPS 修正目录结构。- 它会遍历
-source-dir下的全部文件,并按 basename 聚合。 - 每组文件会尝试找到一个带
DateTimeOriginal的照片文件作为锚点。 - 找到后,会把这组文件整体移动到
-target-dir/YYYY/MMDD/。 - 这个命令按顺序处理,不启用并发移动。
- 如果一组文件里没有任何文件能读出
DateTimeOriginal,这组文件会保留在原目录,并生成待处理清单。
日志文件路径:
Logs/geotag.log
待处理清单路径:
Logs/inbox_pending_report_latest.md
按拍摄日期整理命令的日志和待处理清单路径:
<target-dir>/_organize_logs/organize_by_date.log
<target-dir>/_organize_logs/organize_pending_report_latest.md
终端输出和日志文件语义一致,均为中文。常见信息包括:
开始处理已加载 GPX 轨迹文件发现未配对的 JPG,暂不处理等待配对 JPG照片时区RAW 的 GPS 写入成功RAW 的 GPS 坐标未变化已同步 GPS 到 JPG已归档到处理失败
如果本次运行中有文件因为缺少配对、轨迹未命中或其他原因被保留在 Inbox/,工具会生成一份待处理清单,至少包含:
- 资产 basename
- 失败或待补原因
- 建议动作
- 涉及的 companion files
- 拍摄时间和时区
- 本次参与匹配的
GPX文件列表
运行测试:
go test ./...这个项目当前明确不做以下事情:
- 不做完整的摄影资产管理系统
- 不把
JPG当作与RAW同级的权威元数据源 - 不以内嵌 EXIF 解析重写
exiftool
如果后续要扩展新能力,请先阅读 CORE_CONSTRAINTS.md。
This is an automated processing toolkit for photography workflows, currently providing two core functions:
- GPS Correction (
geotag): After syncing photos from camera to computer, batch write GPS information to photos usingGPXtracks exported from mobile devices. - Photo Archive & Organization (
organize-by-date): Automatically archive and rename photos and their companion files (RAW, JPG, XMP, etc.) based on the shooting date in the metadata.
The project currently uses Go CLI + exiftool, focusing on "metadata enrichment and file organization after import," and does not intend to expand into a full DAM or post-processing system.
- Treat companion files with the same basename as a single "shooting unit" (at least
RAW + JPG, plusXMP,ACR,WAV, etc.). - Use
RAWas the authoritative source of metadata. - Match
GPXtracks using the photo'sDateTimeOriginal + OffsetTimeOriginal. - Write GPS to
RAWfirst, then syncGPS:allto the same-namedJPG, and toXMPif it exists. - Archive photos to
Processed/YYYY/MMDD/based on the original shooting date. - All user-visible logs and output are unified in Chinese (technical labels remain English).
GPS/
├── cmd/photools/ # Unified CLI entry
├── internal/ # Internal core logic
│ ├── exiftool/ # ExifTool interaction & metadata ops
│ ├── geotag/ # GPS correction business logic
│ └── organizer/ # Asset discovery & archive logic
├── Inbox/ # Pending photos from camera
├── GPX/ # Track files from mobile devices
├── Processed/ # Photos archived by date
├── Logs/ # Chinese logs
└── CORE_CONSTRAINTS.md # Core project constraints
- Go
1.26.2 - ExifTool
Verify commands:
go version
exiftool -ver- Place pending photos in
Inbox/ - Place track files in
GPX/
The tool uses the base directory by default:
$HOME/Pictures/GPS
Default run:
go run ./cmd/photools geotagOrganize specific directory by date:
go run ./cmd/photools organize-by-date -source-dir /path/to/source -target-dir /path/to/outputSpecify base directory:
go run ./cmd/photools geotag -base-dir /Users/username/Pictures/GPSSpecify time offset:
go run ./cmd/photools geotag -geosync +00:00:05Specify RAW extensions:
go run ./cmd/photools geotag -raw-exts nef,cr3,arw,dngSpecify concurrent workers:
go run ./cmd/photools geotag -workers 4- Files with the same basename are identified as one shooting unit.
RAW + JPGis the minimum core combination for the GPS correction process.- If
RAWis missing itsJPG, the task records "waiting for JPG" and won't archive early. - If
JPGis missing itsRAW, the task is skipped (not processed alone). - Companion files (XMP, ACR, WAV, etc.) move with the primary files.
- Asset groups in
Inboxare processed concurrently by basename. - The unit of concurrency is the "shooting unit," not individual files.
- Order within a unit is fixed:
RAW->JPG->XMP-> Archive. - Concurrent workers can be controlled via
-workers(default is CPU core count).
- Trust
DateTimeOriginalandOffsetTimeOriginalby default. - Default
geosync=0. - Camera clock drift can be compensated using
-geosync. - Multiple
GPXfiles are passed toexiftoolindividually.
- GPS is written to
RAWfirst. - The tool validates
GPSPositionpresence after the command finishes. - After
RAWsuccess, syncGPS:alltoJPG. - If
XMPexists, sync GPS toXMPas well. - Re-validate
JPGandXMPafter syncing. - Photos outside track ranges stay in
Inbox/and aren't archived.
After successful processing, files are renamed and archived to Processed/YYYY/MMDD/.
Renaming automatically inserts YYYY-MM-DD before the original number. Extensions typically remain unchanged (subject to specific action).
Archive Example:
If Inbox/ has two units shot on 2025-10-06, after processing:
Processed/
└── 2025/
└── 1006/
├── DSC_2025-10-06_1010.NEF # Primary RAW
├── DSC_2025-10-06_1010.JPG # Synced JPG
├── DSC_2025-10-06_1010.xmp # Sidecar XMP
├── DSC_2025-10-06_1011_edit.NEF # Custom suffix
└── DSC_2025-10-06_1011_edit.JPG
organize-by-dateis an independent subcommand.- It scans
-source-dirand groups by basename. - Attempts to find a
DateTimeOriginalanchor in the group. - Moves the entire group to
-target-dir/YYYY/MMDD/. - Processed sequentially (no concurrency for this command).
Log paths:
Logs/geotag.log
Logs/inbox_pending_report_latest.md
Organization command logs:
<target-dir>/_organize_logs/organize_by_date.log
<target-dir>/_organize_logs/organize_pending_report_latest.md
Run tests:
go test ./...This project explicitly DOES NOT:
- Serve as a full DAM system.
- Treat
JPGas an authoritative source alongsideRAW. - Re-implement EXIF writing (uses
exiftool).
Read CORE_CONSTRAINTS.md for further development.
