照片管理 Skill
技能说明
将指定工作目录中的常见图片文件(.jpg、.jpeg、.png)备份到目标备份目录,并依据用户定义的命名规则识别照片版本关系,保留旧版、归档新版、更新主备份。
触发词
> "帮我把 [工作区] 文件夹中的照片备份到 [备份区] 文件夹中,新旧照片规则为 [规则]"
> "备份照片"、"照片备份"、"photo backup"
> "归档照片"、"照片归档"、"archive photos"
> "同步照片"、"照片同步"、"sync photos"
> "把 [工作区] 里新增或修改过的照片增量备份到 [备份区],规则:[规则]" |
> "能不能做一次增量备份,把 [工作区] 里变了或新增的照片同步到 [备份区]?版本按 [规则] 判断" |
> "如果 [工作区] 有比 [备份区] 新的照片,按 [规则] 增量备份过去" |
基本原则
- 仅复制和记录,不删除、不覆盖原始照片。
- 触发后先确认工作目录、备份目录和命名规则,缺失时主动询问。
- 默认处理
.jpg、.jpeg、.png格式。如需处理其他格式(.tiff、.webp、.heic、.raw 等),可调用scripts/photo_scanner.py扩展检测。 - 路径兼容 Windows 盘符、中文、空格和括号。
- 复制完成后对源文件与备份文件进行哈希校验(SHA-256),确保一致。
参数确认
工作流启动后,首先与用户确认以下三项参数。若用户输入已包含,则直接进入扫描阶段;缺任何一项则逐项询问:
| 参数 | 说明 | 示例 |
|---|---|---|
工作目录 |
存放待备份照片的源文件夹 | D:\Photos\workspace |
备份目录 |
存放备份的目标根文件夹 | D:\Photos\backup |
新旧规则 |
识别新旧版本的命名规则 | a01.jpg 是 a.jpg 的新版 |
命名规则说明格式:
用户可以用自然语言描述规则,例如:
- "文件名末尾带数字的是新版,不带数字的是旧版"(如
a01.jpg→ 新版,a.jpg→ 旧版) - "
_v2、_v3后缀的是新版"(如photo_v2.jpg→ 新版,photo.jpg→ 旧版) - "
_edit、_final后缀的是新版"(如IMG_001_edit.jpg→ 新版,IMG_001.jpg→ 旧版)
收到规则后,将其解析为 scripts/version_matcher.py 可理解的参数。
工作流程
本 Skill 按以下顺序分步执行,每步可独立调试验证。
第 1 步:参数确认
确认工作目录、备份目录和新旧照片规则,未提供则询问用户。
- 验证工作目录存在且包含图片文件。
- 验证备份目录存在(若不存在则创建)。
- 解析命名规则为可匹配的模式。
第 2 步:状态扫描
首先检查备份区状态:
- 若备份区为空(无任何
.jpg/.jpeg/.png文件):需先将工作区中的所有照片文件复制到备份目录,建立初始备份基线。复制完成后对每个文件执行哈希校验,校验通过后继续。 - 若备份区已有文件:按正常流程继续。
然后分别对工作区目录和备份区目录执行 scan_status.py,生成或比对 当前状态.md:
python scripts/scan_status.py <工作区目录>
python scripts/scan_status.py <备份区目录>
scan_status.py 的行为:
- 若目标目录中不存在
当前状态.md→ 扫描所有文件,计算 SHA-256 哈希并创建记录。 - 若已存在 → 对比当前文件与已有记录,输出变更报告(新增/修改/删除)。
- 输出包含:文件路径、大小、修改时间、SHA-256 哈希。
> 初始化场景:首次使用时备份区为空,先将工作区全部照片复制到备份区作为基线,再执行扫描。后续备份则基于已有基线按正常流程处理版本变更。
第 3 步:版本比对
使用 scripts/version_matcher.py 依据新旧照片规则判断版本关系:
python scripts/version_matcher.py --work-dir <工作区> --rule <规则类型> [--rule-args ...] [--json]
输出的 JSON 结构:
{
"rule": "numbered_suffix",
"rule_description": "文件名末尾带数字的是新版",
"total_scanned": 5,
"total_pairs": 2,
"total_unmatched_new": 1,
"total_unmatched_old": 1,
"pairs": [
{
"old": {
"filename": "a.jpg",
"path": "D:\\Photos\\workspace\\a.jpg",
"hash": "e3b0c44298fc1c...",
"size_bytes": 2234567,
"size_formatted": "2.1 MB",
"mtime": "2026-06-28 10:15:00"
},
"new": {
"filename": "a01.jpg",
"path": "D:\\Photos\\workspace\\a01.jpg",
"hash": "d4a1b55309fd2e...",
"size_bytes": 2456789,
"size_formatted": "2.3 MB",
"mtime": "2026-06-29 14:20:00"
}
}
],
"unmatched_new": [
{
"filename": "c02.jpg",
"path": "D:\\Photos\\workspace\\c02.jpg",
"hash": "f7b2c6641ae03d...",
"size_bytes": 1890123,
"size_formatted": "1.8 MB",
"mtime": "2026-06-29 11:00:00"
}
],
"unmatched_old": [
{
"filename": "d.jpg",
"path": "D:\\Photos\\workspace\\d.jpg",
"hash": "a1c3d5e7f9b0...",
"size_bytes": 3120456,
"size_formatted": "3.0 MB",
"mtime": "2026-06-27 09:30:00"
}
]
}
字段说明:
pairs:成功匹配的旧版-新版对应关系(已建立版本关系的文件对)。unmatched_new:仅有新版、无对应旧版的文件(新增照片,对旧版无修改参照)。unmatched_old:仅有旧版、无对应新版的未修改文件(本次未产生新版,保持原样归档)。- 每个文件条目统一包含
filename、path(绝对路径)、hash(SHA-256)、size_bytes、size_formatted、mtime,与version_record.json中的记录格式保持一致。
规则参数映射:
| 用户描述 | rule 参数 | rule_args |
|---|---|---|
| 文件名末尾带数字的是新版 | numbered_suffix |
--separator "" --max-digits 2 |
_v2 后缀是版本号 |
numbered_suffix |
--separator "_v" --max-digits 2 |
_edit/_final 等固定后缀 |
keyword_suffix |
--keywords edit,final,retouched,modified |
第 4 步:日期批次备份
在备份根目录下创建以当天日期命名的文件夹:
<备份目录>/YYYY-MM-DD/
├── 旧版/
└── 新版/
若日期文件夹已存在(同一天多次备份),则在日期后追加序号:2026-06-29_2/。
第 5 步:复制与归档
对版本比对出的每对版本执行以下操作:
- 复制旧版:将旧版照片从工作区复制到
<日期批次>/旧版/。 - 复制新版:将新版照片从工作区复制到
<日期批次>/新版/。 - 未匹配文件:
unmatched_new的文件复制到<日期批次>/新版/,unmatched_old的文件复制到<日期批次>/旧版/。 - 哈希校验:对每次复制,计算目标文件的 SHA-256 哈希并与源文件比对。若不匹配,标记错误并重试一次;仍失败则记录异常。
可使用 scripts/backup_executor.py 执行此步骤:
python scripts/backup_executor.py copy-and-verify \
--pairs-file <version_matcher输出的JSON> \
--work-dir <工作区> \
--batch-dir <日期批次目录> \
[--dry-run]
第 6 步:更新主备份(可选操作)
> 重要:此步骤为可选操作,不在正常工作流中自动执行。仅在用户明确要求"更新主备份"或"同步主备份"时才执行。
主备份位于 <备份目录>/(备份根目录),存放各照片的当前最新版本。
当用户主动要求更新主备份时,执行以下规则:
- 若主备份中已存在某照片的旧版本 → 用新版替换(文件名保持旧版命名,内容为新版)。
- 旧版仍保留在
<备份目录>/<日期批次>/旧版/中以便回溯。 - 若主备份中不存在该照片的任何版本 → 直接将新版复制到主备份。
unmatched_new的无对应文件也复制到主备份。unmatched_old的无对应新版文件,保留在主备份中不替换。
可使用 scripts/backup_executor.py 执行:
python scripts/backup_executor.py update-main \
--pairs-file <version_matcher输出的JSON> \
--work-dir <工作区> \
--backup-dir <备份目录> \
[--dry-run]
> 注意: 若用户未明确要求更新主备份,则跳过此步骤,主备份中的文件保持不变。version_record.json 中的 main_backup_path 字段将标注为"未更新主备份"。
第 7 步:生成记录
在日期批次文件夹中生成两份记录文件:
version_record.json
main_backup_path 字段仅在执行了第 6 步(主备份更新)时才填充实际路径,否则为 null 或标注"未更新主备份"。
{
"backup_date": "2026-06-29T15:30:00+08:00",
"work_dir": "D:\\Photos\\workspace",
"backup_dir": "D:\\Photos\\backup",
"rule": "numbered_suffix",
"rule_description": "文件名末尾带数字的是新版",
"main_backup_updated": true,
"total_pairs": 3,
"total_unmatched_new": 1,
"total_unmatched_old": 0,
"pairs": [
{
"old": {
"filename": "a.jpg",
"source_path": "D:\\Photos\\workspace\\a.jpg",
"backup_path": "D:\\Photos\\backup\\2026-06-29\\旧版\\a.jpg",
"hash": "e3b0c44298fc1c...",
"size_bytes": 2234567,
"size_formatted": "2.1 MB",
"mtime": "2026-06-28 10:15:00"
},
"new": {
"filename": "a01.jpg",
"source_path": "D:\\Photos\\workspace\\a01.jpg",
"backup_path": "D:\\Photos\\backup\\2026-06-29\\新版\\a01.jpg",
"main_backup_path": "D:\\Photos\\backup\\a.jpg",
"hash": "d4a1b55309fd2e...",
"size_bytes": 2456789,
"size_formatted": "2.3 MB",
"mtime": "2026-06-29 14:20:00",
"hash_verified": true
}
}
],
"unmatched_new": [
{
"filename": "c02.jpg",
"source_path": "D:\\Photos\\workspace\\c02.jpg",
"backup_path": "D:\\Photos\\backup\\2026-06-29\\新版\\c02.jpg",
"main_backup_path": "D:\\Photos\\backup\\c02.jpg",
"hash": "f7b2c6641ae03d...",
"size_bytes": 1890123,
"size_formatted": "1.8 MB",
"mtime": "2026-06-29 11:00:00",
"hash_verified": true
}
],
"unmatched_old": [],
"errors": []
}
字段说明:
main_backup_updated:true表示已执行主备份更新,各记录的main_backup_path有效;false或不存在时,main_backup_path为null。main_backup_path:仅在main_backup_updated为true时填充,路径指向<备份目录>/<旧版文件名>。unmatched_new/unmatched_old:无对应版本的文件记录,格式与pairs中的单侧条目一致。
summary.md
Markdown 格式的可读备份说明,包含:
- 备份时间和日期
- 工作区和备份区路径
- 命名规则说明
- 版本对应关系表格(旧版 → 新版)
- 新版照片预览(文件名、大小、修改时间)
- 操作统计(复制文件数、总大小、校验状态)
- 主备份更新状态:明确标注 "本次未执行主备份更新(可选操作)" 或 "已更新主备份",并列出更新的文件列表
可使用 scripts/backup_executor.py 生成:
python scripts/backup_executor.py generate-records \
--pairs-file <version_matcher输出的JSON> \
--batch-dir <日期批次目录> \
--work-dir <工作区> \
--backup-dir <备份目录> \
--rule-description "<规则描述>" \
[--main-backup-updated]
其中 --main-backup-updated 标志指示本次是否执行了主备份更新,影响 main_backup_path 字段的填充和 summary.md 中的状态描述。
备份目录结构
完成后的备份目录结构:
<备份目录>/ # 备份根目录
├── a.jpg # 主备份(可选更新,保存各照片最新版本)
├── b.jpg # 主备份(可选更新)
├── 当前状态.md # 备份区文件状态记录(主备份更新后刷新)
└── 2026-06-29/ # 日期批次(必执行)
├── 旧版/
│ ├── a.jpg # 修改前的旧版
│ └── b.jpg # 修改前的旧版
├── 新版/
│ ├── a01.jpg # 修改后的新版
│ └── b01.jpg # 修改后的新版
├── version_record.json # 版本对应记录
└── summary.md # 可读备份说明
说明:
- 日期批次文件(
旧版/、新版/、version_record.json、summary.md) 在每次备份时必定生成。 - 主备份文件(根目录下的
a.jpg、b.jpg) 仅在用户明确要求更新主备份时才更新,为可选操作。 当前状态.md仅在执行主备份更新后刷新,记录备份根目录下当前文件的哈希快照。
脚本资源
本 skill 在 scripts/ 目录下包含以下可复用脚本:
| 脚本 | 用途 |
|---|---|
scan_status.py |
扫描目录生成/比对 当前状态.md,记录 SHA-256 哈希快照 |
version_matcher.py |
根据命名规则匹配新旧版本照片对应关系 |
backup_executor.py |
执行复制归档、主备份更新、记录生成等核心操作(分步子命令) |
photo_manager.py |
综合 CLI,提供 init/scan/status/backup/log/diff/restore 等版本控制命令 |
photo_scanner.py |
工作区照片扫描、基线快照、修图检测(含 EXIF 分析) |
version_control.py |
git 风格的版本控制(提交、日志、差异、回滚) |
utils.py |
通用工具函数(哈希、元数据、EXIF 解析、文件操作) |
脚本职责说明
- 当前工作流(用于本次备份任务):
scan_status.py+version_matcher.py+backup_executor.py - 传统版本控制工作流(用于已有备份仓库):
photo_manager.py(入口)+photo_scanner.py+version_control.py - 通用依赖:
utils.py
执行时,先用 scan_status.py 了解文件状态,再用 version_matcher.py 建立新旧对应关系,然后按需分步调用 backup_executor.py 的各个子命令(copy-and-verify、generate-records),最后根据用户意愿决定是否执行 update-main 更新主备份。