‹ 返回首页技术

从一个部署在 Ubuntu 上的 iOS 风格照片画廊,到后来的 Henry 媒体库:记录功能、失败点、技术选择与开源组件声明。

2026年10月19日·12 分钟阅读·阅读 …

文章目录
  1. 一开始,我只想要一个好看的图库
  2. 我做出来的东西
  3. iOS 风格的照片网格
  4. 全屏查看器
  5. 相簿就是文件夹
  6. 上传、删除和相簿管理
  7. 视频拖动
  8. 零依赖 Python 后端
  9. 那些真正花了时间的细节
  10. HTML 的 hidden 和 class 打架
  11. 中文文件名
  12. 上传大视频
  13. 1Panel 的 Compose 路径
  14. 为什么后来放弃了
  15. 1. 它仍然只是一个“远程文件夹浏览器”
  16. 2. 服务器依赖太重
  17. 3. 大媒体库的性能问题
  18. 4. 它没有一个真正的“库”
  19. 它给 Henry 媒体库留下了什么
  20. 文件夹结构值得保留
  21. 浏览器的交互可以很接近原生
  22. 上传和删除必须可靠
  23. 一个功能齐全的原型,不等于一个长期产品
  24. 现在的状态
  25. 开源项目与第三方组件声明
  26. Python
  27. Pillow
  28. FFmpeg
  29. Nginx / OpenResty
  30. 1Panel
  31. Docker
  32. 关于 Apple 与 iOS
  33. 项目自身
  34. 最后

项目信息

  • 项目:iOS 风格图片视频画廊
  • 状态:已停止维护
  • 开始时间:2026 年 8 月
  • 部署环境:Ubuntu、Docker、systemd、Nginx / OpenResty
  • 技术组成:Python 标准库、原生 JavaScript、CSS、Pillow、FFmpeg
  • 项目性质:个人 Web 图库实验,源码目前未公开
  • 本文作者:Henry

在 Henry 媒体库之前,我先做过一个更直接的东西:一个跑在 Ubuntu 服务器上的 iOS 风格照片和视频画廊。

目标并不复杂:把硬盘里的照片放进服务器目录,打开浏览器就能像使用 iPhone「照片」一样浏览。网格、相簿、全屏查看器、视频播放、上传、删除、搜索,最好再带一点毛玻璃和圆角。

它最后可以运行,也做完了大部分功能,但并没有成为我长期使用的产品。

现在回头看,它更像一次原型验证,也是 Henry 媒体库真正的启发来源之一。


一开始,我只想要一个好看的图库

起因不是想做一个媒体管理系统,而是文件太难找。

照片和视频散落在不同文件夹里。用访达一层层点开很慢,直接用网页目录浏览又太原始。系统「照片」希望我把素材导入它自己的资料库,而我只想保留原来的文件夹结构。

于是我提出的需求很具体:

  • 部署在自己的 Ubuntu 服务器上;
  • 照片和视频都能看;
  • 像 iOS「照片」一样有网格、相簿和全屏查看器;
  • 不需要安装 Node.js、数据库或复杂后端;
  • 文件仍然留在普通目录里;
  • 最好能直接在网页里上传和删除。

这个项目从一开始就没有打算做成云相册,也没有打算接管文件。它只是给我的文件夹加一层更好用的浏览界面。

事实证明,这个边界既让它很快做出来,也决定了它后来为什么停住。


我做出来的东西

第一版完成后,主要功能其实已经齐全。

iOS 风格的照片网格

首页使用按日期分组的网格布局,支持:

  • 今天、昨天和具体日期分组;
  • 照片与视频混合显示;
  • 视频显示首帧和时长;
  • 全部、照片、视频分段筛选;
  • 按文件名搜索。

顶部栏和底部标签栏使用毛玻璃效果,页面支持跟随系统切换深色和浅色模式。图标全部是自绘 SVG,没有使用 Apple 的 SF Symbols 或系统素材。

全屏查看器

点开一张照片后进入全屏查看器,支持:

  • 左右切换;
  • 键盘方向键翻页;
  • 滚轮或双指缩放;
  • 双击放大;
  • 视频空格键播放暂停;
  • 下载和删除。

这些交互是最像 iOS「照片」的部分。它们也让我第一次意识到,一个图库的“高级感”并不只来自毛玻璃,而是来自滚动、缩放、切换和反馈是否连贯。

相簿就是文件夹

media 目录下的子文件夹会自动成为相簿。

这个设计非常简单,也非常舒服:不需要数据库,不需要导入流程,不需要维护额外的相簿关系。把照片放进文件夹,网页里就会出现。

后来做 Henry 媒体库时,我仍然保留了“文件夹本身就是结构”这个想法。

上传、删除和相簿管理

