目录

Local Photo Metadata Browser

Local-first · No authentication · Do not expose directly to the Internet

English

Local Photo Metadata Browser 是一个面向 Python 学习者和个人照片整理者的本地优先 Demo:它在浏览器中接收 JPEG、PNG 与 WebP,读取拍摄时间,并将经过隐私处理的副本、JSON 索引和 Excel 表保存在当前电脑。

这是教学与本地工具原型,不是在线图库。它没有用户账户、权限控制、加密存储、备份、公开分享或生产级部署能力。

本地照片浏览器界面

为什么有这个项目

照片的 EXIF 既有整理价值,也可能暴露精确 GPS、设备和拍摄习惯。本项目把一个课程期的单文件原型改造成可阅读、可测试的开源参考实现,重点展示:

  • 如何基于真实文件内容而不是扩展名验证图片;
  • 如何提取有限的 EXIF 字段并默认隐藏 GPS;
  • 如何重新编码上传副本以移除 EXIF;
  • 如何用 Python 标准库 HTTP 服务组织同源 API;
  • 如何安全地维护 JSON 与 Excel 本地索引。

适合学习 Python 文件处理、Pillow、http.server、本地持久化和基础 Web 安全的人,也适合只想在自己电脑上浏览少量照片元数据的用户。

能做什么

  • 批量上传 JPEG、PNG、WebP;硬上限为每次 10 张、单张 10 MiB、请求体 25 MiB;
  • 依据图片实际格式和像素数验证内容,拒绝伪装文件及超大图片;
  • 读取拍摄时间;GPS 默认不保存,可显式选择近似或精确模式;
  • 默认重新编码保存副本,移除 EXIF 与附加元数据;
  • 按拍摄日期、坐标文本和时间顺序筛选;
  • 原子写入 data/photos.json,并可同步生成 data/photos.xlsx;
  • 只提供登记过的随机文件名,阻止目录穿越和任意静态文件读取;
  • 使用同源 CSP/CORS,无第三方地图、字体、分析脚本或 CDN。

重要边界

主题 默认行为 仍需知道
网络 只监听 127.0.0.1 没有认证;不要做端口转发或直接公网部署
EXIF 保存副本时重新编码并移除 JPEG 会重新压缩;原图请自行备份
GPS hidden,不写入 JSON/Excel/API approximate 约保留两位小数;exact 属于敏感信息
拍摄时间 从 EXIF 提取并写入索引 EXIF 无时区;缺失时使用上传时间
数据 只写本地 uploads/ 与 data/ 没有加密、备份、回收站或多用户隔离
文件格式 JPEG、PNG、WebP 不支持 RAW、HEIC、GIF、视频和云相册

即使保存副本移除了 EXIF,JSON/Excel 仍可能包含文件名和拍摄时间。请把整个运行目录视为私人数据。

快速开始

要求 Python 3.10+;推荐 Python 3.12。项目固定使用 Pillow 12.3.0 和 openpyxl 3.1.5。

python -m venv .venv

Windows PowerShell:

.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
python -m photo_browser

macOS / Linux:

source .venv/bin/activate
python -m pip install -r requirements.txt
python -m photo_browser

浏览器访问 http://127.0.0.1:8000。以下命令应返回 {"status":"ok"}:

curl http://127.0.0.1:8000/health

Windows 用户也可双击 启动服务器.bat;跨平台文档和 CI 仍以命令行入口为准。

最短使用流程

  1. 选择你拥有处理权的 JPEG、PNG 或 WebP;
  2. 查看页面上显示的限制和当前 GPS/EXIF 策略;
  3. 点击“写入本地档案”;
  4. 在联系印样中按日期浏览,点击照片查看已保存元数据;
  5. 结束后按 Ctrl+C 停止服务。

项目不附带公开照片样例。测试图片由代码在内存中生成,不含人物和 GPS。详见 Demo 数据说明。

配置

