群晖 Surveillance Station 用 SYNO.SurveillanceStation.EventCenter.Event(未公开 文档的内部接口,参数是下划线风格 camera_ids/start_time/end_time,公开文档里的 cameraIds/fromTime/toTime 是旧版 Event API 的参数名,两者不通用)记录了摄像头 真实的运动侦测事件(event_type=10=运动,start_time+duration 精确到秒),比自己 本地跑 ffmpeg 帧差分更准、不需要额外算力,之前一直在这台 NAS 上验证可行性。 新增 DsmMotionClient:process_video() 分析每段视频前,先用视频的 [event_start, event_start+duration] 时间窗查一次这个接口,窗口内一条运动事件都 没有就跳过云端分析(标记 done,compute_provider=skipped_no_motion),省掉长期 无人时段白白消耗的 Gemini/NVIDIA 配额。 安全设计:查询本身失败/未配置/账号密码没填一律 fail-open(当作"有运动",照常 分析),不会因为这层可选优化漏检真实事件——这是一个纯粹的省配额优化,不能反过来 影响监控系统的可靠性。 新增 12 个单测覆盖:禁用/缺凭证时的 fail-open、登录失败、网络异常、无事件、有 事件、按 camera_id 过滤(响应是按 ds_id 分组不是按 camera_id,容易搞混)、 最短运动时长阈值、session 过期重登录重试、env 变量解析。 账号密码走 .env 的 DSM_ACCOUNT/DSM_PASSWORD,不明文入库。
140 lines
7.1 KiB
YAML
140 lines
7.1 KiB
YAML
# FAM-Edge 配置文件 (Oracle 端) - 新架构 v2
|
||
#
|
||
# 新架构(2026-08-21 重构):
|
||
# 1. 不再切片/抽帧:整视频直传云端 VLM(Gemini 用 Files API,NVIDIA 用整视频 video_url)
|
||
# 2. 视频来源:rclone 从 Google 硬盘实时同步到本地 local_dir,监听目录处理新视频
|
||
# 3. Oracle 自建 SQLite 库存储所有视频摘要/事件/人物,并对外提供同步接口供 NAS 拉取
|
||
# 4. 独立 person_service 汇总全量人物 -> LLM 合并为规范人物表 -> 回灌视频提示
|
||
# 5. NAS 仅作管理后台,每 30 分钟从甲骨文拉增量镜像到本地 MariaDB
|
||
|
||
# Oracle 端 HTTP 服务
|
||
server:
|
||
host: "0.0.0.0"
|
||
port: 5000
|
||
|
||
# Google 硬盘同步(rclone 负责同步落地,本段仅描述监听行为)
|
||
gdrive_sync:
|
||
enabled: true
|
||
local_dir: "/opt/fam-edge/gdrive_videos" # rclone 同步落地目录(video_processing 监听此目录)
|
||
watch_interval_sec: 30 # 监听新视频的轮询间隔
|
||
camera_name: "客厅" # 摄像头名称(注入视频提示)
|
||
# 文件名解析开始时间:监控文件名含时间戳时使用(如 2026-08-21_081500.mp4)
|
||
parse_start_from_filename: true
|
||
|
||
# Oracle 本地库(视频摘要/事件/人物)
|
||
oracle_db:
|
||
path: "/opt/fam-edge/data/oracle.db"
|
||
|
||
# NAS 拉取同步接口鉴权 token(与 NAS oracle_sync.token 一致,走 .env,不明文入库)
|
||
sync_api:
|
||
token: "${ORACLE_SYNC_TOKEN}"
|
||
|
||
# 人物识别服务
|
||
person_service:
|
||
enabled: true
|
||
schedule_interval_sec: 1800 # 每 30 分钟重新汇总一次人物
|
||
model: "gemini" # 用哪个模型做人物合并(vision 模型也支持纯文本)
|
||
|
||
# 视频处理
|
||
video_processing:
|
||
max_concurrent: 1 # 消费者线程数(串行处理,避免云端并发超额)
|
||
timeout: 900 # 兜底单视频分析超时
|
||
timeout_multiplier: 2 # 模型消费超时倍数:在 models[i].timeout 原值上 ×2(大视频上传+分析耗时)
|
||
max_retries: 10 # 单视频失败最大重试次数(配额/过载等瞬时故障给足重试机会)
|
||
retry_interval_sec: 3600 # 失败重试最小间隔:距上次失败 ≥1h 才重新入队,等配额恢复
|
||
file_validate: true # 登记入队前用 ffprobe 校验文件可解码;失败标记 invalid 不入队
|
||
stable_window_sec: 60 # 文件 mtime 稳定窗口:写入中(rclone 同步未完成)的文件跳过本轮
|
||
# 降级顺序:先 gemini 整视频,失败再 nvidia 整视频;两者都失败 -> 标记 failed
|
||
vision_order: ["gemini", "nvidia"]
|
||
|
||
# DSM 运动侦测预过滤(2026-08-22 新增):分析前先查一下群晖 Surveillance Station
|
||
# 自己记录的运动侦测事件(SYNO.SurveillanceStation.EventCenter.Event,未公开文档的
|
||
# 内部接口,参数名是下划线风格 camera_ids/start_time/end_time),这段时间窗口一条
|
||
# 运动事件都没有就跳过云端分析(标记 done,compute_provider=skipped_no_motion),
|
||
# 省掉长期无人时段白白消耗的 Gemini/NVIDIA 配额。
|
||
# 账号密码走 .env(DSM_ACCOUNT/DSM_PASSWORD),不明文入库;查询失败/未配置一律
|
||
# fail-open(照常送云端分析),不会因为这层可选优化漏检真实事件。
|
||
dsm_motion_prefilter:
|
||
enabled: true
|
||
host: "192.168.50.64"
|
||
port: 5000
|
||
account: "${DSM_ACCOUNT}"
|
||
password: "${DSM_PASSWORD}"
|
||
camera_id: 2 # Surveillance Station 里 Generic_ONVIF-001 的 camera_id
|
||
min_motion_seconds: 0 # 窗口内运动事件总时长需 ≥ 此值才算"有运动"(0=有事件就算)
|
||
timeout_sec: 10
|
||
|
||
# 智能问答降级链(与视频分析独立):Gemini -> NVIDIA -> 本地 Ollama
|
||
models:
|
||
- provider: "gemini"
|
||
role: "vision"
|
||
enabled: true
|
||
model_name: "gemini-flash-latest" # 主模型(每日免费配额 20 请求,按模型独立)
|
||
fallback_models:
|
||
- "gemini-flash-lite-latest"
|
||
api_key: "${GEMINI_API_KEY}"
|
||
# 多 Key 轮换(各自独立 Google Cloud 项目,配额互不影响):主 key 配额用尽时
|
||
# 依次尝试这些 key,每个 key 都会重新走一遍模型 fallback 链。key 本身放 .env,
|
||
# 这里只放环境变量名,不直接写密钥。
|
||
extra_api_keys:
|
||
- "${GEMINI_API_KEY_2}"
|
||
- "${GEMINI_API_KEY_3}"
|
||
- "${GEMINI_API_KEY_4}"
|
||
# 每个 key 对应的 Google Cloud 项目名(顺序对应上面 api_key + extra_api_keys),
|
||
# 用于模型统计页展示是哪个项目在跑,不填则退回 "key1/key2/..."
|
||
key_labels:
|
||
- "智能摄像头-1"
|
||
- "智能摄像头-2"
|
||
- "智能摄像头-3"
|
||
- "智能摄像头-4"
|
||
timeout: 600
|
||
# 模型级独立超时(最终值,不参与编排层 ×2 放大)
|
||
# gemini-flash-lite 实测 ~22-34s,按用户要求放宽至 8 分钟(480s),避免大视频/排队时过早切断
|
||
model_timeouts:
|
||
"gemini-flash-lite-latest": 480
|
||
circuit_breaker:
|
||
enabled: true
|
||
threshold: 5
|
||
cooldown: 300
|
||
|
||
- provider: "nvidia"
|
||
role: "vision"
|
||
enabled: true
|
||
# 模型可用性实测记录(2026-08-21,用真实短视频逐个探测 chat.completions 接口):
|
||
# omni(本行 model_name):video_url 只认 base64 data URI,NVCF asset_id 引用
|
||
# 方式对它直接 500("Only base64 data URLs are supported for now")——本适配器
|
||
# 已改为 base64 内嵌整段视频,见 max_base64_mb。确认可用(真实返回结构化 JSON)。
|
||
# nemotron-nano-12b-v2-vl:走 asset_id 引用需要 NVCF-ASSET-DIR/
|
||
# NVCF-FUNCTION-ASSET-IDS 请求头,这两个头的值由 NVCF 服务端按内部路径生成,
|
||
# 客户端传什么都 400 "Invalid NVCF-ASSET-DIR",标准 OpenAI 兼容调用打不通,已移除。
|
||
# meta/llama-3.2-11b-vision-instruct:明确不支持视频输入
|
||
# ("At most 0 video(s) may be provided"),只能单图,已移除。
|
||
# base64 方案受请求体大小限制(实测约 25MB 上限),真实监控视频压缩后通常在
|
||
# 20MB 上下,超过 max_base64_mb 直接跳过(不做注定失败的慢速编码),不是本地
|
||
# 故意限制过窄——这是当前唯一能打通的 NVIDIA 视频理解路径。
|
||
model_name: "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning"
|
||
fallback_models: []
|
||
base_url: "https://integrate.api.nvidia.com/v1"
|
||
api_key: "${NVIDIA_API_KEY}"
|
||
timeout: 600
|
||
max_base64_mb: 20 # 超过此大小直接跳过 NVIDIA,不做注定失败的编码+上传
|
||
switch_interval_sec: 5 # 模型切换间隔:一个失败后等待再试下一个(未来加模型时用)
|
||
model_timeouts: # 模型级独立超时(最终值,不参与 ×2)
|
||
"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning": 300
|
||
circuit_breaker:
|
||
enabled: true
|
||
threshold: 5
|
||
cooldown: 300
|
||
|
||
# 本地模型:纯文本 qwen2.5:7b,仅参与智能问答兜底
|
||
- provider: "ollama"
|
||
role: "text"
|
||
usage: "qa_fallback"
|
||
enabled: true
|
||
model_name: "qwen2.5:7b"
|
||
base_url: "http://localhost:11434"
|
||
timeout: 120
|
||
num_predict: 512
|
||
circuit_breaker:
|
||
enabled: false
|