Metadata-Version: 2.4
Name: solo-surveillance
Version: 0.3.1
Summary: 自托管、轻量、纯本地运行的 AI 监控 NVR 系统
Project-URL: Homepage, https://github.com/tiancheng91/solo-surveillance
Project-URL: Repository, https://github.com/tiancheng91/solo-surveillance
Project-URL: Bug Tracker, https://github.com/tiancheng91/solo-surveillance/issues
Author: tiancheng91
License: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video :: Capture
Classifier: Topic :: Security
Requires-Python: >=3.11
Requires-Dist: numpy>=1.24.0
Requires-Dist: onvif-zeep>=0.2.0
Requires-Dist: openai>=2.36.0
Requires-Dist: opencv-python-headless>=4.8.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: ultralytics>=8.0.0
Provides-Extra: llm
Requires-Dist: anthropic>=0.49.0; extra == 'llm'
Description-Content-Type: text/markdown

# solo-surveillance

<p>
  <img alt="License" src="https://img.shields.io/github/license/tiancheng91/solo-surveillance?style=flat-square">
  <img alt="Python" src="https://img.shields.io/badge/python-%3E%3D3.11-blue?style=flat-square">
  <img alt="Platform" src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey?style=flat-square">
  <img alt="Last Commit" src="https://img.shields.io/github/last-commit/tiancheng91/solo-surveillance?style=flat-square">
  <img alt="PyPI" src="https://img.shields.io/pypi/v/solo-surveillance?style=flat-square">
  <img alt="CI" src="https://github.com/tiancheng91/solo-surveillance/actions/workflows/ci.yml/badge.svg">
  <a href="https://zread.ai/tiancheng91/solo-surveillance"><img alt="zread" src="https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff"></a>
</p>

> [🇺🇸 English](README.en.md) &nbsp; [🇯🇵 日本語](README.ja.md)

自托管、轻量、纯本地运行的 AI 监控 NVR 系统。  
通过 RTSP 或 ONVIF 连接 IP 摄像头，利用运动检测触发 AI 推理，记录事件——所有操作均在本地运行，零云依赖。

![Web UI 截图](docs/webui.png)

## 工作原理

solo-surveillance 持续监控摄像头，自动判断何时发生值得记录的事件。核心采用**三阶段流水线**：

1. **流接入** — 通过 RTSP/ONVIF 连接每路摄像头，解码视频帧
2. **运动门控** — 帧差检测过滤静态画面；只有真正出现运动的帧才会进入 AI
3. **AI 检测** — YOLOv8 人体检测和/或 LLM 场景理解在运动触发的帧上运行

MotionGate 是系统的性能基石。通过在 AI 推理前过滤掉静态帧，实际部署中通常可减少 **90% 以上**的 AI 调用次数。

每路摄像头运行在各自独立的 `threading.Thread` 中，拥有完全独立的 `RTSPReader`、`MotionGate` 和 `AIPipeline` 实例——摄像头工作线程间无共享可变状态。

## 功能特性

| 功能 | 描述 | 配置键 |
|---|---|---|
| 多路相机 | 每路独立线程，配置与运行时相互独立 | `cameras[]` |
| 双协议接入 | `rtsp://` 直连或 `onvif://` 自动发现 RTSP 地址 | `stream_url` |
| 运动门控 | 帧差检测，预过滤静态帧 | `motion.*` |
| YOLOv8 人体检测 | 内置 YOLO，首次运行自动下载模型 | `detectors.person` |
| LLM 视觉场景 | 使用 Anthropic/OpenAI API 进行复杂场景理解 | `detectors.llm_vision` |
| 区域裁剪 | 仅在归一化 ROI 内检测，保留全分辨率录制 | `region` |
| AI 批量推理 | 多帧采样，按最大置信度合并结果 | `ai.frames` |
| 事件录制 | 按事件类型生成截图 (JPEG) 和视频片段 (MP4) | `recordings.*` |
| 时间线索引 | 基于 CSV 的事件索引，包含起止时间和文件路径 | 自动管理 |
| Web UI | 内置 HTTP 服务器，支持时间线导航、过滤与回放 | `--http` 参数 |
| Home Assistant | 检测到重大事件时推送 REST API 事件 | `hass.*` |
| Hook 脚本 | 事件触发时执行外部命令 | `hooks.*` |
| 自动重连 | RTSP 断流自动恢复，适合 7x24 运行 | 内置于 `RTSPReader` |
| 纯本地运行 | 所有推理、录制和回放均在设备端完成 | — |

