@camera.ui/server 2.1.3 → 2.1.5
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-xXRqIPnI.js → 404-C0XhilKM.js} +1 -1
- package/dist/interface/assets/{About-DmA6YPm3.js → About-DZsTev1Z.js} +1 -1
- package/dist/interface/assets/Adminpanel-BFKj4r-J.js +1 -0
- package/dist/interface/assets/{Automation-BDETmBsr.js → Automation-DftZGCt6.js} +3 -3
- package/dist/interface/assets/{AutomationImport-DiBbViZF.js → AutomationImport-CWz3wK45.js} +2 -2
- package/dist/interface/assets/{AutomationStore-i2EAmdtd.js → AutomationStore-BDgJ0e1s.js} +2 -2
- package/dist/interface/assets/{AutomationStoreDetail-Cs08SdQP.js → AutomationStoreDetail-BxjQz384.js} +2 -2
- package/dist/interface/assets/{Automations-foLiIvMO.js → Automations-CYK5CbkM.js} +2 -2
- package/dist/interface/assets/Camera-C5gF7yVN.css +1 -0
- package/dist/interface/assets/Camera-C7TRw5N0.js +2 -0
- package/dist/interface/assets/{CameraConsole-ByzCncrJ.js → CameraConsole-CNULG_YE.js} +2 -2
- package/dist/interface/assets/Cameras-CXWCJWzR.js +652 -0
- package/dist/interface/assets/Camview-C09leXh7.js +1 -0
- package/dist/interface/assets/Camview-UCj_OW0s.css +1 -0
- package/dist/interface/assets/Config-DNOXV5ek.js +2 -0
- package/dist/interface/assets/{ConfigActionCameraControl-BXO7ipsz.js → ConfigActionCameraControl-BPfBxvxr.js} +1 -1
- package/dist/interface/assets/{ConfigActionDelay-DKg-B1Qg.js → ConfigActionDelay-CZeAJTj7.js} +1 -1
- package/dist/interface/assets/{ConfigActionHttp-DxXmT1sT.js → ConfigActionHttp-ipkZdIWF.js} +1 -1
- package/dist/interface/assets/{ConfigActionImageInput-Bg0PRpEM.js → ConfigActionImageInput-CvETCNjG.js} +1 -1
- package/dist/interface/assets/{ConfigActionMqtt-D8eQzkpn.js → ConfigActionMqtt-B8ZNU0UW.js} +1 -1
- package/dist/interface/assets/{ConfigActionNotification-CzwYhRsN.js → ConfigActionNotification-B-nC2J6v.js} +1 -1
- package/dist/interface/assets/{ConfigActionNotificationControl-B92w8_zI.js → ConfigActionNotificationControl-DjxS7_ID.js} +1 -1
- package/dist/interface/assets/{ConfigActionOutput-DnBrTam_.js → ConfigActionOutput-QG12dzwm.js} +1 -1
- package/dist/interface/assets/{ConfigActionPlugin-ZZLa_9lg.js → ConfigActionPlugin-Q2Tcpdgm.js} +1 -1
- package/dist/interface/assets/{ConfigActionSensor-C4vlKTMo.js → ConfigActionSensor-BTFyt4ER.js} +1 -1
- package/dist/interface/assets/{ConfigActionSnapshot-gv4xP_ke.js → ConfigActionSnapshot-DfnAex92.js} +1 -1
- package/dist/interface/assets/{ConfigActionVariable-BK20Jiwm.js → ConfigActionVariable-COrJ-lFp.js} +1 -1
- package/dist/interface/assets/{ConfigConditionIfElse-CMNQhZVq.js → ConfigConditionIfElse-Dpc6Cwpf.js} +1 -1
- package/dist/interface/assets/ConfigConditionSensorState-BQXY2c2t.js +1 -0
- package/dist/interface/assets/{ConfigConditionSwitch-BD_yekSb.js → ConfigConditionSwitch-BuI8_3Bp.js} +1 -1
- package/dist/interface/assets/{ConfigConditionTime-iYGtS79-.js → ConfigConditionTime-0Du7w_QE.js} +1 -1
- package/dist/interface/assets/ConfigSensorValueInput-vY0DCyRn.js +1 -0
- package/dist/interface/assets/{ConfigTriggerDetection-fT3Ao7ai.js → ConfigTriggerDetection-ChUtfRtm.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerGeofence-CTovEe2t.js → ConfigTriggerGeofence-fHTNHtGp.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerManual-_70uZCm8.js → ConfigTriggerManual-IAo9sbiS.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerMqtt-B3i_U0C8.js → ConfigTriggerMqtt-ZeRfexN1.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerSchedule-opMt64uR.js → ConfigTriggerSchedule-Ct_AN-xu.js} +1 -1
- package/dist/interface/assets/ConfigTriggerSensor-BJkKOmTg.js +1 -0
- package/dist/interface/assets/{ConfigTriggerSystem-l6Z8YkIP.js → ConfigTriggerSystem-DedCuH2F.js} +1 -1
- package/dist/interface/assets/{ConfigTriggerWebhook-BUoC_tvP.js → ConfigTriggerWebhook-DPz9NPdp.js} +1 -1
- package/dist/interface/assets/Console-D5hGnmUg.js +1 -0
- package/dist/interface/assets/CuiAutomationRunsDialog-BP-FYYXr.js +1 -0
- package/dist/interface/assets/{CuiBottomSheet-CD7ayDtu.js → CuiBottomSheet-B25DDhsj.js} +1 -1
- package/dist/interface/assets/CuiBottomSheet-de-zDc6R.js +1 -0
- package/dist/interface/assets/{CuiCameraPipCard-D3o29_wc.css → CuiCameraPipCard-BQKk4fxY.css} +1 -1
- package/dist/interface/assets/CuiCameraPipCard-CeY7Gy9_.js +1 -0
- package/dist/interface/assets/CuiCameraRecordings-AA-sBtb4.css +1 -0
- package/dist/interface/assets/CuiCameraRecordings-CqwUqjAU.js +1 -0
- package/dist/interface/assets/{CuiCameraShares-BNhXZZx4.js → CuiCameraShares-B5pdZrh3.js} +1 -1
- package/dist/interface/assets/{CuiCameraTable-IqEVI4_6.js → CuiCameraTable-BhUWRnwr.js} +2 -2
- package/dist/interface/assets/{CuiConsole-DQdrSaNv.js → CuiConsole-CAHUWadU.js} +2 -2
- package/dist/interface/assets/CuiDataTable-DcyMAPBx.js +1 -0
- package/dist/interface/assets/CuiLightControl-8aZiuOCv.js +1 -0
- package/dist/interface/assets/CuiList-psx5r57o.js +1 -0
- package/dist/interface/assets/CuiMarkdownContent-73RAjkHa.js +1 -0
- package/dist/interface/assets/{CuiMarkdownContent-wt9kwnE3.js → CuiMarkdownContent-BfJOBp3S.js} +1 -1
- package/dist/interface/assets/CuiPluginCard-BZPiou4m.js +2 -0
- package/dist/interface/assets/{CuiPluginCategoryChip-BBg1R80V.js → CuiPluginCategoryChip-1h2moEVm.js} +1 -1
- package/dist/interface/assets/{CuiPluginOAuthButton-CZkDLOyX.js → CuiPluginOAuthButton-CI5gdHHd.js} +2 -2
- package/dist/interface/assets/{CuiPluginTrustBadge-CLGD6iSl.js → CuiPluginTrustBadge-CqkEF6GV.js} +1 -1
- package/dist/interface/assets/{CuiQRCode-BJIth5tK.js → CuiQRCode-CQGx_k0S.js} +1 -1
- package/dist/interface/assets/CuiRecordingsGrid-CLNQoUbw.js +1 -0
- package/dist/interface/assets/CuiRecordingsGrid-Sv3uyj9a.css +1 -0
- package/dist/interface/assets/CuiSchema-U31deYpX.js +1 -0
- package/dist/interface/assets/{CuiTopNavbarItem-DuW8KecA.js → CuiTopNavbarItem-BekRyj7O.js} +1 -1
- package/dist/interface/assets/Faces-BEqO99qz.js +1 -0
- package/dist/interface/assets/FirstSteps-BTDj1olc.js +1 -0
- package/dist/interface/assets/Instances-DTqwb5FF.js +1 -0
- package/dist/interface/assets/{Logs-pqS6zmdG.js → Logs-D8LlWlQ2.js} +2 -2
- package/dist/interface/assets/Menu-Cb5cBA-q.js +1 -0
- package/dist/interface/assets/{OAuthDeviceFlow-FtU9FE8s.js → OAuthDeviceFlow-CExQxxt6.js} +1 -1
- package/dist/interface/assets/Plugin-BDqNeTCT.css +1 -0
- package/dist/interface/assets/Plugin-DtkmtFL6.js +1 -0
- package/dist/interface/assets/{PluginConsole-DpsjUxOt.js → PluginConsole-CktnZFCJ.js} +2 -2
- package/dist/interface/assets/PluginDetail-t5V-ATem.js +2 -0
- package/dist/interface/assets/{PluginSchema-CPbdpwfh.js → PluginSchema-DgXUaGQ2.js} +1 -1
- package/dist/interface/assets/PluginSearch-D8qXgkEP.js +2 -0
- package/dist/interface/assets/PluginSearch-DDa1Jt8s.js +1 -0
- package/dist/interface/assets/{PluginSearch-CLjLT9JT.css → PluginSearch-Dlzc-aJ3.css} +1 -1
- package/dist/interface/assets/{PluginUpdateProgress-B_vd--LX.js → PluginUpdateProgress-B9GSD9wl.js} +1 -1
- package/dist/interface/assets/Plugins-CMjhJaUC.js +1 -0
- package/dist/interface/assets/Recordings-BP3-OD7P.js +1 -0
- package/dist/interface/assets/SensorEdit-B-64Y4xT.js +1 -0
- package/dist/interface/assets/{SensorHistory-D11DdMol.js → SensorHistory-D8xQKiuE.js} +1 -1
- package/dist/interface/assets/Sensors-D5VXCQ6N.js +2 -0
- package/dist/interface/assets/Settings-B3INZjRu.js +1 -0
- package/dist/interface/assets/{Settings-DmZjKfDi.css → Settings-D8L1oiQx.css} +1 -1
- package/dist/interface/assets/SettingsAccount-BEMYyovd.js +1 -0
- package/dist/interface/assets/SettingsAccount-CcPLFq2z.js +1 -0
- package/dist/interface/assets/SettingsAppearance-BSVGF67s.js +1 -0
- package/dist/interface/assets/SettingsAppearance-CCf7QwkP.js +1 -0
- package/dist/interface/assets/SettingsBackup-DSARU1qG.js +1 -0
- package/dist/interface/assets/SettingsList-imzWFgaD.js +1 -0
- package/dist/interface/assets/SettingsMqtt-DkEqSLI4.js +1 -0
- package/dist/interface/assets/{SettingsNotifications-EerQSLcj.js → SettingsNotifications-CE5byZci.js} +2 -2
- package/dist/interface/assets/{SettingsPermissions-j1GCDQER.js → SettingsPermissions-DU1XvFFc.js} +1 -1
- package/dist/interface/assets/SettingsRecordings-CSCgcxrS.js +1 -0
- package/dist/interface/assets/SettingsRemote-DXw6kYER.js +1 -0
- package/dist/interface/assets/SettingsSystem-BctqXTc5.js +2 -0
- package/dist/interface/assets/{SettingsUsers-DSu7YkD6.js → SettingsUsers-DoPPk6w5.js} +1 -1
- package/dist/interface/assets/Terminal-CPOkeiZ5.js +4 -0
- package/dist/interface/assets/{VariableInput-D8fxy8Tr.js → VariableInput-DfDRe6bt.js} +1 -1
- package/dist/interface/assets/{VariableSuggestions-BmrGajtR.js → VariableSuggestions-CjlK2Uqx.js} +1 -1
- package/dist/interface/assets/VersionsHandler-BEWveXqP.js +7 -0
- package/dist/interface/assets/{VirtualSensorCreate-CY0SgW0D.js → VirtualSensorCreate-CUTyLWv-.js} +1 -1
- package/dist/interface/assets/Workers-B_G_FZls.js +2 -0
- package/dist/interface/assets/{account-CY80SLLf.js → account-DuSFtIHf.js} +1 -1
- package/dist/interface/assets/{alert-circle-pDGfnZ4K.js → alert-circle-C2AU59aR.js} +1 -1
- package/dist/interface/assets/{alert-outline-CeoXcnQc.js → alert-outline-GrD7-zHq.js} +1 -1
- package/dist/interface/assets/{api-QDhweZyS.js → api-BrAt3E8w.js} +1 -1
- package/dist/interface/assets/api-DIm7LItL.css +2 -0
- package/dist/interface/assets/api-Da98l_Y7.js +2 -0
- package/dist/interface/assets/{arrow-left-D9jDhvK5.js → arrow-left-C1N6U6kZ.js} +1 -1
- package/dist/interface/assets/{arrow-right-outline-DF7tkKYk.js → arrow-right-outline-C24f5mwe.js} +1 -1
- package/dist/interface/assets/{auth-DtXDXDTz.js → auth-BdsJrXmQ.js} +1 -1
- package/dist/interface/assets/{automations-BtpjXAGj.js → automations-C1EJke-z.js} +1 -1
- package/dist/interface/assets/{back-filled-DUUGErMw.js → back-filled-DcBDBvvP.js} +1 -1
- package/dist/interface/assets/{bell-ring-DV73JIre.js → bell-ring-DSX81yd4.js} +1 -1
- package/dist/interface/assets/cameras.schema-BWH_Q16D.js +1 -0
- package/dist/interface/assets/{cctv-jy5ROx0o.js → cctv-BA5DvNlE.js} +1 -1
- package/dist/interface/assets/{check-circle-FwoylV5-.js → check-circle-Do_mBN_A.js} +1 -1
- package/dist/interface/assets/{chevron-down-umZ7i0mG.js → chevron-down-D111daTn.js} +1 -1
- package/dist/interface/assets/{clock-outline-CbvOAMhO.js → clock-outline-DF1wLNzs.js} +1 -1
- package/dist/interface/assets/{close-CtaDF28n.js → close-BNAWPz0y.js} +1 -1
- package/dist/interface/assets/{close-circle-FZ8IxGd4.js → close-circle-zhDWvz98.js} +1 -1
- package/dist/interface/assets/{cloud-check-CFSukfAF.js → cloud-check-pUK1Fpt9.js} +1 -1
- package/dist/interface/assets/cloudHandoff-1DPEACLE.js +1 -0
- package/dist/interface/assets/{config-BZ9jkdkW.js → config-Bq4jYnKb.js} +1 -1
- package/dist/interface/assets/{copy-CFA_pDRv.js → copy-BPT84cDt.js} +1 -1
- package/dist/interface/assets/{cube-scan-DEuMi23T.js → cube-scan-DOe6W9mW.js} +1 -1
- package/dist/interface/assets/{delete-921w_nKy.js → delete-BF6keuqy.js} +1 -1
- package/dist/interface/assets/{deployed-code-update-CYS6eKfa.js → deployed-code-update-xHE-blDN.js} +1 -1
- package/dist/interface/assets/{dist-BwEXwXSw.js → dist-Yc5s0_JB.js} +1 -1
- package/dist/interface/assets/{download-C-LzNRmM.js → download-BCPfm6aN.js} +1 -1
- package/dist/interface/assets/esm-B3yVlCMw.js +1 -0
- package/dist/interface/assets/esm-BKQ7CQK7.js +1 -0
- package/dist/interface/assets/esm-BiSE05P9.js +1 -0
- package/dist/interface/assets/esm-BoPthGAl.js +1 -0
- package/dist/interface/assets/esm-C2ALrdyx.js +1 -0
- package/dist/interface/assets/esm-CKEGzys4.js +1 -0
- package/dist/interface/assets/esm-CctiCHtK.js +1 -0
- package/dist/interface/assets/esm-Cqq81lnk.js +1 -0
- package/dist/interface/assets/esm-CyrLQTu0.js +1 -0
- package/dist/interface/assets/esm-D6O-eZYo.js +1 -0
- package/dist/interface/assets/esm-DG1f0qwo.js +1 -0
- package/dist/interface/assets/esm-DGegIZMF.js +1 -0
- package/dist/interface/assets/esm-JGwpHVqo.js +1 -0
- package/dist/interface/assets/esm-TRigYTsA.js +1 -0
- package/dist/interface/assets/{eye-off-DESV3QdF.js → eye-off-CaRq7Ks2.js} +1 -1
- package/dist/interface/assets/{flowSchema-lxx7zedO.js → flowSchema-okQoWkh6.js} +1 -1
- package/dist/interface/assets/{history-60iCpGvQ.js → history-SuH8IHAj.js} +1 -1
- package/dist/interface/assets/{home-w5iY2RUg.js → home-CUykMSBW.js} +1 -1
- package/dist/interface/assets/{information-outline-CvBsGtDu.js → information-outline-BQDq8w2t.js} +1 -1
- package/dist/interface/assets/{lightbulb-outline-DLrnCPG5.js → lightbulb-outline-ClK17CO6.js} +1 -1
- package/dist/interface/assets/main-6B8o-buJ.css +1 -0
- package/dist/interface/assets/main-W_rUAd4i.js +3 -0
- package/dist/interface/assets/{microphone-DUVI-s2_.js → microphone-C0weJY9W.js} +1 -1
- package/dist/interface/assets/minus-RI3VlDe8.js +1 -0
- package/dist/interface/assets/{mqtt-DLq_vTiX.js → mqtt-DtPGnb1K.js} +1 -1
- package/dist/interface/assets/native-B01iGjCh.js +1 -0
- package/dist/interface/assets/native-D381pxlG.js +1 -0
- package/dist/interface/assets/{nodeDefinitions-BecZ8I2Q.js → nodeDefinitions-DQQlFmPN.js} +1 -1
- package/dist/interface/assets/{notifications-f-Qb41-N.js → notifications-CtZcAwQu.js} +1 -1
- package/dist/interface/assets/{pencil-outline-BrOrbuzc.js → pencil-outline-D7aD4cCo.js} +1 -1
- package/dist/interface/assets/plugin-W20ihuog.js +1 -0
- package/dist/interface/assets/{plugins-COTLBtoZ.js → plugins-BK7kZLJw.js} +1 -1
- package/dist/interface/assets/{plus-Dd-XLT76.js → plus-CA5o30dE.js} +1 -1
- package/dist/interface/assets/{plus-CL6KSxf8.js → plus-DHwFyXkb.js} +1 -1
- package/dist/interface/assets/{power-lCbm_7y7.js → power-C9UIp2oO.js} +1 -1
- package/dist/interface/assets/{ring-resize-CEqUkpxT.js → ring-resize-DkY5EgVl.js} +1 -1
- package/dist/interface/assets/{round-alt-arrow-left-bold-CwaLYdW5.js → round-alt-arrow-left-bold-BxX4ufd0.js} +1 -1
- package/dist/interface/assets/{round-restart-alt-CZSQhHXV.js → round-restart-alt-ctiUuhp8.js} +1 -1
- package/dist/interface/assets/{search-CT2BLw7G.js → search-BZkSqZO5.js} +1 -1
- package/dist/interface/assets/{sensorIcons-Cg62BVxo.js → sensorIcons-C9h8rHR6.js} +1 -1
- package/dist/interface/assets/{sensors-Dxcrwsnf.js → sensors-Bj4drsO3.js} +1 -1
- package/dist/interface/assets/{server-BHIoih6c.js → server-D-ogiovS.js} +1 -1
- package/dist/interface/assets/{settings-CzS4dvIO.js → settings-BUEZ5iRE.js} +1 -1
- package/dist/interface/assets/{source-branch-C6p1G3a9.js → source-branch-ztDB0e13.js} +1 -1
- package/dist/interface/assets/{table-2-line-_VC4NuFA.js → table-2-line-B-z0xvbG.js} +1 -1
- package/dist/interface/assets/{theme-u1kyGMxl.js → theme-DGxZC_Ti.js} +1 -1
- package/dist/interface/assets/{trash-2-DcOYT3oH.js → trash-2-DMMlSPXn.js} +1 -1
- package/dist/interface/assets/{trash-can-outline-cphfB8Jh.js → trash-can-outline-DLKniIWM.js} +1 -1
- package/dist/interface/assets/{trash-fill-CRBaUdPn.js → trash-fill-DevIK050.js} +1 -1
- package/dist/interface/assets/{trash2-CvhB8xOW.js → trash2-BLScx4HX.js} +1 -1
- package/dist/interface/assets/{types-BAbqJts5.js → types-B1G3t3XJ.js} +1 -1
- package/dist/interface/assets/types-BCygqese.js +1 -0
- package/dist/interface/assets/{types-YwmhC3Ej.js → types-vaT6wuY0.js} +1 -1
- package/dist/interface/assets/{upload-DeN3O-ua.js → upload-ClkX9wUX.js} +1 -1
- package/dist/interface/assets/{useAppVersion-BoISCpsN.js → useAppVersion-Ceg42BEw.js} +1 -1
- package/dist/interface/assets/useBackupRestore-BwnYTBUM.js +1 -0
- package/dist/interface/assets/{useCameraOptions-9POwrGtQ.js → useCameraOptions-BxVd91Ew.js} +1 -1
- package/dist/interface/assets/{useElectron-Cam3T7Zb.js → useElectron-BNGXeZEx.js} +1 -1
- package/dist/interface/assets/{useLogsSocket-Bj6kkAHI.js → useLogsSocket-0JucMhVY.js} +1 -1
- package/dist/interface/assets/useNavLayout-CTTy15kG.js +1 -0
- package/dist/interface/assets/{usePermissions-DYAyOydQ.js → usePermissions-C9kp5HQR.js} +1 -1
- package/dist/interface/assets/{usePluginUpdates-b3WpWgyq.js → usePluginUpdates-B9k-vTcT.js} +1 -1
- package/dist/interface/assets/{useServerRestart-BQh5xnO1.js → useServerRestart-D7dAWdQ6.js} +1 -1
- package/dist/interface/assets/{useStreamStatus-Bw80DXqa.js → useStreamStatus-BlV_fFBy.js} +1 -1
- package/dist/interface/assets/useUpdateChannel-BeP49Gm-.js +1 -0
- package/dist/interface/assets/useUserOptions-B8UiLDx4.js +1 -0
- package/dist/interface/assets/utils-gctUU5q_.js +1 -0
- package/dist/interface/assets/{variable-oLSsiEtj.js → variable-CCMnQpL2.js} +1 -1
- package/dist/interface/assets/{vendor-JQXDMBA-.js → vendor-BaiDFVEr.js} +17 -16
- package/dist/interface/assets/{vendor-cameraui-B1Jik_TX.js → vendor-cameraui-GaXYUHUd.js} +3 -3
- package/dist/interface/assets/{vendor-capacitor-CDwgXF4b.js → vendor-capacitor-4nn-eF9U.js} +2 -2
- package/dist/interface/assets/{vendor-chart-KrNcA52y.js → vendor-chart-DtwjR6yw.js} +1 -1
- package/dist/interface/assets/vendor-core-DNm82LOv.js +1 -0
- package/dist/interface/assets/{vendor-dnd-Jch9IvL6.js → vendor-dnd-ChrDrMuz.js} +1 -1
- package/dist/interface/assets/{vendor-draggabilly-Cn-knCpG.js → vendor-draggabilly-DSeIE_bQ.js} +1 -1
- package/dist/interface/assets/vendor-markdown-BDjboykr.js +14 -0
- package/dist/interface/assets/{vendor-primevue-d9Mn2ip-.js → vendor-primevue-CIRUBYsf.js} +76 -76
- package/dist/interface/assets/{vendor-vueflow-DQrTmoNS.js → vendor-vueflow-CLJNLJXc.js} +1 -1
- package/dist/interface/assets/web-BNRwhIOt.js +1 -0
- package/dist/interface/assets/web-BPkOXv4d.js +1 -0
- package/dist/interface/assets/web-BZIx_2d3.js +1 -0
- package/dist/interface/assets/web-BjKL51lK-BwChM7zd.js +1 -0
- package/dist/interface/assets/web-BkmtZjSt.js +1 -0
- package/dist/interface/assets/web-C6RgriWz.js +1 -0
- package/dist/interface/assets/web-CQFMLa0a.js +1 -0
- package/dist/interface/assets/web-CZGnd6VE.js +1 -0
- package/dist/interface/assets/web-Cfzay11X.js +1 -0
- package/dist/interface/assets/web-DAgBwKL-.js +1 -0
- package/dist/interface/assets/web-DgVHh5ek.js +1 -0
- package/dist/interface/assets/web-DlSXwZBR.js +1 -0
- package/dist/interface/assets/web-eDYxVGH7.js +1 -0
- package/dist/interface/assets/web-h4k3EoRQ.js +1 -0
- package/dist/interface/assets/{webhook-CMAB4nTl.js → webhook-DQiZUPv8.js} +1 -1
- package/dist/interface/assets/workbox-window.prod.es5-CcJiPjAS.js +1 -0
- package/dist/interface/assets/{zoom-in-STm8SNt2.js → zoom-in-Du1IB9oA.js} +1 -1
- package/dist/interface/index.html +44 -41
- package/dist/interface/sw.js +1 -1
- package/dist/server/package.json +16 -16
- package/dist/server/requirements.txt +2 -2
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/__init__.py +6 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/__init__.py +0 -2
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/detection.py +2 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/events.py +11 -47
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/frames.py +2 -4
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/plugin/contract.py +1 -1
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/plugin/notifier.py +7 -0
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/classifier.py +14 -8
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/face.py +23 -7
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/license_plate.py +23 -7
- package/dist/server/src/_camera_ui_tools/camera_ui_sdk/storage/__init__.py +60 -2
- package/dist/server/src/api/controllers/auth.controller.js +2 -0
- package/dist/server/src/api/controllers/auth.controller.js.map +1 -1
- package/dist/server/src/api/controllers/nvr.controller.d.ts +0 -3
- package/dist/server/src/api/controllers/nvr.controller.js +8 -38
- package/dist/server/src/api/controllers/nvr.controller.js.map +1 -1
- package/dist/server/src/api/database/index.d.ts +3 -2
- package/dist/server/src/api/database/index.js +12 -2
- package/dist/server/src/api/database/index.js.map +1 -1
- package/dist/server/src/api/database/migrations/v2-0-50.js +2 -1
- package/dist/server/src/api/database/migrations/v2-0-50.js.map +1 -1
- package/dist/server/src/api/database/migrations/v2-1-1.d.ts +3 -0
- package/dist/server/src/api/database/migrations/v2-1-1.js +35 -0
- package/dist/server/src/api/database/migrations/v2-1-1.js.map +1 -0
- package/dist/server/src/api/database/migrations/v2-1-4.d.ts +3 -0
- package/dist/server/src/api/database/migrations/v2-1-4.js +32 -0
- package/dist/server/src/api/database/migrations/v2-1-4.js.map +1 -0
- package/dist/server/src/api/database/record-schemas.d.ts +3 -0
- package/dist/server/src/api/database/record-schemas.js +1 -0
- package/dist/server/src/api/database/record-schemas.js.map +1 -1
- package/dist/server/src/api/database/types.d.ts +6 -0
- package/dist/server/src/api/schemas/cameras.schema.d.ts +24 -24
- package/dist/server/src/api/schemas/cameras.schema.js +5 -5
- package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
- package/dist/server/src/api/schemas/config.schema.d.ts +1 -0
- package/dist/server/src/api/schemas/config.schema.js +1 -0
- package/dist/server/src/api/schemas/config.schema.js.map +1 -1
- package/dist/server/src/api/schemas/users.schema.d.ts +77 -1
- package/dist/server/src/api/schemas/users.schema.js +27 -0
- package/dist/server/src/api/schemas/users.schema.js.map +1 -1
- package/dist/server/src/api/services/auth.service.js +28 -23
- package/dist/server/src/api/services/auth.service.js.map +1 -1
- package/dist/server/src/api/services/automations.service.js +36 -36
- package/dist/server/src/api/services/automations.service.js.map +1 -1
- package/dist/server/src/api/services/backup.service.js +29 -26
- package/dist/server/src/api/services/backup.service.js.map +1 -1
- package/dist/server/src/api/services/backupScheduler.service.js +1 -2
- package/dist/server/src/api/services/backupScheduler.service.js.map +1 -1
- package/dist/server/src/api/services/cameras.service.js +222 -173
- package/dist/server/src/api/services/cameras.service.js.map +1 -1
- package/dist/server/src/api/services/mqtt.service.js +1 -2
- package/dist/server/src/api/services/mqtt.service.js.map +1 -1
- package/dist/server/src/api/services/notifications.service.d.ts +1 -0
- package/dist/server/src/api/services/notifications.service.js +3 -0
- package/dist/server/src/api/services/notifications.service.js.map +1 -1
- package/dist/server/src/api/services/plugins.service.js +1 -1
- package/dist/server/src/api/services/plugins.service.js.map +1 -1
- package/dist/server/src/api/services/remote.service.js +26 -25
- package/dist/server/src/api/services/remote.service.js.map +1 -1
- package/dist/server/src/api/services/server.service.js +9 -7
- package/dist/server/src/api/services/server.service.js.map +1 -1
- package/dist/server/src/api/services/shares.service.js +19 -14
- package/dist/server/src/api/services/shares.service.js.map +1 -1
- package/dist/server/src/api/services/users.service.js +161 -105
- package/dist/server/src/api/services/users.service.js.map +1 -1
- package/dist/server/src/api/services/workers.service.d.ts +1 -0
- package/dist/server/src/api/services/workers.service.js +55 -45
- package/dist/server/src/api/services/workers.service.js.map +1 -1
- package/dist/server/src/api/types/index.d.ts +8 -0
- package/dist/server/src/api/types/index.js.map +1 -1
- package/dist/server/src/api/utils/cert.js +8 -0
- package/dist/server/src/api/utils/cert.js.map +1 -1
- package/dist/server/src/api/websocket/nsp/server.js +1 -1
- package/dist/server/src/api/websocket/nsp/server.js.map +1 -1
- package/dist/server/src/automations/conditions/sensorState.js +9 -0
- package/dist/server/src/automations/conditions/sensorState.js.map +1 -1
- package/dist/server/src/camera/controller.d.ts +1 -1
- package/dist/server/src/camera/controller.js +12 -11
- package/dist/server/src/camera/controller.js.map +1 -1
- package/dist/server/src/camera/decoder/child.d.ts +1 -0
- package/dist/server/src/camera/decoder/child.js +14 -1
- package/dist/server/src/camera/decoder/child.js.map +1 -1
- package/dist/server/src/camera/decoder/debug/detection-record.d.ts +42 -0
- package/dist/server/src/camera/decoder/debug/detection-record.js +103 -0
- package/dist/server/src/camera/decoder/debug/detection-record.js.map +1 -0
- package/dist/server/src/camera/decoder/debug/perf-tracker.d.ts +20 -0
- package/dist/server/src/camera/decoder/debug/perf-tracker.js +82 -0
- package/dist/server/src/camera/decoder/debug/perf-tracker.js.map +1 -0
- package/dist/server/src/camera/decoder/detection-coordinator.d.ts +28 -6
- package/dist/server/src/camera/decoder/detection-coordinator.js +495 -94
- package/dist/server/src/camera/decoder/detection-coordinator.js.map +1 -1
- package/dist/server/src/camera/decoder/detection-pipeline.d.ts +7 -20
- package/dist/server/src/camera/decoder/detection-pipeline.js +67 -127
- package/dist/server/src/camera/decoder/detection-pipeline.js.map +1 -1
- package/dist/server/src/camera/decoder/event-manager.d.ts +42 -14
- package/dist/server/src/camera/decoder/event-manager.js +304 -217
- package/dist/server/src/camera/decoder/event-manager.js.map +1 -1
- package/dist/server/src/camera/decoder/event-thumbnailer.d.ts +5 -8
- package/dist/server/src/camera/decoder/event-thumbnailer.js +11 -21
- package/dist/server/src/camera/decoder/event-thumbnailer.js.map +1 -1
- package/dist/server/src/camera/decoder/frame-scaler.d.ts +2 -1
- package/dist/server/src/camera/decoder/frame-scaler.js +9 -0
- package/dist/server/src/camera/decoder/frame-scaler.js.map +1 -1
- package/dist/server/src/camera/decoder/moment-crop.d.ts +30 -0
- package/dist/server/src/camera/decoder/moment-crop.js +86 -0
- package/dist/server/src/camera/decoder/moment-crop.js.map +1 -0
- package/dist/server/src/camera/decoder/nvr-sink.d.ts +38 -0
- package/dist/server/src/camera/decoder/nvr-sink.js +36 -0
- package/dist/server/src/camera/decoder/nvr-sink.js.map +1 -0
- package/dist/server/src/camera/decoder/secondary-stage.d.ts +4 -2
- package/dist/server/src/camera/decoder/secondary-stage.js +58 -96
- package/dist/server/src/camera/decoder/secondary-stage.js.map +1 -1
- package/dist/server/src/camera/decoder/sources/analysis-source.d.ts +10 -0
- package/dist/server/src/camera/decoder/sources/analysis-source.js +2 -0
- package/dist/server/src/camera/decoder/sources/analysis-source.js.map +1 -0
- package/dist/server/src/camera/decoder/sources/{snapshot-source.d.ts → buffered-source.d.ts} +17 -5
- package/dist/server/src/camera/decoder/sources/{snapshot-source.js → buffered-source.js} +81 -24
- package/dist/server/src/camera/decoder/sources/buffered-source.js.map +1 -0
- package/dist/server/src/camera/decoder/sources/frame-source.d.ts +4 -7
- package/dist/server/src/camera/decoder/sources/frame-source.js +4 -7
- package/dist/server/src/camera/decoder/sources/frame-source.js.map +1 -1
- package/dist/server/src/camera/decoder/types.d.ts +1 -0
- package/dist/server/src/camera/decoder/types.js.map +1 -1
- package/dist/server/src/camera/decoder/worker.js +15 -3
- package/dist/server/src/camera/decoder/worker.js.map +1 -1
- package/dist/server/src/internal-bus.d.ts +1 -1
- package/dist/server/src/internal-bus.js.map +1 -1
- package/dist/server/src/main.js +1 -1
- package/dist/server/src/main.js.map +1 -1
- package/dist/server/src/manager/discoveryManager.js +0 -11
- package/dist/server/src/manager/discoveryManager.js.map +1 -1
- package/dist/server/src/manager/notificationManager.d.ts +1 -1
- package/dist/server/src/manager/notificationManager.js +60 -41
- package/dist/server/src/manager/notificationManager.js.map +1 -1
- package/dist/server/src/mqtt/bridge.d.ts +1 -0
- package/dist/server/src/mqtt/bridge.js +32 -28
- package/dist/server/src/mqtt/bridge.js.map +1 -1
- package/dist/server/src/plugins/nvr.d.ts +2 -0
- package/dist/server/src/plugins/nvr.js +17 -0
- package/dist/server/src/plugins/nvr.js.map +1 -0
- package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js +1 -1
- package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js.map +1 -1
- package/dist/server/src/plugins/runtime/python/storage.py +18 -1
- package/dist/server/src/plugins/runtime/python/store.py +7 -1
- package/dist/server/src/plugins/schema.d.ts +2 -1
- package/dist/server/src/plugins/schema.js +66 -6
- package/dist/server/src/plugins/schema.js.map +1 -1
- package/dist/server/src/plugins/store/storeFile.js +10 -2
- package/dist/server/src/plugins/store/storeFile.js.map +1 -1
- package/dist/server/src/remote/services/index.js +1 -1
- package/dist/server/src/remote/services/index.js.map +1 -1
- package/dist/server/src/rpc/interfaces/frameworker.d.ts +1 -0
- package/dist/server/src/rpc/server.js +6 -0
- package/dist/server/src/rpc/server.js.map +1 -1
- package/dist/server/src/sensors/sensor.js +13 -1
- package/dist/server/src/sensors/sensor.js.map +1 -1
- package/dist/server/src/services/config/constants.js +1 -0
- package/dist/server/src/services/config/constants.js.map +1 -1
- package/dist/server/src/services/config/index.d.ts +5 -2
- package/dist/server/src/services/config/index.js +38 -5
- package/dist/server/src/services/config/index.js.map +1 -1
- package/dist/server/src/services/config/types.d.ts +4 -0
- package/dist/server/src/utils/path.d.ts +1 -0
- package/dist/server/src/utils/path.js +33 -0
- package/dist/server/src/utils/path.js.map +1 -1
- package/dist/server/src/workers/capabilities/frame-decoding.js +1 -1
- package/dist/server/src/workers/capabilities/frame-decoding.js.map +1 -1
- package/package.json +16 -16
- package/dist/interface/assets/Adminpanel-D6LLMvtn.js +0 -1
- package/dist/interface/assets/Camera-CFfDLaKb.js +0 -2
- package/dist/interface/assets/Camera-yUrhUGmO.css +0 -1
- package/dist/interface/assets/Cameras-Bgdc-8pX.js +0 -652
- package/dist/interface/assets/Camview-DxdH-H8m.js +0 -1
- package/dist/interface/assets/Camview-mLCaY0ld.css +0 -1
- package/dist/interface/assets/Config-CG2z9ZDM.js +0 -2
- package/dist/interface/assets/ConfigConditionSensorState-CMusMag_.js +0 -1
- package/dist/interface/assets/ConfigSensorValueInput-CJ71pU5t.js +0 -1
- package/dist/interface/assets/ConfigTriggerSensor-CEtUqINm.js +0 -1
- package/dist/interface/assets/Console-pO4hRXJ6.js +0 -1
- package/dist/interface/assets/CuiAutomationRunsDialog-C_BN8ib3.js +0 -1
- package/dist/interface/assets/CuiBottomSheet-CU5V52RZ.js +0 -1
- package/dist/interface/assets/CuiCameraPipCard-C5UckhjZ.js +0 -1
- package/dist/interface/assets/CuiCameraRecordings-BHj7CUE7.css +0 -1
- package/dist/interface/assets/CuiCameraRecordings-D4FRIiLH.js +0 -1
- package/dist/interface/assets/CuiDataTable-3GW8h063.js +0 -1
- package/dist/interface/assets/CuiLightControl-e9fXnEWM.js +0 -1
- package/dist/interface/assets/CuiMarkdownContent-CiT0VB6c.js +0 -1
- package/dist/interface/assets/CuiPluginCard-BhuhDtto.js +0 -2
- package/dist/interface/assets/CuiRecordingsGrid-CwuvwFQi.js +0 -1
- package/dist/interface/assets/CuiRecordingsGrid-DGCTfTo9.css +0 -1
- package/dist/interface/assets/CuiSchema-Dg3Y0q8q.js +0 -1
- package/dist/interface/assets/Faces-CY_8TVkm.js +0 -1
- package/dist/interface/assets/FirstSteps-CjetT9eW.js +0 -1
- package/dist/interface/assets/Instances-CEflWTSB.js +0 -1
- package/dist/interface/assets/Menu-voSWnkVE.js +0 -1
- package/dist/interface/assets/Plugin-BLqyZrJP.css +0 -1
- package/dist/interface/assets/Plugin-DAAyIdO6.js +0 -1
- package/dist/interface/assets/PluginDetail-aDecfgRt.js +0 -2
- package/dist/interface/assets/PluginSearch-ChiHBh7O.js +0 -2
- package/dist/interface/assets/PluginSearch-vCzkUW8O.js +0 -1
- package/dist/interface/assets/Plugins-CT0UUtSr.js +0 -1
- package/dist/interface/assets/Recordings-DdAO90wn.js +0 -1
- package/dist/interface/assets/SensorEdit-BJpoqdK8.js +0 -1
- package/dist/interface/assets/Sensors-C6U8ckWK.js +0 -2
- package/dist/interface/assets/Settings-JLdVUq7Z.js +0 -1
- package/dist/interface/assets/SettingsAccount-CqJAqaK5.js +0 -1
- package/dist/interface/assets/SettingsAccount-lem3U0i3.js +0 -1
- package/dist/interface/assets/SettingsAppearance-B4B_oSjk.js +0 -1
- package/dist/interface/assets/SettingsAppearance-DIbztL4M.js +0 -1
- package/dist/interface/assets/SettingsBackup-BNIufsOw.js +0 -1
- package/dist/interface/assets/SettingsList-D6r5S91a.js +0 -1
- package/dist/interface/assets/SettingsMqtt-BsfRfFaN.js +0 -1
- package/dist/interface/assets/SettingsRecordings-DoDsjj0I.js +0 -1
- package/dist/interface/assets/SettingsRemote-DmYD8t4k.js +0 -1
- package/dist/interface/assets/SettingsSystem-QnpE8IdB.js +0 -2
- package/dist/interface/assets/Terminal-DbAqY4Hb.js +0 -4
- package/dist/interface/assets/VersionsHandler-DVnhikOd.js +0 -7
- package/dist/interface/assets/Workers-C4ee5Qu6.js +0 -2
- package/dist/interface/assets/api-CNl5E-Z0.js +0 -2
- package/dist/interface/assets/api-DBqh9A2C.css +0 -2
- package/dist/interface/assets/cameras.schema-BD1hnsDj.js +0 -1
- package/dist/interface/assets/chevron-right-BOQr3ruh.js +0 -1
- package/dist/interface/assets/cloudHandoff-C3N4uaQY.js +0 -1
- package/dist/interface/assets/esm-B1LZs0VG.js +0 -1
- package/dist/interface/assets/esm-BB4SBaek.js +0 -1
- package/dist/interface/assets/esm-BGA8Ydne.js +0 -1
- package/dist/interface/assets/esm-BHI_KH7L.js +0 -1
- package/dist/interface/assets/esm-BQ0EuuZe.js +0 -1
- package/dist/interface/assets/esm-Bfb-tuKt.js +0 -1
- package/dist/interface/assets/esm-BzR_6ma5.js +0 -1
- package/dist/interface/assets/esm-CCIqNfsh.js +0 -1
- package/dist/interface/assets/esm-CSMN-8o1.js +0 -1
- package/dist/interface/assets/esm-CxWbrBxP.js +0 -1
- package/dist/interface/assets/esm-DBTrmJK6.js +0 -1
- package/dist/interface/assets/esm-DRswZUWf.js +0 -1
- package/dist/interface/assets/esm-Dk7gxPHs.js +0 -1
- package/dist/interface/assets/esm-P0RCkAtk.js +0 -1
- package/dist/interface/assets/main-DJb49Tjp.css +0 -1
- package/dist/interface/assets/main-nZuJbwRm.js +0 -3
- package/dist/interface/assets/native-Cr4dkfqy.js +0 -1
- package/dist/interface/assets/native-D1Vvlhds.js +0 -1
- package/dist/interface/assets/plugin-937qnq7E.js +0 -1
- package/dist/interface/assets/types-BHs28Xd0.js +0 -1
- package/dist/interface/assets/useBackupRestore-Sw6F6lrg.js +0 -1
- package/dist/interface/assets/useUserOptions-A6noG3pQ.js +0 -1
- package/dist/interface/assets/vendor-core-D7dApN9I.js +0 -1
- package/dist/interface/assets/vendor-markdown-BhFLJolW.js +0 -14
- package/dist/interface/assets/web-BQtDHNew.js +0 -1
- package/dist/interface/assets/web-BY9kjyTj.js +0 -1
- package/dist/interface/assets/web-BcQtDSat.js +0 -1
- package/dist/interface/assets/web-BgrRQzLO.js +0 -1
- package/dist/interface/assets/web-BjKL51lK-CJYmHsnY.js +0 -1
- package/dist/interface/assets/web-BxvsJylk.js +0 -1
- package/dist/interface/assets/web-C-3iwDhG.js +0 -1
- package/dist/interface/assets/web-C9XOwu07.js +0 -1
- package/dist/interface/assets/web-CPCxRDtw.js +0 -1
- package/dist/interface/assets/web-D0U5B2i2.js +0 -1
- package/dist/interface/assets/web-DtxjXJdS.js +0 -1
- package/dist/interface/assets/web-Duf-oV4e.js +0 -1
- package/dist/interface/assets/web-jy45nBi_.js +0 -1
- package/dist/interface/assets/web-vWx_-Iq5.js +0 -1
- package/dist/interface/assets/workbox-window.prod.es5-Dlqax_Po.js +0 -1
- package/dist/server/src/api/utils/moveFiles.d.ts +0 -4
- package/dist/server/src/api/utils/moveFiles.js +0 -37
- package/dist/server/src/api/utils/moveFiles.js.map +0 -1
- package/dist/server/src/camera/decoder/sources/snapshot-source.js.map +0 -1
- package/dist/server/src/camera/decoder/stationary-suppressor.d.ts +0 -38
- package/dist/server/src/camera/decoder/stationary-suppressor.js +0 -308
- package/dist/server/src/camera/decoder/stationary-suppressor.js.map +0 -1
|
@@ -15,24 +15,35 @@ import { DETECTION_SENSOR_TYPES } from '../../sensors/types.js';
|
|
|
15
15
|
import { normalizeZone } from '../utils/filter.js';
|
|
16
16
|
import { AudioDetectionLoop } from './audio-loop.js';
|
|
17
17
|
import { CascadeManager } from './cascade-manager.js';
|
|
18
|
+
import { detectionRecord } from './debug/detection-record.js';
|
|
19
|
+
import { PerfTracker } from './debug/perf-tracker.js';
|
|
18
20
|
import { DetectionPipeline } from './detection-pipeline.js';
|
|
19
21
|
import { DwellManager } from './dwell-manager.js';
|
|
20
|
-
import { DetectionEventManager } from './event-manager.js';
|
|
22
|
+
import { DetectionEventManager, MOMENT_RANK_ATTRIBUTE, MOMENT_RANK_OBJECT } from './event-manager.js';
|
|
21
23
|
import { EventThumbnailer } from './event-thumbnailer.js';
|
|
22
24
|
import { FrameScaler } from './frame-scaler.js';
|
|
25
|
+
import { directionBetween, directionOf, MOMENT_FORMATS, MOMENT_QUALITY, momentWindow, unionBox } from './moment-crop.js';
|
|
26
|
+
import { normalizePlateText } from './plate-vote.js';
|
|
23
27
|
import { isVideoInputSpec, PluginRegistry } from './plugin-registry.js';
|
|
24
28
|
import { PtzAutotracker } from './ptz/autotracker.js';
|
|
25
29
|
import { ReconnectBackoff } from './reconnect-backoff.js';
|
|
26
30
|
import { SecondaryStage } from './secondary-stage.js';
|
|
27
31
|
import { FrameSource } from './sources/frame-source.js';
|
|
28
|
-
import { StationarySuppressor } from './stationary-suppressor.js';
|
|
29
32
|
import { DETECT_TIMEOUT_MS, ensureDetectionBoxes, MOTION_WIDTH_MAP } from './types.js';
|
|
33
|
+
const MOMENT_EVENTS = new Set(['objectEntered', 'objectWoke', 'objectRecovered', 'bestShotUpdated']);
|
|
34
|
+
const MOMENT_MOVING_SPEED = 0.05;
|
|
35
|
+
const MOMENT_ATTRIBUTE_MIN_AREA = 600;
|
|
30
36
|
const DEFAULT_CASCADE_TIMEOUT = 10;
|
|
31
37
|
const OBJECT_DWELL_SECONDS = 2;
|
|
32
38
|
const SECONDARY_BBOX_TTL_MS = 2000;
|
|
33
39
|
const CADENCE_MIN_SAMPLES = 5;
|
|
34
40
|
const CADENCE_OUTLIER_BAND = 2.5;
|
|
35
41
|
const CADENCE_OUTLIER_RESEED = 5;
|
|
42
|
+
const IDLE_TICK_MS = 200;
|
|
43
|
+
const ACTIVE_TICK_MS = 100;
|
|
44
|
+
const MOTION_INTERVAL_MS = 200;
|
|
45
|
+
const TICK_SLACK_MS = 20;
|
|
46
|
+
const MAIN_STREAM_HOLD_MS = 5000;
|
|
36
47
|
let DetectionCoordinator = class DetectionCoordinator {
|
|
37
48
|
config;
|
|
38
49
|
proxy;
|
|
@@ -41,29 +52,38 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
41
52
|
frameScaler;
|
|
42
53
|
plugins = new PluginRegistry();
|
|
43
54
|
pipeline;
|
|
44
|
-
stationary;
|
|
45
55
|
secondaries;
|
|
46
56
|
thumbnailer;
|
|
47
57
|
audioLoop;
|
|
48
58
|
ptzAutotracker;
|
|
49
59
|
eventManager;
|
|
50
60
|
cascade = new CascadeManager();
|
|
61
|
+
worldSpans = new Set();
|
|
62
|
+
activeSensorTriggerTypes = new Map();
|
|
63
|
+
secondaryBboxSeen = new Map();
|
|
64
|
+
faceIdentities = new Set();
|
|
65
|
+
platesSeen = new Set();
|
|
66
|
+
classifierLabels = new Map();
|
|
67
|
+
feedingSensors = new Map();
|
|
51
68
|
dwell = new DwellManager();
|
|
52
69
|
videoBackoff = new ReconnectBackoff();
|
|
70
|
+
// debugging
|
|
71
|
+
perf = new PerfTracker();
|
|
72
|
+
hqUpgrade;
|
|
73
|
+
mainStreamActive = false;
|
|
74
|
+
idleSince = 0;
|
|
75
|
+
lastMotionAt = 0;
|
|
53
76
|
loopRunning = false;
|
|
54
77
|
loopPromise;
|
|
55
78
|
videoStopPromise;
|
|
56
79
|
adHocVideoLoop = false;
|
|
57
80
|
processingExternalSecondary = false;
|
|
58
|
-
cascadeUnsubscribe;
|
|
59
|
-
dwellUnsubscribe;
|
|
60
|
-
activeSensorTriggerTypes = new Map();
|
|
61
|
-
secondaryBboxSeen = new Map();
|
|
62
|
-
feedingSensors = new Set();
|
|
63
81
|
lastObjectCallAt = 0;
|
|
64
82
|
objectIntervalMs = 0;
|
|
65
83
|
objectIntervalSamples = 0;
|
|
66
84
|
objectIntervalOutliers = 0;
|
|
85
|
+
cascadeUnsubscribe;
|
|
86
|
+
dwellUnsubscribe;
|
|
67
87
|
currentDetectionState = {};
|
|
68
88
|
constructor(config, proxy, logger) {
|
|
69
89
|
this.config = config;
|
|
@@ -72,7 +92,6 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
72
92
|
const frameSourceConfig = {
|
|
73
93
|
streamUrl: config.streamUrl,
|
|
74
94
|
snapshotUrl: config.snapshotUrl,
|
|
75
|
-
fps: this.config.frameWorkerSettings.fps,
|
|
76
95
|
decoder: this.config.frameWorkerSettings.decoder,
|
|
77
96
|
};
|
|
78
97
|
if (config.controllerSnapshotSourceId) {
|
|
@@ -88,13 +107,18 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
88
107
|
this.frameSource = new FrameSource(frameSourceConfig, logger);
|
|
89
108
|
this.frameScaler = new FrameScaler(null, logger);
|
|
90
109
|
this.pipeline = new DetectionPipeline(config.zones, config.detectionSettings);
|
|
91
|
-
this.stationary = new StationarySuppressor(logger);
|
|
92
110
|
this.secondaries = new SecondaryStage(this, this.plugins, this.pipeline, this.frameScaler, this.proxy, logger);
|
|
93
111
|
if (config.lines.length > 0) {
|
|
94
112
|
this.pipeline.updateLines(config.lines, this.videoAspectRatio);
|
|
95
113
|
}
|
|
96
114
|
this.eventManager = new DetectionEventManager(config.cameraId, this.proxy, this.logger);
|
|
115
|
+
this.eventManager.updateNvrRpc(config.nvrRpc);
|
|
97
116
|
this.eventManager.onEventEnd(() => this.handleEventEnded());
|
|
117
|
+
this.eventManager.onSegmentClosed(() => {
|
|
118
|
+
if (!this.dwell.hasActive() && this.worldSpans.size === 0) {
|
|
119
|
+
this.eventManager.forceEndActiveEvent();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
98
122
|
this.thumbnailer = new EventThumbnailer({
|
|
99
123
|
frameSource: this.frameSource,
|
|
100
124
|
frameScaler: this.frameScaler,
|
|
@@ -102,7 +126,19 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
102
126
|
logger,
|
|
103
127
|
decoder: this.config.frameWorkerSettings.decoder,
|
|
104
128
|
}, config.availableSources);
|
|
105
|
-
this.thumbnailer.sync(this.
|
|
129
|
+
this.thumbnailer.sync(this.mainStreamAnalysisWanted);
|
|
130
|
+
// debugging
|
|
131
|
+
detectionRecord.sources({
|
|
132
|
+
roles: config.availableSources?.map((s) => s.role),
|
|
133
|
+
mainStreamAnalysis: this.config.frameWorkerSettings.mainStreamAnalysis === true,
|
|
134
|
+
hasMainStream: this.thumbnailer.hasMainStream,
|
|
135
|
+
cascade: this.cascadeEnabled,
|
|
136
|
+
cascadeTimeout: this.cascadeTimeoutSeconds,
|
|
137
|
+
motionTimeout: this.config.detectionSettings.motion.timeout,
|
|
138
|
+
audioTimeout: this.config.detectionSettings.audio.timeout,
|
|
139
|
+
suppressStatic: this.config.detectionSettings.object.suppressStatic ?? true,
|
|
140
|
+
motionResolution: this.motionResolution,
|
|
141
|
+
});
|
|
106
142
|
this.audioLoop = new AudioDetectionLoop({
|
|
107
143
|
cameraId: config.cameraId,
|
|
108
144
|
getPlugin: () => this.plugins.get(SensorType.Audio),
|
|
@@ -120,11 +156,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
120
156
|
});
|
|
121
157
|
this.cascadeUnsubscribe = this.cascade.onChange((event) => {
|
|
122
158
|
if (event.type === 'activated') {
|
|
123
|
-
this.pipeline.setReidHitCounterMax(this.cascadeTimeoutSeconds * this.targetFps);
|
|
124
159
|
this.startAdHocVideoLoopIfNeeded();
|
|
125
160
|
}
|
|
126
161
|
else {
|
|
127
|
-
this.pipeline.setReidHitCounterMax(0);
|
|
128
162
|
this.stopAdHocVideoLoopIfIdle();
|
|
129
163
|
this.handleCascadeDeactivated();
|
|
130
164
|
}
|
|
@@ -146,7 +180,8 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
146
180
|
this.writeSensorProperties(event.sensorId, { detected: false, blocked: false });
|
|
147
181
|
}
|
|
148
182
|
this.activeSensorTriggerTypes.delete(event.sensorId);
|
|
149
|
-
|
|
183
|
+
this.eventManager.processResults(this.buildSnapshot());
|
|
184
|
+
if (!this.dwell.hasActive() && this.worldSpans.size === 0 && !this.eventManager.hasActiveSegment()) {
|
|
150
185
|
this.eventManager.forceEndActiveEvent();
|
|
151
186
|
}
|
|
152
187
|
}
|
|
@@ -176,7 +211,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
176
211
|
}
|
|
177
212
|
updateFrameWorkerSettings(settings) {
|
|
178
213
|
this.config.frameWorkerSettings = settings;
|
|
179
|
-
this.thumbnailer.sync(
|
|
214
|
+
this.thumbnailer.sync(this.mainStreamAnalysisWanted);
|
|
215
|
+
}
|
|
216
|
+
updateNvrRpc(namespace) {
|
|
217
|
+
this.config.nvrRpc = namespace;
|
|
218
|
+
this.eventManager.updateNvrRpc(namespace);
|
|
180
219
|
}
|
|
181
220
|
updateInterfaceSettings(settings) {
|
|
182
221
|
this.config.interfaceSettings = settings;
|
|
@@ -196,12 +235,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
196
235
|
this.eventManager.destroy();
|
|
197
236
|
this.adHocVideoLoop = false;
|
|
198
237
|
this.frameScaler.dispose();
|
|
199
|
-
this.pipeline.cleanup();
|
|
200
238
|
this.currentDetectionState = {};
|
|
201
239
|
}
|
|
202
240
|
async onSensorAdded(sensor) {
|
|
203
241
|
if (DETECTION_SENSOR_TYPES.has(sensor.sensorType)) {
|
|
204
|
-
this.feedingSensors.
|
|
242
|
+
this.feedingSensors.set(sensor.sensorId, sensor.sensorType);
|
|
205
243
|
}
|
|
206
244
|
if (DETECTION_SENSOR_TYPES.has(sensor.sensorType) && sensor.requiresFrames) {
|
|
207
245
|
await this.registerDetectionPluginInternal(sensor);
|
|
@@ -215,8 +253,22 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
215
253
|
}
|
|
216
254
|
}
|
|
217
255
|
async onSensorRemoved(sensorId) {
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
this.classifierLabels.delete(sensorId);
|
|
257
|
+
const sensorType = this.feedingSensors.get(sensorId);
|
|
258
|
+
if (sensorType === SensorType.Face)
|
|
259
|
+
this.faceIdentities.clear();
|
|
260
|
+
if (sensorType === SensorType.LicensePlate)
|
|
261
|
+
this.platesSeen.clear();
|
|
262
|
+
if (sensorType !== undefined && this.feedingSensors.delete(sensorId)) {
|
|
263
|
+
// a sensor removed mid-segment misses the cascade-end clear, so the
|
|
264
|
+
// recognized lists must reset here or they survive the re-register
|
|
265
|
+
this.writeSensorProperties(sensorId, {
|
|
266
|
+
detected: false,
|
|
267
|
+
detections: [],
|
|
268
|
+
...(sensorType === SensorType.Face ? { identities: [] } : {}),
|
|
269
|
+
...(sensorType === SensorType.LicensePlate ? { plates: [] } : {}),
|
|
270
|
+
...(sensorType === SensorType.Classifier ? { labels: [] } : {}),
|
|
271
|
+
});
|
|
220
272
|
}
|
|
221
273
|
await this.removeDetectionPluginBySensor(sensorId);
|
|
222
274
|
this.ptzAutotracker.unbind(sensorId);
|
|
@@ -247,6 +299,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
247
299
|
if (filtered.detected === true && this.eventManager.hasActiveEvent()) {
|
|
248
300
|
this.thumbnailer.fetchEventThumbnailAsync();
|
|
249
301
|
}
|
|
302
|
+
// the camera reported the end (or zones dropped everything): nothing to
|
|
303
|
+
// localize or crop, and an assist hit on leftover scene objects (a
|
|
304
|
+
// parked car) would resurrect the span the camera just closed
|
|
305
|
+
if (filtered.detected !== true)
|
|
306
|
+
return;
|
|
250
307
|
// assist alone is enough: its boxes upgrade zones, bboxes and thumbnails
|
|
251
308
|
// even when no face/plate/clip secondary consumes them
|
|
252
309
|
if (!this.plugins.hasFrameBasedSecondary() && !this.plugins.hasEligibleObjectAssist())
|
|
@@ -264,7 +321,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
264
321
|
this.ingestAssistedObjects(sensorId, objects);
|
|
265
322
|
// processExternal only adapts the shape, external boxes get no real tracking
|
|
266
323
|
const objectDetections = this.pipeline.processExternal(objects.detections);
|
|
267
|
-
await this.runSecondariesAndThumbnails(peeked.frame, objectDetections, results);
|
|
324
|
+
await this.runSecondariesAndThumbnails({ frame: peeked.frame, scaler: this.frameScaler, isMainStream: false }, objectDetections, results);
|
|
268
325
|
this.ingestResultsForAllSecondaries(results);
|
|
269
326
|
const snapshot = this.buildSnapshot();
|
|
270
327
|
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
@@ -288,7 +345,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
288
345
|
// real boxes: crop the object like the frame pipeline, so face and
|
|
289
346
|
// plate secondaries run on the subject instead of the whole scene
|
|
290
347
|
const objectDetections = this.pipeline.processExternal(objects.detections);
|
|
291
|
-
await this.runSecondariesAndThumbnails(fetched.frame, objectDetections, results);
|
|
348
|
+
await this.runSecondariesAndThumbnails({ frame: fetched.frame, scaler: this.frameScaler, isMainStream: false }, objectDetections, results);
|
|
292
349
|
this.ingestResultsForAllSecondaries(results);
|
|
293
350
|
const snapshot = this.buildSnapshot();
|
|
294
351
|
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
@@ -297,11 +354,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
297
354
|
this.eventManager.processResults(snapshot);
|
|
298
355
|
}
|
|
299
356
|
else {
|
|
300
|
-
const objectDetections = this.pipeline.processExternal(objects.detections);
|
|
301
357
|
const sceneJpeg = await this.secondaries.detectFullFrame(fetched.frame, results);
|
|
302
358
|
try {
|
|
303
|
-
|
|
304
|
-
results.thumbnails = await this.secondaries.generateThumbnails(fetched.frame, this.frameScaler, false, objectDetections, results);
|
|
359
|
+
results.thumbnails = await this.secondaries.generateThumbnails(fetched.frame, this.frameScaler, results);
|
|
305
360
|
}
|
|
306
361
|
catch (error) {
|
|
307
362
|
this.logger.error('Thumbnail generation error:', error);
|
|
@@ -356,7 +411,8 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
356
411
|
}
|
|
357
412
|
this.dwell.refresh(dwellKey, timeoutSeconds);
|
|
358
413
|
if (action === 'activate' && this.cascadeEnabled) {
|
|
359
|
-
|
|
414
|
+
// trigger edges never repeat, the cascade covers the trigger's dwell
|
|
415
|
+
this.cascade.triggerMomentary(Math.max(timeoutSeconds, this.cascadeTimeoutSeconds));
|
|
360
416
|
}
|
|
361
417
|
this.eventManager.processResults(this.buildSnapshot());
|
|
362
418
|
// no loop frame to encode from, fetch async (no-op if already captured)
|
|
@@ -377,6 +433,18 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
377
433
|
hasPlugin(sensorType) {
|
|
378
434
|
return this.plugins.has(sensorType);
|
|
379
435
|
}
|
|
436
|
+
externalObjectSpanActive() {
|
|
437
|
+
// the registry holds frame-based providers only; a smart-camera object
|
|
438
|
+
// sensor (Reolink AI, ONVIF) lives in the feeding set, and its dwell is
|
|
439
|
+
// what keeps the span open between its report edges
|
|
440
|
+
if (this.plugins.get(SensorType.Object)?.requiresFrames)
|
|
441
|
+
return false;
|
|
442
|
+
for (const [sensorId, sensorType] of this.feedingSensors) {
|
|
443
|
+
if (sensorType === SensorType.Object && this.dwell.isActive(sensorId))
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
380
448
|
get cascadeEnabled() {
|
|
381
449
|
return this.config.detectionSettings.cascadeDetection !== false;
|
|
382
450
|
}
|
|
@@ -413,22 +481,26 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
413
481
|
for (const plugin of this.plugins.getAll(SensorType.Classifier)) {
|
|
414
482
|
clearTargets.push(plugin.sensorId);
|
|
415
483
|
}
|
|
484
|
+
this.faceIdentities.clear();
|
|
485
|
+
this.platesSeen.clear();
|
|
486
|
+
this.classifierLabels.clear();
|
|
487
|
+
const faceSensorId = this.plugins.get(SensorType.Face)?.sensorId;
|
|
488
|
+
const plateSensorId = this.plugins.get(SensorType.LicensePlate)?.sensorId;
|
|
489
|
+
const classifierSensorIds = new Set(this.plugins.getAll(SensorType.Classifier).map((p) => p.sensorId));
|
|
416
490
|
for (const sensorId of clearTargets) {
|
|
417
|
-
this.writeSensorProperties(sensorId, {
|
|
491
|
+
this.writeSensorProperties(sensorId, {
|
|
492
|
+
detected: false,
|
|
493
|
+
detections: [],
|
|
494
|
+
...(sensorId === faceSensorId ? { identities: [] } : {}),
|
|
495
|
+
...(sensorId === plateSensorId ? { plates: [] } : {}),
|
|
496
|
+
...(classifierSensorIds.has(sensorId) ? { labels: [] } : {}),
|
|
497
|
+
});
|
|
418
498
|
this.secondaryBboxSeen.delete(sensorId);
|
|
419
499
|
}
|
|
420
500
|
// segment ends here, the event itself continues while motion is active
|
|
421
501
|
this.eventManager.processResults(this.buildSnapshot());
|
|
422
502
|
}
|
|
423
503
|
handleEventEnded() {
|
|
424
|
-
if (this.config.detectionSettings.object.suppressStatic ?? true) {
|
|
425
|
-
this.stationary.retainAcrossEvent((trackIds) => this.pipeline.retainTracks(trackIds));
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
this.pipeline.cleanup();
|
|
429
|
-
this.stationary.clear();
|
|
430
|
-
}
|
|
431
|
-
this.stationary.resetEventState();
|
|
432
504
|
this.activeSensorTriggerTypes.clear();
|
|
433
505
|
this.currentDetectionState = {};
|
|
434
506
|
}
|
|
@@ -451,15 +523,16 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
451
523
|
};
|
|
452
524
|
this.proxy.publish(NamespaceManager.sensorCameraViewNamespaces(this.config.cameraId).sensorWriteSubject, msg);
|
|
453
525
|
}
|
|
454
|
-
buildSnapshot() {
|
|
526
|
+
buildSnapshot(at = Date.now()) {
|
|
455
527
|
const cs = this.currentDetectionState;
|
|
456
528
|
return {
|
|
457
529
|
hasCascadeTrigger: this.cascade.isActive,
|
|
530
|
+
hasOpenSpans: this.worldSpans.size > 0 || this.externalObjectSpanActive(),
|
|
458
531
|
motion: cs.motion ? { detected: cs.motion.detected ?? false } : undefined,
|
|
459
532
|
audio: cs.audio ? { detected: cs.audio.detected ?? false, detections: cs.audio.detections ?? [] } : undefined,
|
|
460
533
|
cascadeTriggered: cs.cascadeTriggered,
|
|
461
534
|
sensorTriggers: [...new Set(this.activeSensorTriggerTypes.values())],
|
|
462
|
-
objects: cs.object?.detected ?
|
|
535
|
+
objects: cs.object?.detected ? cs.object.detections : [],
|
|
463
536
|
faces: cs.face?.detections ?? [],
|
|
464
537
|
faceEmbeddingModel: cs.faceEmbeddingModel,
|
|
465
538
|
plates: cs.licensePlate?.detections ?? [],
|
|
@@ -472,7 +545,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
472
545
|
clipEmbeddingModel: cs.clipEmbeddingModel,
|
|
473
546
|
thumbnails: undefined,
|
|
474
547
|
lineCrossings: cs.lineCrossings,
|
|
475
|
-
timestamp:
|
|
548
|
+
timestamp: at,
|
|
476
549
|
segmentTimeout: 10,
|
|
477
550
|
expectedEndTime: this.dwell.maxExpiry(),
|
|
478
551
|
detectionZones: this.getNormalizedDetectionZones(),
|
|
@@ -532,7 +605,6 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
532
605
|
return properties;
|
|
533
606
|
return { ...properties, detections: filtered, detected: filtered.length > 0 };
|
|
534
607
|
}
|
|
535
|
-
// PTZ-suppression gating happens upstream, callers decide what gets in here
|
|
536
608
|
ingestDetectionResult(sensorType, sensorId, properties, pluginId) {
|
|
537
609
|
if (sensorType === SensorType.Audio) {
|
|
538
610
|
properties = this.applyAudioConfidence(properties);
|
|
@@ -543,7 +615,12 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
543
615
|
const motionTimeout = this.config.detectionSettings.motion.timeout;
|
|
544
616
|
this.dwell.refresh(sensorId, motionTimeout);
|
|
545
617
|
if (this.cascadeEnabled) {
|
|
546
|
-
|
|
618
|
+
// a frame-based motion sensor re-arms on every motion frame, so its
|
|
619
|
+
// short tail is fine; an external sensor fires one edge and goes
|
|
620
|
+
// quiet, that edge must arm the cascade for the whole belief window
|
|
621
|
+
const framePlugin = this.plugins.get(SensorType.Motion);
|
|
622
|
+
const frameBased = framePlugin?.requiresFrames === true && framePlugin.sensorId === sensorId;
|
|
623
|
+
this.cascade.triggerMomentary(frameBased ? this.cascadeTimeoutSeconds : Math.max(motionTimeout, this.cascadeTimeoutSeconds));
|
|
547
624
|
}
|
|
548
625
|
}
|
|
549
626
|
else if (sensorType === SensorType.Audio && detected) {
|
|
@@ -553,28 +630,41 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
553
630
|
this.cascade.triggerMomentary(this.cascadeTimeoutSeconds);
|
|
554
631
|
}
|
|
555
632
|
}
|
|
556
|
-
else if (sensorType === SensorType.Object
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
633
|
+
else if (sensorType === SensorType.Object) {
|
|
634
|
+
const framePlugin = this.plugins.get(SensorType.Object);
|
|
635
|
+
const frameBased = framePlugin?.requiresFrames === true && framePlugin.sensorId === sensorId;
|
|
636
|
+
if (detected) {
|
|
637
|
+
// the dwell bridges single missed frames; anchored stationary tracks
|
|
638
|
+
// don't refresh it, so parked objects can't hold events open
|
|
639
|
+
const detections = properties.detections ?? [];
|
|
640
|
+
if (detections.length > 0) {
|
|
641
|
+
// frame results re-arm every tick, so 2s only bridges a dropped
|
|
642
|
+
// frame; an external report is an edge that must hold until the
|
|
643
|
+
// camera reports the end (cleared below), capped by the object
|
|
644
|
+
// timeout in case that end never arrives
|
|
645
|
+
const dwellSeconds = frameBased ? OBJECT_DWELL_SECONDS : Math.max(this.config.detectionSettings.object.timeout ?? 15, OBJECT_DWELL_SECONDS);
|
|
646
|
+
this.dwell.refresh(sensorId, dwellSeconds);
|
|
647
|
+
// a confirmed object re-arms the cascade like motion does: a time-based
|
|
648
|
+
// motion sensor (ONVIF cool-down) must not blind detection and split
|
|
649
|
+
// the event while someone is still mid-frame
|
|
650
|
+
if (this.cascadeEnabled) {
|
|
651
|
+
this.cascade.triggerMomentary(this.cascadeTimeoutSeconds);
|
|
652
|
+
}
|
|
567
653
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
654
|
+
else {
|
|
655
|
+
// clear detected + buffer so no segment opens, the UI still gets
|
|
656
|
+
// the bboxes via the property publish below
|
|
657
|
+
properties.detected = false;
|
|
658
|
+
if (this.currentDetectionState.object) {
|
|
659
|
+
this.currentDetectionState.object.detected = false;
|
|
660
|
+
this.currentDetectionState.object.detections = [];
|
|
661
|
+
}
|
|
576
662
|
}
|
|
577
|
-
|
|
663
|
+
}
|
|
664
|
+
else if (!frameBased) {
|
|
665
|
+
// the camera's own end report closes the span; the segment linger
|
|
666
|
+
// still bridges flicker between two alarms
|
|
667
|
+
this.dwell.clear(sensorId);
|
|
578
668
|
}
|
|
579
669
|
}
|
|
580
670
|
// dwell owns detected/blocked/lastTriggered for these types, a no-detection
|
|
@@ -711,8 +801,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
711
801
|
for (const plugin of this.plugins.getAll(SensorType.Classifier)) {
|
|
712
802
|
this.clearSecondaryBboxes(plugin.sensorId);
|
|
713
803
|
}
|
|
714
|
-
|
|
715
|
-
this.stationary.dropForCameraMove();
|
|
804
|
+
this.pipeline.notifyCameraMove();
|
|
716
805
|
}
|
|
717
806
|
clearSecondaryBboxes(sensorId) {
|
|
718
807
|
this.writeSensorProperties(sensorId, { detections: [] });
|
|
@@ -744,6 +833,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
744
833
|
}
|
|
745
834
|
this.logger.debug('Stopping video detection loop');
|
|
746
835
|
this.loopRunning = false;
|
|
836
|
+
this.worldSpans.clear();
|
|
837
|
+
this.mainStreamActive = false;
|
|
838
|
+
this.idleSince = 0;
|
|
747
839
|
const doStop = async () => {
|
|
748
840
|
await this.frameSource.stop();
|
|
749
841
|
await this.loopPromise;
|
|
@@ -776,27 +868,51 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
776
868
|
try {
|
|
777
869
|
await this.frameSource.start();
|
|
778
870
|
this.frameScaler.updateHardwareContext(this.frameSource.hardwareContext);
|
|
871
|
+
// the hardware context exists only after start, re-evaluate the HQ session
|
|
872
|
+
this.thumbnailer.sync(this.mainStreamAnalysisWanted);
|
|
779
873
|
this.logger.debug('Stream connected, processing frames...');
|
|
780
874
|
this.videoBackoff.reset();
|
|
781
875
|
let frameCount = 0;
|
|
782
876
|
let lastFrameId = -1;
|
|
783
877
|
while (this.loopRunning) {
|
|
878
|
+
const tickStart = Date.now();
|
|
784
879
|
const snap = await this.frameSource.nextFrame(lastFrameId);
|
|
785
880
|
if (!snap)
|
|
786
881
|
break; // source ended (stop or EOF)
|
|
787
882
|
lastFrameId = snap.id;
|
|
788
883
|
frameCount++;
|
|
884
|
+
// motion always reads the low stream: switching its input resolution
|
|
885
|
+
// would reset the background model on every transition
|
|
886
|
+
const analysis = await this.acquireAnalysisFrame(snap.frame);
|
|
887
|
+
// debugging
|
|
888
|
+
detectionRecord.setFrame(analysis.isMainStream ? 'main' : 'low', analysis.frame.width, analysis.frame.height);
|
|
789
889
|
try {
|
|
790
|
-
await this.processRawFrame(snap.frame);
|
|
890
|
+
await this.processRawFrame(analysis, snap.frame);
|
|
791
891
|
}
|
|
792
892
|
finally {
|
|
793
893
|
try {
|
|
894
|
+
if (this.hqUpgrade) {
|
|
895
|
+
this.hqUpgrade.frame[Symbol.dispose]?.();
|
|
896
|
+
this.hqUpgrade = undefined;
|
|
897
|
+
}
|
|
898
|
+
if (analysis.frame !== snap.frame)
|
|
899
|
+
analysis.frame[Symbol.dispose]?.();
|
|
794
900
|
snap.frame[Symbol.dispose]?.();
|
|
795
901
|
}
|
|
796
902
|
catch {
|
|
797
903
|
// best-effort
|
|
798
904
|
}
|
|
799
905
|
}
|
|
906
|
+
this.updateStreamState();
|
|
907
|
+
if (this.mainStreamActive)
|
|
908
|
+
this.perf.activeTicks++;
|
|
909
|
+
else
|
|
910
|
+
this.perf.idleTicks++;
|
|
911
|
+
this.perf.report(this.logger);
|
|
912
|
+
const remaining = (this.mainStreamActive ? ACTIVE_TICK_MS : IDLE_TICK_MS) - (Date.now() - tickStart);
|
|
913
|
+
if (remaining > 0)
|
|
914
|
+
await sleep(remaining);
|
|
915
|
+
this.perf.loopMs += Date.now() - tickStart;
|
|
800
916
|
}
|
|
801
917
|
const streamError = this.frameSource.lastError;
|
|
802
918
|
await this.frameSource.stop();
|
|
@@ -825,25 +941,85 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
825
941
|
this.frameScaler.clearCache();
|
|
826
942
|
this.logger.debug('Detection loop ended');
|
|
827
943
|
}
|
|
828
|
-
|
|
944
|
+
get mainStreamAvailable() {
|
|
945
|
+
if (!this.thumbnailer.hasMainStream)
|
|
946
|
+
return false;
|
|
947
|
+
return this.mainStreamAnalysisWanted;
|
|
948
|
+
}
|
|
949
|
+
get mainStreamAnalysisWanted() {
|
|
950
|
+
return this.config.frameWorkerSettings.mainStreamAnalysis === true || this.frameSource.hardwareContext !== null;
|
|
951
|
+
}
|
|
952
|
+
async acquireAnalysisFrame(lowFrame) {
|
|
953
|
+
if (this.mainStreamActive) {
|
|
954
|
+
const t0 = Date.now();
|
|
955
|
+
try {
|
|
956
|
+
const main = await this.thumbnailer.acquireHqFrame(0);
|
|
957
|
+
if (main) {
|
|
958
|
+
this.perf.mainFrames++;
|
|
959
|
+
this.perf.decodeMs += Date.now() - t0;
|
|
960
|
+
return { frame: main.frame, scaler: main.scaler, isMainStream: true };
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
this.logger.debug('Main stream frame unavailable:', error);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return { frame: lowFrame, scaler: this.frameScaler, isMainStream: false };
|
|
968
|
+
}
|
|
969
|
+
async acquireOpeningHqFrame() {
|
|
970
|
+
if (!this.mainStreamAvailable)
|
|
971
|
+
return undefined;
|
|
972
|
+
const t0 = Date.now();
|
|
973
|
+
try {
|
|
974
|
+
const main = await this.thumbnailer.acquireHqFrame(0);
|
|
975
|
+
if (main) {
|
|
976
|
+
this.perf.mainFrames++;
|
|
977
|
+
this.perf.decodeMs += Date.now() - t0;
|
|
978
|
+
return { frame: main.frame, scaler: main.scaler, isMainStream: true };
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
catch (error) {
|
|
982
|
+
this.logger.debug('HQ frame unavailable for the opening tick:', error);
|
|
983
|
+
}
|
|
984
|
+
return undefined;
|
|
985
|
+
}
|
|
986
|
+
updateStreamState() {
|
|
987
|
+
if (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment()) {
|
|
988
|
+
this.idleSince = 0;
|
|
989
|
+
if (!this.mainStreamActive && this.mainStreamAvailable) {
|
|
990
|
+
this.mainStreamActive = true;
|
|
991
|
+
this.perf.switches++;
|
|
992
|
+
this.logger.debug('Analysis switched to the main stream');
|
|
993
|
+
}
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (!this.mainStreamActive)
|
|
997
|
+
return;
|
|
998
|
+
if (this.idleSince === 0) {
|
|
999
|
+
this.idleSince = Date.now();
|
|
1000
|
+
}
|
|
1001
|
+
else if (Date.now() - this.idleSince >= MAIN_STREAM_HOLD_MS) {
|
|
1002
|
+
this.mainStreamActive = false;
|
|
1003
|
+
this.idleSince = 0;
|
|
1004
|
+
this.perf.switches++;
|
|
1005
|
+
this.logger.debug('Analysis back on the low-resolution stream');
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async processRawFrame(analysis, motionRawFrame) {
|
|
829
1009
|
if (!this.loopRunning || !this.plugins.hasAny())
|
|
830
1010
|
return;
|
|
831
1011
|
try {
|
|
832
|
-
await this.processDetection(
|
|
1012
|
+
await this.processDetection(analysis, motionRawFrame);
|
|
833
1013
|
}
|
|
834
1014
|
catch (error) {
|
|
835
1015
|
this.logger.error('Detection processing error:', error);
|
|
836
1016
|
}
|
|
837
1017
|
}
|
|
838
|
-
async processDetection(
|
|
1018
|
+
async processDetection(analysis, motionRawFrame) {
|
|
839
1019
|
const t0 = Date.now();
|
|
840
1020
|
let motionDetected = false;
|
|
841
1021
|
let objectDetections = [];
|
|
842
1022
|
const results = { timestamp: t0 };
|
|
843
|
-
// keeps dead tracks from expiring during the cascade window
|
|
844
|
-
if (this.cascade.isActive) {
|
|
845
|
-
this.pipeline.refreshReid();
|
|
846
|
-
}
|
|
847
1023
|
// while the PTZ repositions: motion and object keep running so the
|
|
848
1024
|
// background model and track ids survive the pan, but nothing below
|
|
849
1025
|
// reaches dwell/events until the settle window ends
|
|
@@ -852,12 +1028,20 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
852
1028
|
// object detection, not motion
|
|
853
1029
|
const motionPlugin = this.plugins.get(SensorType.Motion);
|
|
854
1030
|
if (motionPlugin?.requiresFrames) {
|
|
855
|
-
|
|
1031
|
+
// motion keeps its own rate, a background diff gains nothing from the active tick speed
|
|
1032
|
+
const motionDue = t0 - this.lastMotionAt >= MOTION_INTERVAL_MS - TICK_SLACK_MS;
|
|
1033
|
+
const motionScaleStart = Date.now();
|
|
1034
|
+
const motionFrame = motionDue ? await this.scaleForMotion(motionRawFrame) : undefined;
|
|
1035
|
+
if (motionDue)
|
|
1036
|
+
this.perf.scaleMs += Date.now() - motionScaleStart;
|
|
856
1037
|
if (!this.loopRunning)
|
|
857
1038
|
return;
|
|
858
1039
|
if (motionFrame) {
|
|
1040
|
+
this.lastMotionAt = t0;
|
|
859
1041
|
try {
|
|
1042
|
+
const motionInferStart = Date.now();
|
|
860
1043
|
const result = await motionPlugin.proxy.detectMotion(motionFrame);
|
|
1044
|
+
this.perf.inferMs += Date.now() - motionInferStart;
|
|
861
1045
|
if (!this.loopRunning)
|
|
862
1046
|
return;
|
|
863
1047
|
if (ptzSuppressed) {
|
|
@@ -899,12 +1083,16 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
899
1083
|
const objectPlugin = this.plugins.get(SensorType.Object);
|
|
900
1084
|
const shouldDetectObjects = objectPlugin?.requiresFrames === true && (ptzSuppressed || !this.cascadeEnabled || this.cascade.isActive);
|
|
901
1085
|
if (shouldDetectObjects) {
|
|
902
|
-
const
|
|
1086
|
+
const scaleStart = Date.now();
|
|
1087
|
+
const objectFrame = await this.scaleForObject(analysis.frame, analysis.scaler);
|
|
1088
|
+
this.perf.scaleMs += Date.now() - scaleStart;
|
|
903
1089
|
if (!this.loopRunning)
|
|
904
1090
|
return;
|
|
905
1091
|
if (objectFrame) {
|
|
906
1092
|
try {
|
|
1093
|
+
const inferStart = Date.now();
|
|
907
1094
|
const result = await PromiseTimeout(objectPlugin.proxy.detectObjects(objectFrame.model), DETECT_TIMEOUT_MS, undefined, `Object detection timed out after ${DETECT_TIMEOUT_MS}ms`);
|
|
1095
|
+
this.perf.inferMs += Date.now() - inferStart;
|
|
908
1096
|
if (!this.loopRunning)
|
|
909
1097
|
return;
|
|
910
1098
|
this.trackDetectionCadence();
|
|
@@ -912,14 +1100,26 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
912
1100
|
// Kalman state; the pose delta keeps predictions stable across pans
|
|
913
1101
|
const poseDelta = this.ptzAutotracker.consumePoseDelta();
|
|
914
1102
|
const detected = FrameScaler.undoLetterbox(ensureDetectionBoxes(result.detections), objectFrame.geometry);
|
|
915
|
-
const pipelineResult = this.pipeline.process(detected,
|
|
1103
|
+
const pipelineResult = this.pipeline.process(detected, poseDelta);
|
|
1104
|
+
// the tick that opens a span analysed the low stream (the switch lands
|
|
1105
|
+
// next tick), its picture work is worth a one-off HQ decode
|
|
1106
|
+
if (this.updateWorldSpans(pipelineResult) && !analysis.isMainStream) {
|
|
1107
|
+
this.hqUpgrade = await this.acquireOpeningHqFrame();
|
|
1108
|
+
}
|
|
1109
|
+
await this.captureMoments(pipelineResult, this.hqUpgrade ?? analysis, t0);
|
|
1110
|
+
// open spans and a lingering segment keep the cascade armed: the
|
|
1111
|
+
// world can only observe absence or a reappearance while it still
|
|
1112
|
+
// receives ticks
|
|
1113
|
+
if (this.cascadeEnabled && (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment())) {
|
|
1114
|
+
this.cascade.triggerMomentary(this.cascadeTimeoutSeconds);
|
|
1115
|
+
}
|
|
916
1116
|
// track id churn is invisible in the event log without this
|
|
917
1117
|
if (pipelineResult.created.length > 0 || pipelineResult.removed.length > 0) {
|
|
918
1118
|
const born = pipelineResult.created.map((id) => {
|
|
919
1119
|
const t = pipelineResult.tracked.find((d) => d.trackId === id);
|
|
920
1120
|
return `${t?.label ?? 'unknown'}#${id}`;
|
|
921
1121
|
});
|
|
922
|
-
const died = pipelineResult.removed.map((id) => `#${id}
|
|
1122
|
+
const died = pipelineResult.removed.map((id) => `#${id}`);
|
|
923
1123
|
this.logger.trace(`[tracker] ${born.length ? `born: ${born.join(', ')}` : ''}${born.length && died.length ? ' — ' : ''}${died.length ? `died: ${died.join(', ')}` : ''}`);
|
|
924
1124
|
}
|
|
925
1125
|
// extrapolated (Kalman-only) tracks smooth single-frame misses in
|
|
@@ -974,7 +1174,11 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
974
1174
|
if (!this.loopRunning)
|
|
975
1175
|
return;
|
|
976
1176
|
if (objectDetections.length > 0) {
|
|
977
|
-
|
|
1177
|
+
const images = this.hqUpgrade ?? analysis;
|
|
1178
|
+
await this.runSecondariesAndThumbnails(images, objectDetections, results);
|
|
1179
|
+
if (!this.loopRunning)
|
|
1180
|
+
return;
|
|
1181
|
+
await this.captureAttributeMoment(results, images, t0);
|
|
978
1182
|
}
|
|
979
1183
|
if (!this.loopRunning)
|
|
980
1184
|
return;
|
|
@@ -984,7 +1188,7 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
984
1188
|
// messages already carry them
|
|
985
1189
|
if (!this.loopRunning)
|
|
986
1190
|
return;
|
|
987
|
-
const snapshot = this.buildSnapshot();
|
|
1191
|
+
const snapshot = this.buildSnapshot(t0);
|
|
988
1192
|
if (results.thumbnails && results.thumbnails.length > 0) {
|
|
989
1193
|
snapshot.thumbnails = results.thumbnails;
|
|
990
1194
|
}
|
|
@@ -994,7 +1198,9 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
994
1198
|
let hqThumbAttached = false;
|
|
995
1199
|
if (wantsEventThumb) {
|
|
996
1200
|
try {
|
|
997
|
-
const
|
|
1201
|
+
const thumbStart = Date.now();
|
|
1202
|
+
const thumb = await this.thumbnailer.captureEventThumbnail((this.hqUpgrade ?? analysis).frame);
|
|
1203
|
+
this.perf.jpegMs += Date.now() - thumbStart;
|
|
998
1204
|
if (thumb.jpeg)
|
|
999
1205
|
snapshot.eventThumbnail = thumb.jpeg;
|
|
1000
1206
|
hqThumbAttached = thumb.fromHq;
|
|
@@ -1004,25 +1210,29 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1004
1210
|
}
|
|
1005
1211
|
}
|
|
1006
1212
|
this.eventManager.processResults(snapshot);
|
|
1213
|
+
// spans can outlive every trigger dwell (gate case); once spans, dwells
|
|
1214
|
+
// and the segment linger are all quiet the event ends here
|
|
1215
|
+
if (this.worldSpans.size === 0 && !this.dwell.hasActive() && !this.eventManager.hasActiveSegment()) {
|
|
1216
|
+
this.eventManager.forceEndActiveEvent();
|
|
1217
|
+
}
|
|
1007
1218
|
// covers the window where the HQ source wasn't ready at event start
|
|
1008
1219
|
if (wantsEventThumb && !hqThumbAttached) {
|
|
1009
1220
|
this.thumbnailer.upgradeEventThumbnailAsync();
|
|
1010
1221
|
}
|
|
1011
1222
|
}
|
|
1012
|
-
async runSecondariesAndThumbnails(
|
|
1013
|
-
objectDetections = this.stationary.excludeSealed(objectDetections);
|
|
1223
|
+
async runSecondariesAndThumbnails(analysis, objectDetections, results) {
|
|
1014
1224
|
if (objectDetections.length === 0)
|
|
1015
1225
|
return;
|
|
1016
|
-
|
|
1226
|
+
this.perf.objects += objectDetections.length;
|
|
1227
|
+
const secondaryStart = Date.now();
|
|
1228
|
+
await this.secondaries.detect(analysis.frame, analysis.scaler, objectDetections, results);
|
|
1229
|
+
this.perf.secondaryMs += Date.now() - secondaryStart;
|
|
1017
1230
|
try {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
finally {
|
|
1024
|
-
hq?.frame[Symbol.dispose]?.();
|
|
1025
|
-
}
|
|
1231
|
+
const jpegStart = Date.now();
|
|
1232
|
+
results.thumbnails = await this.secondaries.generateThumbnails(analysis.frame, analysis.scaler, results);
|
|
1233
|
+
this.perf.jpegMs += Date.now() - jpegStart;
|
|
1234
|
+
this.perf.faces += results.face?.detections?.length ?? 0;
|
|
1235
|
+
this.perf.plates += results.licensePlate?.detections?.length ?? 0;
|
|
1026
1236
|
}
|
|
1027
1237
|
catch (error) {
|
|
1028
1238
|
this.logger.error('Thumbnail generation error:', error);
|
|
@@ -1035,18 +1245,31 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1035
1245
|
}
|
|
1036
1246
|
const facePlugin = this.plugins.get(SensorType.Face);
|
|
1037
1247
|
if (facePlugin) {
|
|
1248
|
+
// no mid-segment clear: a turned-away head yields a no-face frame and
|
|
1249
|
+
// would flap the list, departure is reflected when the segment ends
|
|
1250
|
+
for (const detection of results.face.detections) {
|
|
1251
|
+
if (detection.identity)
|
|
1252
|
+
this.faceIdentities.add(detection.identity);
|
|
1253
|
+
}
|
|
1038
1254
|
this.ingestDetectionResult(SensorType.Face, facePlugin.sensorId, {
|
|
1039
1255
|
detected: results.face.detected,
|
|
1040
1256
|
detections: results.face.detections,
|
|
1257
|
+
identities: [...this.faceIdentities].sort(),
|
|
1041
1258
|
});
|
|
1042
1259
|
}
|
|
1043
1260
|
}
|
|
1044
1261
|
if (results.licensePlate) {
|
|
1045
1262
|
const lpdPlugin = this.plugins.get(SensorType.LicensePlate);
|
|
1046
1263
|
if (lpdPlugin) {
|
|
1264
|
+
for (const detection of results.licensePlate.detections) {
|
|
1265
|
+
const plate = detection.plateText ? normalizePlateText(detection.plateText) || detection.plateText : '';
|
|
1266
|
+
if (plate)
|
|
1267
|
+
this.platesSeen.add(plate);
|
|
1268
|
+
}
|
|
1047
1269
|
this.ingestDetectionResult(SensorType.LicensePlate, lpdPlugin.sensorId, {
|
|
1048
1270
|
detected: results.licensePlate.detected,
|
|
1049
1271
|
detections: results.licensePlate.detections,
|
|
1272
|
+
plates: [...this.platesSeen].sort(),
|
|
1050
1273
|
});
|
|
1051
1274
|
}
|
|
1052
1275
|
}
|
|
@@ -1054,9 +1277,21 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1054
1277
|
for (const [pluginId, classifierResult] of Object.entries(results.classifiers)) {
|
|
1055
1278
|
const plugin = this.plugins.getAll(SensorType.Classifier).find((p) => p.pluginId === pluginId);
|
|
1056
1279
|
if (plugin) {
|
|
1280
|
+
let seen = this.classifierLabels.get(plugin.sensorId);
|
|
1281
|
+
if (!seen) {
|
|
1282
|
+
seen = new Set();
|
|
1283
|
+
this.classifierLabels.set(plugin.sensorId, seen);
|
|
1284
|
+
}
|
|
1285
|
+
for (const detection of classifierResult.detections) {
|
|
1286
|
+
// the specific answer wins (subAttribute, e.g. the bird species)
|
|
1287
|
+
const label = detection.subAttribute || detection.attribute || detection.label;
|
|
1288
|
+
if (label)
|
|
1289
|
+
seen.add(label);
|
|
1290
|
+
}
|
|
1057
1291
|
this.ingestDetectionResult(SensorType.Classifier, plugin.sensorId, {
|
|
1058
1292
|
detected: classifierResult.detected,
|
|
1059
1293
|
detections: classifierResult.detections,
|
|
1294
|
+
labels: [...seen].sort(),
|
|
1060
1295
|
}, pluginId);
|
|
1061
1296
|
}
|
|
1062
1297
|
}
|
|
@@ -1082,6 +1317,173 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1082
1317
|
return true;
|
|
1083
1318
|
return false;
|
|
1084
1319
|
}
|
|
1320
|
+
updateWorldSpans(result) {
|
|
1321
|
+
let opened = false;
|
|
1322
|
+
const suppressStatic = this.config.detectionSettings.object.suppressStatic ?? true;
|
|
1323
|
+
for (const event of result.events) {
|
|
1324
|
+
const obj = event.object;
|
|
1325
|
+
const recoveredRelevant = event.eventType === 'objectRecovered' && (obj.state !== 'stationary' || !suppressStatic);
|
|
1326
|
+
if (event.eventType === 'objectEntered' || event.eventType === 'objectWoke' || recoveredRelevant) {
|
|
1327
|
+
if (!this.worldSpans.has(obj.trackId))
|
|
1328
|
+
opened = true;
|
|
1329
|
+
this.worldSpans.add(obj.trackId);
|
|
1330
|
+
}
|
|
1331
|
+
else if (event.eventType === 'objectDeparted' || event.eventType === 'objectLost' || (event.eventType === 'objectSettled' && suppressStatic)) {
|
|
1332
|
+
this.worldSpans.delete(obj.trackId);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return opened;
|
|
1336
|
+
}
|
|
1337
|
+
async captureMoments(result, analysis, at) {
|
|
1338
|
+
let subject;
|
|
1339
|
+
let bestScore = -1;
|
|
1340
|
+
let trigger = '';
|
|
1341
|
+
for (const event of result.events) {
|
|
1342
|
+
const obj = event.object;
|
|
1343
|
+
// a moment is worth a picture when a span opens or the view got better;
|
|
1344
|
+
// closing events report absence, there is nothing to show
|
|
1345
|
+
if (!MOMENT_EVENTS.has(event.eventType))
|
|
1346
|
+
continue;
|
|
1347
|
+
// and only for a track that holds a span: a tentative first sighting or a
|
|
1348
|
+
// stationary recovery opens nothing, so its picture is never handed over
|
|
1349
|
+
if (!this.worldSpans.has(obj.trackId))
|
|
1350
|
+
continue;
|
|
1351
|
+
const score = obj.confidence * Math.sqrt(obj.width * obj.height);
|
|
1352
|
+
if (score > bestScore) {
|
|
1353
|
+
bestScore = score;
|
|
1354
|
+
subject = obj;
|
|
1355
|
+
trigger = event.eventType;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
// one frame per tick, not one per object: they share the picture
|
|
1359
|
+
if (!subject)
|
|
1360
|
+
return;
|
|
1361
|
+
if (!this.eventManager.wantsMoment(MOMENT_RANK_OBJECT, bestScore, analysis.isMainStream ? 'main' : 'low', at))
|
|
1362
|
+
return;
|
|
1363
|
+
await this.captureMoment(subject, bestScore, trigger, result.tracked, analysis, at);
|
|
1364
|
+
}
|
|
1365
|
+
async captureMoment(subject, score, trigger, tracked, analysis, at) {
|
|
1366
|
+
const box = { x: subject.x, y: subject.y, width: subject.width, height: subject.height };
|
|
1367
|
+
const moving = tracked.filter((d) => (d.trackSpeed ?? 0) >= MOMENT_MOVING_SPEED).map((d) => d.box);
|
|
1368
|
+
const target = { subject: box, base: unionBox([box, ...moving]), hint: directionOf(subject.velocityX, subject.velocityY) };
|
|
1369
|
+
const rendered = await this.renderMoment(target, analysis);
|
|
1370
|
+
if (!rendered)
|
|
1371
|
+
return;
|
|
1372
|
+
const stream = analysis.isMainStream ? 'main' : 'low';
|
|
1373
|
+
this.eventManager.offerMoment({ strip: rendered.strip, card: rendered.card, capturedAt: at, score, rank: MOMENT_RANK_OBJECT, stream });
|
|
1374
|
+
// debugging
|
|
1375
|
+
await this.recordMoment(target, rendered, analysis, `${subject.label}-${subject.trackId}`, score, trigger, at);
|
|
1376
|
+
}
|
|
1377
|
+
async captureAttributeMoment(results, analysis, at) {
|
|
1378
|
+
const candidates = [];
|
|
1379
|
+
// same rule as the object rank: only a span-holding subject hands over a
|
|
1380
|
+
// picture. A face or plate riding on a settled bystander (the parked car
|
|
1381
|
+
// at the kerb) must not anchor the segment of an unrelated event
|
|
1382
|
+
const spanHolder = (parentTrackId) => parentTrackId !== undefined && this.worldSpans.has(parentTrackId);
|
|
1383
|
+
for (const face of results.face?.detections ?? []) {
|
|
1384
|
+
const f = face;
|
|
1385
|
+
if (!spanHolder(f.parentTrackId))
|
|
1386
|
+
continue;
|
|
1387
|
+
candidates.push({ label: `face:${f.identity ?? 'unknown'}`, box: f.box, parentBox: f.parentBox, confidence: f.confidence });
|
|
1388
|
+
}
|
|
1389
|
+
for (const plate of results.licensePlate?.detections ?? []) {
|
|
1390
|
+
const p = plate;
|
|
1391
|
+
if (!p.plateText || !spanHolder(p.parentTrackId))
|
|
1392
|
+
continue;
|
|
1393
|
+
candidates.push({ label: `plate:${normalizePlateText(p.plateText)}`, box: p.box, parentBox: p.parentBox, confidence: p.confidence });
|
|
1394
|
+
}
|
|
1395
|
+
// one picture per tick, never one per subject: with several faces the most
|
|
1396
|
+
// legible one frames the shot, the rest stay their own attribute tiles
|
|
1397
|
+
let best;
|
|
1398
|
+
let bestScore = -1;
|
|
1399
|
+
let bestCard;
|
|
1400
|
+
for (const candidate of candidates) {
|
|
1401
|
+
const cut = results.thumbnails?.find((t) => t.label === candidate.label);
|
|
1402
|
+
// a subject cut off by the frame edge makes a poor card
|
|
1403
|
+
if (cut?.onEdge)
|
|
1404
|
+
continue;
|
|
1405
|
+
// a few-pixel face or plate carries no information even at the crop's max
|
|
1406
|
+
// upscale: it stays an attribute tile, the anchor keeps the object shot
|
|
1407
|
+
const areaPx = candidate.box.width * analysis.frame.width * candidate.box.height * analysis.frame.height;
|
|
1408
|
+
if (areaPx < MOMENT_ATTRIBUTE_MIN_AREA)
|
|
1409
|
+
continue;
|
|
1410
|
+
const score = candidate.confidence * Math.sqrt(candidate.box.width * candidate.box.height);
|
|
1411
|
+
if (score > bestScore) {
|
|
1412
|
+
bestScore = score;
|
|
1413
|
+
best = candidate;
|
|
1414
|
+
bestCard = cut?.jpeg;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
// ask before cutting: the same face rides along on every tick of a segment
|
|
1418
|
+
// and re-encoding for a picture that loses anyway is pure cost
|
|
1419
|
+
if (!best || !this.eventManager.wantsMoment(MOMENT_RANK_ATTRIBUTE, bestScore, analysis.isMainStream ? 'main' : 'low', at))
|
|
1420
|
+
return;
|
|
1421
|
+
const target = {
|
|
1422
|
+
subject: best.box,
|
|
1423
|
+
hint: best.parentBox ? directionBetween(best.box, best.parentBox) : undefined,
|
|
1424
|
+
};
|
|
1425
|
+
// the attribute tile was cut from this frame with this very target, so the
|
|
1426
|
+
// card is already encoded; only the 16:9 companion is still missing
|
|
1427
|
+
const rendered = await this.renderMoment(target, analysis, bestCard);
|
|
1428
|
+
if (!rendered)
|
|
1429
|
+
return;
|
|
1430
|
+
const stream = analysis.isMainStream ? 'main' : 'low';
|
|
1431
|
+
this.eventManager.offerMoment({ strip: rendered.strip, card: rendered.card, capturedAt: at, score: bestScore, rank: MOMENT_RANK_ATTRIBUTE, stream });
|
|
1432
|
+
// debugging
|
|
1433
|
+
await this.recordMoment(target, rendered, analysis, best.label.replace(':', '-'), bestScore, 'attributeFound', at);
|
|
1434
|
+
}
|
|
1435
|
+
async renderMoment(target, analysis, card) {
|
|
1436
|
+
const start = Date.now();
|
|
1437
|
+
const rendered = new Map();
|
|
1438
|
+
const windows = {};
|
|
1439
|
+
if (card)
|
|
1440
|
+
rendered.set('card', card);
|
|
1441
|
+
try {
|
|
1442
|
+
for (const format of MOMENT_FORMATS) {
|
|
1443
|
+
const window = momentWindow(target, analysis.frame.width, analysis.frame.height, format);
|
|
1444
|
+
if (!window)
|
|
1445
|
+
continue;
|
|
1446
|
+
windows[format.name] = window;
|
|
1447
|
+
if (rendered.has(format.name))
|
|
1448
|
+
continue;
|
|
1449
|
+
const jpeg = await analysis.scaler.cropWindowToJPEG(analysis.frame, window, format.width, format.height, MOMENT_QUALITY);
|
|
1450
|
+
if (jpeg)
|
|
1451
|
+
rendered.set(format.name, jpeg);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
catch (error) {
|
|
1455
|
+
this.logger.debug('Moment crop failed:', error);
|
|
1456
|
+
}
|
|
1457
|
+
this.perf.jpegMs += Date.now() - start;
|
|
1458
|
+
const strip = rendered.get('strip');
|
|
1459
|
+
return strip ? { strip, card: rendered.get('card'), windows } : null;
|
|
1460
|
+
}
|
|
1461
|
+
// debugging
|
|
1462
|
+
async recordMoment(target, rendered, analysis, subjectName, score, trigger, at) {
|
|
1463
|
+
if (!detectionRecord.active)
|
|
1464
|
+
return;
|
|
1465
|
+
const stream = analysis.isMainStream ? 'main' : 'low';
|
|
1466
|
+
const detail = `${subjectName}_${stream}_s${score.toFixed(3)}`;
|
|
1467
|
+
try {
|
|
1468
|
+
const frameJpeg = await analysis.scaler.frameToJPEG(analysis.frame, analysis.frame.width, 85);
|
|
1469
|
+
detectionRecord.moment({
|
|
1470
|
+
at,
|
|
1471
|
+
trigger,
|
|
1472
|
+
score,
|
|
1473
|
+
subject: target.subject,
|
|
1474
|
+
base: target.base,
|
|
1475
|
+
windows: rendered.windows,
|
|
1476
|
+
pictures: {
|
|
1477
|
+
strip: detectionRecord.picture(at, 'moment', `strip_${detail}`, rendered.strip),
|
|
1478
|
+
card: rendered.card ? detectionRecord.picture(at, 'moment', `card_${detail}`, rendered.card) : undefined,
|
|
1479
|
+
},
|
|
1480
|
+
frame: frameJpeg ? detectionRecord.picture(at, 'frame', detail, frameJpeg) : undefined,
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
catch (error) {
|
|
1484
|
+
this.logger.debug('Moment record failed:', error);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1085
1487
|
async scaleForMotion(rawFrame) {
|
|
1086
1488
|
const motionPlugin = this.plugins.get(SensorType.Motion);
|
|
1087
1489
|
if (!motionPlugin?.requiresFrames)
|
|
@@ -1116,24 +1518,23 @@ let DetectionCoordinator = class DetectionCoordinator {
|
|
|
1116
1518
|
this.objectIntervalSamples++;
|
|
1117
1519
|
if (this.objectIntervalSamples < CADENCE_MIN_SAMPLES)
|
|
1118
1520
|
return;
|
|
1119
|
-
this.pipeline.syncDetectionCadence(this.objectIntervalMs);
|
|
1120
1521
|
}
|
|
1121
|
-
async scaleForObject(rawFrame) {
|
|
1522
|
+
async scaleForObject(rawFrame, scaler) {
|
|
1122
1523
|
const objectPlugin = this.plugins.get(SensorType.Object);
|
|
1123
1524
|
if (!objectPlugin?.requiresFrames)
|
|
1124
1525
|
return undefined;
|
|
1125
|
-
return this.scaleFrameForPlugin(rawFrame, objectPlugin);
|
|
1526
|
+
return this.scaleFrameForPlugin(rawFrame, objectPlugin, scaler);
|
|
1126
1527
|
}
|
|
1127
|
-
async scaleFrameForPlugin(rawFrame, plugin) {
|
|
1528
|
+
async scaleFrameForPlugin(rawFrame, plugin, scaler = this.frameScaler) {
|
|
1128
1529
|
const inputSpec = plugin.modelSpec?.input;
|
|
1129
1530
|
if (!isVideoInputSpec(inputSpec))
|
|
1130
1531
|
return undefined;
|
|
1131
|
-
const letterboxed = await
|
|
1532
|
+
const letterboxed = await scaler.letterboxToSpec(rawFrame, inputSpec);
|
|
1132
1533
|
if (!letterboxed)
|
|
1133
1534
|
return undefined;
|
|
1134
1535
|
return {
|
|
1135
|
-
model:
|
|
1136
|
-
tracking:
|
|
1536
|
+
model: scaler.toVideoFrameData(letterboxed.padded, 'model'),
|
|
1537
|
+
tracking: scaler.toVideoFrameData(letterboxed.inner, 'tracking'),
|
|
1137
1538
|
geometry: letterboxed.geometry,
|
|
1138
1539
|
};
|
|
1139
1540
|
}
|