PinPoint 是一款自托管的个人照片库应用,旨在帮助您组织和展示珍贵的照片回忆。它支持自动提取 EXIF 信息、在交互式地图上展示拍摄位置,并提供美观的瀑布流布局。
本项目基于 ChronoFrame 二次开发。
- 瀑布流布局:美观且响应式的照片展示
- 地图视图:在地图上探索您的照片足迹
- EXIF 解析:自动提取并展示拍摄参数
- S3 兼容:支持各类 S3 兼容对象存储 (AWS, 腾讯云 COS, MinIO 等)
- 高性能:基于服务端渲染 (SSR) 和图片优化技术
- 隐私优先:完全自托管,数据掌握在自己手中
- 在线管理照片 - 通过 Web 界面轻松管理和浏览照片
- 探索地图 - 在地图上浏览照片拍摄位置
- 智能 EXIF 解析 - 自动提取拍摄时间、地理位置、相机参数等元数据
- 地理位置识别 - 自动识别(Reverse Geocoding)照片拍摄地点
- 多格式支持 - 支持 JPEG、PNG、HEIC/HEIF 等主流图片格式
- 智能缩略图 - 基于 ThumbHash 技术的高效缩略图生成
- Nuxt 4 - 基于最新的 Nuxt 框架,提供 SSR/SSG 支持
- TypeScript - 完整的类型安全保障
- TailwindCSS - 现代化的 CSS 框架
- Drizzle ORM - 类型安全的数据库 ORM
- 多存储后端 - 支持 S3 兼容存储、本地文件系统
- CDN 加速 - 可配置 CDN 地址加速图片访问
推荐使用 Docker 部署。
创建 .env 文件并配置。
下面是最小化配置示例:
# 管理员邮箱(必须)
CFRAME_ADMIN_EMAIL=
# 管理员用户名(可选,默认 PinPoint)
CFRAME_ADMIN_NAME=
# 管理员密码(可选,默认 CF1234@!)
CFRAME_ADMIN_PASSWORD=
# 站点信息(均可选)
NUXT_PUBLIC_APP_TITLE=
NUXT_PUBLIC_APP_SLOGAN=
NUXT_PUBLIC_APP_AUTHOR=
NUXT_PUBLIC_APP_AVATAR_URL=
# 地图提供器 (maplibre/mapbox)
NUXT_PUBLIC_MAP_PROVIDER=maplibre
# 使用 MapLibre 需要 MapTiler 访问令牌
NUXT_PUBLIC_MAP_MAPLIBRE_TOKEN=
# 使用 Mapbox 需要 Mapbox 访问令牌
NUXT_PUBLIC_MAPBOX_ACCESS_TOKEN=
# Mapbox 无域名限制令牌(反向地理编码,可选)
NUXT_MAPBOX_ACCESS_TOKEN=
# 存储提供者(local、s3 或 openlist)
NUXT_STORAGE_PROVIDER=local
NUXT_PROVIDER_LOCAL_PATH=/app/data/storage
# 会话密码(必须,32 位随机字符串)
NUXT_SESSION_PASSWORD=一行命令启动:
docker build -t pinpoint .
docker run -d --name pinpoint -p 3000:3000 -v $(pwd)/data:/app/data --env-file .env pinpoint创建 docker-compose.yml:
services:
pinpoint:
build: .
container_name: pinpoint
restart: unless-stopped
ports:
- '3000:3000'
volumes:
- ./data:/app/data
env_file:
- .env启动:
docker-compose up -d如未配置
CFRAME_ADMIN_EMAIL和CFRAME_ADMIN_PASSWORD,默认账号如下:
- 邮箱:
admin@chronoframe.com- 密码:
CF1234@!
- 点击头像跳转到登录页面,可以使用账号密码或 GitHub 登录
- 访问仪表板页面
/dashboard - 在
Photos页面中选择图片并点击上传(支持批量上传和拖拽上传) - 系统将自动提取 EXIF 信息、生成缩略图并逆编码照片地理位置
- Node.js 18+
- pnpm 9.0+
# 使用 pnpm (推荐)
pnpm install
# 或使用其他包管理器
npm install
yarn install复制环境变量模板并根据需要配置:
cp .env.example .env# 2. 生成数据库迁移文件(可选)
pnpm db:generate
# 3. 执行数据库迁移
pnpm db:migratepnpm dev应用将在 http://localhost:3000 启动。
pinpoint/
├── app/ # Nuxt 应用
│ ├── components/ # 组件
│ ├── pages/ # 页面路由
│ ├── composables/ # 组合式函数
│ └── stores/ # Pinia 状态管理
├── packages/
│ └── webgl-image/ # WebGL 图片查看器
├── server/
│ ├── api/ # API 路由
│ ├── database/ # 数据库 schema 和迁移
│ └── services/ # 业务逻辑服务
└── shared/ # 共享类型和工具
# 开发模式 (包含依赖包构建)
pnpm dev
# 仅构建依赖包
pnpm build:deps
# 构建生产版本
pnpm build
# 数据库操作
pnpm db:generate # 生成迁移文件
pnpm db:migrate # 执行迁移
# 预览生产版本
pnpm preview欢迎贡献代码!请确保:
- Fork 本仓库
- 创建功能分支 (
git checkout -b feature/amazing-feature) - 提交更改 (
git commit -m 'Add some amazing feature') - 推送到分支 (
git push origin feature/amazing-feature) - 开启 Pull Request
- 使用 TypeScript 进行类型安全的开发
- 遵循 ESLint 和 Prettier 代码规范
- 更新相关文档
本项目基于 MIT 许可证 开源。
nianshu2022
- GitHub: @nianshu2022
- 项目地址: WithYou
如何创建管理员用户?
首次启动时,会根据环境变量 CFRAME_ADMIN_EMAIL、CFRAME_ADMIN_NAME 和 CFRAME_ADMIN_PASSWORD 环境变量创建一个管理员用户。CFRAME_ADMIN_EMAIL 必须是登录使用的 GitHub 账户的邮箱。
支持哪些图片格式?
支持 JPEG、PNG、HEIC/HEIF、MOV(作为实况照片) 格式。
为什么无法使用 GitHub/Local 存储?
目前支持 S3 兼容存储,未来计划支持 GitHub 和本地文件系统存储。
为什么需要/如何配置地图服务?
地图服务用于在地图上浏览照片拍摄位置,以及照片详情中的小地图渲染。目前使用 Mapbox,注册后获取访问令牌并配置到 MAPBOX_TOKEN 环境变量中。
为什么我上传的 MOV 文件没有被识别为实况照片?
需要确保实况照片对的图片(.heic)和视频(.mov)的文件名一致(例如 IMG_1234.heic 与 IMG_1234.mov 会自动匹配)。
一般情况来说,不管是上传 .heic 还是 .mov,都会检测一次配对,因此上传的顺序无关紧要。
如果仍然没有被识别为实况照片,请在仪表盘中找到图片,在操作菜单中手动触发配对检测。
如何导入存储中已有的照片?
目前不支持直接导入已有照片,未来计划支持通过指定目录扫描导入。
本项目与 Afilmory 有何区别?
Afilmory 是一个非常优秀的项目,其部署方式是在本地或 CI 中处理存储中的照片并生成清单文件,然后前端通过读取清单文件来展示照片,属于静态部署。 ChronoFrame 则是一个动态的照片管理应用,提供在线上传、管理和浏览照片的功能,适合需要频繁更新照片的场景。
本项目基于 ChronoFrame 二次开发。
感谢以下优秀的开源项目和库:
- ChronoFrame - 原始项目
- Nuxt
- TailwindCSS
- Drizzle ORM