## 快速开始

### 1. 安装

```bash
# 方式 A（推荐）——自动隔离环境，无需手动安装
uvx solo-surveillance

# 方式 B——全局安装
pip install solo-surveillance
```

### 2. 配置相机

```bash
curl -O https://raw.githubusercontent.com/tiancheng91/solo-surveillance/main/config.example.yaml
mv config.example.yaml config.yaml
```

编辑 `config.yaml`，填入相机地址：

```yaml
cameras:
  - id: door
    enabled: true
    stream_url: "rtsp://user:password@192.168.1.100:554/stream1"
```

也支持 ONVIF 自动发现：

```yaml
  - id: front_door
    stream_url: "onvif://admin:password@192.168.1.100:80?profile=0"
```

> 完整配置参考 `config.example.yaml`（含 LLM 视觉、HA 集成、Hook 脚本等全部选项）。

### 3. 启动

```bash
solo-surveillance
```

首次启动会自动下载 YOLOv8 模型。看到如下日志即正常运行：

```
INFO  [cam-door] 线程启动: door
INFO  [cam-door] 已连接 RTSP
```

### 4. 打开 Web UI（可选）

```bash
solo-surveillance --http 0.0.0.0:8080
```

浏览器访问 `http://<设备IP>:8080`：

- 按相机、日期、时间段筛选事件
- 缩略图懒加载，点击放大查看截图
- 支持播放 MP4 视频片段
- 右侧时间轴：黄色时段表示有事件，拖拽可快速导航
- 排序切换：默认倒序（最新在前），点击翻转

> Web UI 仅用于本地网络回放，不会将视频上传到云端。

## 命令行参考

```bash
solo-surveillance            # 启动（默认读取当前目录 config.yaml）
solo-surveillance -v         # 调试模式——查看 motion 触发、AI 冷却等详细日志
solo-surveillance -c /path/to/config.yaml  # 指定配置路径
solo-surveillance --http :8080     # 启动 Web UI（默认端口 8080）
solo-surveillance --http 0.0.0.0:9090  # 指定监听地址和端口
```

---

## Home Assistant 集成

支持将检测到的事件实时推送到 Home Assistant 事件总线，用于自动化联动（如灯光、报警、通知）。使用 Python 标准库实现，零额外依赖。

```yaml
hass:
  enabled: true
  url: "http://homeassistant:8123"
  token: "${HASS_TOKEN}"
```

配置后，每个事件（`camera.motion`、`camera.person`、`camera.feeding` 等）会自动 POST 到 HA 的 `/api/events/{event_type}`。

> 详细配置说明见 [docs/homeassistant.md](docs/homeassistant.md)。

---

*以下章节面向进阶用户，详细介绍配置选项与系统设计。*

---

## 配置详解

YAML 格式，`defaults` 块设置全局默认值，`cameras` 列表中每路相机可选择性覆盖。配置值支持 `${ENV_VAR}` 环境变量替换。

核心结构：

```yaml
defaults:
  motion:         # 运动检测参数
  ai:             # AI 推理参数（帧数、冷却）
  recordings:     # 截图/视频录制
  detectors:      # YOLO / LLM 检测器
  region:         # 可选检测区域

cameras:          # 相机列表，每路可覆盖 defaults

hass:             # 可选：Home Assistant 集成
hooks:            # 可选：外部脚本
llm:              # 可选：LLM API 连接配置
```

> 完整配置见 `config.example.yaml`，涵盖所有选项及详细注释。
> 详细配置说明与最佳实践见 [docs/configuration.md](docs/configuration.md)，场景配置示例见 [docs/scenarios.md](docs/scenarios.md)。

> **ONVIF URL 格式**：`onvif://username:password@host:port?profile=N`
> - `profile`：media profile 索引，默认 0
> - 支持 `${ENV_VAR}` 避免明文密码：`onvif://admin:${CAM_PASSWORD}@192.168.1.100`

> **提示**：`config.yaml` 建议加入 `.gitignore`，避免泄露摄像头地址和凭据。

## 数据流

```
RTSP / ONVIF 流 ──> MotionGate (帧差门控)
                     │
              min_change_ratio ≥ threshold?
                     │否└─ 跳过
                     │是
              [AI cooldown 冷却检查]
                     │
              collect_frames() 采集多帧
                     │
              AIPipeline.run_batch() 批量推理
                     │
              显著标签 ≥ threshold?
                     │否└─ 跳过
                     │是
              录制截图/视频 → 追加 timeline.csv
                     │
              Notifier 推送（HA / Hook 脚本）
```

