照片整理成网页相册(photo-album-builder)
Overview
把任意本地照片文件夹(相机直出 JPG/PNG 等)整理成一个自包含、可离线浏览的网页相册:
- 原图保持原位不改动,只额外生成 720px 网格缩略图与 1600px 灯箱预览图;
- 按拍摄日期分组(侧边栏导航),支持瀑布流懒加载、灯箱大图(←/→ 翻页、空格幻灯片、Esc 关闭)、文件名搜索、回到顶部;
- 双击 index.html 即可打开,整文件夹拷走也能在别的电脑上看(无需服务器、无外部 CDN)。
核心脚本 scripts/build_album.py 会从本 skill 的 assets/ 自动复制前端模板,因此调用者无需手动准备网页文件。
何时使用
- 用户说「把这些照片整理成电子相册 / 网页相册 / 本地相册」;
- 用户有一个堆满照片的文件夹,希望「按时间浏览」「查阅方便、视觉效果强」;
- 任何「把照片变成可滑动观看的画廊」类需求。
不适用:需要上传到云端、需要多人协作编辑、或需要 AI 自动识别人脸/场景打标签的复杂图库系统(本 skill 只做本地静态相册)。
工作流程
1. 澄清关键决策(务必先问,避免返工)
开工前用 AskUserQuestion 确认三点,给出推荐项:
1. 整理方式:按拍摄日期分组(推荐)/ 单一网格不分组 / 用户指定规则;
2. 生成位置:照片文件夹内建 相册\(推荐,自包含)/ 当前工作区(引用绝对路径);
3. 原图处理:原图保持原位只生成缩略图(推荐,安全)/ 按日期移动原文件(慢且有风险)。
> 分组依据必须先和用户对齐。相机时钟常常是错误的(见下方「关键坑」),不要盲信 EXIF 年份。
2. 准备 Python 环境与依赖
PY="<managed python 3.x>"
"$PY" -m venv <venv>
"$PY" -m pip install Pillow
(runtime 隔离规则:venv 建在隔离目录,Pillow 装进 venv,不要用全局 pip。)
3. 扫描与日期解析
build_album.py 会扫描源目录全部图片,对每张照片确定日期:
- 优先从文件名恢复真实日期:微信图片_YYYYMMDDHHMMSS、IMG_YYYYMMDD_HHMMSS、mmexport/wx_camera_<13位毫秒时间戳>;
- 都解析不到的(主要是相机 DSC 连拍、按人名命名的照片、IMG_ 短序号)视为无日期,后续处理。
4. 生成缩略图 / 预览图(多进程)
- 720px 缩略图 →
相册/thumbnails/;1600px 预览 →相册/preview/; - 自动按 EXIF Orientation 转正;已存在的图自动跳过(幂等可重跑);
- 用
ProcessPoolExecutor+as_completed并行加速(注意不是imap)。
5. 分组与导出数据
- 有真实日期的照片:归入其真实日(超出事件窗口则夹紧到边界);
- 无日期的照片:按连拍序号(DSC 编号 / 文件顺序)排序后均分到事件窗口的若干天,保留时间先后;
- 导出
相册/data/photos.js(window.PHOTOS = {...},含分组、封面、每张的 thumb/preview/src 路径与尺寸)。
事件窗口由 --event-start YYYY-MM-DD 与 --event-days N 控制(默认 2026-07-12 / 7)。例如用户确认全部照片拍在 7/12–7/18,就传 --event-start 2026-07-12 --event-days 7。
6. 复制前端模板
build_album.py 自动把本 skill assets/ 下的 index.html、style.css、app.js 复制到相册目录,无需手工准备。
7. 验证
python scripts/verify_album.py <相册目录> [<源照片目录>]
确认:缩略图/预览图数量与 photos.js 展开数一致、无重复收录、文件均存在。(若提供源目录还会对比原图数。)
8. 预览交付
python -m http.server 8765 --directory <相册目录>
# 浏览器打开 http://localhost:8765/
或直接告知用户双击 相册/index.html。
关键坑(实战教训,务必规避)
- 相机时钟不可信:EXIF
DateTimeOriginal可能是旧年份(如 2022),绝不能直接按 EXIF 年份分组。分组前先和用户确认真实拍摄窗口,再决定均分策略。 - 分组逻辑重复 bug:在
build_groups中,undated列表是meta中对象的引用,若先修改其date再遍历meta追加,会导致同一张照片被收录两次(总数翻倍)。修复方式:把「有真实日期」与「无日期」拆成dated/undated两个互斥列表,分别处理。 - 文件名日期格式要全:iPhone 导出是
IMG_YYYYMMDD_HHMMSS(含真实日期),不要只认「微信图片_」前缀,否则这批照片会被误判无日期而归错天。 - 排序优先级:均分来的无日期照片会被填占位时间
00:00:00,若直接按时间字符串排序会排到真实时间戳照片前面。用real_dt标志让真实时间戳照片排前、占位照片排后。 - 免读原图重排:只改分组策略想快速重跑时,用
--skip-resize,脚本从已生成的预览图读尺寸(宽高比与原图一致),免去重读几十 GB 原图,几秒完成。
命令参考
# 常规构建(输出到 <源目录>/相册)
python scripts/build_album.py <源照片目录>
# 指定输出目录
python scripts/build_album.py <源照片目录> --out /别处/相册
# 仅重排分组(已生成过缩略图,免读原图,秒级)
python scripts/build_album.py <源照片目录> --skip-resize
# 指定真实事件窗口(无日期照片按连拍均分到该区间)
python scripts/build_album.py <源照片目录> --event-start 2026-07-12 --event-days 7
# 强制重新生成所有缩放图
python scripts/build_album.py <源照片目录> --force
# 调试:只处理前 N 张
python scripts/build_album.py <源照片目录> --limit 20
资源
scripts/build_album.py— 主构建脚本(扫描、生成缩放图、分组、导出数据、复制前端)。scripts/verify_album.py— 完整性验证(数量一致、无重复、文件存在)。assets/index.html、assets/style.css、assets/app.js— 网页相册前端模板,由build_album.py自动复制到输出目录。