@camera.ui/server 0.0.113 → 0.0.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/interface/assets/{404-CZtDF9in.js → 404-Boj4Vl-R.js} +1 -1
- package/dist/interface/assets/{About-BzuuHdW1.js → About-C555n8hm.js} +1 -1
- package/dist/interface/assets/{Adminpanel-uj7eTmlN.js → Adminpanel-CzhTcMyI.js} +1 -1
- package/dist/interface/assets/Automation-c-WoDz5p.js +2 -0
- package/dist/interface/assets/{AutomationImport-CR4Vsxkb.js → AutomationImport-TTsCdeBi.js} +2 -2
- package/dist/interface/assets/{AutomationStore-BcMX7WbG.js → AutomationStore-Dfv66tKr.js} +2 -2
- package/dist/interface/assets/{AutomationStoreDetail-D6Ugj8ba.js → AutomationStoreDetail-D6Kxd8qb.js} +2 -2
- package/dist/interface/assets/{Automations-DBACrYHy.js → Automations-B-Vx8ES0.js} +2 -2
- package/dist/interface/assets/{Camera-Cf4QZbsx.js → Camera-D9lOlctO.js} +2 -2
- package/dist/interface/assets/{CameraConsole-uD4aWou8.js → CameraConsole-D7INO7No.js} +1 -1
- package/dist/interface/assets/{Cameras-DbBU5mou.js → Cameras-CoS5FGgt.js} +2 -2
- package/dist/interface/assets/{Camview-CDM4Q_tc.css → Camview-CS2a2okQ.css} +1 -1
- package/dist/interface/assets/{Camview-DD9jLT7-.js → Camview-D9dWFP3P.js} +1 -1
- package/dist/interface/assets/{Config-B9jI0lTQ.js → Config-Bt-dYeu3.js} +1 -1
- package/dist/interface/assets/{ConfigActionCameraControl-D4hJLWmj.js → ConfigActionCameraControl-BG6SX9R7.js} +1 -1
- package/dist/interface/assets/{ConfigActionDelay-DOUAx4lh.js → ConfigActionDelay-aDgo-kGM.js} +1 -1
- package/dist/interface/assets/{ConfigActionHttp-BGz4r_Nr.js → ConfigActionHttp-CO5QD14x.js} +1 -1
- package/dist/interface/assets/{ConfigActionImageInput-BhO3ekJT.js → ConfigActionImageInput-DapP_uBr.js} +1 -1
- package/dist/interface/assets/{ConfigActionNotification-B5cCmBXr.js → ConfigActionNotification-BTO9Mcqc.js} +1 -1
- package/dist/interface/assets/{ConfigActionOutput-P0WY0DlF.js → ConfigActionOutput-CXJ03zBN.js} +1 -1
- package/dist/interface/assets/{ConfigActionPlugin-B7YAbFlf.js → ConfigActionPlugin-BddeoS5Q.js} +1 -1
- package/dist/interface/assets/{ConfigActionSensor-D_Dt1qtt.js → ConfigActionSensor-Bs4CS6z9.js} +1 -1
- package/dist/interface/assets/{ConfigActionSnapshot-ClyCQuB3.js → ConfigActionSnapshot-Ba5eeeDx.js} +1 -1
- package/dist/interface/assets/{ConfigActionVariable-B2BoiQPb.js → ConfigActionVariable-CtDsvCYd.js} +1 -1
- package/dist/interface/assets/{ConfigConditionIfElse-Bhd2FYyz.js → ConfigConditionIfElse-4vRtS2gU.js} +1 -1
- package/dist/interface/assets/{ConfigConditionSensorState-x0M0kHwL.js → ConfigConditionSensorState-DXsn0AoB.js} +1 -1
- package/dist/interface/assets/{ConfigConditionSwitch-CC0w-Hvy.js → ConfigConditionSwitch-BAAlXCNL.js} +1 -1
- package/dist/interface/assets/{ConfigConditionTime-lhWiIy2x.js → ConfigConditionTime-CbGe7r2f.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerDetection-DWjlecON.js → ConfigTriggerDetection-CUvVQzWC.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerGeofence-CTqptMUN.js → ConfigTriggerGeofence-C8JI5HfV.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerManual-ChsUsNCK.js → ConfigTriggerManual-CHe56Uwq.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSchedule-CE47WiIV.js → ConfigTriggerSchedule-CfYWEMEa.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSensor-DuCtavZo.js → ConfigTriggerSensor-CxfydqlA.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSystem-D1oGAhLn.js → ConfigTriggerSystem-CKY5ikOU.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerWebhook-D3cHvW8H.js → ConfigTriggerWebhook-B38CUm9k.js} +1 -1
- package/dist/interface/assets/Console-iQYK1wv3.js +1 -0
- package/dist/interface/assets/{CuiActionButton-Bl7gqhIV.js → CuiActionButton-_24B3o_r.js} +1 -1
- package/dist/interface/assets/{CuiAvatar-of_eVQsv.js → CuiAvatar-Dq7tZWI5.js} +1 -1
- package/dist/interface/assets/{CuiBBoxPlayground-CIU5kteO.js → CuiBBoxPlayground-wkKhVNXF.js} +1 -1
- package/dist/interface/assets/CuiBottomSheet-BCwkNHr0.js +1 -0
- package/dist/interface/assets/{CuiBottomSheet-B7aQ7GCw.js → CuiBottomSheet-CmcHyY52.js} +1 -1
- package/dist/interface/assets/CuiCameraCard-Cjltc6jX.js +1 -0
- package/dist/interface/assets/{CuiCameraPipCard-KUBxGPoU.js → CuiCameraPipCard-Fqb-8bT0.js} +1 -1
- package/dist/interface/assets/CuiCameraRecordings-8rROz71S.js +1 -0
- package/dist/interface/assets/{CuiCameraShares-BCMYN11A.js → CuiCameraShares-CVqgvvb9.js} +1 -1
- package/dist/interface/assets/{CuiCameraSnapshot-DTtvEMy1.js → CuiCameraSnapshot-CDqd-Ncv.js} +1 -1
- package/dist/interface/assets/{CuiCameraTable-B7g53tPM.js → CuiCameraTable-CZI750xd.js} +1 -1
- package/dist/interface/assets/CuiDataTable-N4GSbqxH.js +1 -0
- package/dist/interface/assets/CuiDataTable-W68cyoLf.css +1 -0
- package/dist/interface/assets/{CuiFloatingButton-B5YMdr0X.js → CuiFloatingButton-PyVdoOUT.js} +1 -1
- package/dist/interface/assets/{CuiImage-B97QDAl7.js → CuiImage-kthY6BIz.js} +1 -1
- package/dist/interface/assets/{CuiMenu-Ds6GufVn.js → CuiMenu-9X-MfyCk.js} +1 -1
- package/dist/interface/assets/{CuiPluginCard-D1SylQbM.js → CuiPluginCard-CN3TW6AC.js} +2 -2
- package/dist/interface/assets/CuiPluginCategoryChip-CwNmRXlc.js +1 -0
- package/dist/interface/assets/{CuiPluginOAuthButton-X8ZGFPwW.js → CuiPluginOAuthButton-BBHNQ3Jp.js} +2 -2
- package/dist/interface/assets/{CuiPluginTrustBadge-YhfqGTyR.js → CuiPluginTrustBadge-DX-P6_uN.js} +1 -1
- package/dist/interface/assets/{CuiQRCode-D_31ri5b.js → CuiQRCode-C6O_MBni.js} +1 -1
- package/dist/interface/assets/{CuiRecordingsGrid-CzMzhUXb.js → CuiRecordingsGrid-CwKCU5_u.js} +1 -1
- package/dist/interface/assets/{CuiSchema-CfqOJmDH.js → CuiSchema-VI2Luf-k.js} +1 -1
- package/dist/interface/assets/{CuiTopNavbarItem-Q7M4nMw5.js → CuiTopNavbarItem-Ds70WyHx.js} +1 -1
- package/dist/interface/assets/{CuiTopbarSlot-ac4GLLbf.js → CuiTopbarSlot-B9Et-Pj7.js} +1 -1
- package/dist/interface/assets/{Faces-IVmqcGuF.js → Faces-lzakFq5o.js} +1 -1
- package/dist/interface/assets/{FirstSteps-Boec1bzd.js → FirstSteps-Bqmgm_iz.js} +1 -1
- package/dist/interface/assets/{Instances-kmkRiBrV.js → Instances-qk8oD08W.js} +1 -1
- package/dist/interface/assets/{Logs-DzR8mmLu.js → Logs-72k5RAn7.js} +1 -1
- package/dist/interface/assets/{Menu-Cl-biyd7.js → Menu-1_PvQcZH.js} +1 -1
- package/dist/interface/assets/{OAuthDeviceFlow-Cp9uZWm1.js → OAuthDeviceFlow-DuZPGhPb.js} +1 -1
- package/dist/interface/assets/Plugin-CoIr19GV.js +1 -0
- package/dist/interface/assets/Plugin-DHhrlRqP.css +1 -0
- package/dist/interface/assets/{PluginConsole-BdbmROpF.js → PluginConsole-CnqDVu4K.js} +1 -1
- package/dist/interface/assets/PluginDetail-BSao1Bk3.js +2 -0
- package/dist/interface/assets/PluginDetail-L1Vtmr5x.css +1 -0
- package/dist/interface/assets/{PluginSchema-CWcq67pj.js → PluginSchema-BNB_PpKf.js} +1 -1
- package/dist/interface/assets/PluginSearch-Cq0r9Usy.js +2 -0
- package/dist/interface/assets/{PluginSearch-XjGr5qdW.css → PluginSearch-NuKgD1r3.css} +1 -1
- package/dist/interface/assets/PluginSearch-ZRpBJ3RH.js +1 -0
- package/dist/interface/assets/{Plugins-DMLQY_6J.js → Plugins-Bm0cyXcG.js} +1 -1
- package/dist/interface/assets/Recordings-BArCkOPF.js +1 -0
- package/dist/interface/assets/{Settings-BljKDhYY.js → Settings-CcwljkEU.js} +1 -1
- package/dist/interface/assets/SettingsAccount-UGedhV9S.js +1 -0
- package/dist/interface/assets/{SettingsAccount-DKHO4mDj.js → SettingsAccount-g0Xg9CEA.js} +1 -1
- package/dist/interface/assets/{SettingsAppearance-V5aeQ9c8.js → SettingsAppearance-D7vpw4Pf.js} +1 -1
- package/dist/interface/assets/SettingsAppearance-dUmIIO1_.js +1 -0
- package/dist/interface/assets/SettingsBackup-DDMNMyGv.js +1 -0
- package/dist/interface/assets/{SettingsNotifications-BZ1IXj0a.js → SettingsNotifications-DHXDTw47.js} +1 -1
- package/dist/interface/assets/{SettingsRecordings-BsJ9z-RE.js → SettingsRecordings-B8pVTT97.js} +1 -1
- package/dist/interface/assets/{SettingsRemote-Bf8bqXYs.js → SettingsRemote-Cm72h9O2.js} +1 -1
- package/dist/interface/assets/{SettingsSystem-obAYrm8J.js → SettingsSystem-IU5zzJ8r.js} +2 -2
- package/dist/interface/assets/{SettingsUsers-Ciu1ry18.js → SettingsUsers-Wgu60ci9.js} +1 -1
- package/dist/interface/assets/{Terminal-CD9he5xW.js → Terminal-D1CRBDlt.js} +1 -1
- package/dist/interface/assets/{VariableInput-CFs-kzqd.js → VariableInput-D5330Mfo.js} +1 -1
- package/dist/interface/assets/{VariableSuggestions-PEzupqiE.js → VariableSuggestions-BYRGZZhC.js} +1 -1
- package/dist/interface/assets/VersionsHandler-hDdaLt6c.js +7 -0
- package/dist/interface/assets/Workers-De7GyYLY.js +2 -0
- package/dist/interface/assets/{Workers-DCx1wSq5.css → Workers-E6LM_yAl.css} +1 -1
- package/dist/interface/assets/api-CEmKJS_k.js +2 -0
- package/dist/interface/assets/{api-CbBOEbKQ.js → api-CniTJ7zF.js} +1 -1
- package/dist/interface/assets/api-D7tGlI-z.css +2 -0
- package/dist/interface/assets/{auth-cdPNNpNC.js → auth-XrGrvKJw.js} +1 -1
- package/dist/interface/assets/{automations-E7vwN3kw.js → automations-BTNkOZVN.js} +1 -1
- package/dist/interface/assets/{automations-C309DNNg.js → automations-DBL-AVqV.js} +1 -1
- package/dist/interface/assets/backup-DCACCZlp.js +1 -0
- package/dist/interface/assets/{cameras-CvJDLEZ3.js → cameras-BU1ZCguU.js} +1 -1
- package/dist/interface/assets/{cameras.schema-Clwro_L9.js → cameras.schema-xccbs98C.js} +1 -1
- package/dist/interface/assets/cloudHandoff-l8EehWAq.js +1 -0
- package/dist/interface/assets/{lightbulb-outline-B0CFho4y.js → cog-outline-DUNHSyxs.js} +1 -1
- package/dist/interface/assets/{deviceId-OXLTtG-_.js → deviceId-BDIV3wVa.js} +1 -1
- package/dist/interface/assets/download-D5N3Bjsy.js +1 -0
- package/dist/interface/assets/{hasPermission-P1TgpAur.js → hasPermission-Dt66r7m6.js} +1 -1
- package/dist/interface/assets/{instances-DwnEOXk1.js → instances-Cm2uvc3I.js} +1 -1
- package/dist/interface/assets/lightbulb-outline-CSHur8g1.js +1 -0
- package/dist/interface/assets/{locale-BWvy9Nlz.js → locale-DHFGj38b.js} +1 -1
- package/dist/interface/assets/main-BpXAVhDw.js +3 -0
- package/dist/interface/assets/{main-DudyqPaR.css → main-DYLy_5Pn.css} +1 -1
- package/dist/interface/assets/nodeDefinitions-BvHN6Rgq.js +1 -0
- package/dist/interface/assets/{notifications-DHmx6O_s.js → notifications-CcPAq-AU.js} +1 -1
- package/dist/interface/assets/{plugins-NVAodP6s.js → plugins-Byvz--5u.js} +1 -1
- package/dist/interface/assets/round-restart-alt-BhSxsPhN.css +1 -0
- package/dist/interface/assets/{round-restart-alt-c8MQjhLh.js → round-restart-alt-wY5ZWF_I.js} +1 -1
- package/dist/interface/assets/router-BI5yR4wx.js +2 -0
- package/dist/interface/assets/{router-BUTKN1yM.css → router-qrIK2v9X.css} +1 -1
- package/dist/interface/assets/{routerState-dQtObsFs.js → routerState-1OSE3RrK.js} +1 -1
- package/dist/interface/assets/{server-CRxodRik.js → server-BZ6moxcL.js} +1 -1
- package/dist/interface/assets/{shares-4w6GDwjp.js → shares-Dnb9ln4i.js} +1 -1
- package/dist/interface/assets/{theme-DhaXe7yA.js → theme-CMPHpEel.js} +1 -1
- package/dist/interface/assets/{types-6RvFwckU.js → types-CxqXp8-9.js} +1 -1
- package/dist/interface/assets/types-Dhfpp18Q.js +1 -0
- package/dist/interface/assets/{ui-DrzKhUXW.js → ui-DoqHmuEp.js} +1 -1
- package/dist/interface/assets/{useBiometricCredentials-BGRyKe48.js → useBiometricCredentials-D0zMU6nI.js} +1 -1
- package/dist/interface/assets/{useCameraOptions-CO1HD_gL.js → useCameraOptions-ICvT3348.js} +1 -1
- package/dist/interface/assets/{useCuiCameraDrawer-DnN9lwiy.js → useCuiCameraDrawer-Cz6XoSl4.js} +1 -1
- package/dist/interface/assets/{useCuiDialog-DHktK-mZ.js → useCuiDialog-D3TnoSLA.js} +1 -1
- package/dist/interface/assets/{useFileDownload-Cp1oH80I.js → useFileDownload-s0dzBJ-h.js} +1 -1
- package/dist/interface/assets/{useLogsSocket-OnTDnkPr.js → useLogsSocket-GhBHm7PL.js} +1 -1
- package/dist/interface/assets/{useSharedComposables-gpJ--qZh.js → useSharedComposables-BwzJuqID.js} +1 -1
- package/dist/interface/assets/{useStreamStatus-N--czepD.js → useStreamStatus-DffcpWzm.js} +1 -1
- package/dist/interface/assets/{useUserOptions-CPkl1O0A.js → useUserOptions-Cvb_O9Oh.js} +1 -1
- package/dist/interface/assets/{users.schema-BIL9DqRo.js → users.schema-Csh8aGMQ.js} +1 -1
- package/dist/interface/assets/{vendor-cameraui-DgSMexWY.js → vendor-cameraui-DQVBM-Sz.js} +38 -38
- package/dist/interface/assets/{vendor-core-BKHiXSX7.js → vendor-core-C5n49w0D.js} +1 -1
- package/dist/interface/assets/{vendor-primevue-7iEzPOvf.js → vendor-primevue-CCUVk9_F.js} +1 -1
- package/dist/interface/assets/{view-grid-B0KB2pRf.js → view-grid-CK2x8Wqf.js} +1 -1
- package/dist/interface/index.html +46 -45
- package/dist/interface/sw.js +1 -1
- package/dist/server/package.json +3 -3
- package/dist/server/requirements.txt +1 -1
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/frames.py +3 -1
- package/dist/server/src/api/controllers/backup.controller.d.ts +7 -1
- package/dist/server/src/api/controllers/backup.controller.js +65 -1
- package/dist/server/src/api/controllers/backup.controller.js.map +1 -1
- package/dist/server/src/api/database/index.d.ts +1 -1
- package/dist/server/src/api/database/index.js +5 -5
- package/dist/server/src/api/database/index.js.map +1 -1
- package/dist/server/src/api/database/types.d.ts +18 -0
- package/dist/server/src/api/routes/backup.routes.js +54 -1
- package/dist/server/src/api/routes/backup.routes.js.map +1 -1
- package/dist/server/src/api/schemas/backup.schema.d.ts +28 -25
- package/dist/server/src/api/schemas/backup.schema.js +23 -60
- package/dist/server/src/api/schemas/backup.schema.js.map +1 -1
- package/dist/server/src/api/schemas/cameras.schema.d.ts +8 -0
- package/dist/server/src/api/schemas/cameras.schema.js +2 -0
- package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
- package/dist/server/src/api/services/backup.service.d.ts +1 -1
- package/dist/server/src/api/services/backup.service.js +2 -1
- package/dist/server/src/api/services/backup.service.js.map +1 -1
- package/dist/server/src/api/services/backupScheduler.service.d.ts +31 -0
- package/dist/server/src/api/services/backupScheduler.service.js +203 -0
- package/dist/server/src/api/services/backupScheduler.service.js.map +1 -0
- package/dist/server/src/api/types/index.d.ts +7 -1
- package/dist/server/src/api/websocket/nsp/notifications.js +3 -3
- package/dist/server/src/api/websocket/nsp/notifications.js.map +1 -1
- package/dist/server/src/camera/decoder/detection-coordinator.d.ts +13 -1
- package/dist/server/src/camera/decoder/detection-coordinator.js +242 -242
- package/dist/server/src/camera/decoder/detection-coordinator.js.map +1 -1
- package/dist/server/src/camera/decoder/event-manager.js +41 -18
- package/dist/server/src/camera/decoder/event-manager.js.map +1 -1
- package/dist/server/src/camera/decoder/frame-scaler.d.ts +1 -0
- package/dist/server/src/camera/decoder/frame-scaler.js +2 -2
- package/dist/server/src/camera/decoder/frame-scaler.js.map +1 -1
- package/dist/server/src/camera/decoder/frame-source.js +2 -1
- package/dist/server/src/camera/decoder/frame-source.js.map +1 -1
- package/dist/server/src/camera/decoder/snapshot-source.d.ts +49 -0
- package/dist/server/src/camera/decoder/snapshot-source.js +279 -0
- package/dist/server/src/camera/decoder/snapshot-source.js.map +1 -0
- package/dist/server/src/camera/decoder/worker.d.ts +1 -0
- package/dist/server/src/camera/decoder/worker.js +27 -4
- package/dist/server/src/camera/decoder/worker.js.map +1 -1
- package/dist/server/src/go2rtc/index.js +3 -2
- package/dist/server/src/go2rtc/index.js.map +1 -1
- package/dist/server/src/main.d.ts +1 -0
- package/dist/server/src/main.js +11 -0
- package/dist/server/src/main.js.map +1 -1
- package/dist/server/src/manager/downloadManager.js +2 -2
- package/dist/server/src/manager/downloadManager.js.map +1 -1
- package/dist/server/src/plugins/runtime/python/child.py +11 -0
- package/dist/server/src/plugins/runtime/python/namespaces.py +4 -0
- package/dist/server/src/plugins/runtime/python/proxy/download_manager.py +12 -2
- package/dist/server/src/plugins/runtime/python/proxy/file_serve.py +49 -0
- package/dist/server/src/plugins/worker.js +14 -4
- package/dist/server/src/plugins/worker.js.map +1 -1
- package/dist/server/src/remote/tunnel/client.js +3 -2
- package/dist/server/src/remote/tunnel/client.js.map +1 -1
- package/dist/server/src/rpc/server.d.ts +0 -1
- package/dist/server/src/rpc/server.js +8 -7
- package/dist/server/src/rpc/server.js.map +1 -1
- package/dist/server/src/shutdown-state.d.ts +3 -0
- package/dist/server/src/shutdown-state.js +11 -0
- package/dist/server/src/shutdown-state.js.map +1 -0
- package/dist/server/src/workers/agent.js +4 -4
- package/dist/server/src/workers/agent.js.map +1 -1
- package/dist/server/src/workers/capabilities/plugin-host.js +5 -4
- package/dist/server/src/workers/capabilities/plugin-host.js.map +1 -1
- package/package.json +3 -3
- package/dist/interface/assets/Automation-D0ps_z9e.js +0 -2
- package/dist/interface/assets/Console-DrDqDUtO.js +0 -1
- package/dist/interface/assets/CuiBottomSheet-DKUg_g5x.js +0 -1
- package/dist/interface/assets/CuiCameraCard-IyJNZMGs.js +0 -1
- package/dist/interface/assets/CuiCameraRecordings-iWAFGo8e.js +0 -1
- package/dist/interface/assets/CuiDataTable-DWuXg9Zg.css +0 -1
- package/dist/interface/assets/CuiDataTable-saRt0sBc.js +0 -1
- package/dist/interface/assets/CuiPluginCategoryChip-rfdhCEi-.js +0 -1
- package/dist/interface/assets/Plugin-CeLrucXT.js +0 -1
- package/dist/interface/assets/Plugin-Yv0ZmTNZ.css +0 -1
- package/dist/interface/assets/PluginDetail-DKywYzmS.js +0 -2
- package/dist/interface/assets/PluginDetail-DbYxVe3g.css +0 -1
- package/dist/interface/assets/PluginSearch-BC1L0-VN.js +0 -1
- package/dist/interface/assets/PluginSearch-DCuLwE3v.js +0 -2
- package/dist/interface/assets/Recordings--XaVJZJi.js +0 -1
- package/dist/interface/assets/SettingsAccount-DFMOh1ci.js +0 -1
- package/dist/interface/assets/SettingsAppearance-DzG2tlwK.js +0 -1
- package/dist/interface/assets/SettingsBackup-CfOHDar8.js +0 -1
- package/dist/interface/assets/VersionsHandler-Dpo-L314.js +0 -7
- package/dist/interface/assets/Workers-DiIBLgfr.js +0 -2
- package/dist/interface/assets/api-aSYFKxo6.js +0 -2
- package/dist/interface/assets/api-vCJ1T8Ow.css +0 -2
- package/dist/interface/assets/backup-Co1bwdSv.js +0 -1
- package/dist/interface/assets/cloudHandoff-Bym-XqR1.js +0 -1
- package/dist/interface/assets/main-CIorSaV7.js +0 -3
- package/dist/interface/assets/nodeDefinitions-Dvawtk76.js +0 -1
- package/dist/interface/assets/round-restart-alt-fCNtJi_k.css +0 -1
- package/dist/interface/assets/router-i0r3QZvk.js +0 -2
- package/dist/interface/assets/types-BXo-JDGR.js +0 -1
|
@@ -62,7 +62,6 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
62
62
|
});
|
|
63
63
|
import { isNoRespondersError, RPCClass, RPCMethod } from '@camera.ui/rpc';
|
|
64
64
|
import { SensorType } from '@camera.ui/sdk';
|
|
65
|
-
// import { writeFileSync } from 'node:fs';
|
|
66
65
|
import { NamespaceManager } from '../../rpc/namespaces.js';
|
|
67
66
|
import { DETECTION_SENSOR_TYPES, SENSOR_TYPE_CONFIG } from '../sensors/types.js';
|
|
68
67
|
import { normalizeZone } from '../utils/filter.js';
|
|
@@ -74,19 +73,26 @@ import { DetectionEventManager } from './event-manager.js';
|
|
|
74
73
|
import { FrameScaler } from './frame-scaler.js';
|
|
75
74
|
import { FrameSource } from './frame-source.js';
|
|
76
75
|
import { PtzAutotracker } from './ptz-autotracker.js';
|
|
76
|
+
import { SnapshotSource } from './snapshot-source.js';
|
|
77
77
|
import { MOTION_WIDTH_MAP } from './types.js';
|
|
78
78
|
// Default cascade hold-open window in seconds
|
|
79
79
|
export const DEFAULT_CASCADE_TIMEOUT = 10;
|
|
80
80
|
// Tracks below this speed (normalized units/s) are considered stationary.
|
|
81
81
|
// Typical values: parked bike ~0.0006, standing person ~0.003–0.03.
|
|
82
82
|
export const STATIONARY_SPEED_THRESHOLD = 0.002;
|
|
83
|
-
// Object detection dwell window — bridges 1-2 missed frames inside an active
|
|
84
|
-
// cascade so a single uncertain frame doesn't fire `detected=false` and split
|
|
85
|
-
// the event. Mirrors the motion/audio dwell pattern.
|
|
86
83
|
const OBJECT_DWELL_SECONDS = 2;
|
|
87
|
-
// Hard deadline on a detector-plugin inference RPC. A hung detector must not
|
|
88
|
-
// stall the whole detection loop — on timeout we skip the frame and continue.
|
|
89
84
|
const DETECT_TIMEOUT_MS = 30_000;
|
|
85
|
+
const EVENT_THUMB_MAX_WIDTH = 640;
|
|
86
|
+
const DETECTION_THUMB_MAX_WIDTH = 640;
|
|
87
|
+
const DETECTION_THUMB_MIN_CROP = 160;
|
|
88
|
+
const DETECTION_THUMB_HQ_MAX_WIDTH = 640;
|
|
89
|
+
const DETECTION_THUMB_HQ_MIN_CROP = 640;
|
|
90
|
+
const DETECTION_THUMB_HQ_QUALITY = 80;
|
|
91
|
+
const ATTRIBUTE_THUMB_MAX_WIDTH = 640;
|
|
92
|
+
const ATTRIBUTE_THUMB_MIN_CROP = 128;
|
|
93
|
+
const EVENT_THUMB_HQ_MAX_WIDTH = 960;
|
|
94
|
+
const EVENT_THUMB_HQ_QUALITY = 75;
|
|
95
|
+
const HQ_FRAME_MAX_AGE_MS = 500;
|
|
90
96
|
async function withTimeout(promise, ms, label) {
|
|
91
97
|
let timer;
|
|
92
98
|
const timeout = new Promise((_resolve, reject) => {
|
|
@@ -121,6 +127,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
121
127
|
pipeline;
|
|
122
128
|
adHocVideoLoop = false;
|
|
123
129
|
processingExternalSecondary = false;
|
|
130
|
+
hqSnapshotSource;
|
|
131
|
+
hqUpgradeInflight = false;
|
|
132
|
+
hqAspectWarned = false;
|
|
124
133
|
cascadeUnsubscribe;
|
|
125
134
|
dwellUnsubscribe;
|
|
126
135
|
seenStationaryTracks = new Set();
|
|
@@ -159,6 +168,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
159
168
|
this.frameSource = new FrameSource(frameSourceConfig, logger);
|
|
160
169
|
this.frameScaler = new FrameScaler(null, logger);
|
|
161
170
|
this.pipeline = new DetectionPipeline(config.zones, config.detectionSettings);
|
|
171
|
+
const hqUrl = this.resolveHqSourceUrl(config.availableSources);
|
|
172
|
+
if (hqUrl) {
|
|
173
|
+
this.hqSnapshotSource = new SnapshotSource({ url: hqUrl }, logger);
|
|
174
|
+
}
|
|
175
|
+
this.syncHqSnapshotSource();
|
|
162
176
|
if (config.lines.length > 0) {
|
|
163
177
|
this.pipeline.updateLines(config.lines, this.videoAspectRatio);
|
|
164
178
|
}
|
|
@@ -312,19 +326,13 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
312
326
|
detected: filtered.length > 0,
|
|
313
327
|
};
|
|
314
328
|
}
|
|
329
|
+
// PTZ-suppression gating happens upstream — callers decide whether a write
|
|
330
|
+
// belongs in the full pipeline or autotracker-only.
|
|
315
331
|
ingestDetectionResult(sensorType, sensorId, properties, pluginId) {
|
|
316
|
-
// Note: PTZ-suppression gating happens upstream — processDetection and
|
|
317
|
-
// reportSensorWrite decide whether to route to autotracker-only vs. full
|
|
318
|
-
// ingest. By the time we land here, the caller has already decided this
|
|
319
|
-
// write belongs in the full pipeline.
|
|
320
|
-
// Update aggregated buffer based on sensor type. Each type maps to a different
|
|
321
|
-
// ProcessedDetectionData field — partial-merge with what's already buffered.
|
|
322
332
|
this.updateBufferForType(sensorType, properties, pluginId);
|
|
323
333
|
const detected = properties.detected === true;
|
|
324
|
-
// Motion/Audio:
|
|
325
|
-
//
|
|
326
|
-
// dwell.onChange listener wired in the constructor. Motion additionally
|
|
327
|
-
// triggers cascade.
|
|
334
|
+
// Motion/Audio: dwell drives the detected/blocked/lastTriggered lifecycle
|
|
335
|
+
// entirely (see dwell.onChange in the constructor); motion also cascades.
|
|
328
336
|
if (sensorType === SensorType.Motion && detected) {
|
|
329
337
|
const motionTimeout = this.config.detectionSettings.motion.timeout;
|
|
330
338
|
this.dwell.refresh(sensorId, motionTimeout);
|
|
@@ -340,15 +348,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
340
348
|
}
|
|
341
349
|
}
|
|
342
350
|
else if (sensorType === SensorType.Object && detected) {
|
|
343
|
-
// Object dwell — anti-chatter
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
// This prevents parked cars/bikes from keeping events alive forever.
|
|
349
|
-
// Moving tracks always refresh. Stationary tracks refresh only once
|
|
350
|
-
// (first time seen) — after that they're in seenStationaryTracks and
|
|
351
|
-
// get skipped. The set is cleared on cascade deactivate (event boundary).
|
|
351
|
+
// Object dwell — anti-chatter: a single missed frame must not split the
|
|
352
|
+
// event. Stationary tracks refresh the dwell only ONCE (first sighting);
|
|
353
|
+
// afterwards they're in seenStationaryTracks and don't count, so parked
|
|
354
|
+
// cars/bikes can't keep events alive forever. Moving tracks always count.
|
|
355
|
+
// The set is cleared on cascade deactivate (event boundary).
|
|
352
356
|
const detections = properties.detections ?? [];
|
|
353
357
|
let hasActiveTrack = false;
|
|
354
358
|
for (const t of detections) {
|
|
@@ -357,40 +361,31 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
357
361
|
const isStationary = (t.trackSpeed ?? 0) < STATIONARY_SPEED_THRESHOLD;
|
|
358
362
|
if (isStationary) {
|
|
359
363
|
if (t.trackId !== undefined && !this.seenStationaryTracks.has(t.trackId)) {
|
|
360
|
-
// First time seeing this stationary track — count it as active
|
|
361
364
|
this.seenStationaryTracks.add(t.trackId);
|
|
362
365
|
hasActiveTrack = true;
|
|
363
366
|
}
|
|
364
|
-
// Already seen stationary track — does not count
|
|
365
367
|
}
|
|
366
368
|
else {
|
|
367
|
-
// Moving track — always counts
|
|
368
369
|
hasActiveTrack = true;
|
|
369
370
|
}
|
|
370
371
|
}
|
|
371
372
|
if (hasActiveTrack) {
|
|
372
373
|
this.dwell.refresh(sensorId, OBJECT_DWELL_SECONDS);
|
|
373
|
-
// this.logger.debug('[stationary-debug] active track found, dwell refreshed');
|
|
374
374
|
}
|
|
375
375
|
else {
|
|
376
|
-
// Only known stationary tracks — suppress detected and clear
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
// property publish (publishProps path below).
|
|
376
|
+
// Only known stationary tracks — suppress detected and clear buffered
|
|
377
|
+
// detections so the EventManager doesn't open a new segment; the UI
|
|
378
|
+
// still gets the bboxes via the property publish below.
|
|
380
379
|
properties.detected = false;
|
|
381
380
|
if (this.currentDetectionState.object) {
|
|
382
381
|
this.currentDetectionState.object.detected = false;
|
|
383
382
|
this.currentDetectionState.object.detections = [];
|
|
384
383
|
}
|
|
385
|
-
// this.logger.debug('[stationary-debug] suppressed — all tracks stationary+known');
|
|
386
384
|
}
|
|
387
385
|
}
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
// must NOT overwrite the dwell-set `detected=true`. Always strip these
|
|
392
|
-
// dwell-managed properties from the publish, and let `dwell.onChange`
|
|
393
|
-
// (activated/expired) be the only path that writes them.
|
|
386
|
+
// Dwell is the SOLE owner of detected/blocked/lastTriggered for these
|
|
387
|
+
// types — a "no detection" frame must not overwrite the dwell-set state,
|
|
388
|
+
// so strip them from the publish; dwell.onChange is the only writer.
|
|
394
389
|
let publishProps = properties;
|
|
395
390
|
if (sensorType === SensorType.Motion || sensorType === SensorType.Audio || sensorType === SensorType.Object) {
|
|
396
391
|
const { detected: _d, blocked: _b, lastTriggered: _t, ...rest } = properties;
|
|
@@ -399,8 +394,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
399
394
|
if (Object.keys(publishProps).length > 0) {
|
|
400
395
|
this.writeSensorProperties(sensorId, publishProps);
|
|
401
396
|
}
|
|
402
|
-
// EventManager is NOT ticked here —
|
|
403
|
-
// per frame at the end of processDetection() after all detectors + thumbnails.
|
|
397
|
+
// EventManager is NOT ticked here — processDetection flushes once per frame.
|
|
404
398
|
}
|
|
405
399
|
updateBufferForType(sensorType, properties, pluginId) {
|
|
406
400
|
const cs = this.currentDetectionState;
|
|
@@ -455,6 +449,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
455
449
|
}
|
|
456
450
|
updateFrameWorkerSettings(settings) {
|
|
457
451
|
this.config.frameWorkerSettings = settings;
|
|
452
|
+
this.syncHqSnapshotSource();
|
|
458
453
|
}
|
|
459
454
|
updateInterfaceSettings(settings) {
|
|
460
455
|
this.config.interfaceSettings = settings;
|
|
@@ -465,6 +460,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
465
460
|
async dispose() {
|
|
466
461
|
await this.stopVideoLoop();
|
|
467
462
|
await this.stopAudioLoop();
|
|
463
|
+
await this.hqSnapshotSource?.stop();
|
|
468
464
|
this.ptzAutotracker.dispose();
|
|
469
465
|
this.cascadeUnsubscribe?.();
|
|
470
466
|
this.dwellUnsubscribe?.();
|
|
@@ -571,9 +567,8 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
571
567
|
if (!this.singlePlugins.has(SensorType.Object)) {
|
|
572
568
|
this.adHocVideoLoop = false;
|
|
573
569
|
}
|
|
574
|
-
// Hot-reload:
|
|
575
|
-
//
|
|
576
|
-
// don't accidentally stop a loop still needed for F object (requiresFrames=true).
|
|
570
|
+
// Hot-reload: uses needsAdHocLoop() (not shouldVideoBeActive) so a loop
|
|
571
|
+
// still needed by a frame-based object plugin isn't stopped accidentally.
|
|
577
572
|
if (this.adHocVideoLoop && !this.needsAdHocLoop()) {
|
|
578
573
|
this.adHocVideoLoop = false;
|
|
579
574
|
this.logger.debug('Stopping ad-hoc video loop — last frame-based consumer removed');
|
|
@@ -591,17 +586,13 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
591
586
|
async reportSensorWrite(sensorId, sensorType, properties) {
|
|
592
587
|
if (sensorType === SensorType.Object) {
|
|
593
588
|
const filtered = this.applyExternalDetectionFilters(sensorType, properties);
|
|
594
|
-
// Always feed the autotracker
|
|
595
|
-
//
|
|
596
|
-
//
|
|
597
|
-
// full-frame bbox when the plugin reports without coordinates), so use
|
|
598
|
-
// the presence channel — it holds the return-to-home timer without
|
|
599
|
-
// driving the motor.
|
|
589
|
+
// Always feed the autotracker (even during suppression, so it can decide
|
|
590
|
+
// when to stop). External writes are presence-only — the presence channel
|
|
591
|
+
// holds the return-to-home timer without driving the motor.
|
|
600
592
|
const externalDetections = filtered.detections ?? [];
|
|
601
593
|
this.ptzAutotracker.handlePresenceDetections(externalDetections);
|
|
602
|
-
// During
|
|
603
|
-
//
|
|
604
|
-
// change). Skip the rest.
|
|
594
|
+
// During suppression tracker/pipeline/events stay gated — no stale
|
|
595
|
+
// bboxes or false events from the camera pose change.
|
|
605
596
|
if (this.ptzAutotracker.suppressionActive)
|
|
606
597
|
return;
|
|
607
598
|
this.ingestDetectionResult(SensorType.Object, sensorId, filtered);
|
|
@@ -612,9 +603,8 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
612
603
|
return; // busy-skip: previous RPC still running
|
|
613
604
|
this.processingExternalSecondary = true;
|
|
614
605
|
try {
|
|
615
|
-
// Wrap
|
|
616
|
-
// tracking
|
|
617
|
-
// pipeline can treat them uniformly with frame-driven output.
|
|
606
|
+
// Wrap external boxes as TrackedDetection (shape adapter, no real
|
|
607
|
+
// tracking) so the secondary pipeline treats them like loop output.
|
|
618
608
|
const rawExternal = filtered.detections ?? [];
|
|
619
609
|
const objectDetections = this.pipeline.processExternal(rawExternal);
|
|
620
610
|
// Fast path: loop is running and has a frame → crop mode.
|
|
@@ -635,9 +625,8 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
635
625
|
}
|
|
636
626
|
return;
|
|
637
627
|
}
|
|
638
|
-
// Slow path: loop
|
|
639
|
-
//
|
|
640
|
-
// detection on the whole scaled frame.
|
|
628
|
+
// Slow path: no loop frame yet → snapshot fetch, secondaries run
|
|
629
|
+
// full-frame (no per-detection crops).
|
|
641
630
|
const fetched = await this.frameSource.fetchSnapshotFrame();
|
|
642
631
|
if (!fetched)
|
|
643
632
|
return;
|
|
@@ -648,7 +637,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
648
637
|
const snapshot = this.buildSnapshot();
|
|
649
638
|
if (sceneJpeg)
|
|
650
639
|
snapshot.eventThumbnail = sceneJpeg;
|
|
640
|
+
const wantsEventThumb = this.eventManager.needsThumbnail() || this.snapshotWillStartEvent(snapshot);
|
|
651
641
|
this.eventManager.processResults(snapshot);
|
|
642
|
+
if (wantsEventThumb) {
|
|
643
|
+
this.upgradeEventThumbnailAsync();
|
|
644
|
+
}
|
|
652
645
|
}
|
|
653
646
|
finally {
|
|
654
647
|
await fetched[Symbol.asyncDispose]();
|
|
@@ -660,8 +653,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
660
653
|
return;
|
|
661
654
|
}
|
|
662
655
|
if (sensorType === SensorType.Motion) {
|
|
663
|
-
//
|
|
664
|
-
// camera repositioning.
|
|
656
|
+
// Fully gated during suppression — frame-diff is fake while the camera repositions.
|
|
665
657
|
if (this.ptzAutotracker.suppressionActive)
|
|
666
658
|
return;
|
|
667
659
|
const filtered = this.applyExternalDetectionFilters(sensorType, properties);
|
|
@@ -670,9 +662,8 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
670
662
|
this.fetchEventThumbnailAsync();
|
|
671
663
|
return;
|
|
672
664
|
}
|
|
673
|
-
// External secondaries
|
|
674
|
-
//
|
|
675
|
-
// up the owning pluginId for buffer keying.
|
|
665
|
+
// External secondaries: direct ingest, no pixel work. Classifier (multi-
|
|
666
|
+
// provider) needs the owning pluginId for buffer keying.
|
|
676
667
|
let pluginId;
|
|
677
668
|
if (sensorType === SensorType.Classifier) {
|
|
678
669
|
const typePlugins = this.multiPlugins.get(SensorType.Classifier);
|
|
@@ -690,26 +681,19 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
690
681
|
this.eventManager.processResults(this.buildSnapshot());
|
|
691
682
|
}
|
|
692
683
|
reportSensorTrigger(sensorId, triggerType, action, _sustained, timeoutSeconds) {
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
// have their properties managed by dwell; contact/switch/light sensors don't.
|
|
684
|
+
// Namespaced dwell key so trigger dwells don't overwrite the sensor's own
|
|
685
|
+
// detected/blocked properties (contact/switch/light manage those themselves).
|
|
696
686
|
const dwellKey = `trigger:${sensorId}`;
|
|
697
|
-
// Track active sensor trigger types for snapshot
|
|
698
687
|
if (action === 'activate') {
|
|
699
688
|
this.activeSensorTriggerTypes.set(dwellKey, triggerType);
|
|
700
689
|
}
|
|
701
|
-
// Dwell for event lifecycle — consistent with motion/audio.
|
|
702
|
-
// Activate: starts/extends the event. Deactivate: refreshes for cooldown window.
|
|
703
690
|
this.dwell.refresh(dwellKey, timeoutSeconds);
|
|
704
|
-
// Cascade for object-detection gating — only on activate
|
|
705
691
|
if (action === 'activate' && this.cascadeEnabled) {
|
|
706
692
|
this.cascade.triggerMomentary(this.cascadeTimeoutSeconds);
|
|
707
693
|
}
|
|
708
|
-
// Flush immediately so EventManager sees the trigger (like reportSensorWrite)
|
|
709
694
|
this.eventManager.processResults(this.buildSnapshot());
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
// No-op if a loop frame already populated the thumb earlier.
|
|
695
|
+
// Trigger-only path (doorbell, contact) has no loop frame to encode from —
|
|
696
|
+
// fetch the event thumb async; no-op if one was already captured.
|
|
713
697
|
if (action === 'activate') {
|
|
714
698
|
this.fetchEventThumbnailAsync();
|
|
715
699
|
}
|
|
@@ -913,10 +897,15 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
913
897
|
try {
|
|
914
898
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
915
899
|
try {
|
|
900
|
+
const hqJpeg = await this.hqSnapshotSource?.snapshotJpeg(EVENT_THUMB_HQ_MAX_WIDTH, EVENT_THUMB_HQ_QUALITY);
|
|
901
|
+
if (hqJpeg) {
|
|
902
|
+
this.eventManager.publishEventThumbnail(hqJpeg);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
916
905
|
const handle = __addDisposableResource(env_1, await this.frameSource.fetchSnapshotFrame(), true);
|
|
917
906
|
if (!handle)
|
|
918
907
|
return;
|
|
919
|
-
const jpeg = await this.frameScaler.frameToJPEG(handle.frame,
|
|
908
|
+
const jpeg = await this.frameScaler.frameToJPEG(handle.frame, EVENT_THUMB_MAX_WIDTH);
|
|
920
909
|
if (!jpeg)
|
|
921
910
|
return;
|
|
922
911
|
this.eventManager.publishEventThumbnail(jpeg);
|
|
@@ -936,6 +925,81 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
936
925
|
}
|
|
937
926
|
})();
|
|
938
927
|
}
|
|
928
|
+
// Fallback: async snapshot replacement of a substream event thumbnail —
|
|
929
|
+
// only reached when the HQ source couldn't deliver a frame inline at event
|
|
930
|
+
// start (still connecting, buffer not warm yet).
|
|
931
|
+
upgradeEventThumbnailAsync() {
|
|
932
|
+
const source = this.hqSnapshotSource;
|
|
933
|
+
if (!source?.isRunning || !source.hasBuffer || this.hqUpgradeInflight)
|
|
934
|
+
return;
|
|
935
|
+
this.hqUpgradeInflight = true;
|
|
936
|
+
void (async () => {
|
|
937
|
+
try {
|
|
938
|
+
const jpeg = await source.snapshotJpeg(EVENT_THUMB_HQ_MAX_WIDTH, EVENT_THUMB_HQ_QUALITY);
|
|
939
|
+
if (jpeg && this.eventManager.hasActiveEvent()) {
|
|
940
|
+
this.eventManager.publishEventThumbnail(jpeg);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
this.logger.debug('Snapshot event thumbnail upgrade failed:', error);
|
|
945
|
+
}
|
|
946
|
+
finally {
|
|
947
|
+
this.hqUpgradeInflight = false;
|
|
948
|
+
}
|
|
949
|
+
})();
|
|
950
|
+
}
|
|
951
|
+
// Recent snapshot frame + its scaler (own hardware context); caller disposes
|
|
952
|
+
// the frame. With a reference size the aspect ratios must match — normalized
|
|
953
|
+
// boxes only transfer between streams showing the same sensor area.
|
|
954
|
+
async acquireHqFrame(reference) {
|
|
955
|
+
const source = this.hqSnapshotSource;
|
|
956
|
+
if (!source?.isRunning || !source.hasBuffer)
|
|
957
|
+
return null;
|
|
958
|
+
const scaler = source.scaler;
|
|
959
|
+
if (!scaler)
|
|
960
|
+
return null;
|
|
961
|
+
const frame = await source.getFrame(HQ_FRAME_MAX_AGE_MS);
|
|
962
|
+
if (!frame)
|
|
963
|
+
return null;
|
|
964
|
+
if (reference) {
|
|
965
|
+
const refAspect = reference.width / reference.height;
|
|
966
|
+
const hqAspect = frame.width / frame.height;
|
|
967
|
+
if (Math.abs(refAspect - hqAspect) / refAspect > 0.02) {
|
|
968
|
+
if (!this.hqAspectWarned) {
|
|
969
|
+
this.hqAspectWarned = true;
|
|
970
|
+
this.logger.warn(`[hq-thumb] aspect mismatch (detection ${reference.width}x${reference.height} vs snapshot ${frame.width}x${frame.height}) — detection crops stay on the substream`);
|
|
971
|
+
}
|
|
972
|
+
frame[Symbol.dispose]?.();
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return { frame, scaler };
|
|
977
|
+
}
|
|
978
|
+
resolveHqSourceUrl(sources) {
|
|
979
|
+
if (!sources?.length)
|
|
980
|
+
return undefined;
|
|
981
|
+
const order = ['high-resolution', 'mid-resolution', 'low-resolution'];
|
|
982
|
+
const byRole = new Map(sources.map((s) => [s.role, s]));
|
|
983
|
+
// Detection consumes the LOWEST available role (worker: lowRes ?? midRes
|
|
984
|
+
// ?? streamSource) — the HQ companion is only worth a session when a
|
|
985
|
+
// strictly higher-resolution role exists.
|
|
986
|
+
const detectionRole = [...order].reverse().find((role) => byRole.has(role));
|
|
987
|
+
const hqRole = order.find((role) => byRole.has(role));
|
|
988
|
+
if (!detectionRole || !hqRole || hqRole === detectionRole)
|
|
989
|
+
return undefined;
|
|
990
|
+
return byRole.get(hqRole).url;
|
|
991
|
+
}
|
|
992
|
+
syncHqSnapshotSource() {
|
|
993
|
+
if (!this.hqSnapshotSource)
|
|
994
|
+
return;
|
|
995
|
+
const wanted = this.config.frameWorkerSettings.hqSnapshots === true;
|
|
996
|
+
if (wanted && !this.hqSnapshotSource.isRunning) {
|
|
997
|
+
this.hqSnapshotSource.start();
|
|
998
|
+
}
|
|
999
|
+
else if (!wanted && this.hqSnapshotSource.isRunning) {
|
|
1000
|
+
void this.hqSnapshotSource.stop();
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
939
1003
|
calculateDBFS(data, format) {
|
|
940
1004
|
let sumSquares = 0;
|
|
941
1005
|
let sampleCount = 0;
|
|
@@ -1118,28 +1182,19 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1118
1182
|
let motionDetected = false;
|
|
1119
1183
|
let objectDetections = [];
|
|
1120
1184
|
const results = { timestamp: t0 };
|
|
1121
|
-
// Refresh ReID
|
|
1122
|
-
//
|
|
1123
|
-
// cascade lifecycle, no estimated timeout needed.
|
|
1185
|
+
// Refresh ReID every frame while cascade is active so dead tracks never
|
|
1186
|
+
// expire during the cascade window.
|
|
1124
1187
|
if (this.cascade.isActive) {
|
|
1125
1188
|
this.pipeline.refreshReid();
|
|
1126
1189
|
}
|
|
1127
|
-
// PTZ
|
|
1128
|
-
//
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
// per-frame PTZ pose delta (via `consumePoseDelta()`) so Kalman
|
|
1132
|
-
// predictions stay aligned with the world frame across pans — track ids
|
|
1133
|
-
// survive camera motion instead of being lost-and-re-acquired every
|
|
1134
|
-
// cycle. The autotracker itself gates motor decisions on its own
|
|
1135
|
-
// `isPtzMoving` state, so it doesn't issue commands during a pan.
|
|
1190
|
+
// PTZ suppression: motion is skipped entirely (frame-diff is garbage while
|
|
1191
|
+
// the camera repositions); object keeps running — Norfair gets the pose
|
|
1192
|
+
// delta so track ids survive pans, and the autotracker gates motor
|
|
1193
|
+
// commands on its own isPtzMoving state.
|
|
1136
1194
|
const ptzSuppressed = this.ptzAutotracker.suppressionActive;
|
|
1137
|
-
// 1. Motion
|
|
1138
|
-
//
|
|
1139
|
-
//
|
|
1140
|
-
// b) Re-triggering cascade.triggerMomentary on each detected frame extends the cascade
|
|
1141
|
-
// naturally, so a sustained motion event keeps cascade alive as long as motion happens.
|
|
1142
|
-
// The cascade is the gate for OBJECT detection, not for motion.
|
|
1195
|
+
// 1. Motion — every frame regardless of cascade state: keeps UI bboxes
|
|
1196
|
+
// fresh AND re-triggers the cascade so sustained motion keeps it alive.
|
|
1197
|
+
// The cascade gates OBJECT detection, not motion.
|
|
1143
1198
|
const motionPlugin = this.singlePlugins.get(SensorType.Motion);
|
|
1144
1199
|
if (motionPlugin?.requiresFrames && !ptzSuppressed) {
|
|
1145
1200
|
const motionFrame = await this.scaleForMotion(rawFrame);
|
|
@@ -1172,29 +1227,18 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1172
1227
|
motionDetected = true;
|
|
1173
1228
|
results.cascadeTriggered = true;
|
|
1174
1229
|
}
|
|
1175
|
-
//
|
|
1176
|
-
//
|
|
1177
|
-
// motion but the zone filter wiped all boxes, effective detected is false. The plugin's
|
|
1178
|
-
// own `detected` flag is ignored on purpose — zone filtering wins.
|
|
1230
|
+
// `detected` is re-derived from post-zone-filter detections — the plugin's
|
|
1231
|
+
// own flag is ignored on purpose, zone filtering wins.
|
|
1179
1232
|
if (results.motion && motionPlugin) {
|
|
1180
1233
|
this.ingestDetectionResult(SensorType.Motion, motionPlugin.sensorId, {
|
|
1181
1234
|
detected: motionDetected,
|
|
1182
1235
|
detections: results.motion.detections,
|
|
1183
1236
|
});
|
|
1184
1237
|
}
|
|
1185
|
-
// 2. Object
|
|
1186
|
-
//
|
|
1187
|
-
//
|
|
1188
|
-
// config, runs unconditionally as an always-on detector.
|
|
1189
|
-
//
|
|
1190
|
-
// A motion plugin (frame-based OR external) must be registered for object
|
|
1191
|
-
// detection to ever fire — without one, no wake source exists for object,
|
|
1192
|
-
// so the cascade would never become active.
|
|
1238
|
+
// 2. Object — gated by the cascade (wake sources: motion, audio, sensor
|
|
1239
|
+
// trigger), always-on when cascade is disabled, and kept running during
|
|
1240
|
+
// PTZ suppression so the tracker holds its lock across pans.
|
|
1193
1241
|
const objectPlugin = this.singlePlugins.get(SensorType.Object);
|
|
1194
|
-
// Object detection runs during suppression too — the tracker stays alive
|
|
1195
|
-
// across pans via Norfair's camera-motion compensation so autotracker
|
|
1196
|
-
// keeps a stable lock. Without suppression: needs an active cascade (or
|
|
1197
|
-
// cascade disabled globally).
|
|
1198
1242
|
const shouldDetectObjects = objectPlugin?.requiresFrames === true && (ptzSuppressed || !this.cascadeEnabled || this.cascade.isActive);
|
|
1199
1243
|
if (shouldDetectObjects) {
|
|
1200
1244
|
const objectFrame = await this.scaleForObject(rawFrame);
|
|
@@ -1205,23 +1249,14 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1205
1249
|
const result = await withTimeout(objectPlugin.proxy.detectObjects(objectFrame), DETECT_TIMEOUT_MS, 'Object');
|
|
1206
1250
|
if (!this.loopRunning)
|
|
1207
1251
|
return;
|
|
1208
|
-
// Full pipeline
|
|
1209
|
-
//
|
|
1210
|
-
//
|
|
1211
|
-
// extrapolated positions instead of a momentary hole.
|
|
1212
|
-
//
|
|
1213
|
-
// The PTZ pose delta is handed to Norfair so Kalman predictions
|
|
1214
|
-
// are transformed into a world-stable frame — track ids survive
|
|
1215
|
-
// pans instead of going through LOST → REACQUIRE every cycle.
|
|
1252
|
+
// Full pipeline even on empty frames so Norfair advances Kalman
|
|
1253
|
+
// state; the PTZ pose delta keeps predictions world-stable across
|
|
1254
|
+
// pans (track ids survive instead of LOST → REACQUIRE).
|
|
1216
1255
|
const poseDelta = this.ptzAutotracker.consumePoseDelta();
|
|
1217
1256
|
const pipelineResult = this.pipeline.process(result.detections, objectFrame, poseDelta);
|
|
1218
|
-
//
|
|
1219
|
-
//
|
|
1220
|
-
//
|
|
1221
|
-
// When a fast-moving target walks off-screen, Norfair keeps
|
|
1222
|
-
// extrapolating based on the last-known velocity; without this
|
|
1223
|
-
// guard the UI ends up drawing ghost bboxes floating outside the
|
|
1224
|
-
// stream and the autotracker has no reason to chase them anyway.
|
|
1257
|
+
// Extrapolated (Kalman-only) tracks smooth single-frame detector
|
|
1258
|
+
// misses in the UI, but are dropped once they drift off-frame —
|
|
1259
|
+
// otherwise ghost bboxes float outside the stream.
|
|
1225
1260
|
const visibleTracks = pipelineResult.tracked.filter((t) => {
|
|
1226
1261
|
if (!t.trackLost)
|
|
1227
1262
|
return true;
|
|
@@ -1233,16 +1268,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1233
1268
|
results.object = { ...result, detections: visibleTracks };
|
|
1234
1269
|
if (pipelineResult.crossings.length > 0)
|
|
1235
1270
|
results.lineCrossings = pipelineResult.crossings;
|
|
1236
|
-
// Feed the autotracker
|
|
1237
|
-
//
|
|
1238
|
-
//
|
|
1239
|
-
// the best estimate of where the target still is — hiding that
|
|
1240
|
-
// from the autotracker would cause a one-frame "target gone" blip
|
|
1241
|
-
// and flip it into LOST, only to REACQUIRE on the very next frame
|
|
1242
|
-
// when the detection returns. That churn is orthogonal to camera
|
|
1243
|
-
// motion and shouldn't drive state transitions. The autotracker
|
|
1244
|
-
// gates motor decisions on its own `isPtzMoving` flag, so during
|
|
1245
|
-
// suppression it observes without commanding.
|
|
1271
|
+
// Feed the autotracker ALL tracked detections incl. extrapolated —
|
|
1272
|
+
// a single missed detector frame must not flip it into LOST/REACQUIRE
|
|
1273
|
+
// churn. It gates motor decisions on its own isPtzMoving flag.
|
|
1246
1274
|
this.ptzAutotracker.handleObjectDetections(pipelineResult.tracked);
|
|
1247
1275
|
}
|
|
1248
1276
|
catch (error) {
|
|
@@ -1259,9 +1287,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1259
1287
|
// Buffer line crossings so the EventManager snapshot picks them up
|
|
1260
1288
|
this.currentDetectionState.lineCrossings = results.lineCrossings;
|
|
1261
1289
|
}
|
|
1262
|
-
//
|
|
1263
|
-
// detected is re-derived from post-pipeline detections, the plugin's own
|
|
1264
|
-
// detected flag is ignored after pipeline filtering.
|
|
1290
|
+
// Same rule as motion: detected is re-derived from post-pipeline detections.
|
|
1265
1291
|
if (results.object && objectPlugin) {
|
|
1266
1292
|
this.ingestDetectionResult(SensorType.Object, objectPlugin.sensorId, {
|
|
1267
1293
|
detected: objectDetections.length > 0,
|
|
@@ -1270,49 +1296,62 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1270
1296
|
}
|
|
1271
1297
|
if (!this.loopRunning)
|
|
1272
1298
|
return;
|
|
1273
|
-
// 3. Secondary detectors + per-detection thumbnails. Result-collection
|
|
1274
|
-
//
|
|
1275
|
-
//
|
|
1299
|
+
// 3. Secondary detectors + per-detection thumbnails. Result-collection
|
|
1300
|
+
// only — the shared ingest below keeps loop tick and external-RPC path
|
|
1301
|
+
// on the same helper.
|
|
1276
1302
|
if (objectDetections.length > 0) {
|
|
1277
1303
|
await this.runSecondariesAndThumbnails(rawFrame, objectDetections, results);
|
|
1278
1304
|
}
|
|
1279
1305
|
if (!this.loopRunning)
|
|
1280
1306
|
return;
|
|
1281
|
-
// 4.
|
|
1307
|
+
// 4. Unified ingest.
|
|
1282
1308
|
this.ingestResultsForAllSecondaries(results);
|
|
1283
|
-
// 5. Single flush
|
|
1284
|
-
//
|
|
1285
|
-
// data (faces, plates, classifiers, clips) is available when the segment opens.
|
|
1309
|
+
// 5. Single flush per frame, AFTER all detectors + thumbnails — so
|
|
1310
|
+
// segment-start messages already carry thumbnails and secondary data.
|
|
1286
1311
|
if (!this.loopRunning)
|
|
1287
1312
|
return;
|
|
1288
1313
|
const snapshot = this.buildSnapshot();
|
|
1289
1314
|
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
1290
1315
|
snapshot.thumbnails = results.thumbnails;
|
|
1291
1316
|
}
|
|
1292
|
-
//
|
|
1293
|
-
//
|
|
1294
|
-
//
|
|
1295
|
-
//
|
|
1296
|
-
//
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
// by this processResults call. Either way, attaching is harmless because
|
|
1301
|
-
// EventManager only consumes it if `needsEventThumbnail` is true.
|
|
1302
|
-
if (this.eventManager.needsThumbnail() || this.snapshotWillStartEvent(snapshot)) {
|
|
1317
|
+
// Event-level thumbnail — generated lazily when the EventManager still
|
|
1318
|
+
// needs one OR this frame's triggers will start an event (pre-encode so
|
|
1319
|
+
// the 'start' message carries it inline). Attaching is harmless either
|
|
1320
|
+
// way; EventManager only consumes it while needsEventThumbnail is true.
|
|
1321
|
+
// Full frame prefers the snapshot source (no aspect constraint needed).
|
|
1322
|
+
const wantsEventThumb = this.eventManager.needsThumbnail() || this.snapshotWillStartEvent(snapshot);
|
|
1323
|
+
let hqThumbAttached = false;
|
|
1324
|
+
if (wantsEventThumb) {
|
|
1303
1325
|
try {
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1326
|
+
const hq = await this.acquireHqFrame();
|
|
1327
|
+
try {
|
|
1328
|
+
if (hq) {
|
|
1329
|
+
const jpeg = await hq.scaler.frameToJPEG(hq.frame, EVENT_THUMB_HQ_MAX_WIDTH, EVENT_THUMB_HQ_QUALITY);
|
|
1330
|
+
if (jpeg) {
|
|
1331
|
+
snapshot.eventThumbnail = jpeg;
|
|
1332
|
+
hqThumbAttached = true;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (!snapshot.eventThumbnail) {
|
|
1336
|
+
const jpeg = await this.frameScaler.frameToJPEG(rawFrame, EVENT_THUMB_MAX_WIDTH);
|
|
1337
|
+
if (jpeg)
|
|
1338
|
+
snapshot.eventThumbnail = jpeg;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
finally {
|
|
1342
|
+
hq?.frame[Symbol.dispose]?.();
|
|
1343
|
+
}
|
|
1307
1344
|
}
|
|
1308
1345
|
catch (error) {
|
|
1309
1346
|
this.logger.error('Event thumbnail generation error:', error);
|
|
1310
1347
|
}
|
|
1311
1348
|
}
|
|
1312
|
-
// this.logger.debug(
|
|
1313
|
-
// `[flush-debug] objects=${snapshot.objects.length} faces=${snapshot.faces.length} plates=${snapshot.plates.length} cls=${snapshot.classifiers.length} clips=${snapshot.clips.length} cascade=${snapshot.hasCascadeTrigger} thumbs=${snapshot.thumbnails?.length ?? 0}`,
|
|
1314
|
-
// );
|
|
1315
1349
|
this.eventManager.processResults(snapshot);
|
|
1350
|
+
// Inline HQ succeeded → nothing to upgrade; otherwise the async path
|
|
1351
|
+
// covers the window where the HQ source wasn't ready at event start.
|
|
1352
|
+
if (wantsEventThumb && !hqThumbAttached) {
|
|
1353
|
+
this.upgradeEventThumbnailAsync();
|
|
1354
|
+
}
|
|
1316
1355
|
}
|
|
1317
1356
|
snapshotWillStartEvent(snapshot) {
|
|
1318
1357
|
if (this.eventManager.hasActiveEvent())
|
|
@@ -1375,7 +1414,6 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1375
1414
|
}
|
|
1376
1415
|
async prepareSecondaryRegions(rawFrame, objectDetections) {
|
|
1377
1416
|
const result = new Map();
|
|
1378
|
-
// Collect all active secondary plugins and their requirements
|
|
1379
1417
|
const consumers = [];
|
|
1380
1418
|
const facePlugin = this.singlePlugins.get(SensorType.Face);
|
|
1381
1419
|
if (facePlugin && this.hasSecondaryModelSpec(facePlugin.modelSpec) && this.isVideoInputSpec(facePlugin.modelSpec.input)) {
|
|
@@ -1399,7 +1437,6 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1399
1437
|
}
|
|
1400
1438
|
if (consumers.length === 0)
|
|
1401
1439
|
return result;
|
|
1402
|
-
// For each detection, determine which consumers need it and what sizes
|
|
1403
1440
|
for (const detection of objectDetections) {
|
|
1404
1441
|
const detLabel = detection.label.toLowerCase();
|
|
1405
1442
|
const targets = [];
|
|
@@ -1410,8 +1447,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1410
1447
|
}
|
|
1411
1448
|
if (targets.length === 0)
|
|
1412
1449
|
continue;
|
|
1413
|
-
//
|
|
1414
|
-
// — keep first key, share the result
|
|
1450
|
+
// Consumers with identical dimensions share one scaled region
|
|
1415
1451
|
const uniqueTargets = [];
|
|
1416
1452
|
const dupeMap = new Map(); // sizeKey → [consumerKey, ...]
|
|
1417
1453
|
for (const t of targets) {
|
|
@@ -1426,7 +1462,6 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1426
1462
|
}
|
|
1427
1463
|
}
|
|
1428
1464
|
const regions = await this.frameScaler.cropAndScaleMulti(rawFrame, detection, uniqueTargets);
|
|
1429
|
-
// Distribute results to all consumers (including those sharing dimensions)
|
|
1430
1465
|
for (const [_sizeKey, consumerKeys] of dupeMap) {
|
|
1431
1466
|
const primaryKey = consumerKeys[0];
|
|
1432
1467
|
const region = regions.get(primaryKey);
|
|
@@ -1534,7 +1569,15 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1534
1569
|
this.currentDetectionState.faceEmbeddingModel = results.faceEmbeddingModel;
|
|
1535
1570
|
}
|
|
1536
1571
|
try {
|
|
1537
|
-
|
|
1572
|
+
// Detectors consumed the substream frame above; images destined for the
|
|
1573
|
+
// event (chips, scene) prefer the snapshot frame when available.
|
|
1574
|
+
const hq = await this.acquireHqFrame({ width: rawFrame.width, height: rawFrame.height });
|
|
1575
|
+
try {
|
|
1576
|
+
results.thumbnails = await this.generateThumbnails(hq?.frame ?? rawFrame, hq?.scaler ?? this.frameScaler, hq !== null, objectDetections, results);
|
|
1577
|
+
}
|
|
1578
|
+
finally {
|
|
1579
|
+
hq?.frame[Symbol.dispose]?.();
|
|
1580
|
+
}
|
|
1538
1581
|
}
|
|
1539
1582
|
catch (error) {
|
|
1540
1583
|
this.logger.error('Thumbnail generation error:', error);
|
|
@@ -1556,7 +1599,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1556
1599
|
}
|
|
1557
1600
|
}
|
|
1558
1601
|
try {
|
|
1559
|
-
return await this.frameScaler.frameToJPEG(rawFrame,
|
|
1602
|
+
return await this.frameScaler.frameToJPEG(rawFrame, EVENT_THUMB_MAX_WIDTH);
|
|
1560
1603
|
}
|
|
1561
1604
|
catch (error) {
|
|
1562
1605
|
this.logger.debug('Full-frame scene thumbnail failed:', error);
|
|
@@ -1617,65 +1660,12 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1617
1660
|
const facePlugin = this.singlePlugins.get(SensorType.Face);
|
|
1618
1661
|
if (!facePlugin || croppedRegions.length === 0)
|
|
1619
1662
|
return undefined;
|
|
1620
|
-
// for (let i = 0; i < croppedRegions.length; i++) {
|
|
1621
|
-
// const r = croppedRegions[i];
|
|
1622
|
-
// // prettier-ignore
|
|
1623
|
-
// this.logger.debug(
|
|
1624
|
-
// `[face-debug] crop[${i}] label=${r.detection.label} ` +
|
|
1625
|
-
// `cropSize=${r.cropSize.width}x${r.cropSize.height} ` +
|
|
1626
|
-
// `scaledTo=${r.frame.width}x${r.frame.height} ` +
|
|
1627
|
-
// `offset=(${r.offset.x},${r.offset.y}) ` +
|
|
1628
|
-
// `original=${r.originalSize.width}x${r.originalSize.height}`,
|
|
1629
|
-
// );
|
|
1630
|
-
// // Debug: save person crop as raw RGB for visual inspection
|
|
1631
|
-
// if (r.frame.data && r.detection.label === 'person') {
|
|
1632
|
-
// try {
|
|
1633
|
-
// const ts = Date.now();
|
|
1634
|
-
// writeFileSync(`/tmp/crop_person_${ts}_${r.frame.width}x${r.frame.height}.rgb`, Buffer.from(r.frame.data as Buffer));
|
|
1635
|
-
// this.logger.debug(`[face-debug] saved person crop: /tmp/crop_person_${ts}_${r.frame.width}x${r.frame.height}.rgb`);
|
|
1636
|
-
// } catch {
|
|
1637
|
-
// /* ignore */
|
|
1638
|
-
// }
|
|
1639
|
-
// }
|
|
1640
|
-
// }
|
|
1641
1663
|
const batchResults = await withTimeout(facePlugin.proxy.detectFaces(this.prepareSecondaryFrames(croppedRegions)), DETECT_TIMEOUT_MS, 'Face');
|
|
1642
1664
|
const allFaces = [];
|
|
1643
1665
|
for (let i = 0; i < batchResults.length; i++) {
|
|
1644
1666
|
const parentTrackId = 'trackId' in croppedRegions[i].detection ? croppedRegions[i].detection.trackId : undefined;
|
|
1645
1667
|
for (const face of batchResults[i].detections) {
|
|
1646
1668
|
const transformed = this.transformBoxToOriginal(face.box, croppedRegions[i]);
|
|
1647
|
-
// const rawBox = face.box;
|
|
1648
|
-
// // prettier-ignore
|
|
1649
|
-
// this.logger.debug(
|
|
1650
|
-
// `[face-debug] face in crop[${i}] [parentTrackId=${parentTrackId}]: ` +
|
|
1651
|
-
// `raw=(${rawBox.x.toFixed(4)},${rawBox.y.toFixed(4)},${rawBox.width.toFixed(4)},${rawBox.height.toFixed(4)}) ` +
|
|
1652
|
-
// `transformed=(${transformed.x.toFixed(4)},${transformed.y.toFixed(4)},${transformed.width.toFixed(4)},${transformed.height.toFixed(4)}) ` +
|
|
1653
|
-
// `conf=${face.confidence.toFixed(3)}`,
|
|
1654
|
-
// );
|
|
1655
|
-
// // Debug: save face crop region from the person crop for visual inspection
|
|
1656
|
-
// if (croppedRegions[i].frame.data && face.confidence > 0.6) {
|
|
1657
|
-
// try {
|
|
1658
|
-
// const cropData = croppedRegions[i].frame.data as Buffer;
|
|
1659
|
-
// const fw = croppedRegions[i].frame.width;
|
|
1660
|
-
// const fh = croppedRegions[i].frame.height;
|
|
1661
|
-
// const bpp = cropData.length / (fw * fh); // bytes per pixel (3 for rgb)
|
|
1662
|
-
// const fx = Math.floor(rawBox.x * fw);
|
|
1663
|
-
// const fy = Math.floor(rawBox.y * fh);
|
|
1664
|
-
// const fbw = Math.floor(rawBox.width * fw);
|
|
1665
|
-
// const fbh = Math.floor(rawBox.height * fh);
|
|
1666
|
-
// const faceBuf = Buffer.alloc(fbw * fbh * bpp);
|
|
1667
|
-
// for (let row = 0; row < fbh; row++) {
|
|
1668
|
-
// const srcOff = ((fy + row) * fw + fx) * bpp;
|
|
1669
|
-
// const dstOff = row * fbw * bpp;
|
|
1670
|
-
// cropData.copy(faceBuf, dstOff, srcOff, srcOff + fbw * bpp);
|
|
1671
|
-
// }
|
|
1672
|
-
// const ts = Date.now();
|
|
1673
|
-
// writeFileSync(`/tmp/crop_face_${ts}_${fbw}x${fbh}.rgb`, faceBuf);
|
|
1674
|
-
// this.logger.debug(`[face-debug] saved face crop: /tmp/crop_face_${ts}_${fbw}x${fbh}.rgb`);
|
|
1675
|
-
// } catch {
|
|
1676
|
-
// /* ignore */
|
|
1677
|
-
// }
|
|
1678
|
-
// }
|
|
1679
1669
|
allFaces.push({ ...face, box: transformed, parentTrackId });
|
|
1680
1670
|
}
|
|
1681
1671
|
}
|
|
@@ -1753,10 +1743,19 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1753
1743
|
const embeddingModel = clipPlugin.modelSpec?.embeddingModel ?? '';
|
|
1754
1744
|
return { embeddings: allEmbeddings, embeddingModel };
|
|
1755
1745
|
}
|
|
1756
|
-
async generateThumbnails(
|
|
1746
|
+
async generateThumbnails(sourceFrame, scaler, usingHq, objectDetections, results) {
|
|
1757
1747
|
const thumbnails = [];
|
|
1748
|
+
const attributePadding = usingHq ? 0.4 : 0.75;
|
|
1749
|
+
const detectionMaxWidth = usingHq ? DETECTION_THUMB_HQ_MAX_WIDTH : DETECTION_THUMB_MAX_WIDTH;
|
|
1750
|
+
const detectionMinCrop = usingHq ? DETECTION_THUMB_HQ_MIN_CROP : DETECTION_THUMB_MIN_CROP;
|
|
1751
|
+
const detectionQuality = usingHq ? DETECTION_THUMB_HQ_QUALITY : undefined;
|
|
1758
1752
|
// Object thumbnails — objectDetections are TrackedDetection[] when tracker ran
|
|
1759
|
-
const objectCrops = await
|
|
1753
|
+
const objectCrops = await scaler.cropToJPEG(sourceFrame, objectDetections, {
|
|
1754
|
+
maxWidth: detectionMaxWidth,
|
|
1755
|
+
padding: 0.3,
|
|
1756
|
+
minCrop: detectionMinCrop,
|
|
1757
|
+
quality: detectionQuality,
|
|
1758
|
+
});
|
|
1760
1759
|
for (const crop of objectCrops) {
|
|
1761
1760
|
const detection = objectDetections[crop.index];
|
|
1762
1761
|
if (detection) {
|
|
@@ -1772,24 +1771,16 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1772
1771
|
}
|
|
1773
1772
|
}
|
|
1774
1773
|
if (results.face?.detections) {
|
|
1775
|
-
const faceCrops = await
|
|
1774
|
+
const faceCrops = await scaler.cropToJPEG(sourceFrame, results.face.detections, {
|
|
1775
|
+
maxWidth: ATTRIBUTE_THUMB_MAX_WIDTH,
|
|
1776
|
+
padding: attributePadding,
|
|
1777
|
+
minCrop: ATTRIBUTE_THUMB_MIN_CROP,
|
|
1778
|
+
});
|
|
1776
1779
|
for (const crop of faceCrops) {
|
|
1777
1780
|
const detection = results.face.detections[crop.index];
|
|
1778
1781
|
if (detection) {
|
|
1779
|
-
//
|
|
1780
|
-
//
|
|
1781
|
-
// const ts = Date.now();
|
|
1782
|
-
// writeFileSync(`/tmp/face_thumb_${ts}_${crop.index}.jpg`, crop.jpeg);
|
|
1783
|
-
// this.logger.debug(
|
|
1784
|
-
// `[face-debug] saved face thumbnail from rawFrame: /tmp/face_thumb_${ts}_${crop.index}.jpg ` +
|
|
1785
|
-
// `box=(${detection.box.x.toFixed(4)},${detection.box.y.toFixed(4)},${detection.box.width.toFixed(4)},${detection.box.height.toFixed(4)}) ` +
|
|
1786
|
-
// `rawFrame=${rawFrame.width}x${rawFrame.height}`,
|
|
1787
|
-
// );
|
|
1788
|
-
// } catch {
|
|
1789
|
-
// /* ignore */
|
|
1790
|
-
// }
|
|
1791
|
-
// Set thumbnail on the detection itself so enrichSegment can inline it
|
|
1792
|
-
// for unknown faces (needed for face store persistence).
|
|
1782
|
+
// Inline on the detection so enrichSegment can persist unknown
|
|
1783
|
+
// faces to the face store.
|
|
1793
1784
|
detection.thumbnail ??= crop.jpeg;
|
|
1794
1785
|
thumbnails.push({
|
|
1795
1786
|
label: `face:${detection.identity ?? 'unknown'}`,
|
|
@@ -1802,7 +1793,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1802
1793
|
}
|
|
1803
1794
|
}
|
|
1804
1795
|
if (results.licensePlate?.detections) {
|
|
1805
|
-
const plateCrops = await
|
|
1796
|
+
const plateCrops = await scaler.cropToJPEG(sourceFrame, results.licensePlate.detections, {
|
|
1797
|
+
maxWidth: ATTRIBUTE_THUMB_MAX_WIDTH,
|
|
1798
|
+
padding: attributePadding,
|
|
1799
|
+
minCrop: ATTRIBUTE_THUMB_MIN_CROP,
|
|
1800
|
+
});
|
|
1806
1801
|
for (const crop of plateCrops) {
|
|
1807
1802
|
const detection = results.licensePlate.detections[crop.index];
|
|
1808
1803
|
if (detection) {
|
|
@@ -1818,7 +1813,12 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1818
1813
|
}
|
|
1819
1814
|
if (results.classifiers) {
|
|
1820
1815
|
for (const classifierResult of Object.values(results.classifiers)) {
|
|
1821
|
-
const clsCrops = await
|
|
1816
|
+
const clsCrops = await scaler.cropToJPEG(sourceFrame, classifierResult.detections, {
|
|
1817
|
+
maxWidth: detectionMaxWidth,
|
|
1818
|
+
padding: 0.3,
|
|
1819
|
+
minCrop: detectionMinCrop,
|
|
1820
|
+
quality: detectionQuality,
|
|
1821
|
+
});
|
|
1822
1822
|
for (const crop of clsCrops) {
|
|
1823
1823
|
const detection = classifierResult.detections[crop.index];
|
|
1824
1824
|
if (detection) {
|