## 录制与 Timeline

```
data/
  {camera_id}/
    {date}/
      snapshots/
        140530_person.jpg      # 事件截图
      clips/
        140530_person.mp4      # 事件视频片段
      timeline.csv             # 本日事件索引
```

`timeline.csv` 格式：

```
start_time,end_time,event_type,snapshot_path,clip_path
2026-05-07T14:05:30,2026-05-07T14:05:35,person,snapshots/140530_person.jpg,clips/140530_person.mp4
```

同类型事件 3 秒内防重，避免连续重复录制。

## Hook 脚本

Hook 脚本在 `config.yaml` 根级别配置（全局，所有事件类型均触发所有脚本）：

```yaml
# 可选：事件触发时执行的外部脚本
hooks:
  - command: scripts/event_logger.sh
```

每个脚本接收命令行参数：

```
--camera-id xiaomi1
--event-type person
--start-time 2026-05-07T14:05:30
--end-time 2026-05-07T14:05:35
--snapshot-path snapshots/140530_person.jpg
--clip-path clips/140530_person.mp4
--labels '{"person": 0.85}'
```

## 检测器扩展

内置 `PersonYoloDetector`（YOLOv8 人体检测），可按以下步骤添加自定义检测器：

1. 继承 `VisionDetector` 或 `AudioDetector`（`surveillance/detectors/base.py`）
2. 设置唯一 `name` 类变量
3. 实现 `analyze_batch()` 返回 `VisionResult` / `AudioResult`（接收多帧，自行决定如何使用）
4. 在 `AIPipeline.from_camera_detectors()` 中注册

```python
from surveillance.detectors.base import VisionDetector, VisionResult, VisionContext

class FireDetector(VisionDetector):
    name = "fire_detector"

    def analyze_batch(self, frames, ctx: VisionContext | None = None):
        # 火焰检测逻辑...
        return VisionResult(labels={"fire": 0.92})
```

## 项目结构

```
solo-surveillance/
├── config.example.yaml              # 示例配置（含全部选项）
├── pyproject.toml                   # 包元数据、依赖、CLI 入口点
├── surveillance/                    # 核心应用包
│   ├── __main__.py                  # `python -m surveillance` 入口
│   ├── main.py                      # CLI 解析、线程编排
│   ├── config_loader.py             # YAML 加载、deep_merge、${ENV_VAR} 展开
│   ├── stream.py                    # RTSPReader — 帧捕获与自动重连
│   ├── motion.py                    # MotionGate — 帧差运动检测
│   ├── region.py                    # 帧裁剪至归一化 ROI
│   ├── vision_burst.py              # 多帧采样与结果合并
│   ├── recordings.py                # 截图/视频录制与 timeline CSV
│   ├── onvif.py                     # ONVIF 发现 → RTSP URL 解析
│   ├── http_server.py               # 内置 Web UI 与 API 服务器
│   ├── static/index.html            # 单页 Web UI
│   └── detectors/
│       ├── base.py                  # 抽象 VisionDetector / AudioDetector
│       ├── person_yolo.py           # YOLOv8 人体检测
│       ├── llm_vision.py            # LLM API 场景分析
│       └── pipeline.py              # AIPipeline — 编排所有检测器
└── docs/                            # 文档
    ├── configuration.md             # 详细配置指南与最佳实践
    ├── scenarios.md                 # 场景配置示例
    └── homeassistant.md             # Home Assistant 集成详情
```

### 线程模型

- 每路已启用相机创建独立 `threading.Thread`
- `threading.Event` 协调关闭（SIGINT / SIGTERM）
- 每线程持有独立 `RTSPReader`、`MotionGate`、`AIPipeline`（无共享状态）
- HTTP 服务器在守护线程中运行，不阻塞主流程
- Hook 脚本在独立守护线程中执行（30s 超时自动终止）

## 依赖

| 依赖 | 用途 | 使用模块 |
|---|---|---|
| opencv-python-headless | RTSP 捕获、图像处理、视频编码 | stream, motion, recordings, llm_vision |
| numpy | 帧数组运算 | 全局使用 |
| ultralytics | YOLOv8 推理 | detectors/person_yolo.py |
| PyYAML | 配置文件解析 | config_loader.py |
| onvif-zeep | ONVIF 设备发现与控制 | onvif.py（可选） |
| anthropic / openai | LLM 视觉 API | detectors/llm_vision.py（可选） |

要求 Python >= 3.11，支持 macOS 和 Linux。

## License

MIT