网页支持:

  • 点击或拖拽上传;
  • 上传进度浮层;
  • 重名文件自动改名;
  • 中文文件名;
  • 查看器内删除;
  • 新建、重命名和删除相簿。

上传后端使用流式 multipart 解析,文件边读边写盘,不把整个视频先塞进内存。测试时用一个超过 50MB 的视频做了往返校验,确认文件内容一致。

视频拖动

视频播放不是简单地把文件路径塞进 <video>。

后端实现了 HTTP Range 请求,浏览器拖动进度条时可以按区间请求数据;Nginx 反代配置也关闭了代理缓冲,避免视频流被整段缓存。

零依赖 Python 后端

最开始的版本只使用 Python 标准库,python3 server.py 就能运行。

Pillow 和 FFmpeg 是可选增强:

  • 有 Pillow 时生成图片缩略图;
  • 有 FFmpeg 时生成视频封面;
  • 没有它们时仍然可以运行,只是退化为原图或没有封面。

这让部署变得很轻:一台普通的 Ubuntu 服务器、一个 Python 进程和一个 media 目录就能工作。


那些真正花了时间的细节

这个项目看起来像一个前端练习,实际大部分时间花在边界情况上。

HTML 的 hidden 和 class 打架

早期版本里,查看器弹层在 HTML 上用了 hidden 属性,前端脚本却用 classList 控制显示。

结果就是:点击图片看起来没有任何反应。

用户看到的是“点了没反应”,实际原因是两个显示状态互相覆盖。这个 bug 同时影响了查看器、快捷键和删除入口,也是整个项目里最典型的一次“界面问题其实是状态管理问题”。

中文文件名

上传中文文件名时曾经出现乱码。问题出在 multipart 头部解析使用了错误的字符集,后来改成按原始字节处理,再用 UTF-8 解码。

这类问题在英文示例里永远不会出现,只有真正上传自己的素材时才会暴露。

上传大视频

最初的 multipart 解析会把整个请求读进内存。小图片没问题,大视频会直接把内存推高。

改成流式写入后,才真正具备处理视频的能力。对于图库这种“必然会出现大文件”的应用,这是必须解决的问题。

1Panel 的 Compose 路径

通过 1Panel 部署时,曾经遇到 Dockerfile: no such file or directory。

原因是 1Panel 的“编辑 Compose”会把配置存到自己的目录,而其中的 build: . 会相对那个目录寻找 Dockerfile,自然找不到。

这不是 Docker 的问题,也不是 YAML 的问题,而是部署工具的路径语义问题。后来改成手动选择项目里的 docker-compose.yml,或者使用绝对路径构建,才恢复正常。


为什么后来放弃了

它已经能用,但我逐渐发现,它解决的是一个比想象中更小的问题。

1. 它仍然只是一个“远程文件夹浏览器”

相簿来自文件夹,文件名来自磁盘,搜索只能搜文件名。

但我真正需要的媒体库还包括:

  • 按拍摄时间、地点、设备筛选;
  • 读取 EXIF;
  • 识别重复照片;
  • 视频缩略图和转码;
  • 收藏、评分、标签;
  • 大量素材的快速索引;
  • 客户端与服务器之间的增量同步。

这些都不是给网格加几个 CSS 效果能解决的。

2. 服务器依赖太重

每次想看照片,都必须保证服务器在线、容器在跑、网络可达、证书有效。

对“偶尔浏览一下自己的照片”来说,这个链条太长了。

Henry 媒体库后来选择“本地优先”,很大程度上就是因为这次经历:文件和索引应该尽量留在自己的设备上,服务器应该是增强,而不是唯一入口。

3. 大媒体库的性能问题

几千张照片时,目录扫描和缩略图缓存都还能接受。

但到几万张照片和上千个视频时,问题会变成:

  • 目录扫描耗时;
  • 缩略图缓存占用空间;
  • 首次打开需要等待;
  • 浏览器网格内存占用上升;
  • 视频格式兼容性不一致;
  • 移动端网络加载原图太慢。

这个项目的结构适合一个轻量图库,不适合作为一个长期增长的私人媒体系统。

4. 它没有一个真正的“库”

文件夹是很好的起点,但不是一个完整的数据库。

没有索引、元数据和搜索能力,最终就只能停留在“能看”的层面。

这也是为什么后来做 Henry 媒体库时,我把架构改成了完全不同的组合:

  • 浏览器负责界面和交互;
  • IndexedDB 负责本地索引;
  • Python 后端负责文件与媒体处理;
  • SwiftUI 客户端负责 macOS 原生体验;
  • 文件仍然是普通文件,但索引和浏览状态不再完全依赖服务器。

