文章目录
项目信息
- 项目: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 媒体库的最终版本,但它让我第一次清楚地看到:“能浏览”与“能长期管理”之间,隔的不是一个更漂亮的界面,而是一整套数据模型。
如果你也想看看这个失败原型后来长成了什么,可以继续读:
评论 0
留下想法或补充。评论经审核后会显示在这里。
正在读取评论…
成为第一个留下想法的人。