配置来自环境变量;不需要 .env 文件。.env.example 仅用于列出选项。

变量 默认值 说明
PHOTO_HOST 127.0.0.1 监听地址
PHOTO_PORT 8000 监听端口,0 表示随机可用端口
PHOTO_MAX_FILE_BYTES 10485760 单张文件字节上限
PHOTO_MAX_REQUEST_BYTES 26214400 含 multipart 开销的单次请求体上限
PHOTO_MAX_FILES 10 单次文件数量上限
PHOTO_MAX_IMAGE_PIXELS 40000000 单张解码像素上限
PHOTO_REQUEST_TIMEOUT_SECONDS 15 连接及请求体读取超时(5–60 秒)
PHOTO_MAX_CONCURRENT_UPLOADS 2 同时处理的上传数(1–4)
PHOTO_GPS_MODE hidden hidden / approximate / exact
PHOTO_STRIP_METADATA true 是否重新编码并移除元数据
PHOTO_EXPORT_EXCEL true 是否生成 Excel 索引
PHOTO_DATA_DIR data JSON/Excel 目录,可用绝对路径
PHOTO_UPLOAD_DIR uploads 保存副本目录,可用绝对路径
PHOTO_ALLOWED_ORIGINS 空 额外允许的精确 Origin,逗号分隔
PHOTO_ALLOW_NETWORK false 是否确认允许非回环监听

如果确实要在可信局域网实验,必须同时指定外部地址并显式确认:

python -m photo_browser --host 0.0.0.0 --allow-network

这不会增加认证或安全隔离。即使显式使用非回环监听,也只适合受信局域网内的短时实验,仍不建议且不得直接暴露到互联网。服务对上传设置 15 秒读取超时和最多 2 个并发上传槽位;这些资源限制不能替代认证、反向代理或生产级防护。

数据在哪里

uploads/          经过验证和隐私处理的图片副本
data/photos.json  API 使用的本地索引
data/photos.xlsx  可选的表格导出

这些运行内容均被 .gitignore 排除。要清空:先停止服务,确认不再需要后,使用文件管理器删除 uploads/ 与 data/ 中除 .gitkeep 外的内容。项目没有远程副本,也无法替你恢复误删数据。

架构

浏览器 index.html + static/
          │ 同源 HTTP
          ▼
photo_browser/server.py ── 请求限制、Origin、路由、路径校验
          │
          ├── images.py / exif.py ── 内容验证、EXIF、重新编码
          └── storage.py ── 随机文件名、JSON/Excel 原子写入

详细设计见 架构说明。

测试与质量

python -m compileall -q photo_browser tests upload.py
python -m unittest discover -s tests -v
python -m pip install -e ".[dev]"
ruff check .
pip-audit -r requirements.txt

测试只在系统临时目录生成图片、JSON 和 Excel,覆盖:默认回环绑定、非图片、文件/请求体/像素限制、上传并发闸门、GPS 策略、EXIF 移除、同源 CORS、跨域拒绝、路径穿越、JSON/Excel 和公式注入防护。CI 覆盖 Python 3.10、3.12 与 3.13。

贡献与安全

Roadmap

  • 可选择性删除单张本地记录,并提供明确的二次确认;
  • 在不上传云端的前提下生成缩略图,减少大图加载开销;
  • 为原始文件与隐私处理副本增加可验证的差异报告;
  • 增加可选的加密索引与本地备份策略(不会在 0.1.x 中承诺)。

来源与许可证

项目由课程期照片浏览原型重构而来;旧照片、上传记录、GPS/Excel/JSON 和报告已隔离且不属于公开发行内容。公开代码以 MIT License 提供,第三方运行依赖见 THIRD_PARTY_NOTICES.md。

关于

本地优先的 Python 照片上传与 EXIF 元数据浏览 Demo,支持隐私友好的 GPS 策略、JSON 记录和 Excel 导出。

158.0 KB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号