WebMeet Recorder 是一个自托管的网页会议参会与录制服务。它通过 Playwright 进入 Zoom Web Client,可只保持在线参会,也可使用 Xvfb、PulseAudio 与 FFmpeg 捕获会议画面和系统声音,并提供带登录态管理、定时入会、实时截图及录像下载的网页控制台。
本项目不是 Zoom 官方产品,也不隶属于或代表 Zoom Video Communications, Inc.。请只录制你有权参加和录制的会议,并遵守所在地法律及会议参与者的知情同意要求。
- 支持 Zoom 子域下的
/j/<meeting-id>与/w/<webinar-id>链接 - 完整保留 Webinar
tk、会议pwd等邀请参数 - 支持 Cookie Editor JSON 登录态、到期摘要与自动保活,以及可选的 Zoom 账号密码后备登录
- 每个立即或定时任务可选择“参会并录制”或“仅参会”
- 支持立即录制和定时录制,可设置会议时间及提前入会分钟/小时
- 可识别 Zoom 中英文会议结束提示,并按任务选择自动安全停录或等待手动停止
- 定时任务持久化,服务重启后自动恢复等待
- 录制 Zoom 网页画面和 PulseAudio 系统输出声音
- 支持 MKV 和 fragmented MP4
- 支持任务状态、实时截图、失败截图、Debug 操作截图及录像下载
- 新建任务默认开启 Debug 操作截图,可在高级选项中按任务关闭
- 自带 Basic Auth,可自动生成持久化自签名 TLS 证书
- 使用根目录
VERSION管理版本;版本变更后 GitHub Actions 自动发布 Release 与 GHCR 镜像
网页控制台提供当前任务、会议任务表单、Zoom 身份验证、定时任务和录像归档五个区域,并针对手机与桌面浏览器做了响应式适配。任务模式使用显式卡片切换;选择“仅参会”时,录像格式会自动隐藏,状态区会显示“参会中”和“不生成录像文件”。
默认采集参数:
| 项目 | 参数 |
|---|---|
| 虚拟显示器 | 1920×1200×24 bit |
| 帧率 | 24 FPS |
| 视频 | H.264、CRF 26、veryfast、yuv420p |
| 音频 | PulseAudio 系统输出、AAC 128 kbps |
| 默认格式 | MKV |
正式录像会动态裁掉 Chromium 顶部区域,因此成品高度通常接近 1080 像素;实时截图和 Debug 截图保留完整的 1920×1200 虚拟屏幕。
完整步骤见 部署文档。准备公共仓库时请先阅读 安全发布文档,不要直接推送包含旧演示文件的历史。
cp .env.example .env
# 编辑 .env,至少设置 WEB_PASSWORD 与 WEB_TLS_HOST
docker compose build
docker compose up -d
docker compose logs -f zoomy已有 zoomy-zoomy:latest 本地镜像的服务器可以继续复用旧镜像,不需要重新构建:
docker compose up -d --no-build --force-recreate默认访问地址为:
https://<服务器公网 IP 或域名>:8080
创建任务时启用“定时执行”,填写:
- Zoom 会议或 Webinar 链接;
- 会议开始时间;
- 提前进入数值与单位;
- 任务模式、任务名称及可选入会参数。
“仅参会”模式会正常登录并进入 Zoom Web Client,保留实时截图、会议结束检测和手动停止能力,但会跳过音频等待与 FFmpeg,不生成 MKV/MP4。未带 mode 字段的旧 API 请求和旧定时任务仍默认按“参会并录制”执行。
等待执行的定时任务可直接在列表中点击“修改”,原配置会回填到会议任务表单。运行中、已完成或执行失败的任务不可修改。单独会议密码不会回显:编辑时留空表示保留,输入新值表示替换,勾选“清除已保存的单独会议密码”才会删除。
“会议结束后自动停止”默认开启,并随立即任务或定时任务单独保存。开启时,会扫描 Zoom PWA 主页面及 #webclient 子 iframe,检测主持人结束会议/网络研讨会、会议已结束、被移出或页面离开 Web Client,并安全结束 FFmpeg;即使仍在等待首次音频也会立即结束任务。关闭时,控制台会提示已检测到会议结束,录像持续到手动点击“停止”。
浏览器会把本地时间转换为带时区的 ISO 时间发送给服务器。例如会议时间是 10:00、提前 30 分钟,服务会在 09:30 自动启动浏览器并进入会议。若此时已有会议任务,计划会保留并在任务执行器空闲后执行。
定时任务保存在 /data/schedules.json,采用原子写入和 0600 权限。API 不回显单独填写的会议密码,但会议链接本身会按原样显示。
Cookie 与账号密码可以单独使用,也可以同时配置。只有 Cookie 时直接使用 Cookie;只有账号密码时必须明确完成账号和密码登录,绝不会因为登录控件暂未出现就推断为“已经登录”;两者同时存在时,录制器先注入 Cookie 并打开 https://app.zoom.us/wc 检查会话,仍处于登录状态就直接入会,已退出登录才填写保存的账号密码。两者都未配置时,公开会议仍可匿名加入。FFmpeg 只会在登录完成、进入会议且音频就绪后启动,因此账号、密码和登录页面不会进入录像。
- 在本地 Chrome 或 Edge 登录本人有权使用的 Zoom 账号。
- 打开目标 Zoom 子域,例如
https://example.zoom.us/。 - 使用可信的 Cookie Editor,选择 Export → JSON。
- 通过本服务的 HTTPS 控制台上传 JSON。
- 确认显示有效 Cookie 数量后删除本地导出文件。
Cookie 文件等同于账号凭据。它保存在 /auth/zoom-cookies.json,不会被状态 API 回显,也已被 Git 和 Docker 构建上下文排除。
上传后,后台任务会先访问 https://app.zoom.us/wc,确认页面仍处于登录状态后保存 Zoom 返回的最新 Cookie;以后按配置的固定间隔和最早持久 Cookie 到期时间中较早者继续刷新。会议任务运行时后台保活会暂停,由实际会议浏览器在入会成功后、参会期间及关闭前保存最新 Cookie。刷新失败或页面已经退出登录时不会覆盖原文件,控制台会显示失败和下次重试时间。
保活适用于 Zoom 允许滑动续期的登录会话,不能绕过服务端强制过期、账号安全策略、MFA 或 SSO。若控制台提示登录态失效,仍需重新从本人浏览器导出。
通过 HTTPS 控制台填写 Zoom 账号和密码并保存。服务只返回“是否配置”和更新时间,不回显账号或密码;每次录制前仅在 Cookie 会话失效时尝试一次自动登录。验证码、MFA、SSO 以及 Google/Microsoft/Apple 等第三方登录不能自动完成,遇到这些页面时任务会停止并提示人工处理。
凭据以 0600 权限保存在 /auth/zoom-credentials.json。该文件是明文凭据,必须像私钥一样保护、加密备份,并确保 auth/ 永不提交到 Git。自动登录期间不会生成 Debug 截图;失败截图会先清除输入框并隐藏账号区域。
webmeet-recorder/
├── app/
│ ├── static/ # 独立 HTML、CSS、JavaScript
│ ├── server.py # Web/API 与任务编排
│ ├── cookie_keepalive.py # Zoom Cookie 空闲保活与续期调度
│ ├── recorder.py # Playwright、Xvfb、PulseAudio、FFmpeg
│ ├── scheduler.py # 定时任务持久化与到点执行
│ ├── tls_server.py # 独立线程 TLS 握手与超时保护
│ ├── zoom_auth.py # Zoom URL、Cookie 与账号密码管理
│ ├── entrypoint.sh
│ └── Dockerfile
├── docs/DEPLOYMENT.md
├── docs/PUBLISHING.md
├── tests/
├── .github/workflows/
├── VERSION
├── docker-compose.yml
└── .env.example
Compose 中仍保留 zoomy 服务名、容器名以及 zoomy-zoomy:latest 的兼容默认值,确保已有服务器可以原地更新。产品名称、页面和新镜像发布名称均使用 WebMeet Recorder。
根目录 VERSION 使用语义化版本,例如:
1.1.0
向 main 或 master 推送包含 VERSION 变更的提交后,Release 工作流会:
- 校验语义化版本并确认
v<version>尚不存在; - 运行 Python 测试、语法检查、前端 JavaScript 检查和 Compose 校验;
- 构建容器并推送
ghcr.io/<owner>/<repo>:<version>与:latest; - 创建
v<version>GitHub Release,并自动生成 Release Notes。
没有修改 VERSION 的普通提交不会发布新版本。详细发布和回滚方法见 部署文档。
除 /health 外,所有端点均受 Basic Auth 保护。
| 方法 | 路径 | 用途 |
|---|---|---|
GET |
/health |
健康状态与版本 |
GET |
/api/status |
当前录制状态 |
POST |
/api/start |
立即启动录制 |
POST |
/api/stop |
停止当前录制 |
GET |
/api/schedules |
定时任务列表 |
POST |
/api/schedules |
创建定时任务 |
PUT |
/api/schedules?id=... |
修改等待执行的定时任务 |
DELETE |
/api/schedules?id=... |
删除非运行中的定时任务 |
GET |
/api/recordings |
录像列表 |
DELETE |
/api/recordings?path=... |
删除指定的非活动录像文件 |
GET |
/api/download?path=... |
下载录像或查看失败截图 |
GET |
/api/screenshot |
获取当前虚拟显示器截图 |
GET |
/api/auth/status |
Zoom Cookie 非敏感摘要 |
POST |
/api/auth/cookies |
通过 HTTPS 上传 Cookie JSON |
DELETE |
/api/auth/cookies |
通过 HTTPS 删除登录态 |
GET |
/api/auth/credentials/status |
账号密码配置的非敏感摘要 |
POST |
/api/auth/credentials |
通过 HTTPS 保存或替换账号密码 |
DELETE |
/api/auth/credentials |
通过 HTTPS 删除账号密码 |
python -m unittest discover -s tests -v
python -m py_compile app/cookie_keepalive.py app/media_store.py app/recorder.py app/server.py app/scheduler.py app/tls_server.py app/zoom_auth.py
node --check app/static/app.js
docker compose config --quiet以下内容不会进入 Git 或容器构建上下文:
.envauth/下的 Cookie 与账号密码certs/下的私钥和证书data/下的定时任务数据recordings/下的录像、日志与截图- 本地 rootfs、Python 缓存和编辑器文件
公开仓库前仍建议执行一次:
git status --short
git grep -nE '公网IP|真实会议号|真实令牌|真实密码'不要把带 tk、pwd 的真实会议链接粘贴到 Issue、日志附件或公开截图中。