关于这段后续,我单独写过一篇文章:我给自己写了一个媒体库:几万张照片和视频,为什么最后跑进了浏览器。


它给 Henry 媒体库留下了什么

虽然这个项目被放弃了,但我并不认为它是一次纯粹的失败。

它留下了几个后来一直被沿用的判断。

文件夹结构值得保留

直接把子文件夹当相簿,比强迫用户先导入再分类更自然。

Henry 媒体库后来依然尊重文件系统,而不是把每个文件吞进一个不可见的私有数据库。

浏览器的交互可以很接近原生

全屏查看、键盘切换、缩放、视频 Range 请求,这些都证明了浏览器不只是“显示文件列表”的地方。

只要交互足够顺,网页也可以有原生应用的质感。

上传和删除必须可靠

小文件测试通过,不代表真实场景可用。

中文文件名、大视频、断点、重名、权限和错误反馈,才是图库能不能长期使用的关键。

一个功能齐全的原型,不等于一个长期产品

这个 iOS 风格图库有相当完整的界面和功能,却仍然没有成为我日常使用的媒体库。

因为它解决的是“如何浏览服务器上的文件”,而不是“如何管理我自己的全部照片和视频”。

这句话后来变成了我重新设计 Henry 媒体库时最重要的起点。


现在的状态

这个项目已经停止维护。

它没有继续增加人脸识别、地图、时间轴、智能相簿或视频转码。服务器上的部署也已经不再作为我的主要照片入口。

代码和部署文档仍然保留着,作为一种原型记录。它证明了:

  • 纯 Python 标准库可以支撑一个可用的媒体浏览器;
  • 原生 JavaScript 也能做出足够细腻的 iOS 风格交互;
  • 文件夹式相簿是一个非常好的产品起点;
  • 但如果没有索引、元数据和本地优先策略,它走不到真正的媒体库。

这也是为什么后来 Henry 媒体库没有沿着这个项目继续打补丁,而是重新做了一套架构。


开源项目与第三方组件声明

这个项目使用了以下开源软件、运行时或第三方服务。相关名称、代码和商标归各自作者、维护者与项目所有。

Python

  • 项目:Python
  • 官网:python.org
  • 许可证:Python Software Foundation License 2.0(PSF-2.0)
  • 用途:实现零依赖的 HTTP 服务、目录扫描、Range 响应和上传解析。

Pillow

  • 项目:Pillow
  • 仓库:github.com/python-pillow/Pillow
  • 许可证:HPND / PIL License
  • 用途:生成图片缩略图。未安装时项目会退化为直接显示原图。

FFmpeg

  • 项目:FFmpeg
  • 官网:ffmpeg.org
  • 许可证:LGPL-2.1-or-later;具体二进制取决于构建选项,也可能包含 GPL 组件
  • 用途:从视频中提取封面帧。项目没有修改 FFmpeg 源码。

Nginx / OpenResty

  • 项目:Nginx
  • 官网:nginx.org
  • 许可证:BSD-2-Clause
  • 用途:反向代理、HTTPS 入口和视频 Range 转发。

如果部署时使用 1Panel 提供的 OpenResty:

  • 项目:OpenResty
  • 官网:openresty.org
  • 许可证:BSD 风格的 OpenResty 许可证,详见其 COPYRIGHT
  • 用途:作为 1Panel 网站与 HTTPS 的运行环境。

1Panel

  • 项目:1Panel
  • 仓库:github.com/1Panel-dev/1Panel
  • 许可证:GPL-3.0
  • 用途:服务器管理、Docker 编排和 Nginx 配置托管。

Docker

  • 项目:Docker Engine
  • 官网:docker.com
  • 许可证:Apache-2.0
  • 用途:构建和运行图库容器。

关于 Apple 与 iOS

这个项目只参考了 iOS「照片」的信息架构和交互方式,没有使用 Apple 的图标、SF Symbols、照片应用素材或其他受版权保护资源。

iOS、iPhone 和 Apple 是其各自权利人的商标。本项目是非官方个人实验,与 Apple 没有任何关联或授权关系。

项目自身

图库项目由 Henry 编写,项目 README 曾标注 MIT License,但源码目前没有单独公开为开源仓库。

本文只记录项目历史,不重新声明项目的发布或授权状态。文中不包含真实服务器地址、访问密码或 API 凭据。


最后

这个项目没有成为 Henry 媒体库的最终版本,但它让我第一次清楚地看到:“能浏览”与“能长期管理”之间,隔的不是一个更漂亮的界面,而是一整套数据模型。

如果你也想看看这个失败原型后来长成了什么,可以继续读:

H
Henry

记录技术实践、设计观察与生活里的慢思考。

关于作者
DISCUSSION

评论 0

留下想法或补充。评论经审核后会显示在这里。

正在读取评论…