init
This commit is contained in:
126
scripts/demo_client/README.md
Executable file
126
scripts/demo_client/README.md
Executable file
@@ -0,0 +1,126 @@
|
||||
# Demo Client
|
||||
|
||||
一个浏览器里的单页 demo,用于手动触发 `app/api.py` 里部分 `/client/*` 接口:开始/结束手术、查询结果等。语音待确认、TTS 与麦克风录音请使用 **`voice-confirmation-web/`**(独立静态页)或其它专用客户端,或通过 Swagger/`curl` 直接调 API。
|
||||
|
||||
## 结构
|
||||
|
||||
```
|
||||
scripts/demo_client/
|
||||
server.py # 基于 stdlib 的静态服务器;额外暴露 /labels.json
|
||||
index.html # 单文件页面(原生 JS,零构建依赖)
|
||||
fake_rtsp_from_file.py # 无真摄像头时:把本地视频按文件时长推一次到 RTSP(ffmpeg + Docker MediaMTX)
|
||||
```
|
||||
|
||||
## 调试:无真实摄像头,用录好的视频模拟 RTSP
|
||||
|
||||
监控服务**只从 RTSP URL 拉流**(`cv2.VideoCapture`),**没有**「上传视频文件」的 HTTP 接口;在不改 Python 后端的前提下,只能让「摄像头地址」指向一个**真实可连的 RTSP 源。
|
||||
|
||||
推荐做法:在**本机**把视频文件用 **ffmpeg** 推到本机上的 **RTSP 服务**(脚本用 Docker 启动 [MediaMTX](https://github.com/bluenviron/mediamtx)),得到 `rtsp://127.0.0.1:<端口>/<路径>`,再通过**环境变量**告诉后端(**只改配置,不改仓库里的后端代码**):
|
||||
|
||||
**单路**(一个文件、一个 `camera_id`,兼容旧命令):
|
||||
|
||||
```bash
|
||||
# 依赖:ffmpeg、Docker(首次会拉取 bluenviron/mediamtx)
|
||||
cd /path/to/operation-room-monitor-server
|
||||
python3 scripts/demo_client/fake_rtsp_from_file.py /path/to/recording.mp4 --port 18554 --path demo
|
||||
```
|
||||
|
||||
**两路**(两路不同视频、两个 `camera_id`;**一个** MediaMTX、**两路** ffmpeg;每路用不同的 `RTSP_PATH`):
|
||||
|
||||
```bash
|
||||
python3 scripts/demo_client/fake_rtsp_from_file.py --port 18554 \
|
||||
--stream 'or-cam-01|./a.mp4|demo1' \
|
||||
--stream 'or-cam-02|./b.mp4|demo2'
|
||||
```
|
||||
|
||||
`--stream` 格式为 `CAMERA_ID|文件路径|RTSP_PATH`(竖线分隔,整条加引号),脚本会在 stderr 打印含 `video_rtsp_urls` 与 `voice_or_room_bindings: []` 的 **站点 JSON 片段**,可合并进 `OR_SITE_CONFIG_JSON_FILE`。
|
||||
|
||||
在**另一终端**启动监控服务前 `source` 或手动 `export` 上述变量,使 `POST /client/surgeries/start` 里使用的 `camera_ids`(如 `or-cam-01,or-cam-02`)能解析到对应 URL。Demo 页里「将 camera_id 填到开始手术」可一键同步两路 id。
|
||||
|
||||
### 监控在 Docker、假 RTSP 在宿主机(推荐联调拓扑)
|
||||
|
||||
常见安排是:**假摄像头脚本**(`fake_rtsp_from_file.py` + ffmpeg + MediaMTX)在**宿主机**终端里跑,推流地址是 `rtsp://127.0.0.1:<端口>/...`;**监控 API 服务**在 **Docker 容器**里跑,容器里的进程要访问宿主机上的 RTSP,应使用:
|
||||
|
||||
- **macOS / Windows Docker Desktop**:`rtsp://host.docker.internal:<端口>/<路径>`
|
||||
- **Linux**:`host.docker.internal` 可能未预置,可任选其一:
|
||||
- 给该服务容器加 `--add-host=host.docker.internal:host-gateway`(Docker 20.10+),或
|
||||
- 直接把 URL 写成宿主在 **docker0/桥接网** 上可达的局域网 IP(如 `192.168.x.x`),保证从容器内 `curl`/`ffprobe` 能通
|
||||
|
||||
生产/容器环境请使用 `**OR_SITE_CONFIG_JSON_FILE`** 指向完整站点 JSON(含 `video_rtsp_urls` 与 `voice_or_room_bindings`)。**不要**在仅容器可解析的配置里写 `127.0.0.1` 去指宿主机上的 RTSP(`127.0.0.1` 在容器内是容器自己)。
|
||||
|
||||
若监控与假 RTSP **都在宿主机同一系统**里直接跑(非容器),则用 `rtsp://127.0.0.1:...` 即可;否则应使用上面「容器连宿主」的写法。
|
||||
|
||||
发布失败时,可尝试把输入转码后再推流(示例,需自行调整):
|
||||
|
||||
```bash
|
||||
ffmpeg -re -i recording.mp4 -c:v libx264 -pix_fmt yuv420p -f rtsp -rtsp_transport tcp rtsp://127.0.0.1:18554/demo
|
||||
```
|
||||
|
||||
(仍须先自行启动 MediaMTX 或等价 RTSP 服务端;上例为**播完即止**,若要循环请加 `-stream_loop -1`。)
|
||||
|
||||
Demo 页面「调试:两路视频」中可用 **选择视频** / **拖放** 为路1/路2 指定文件,并配合下面 **一键开录** 上传。若必须完全手跑 `fake_rtsp_from_file.py`,请将其打印的站点 JSON 合并进 `OR_SITE_CONFIG_JSON_FILE`。
|
||||
|
||||
## 一键开录(不再手抄命令)
|
||||
|
||||
在 §4.1 勾选 **「一键联调」** 后,在「调试」里为**路1/路2**各选一段视频,再点 **开始手术**,浏览器会把两路视频 **multipart 上传到监控 API**(`POST /internal/demo/orchestrate-and-start`),由服务进程依次:
|
||||
|
||||
1. 落盘两路视频到临时目录
|
||||
2. 用 Docker 起 MediaMTX、两路 ffmpeg 推 RTSP(与 `fake_rtsp_from_file.py` 等效)
|
||||
3. 把当前假流的 **video_rtsp_urls** 合并写入 `OR_SITE_CONFIG_JSON_FILE`(保留已有 `voice_or_room_bindings`;与开录/拉流同进程,固定本机回环)
|
||||
4. 调用与普通开录相同逻辑
|
||||
|
||||
**需同时满足**:
|
||||
|
||||
- `.env` 中 `DEMO_ORCHESTRATOR_ENABLED=true`(并重启 API)
|
||||
- 已设置 `OR_SITE_CONFIG_JSON_FILE` 指向**可写**的站点 JSON;Docker 中请用 **bind-mount** 到容器内同一路径
|
||||
- **运行 `main.py` 的进程**能执行本机 `docker` 与 `ffmpeg`(与手动跑 `fake_rtsp_from_file` 相同)。**仅将 API 放 Docker、且不挂载** `/var/run/docker.sock` 时,容器内往往无法为你在宿主机起 MediaMTX,此时请继续用手动假流方式。
|
||||
|
||||
由于每次解析都会重新读取 `video_rtsp_url_map()`,覆盖 JSON 后**无需重启**主服务即可被下一次开录用到。
|
||||
|
||||
## 运行方式
|
||||
|
||||
```bash
|
||||
# 1) 启动宿主机 API(默认自动使用已激活的非 base conda 环境,否则使用 uv)
|
||||
./deploy.sh
|
||||
|
||||
# 2) 启动 demo 客户端静态服务(默认 0.0.0.0:38081,局域网可访问)
|
||||
./start_demo_client.sh
|
||||
# 或直接:
|
||||
python scripts/demo_client/server.py
|
||||
# 浏览器热重载(需 dev 依赖;会生成本目录 labels.json 供静态托管):
|
||||
uv run --group dev python scripts/demo_client/server.py --live
|
||||
# 或指定端口:
|
||||
python scripts/demo_client/server.py -p 9000 --host 0.0.0.0
|
||||
|
||||
# 3) 浏览器访问:
|
||||
open http://127.0.0.1:38081/
|
||||
```
|
||||
|
||||
页面顶部的「服务端 Base URL」默认指向当前页面同主机的 `:38080`。局域网访问 Demo 页时,通常会自动变成 `http://<宿主机局域网IP>:38080`;如果后端部署在其他主机/端口,直接改这里即可。
|
||||
|
||||
### 与「语音确认」页一致的热重载
|
||||
|
||||
- **API**:默认在宿主机 conda/uv 环境运行;修改后端代码后重启 `./deploy.sh` 进程即可。
|
||||
- **本 Demo**:`--live`(livereload,监视本目录与 `app/resources/consumable_classifier_labels.yaml`)。
|
||||
- **`voice-confirmation-web/`**:部署载体与 **`start_http.*`** 说明见 [voice-confirmation-web/README.md](../../voice-confirmation-web/README.md);仓库根的 `start_voice_confirmation_web.sh|.bat` 为封装入口(`--plain` / `--single` / livereload)。
|
||||
|
||||
## 页面包含什么
|
||||
|
||||
- `GET /health` 连通性检查
|
||||
- §4.1 `POST /client/surgeries/start` — 含 `surgery_id` 校验、`camera_ids` 多值输入、`candidate_consumables` 标签编辑器(初始值从 `/labels.json` 载入,可增删)
|
||||
- §4.2 `POST /client/surgeries/end`
|
||||
- §4.3 `GET /client/surgeries/{id}/result` — 以表格渲染 `details` 与 `summary`
|
||||
- **调试:无摄像头** — 多路视频选择与 `camera_id`;一键联调见上文;手跑假流见 `fake_rtsp_from_file.py` 与本文「调试:无真实摄像头」
|
||||
|
||||
`GET /client/surgeries/{id}/pending-confirmation` 与 `POST .../resolve` 仍由后端提供,本 Demo 页**不包含**相应 UI(无待确认拉取、无 TTS、无麦克风录音)。
|
||||
|
||||
右侧「响应日志」按时间倒序展示每次请求的 method/url/status/body,便于联调截图。
|
||||
|
||||
## 关于 `/labels.json`
|
||||
|
||||
`server.py` 在进程启动时读 `app/resources/consumable_classifier_labels.yaml` 的 `names` 映射并返回 `{"labels": [...]}`。优先用 `PyYAML`(主项目依赖已间接引入),缺失时回退到手写的最小 YAML 解析器(仅兼容该文件的已知形状)。
|
||||
|
||||
## 关闭 CORS(生产环境)
|
||||
|
||||
独立部署的 Demo 页和语音确认页跨域访问 API 时,需要 `DEMO_CORS_ENABLED=true`。正式部署建议在 `.env` 里把 `DEMO_CORS_ORIGINS` 收窄为具体来源,例如 `http://my-host:38081,https://or-demo.example.com`。
|
||||
|
||||
Reference in New Issue
Block a user