家里装了监控摄像头,日常拍摄录像自动保存到 NAS 上。日积月累,盘里堆了 67,000 多段视频、4.5TB 数据,横跨 407 天。
然后问题来了:怎么快速找到想看的那段录像?
你可能会想:装个开源的监控系统不就行了?Frigate?Jellyfin?我都试过。结果分别是:太肥、不对、不合适。
这不是一篇教程——是记录我怎么从”找开源方案”到”算了我自己写一个”的过程,以及这中间的权衡和踩坑。
需求到底是什么
先理清楚我到底需要什么。
录像已经有了——摄像头自动推到 NAS,不存在”需要录制”的问题。AI 检测也有了——一个独立的 cam-filter 脚本在跑,识别出有人活动的片段单独归档。
缺的只有一件事:方便地浏览和回放。
具体来说:按日期选、拖时间轴找片段、在手机上也行。说白了就是安防 DVR 软件那种体验——日历上显示哪天有录像,点进去是时间线,选一段就播。
这不是 NVR(网络录像机)该干的事,也不是媒体服务器的活。这是一个 surveillance-specific 录像浏览器。
试过的开源方案(为什么都不行)
Frigate
Frigate 是目前最流行的开源 NVR,AI 物体检测能力很强。但它强的前提是你有 Coral TPU 或 GPU。
我只有一台纯 CPU 服务器。关掉 AI 检测当纯录像机用?可以,但 Frigate 的设计重心在检测,回放界面做得很基础——只有一个时间线,不支持按日期跳转。而且为了一个”浏览录像”的需求,装一套带 AI 识别能力的 NVR,感觉像买了一辆法拉利用来买买菜。
Shinobi / ZoneMinder / 其他传统 NVR
这些 NVR 都设计成”从摄像头拉流、录制、管理”。但我的录像已经存好了,不需要它们来录。它们的回放界面也偏功能化,手机体验普遍一般。
Jellyfin / Plex / Immich
通用媒体服务器,本质上是”看电影”的。面对 67,000 个按分钟切割的录像文件,它们的索引效率很低。还有一个根本问题:它们不理解”监控录像”这个场景——没有按日期标记覆盖、没有按分钟定位片段的能力。
开源的 gap
一圈看下来就发现:没有一个开源方案专门做”浏览已有监控录像”这一件事。NVR 太重,媒体服务器太偏,都不在点上。这是个挺小众的需求——大部分人要么不需要,要么买了商业方案,要么用 Frigate 跑全套。

但对我来说,这就是明显的 gap。
自研方案:Cam Player
既然没有现成的,就自己写。用 Python 3.9 标准库,零外部依赖,两个文件搞定。
架构
浏览器 (HTML/JS) ←HTTP→ server.py (Python 标准库)
↑
Index (内存: 日期 → 文件列表)
↑
录像目录 + 人物片段目录
后端一个文件 server.py,前端一个文件 index.html。部署就是 python3 server.py,跑在 8099 端口。

API 设计
| 接口 | 返回数据 | 用途 |
|---|---|---|
GET /api/stats |
文件数、数据量 | 首页统计 |
GET /api/dates |
日期列表 | 哪些天有录像 |
GET /api/date-types |
每日录像类型 | 原始/人物/都有 |
GET /api/day/2026-06-25 |
该天所有片段 | 某天详细 |
GET /video/... |
视频流 (206) | 播放 |
前端界面
页面分三块区域:日历、时间线、视频播放器。
日历:月度网格,日期下方带彩色圆点——橙色代表有原始录像,绿色代表有人物片段。左右箭头切换月份,固定 250px 高度防止抖动。
时间线:24 小时柱状条,显示当天所有录影片段。两种显示模式:
- 原始 + 人物同时显示:灰色柱状条叠加
- 仅人物片段:绿色针脚标记突出显示
播放器:HTML5 <video>,支持 206 Partial Content 拖进度条。键盘快捷键:空格播放/暂停、左右箭头快进快退、上下箭头切换片段。

索引机制
启动时扫描录像目录,从文件名解析时间和日期。67,000+ 文件(按文件名格式 YYYYMMDD_HHMMSS.mp4 命名),扫描耗时约 60 秒。结果缓存在内存里,重建索引只需重启服务。
部署方式
# 假设录像存在 /mnt/nas/footage/ 和 /mnt/nas/archive/
SOURCE_DIR=/mnt/nas/footage ARCHIVE_DIR=/mnt/nas/archive python3 server.py
资源占用:CPU 约 6%,内存占用极少(索引建立后)。
如果你习惯用 Docker 管理服务,也可以封装成容器:
FROM python:3.11-slim
COPY server.py index.html /app/
EXPOSE 8099
CMD ["python3", "/app/server.py"]
踩坑记录
1. HTML 缓存拖慢调试
server.py 启动时把 index.html 读入内存缓存。改 HTML 后必须重启才能看到效果,前端调试效率很低。
解决:改为每次请求重新读取文件。牺牲了一丁点性能,换来调试时即时刷新。
2. 日历高度抖动
不同月份的天数和起始星期不同,日历高度会变化——30 天 vs 31 天,周日开始 vs 周六开始,页面布局跟着跳。
解决:给日历容器设固定高度,内部内容滚动。
3. 两种录像的显示区分
同时有原始录像(按分钟切割,连续覆盖全天)和人物片段(每段一个事件),在时间线上需要不同的视觉标记。
决策:两种都选时柱状条显示原始录像覆盖范围;仅选人物时用针脚标记突出,不占比例。
4. 手机端适配
桌面是左右分栏(日历 + 视频),手机屏幕太小放不下。
解决:CSS @media 切换——手机端视频全屏、日历做成弹出抽屉、时间线在底部面板。
5. Range 请求
浏览器要拖动进度条,必须支持 206 Partial Content。家用摄像头录的是标准 H.264 MP4,如果服务器只返回完整文件,就拖不动。
实现:解析 HTTP Range header,返回对应字节范围 + 206 状态码。标准做法,关键是要记得做。
效果
这个单文件 Python 服务跑了几个月了,稳定性和响应速度都满意:
- 60 秒索引 67,000+ 文件
- CPU 占用约 6%
- 手机浏览器直接访问,和桌面体验一致
- 从任意日期到任意片段,点几下就能播放
最近有需求的朋友来家里看到这界面,反应通常是”这个多少钱?”——听说是一个 Python 文件自己写的,都不太信。
总结
这段经历让我重新思考了一个问题:当现有开源方案都不能完全满足需求时,到底是凑合用,还是自己造。
开源社区的繁荣容易让人形成惯性思维——遇到问题第一反应是”找个开源方案”。但开源方案的设计目标往往和你不一样。Frigate 的定位是 AI 检测 NVR,Jellyfin 的定位是家庭媒体服务器。它们都很优秀,但不是为”浏览已有监控录像”这个场景设计的。
在这种情况下,自己写一个反而是更务实的做法——没有依赖、没有学习成本、完全按自己的需求来。而且事实证明,很多时候事情没有你想象的那么复杂。一个 Python 文件、零外部依赖、一晚上搞定的原型,已经稳定跑了几个月。
如果你的需求也很”小众”(不在主流开源项目瞄准的方向上),不妨试试这条路。