feat: 事件时间轴缩略帧 + 人物管理头像 + 人物合并硬规则校验
## 新架构:Oracle 集中计算 + NAS 代理展示 ### Oracle 端 (fam-edge) - 新增 frame_service: ffmpeg 视频抽帧 + VLM 人物定位裁剪头像(磁盘缓存) - 新增 /api/oracle/frame: 按 video_id+ts 抽帧返回 jpeg(带 token) - 新增 /api/oracle/avatar: 按 label 生成人物头像(VLM 定位人物 + 兜底整帧居中) - 新增 person_identifier: 人物身份识别模块 - Gemini 适配器支持 flash/flash-lite 双模型切换,429 自动降级 - frame_service VLM 全模型 429 时进入 10 分钟熔断,避免每次请求白打配额 - 兜底头像不落缓存,配额恢复后自动重试 VLM 精确定位 ### 人物合并硬规则校验(框架级修复) - person_service: LLM 合并结果落库前加硬冲突检测 - 性别冲突 → 绝不合并 - 年龄档跨未成年/成年 → 绝不合并(防止把爷爷/宝宝并进同一人) - oracle_db: upsert_person 入口剥离括号后缀(人物A(别名:人物B) → 人物A),消灭垃圾人物行 - 修复 set_canonical 丢弃 source 参数的 bug(旧代码硬编码 'manual' 导致错误合并被永久固化) - get_events_for_label: 只提取该身份组的特征文本,头像定位更精准 ### NAS 端 (fam-core) - 新增 img_proxy: /api/proxy/frame 和 /api/proxy/avatar 代理 Oracle 图片 - app.py 注册 img_bp 蓝图 - oracle_sync / db_layer / member_manager 同步人物表 ### UI 端 (fam-ui) - 事件时间轴: 每条事件卡片加时间点缩略帧 - 人物管理: 每人卡片加头像(150x150 圆角) - parse_persons: 剥离括号备注,与 Oracle 归一化一致 - 新增 EventItem 组件、Timeline 页改造 - Chat / ServiceStatus 页相应调整 ### 数据库 - scripts/ddl.sql: 同步表结构更新 - Oracle people 表: features_json / display_uid / source 字段完善
This commit is contained in:
31
.workbuddy/memory/2026-08-20.md
Normal file
31
.workbuddy/memory/2026-08-20.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 2026-08-20 工作记录
|
||||
|
||||
## NAS 瘦身 + 甲骨文同步(架构重构收尾,任务 #49/#50/#57/#59)
|
||||
|
||||
完成 NAS 端从「视频处理节点」到「纯管理后台」的改造:
|
||||
|
||||
### fam-core(NAS)改动
|
||||
- 删除:scheduler / dispatcher / poller / event_receiver / video_server 五个模块 + tools/ 下 backfill 脚本(不再切片/抽帧/上传/提供视频下载)
|
||||
- 新增 `oracle_sync.py`:唯一后台线程,每 30 分钟 `GET /api/oracle/sync?since=<cursor>&token=` 拉增量 → upsert 到本地 MariaDB `sync_*` 镜像表 → 推进 `sync_cursor`;`push_name_correct` 回推命名校正
|
||||
- `db_layer.py` 重写:移除 process_tasks/event_details/monitor_events/family_members 旧逻辑,新增 sync_videos/sync_events/sync_people/sync_cursor 的 upsert/查询、`get_sync_stats`、`query_sync_events_for_person_date`、`get_sync_known_members_context`
|
||||
- `member_manager.py`:命名/合并改回推 Oracle `/api/oracle/people/correct` + 即时 trigger_now 拉回
|
||||
- `chat_handler.py`:上下文改查 sync_events(按 person_list_json + 视频日期过滤),仍走 Oracle `/api/edge/chat/ask`
|
||||
- `app.py`:仅启动 OracleSync 线程,移除看门狗
|
||||
- config:删除 scheduler/dispatcher/poller/video_server/storage,新增 `oracle_sync` 段(token 用 `${ORACLE_SYNC_TOKEN}` 环境变量)
|
||||
|
||||
### fam-ui(NAS)改动
|
||||
- 改写读 sync_videos/sync_events/sync_people;事件时间轴改为「视频会话列表 + 事件时间线」(无帧图);人物管理移除照片逻辑(仅标签/规范名/命名/合并);统计改自 sync 表;侧边栏任务队列状态改为同步状态
|
||||
|
||||
### DDL / 文档
|
||||
- `scripts/ddl.sql` 新增 sync_videos / sync_events / sync_people / sync_cursor 四张镜像表(时间字段用 VARCHAR 规避 MariaDB 严格模式)
|
||||
- README §1.1/§2.2/§3.1/§3.2/§3.3/§4/§5/§8.3 全面更新到新数据流(Google 硬盘→rclone→甲骨文→同步→NAS 镜像)
|
||||
|
||||
### 提交
|
||||
- `[阶段2]` fam-edge 重构(整视频分析+同步接口+人物服务)已 commit+push
|
||||
- `[阶段3]` fam-core 瘦身+Oracle-Sync+UI 改读 已 commit+push
|
||||
|
||||
### 待办(#51 部署验证,未做)
|
||||
- Oracle:rclone 配 Google 服务账号 JSON(orcLenas@gen-lang-client-0523399799.iam.gserviceaccount.com)→ systemd timer 定时同步到 /opt/fam-edge/gdrive_videos;设 ORACLE_SYNC_TOKEN 环境变量;重启 fam-edge
|
||||
- NAS:MariaDB 跑新 DDL 建 sync_* 表;设 ORACLE_SYNC_TOKEN;重启 fam-core + fam-ui;卸载 NAS venv 无用包(opencv/numpy 等)
|
||||
- 端到端验证:Google 硬盘新视频 → 甲骨文处理 → NAS 30 分钟同步可见
|
||||
- 服务账号私钥未入库,部署时需单独落到 Oracle 服务器本地
|
||||
180
.workbuddy/memory/2026-08-21.md
Normal file
180
.workbuddy/memory/2026-08-21.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# 2026-08-21 工作记录
|
||||
|
||||
## #51 部署验证 - rclone 同步 Google 硬盘 + 全链路跑通
|
||||
|
||||
### Oracle(fam-edge)部署完成
|
||||
- rclone v1.75.0 装到 /usr/local/bin(aarch64,zip 解压)
|
||||
- 服务账号 JSON 存 /opt/fam-edge/gdrive-sa.json(chmod 600,用户提供)
|
||||
- rclone remote `gdrive:`(drive scope + service_account_file)
|
||||
- 同步脚本 /opt/fam-edge/rclone_sync.sh + systemd timer(每 5 分钟):
|
||||
`rclone sync --drive-shared-with-me gdrive:SS/Generic_ONVIF-001 /opt/fam-edge/gdrive_videos`
|
||||
- 首次全量同步 20 个视频 7.52GB(~2 分钟完成)
|
||||
- 共享目录是 `SS`(群晖监控站),摄像头 `Generic_ONVIF-001` 按 YYYYMMDDAM/PM 分目录
|
||||
|
||||
### 踩坑与修复(3 个 commit)
|
||||
1. `watch_processor._scan_files` 改 os.walk 递归(原 listdir 只看根目录,视频在子目录)
|
||||
2. `.env` 的 GEMINI/NVIDIA key 是旧无效值 → 用 env.sh 的真实 key 修复(config_loader 只读 .env)
|
||||
3. **Gemini 上传**:必须用 `/upload/v1beta/files` 端点(/v1beta/files 是元数据端点)+ resumable 协议,370MB 整视频 ~55s 上传成功
|
||||
4. **NVIDIA**:base64 塞 payload 超 25MB 上限 → 改 Assets API(POST 拿 assetId+uploadUrl,PUT 上传);PUT 必须**全小写 content-type 且值=POST contentType(video/mp4)**,否则 S3 预签名 SignatureDoesNotMatch
|
||||
5. `parse_vlm_json` schema 从旧架构(entities_json/frame_details)改为新架构(global_summary/events/people_mentioned),兼容旧结构转换
|
||||
|
||||
### 端到端验证通过
|
||||
- 21 视频全部登记,video 1/2 已 done(provider=gemini,凌晨视频无事件=分析正确)
|
||||
- NAS 重启 fam-core 触发立即同步:sync_videos=21,cursor 推进正确
|
||||
- 剩余 19 个视频串行处理中(Gemini 免费配额 429 会降级 flash-lite,之后可考虑 NVIDIA)
|
||||
|
||||
### 待观察
|
||||
- Gemini 免费 key 配额限制(~20 req/day),后续 19 个视频可能大量 429
|
||||
- rclone timer 每 5 分钟增量同步验证
|
||||
- NAS 30 分钟自动同步已生效
|
||||
|
||||
## 生产-消费队列 + 模型超时×2(新需求,commit ce27e4a)
|
||||
- 新增 `fam-edge/src/fam_edge/video_queue.py`:VideoQueue 生产-消费队列
|
||||
- 生产者线程:30s 轮询 rclone 落地目录,新文件登记 pending 并入队;启动时补入队 DB 未处理完的(重启恢复,实测 14 个)
|
||||
- 消费者线程:max_concurrent=1,从队列取 video_id → VideoProcessor.process_video(timeout_multiplier=2)
|
||||
- 防重复入队:_queued set;重试上限:max_retries=2(oracle_db.videos 加 retry_count 列,mark_video_failed 递增)
|
||||
- `video_processor.process_video` 加 timeout_multiplier:遍历 adapter 时临时 adapter.timeout = 原配置 ×倍数,finally 恢复(gemini 600→1200s 生效日志确认)
|
||||
- config.yaml video_processing 加 timeout_multiplier: 2 / max_retries: 2
|
||||
- 删除旧 watch_processor.py,app.py 改启动 VideoQueue
|
||||
- oracle_db:retry_count 列(建表+ALTER 兼容旧库)、get_video_by_id、PRAGMA busy_timeout=10000
|
||||
- 部署验证:启动恢复入队 14 个、video_id=8 gemini 600→1200s、生产者登记 id=22 新视频、done 持续增长
|
||||
|
||||
## 模型调用统计界面 + lite 超时实测×4(commit b87b38b + 3e94c2a)
|
||||
- Oracle 端:oracle_db 新增 model_calls 表(provider/model/video_id/started_at/duration_sec/success/error/created_at);BaseModelAdapter 加 model_call_hook + _emit_model_call,gemini(_generate_video 每模型每attempt)/nvidia(analyze_video) 每次请求记录;get_sync_delta 下发 model_calls(created_at >= since + NAS 幂等 upsert 防漏)
|
||||
- gemini 支持模型级 model_timeouts(最终值不参与 ×2):实测 lite 370MB 视频耗时 22.6s(生产 27~34s),lite 限制 = 22.6×4 ≈ 90s;flash 仍 1200s(600×2)。日志确认:flash 本轮 1200s / lite 本轮 90s
|
||||
- NAS 端:sync_model_calls 镜像表(ddl 8.1);db_layer upsert_sync_model_calls/get_sync_model_calls/get_sync_model_calls_stats;oracle_sync 拉 model_calls(last_count 变 4 元组)
|
||||
- fam-ui 新增 "🤖 模型统计" 页:按模型聚合(成功/失败/成功率/平均耗时/最后调用)+ 最近 100 条调用明细(请求时间/模型/耗时/状态/失败原因/视频)
|
||||
- 验证:Oracle model_calls 正常记录(flash 429_quota 失败、lite 成功 27~34s);NAS 同步 4 条,fam-ui 200
|
||||
|
||||
## 整体排查 + lite 超时 8 分钟(commit 83eefc1)
|
||||
- **NVIDIA 400 排查结论**:`nvidia/nemotron-nano-12b-v2-vl` 对整视频分析**稳定复现 400/500 服务端内部错误**("not enough values to unpack (expected 2, got 1)"),所有视频、有无 num_frames 均复现 → NVIDIA 兜底在当前模型/端点**不可用**(asset 上传 OK,chat.completions 必失败)。建议从 vision_order 移除或换模型
|
||||
- gemini-flash-lite 超时 90s → **480s(8 分钟)**,config model_timeouts 已改并部署(日志确认 480s)
|
||||
- 处理进度:done 33 / failed 1(video24 待重试)/ pending 12
|
||||
|
||||
## NVIDIA 多模型降级链 + 实测结论(commit b7b5fe6)
|
||||
- nvidia_adapter 改造:model_chain(asset 上传一次,逐个模型 video_url 引用尝试)+ switch_interval_sec 切换间隔(默认 5s)+ model_timeouts 每模型独立超时 + compute_provider 带模型名(nvidia:{model})
|
||||
- **实测全部 NVIDIA 云端视频候选不可用**:omni 500(asset_id 引用失败)、12b 400、llama-3.2-11b-vision 400(不支持视频)、cosmos-reason2/phi-3-vision/gemma-3/kosmos-2/fuyu-8b/paligemma 404;base64 超 25MB;public URL 方案被用户否决(不暴露视频)
|
||||
- config:model_name=omni + fallback 12b/llama-11b,链机制保留,未来可用模型出现只需改配置
|
||||
- 手动验证:模型链 [1/3]→[2/3]→[3/3] 逐个尝试+5s 间隔+失败原因记录,全部失败返回 None
|
||||
- ⚠️ 安全提醒:Oracle 曾短暂起 http.server:8000 暴露视频目录做 POC,已按用户要求关闭
|
||||
|
||||
## 前端修复:SQL 1054 + 视频缩略图(commit 4148507)
|
||||
- **SQL 1054 修复**:fam-ui 事件查询 `SELECT ... camera_name FROM sync_events`(该列在 sync_videos)→ 改 JOIN sync_videos 取 v.camera_name
|
||||
- **视频缩略图**:Oracle 装 opencv-python-headless(5.0);video_processor 处理成功后抽首帧(宽≤640, JPEG q65)存 /opt/fam-edge/thumbs/{video_id}.jpg;api_gateway 新增 GET /api/oracle/video/{id}/thumb(token 校验,无 token 401);已 done 46 视频 backfill 46/46
|
||||
- **fam-ui**:config 加 oracle_url(129.146.203.203:5000)+oracle_token(${ORACLE_SYNC_TOKEN});事件时间轴视频会话头显示缩略图(img onerror 优雅降级);start_ui.sh 补 source 项目 .env 注入 token
|
||||
- 验证:NAS→Oracle thumb HTTP 200/40KB;fam-ui 进程 token env 就绪;UI 8501 正常
|
||||
- 人物管理无照片:架构局限(LLM 仅输出人物名,无图像锚点),如需人物照片需从视频定位+裁剪,待后续
|
||||
|
||||
## 每个事件对应时间点画面截图(commit 05f727a)
|
||||
- oracle_db.mark_video_processed 返回 event_ids(与 events 一一对应)
|
||||
- video_processor._generate_event_thumbs:事件 ts - 视频 event_start_time = 偏移秒 → cv2 跳帧(CAP_PROP_POS_MSEC)截图,存 /opt/fam-edge/thumbs/ev_{event_id}.jpg(宽≤640, q65;解析失败/负偏移取首帧)
|
||||
- api_gateway 新增 GET /api/oracle/event/{event_id}/thumb(token 校验)
|
||||
- fam-ui 事件查询加 e.id;render_event_list 每条事件显示对应截图(onerror 隐藏降级)
|
||||
- backfill:已 done 视频 102 个事件截图全部生成;验证 NAS→Oracle HTTP 200/51KB、无 token 401
|
||||
|
||||
## 事件截图时间对不上修复(commit 5233296 + 3fcd00d)
|
||||
- **根因**:模型输出"绝对北京时间"靠自身推算,1 小时视频内误差可达分钟级 → 截图按不准的时间定位帧必然图文不符
|
||||
- **修复**:① prompt 改为要求输出"视频内相对时间 HH:MM:SS"(模型对相对位置判断准);② 后端 _parse_event_ts 解析相对时间 → 绝对时间 = start + offset 精确落库(兼容旧绝对格式);③ 截图直接用 offset 跳帧
|
||||
- **连带 bug**:_parse_event_start_from_filename 只认带分隔符日期(2026-08-21),监控文件名是纯数字 20260820-140416 → event_start_time 空 → 新增纯数字格式解析
|
||||
- 启发式:相对时间 >6h 视为模型误输出绝对时间,不强行定位
|
||||
- 验证:重置 video 46 重分析 → event_start=14:04:16 ✓,事件 ts 14:05:37/14:06:21/14:13:02,截图 offset 36/48/81/125/526s 精确 ✓
|
||||
- 旧视频(除重分析的)仍用旧时间戳截图;如需全部修正需批量重分析(成本高,用户确认后再做)
|
||||
|
||||
## 全部旧视频重分析 + 人物管理图片(commit 92ef9fe)
|
||||
- **全量重分析**:清空 events(104) + 旧事件截图(108) + 重置全部 45+1 个视频 pending → 队列后台串行重跑(新相对时间逻辑,预计 1.5-2.5h,Gemini flash 429 → lite 兜底)
|
||||
- **人物管理图片**:fam-ui 每个人物身份取其一 label 出现事件的截图作头像(查 sync_events.person_list_json LIKE → 显示 Oracle event thumb)
|
||||
- 修正:video 46 起初被排除重跑但 events 被清 → 一并重置统一重跑
|
||||
|
||||
## 队列健壮性三项(commit 6afdda5,用户需求清单)
|
||||
1. **失败重试间隔 30s→1h**:videos 表加 last_fail_at(mark_video_failed 记录);video_queue._retry_allowed 对 failed 要求 retry_count<max_retries 且距上次失败 ≥ retry_interval_sec(3600) 才重新入队(配额类瞬时故障等恢复,避免重复打爆)
|
||||
2. **max_retries 2→10**:默认值与 config 均改 10(瞬时故障更多机会)
|
||||
3. **入队前文件校验**:validate_video()(OpenCV:大小>0/可打开/可读帧/元数据 fps-frames-duration-分辨率);新文件先过 mtime 稳定窗口(stable_window_sec=60 防 rclone 半成品)再校验,失败登记 status='invalid' 不入队(可追溯,_retry_allowed 排除);process_video 处理前二次确认(失败标 failed:invalid_file:xxx)
|
||||
- 验证:正常视频 ok+meta(30min/2880x1620/14.28fps)、损坏 cannot_open、空文件 file_empty;部署后日志"重试上限 10"
|
||||
- 重分析 42 个 pending 继续后台跑
|
||||
|
||||
## 代码审查 18 项处理(commit 034dca9 + 4e85a98)
|
||||
- **已解决(新增)**:#18 密钥明文直配 config.yaml(token/gemini/nvidia,弃 .env 依赖,验证 token_ok/key 长度正常);#5 appearances 改 distinct 视频数覆盖校准(set_person_appearances,防 reconcile 累加膨胀);#9 oracle_sync _pull_lock 防 trigger_now 与后台并发双拉;#11 LLM 合并命名解析 target 已有 canonical(统一显示名);#12 熔断器状态转换加锁;#10 问答人名匹配改 JSON_CONTAINS 精确匹配(MariaDB 语法验证 OK);#4 OracleDB _write_lock 复合写串行化;#6 每消费者独立 VideoProcessor(消 adapter.timeout 共享竞争);#15 done 视频文件被覆盖 mtime>processed_at 自动重置重分析
|
||||
- **累计已解决**:#1/#2/#3/#4/#5/#6/#9/#10/#11/#12/#15/#18;部分缓解 #7/#14/#17
|
||||
- **未解决(说明)**:#8 删除传播(tombstone 大工程,暂缓);#13 Gemini 文件/NVIDIA asset 残留清理(暂缓);#16 gunicorn 线程数(运维配置,暂缓)
|
||||
- 重分析进度:done 13 / pending 33(约剩 1h)
|
||||
|
||||
## Prompt 集中化重构(commit a72b286,用户提供详细设计文档)
|
||||
- 新增 fam-edge/src/fam_edge/ai_orchestrator/prompts.py:build_video_prompt(含 3 秒密度抽取/7 维度描述/people_mentioned 一致性强制/输出硬约束/边界情况/camera_name 注入)、build_chat_prompt、build_person_merge_prompt(唯一性+保守不合并);__init__ 导出
|
||||
- gemini/nvidia adapter._build_video_prompt 改调共享函数(消除两份发散),注入 camera_name
|
||||
- person_service._llm_merge 改调 build_person_merge_prompt
|
||||
- fam-core chat_handler 新增 prompts.py(跨模块独立维护,风格一致),删除内联 CHAT_SYSTEM_PROMPT,chat_ask 改调 build_chat_prompt
|
||||
- 部署验证:prompt 生成正常(3 秒密度/camera/唯一性均含);fam-edge active;fam-core health OK
|
||||
- 注意:3 秒密度可能让有人时段 events 数百条,Gemini maxOutputTokens=4096 可能截断——若出现 JSON 解析失败需提高 max_tokens
|
||||
|
||||
## 最新代码调试 + 人物标识清洗(commit 0d73c23)
|
||||
- 调试结果:服务全正常(fam-edge active、NAS core/ui 200、rclone timer active);重分析 done 26/pending 20;新 prompt 正常——有人视频 5-16 事件、无人时段(晚 19:31/凌晨 5 点)events=0 合理、无 JSON 解析失败
|
||||
- 发现并修复:模型输出 people 含 known_members 上下文格式串"人物A(别名/标识:人物B)"污染人物表 → prompts.py 明确"people 只填标识本身不带括号注释" + video_processor._clean_person 后端清洗(events.people 与 people_mentioned 均清洗)+ 清理存量脏 label(人物A(别名/标识:人物B)→ 人物A)
|
||||
- people 表现状:A/B/C/D 四标签,LLM 合并 B/C/D → canonical 人物A
|
||||
|
||||
## 前端日期标签与视频文件对不上(commit da76319)
|
||||
|
||||
**用户反馈**:前端显示"2026-08-21 · 画面静止/无人员活动",和下面的视频文件对不上。
|
||||
|
||||
**根因链**(排查发现):
|
||||
1. 前端事件时间轴/统计按 `processed_at`(**分析处理时间**)做日期分组——全量重分析都在 8/21 完成,导致 8/15~8/20 录制的视频全部堆在"2026-08-21"标签下,与文件名(录制时间)错位
|
||||
2. Oracle videos 表 8/21 11:29~12:59 被整表重建(created 全在此区间),重分析串行进行
|
||||
3. 20 个视频 event_start_time 为空:video 30-45 在 13:00~13:25 处理时服务器跑的是旧文件名解析代码(无纯数字正则 YYYYMMDD-HHMMSS),且被后续部署重启中断(pending);28/29 从未处理
|
||||
4. NAS 镜像状态与 Oracle 错位(35-45 NAS 显示 done、Oracle 实为 pending)——Oracle 重建后 NAS 增量未对齐
|
||||
|
||||
**修复**:
|
||||
1. **日期维度统一改视频实际录制时间**:fam-ui(事件时间轴统计/列表/关注事件统计)与 db_layer(get_sync_videos/get_sync_stats/query_sync_events_for_person_date)全部改用 `COALESCE(NULLIF(event_start_time,''), processed_at)`(录制时间优先,回退处理时间)
|
||||
2. **Oracle backfill**:对 12 个缺失 start 的视频按文件名解析回填(30/33-42/45)→ Oracle 46 个视频全部有 event_start_time
|
||||
3. **NAS 强制全量重同步**:删 sync_cursor last_since + 重启 fam-core → since='' 全量拉取 → EMPTY_START 20→0、状态与 Oracle 对齐(done 28/failed 4/pending 14)
|
||||
|
||||
**验证**:done 视频按录制日期分布 8/15:10、8/16:8、8/17:2、8/18:2、8/19:2、8/20:1、8/21:3;8/15/8/20 筛选正确;fam-ui 200 无报错。
|
||||
|
||||
**遗留**:Oracle 端 pending 14 个在队列继续串行处理(video 28 处理中);failed 4 个(24/25/26 等,Gemini 429)等 1h 重试间隔自动重试。
|
||||
|
||||
## Google 硬盘删除联动 + DB 摘要保留(用户需求确认,无代码改动)
|
||||
- **用户需求**:①谷歌删了甲骨文也删(文件层);②Oracle/NAS 数据库生成的摘要不能删(DB 层)
|
||||
- **验证结论**:
|
||||
- 文件层:rclone sync 本就是镜像语义(远程删→本地删),实测放临时文件→sync→Deleted:1 确认生效
|
||||
- DB 层:video_queue 生产者只扫描"目录存在的文件",文件消失不影响 Oracle videos/events/people 记录;NAS 镜像照常同步,前端摘要保留;截图接口 404 由前端 onerror 降级
|
||||
- **加固**:rclone_sync.sh 加 `--max-delete 200`(防 Google API 临时故障级联误删本地,单次最多删 200 个),脚本验证 exit=0、75 文件不受影响
|
||||
- 注意:当前是**单向镜像**(Google 为源→本地),不建议反向传播(本地删→谷歌删会误删原始监控),如需 rclone bisync 真双向需用户确认风险
|
||||
|
||||
## 实时服务状态界面 + 7 天活动记录(commit 0d0a7f6 + fec9a3e)
|
||||
- **Oracle**:service_activity 表(service/action/detail/ts,写入时清 7 天前);record_activity/get_recent_activities/get_queue_status;VideoQueue 打点(register/reanalyze/process_start/process_done/process_fail)+_current 当前处理跟踪+status();PersonService 打点(merge_done/merge_skip);api_gateway 新增 GET /api/oracle/activity(token 鉴权,返回 queue/db.by_status/rclone/person/model_calls/最近50条活动)
|
||||
- **rclone_sync.sh**:同步结果写 activity 表(service=rclone, transferred/deleted/exit);TRANS 提取正则修过一次(输出带 B 单位)
|
||||
- **fam-ui**:新增"🖥 服务状态"页(导航第7项):状态卡(队列运行/排队/完成/待处理/失败 + 当前处理视频 + rclone/人物/NAS同步/模型 4 张服务卡)+ 最近活动时间流(服务徽章),直连 Oracle /api/oracle/activity(复用 oracle_url+token)
|
||||
- **踩坑**:video_queue.status() 用 Dict 注解未导入 → worker 启动 NameError → 补 typing.Dict
|
||||
- 验证:接口数据正常(queue running/queued 19/当前 video 56;rclone sync_done/person merge_done 打点生效;401 鉴权;NAS→Oracle HTTP 200);fam-ui 200
|
||||
|
||||
## 模型调用时间差 8 小时修复(commit 02fc80b)
|
||||
- **根因**:gemini/nvidia adapter 的 model_calls.started_at 用 `datetime.now()`(Oracle 服务器 UTC),而 created_at 用 _now_iso()(北京时间)→ 前端模型统计/服务状态页时间差 8h
|
||||
- **修复**:两个 adapter started_at 改 `datetime.now(timezone(timedelta(hours=8)))`;Oracle 历史 215 条 +8h;NAS 镜像 sync_model_calls 210 条 DATE_ADD +8h
|
||||
- 验证:最新记录 started 16:05:02 与 created 对齐(北京时间)✓
|
||||
|
||||
## 人物管理无照片修复(commit 27bfd6a)
|
||||
- **根因**:NAS sync_events 残留重分析前旧事件(223 条 vs Oracle 115 条,id 1-108 旧事件未删除——#8 删除传播未做的副作用)。人物页头像查 `ORDER BY e.id LIMIT 1` 取到旧事件 id=1 → Oracle ev_1.jpg 不存在(重分析后事件从 109 起)→ 404 → onerror 隐藏 → 无照片
|
||||
- **修复**:① NAS 清空 sync_events + 重置 cursor 全量重拉 → 精确 115 条镜像;② api_gateway event_thumb 兜底:ev 缺失时查所属 video 返回 thumbs/{video_id}.jpg(视频首帧);③ 清理 Oracle people 脏 label(人物A(别名/标识:人物B)→人物A)
|
||||
- 验证:NAS→Oracle ev109 HTTP 200/51KB ✓;人物页查询到的都是新事件 id → 截图存在
|
||||
|
||||
## 人物头像改为"出现事件画面"(commit 2ba3478,用户否决视频首帧方案)
|
||||
- **用户意见**:不能用视频首帧当头像(首帧可能无人/非本人)——该人物在众多视频中多次出现,肯定能在他出现的事件里找到画面
|
||||
- **新方案**:Oracle 新增 GET /api/oracle/person/avatar?label=X(token 鉴权):events.person_list_json 按 `%"label"%` JSON 数组精确匹配,按 id DESC 遍历返回第一个 ev_{id}.jpg 存在的截图(人物出现事件中最新的有截图画面);撤销 event_thumb 的视频首帧兜底(改回 404)
|
||||
- **fam-ui 人物页**:头像改调 avatar 接口(先 requests 探测 200 再渲染 img,label 用 urllib.parse.quote 编码)
|
||||
- 验证:人物A/B avatar 均 200(39-41KB 真实事件画面)、无 token 401、NAS→Oracle 200、UI 200
|
||||
|
||||
## Oracle 磁盘扩容(用户控制台扩盘 + 重启生效)
|
||||
- **背景**:rclone 持续同步视频导致 45G 盘写满(剩 46M,gdrive_videos 31G/75 个视频)。用户说已扩容但服务器 lsblk 一直 46.6G(在线未生效)
|
||||
- **处理**:growpart/resize2fs 均 NOCHANGE(块设备没变);临时清理 /tmp 残留+apt+journal 释放 ~900M;禁用 rclone timer 防写满;按用户要求重启服务器
|
||||
- **重启后扩容生效**:sda 46.6G→**150G**,Ubuntu cloud-init 开机自动扩展分区+文件系统 → df 146G 可用 90G(39%)
|
||||
- **恢复**:enable --now rclone-sync.timer;同步恢复(视频 75→121 个持续下载中);fam-edge active
|
||||
- 经验:Oracle 在线扩容偶尔不立即生效,重启可触发(Ubuntu 自动 growfs);扩容后无需手动 growpart
|
||||
- 提醒:视频持续增长(121 个已占 56G),150G 约可再装 240 个视频,长期需考虑清理策略(如只保留分析完的摘要+删本地视频,需改 rclone 策略避免重新拉回)或再扩盘
|
||||
|
||||
## 人物模块重构 v3 - 大模型特征值替代 OpenCV(commit ff01d14 + 68337f8 + 677c5bd,用户提供设计文档)
|
||||
- **核心**:prompt 增加 person_appearances(uid+7特征+action),VLM 直接产出结构化特征,跨视频靠特征合并,彻底移除 cv2
|
||||
- **改动**(12 文件):prompts.py(6原则+schema+规则8条+合并prompt重写)、gemini _normalize 透传、oracle_db(events.person_appearances_json/people.features_json/display_uid + _merge_features + upsert_person/mark_video_processed)、video_processor(validate_video 改 ffprobe 去 cv2、_store_result 聚合 uid 特征落 people、删 thumb/event_thumbs)、person_service(_aggregate_features/_collect_features_text 特征文本合并)、api_gateway 删 3 图接口、NAS db_layer+ddl 加字段、fam-ui 特征卡替代头像+事件人物特征块
|
||||
- **踩坑 2 个**:
|
||||
1. max_tokens 4096→16384:3秒密度+特征使 JSON 巨大被截断解析失败
|
||||
2. **json_parser.validate_schema 白名单丢弃 person_appearances**(在 _normalize 之前执行)→ 补透传
|
||||
- **验证**:video 46 重分析 → lite 输出 16 事件含完整特征(性别男/中年/中等/短发/蓝色POLO衫/无辨识);events 带特征 6、people.features_json 落库;NAS 同步 6 条+人物A特征卡数据 ✓
|
||||
- 部署:Oracle 卸载 opencv/numpy(ffprobe 已有);NAS ALTER 加 3 列
|
||||
- 遗留:NVIDIA 模型链仍不可用(gemini 429 时 lite 兜底);历史视频无特征(前端显示"特征待大模型补充",下段分析自动补)
|
||||
214
.workbuddy/memory/2026-08-22.md
Normal file
214
.workbuddy/memory/2026-08-22.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 2026-08-22
|
||||
|
||||
## 核查 SS Webhook 配置字段(sentinel-home-ai)
|
||||
- 本机 SS 版本 9.3.0-12139(≥9.1.1),Webhook 动作设备可用。
|
||||
- 关键发现:**SS Webhook 与 motion_bp.py 接收端 schema 不匹配**。
|
||||
- SS Webhook 是「用户自定义参数名 + 模板变量」机制,Motion Detection 支持:
|
||||
%EVENT_TIME%(字符串,非 epoch)、%DEVICE_NAME%(摄像头名文本,非 camera_id)、
|
||||
%EVENT_NAME%、%SERVER_NAME%、%THUMBNAIL_URL%。每事件单次 POST,body 为 form/JSON 的 key=value。
|
||||
- 接收端 motion_bp.py 期望 EventCenter.Event.List 风格:events:[{event_id(int 主键),
|
||||
camera_id, event_type=10, start_time(epoch), duration, thumbnail_url}]。
|
||||
- 因 Webhook 无法提供 event_id,`record_motion_events` 中 `if eid is None: continue`
|
||||
导致真实 webhook 事件 100% 被静默丢弃。
|
||||
- 结论:轮询路径(MotionNotifier._fetch_events 走 EventCenter.Event.List)是唯一可靠数据源,
|
||||
已端到端验证。Webhook 如需启用,须改接收端做字段映射 + 与轮询去重(待用户拍板)。
|
||||
|
||||
## Webhook 驱动改造(去轮询)—— 已完成并端到端验证
|
||||
- 用户决策:**不要轮询,改用 SS Webhook 作为唯一数据源**。
|
||||
- 改造内容(commit ca6d425 / e52d8e9,已 push Gitea + 部署 NAS):
|
||||
- `motion_notifier.py`:`poll_enabled=false`(默认关),关闭轮询线程;新增
|
||||
`refresh_camera_map()`(启动时一次性从 SS Camera API 拉 name→id 映射)、
|
||||
`resolve_camera_id()`、`parse_ss_time_to_epoch()`(EVENT_TIME 字符串→epoch,按 NAS +8)、
|
||||
`_synth_event_id()`(device_name+event_time+thumbnail_url 哈希成稳定 int,幂等去重)、
|
||||
`build_event_from_webhook()`。
|
||||
- `motion_bp.py`:`/api/ss/webhook` 解析 JSON/表单、单条/数组,用 build_event_from_webhook
|
||||
映射后推送甲骨文;SS 端需按固定参数名配置(event_time/device_name/event_name/server_name/thumbnail_url)。
|
||||
- `config.yaml`:加 `camera_name_to_id: {"Generic_ONVIF-001": 2}` 兜底,删原 camera_ids 轮询项。
|
||||
- `app.py`:启动加载摄像头映射;`start_core.sh` 修正 APP_DIR 路径 bug 并显式 source .env。
|
||||
- 验证:模拟 JSON + 表单 + 重复推送,均 pushed=1;甲骨文 ss_motion_events 落库正确
|
||||
(camera_id=2, event_type=10, start_time epoch 北京时区对齐);`has_motion_in_range_local`
|
||||
窗口命中 True / 远离 False;测试数据已清理。
|
||||
- **待用户操作**:在 SS「行動規則 → 事件=偵測到動作 → 動作=Webhook」配置,URL 填
|
||||
`http://127.0.0.1:8000/api/ss/webhook`,参数名严格用 event_time/device_name/event_name/
|
||||
server_name/thumbnail_url(值分别对应 %EVENT_TIME%/%DEVICE_NAME%/%EVENT_NAME%/%SERVER_NAME%/%THUMBNAIL_URL%)。
|
||||
- 注意:甲骨文 ss_motion_events 现存 21 条为历史轮询真实事件(2026-08-22 上午),轮询已停,不再新增。
|
||||
|
||||
## 「webhook→回查 SS 事件列表补全」链路测试(不写生产代码)
|
||||
- 用户要求先测清整条链路再决定是否实现。测试结论:**思路完全可行,各段均真机验证通过**。
|
||||
- **链路 A(SS 事件列表真实字段)**:NAS 上 `SYNO.SurveillanceStation.EventCenter.Event` method=List
|
||||
(camera_ids=2, event_types=10)真实返回 id/start_time/duration/thumbnail_url/thumbnail_dir。
|
||||
实测 e.g. id=25536 dur=63s、id=25535 dur=95s、id=25538 dur=0。即 duration 真实存在(短动作 0~几秒,长动作数十秒)。
|
||||
- **链路 A2(webhook 触发时刻→补全匹配)**:模拟 webhook 触发时刻=事件 start_time(epoch),
|
||||
用 ±120s 窗口找时间最接近事件,diff=0 精准匹配,补全出真实 event_id/start_time/duration/thumbnail。
|
||||
(注:真实 %EVENT_TIME% 是分钟级字符串,解析为 epoch 与真实 start_time 差 ≤ 数秒,120s 窗口足够覆盖。)
|
||||
- **链路 B(补全结果推甲骨文落库)**:POST /api/ss/motion(body 含 token + events[{
|
||||
event_id,camera_id,event_type,start_time,duration,thumbnail_url}])均 stored=1;
|
||||
甲骨文库 ss_motion_events 字段核对正确(event_id=25535 dur=95 / 25536 dur=63)。
|
||||
- **链路 C(预过滤)**:has_motion_in_range_local 窗口含事件=True、远离时段=False,逻辑正确。
|
||||
- 全部测试数据已清理,库恢复 21 条历史真实事件。
|
||||
- **未真机验证的一环**:SS 后台尚未配 Webhook,故「真实 webhook 触发」这步是模拟(用真实 start_time 当 trigger)。
|
||||
其余 SS 查列表 / 推送甲骨文 / 落库 / 预过滤均为真机。
|
||||
- 下一步若实现:在 motion_notifier 加 enrich_event_from_ss(),webhook 收到后本机回查 SS 列表补全真实字段。
|
||||
|
||||
## 「能否输出对应运动视频」调研——可行,直接读录像文件 + ffmpeg 裁剪
|
||||
- 用户问运动事件能否输出对应视频片段。结论:**完全可行,且已真机裁剪出 22s MP4 演示**。
|
||||
- **SS API 导出路径全部不可用(SS 9.3.0-12139)**:
|
||||
- `Recording.Export` 仅剩向导方法(CamEnum/CheckName/Save/Load/Delete,EventArchive.js),Save 需复杂参数(431),
|
||||
非旧文档的 RangeExport(9.3 已无此方法,实测 103)。
|
||||
- `ThirdParty/Recording/Download/v1`(camId/startTime/endTime ISO 字符串)返回 401(需第三方授权,不可用)。
|
||||
- **关键发现:录像文件明文直读**(无需任何 API):
|
||||
- 路径:`/volume1/surveillance/<摄像头名>/<YYYYMMDDAM|PM>/<摄像头名>-YYYYMMDD-HHMMSS-<起始epoch毫秒>-<seq>.mp4`
|
||||
- 每 30 分钟一个片段(约 377MB),h264 2880x1620@14.28fps + pcm_alaw(8kHz) 音频;
|
||||
文件名含起始 epoch 毫秒,可用 (camera_id, start_time) 直接定位(camera_id→目录名需 SS Camera API 映射)。
|
||||
- recording_encrypt.db 存在但当前录像为明文(未启用加密)。
|
||||
- **裁剪验证(真机)**:事件 id=25535(start=1787369601)落在
|
||||
`Generic_ONVIF-001-20260822-110859-1787368139533-7.mp4`(11:08:59 起 30min),偏移=1787369601-1787368139=1462s。
|
||||
`ffmpeg -ss 1462 -t 20 -i 片段 -an -c:v copy` → 22.13s / 4.4MB MP4(copy 模式从关键帧起,±秒级偏差;需精确可重编码)。
|
||||
**必须 -an**:pcm_alaw 音频无法封装进 mp4 容器,-c copy 会报 "Could not find tag for codec pcm_alaw"。
|
||||
- 注意:NAS ffmpeg 为老版本(/usr/bin/ffmpeg,不支持 -show_entries,用 `-i 2>&1 | grep Duration` 查时长)。
|
||||
- 下一步若实现:motion 事件补全 start_time/duration 后,NAS 端按 (camera, start) 定位片段 + ffmpeg 裁剪输出。
|
||||
|
||||
## Webhook vs 事件列表一致性核查(重要认知)
|
||||
- 用户质疑:Webhook 与事件列表是否 1:1?事件列表有的会不会 webhook 没推?
|
||||
- 实测(NAS):**ActionRule List = 0 条**(SS 后台尚未配任何行动规则 → 真实 webhook 零推送);
|
||||
今日 camera2 运动事件 = 141 条(id 25405~25545),事件稀疏突发(35 个 >120s 断档,最长 4h 无动作)。
|
||||
- 机制结论:**Webhook ⊂ 事件列表,不是 1:1**。EventCenter 是全量事件记录;行动规则引擎按
|
||||
「摄像头范围/时间段/去抖合并/开关/规则数」过滤后才触发 Webhook。故**事件列表有、webhook 没推
|
||||
是完全可能的**,纯 Webhook 驱动存在漏事件 → 预过滤误杀风险。
|
||||
- 反方向(webhook 有、列表无):罕见,仅事件落库毫秒级时序,±120s 窗口 + fallback 已兜底。
|
||||
- 对策待用户拍板:A. Webhook 实时 + 每 5 分钟增量对账补推(推荐,对账非事件轮询,只补差异);
|
||||
B. 接受漏风险(规则配全量摄像头+关去抖);C. 回轮询(用户已否决)。
|
||||
|
||||
## Webhook 实现审查 + 用户最终决策:恢复轮询主路径(commit 7adc324,已部署验证)
|
||||
- Webhook 实现审查发现 P0/P1/P2 问题:①合成 event_id 碰撞(同分钟多条事件若 thumbnail 为空 → 同 id 被
|
||||
UNIQUE 吞掉);②推 Oracle 失败无重试直接丢;③无对账兜底(Webhook⊂事件列表);④SS Webhook 请求格式
|
||||
从未真机验证(行动规则 0 条);⑤时间解析失败伪造为 now(UTC) 混时区;⑥camera_id=None 照常入库。
|
||||
- **用户拍板:放弃 Webhook,恢复轮询(简单稳定)**。改动:
|
||||
- motion_notifier.py:poll_enabled 默认 true;_fetch_events limit 100→1000;
|
||||
_init_cursor 优先续用 DB 游标(重启补推停机期间事件),DB 空才初始化为 SS 当前最大 id;
|
||||
_poll_once 仅推送成功批次才前进游标(失败批次下轮窗口回看重试,不丢事件);
|
||||
docstring 改为轮询主路径说明。
|
||||
- config.yaml:poll_enabled: true,加 camera_ids: [2],注释同步。
|
||||
- motion_bp.py:docstring 改为"Webhook 可选补充,非主路径"(端点保留)。
|
||||
- **端到端验证(真机)**:重启后游标续用 DB=25491 → 第一轮补推 55 条停机期间事件(25492~25546)、
|
||||
第二轮增量 2 条(25547/25548);Oracle ss_motion_events 总数 21+55+2=**78**,event_id 全为真实 SS id
|
||||
(25471~25548),duration 真实分布(0~31s);/api/ss/status:poll_enabled=true running=true
|
||||
heartbeat_running=true pushed_total=57。
|
||||
- 结论:轮询路径现在简单稳定、不丢事件(重启补推 + 失败重试 + 真实字段),Webhook 降级为可选。
|
||||
|
||||
## 运动事件驱动架构 v3(commit a1523b4/8d6cfad/ffcc292,已部署验证)
|
||||
- 用户决策:**不再处理整段视频**;timeline 事件轴/人物管理/统计全部用"运动时间处理后的数据"。前端不改。
|
||||
- 澄清:rclone 整段素材**必须保留**(在甲骨文按运动时间分割);音频**保留转码**(aac)。
|
||||
- **实测纠正认知**:SS 事件 duration=0 只出现在动作进行中(查询时未结束),结束必为正数
|
||||
(id=25538 11:38 查 dur=0,12:13 查 dur=2;近 1h 43 条无一条已结束且 dur=0)。
|
||||
故分割只处理已结束事件(start+duration ≤ now+grace 10s),进行中的下轮再分割。
|
||||
- **Oracle 端改动**:oracle_db 加 motion_event_id/camera_id 列 + get_motion_events_in_range/
|
||||
has_unfinished_motion_in_range/get_video_by_motion_event_id;video_processor 素材→分割/片段→分析
|
||||
双分支(ffmpeg -c:v copy -c:a aac 保留音频,motion_event_id 幂等);video_queue 片段入队;
|
||||
config 加 motion_segment 块(clips_dir=/opt/fam-edge/motion_clips)。
|
||||
- **E2E 真机验证**:素材 327(11:08:59)→ 分割 35 段(event_id 25504-25538,dur 1-132s)→ 片段只分析
|
||||
(summary 真实:媳妇/爷爷/汤圆)→ events 绝对时间 ts → NAS 同步 → /api/ui/videos 显示 motion_ 片段
|
||||
(前端契约字段全在)→ /api/ui/videos/<id> 详情 events+people 正常 → /api/proxy/frame 帧图 200。
|
||||
- **踩坑**:① sqlite3.Row 无 .get()(vrow 访问用下标);② ffmpeg subprocess args 首元素必须是可执行
|
||||
文件(漏了 ffmpeg 路径 → "No such file or directory: '-y'");③ **Oracle fam-edge 由 systemd
|
||||
fam-edge.service 守护(Restart=always)**——部署后必须 `sudo systemctl restart fam-edge`,手动
|
||||
setsid 启动会和守护打架(端口冲突 Connection in use)。
|
||||
- 旧整段分析的历史数据(319 个 done 素材)保留展示;新素材按新逻辑分割。
|
||||
|
||||
## 清理旧数据重提取 + 人物管理按运动视频重设计(commits 4cf4fc4/6a29e88/9664459)
|
||||
- **用户决策**:甲骨文+NAS 后台数据全删(整段视频提取的旧结果 + 人物数据),按新框架运动视频重新提取。
|
||||
- **清理**:Oracle videos(364)/events(1687)/people(44)/model_calls/service_activity 全清 + motion_clips 目录清空
|
||||
(保留 ss_motion_events 93 条 + 素材文件 + 配置;先备份 oracle.db.bak);NAS sync_videos/sync_events/sync_people/sync_cursor 全清。
|
||||
- **重提取**:systemctl restart fam-edge → producer 重新登记全部素材 → 分割(8/15-21 素材无运动事件→0 段;
|
||||
8/22 素材→运动片段)→ 片段分析 → 重新聚合人物(3 身份:爷爷/汤圆/媳妇)。
|
||||
- **问题**:历史素材分割 0 段会以"空会话"占满时间轴 → db_layer.get_sync_videos/get_sync_stats 加内容过滤
|
||||
(LEFT(filename,7)='motion_' OR EXISTS 有事件),前端契约不变。
|
||||
- **人物管理重设计**:PersonCard.vue 新增「运动片段」区块(缩略图/时间/摘要/事件数,点击跳 /timeline?video=);
|
||||
后端新增 GET /api/ui/people/clips?label=(db_layer.get_sync_people_clips 按 label/canonical_name 匹配
|
||||
person_list_json → 关联运动片段,含 first_ts/clip_events);Timeline.vue 支持 ?video= 定位。
|
||||
- **踩坑**:pymysql execute 用 % 做参数占位符,SQL 字面量含 'motion_%' 的 % 会报
|
||||
"unsupported format character" 500 → 改用 LEFT(filename,7)='motion_'。
|
||||
- 验证:/api/ui/videos 全为 motion_ 片段(15 条,含重新提取的 12:34 等新片段);people 3 身份;
|
||||
people/clips 返回媳妇 3 个片段。前端 dist 已构建部署(fam-ui/dist 不入库,构建产物单独 tar 部署)。
|
||||
|
||||
## 代码-文档一致性整理(commits 354ff18/01a8ac2,已 push)
|
||||
- README 全量同步 v3:2.1 网络要点(轮询主路径/Webhook 可选);2.2 拓扑图(Streamlit→Vue3、Video-Queue/分割);
|
||||
3.1-3.3 模块表(MotionNotifier poll 主路径+游标/心跳、UI-API、people clips、Person-Service);4.1 表清单
|
||||
(videos 加 motion_event_id/camera_id、ss_motion_events、sync_cursor 双游标);5.1-5.2 API 表(/api/ss/motion、
|
||||
/api/oracle/frame|avatar、/api/ui/people-clips、/api/ui/* 全表、/api/proxy/*);5.3 片段直传;6.1 历史标注;
|
||||
8.1-8.4 部署(systemd fam-edge、start_core.sh source .env、npm build、motion_segment config、数据清理指引);
|
||||
9 快速开始;12 进度。
|
||||
- docs/DEPLOY.md 重写为 v3(Vue3 构建/部署、systemd、tar 管道、验证清单含 motion_clips)。
|
||||
- 删除过时 scripts/start_ui.sh(Streamlit);start_edge.sh 注明 systemd 为准。
|
||||
- 补提交 fam-core/tests/test_motion_notifier.py。
|
||||
- 残留扫描:README 仅剩"不再依赖 Streamlit/从 Streamlit 迁移"正确表述。git 工作区干净(仅 .workbuddy/ 记忆)。
|
||||
|
||||
## 服务状态页「视频分割」状态卡(commit cbcc5d0,已部署)
|
||||
- 用户需求:服务状态界面要能看到视频分割状态(前端口误选方向后澄清)。
|
||||
- Oracle:oracle_db.get_segment_status()(motion_event_id 片段 total/done/pending/failed + ss_motion_events 事件数 +
|
||||
最近 segment 活动);api_gateway /api/oracle/activity 加 segment 字段 + clips_files(motion_clips 目录文件数)。
|
||||
- NAS:/api/ui/service-status 代理自动透传 segment(前端零后端改动)。
|
||||
- 前端:ServiceStatus.vue 新增「视频分割」ServiceCard(运动片段 done/total、文件数、待处理/失败、运动事件数、最近分割活动)。
|
||||
- 验证:Oracle activity.segment 返回 {total:75, done:54, pending:21, failed:0, clips_files:75, motion_events:99};
|
||||
NAS 代理 service-status 透传正常;前端 dist 已构建部署。
|
||||
|
||||
## 事件-分割一致性保障 + 历史事件回灌(commit 177c2a8)
|
||||
- **用户质疑**:Oracle 的 ss_motion_events 是否=SS 全部事件?为何没有 8/22 之前的?
|
||||
- **实测**:SS 事件中心保留 8/15 起全部运动事件(按天 191/47/256/419/334/256/312/196 ≈ 2000 条);
|
||||
Oracle 只有 8/22 起的——NAS 游标初始化故意"不回灌历史",属设计缺陷。
|
||||
- **修复 1(一致性缺口)**:SS 事件在动作进行中 duration=0,NAS 首次推送后游标前进不再重推 →
|
||||
Oracle duration 永久 0 → 分割过滤跳过 → 事件丢失(此前被"重启重推"掩盖)。MotionNotifier 加
|
||||
`_zero_dur_ids`:推送过 duration<=0 的事件记入,下轮窗口回查已结束(duration>0)补推覆盖。
|
||||
- **修复 2(历史回灌)**:NAS 一次性脚本拉取 8/15 00:00~8/22 00:00 的 SS 运动事件(分 6h 段)→
|
||||
POST /api/ss/motion 幂等落库,**1815 条全部入库**(与按天统计吻合);Oracle ss_motion_events 达
|
||||
1947 条(min event_id=1);重置 304 个 8/15-21 已 done 素材为 pending → 重启 Oracle 重新分割,
|
||||
历史运动片段持续生成(60s 内已 404 片段)。
|
||||
- **一致性保障清单**:①事件全覆盖(SS 全量→Oracle 幂等,回灌兜底);②duration 真实(0→补推);
|
||||
③游标续用/失败批次不前进(重启补推、不丢);④分割按 event_id 幂等(motion_event_id 关联);
|
||||
⑤start_time 对齐(SS epoch ↔ 素材文件名解析整秒,±0.5s 素材毫秒偏差可接受)。
|
||||
- **注意**:历史 1800+ 片段分析需大量 Gemini 配额(max_concurrent=1 + 429 重试),会排队跑很久。
|
||||
|
||||
## 事件-片段一致性对账(commit 1dd9df3,已部署)
|
||||
- 用户重复追问"如何保证 SS 事件与分割视频一致(含 start/duration)"→ 补可验证的对账机制。
|
||||
- oracle_db.get_segment_consistency():event_total(SS 全量 1947)/ finished(已结束可分割 1922)/
|
||||
segmented(已分割去重 1793)/ gap_count(缺口 129,动态收敛中)/ gaps 明细(event_id/start_time/duration)/
|
||||
material_range(素材覆盖 8/15 10:31~8/22 12:39)。
|
||||
- api_gateway activity.segment.consistency;前端服务状态页分割卡展示"一致性:事件/已结束/已分割/缺口"。
|
||||
- **缺口解读**:129 缺口主要是素材仍在队列处理(今天 13:0x 事件 + 少量历史 1222-1226),随分割收敛;
|
||||
真正无法分割的(素材窗口外/素材失败)会持续显示,可据此排查。
|
||||
- 一致性三层:数值(SS 原样+DO UPDATE+duration=0 补推)/数量(motion_event_id 幂等 1:1+对账缺口)/
|
||||
文件(-ss offset -t dur,±0.5s 毫秒取整、±1-2s 关键帧对齐)。
|
||||
|
||||
## 人物管理数据彻底清除(用户要求,无代码改动)
|
||||
- 用户:"人物管理全是错的(历史数据原因),人物相关数据都删除,相关的都做下删除"。
|
||||
- **删除范围**:Oracle people 表(9)、videos.people_json(465)、events.person_list_json(683)/person_appearances_json(640);
|
||||
NAS sync_people(7)、sync_videos.people_json(397)、sync_events 两字段(424/416)。备份 oracle.db.bak.20260822_055139。
|
||||
保留 videos(2124)/events(683)/ss_motion_events(1947)/chat_history。
|
||||
- **关键发现**:重启 Oracle 后 producer 会重新分析 **1646 个历史(8/15-21) pending 片段**并立即产生新人物
|
||||
(爷爷/人物A/人物B…),删了还会长 → 必须同时停掉历史片段分析:把 event_start_time < '2026-08-22 00:00:00'
|
||||
的 pending 片段标记 **skipped_reset**(不再分析、不产生人物),只保留 8/22 当天新运动片段分析提取人物。
|
||||
- 重启后 Oracle 正以 known_members=无 分析今日新片段 motion_25579(13:10 事件)→ 人物管理从今日数据重新提取
|
||||
(当前仅 1 个未命名"人物A")。验证:/api/ui/people=1 group、named-members=空、视频详情 people_json=None。
|
||||
|
||||
## 外网访问 8000 + 登录校验(commits 9c51c65/aca1a67,已部署)
|
||||
- 需求:NAS :8000 参考 3000 端口(Gitea)通过外网访问,且先登录才能访问(账号 ericwyuan / 密码 iLoveJava5)。
|
||||
- **外网机制(已查明)**:NAS 跑 frpc(/etc/frp/frpc.toml,S99frpc.sh 守护)→ 甲骨文 129.146.203.203:7000(frps)。
|
||||
已有 gitea(外3000→NAS3000)、wordpress(外8500→NAS8088)。新增 fam-core proxy:外网 **8000→NAS 8000**,
|
||||
S99frpc.sh restart 生效(proxy added: [fam-core gitea wordpress])。甲骨文安全组 8000 已放行(实测 200)。
|
||||
- **登录校验(新模块 auth.py)**:POST /api/login(凭据 FAM_AUTH_USER/FAM_AUTH_PASS,NAS .env 默认 ericwyuan/iLoveJava5)、
|
||||
/api/logout、/api/auth/check、GET /login 内置深色登录页(SPA 零改动);init_auth(app) 全局 before_request:
|
||||
页面未登录 302 /login、/api/* 未登录 401;白名单免登录:/login /api/login /api/logout /api/auth/check /health
|
||||
/api/ss/webhook /assets/* /favicon.ico。登录态=进程内 token+HttpOnly cookie(7 天),重启 fam-core 需重新登录。
|
||||
- **验证(真机)**:内网+外网全链路:未登录 302→/login、登录页 200、API 401、错误密码 401、正确登录 200+cookie、
|
||||
带 cookie 访问 /api/ui/videos 200(15 条运动片段)、/api/auth/check authed true/false、SS webhook 免登录 200。
|
||||
外网 http://129.146.203.203:8000 完整流程通过。测试注入的假 webhook 事件已清理(ss_motion_events 1947)。
|
||||
- **注意**:SS Webhook(/api/ss/webhook)是唯一免登录入站端点(SS 无法带登录态),外网暴露后有被滥发假事件的
|
||||
风险,如介意可后续给 webhook 加独立 token 或 frp secretKey;HTTP 明文传输,HTTPS 需另配。
|
||||
|
||||
## 恢复全部历史片段分析(用户决策,无代码改动)
|
||||
- 用户:"所有分割后没有处理的运动视频都需要处理" → 把之前删人物数据时标记的 **1646 个 skipped_reset 历史片段
|
||||
(8/15-21)全部改回 pending**(清空分析结果字段,文件齐全 0 缺失)。
|
||||
- 机制:video_queue.start() 的 `_enqueue_existing()`(get_pending_videos limit=10000)重启时把 pending 全部补入队;
|
||||
片段走 process_video 片段分支直接分析(motion_event_id 非空,local_path→motion_clips)。
|
||||
- 结果:systemctl restart fam-edge → 日志"启动恢复入队 1646 个待处理视频",queued=1645 + current=1(motion_25148,
|
||||
8/21 09:33 事件),从 8/21 往 8/15 顺序处理。单并发 + Gemini 429 重试,预计跑数小时~十数小时。
|
||||
30
.workbuddy/memory/MEMORY.md
Normal file
30
.workbuddy/memory/MEMORY.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# sentinel-home-ai 项目长期记忆
|
||||
|
||||
## 项目概况
|
||||
- 家庭多模态智能监控系统,仓库 `http://192.168.50.64:3000/ericwyuan/sentinel-home-ai`(Gitea 内网)
|
||||
- 本地 `/Users/ericwyuan/Desktop/Work/sentinel-home-ai`(monorepo:fam-core NAS 端 + fam-edge Oracle 端 + fam-ui Vue3 前端)
|
||||
|
||||
## 架构 v3(2026-08-22 定稿:运动事件驱动)
|
||||
- **不再分析整段视频**。Google Drive --rclone--> Oracle gdrive_videos(整段素材保留);
|
||||
Oracle 按 NAS 推送的 `ss_motion_events`(start_time/duration,Unix epoch)ffmpeg 分割运动片段
|
||||
(`-c:v copy -c:a aac` 保留音频,产物 `/opt/fam-edge/motion_clips/`),**只分析运动片段**。
|
||||
- NAS MotionNotifier 轮询 SS `EventCenter.Event.List`(60s,camera_ids=2, event_types=10)推送
|
||||
/api/ss/motion;游标 MariaDB 续用 + 失败批次不前进 + 心跳。Webhook 端点保留为可选补充。
|
||||
- **SS duration=0 = 动作进行中**(结束才回填真实时长),分割只处理已结束事件。
|
||||
- 前端(fam-ui Vue3)不改:/api/ui/videos|stats|people|attention-events|... 契约保持;
|
||||
事件帧图 /api/proxy/frame 用绝对 ts − event_start_time 偏移取帧,运动片段天然兼容。
|
||||
|
||||
## 服务器与部署
|
||||
- NAS (192.168.50.64):SSH 2222, ericwyuan/iLoveJava5;fam-core 代码 `/volume1/web/sentinel-home-ai`,
|
||||
venv `fam-core/venv`,`start_core.sh` source .env;重启 = kill gunicorn + `setsid bash start_core.sh`
|
||||
(NAS 无 systemd 守护)。
|
||||
- **Oracle (129.146.203.203)**:SSH key `~/.ssh/oracle_sentinel`;fam-edge 由 **systemd
|
||||
`fam-edge.service` 守护(Restart=always)**——部署代码后必须 `sudo systemctl restart fam-edge`,
|
||||
手动 setsid 会端口冲突;venv `/opt/fam-edge/venv`;库 `/opt/fam-edge/data/oracle.db`;
|
||||
素材 `/opt/fam-edge/gdrive_videos`;logs 目录 ubuntu 可写。
|
||||
- 部署:本地 git 提交 push Gitea → tar 管道(NAS 直接解包;Oracle `--strip-components=1` 到临时目录再 cp,避免动 data/venv)。
|
||||
|
||||
## 提交规范(强制)
|
||||
- 格式 `[阶段X.Y子任务号] 子任务名称 - 完成内容简述`;Bug 修复 `fix(模块): 问题简述`
|
||||
- 一任务一 commit;开工先 `git pull --rebase`;阻塞先 commit 加 `[WIP]`
|
||||
- 禁止 `git push --force` 和 `--no-verify`
|
||||
Reference in New Issue
Block a user