@camera.ui/server 2.1.7 → 2.1.9

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.
Files changed (425) hide show
  1. package/dist/interface/assets/{404-D0lfw52Y.js → 404-BEb5gvkW.js} +1 -1
  2. package/dist/interface/assets/{About-DaaVZ9Cn.js → About-BKkjuIVJ.js} +1 -1
  3. package/dist/interface/assets/Automation-DA89AEEI.js +3 -0
  4. package/dist/interface/assets/AutomationImport-hYr3Lp8F.js +2 -0
  5. package/dist/interface/assets/{AutomationStore-CeRF0iz6.js → AutomationStore-DKhnExZn.js} +2 -2
  6. package/dist/interface/assets/{AutomationStoreDetail-DcCFUBJf.js → AutomationStoreDetail-DmqqEOir.js} +2 -2
  7. package/dist/interface/assets/{Automations-GMWeSNQv.js → Automations-B6WFJyxS.js} +2 -2
  8. package/dist/interface/assets/Camera-DmsSKvpY.js +2 -0
  9. package/dist/interface/assets/{CameraConsole-B0crXI59.js → CameraConsole-DVpyGhx8.js} +1 -1
  10. package/dist/interface/assets/Cameras-KOOphtlx.js +652 -0
  11. package/dist/interface/assets/Camview-BPQIinyP.js +1 -0
  12. package/dist/interface/assets/{Camview-DxxONfeK.css → Camview-Lp3QU0de.css} +1 -1
  13. package/dist/interface/assets/Config-DeUec_TN.js +2 -0
  14. package/dist/interface/assets/ConfigActionCameraControl-DWHg-YEU.js +1 -0
  15. package/dist/interface/assets/{ConfigActionDelay-DjFp5qpw.js → ConfigActionDelay-C8nNA7TC.js} +1 -1
  16. package/dist/interface/assets/{ConfigActionHttp-Cmjz4iAV.js → ConfigActionHttp-Dc97gItH.js} +1 -1
  17. package/dist/interface/assets/{ConfigActionImageInput-B0Hll2M3.js → ConfigActionImageInput-DpOGqlzA.js} +1 -1
  18. package/dist/interface/assets/{ConfigActionMqtt-D2boY4u0.js → ConfigActionMqtt-D5DJ21oe.js} +1 -1
  19. package/dist/interface/assets/{ConfigActionNotification-7iRd0L5l.js → ConfigActionNotification-4wKfanGB.js} +1 -1
  20. package/dist/interface/assets/ConfigActionNotificationControl-jSgxCZmV.js +1 -0
  21. package/dist/interface/assets/{ConfigActionOutput-BTGUzNQW.js → ConfigActionOutput-C8Esieni.js} +1 -1
  22. package/dist/interface/assets/{ConfigActionPlugin-CzspRdJx.js → ConfigActionPlugin-C_cv60d3.js} +1 -1
  23. package/dist/interface/assets/{ConfigActionSensor-CPK-s1E2.js → ConfigActionSensor-CFIKoJ3-.js} +1 -1
  24. package/dist/interface/assets/{ConfigActionSnapshot-CcSw9Yw-.js → ConfigActionSnapshot-BqaTwtN3.js} +1 -1
  25. package/dist/interface/assets/{ConfigActionVariable-BCDtM13I.js → ConfigActionVariable-De9RCGU4.js} +1 -1
  26. package/dist/interface/assets/{ConfigConditionIfElse-DZJkBeHI.js → ConfigConditionIfElse-CeNDIucw.js} +1 -1
  27. package/dist/interface/assets/{ConfigConditionSensorState-CxWutq1T.js → ConfigConditionSensorState-C_Q9gU23.js} +1 -1
  28. package/dist/interface/assets/{ConfigConditionSwitch-DBRR9cW4.js → ConfigConditionSwitch-DR63IGcD.js} +1 -1
  29. package/dist/interface/assets/{ConfigConditionTime-B-5s6QW2.js → ConfigConditionTime-C9iuSxxe.js} +1 -1
  30. package/dist/interface/assets/{ConfigSensorValueInput-B9MmHF-E.js → ConfigSensorValueInput-DnMlFzil.js} +1 -1
  31. package/dist/interface/assets/{ConfigTriggerDetection-DlY8ne3V.js → ConfigTriggerDetection-iThbCKOO.js} +1 -1
  32. package/dist/interface/assets/ConfigTriggerGeofence-BeKwVWvS.js +1 -0
  33. package/dist/interface/assets/{ConfigTriggerManual-BSxA9fGt.js → ConfigTriggerManual-DPkRlUqD.js} +1 -1
  34. package/dist/interface/assets/{ConfigTriggerMqtt-C7sFSgYi.js → ConfigTriggerMqtt-kd65tGJE.js} +1 -1
  35. package/dist/interface/assets/{ConfigTriggerSchedule-fqVY5k3V.js → ConfigTriggerSchedule-D6MoVDPc.js} +1 -1
  36. package/dist/interface/assets/{ConfigTriggerSensor-BMZ74d8e.js → ConfigTriggerSensor-AMQM6f2r.js} +1 -1
  37. package/dist/interface/assets/{ConfigTriggerSystem-B8zwJpaY.js → ConfigTriggerSystem-CNBgtDF1.js} +1 -1
  38. package/dist/interface/assets/{ConfigTriggerWebhook-SklqcrZq.js → ConfigTriggerWebhook-B70DobSi.js} +1 -1
  39. package/dist/interface/assets/Console-N_2Vornl.js +1 -0
  40. package/dist/interface/assets/{CuiAutomationRunsDialog-N6NFABq2.js → CuiAutomationRunsDialog-Cp6Apki9.js} +1 -1
  41. package/dist/interface/assets/CuiBottomSheet-CIR_fmSk.js +1 -0
  42. package/dist/interface/assets/{CuiBottomSheet-BuK_lFkK.js → CuiBottomSheet-D5N8X1K-.js} +1 -1
  43. package/dist/interface/assets/CuiCameraPipCard-DP6X45oq.js +1 -0
  44. package/dist/interface/assets/CuiCameraRecordings-CRjXsdO4.js +1 -0
  45. package/dist/interface/assets/CuiCameraShares-DMHwZEf1.js +1 -0
  46. package/dist/interface/assets/{CuiCameraTable-DOPSHZ28.js → CuiCameraTable-AnEPu3XS.js} +1 -1
  47. package/dist/interface/assets/CuiCameraTable-D5ZqTLVt.js +1 -0
  48. package/dist/interface/assets/CuiConsole-CqEvNGe_.js +5 -0
  49. package/dist/interface/assets/{CuiDataTable-DBNU5MJ_.js → CuiDataTable-wA_LbHH8.js} +1 -1
  50. package/dist/interface/assets/{CuiLightControl-CCwkaMEn.js → CuiLightControl-CB_Tp4tI.js} +1 -1
  51. package/dist/interface/assets/{CuiList-BOMdGNXj.js → CuiList-DrYuFhK4.js} +1 -1
  52. package/dist/interface/assets/CuiPluginCard-DEuNt5ni.js +2 -0
  53. package/dist/interface/assets/CuiPluginCategoryChip-BbkbkYj6.js +1 -0
  54. package/dist/interface/assets/{CuiPluginOAuthButton-6HQLdlfg.js → CuiPluginOAuthButton-vMuRa9Qd.js} +2 -2
  55. package/dist/interface/assets/{CuiPluginTrustBadge-BS9iEcZr.js → CuiPluginTrustBadge-2wsPzYZA.js} +1 -1
  56. package/dist/interface/assets/CuiQRCode-Dh9EcaVc.js +1 -0
  57. package/dist/interface/assets/CuiRecordingsGrid-HjZEiSyp.js +1 -0
  58. package/dist/interface/assets/{CuiRecordingsGrid-Sv3uyj9a.css → CuiRecordingsGrid-Q4C3aeLo.css} +1 -1
  59. package/dist/interface/assets/CuiSchema-CSFoI6cg.js +1 -0
  60. package/dist/interface/assets/{CuiTopNavbarItem-MCdYsYHh.js → CuiTopNavbarItem-CSdvkKHR.js} +1 -1
  61. package/dist/interface/assets/{Faces-DDTbgPtz.js → Faces-CPZ5RA68.js} +1 -1
  62. package/dist/interface/assets/FirstSteps-CaF8STWH.js +1 -0
  63. package/dist/interface/assets/Instances-Cw7A4I7E.js +1 -0
  64. package/dist/interface/assets/{Logs-DEQlc2WM.js → Logs-4Mgj7YWu.js} +2 -2
  65. package/dist/interface/assets/{Menu-0EVo1-xs.js → Menu-T-54ZSSD.js} +1 -1
  66. package/dist/interface/assets/Metrics-CQGKmHev.css +1 -0
  67. package/dist/interface/assets/Metrics-Dgm5qpzh.js +6 -0
  68. package/dist/interface/assets/{OAuthDeviceFlow--YqcKa9u.js → OAuthDeviceFlow-DGikOkgK.js} +1 -1
  69. package/dist/interface/assets/Plugin-YxqbMbUc.js +1 -0
  70. package/dist/interface/assets/{PluginConsole-BB_dbd25.js → PluginConsole-ZZvJlgIh.js} +1 -1
  71. package/dist/interface/assets/PluginDetail-B9NgbzkJ.js +2 -0
  72. package/dist/interface/assets/{PluginSchema-C4dQGJOV.js → PluginSchema-Cx_LInpM.js} +1 -1
  73. package/dist/interface/assets/PluginSearch-5AgSWlmi.js +1 -0
  74. package/dist/interface/assets/{PluginSearch-gY-PHMyu.js → PluginSearch-P274oMvy.js} +2 -2
  75. package/dist/interface/assets/{PluginUpdateProgress-BjeH5abL.js → PluginUpdateProgress-0nV-8LvI.js} +1 -1
  76. package/dist/interface/assets/{Plugins-Kz1A-VUA.js → Plugins-Bvru3q2P.js} +1 -1
  77. package/dist/interface/assets/Recordings-BWGWC6Kq.js +1 -0
  78. package/dist/interface/assets/Recordings-cR4_6aGN.css +1 -0
  79. package/dist/interface/assets/{SensorEdit-CZyv_BYp.js → SensorEdit-gEw6HHZs.js} +1 -1
  80. package/dist/interface/assets/{SensorHistory-Mr03IqiL.js → SensorHistory-IgMpuLdC.js} +1 -1
  81. package/dist/interface/assets/Sensors-BUpob7LU.css +1 -0
  82. package/dist/interface/assets/Sensors-DukA8Xqx.js +2 -0
  83. package/dist/interface/assets/{Settings-CIv_iZ_m.js → Settings-DWpeWPJh.js} +1 -1
  84. package/dist/interface/assets/{Settings-B1MnGPuf.css → Settings-jQIhZFRY.css} +1 -1
  85. package/dist/interface/assets/SettingsAccount-C1OpCtob.js +1 -0
  86. package/dist/interface/assets/SettingsAccount-CrLcqzSn.js +1 -0
  87. package/dist/interface/assets/SettingsAppearance-AhUjml5E.js +1 -0
  88. package/dist/interface/assets/{SettingsAppearance-DEQyCR5b.js → SettingsAppearance-C8EEBVZN.js} +1 -1
  89. package/dist/interface/assets/{SettingsBackup-4y9MuseJ.js → SettingsBackup-BJupyZCJ.js} +1 -1
  90. package/dist/interface/assets/{SettingsList-DqyvUuS3.js → SettingsList-MXYSQMZ3.js} +1 -1
  91. package/dist/interface/assets/SettingsMqtt-F3pV1Bvo.js +1 -0
  92. package/dist/interface/assets/SettingsNotifications-BmaAccX9.js +1 -0
  93. package/dist/interface/assets/SettingsPermissions-Dd_yfNlk.js +1 -0
  94. package/dist/interface/assets/{SettingsRecordings-r9AwjQZX.js → SettingsRecordings-85B2bQIf.js} +1 -1
  95. package/dist/interface/assets/SettingsRemote-C_ikoXW7.js +2 -0
  96. package/dist/interface/assets/SettingsRemote-DzrfpHjw.css +1 -0
  97. package/dist/interface/assets/SettingsSystem-Ct3qms5O.js +2 -0
  98. package/dist/interface/assets/SettingsUsers-D36vZ8Zf.js +1 -0
  99. package/dist/interface/assets/{Terminal-Xo2uy0RX.js → Terminal-ltPiB9CB.js} +3 -3
  100. package/dist/interface/assets/{VariableInput-C3Jbe-vC.js → VariableInput-7c7Px-DM.js} +1 -1
  101. package/dist/interface/assets/{VariableSuggestions-pOrYkqys.js → VariableSuggestions-CrEa2zN-.js} +1 -1
  102. package/dist/interface/assets/{VersionsHandler-BhBynPF8.js → VersionsHandler-BFuLGk6Y.js} +1 -1
  103. package/dist/interface/assets/{VirtualSensorCreate-B3bwlAJz.js → VirtualSensorCreate-uumfOHgB.js} +1 -1
  104. package/dist/interface/assets/Workers-Bowrc1AF.js +2 -0
  105. package/dist/interface/assets/{api-B7yg8bdk.js → api-DLVsQBAO.js} +1 -1
  106. package/dist/interface/assets/api-daCggwpn.css +2 -0
  107. package/dist/interface/assets/api-py5HP4SF.js +2 -0
  108. package/dist/interface/assets/{auth-y-X5RKO6.js → auth-uQlIia9h.js} +1 -1
  109. package/dist/interface/assets/{automations-h1Wvnh36.js → automations-ChG1qcO7.js} +1 -1
  110. package/dist/interface/assets/cameras.schema-Cv7cjMz3.js +1 -0
  111. package/dist/interface/assets/cloudHandoff-CL4sircA.js +1 -0
  112. package/dist/interface/assets/{config-BZTZOfxI.js → config-B1aWtF-N.js} +1 -1
  113. package/dist/interface/assets/content-copy-CLkJTA4w.js +1 -0
  114. package/dist/interface/assets/eventLabels-BsPoPWS9.js +1 -0
  115. package/dist/interface/assets/{flowSchema-Dxxlrn77.js → flowSchema-CNnBxcwy.js} +1 -1
  116. package/dist/interface/assets/intercomService-DJHBix4X.js +1 -0
  117. package/dist/interface/assets/main-CLvcwWW7.js +3 -0
  118. package/dist/interface/assets/main-mJXdi_gx.css +1 -0
  119. package/dist/interface/assets/mqtt-Cyhv8shC.js +1 -0
  120. package/dist/interface/assets/{nodeDefinitions-DQQlFmPN.js → nodeDefinitions-c5rXs9EW.js} +1 -1
  121. package/dist/interface/assets/{notifications-C8eyTPRM.js → notifications-DniREnpL.js} +1 -1
  122. package/dist/interface/assets/{plugins-D0Txz0BA.js → plugins-5gXj0utE.js} +1 -1
  123. package/dist/interface/assets/round-alt-arrow-left-bold-CfccdiWp.js +1 -0
  124. package/dist/interface/assets/round-restart-alt-D3jT5i_T.js +1 -0
  125. package/dist/interface/assets/round-restart-alt-DdIfy7mU.css +1 -0
  126. package/dist/interface/assets/{sensorIcons-r5wmAxeD.js → sensorIcons-BXauWntX.js} +1 -1
  127. package/dist/interface/assets/{sensors-MIbrTUSa.js → sensors-Bc02Z1G9.js} +1 -1
  128. package/dist/interface/assets/{server-alwZXRo-.js → server-D2KGja0u.js} +1 -1
  129. package/dist/interface/assets/{theme-qmnHJ2WT.js → theme-vkX_uBgJ.js} +1 -1
  130. package/dist/interface/assets/types-CrvnT4-i.js +1 -0
  131. package/dist/interface/assets/types-D6IQ6ssc.js +1 -0
  132. package/dist/interface/assets/{types-DBWHsh0W.js → types-fCQkAGfW.js} +1 -1
  133. package/dist/interface/assets/useAppVersion-9zH_FspP.js +1 -0
  134. package/dist/interface/assets/useBackupRestore-Dbf24OqP.js +1 -0
  135. package/dist/interface/assets/{useCameraOptions-DYZKQx7X.js → useCameraOptions-BIaDXHeq.js} +1 -1
  136. package/dist/interface/assets/{useLogsSocket-DvwkpzT2.js → useLogsSocket-BLV7dWSG.js} +1 -1
  137. package/dist/interface/assets/{useNavLayout-CTX4KBkz.js → useNavLayout-wq-6JyWK.js} +1 -1
  138. package/dist/interface/assets/usePermissions-BiBlyeQl.js +1 -0
  139. package/dist/interface/assets/{usePluginUpdates-CiqRTrjF.js → usePluginUpdates-BZJSqnSF.js} +1 -1
  140. package/dist/interface/assets/usePushRegistration-CGFiyCuv.js +2 -0
  141. package/dist/interface/assets/{useServerRestart-Bqpn8N4T.js → useServerRestart-CjPAgzoX.js} +1 -1
  142. package/dist/interface/assets/{useStreamStatus-BWCnwuaj.js → useStreamStatus-BFCF1B3T.js} +1 -1
  143. package/dist/interface/assets/{useUpdateChannel-Cxm4QR-6.js → useUpdateChannel-F4WpbMJR.js} +1 -1
  144. package/dist/interface/assets/{useUserOptions-B2LNsU9C.js → useUserOptions-GbSC7_qt.js} +1 -1
  145. package/dist/interface/assets/{vendor-cameraui-C6beJsya.js → vendor-cameraui-0EJfDg8o.js} +56 -56
  146. package/dist/interface/assets/vendor-core-RnPzfwaA.js +1 -0
  147. package/dist/interface/assets/{vendor-primevue-DfyAPSov.js → vendor-primevue-b9CB9Uh0.js} +1 -1
  148. package/dist/interface/index.html +27 -27
  149. package/dist/interface/sw.js +1 -1
  150. package/dist/server/package.json +7 -7
  151. package/dist/server/requirements.txt +2 -2
  152. package/dist/server/src/_camera_ui_tools/camera_ui_rpc/client.py +8 -0
  153. package/dist/server/src/_camera_ui_tools/camera_ui_rpc/types.py +20 -0
  154. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/__init__.py +12 -4
  155. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/__init__.py +16 -4
  156. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/config.py +7 -10
  157. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/detection.py +96 -15
  158. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/device.py +7 -13
  159. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/enums.py +0 -7
  160. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/events.py +2 -0
  161. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/notification.py +28 -0
  162. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/__init__.py +4 -0
  163. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/base.py +18 -0
  164. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/spec.py +27 -3
  165. package/dist/server/src/api/controllers/auth.controller.js +5 -0
  166. package/dist/server/src/api/controllers/auth.controller.js.map +1 -1
  167. package/dist/server/src/api/controllers/cameras.controller.d.ts +2 -7
  168. package/dist/server/src/api/controllers/cameras.controller.js +2 -94
  169. package/dist/server/src/api/controllers/cameras.controller.js.map +1 -1
  170. package/dist/server/src/api/controllers/frameWorkers.controller.d.ts +14 -0
  171. package/dist/server/src/api/controllers/frameWorkers.controller.js +120 -1
  172. package/dist/server/src/api/controllers/frameWorkers.controller.js.map +1 -1
  173. package/dist/server/src/api/controllers/remote.controller.js +3 -0
  174. package/dist/server/src/api/controllers/remote.controller.js.map +1 -1
  175. package/dist/server/src/api/controllers/server.controller.js +1 -0
  176. package/dist/server/src/api/controllers/server.controller.js.map +1 -1
  177. package/dist/server/src/api/database/constants.d.ts +0 -1
  178. package/dist/server/src/api/database/constants.js +0 -1
  179. package/dist/server/src/api/database/constants.js.map +1 -1
  180. package/dist/server/src/api/database/index.d.ts +2 -3
  181. package/dist/server/src/api/database/index.js +3 -6
  182. package/dist/server/src/api/database/index.js.map +1 -1
  183. package/dist/server/src/api/database/migrations/v2-1-10.js +33 -0
  184. package/dist/server/src/api/database/migrations/v2-1-10.js.map +1 -0
  185. package/dist/server/src/api/database/migrations/v2-1-11.js +31 -0
  186. package/dist/server/src/api/database/migrations/v2-1-11.js.map +1 -0
  187. package/dist/server/src/api/database/migrations/v2-1-12.js +25 -0
  188. package/dist/server/src/api/database/migrations/v2-1-12.js.map +1 -0
  189. package/dist/server/src/api/database/migrations/v2-1-6.d.ts +3 -0
  190. package/dist/server/src/api/database/migrations/v2-1-6.js +62 -0
  191. package/dist/server/src/api/database/migrations/v2-1-6.js.map +1 -0
  192. package/dist/server/src/api/database/migrations/v2-1-7.d.ts +3 -0
  193. package/dist/server/src/api/database/migrations/v2-1-7.js +44 -0
  194. package/dist/server/src/api/database/migrations/v2-1-7.js.map +1 -0
  195. package/dist/server/src/api/database/migrations/v2-1-9.d.ts +3 -0
  196. package/dist/server/src/api/database/migrations/v2-1-9.js +38 -0
  197. package/dist/server/src/api/database/migrations/v2-1-9.js.map +1 -0
  198. package/dist/server/src/api/database/record-schemas.d.ts +18 -13
  199. package/dist/server/src/api/database/record-schemas.js +3 -3
  200. package/dist/server/src/api/database/record-schemas.js.map +1 -1
  201. package/dist/server/src/api/database/types.d.ts +4 -11
  202. package/dist/server/src/api/routes/cameras.routes.js +5 -62
  203. package/dist/server/src/api/routes/cameras.routes.js.map +1 -1
  204. package/dist/server/src/api/routes/frameWorkers.routes.js +22 -1
  205. package/dist/server/src/api/routes/frameWorkers.routes.js.map +1 -1
  206. package/dist/server/src/api/schemas/cameras.schema.d.ts +466 -154
  207. package/dist/server/src/api/schemas/cameras.schema.js +85 -43
  208. package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
  209. package/dist/server/src/api/schemas/frameWorkers.schema.d.ts +6 -0
  210. package/dist/server/src/api/schemas/frameWorkers.schema.js +7 -0
  211. package/dist/server/src/api/schemas/frameWorkers.schema.js.map +1 -1
  212. package/dist/server/src/api/schemas/server.schema.d.ts +1 -0
  213. package/dist/server/src/api/schemas/server.schema.js +4 -0
  214. package/dist/server/src/api/schemas/server.schema.js.map +1 -1
  215. package/dist/server/src/api/schemas/users.schema.d.ts +119 -84
  216. package/dist/server/src/api/schemas/users.schema.js +3 -0
  217. package/dist/server/src/api/schemas/users.schema.js.map +1 -1
  218. package/dist/server/src/api/services/cameras.service.d.ts +2 -4
  219. package/dist/server/src/api/services/cameras.service.js +2 -32
  220. package/dist/server/src/api/services/cameras.service.js.map +1 -1
  221. package/dist/server/src/api/types/index.d.ts +4 -9
  222. package/dist/server/src/api/types/index.js.map +1 -1
  223. package/dist/server/src/api/websocket/nsp/metrics.d.ts +1 -1
  224. package/dist/server/src/api/websocket/nsp/metrics.js +54 -33
  225. package/dist/server/src/api/websocket/nsp/metrics.js.map +1 -1
  226. package/dist/server/src/api/websocket/nsp/server.js +2 -2
  227. package/dist/server/src/api/websocket/nsp/server.js.map +1 -1
  228. package/dist/server/src/api/websocket/types.d.ts +20 -17
  229. package/dist/server/src/automations/actions/cameraControl.js +15 -1
  230. package/dist/server/src/automations/actions/cameraControl.js.map +1 -1
  231. package/dist/server/src/automations/actions/notificationControl.js +10 -1
  232. package/dist/server/src/automations/actions/notificationControl.js.map +1 -1
  233. package/dist/server/src/camera/controller.d.ts +1 -0
  234. package/dist/server/src/camera/controller.js +22 -8
  235. package/dist/server/src/camera/controller.js.map +1 -1
  236. package/dist/server/src/camera/decoder/audio-loop.d.ts +0 -1
  237. package/dist/server/src/camera/decoder/audio-loop.js +38 -68
  238. package/dist/server/src/camera/decoder/audio-loop.js.map +1 -1
  239. package/dist/server/src/camera/decoder/child.d.ts +6 -5
  240. package/dist/server/src/camera/decoder/child.js +31 -22
  241. package/dist/server/src/camera/decoder/child.js.map +1 -1
  242. package/dist/server/src/camera/decoder/detection-coordinator.d.ts +12 -10
  243. package/dist/server/src/camera/decoder/detection-coordinator.js +274 -163
  244. package/dist/server/src/camera/decoder/detection-coordinator.js.map +1 -1
  245. package/dist/server/src/camera/decoder/detection-pipeline.d.ts +12 -4
  246. package/dist/server/src/camera/decoder/detection-pipeline.js +71 -14
  247. package/dist/server/src/camera/decoder/detection-pipeline.js.map +1 -1
  248. package/dist/server/src/camera/decoder/event-manager.d.ts +2 -0
  249. package/dist/server/src/camera/decoder/event-manager.js +14 -0
  250. package/dist/server/src/camera/decoder/event-manager.js.map +1 -1
  251. package/dist/server/src/camera/decoder/event-thumbnailer.d.ts +4 -2
  252. package/dist/server/src/camera/decoder/event-thumbnailer.js +3 -3
  253. package/dist/server/src/camera/decoder/event-thumbnailer.js.map +1 -1
  254. package/dist/server/src/camera/decoder/frame-scaler.d.ts +10 -3
  255. package/dist/server/src/camera/decoder/frame-scaler.js +76 -13
  256. package/dist/server/src/camera/decoder/frame-scaler.js.map +1 -1
  257. package/dist/server/src/camera/decoder/perf-tracker.d.ts +27 -8
  258. package/dist/server/src/camera/decoder/perf-tracker.js +72 -50
  259. package/dist/server/src/camera/decoder/perf-tracker.js.map +1 -1
  260. package/dist/server/src/camera/decoder/plugin-registry.d.ts +1 -0
  261. package/dist/server/src/camera/decoder/plugin-registry.js +7 -0
  262. package/dist/server/src/camera/decoder/plugin-registry.js.map +1 -1
  263. package/dist/server/src/camera/decoder/ptz/autotracker.d.ts +4 -0
  264. package/dist/server/src/camera/decoder/ptz/autotracker.js +97 -18
  265. package/dist/server/src/camera/decoder/ptz/autotracker.js.map +1 -1
  266. package/dist/server/src/camera/decoder/secondary-stage.d.ts +4 -1
  267. package/dist/server/src/camera/decoder/secondary-stage.js +26 -3
  268. package/dist/server/src/camera/decoder/secondary-stage.js.map +1 -1
  269. package/dist/server/src/camera/decoder/sources/analysis-source.d.ts +14 -1
  270. package/dist/server/src/camera/decoder/sources/audio-source.d.ts +7 -3
  271. package/dist/server/src/camera/decoder/sources/audio-source.js +89 -78
  272. package/dist/server/src/camera/decoder/sources/audio-source.js.map +1 -1
  273. package/dist/server/src/camera/decoder/sources/buffered-source.d.ts +28 -9
  274. package/dist/server/src/camera/decoder/sources/buffered-source.js +114 -66
  275. package/dist/server/src/camera/decoder/sources/buffered-source.js.map +1 -1
  276. package/dist/server/src/camera/decoder/sources/frame-handle.d.ts +13 -0
  277. package/dist/server/src/camera/decoder/sources/frame-handle.js +106 -0
  278. package/dist/server/src/camera/decoder/sources/frame-handle.js.map +1 -0
  279. package/dist/server/src/camera/decoder/sources/frame-source.d.ts +25 -26
  280. package/dist/server/src/camera/decoder/sources/frame-source.js +213 -268
  281. package/dist/server/src/camera/decoder/sources/frame-source.js.map +1 -1
  282. package/dist/server/src/camera/decoder/sources/reconnect-loop.d.ts +17 -0
  283. package/dist/server/src/camera/decoder/sources/reconnect-loop.js +59 -0
  284. package/dist/server/src/camera/decoder/sources/reconnect-loop.js.map +1 -0
  285. package/dist/server/src/camera/decoder/sources/snapshot-fetcher.d.ts +15 -0
  286. package/dist/server/src/camera/decoder/sources/snapshot-fetcher.js +58 -0
  287. package/dist/server/src/camera/decoder/sources/snapshot-fetcher.js.map +1 -0
  288. package/dist/server/src/camera/decoder/sources/video-input.d.ts +18 -0
  289. package/dist/server/src/camera/decoder/sources/video-input.js +55 -0
  290. package/dist/server/src/camera/decoder/sources/video-input.js.map +1 -0
  291. package/dist/server/src/camera/decoder/types.d.ts +74 -18
  292. package/dist/server/src/camera/decoder/types.js +5 -0
  293. package/dist/server/src/camera/decoder/types.js.map +1 -1
  294. package/dist/server/src/camera/decoder/worker.d.ts +5 -1
  295. package/dist/server/src/camera/decoder/worker.js +39 -29
  296. package/dist/server/src/camera/decoder/worker.js.map +1 -1
  297. package/dist/server/src/camera/index.d.ts +3 -4
  298. package/dist/server/src/camera/index.js +6 -8
  299. package/dist/server/src/camera/index.js.map +1 -1
  300. package/dist/server/src/camera/privacy/mask.d.ts +27 -0
  301. package/dist/server/src/camera/privacy/mask.js +204 -0
  302. package/dist/server/src/camera/privacy/mask.js.map +1 -0
  303. package/dist/server/src/camera/privacy/snapshot.d.ts +13 -0
  304. package/dist/server/src/camera/privacy/snapshot.js +126 -0
  305. package/dist/server/src/camera/privacy/snapshot.js.map +1 -0
  306. package/dist/server/src/camera/streaming/fmp4-session.js +1 -1
  307. package/dist/server/src/camera/streaming/fmp4-session.js.map +1 -1
  308. package/dist/server/src/camera/streaming/rtp-session.js +1 -1
  309. package/dist/server/src/camera/streaming/rtp-session.js.map +1 -1
  310. package/dist/server/src/camera/utils/filter.d.ts +3 -3
  311. package/dist/server/src/camera/utils/filter.js.map +1 -1
  312. package/dist/server/src/camera/zones.d.ts +2 -0
  313. package/dist/server/src/camera/zones.js +11 -0
  314. package/dist/server/src/camera/zones.js.map +1 -0
  315. package/dist/server/src/manager/notificationManager.d.ts +2 -0
  316. package/dist/server/src/manager/notificationManager.js +21 -2
  317. package/dist/server/src/manager/notificationManager.js.map +1 -1
  318. package/dist/server/src/mqtt/bridge.d.ts +0 -1
  319. package/dist/server/src/mqtt/bridge.js +0 -33
  320. package/dist/server/src/mqtt/bridge.js.map +1 -1
  321. package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js +9 -0
  322. package/dist/server/src/plugins/runtime/node/proxy/cameraDevice.js.map +1 -1
  323. package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js +8 -0
  324. package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js.map +1 -1
  325. package/dist/server/src/plugins/runtime/python/proxy/camera_device.py +22 -13
  326. package/dist/server/src/plugins/runtime/python/proxy/sensor.py +1 -0
  327. package/dist/server/src/plugins/runtime/python/proxy/sensor_manager.py +13 -0
  328. package/dist/server/src/plugins/schema.js +1 -1
  329. package/dist/server/src/plugins/schema.js.map +1 -1
  330. package/dist/server/src/remote/services/cloudflare-managed.d.ts +2 -0
  331. package/dist/server/src/remote/services/cloudflare-managed.js +9 -2
  332. package/dist/server/src/remote/services/cloudflare-managed.js.map +1 -1
  333. package/dist/server/src/remote/services/cloudflare.d.ts +3 -0
  334. package/dist/server/src/remote/services/cloudflare.js +11 -0
  335. package/dist/server/src/remote/services/cloudflare.js.map +1 -1
  336. package/dist/server/src/remote/services/index.d.ts +2 -0
  337. package/dist/server/src/remote/services/index.js +60 -8
  338. package/dist/server/src/remote/services/index.js.map +1 -1
  339. package/dist/server/src/remote/services/tunnelConnections.d.ts +9 -0
  340. package/dist/server/src/remote/services/tunnelConnections.js +45 -0
  341. package/dist/server/src/remote/services/tunnelConnections.js.map +1 -0
  342. package/dist/server/src/remote/services/urlReadiness.d.ts +2 -0
  343. package/dist/server/src/remote/services/urlReadiness.js +7 -4
  344. package/dist/server/src/remote/services/urlReadiness.js.map +1 -1
  345. package/dist/server/src/rpc/interfaces/frameworker.d.ts +6 -5
  346. package/dist/server/src/rpc/interfaces/sensor.d.ts +1 -0
  347. package/dist/server/src/sensors/registry.d.ts +2 -0
  348. package/dist/server/src/sensors/registry.js +16 -0
  349. package/dist/server/src/sensors/registry.js.map +1 -1
  350. package/dist/server/src/utils/plugin-registry/index.js +1 -1
  351. package/dist/server/src/utils/plugin-registry/index.js.map +1 -1
  352. package/dist/server/src/workers/agent.js +1 -1
  353. package/dist/server/src/workers/agent.js.map +1 -1
  354. package/package.json +7 -7
  355. package/dist/interface/assets/Automation-DQyPNAzK.js +0 -3
  356. package/dist/interface/assets/AutomationImport-BDD_LuC1.js +0 -2
  357. package/dist/interface/assets/Camera-CYxRoESX.js +0 -2
  358. package/dist/interface/assets/Cameras-V_twilcC.js +0 -652
  359. package/dist/interface/assets/Camview-CSb2iVBf.js +0 -1
  360. package/dist/interface/assets/Config-BKuXgZxF.js +0 -2
  361. package/dist/interface/assets/ConfigActionCameraControl-TFe1_K6j.js +0 -1
  362. package/dist/interface/assets/ConfigActionNotificationControl-DI_t2wHh.js +0 -1
  363. package/dist/interface/assets/ConfigTriggerGeofence-DJt-3FpV.js +0 -1
  364. package/dist/interface/assets/Console-BZB3MZKj.js +0 -1
  365. package/dist/interface/assets/CuiBottomSheet-BnKYWBBA.js +0 -1
  366. package/dist/interface/assets/CuiCameraPipCard-DaJlFY1g.js +0 -1
  367. package/dist/interface/assets/CuiCameraRecordings-z4WblFJ9.js +0 -1
  368. package/dist/interface/assets/CuiCameraShares-CvKMe_kX.js +0 -1
  369. package/dist/interface/assets/CuiCameraTable-_7KwbPyM.js +0 -1
  370. package/dist/interface/assets/CuiConsole-BOfQ5I3b.js +0 -5
  371. package/dist/interface/assets/CuiPluginCard-CvNfBRhG.js +0 -2
  372. package/dist/interface/assets/CuiPluginCategoryChip-JG_8D7Wt.js +0 -1
  373. package/dist/interface/assets/CuiQRCode-Vd-1ssJK.js +0 -1
  374. package/dist/interface/assets/CuiRecordingsGrid-skKnK074.js +0 -1
  375. package/dist/interface/assets/CuiSchema-DvY3Bdnz.js +0 -1
  376. package/dist/interface/assets/FirstSteps-T4ra4jxE.js +0 -1
  377. package/dist/interface/assets/Instances-D_Td0oJ-.js +0 -1
  378. package/dist/interface/assets/Metrics-BstlZavQ.css +0 -1
  379. package/dist/interface/assets/Metrics-FFvFP2kR.js +0 -1
  380. package/dist/interface/assets/Plugin-CKcptqV9.js +0 -1
  381. package/dist/interface/assets/PluginDetail-Y_jg8hAX.js +0 -2
  382. package/dist/interface/assets/PluginSearch-D2efYsof.js +0 -1
  383. package/dist/interface/assets/Recordings-80Xd1V3L.css +0 -1
  384. package/dist/interface/assets/Recordings-BlBmyZlM.js +0 -1
  385. package/dist/interface/assets/Sensors-B-Qhfr5i.css +0 -1
  386. package/dist/interface/assets/Sensors-CHbtecpf.js +0 -2
  387. package/dist/interface/assets/SettingsAccount-DA67jXmp.js +0 -1
  388. package/dist/interface/assets/SettingsAccount-DPPwXOlM.js +0 -1
  389. package/dist/interface/assets/SettingsAppearance-C92Nlobb.js +0 -1
  390. package/dist/interface/assets/SettingsMqtt-CuTZBTrA.js +0 -1
  391. package/dist/interface/assets/SettingsNotifications-D7Vs6NoU.js +0 -1
  392. package/dist/interface/assets/SettingsPermissions-C_8g_nff.js +0 -1
  393. package/dist/interface/assets/SettingsRemote-DYld6-1F.css +0 -1
  394. package/dist/interface/assets/SettingsRemote-oDVzvhmy.js +0 -1
  395. package/dist/interface/assets/SettingsSystem-QfBIPq4i.js +0 -2
  396. package/dist/interface/assets/SettingsUsers-CqRJ8-7H.js +0 -1
  397. package/dist/interface/assets/Workers-tgzMqP3o.js +0 -2
  398. package/dist/interface/assets/api-1K2Gzz8B.js +0 -2
  399. package/dist/interface/assets/api-Bj4QRftG.css +0 -2
  400. package/dist/interface/assets/cameras.schema-CKnsFYBP.js +0 -1
  401. package/dist/interface/assets/cctv-BA5DvNlE.js +0 -1
  402. package/dist/interface/assets/cloudHandoff-qezHiAZI.js +0 -1
  403. package/dist/interface/assets/main-Ei2ORV9y.js +0 -3
  404. package/dist/interface/assets/main-kdyR9bI7.css +0 -1
  405. package/dist/interface/assets/mqtt-CCq5JGGk.js +0 -1
  406. package/dist/interface/assets/round-alt-arrow-left-bold-BxX4ufd0.js +0 -1
  407. package/dist/interface/assets/round-restart-alt-Bw-T1H6w.js +0 -1
  408. package/dist/interface/assets/round-restart-alt-CmrjVFat.css +0 -1
  409. package/dist/interface/assets/types-BTVMzSuj.js +0 -1
  410. package/dist/interface/assets/types-vaT6wuY0.js +0 -1
  411. package/dist/interface/assets/useAppVersion-BRc6OWZP.js +0 -1
  412. package/dist/interface/assets/useBackupRestore-DCO_Pw9q.js +0 -1
  413. package/dist/interface/assets/useElectron-CtslU_0f.js +0 -1
  414. package/dist/interface/assets/usePermissions-DpsfFydh.js +0 -1
  415. package/dist/interface/assets/usePushRegistration-Coz8RZob.js +0 -2
  416. package/dist/interface/assets/vendor-core-_ft7qIgH.js +0 -1
  417. package/dist/server/src/api/database/migrations/v2-0-50.js +0 -45
  418. package/dist/server/src/api/database/migrations/v2-0-50.js.map +0 -1
  419. package/dist/server/src/api/database/migrations/v2-0-51.js +0 -43
  420. package/dist/server/src/api/database/migrations/v2-0-51.js.map +0 -1
  421. package/dist/server/src/api/database/migrations/v2-1-0.js +0 -72
  422. package/dist/server/src/api/database/migrations/v2-1-0.js.map +0 -1
  423. /package/dist/server/src/api/database/migrations/{v2-0-50.d.ts → v2-1-10.d.ts} +0 -0
  424. /package/dist/server/src/api/database/migrations/{v2-0-51.d.ts → v2-1-11.d.ts} +0 -0
  425. /package/dist/server/src/api/database/migrations/{v2-1-0.d.ts → v2-1-12.d.ts} +0 -0
@@ -12,7 +12,9 @@ import { isNoRespondersError, RPCClass, RPCMethod } from '@camera.ui/rpc';
12
12
  import { SensorType } from '@camera.ui/sdk';
13
13
  import { NamespaceManager } from '../../rpc/namespaces.js';
14
14
  import { DETECTION_SENSOR_TYPES } from '../../sensors/types.js';
15
- import { normalizePolygon, normalizeZone } from '../utils/filter.js';
15
+ import { PrivacyMask } from '../privacy/mask.js';
16
+ import { normalizePolygon } from '../utils/filter.js';
17
+ import { normalizeZones } from '../zones.js';
16
18
  import { AudioDetectionLoop } from './audio-loop.js';
17
19
  import { CascadeManager } from './cascade-manager.js';
18
20
  import { detectionRecord } from './debug/detection-record.js';
@@ -24,12 +26,12 @@ import { FrameScaler } from './frame-scaler.js';
24
26
  import { directionBetween, directionOf, MOMENT_FORMATS, MOMENT_QUALITY, momentWindow, unionBox } from './moment-crop.js';
25
27
  import { PerfTracker } from './perf-tracker.js';
26
28
  import { normalizePlateText } from './plate-vote.js';
27
- import { isVideoInputSpec, PluginRegistry } from './plugin-registry.js';
29
+ import { isVideoInputSpec, modelIdentity, PluginRegistry } from './plugin-registry.js';
28
30
  import { PtzAutotracker } from './ptz/autotracker.js';
29
- import { ReconnectBackoff } from './reconnect-backoff.js';
30
31
  import { SecondaryStage } from './secondary-stage.js';
32
+ import { BufferedSource } from './sources/buffered-source.js';
31
33
  import { FrameSource } from './sources/frame-source.js';
32
- import { DETECT_TIMEOUT_MS, ensureDetectionBoxes, MOTION_WIDTH_MAP } from './types.js';
34
+ import { DETECT_TIMEOUT_MS, DETECTOR_METRIC_TYPES, ensureDetectionBoxes, isFullFrameBox, MOTION_WIDTH_MAP } from './types.js';
33
35
  const MOMENT_EVENTS = new Set(['objectEntered', 'objectWoke', 'objectRecovered', 'bestShotUpdated']);
34
36
  const MOMENT_MOVING_SPEED = 0.05;
35
37
  const MOMENT_ATTRIBUTE_MIN_AREA = 600;
@@ -44,6 +46,7 @@ const ACTIVE_TICK_MS = 100;
44
46
  const MOTION_INTERVAL_MS = 200;
45
47
  const TICK_SLACK_MS = 20;
46
48
  const MAIN_STREAM_HOLD_MS = 5000;
49
+ const EXTERNAL_FRAME_MAX_AGE_MS = 1000;
47
50
  let DetectionCoordinator = class DetectionCoordinator {
48
51
  config;
49
52
  proxy;
@@ -66,9 +69,10 @@ let DetectionCoordinator = class DetectionCoordinator {
66
69
  classifierLabels = new Map();
67
70
  feedingSensors = new Map();
68
71
  dwell = new DwellManager();
69
- videoBackoff = new ReconnectBackoff();
72
+ privacy;
70
73
  // debugging
71
74
  perf = new PerfTracker();
75
+ benchmarkRunning = false;
72
76
  hqUpgrade;
73
77
  mainStreamActive = false;
74
78
  idleSince = 0;
@@ -89,27 +93,32 @@ let DetectionCoordinator = class DetectionCoordinator {
89
93
  this.config = config;
90
94
  this.proxy = proxy;
91
95
  this.logger = logger;
92
- const frameSourceConfig = {
93
- streamUrl: config.streamUrl,
94
- snapshotUrl: config.snapshotUrl,
95
- decoder: this.config.frameWorkerSettings.decoder,
96
- };
96
+ const snapshot = { snapshotUrl: config.snapshotUrl };
97
97
  if (config.controllerSnapshotSourceId) {
98
98
  const sourceId = config.controllerSnapshotSourceId;
99
99
  const controllerProxy = this.proxy.createProxy(NamespaceManager.cameraNamespaces(config.cameraId).cameraControllerRpc);
100
- frameSourceConfig.snapshotProvider = async () => {
100
+ snapshot.snapshotProvider = async () => {
101
101
  const jpeg = await controllerProxy.snapshot(sourceId, true, true);
102
102
  if (!jpeg || jpeg.byteLength === 0)
103
103
  return null;
104
104
  return Buffer.from(jpeg);
105
105
  };
106
106
  }
107
- this.frameSource = new FrameSource(frameSourceConfig, logger);
108
- this.frameScaler = new FrameScaler(null, logger);
109
- this.pipeline = new DetectionPipeline(config.zones, config.detectionSettings);
110
- this.secondaries = new SecondaryStage(this, this.plugins, this.pipeline, this.frameScaler, this.proxy, logger);
111
- if (config.lines.length > 0) {
112
- this.pipeline.updateLines(config.lines, this.videoAspectRatio);
107
+ this.config.zones = normalizeZones(config.zones);
108
+ this.privacy = new PrivacyMask(logger);
109
+ this.privacy.update(this.config.zones);
110
+ const decoder = this.config.frameWorkerSettings.decoder;
111
+ this.frameSource = config.streamHot
112
+ ? new BufferedSource({ url: config.streamUrl, decoder, privacy: this.privacy, snapshot }, logger)
113
+ : new FrameSource({ streamUrl: config.streamUrl, decoder, ...snapshot }, logger);
114
+ // a hot source buffers from the start, the loop only decides what is analysed
115
+ if (config.streamHot)
116
+ this.frameSource.start();
117
+ this.frameScaler = new FrameScaler(null, logger, this.privacy);
118
+ this.pipeline = new DetectionPipeline(this.pipelineZones(), config.detectionSettings);
119
+ this.secondaries = new SecondaryStage(this, this.plugins, this.pipeline, this.frameScaler, this.proxy, this.perf, logger);
120
+ if (config.zones.lines.length > 0) {
121
+ this.pipeline.updateLines(config.zones.lines, this.videoAspectRatio);
113
122
  }
114
123
  this.eventManager = new DetectionEventManager(config.cameraId, this.proxy, this.logger);
115
124
  this.eventManager.updateNvrRpc(config.nvrRpc);
@@ -122,6 +131,7 @@ let DetectionCoordinator = class DetectionCoordinator {
122
131
  this.thumbnailer = new EventThumbnailer({
123
132
  frameSource: this.frameSource,
124
133
  frameScaler: this.frameScaler,
134
+ privacy: this.privacy,
125
135
  eventManager: this.eventManager,
126
136
  logger,
127
137
  decoder: this.config.frameWorkerSettings.decoder,
@@ -194,18 +204,93 @@ let DetectionCoordinator = class DetectionCoordinator {
194
204
  return this.config.detectionSettings;
195
205
  }
196
206
  getPerfSnapshot() {
197
- return { ...this.perf.snapshot(), mainStreamEnabled: this.mainStreamAvailable, frameAnalysis: this.plugins.shouldVideoBeActive() };
198
- }
199
- updateZones(zones) {
200
- this.config.zones = zones;
201
- this.pipeline.updateZones(zones);
207
+ const { timings, ...counters } = this.perf.snapshot();
208
+ const detectors = {};
209
+ for (const type of DETECTOR_METRIC_TYPES) {
210
+ const plugin = this.plugins.get(type);
211
+ // motion without frames is the camera's own signal, there is no detector to report
212
+ if (!plugin || (type === SensorType.Motion && !plugin.requiresFrames))
213
+ continue;
214
+ const input = plugin.modelSpec?.input;
215
+ detectors[type] = {
216
+ plugin: plugin.pluginId,
217
+ input: isVideoInputSpec(input) ? `${input.width}x${input.height}` : undefined,
218
+ runtime: plugin.modelSpec?.runtime,
219
+ models: plugin.modelSpec?.models,
220
+ ...timings[type],
221
+ };
222
+ }
223
+ return {
224
+ ...counters,
225
+ detectors,
226
+ mainStreamEnabled: this.mainStreamAvailable,
227
+ frameAnalysis: this.plugins.shouldVideoBeActive(),
228
+ };
202
229
  }
203
- updateAlertZones(zones) {
204
- this.config.alertZones = zones;
230
+ resetPerf() {
231
+ this.perf.reset();
232
+ }
233
+ pauseForBenchmark(paused) {
234
+ this.benchmarkRunning = paused;
235
+ }
236
+ async runObjectBenchmark(iterations, concurrency) {
237
+ const plugin = this.plugins.get(SensorType.Object);
238
+ const input = plugin?.modelSpec?.input;
239
+ if (!plugin || !isVideoInputSpec(input))
240
+ return null;
241
+ const channels = input.format === 'gray' ? 1 : input.format === 'nv12' ? 1.5 : 3;
242
+ const frame = {
243
+ id: 'benchmark',
244
+ data: Buffer.alloc(Math.round(input.width * input.height * channels)),
245
+ width: input.width,
246
+ height: input.height,
247
+ format: input.format,
248
+ };
249
+ let handlerMs = 0;
250
+ let completed = 0;
251
+ let failed = 0;
252
+ const timed = this.proxy.createProxy(NamespaceManager.sensorProviderNamespaces(plugin.pluginId, plugin.sensorId).sensorRpc, {
253
+ onTiming: (_method, timing) => {
254
+ handlerMs += timing.handlerMs;
255
+ },
256
+ });
257
+ const startedAt = Date.now();
258
+ const workers = Array.from({ length: Math.max(1, concurrency) }, async () => {
259
+ while (completed + failed < iterations) {
260
+ completed++;
261
+ try {
262
+ await PromiseTimeout(timed.detectObjects(frame), DETECT_TIMEOUT_MS, undefined, `Benchmark call timed out after ${DETECT_TIMEOUT_MS}ms`);
263
+ }
264
+ catch (error) {
265
+ completed--;
266
+ failed++;
267
+ this.logger.debug('Benchmark call failed:', error);
268
+ }
269
+ }
270
+ });
271
+ await Promise.all(workers);
272
+ const totalMs = Date.now() - startedAt;
273
+ return {
274
+ camera: '',
275
+ plugin: plugin.pluginId,
276
+ sensorId: plugin.sensorId,
277
+ runtime: plugin.modelSpec?.runtime,
278
+ models: plugin.modelSpec?.models,
279
+ input: `${input.width}x${input.height}`,
280
+ iterations: completed,
281
+ failed,
282
+ concurrency,
283
+ totalMs,
284
+ perSecond: totalMs > 0 ? Math.round((completed / (totalMs / 1000)) * 10) / 10 : 0,
285
+ handlerMs: completed > 0 ? Math.round((handlerMs / completed) * 10) / 10 : 0,
286
+ };
205
287
  }
206
- updateLines(lines) {
207
- this.config.lines = lines;
208
- this.pipeline.updateLines(lines, this.videoAspectRatio);
288
+ updateZoneConfig(zones) {
289
+ zones = normalizeZones(zones);
290
+ this.config.zones = zones;
291
+ this.pipeline.updateZones(this.pipelineZones());
292
+ this.privacy.update(zones);
293
+ this.pipeline.updateLines(zones.lines, this.videoAspectRatio);
209
294
  }
210
295
  updateDetectionSettings(settings) {
211
296
  this.config.detectionSettings = settings;
@@ -225,12 +310,13 @@ let DetectionCoordinator = class DetectionCoordinator {
225
310
  }
226
311
  updateInterfaceSettings(settings) {
227
312
  this.config.interfaceSettings = settings;
228
- if (this.config.lines.length > 0) {
229
- this.pipeline.updateLines(this.config.lines, this.videoAspectRatio);
313
+ if (this.config.zones.lines.length > 0) {
314
+ this.pipeline.updateLines(this.config.zones.lines, this.videoAspectRatio);
230
315
  }
231
316
  }
232
317
  async dispose() {
233
318
  await this.stopVideoLoop();
319
+ await this.frameSource.stop();
234
320
  await this.audioLoop.stop();
235
321
  await this.thumbnailer.stop();
236
322
  this.ptzAutotracker.dispose();
@@ -290,6 +376,12 @@ let DetectionCoordinator = class DetectionCoordinator {
290
376
  // the registry only announces the plugin assigned for this type, everyone else reports into the void
291
377
  if (!this.feedingSensors.has(sensorId))
292
378
  return;
379
+ // a detector reports its spec again once its models finished loading
380
+ if (properties.modelSpec) {
381
+ this.applyModelSpec(sensorId, sensorType, properties.modelSpec);
382
+ if (Object.keys(properties).length === 1)
383
+ return;
384
+ }
293
385
  if (sensorType === SensorType.Object) {
294
386
  const filtered = this.applyExternalDetectionFilters(sensorType, properties);
295
387
  // presence feed keeps the autotracker's return-home timer alive even while
@@ -319,39 +411,19 @@ let DetectionCoordinator = class DetectionCoordinator {
319
411
  this.processingExternalSecondary = true;
320
412
  try {
321
413
  const rawExternal = filtered.detections ?? [];
322
- const peeked = this.frameSource.peekLatestFrame();
323
- if (peeked) {
324
- try {
325
- const results = { timestamp: Date.now() };
326
- const objects = await this.runObjectAssist(peeked.frame, rawExternal);
327
- this.ingestAssistedObjects(sensorId, objects);
328
- // processExternal only adapts the shape, external boxes get no real tracking
329
- const objectDetections = this.pipeline.processExternal(objects.detections);
330
- await this.runSecondariesAndThumbnails({ frame: peeked.frame, scaler: this.frameScaler, isMainStream: false }, objectDetections, results);
331
- this.ingestResultsForAllSecondaries(results);
332
- const snapshot = this.buildSnapshot();
333
- if (results.thumbnails && results.thumbnails.length > 0) {
334
- snapshot.thumbnails = results.thumbnails;
335
- }
336
- this.eventManager.processResults(snapshot);
337
- }
338
- finally {
339
- await peeked[Symbol.asyncDispose]();
340
- }
341
- return;
342
- }
343
- const fetched = await this.frameSource.fetchSnapshotFrame();
344
- if (!fetched)
414
+ const handle = await this.frameSource.getFrame(EXTERNAL_FRAME_MAX_AGE_MS);
415
+ if (!handle)
345
416
  return;
346
417
  try {
347
418
  const results = { timestamp: Date.now() };
348
- const objects = await this.runObjectAssist(fetched.frame, rawExternal);
419
+ const objects = await this.runObjectAssist(handle.frame, rawExternal);
349
420
  this.ingestAssistedObjects(sensorId, objects);
350
421
  if (objects.assisted) {
351
422
  // real boxes: crop the object like the frame pipeline, so face and
352
423
  // plate secondaries run on the subject instead of the whole scene
424
+ // (processExternal only adapts the shape, external boxes get no real tracking)
353
425
  const objectDetections = this.pipeline.processExternal(objects.detections);
354
- await this.runSecondariesAndThumbnails({ frame: fetched.frame, scaler: this.frameScaler, isMainStream: false }, objectDetections, results);
426
+ await this.runSecondariesAndThumbnails({ frame: handle.frame, scaler: this.frameScaler, isMainStream: false }, objectDetections, results);
355
427
  this.ingestResultsForAllSecondaries(results);
356
428
  const snapshot = this.buildSnapshot();
357
429
  if (results.thumbnails && results.thumbnails.length > 0) {
@@ -360,9 +432,9 @@ let DetectionCoordinator = class DetectionCoordinator {
360
432
  this.eventManager.processResults(snapshot);
361
433
  }
362
434
  else {
363
- const sceneJpeg = await this.secondaries.detectFullFrame(fetched.frame, results);
435
+ const sceneJpeg = await this.secondaries.detectFullFrame(handle.frame, results);
364
436
  try {
365
- results.thumbnails = await this.secondaries.generateThumbnails(fetched.frame, this.frameScaler, results);
437
+ results.thumbnails = await this.secondaries.generateThumbnails(handle.frame, this.frameScaler, results);
366
438
  }
367
439
  catch (error) {
368
440
  this.logger.error('Thumbnail generation error:', error);
@@ -382,7 +454,7 @@ let DetectionCoordinator = class DetectionCoordinator {
382
454
  }
383
455
  }
384
456
  finally {
385
- await fetched[Symbol.asyncDispose]();
457
+ await handle[Symbol.asyncDispose]();
386
458
  }
387
459
  }
388
460
  finally {
@@ -543,7 +615,7 @@ let DetectionCoordinator = class DetectionCoordinator {
543
615
  faceEmbeddingModel: cs.faceEmbeddingModel,
544
616
  plates: cs.licensePlate?.detections ?? [],
545
617
  plateVoting: this.plugins.get(SensorType.LicensePlate)?.requiresFrames === true,
546
- plateMinConfidence: this.config.detectionSettings.licensePlate?.confidence,
618
+ plateMinConfidence: this.config.detectionSettings.licensePlate?.ocrConfidence,
547
619
  plateMinLength: this.config.detectionSettings.licensePlate?.minLength,
548
620
  faceMinConfidence: this.config.detectionSettings.face?.confidence,
549
621
  classifiers: cs.classifiers ? Object.values(cs.classifiers).flatMap((c) => c.detections) : [],
@@ -557,57 +629,69 @@ let DetectionCoordinator = class DetectionCoordinator {
557
629
  detectionZones: this.getNormalizedDetectionZones(),
558
630
  };
559
631
  }
632
+ pipelineZones() {
633
+ return { motion: this.config.zones.motion, object: this.config.zones.object, privacy: this.config.zones.privacy };
634
+ }
560
635
  getNormalizedDetectionZones() {
561
636
  const result = [];
562
- for (const zone of this.config.zones ?? []) {
563
- if (zone.isPrivacyMask)
564
- continue;
637
+ for (const zone of this.config.zones.object) {
565
638
  if (!zone.name)
566
639
  continue;
567
- result.push({ name: zone.name, points: normalizeZone(zone).points });
640
+ result.push({ name: zone.name, points: normalizePolygon(zone.points) });
568
641
  }
569
- for (const zone of this.config.alertZones ?? []) {
642
+ for (const zone of this.config.zones.alert) {
570
643
  if (!zone.name)
571
644
  continue;
572
- result.push({ name: zone.name, points: normalizePolygon(zone.points), match: zone.match ?? 'contain' });
645
+ result.push({
646
+ name: zone.name,
647
+ points: normalizePolygon(zone.points),
648
+ match: zone.match ?? 'contain',
649
+ alertLabels: zone.labels.map((label) => label.toLowerCase()),
650
+ });
573
651
  }
574
652
  return result;
575
653
  }
576
- filterAllowedObjectLabels(detections) {
577
- const allowed = this.config.detectionSettings.object.labels;
578
- if (!allowed || allowed.length === 0 || detections.length === 0)
579
- return detections;
580
- const set = new Set(allowed.map((label) => label.toLowerCase()));
581
- return detections.filter((detection) => set.has(detection.label.toLowerCase()));
582
- }
583
654
  applyExternalDetectionFilters(sensorType, properties) {
584
655
  const raw = properties.detections;
585
656
  if (!Array.isArray(raw) || raw.length === 0)
586
657
  return properties;
587
658
  // the zone filter and the rust merge assume a box on every detection
588
659
  const detections = ensureDetectionBoxes(raw);
660
+ // a camera that reports a label without coordinates gives no position, a
661
+ // zone cannot judge it and the object assist has not run yet
662
+ const positioned = detections.filter((detection) => !isFullFrameBox(detection.box));
663
+ const boxless = detections.filter((detection) => isFullFrameBox(detection.box));
589
664
  let filtered;
665
+ let boxlessLabel;
590
666
  switch (sensorType) {
591
667
  case SensorType.Face:
592
- filtered = this.pipeline.runZoneFilterWithLabel(detections, 'person');
668
+ boxlessLabel = 'face';
669
+ filtered = this.pipeline.runZoneFilterWithLabel(positioned, 'face');
593
670
  break;
594
671
  case SensorType.LicensePlate:
595
- filtered = this.pipeline.runZoneFilterWithLabel(detections, 'vehicle');
672
+ boxlessLabel = 'license_plate';
673
+ filtered = this.pipeline.runZoneFilterWithLabel(positioned, 'license_plate');
596
674
  break;
597
675
  case SensorType.Object:
598
- filtered = this.pipeline.runZoneFilter(this.filterAllowedObjectLabels(detections));
676
+ filtered = this.pipeline.runZoneFilter(positioned);
599
677
  break;
600
678
  case SensorType.Classifier:
601
679
  case SensorType.Motion:
602
- filtered = this.pipeline.runZoneFilter(detections);
680
+ filtered = this.pipeline.runZoneFilter(positioned);
603
681
  break;
604
682
  default:
605
683
  return { ...properties, detections }; // audio etc, no zone filter
606
684
  }
685
+ const survivors = new Set(filtered);
686
+ for (const detection of boxless) {
687
+ if (this.pipeline.objectLabelAllowed(boxlessLabel ?? detection.label))
688
+ survivors.add(detection);
689
+ }
690
+ const kept = detections.filter((detection) => survivors.has(detection));
607
691
  return {
608
692
  ...properties,
609
- detections: filtered,
610
- detected: filtered.length > 0,
693
+ detections: kept,
694
+ detected: kept.length > 0,
611
695
  };
612
696
  }
613
697
  applyAudioConfidence(properties) {
@@ -740,6 +824,18 @@ let DetectionCoordinator = class DetectionCoordinator {
740
824
  break;
741
825
  }
742
826
  }
827
+ applyModelSpec(sensorId, sensorType, modelSpec) {
828
+ const current = this.plugins.get(sensorType);
829
+ if (current?.sensorId !== sensorId)
830
+ return;
831
+ // another model means other numbers, keeping the old average would mislead;
832
+ // load time is not part of the identity, a reload of the same model is not a change
833
+ if (sensorType === SensorType.Object || sensorType === SensorType.Motion) {
834
+ if (modelIdentity(current.modelSpec) !== modelIdentity(modelSpec))
835
+ this.perf.reset();
836
+ }
837
+ this.plugins.updateModelSpec(sensorId, modelSpec);
838
+ }
743
839
  async registerDetectionPluginInternal(sensor) {
744
840
  if (this.videoStopPromise)
745
841
  await this.videoStopPromise;
@@ -747,7 +843,9 @@ let DetectionCoordinator = class DetectionCoordinator {
747
843
  const wasVideoNeeded = this.plugins.shouldVideoBeActive();
748
844
  const wasAudioNeeded = this.plugins.shouldAudioBeActive();
749
845
  const namespaces = NamespaceManager.sensorProviderNamespaces(sensor.pluginId, sensor.sensorId);
750
- const sensorProxy = this.proxy.createProxy(namespaces.sensorRpc);
846
+ const sensorProxy = this.proxy.createProxy(namespaces.sensorRpc, {
847
+ onTiming: (_method, timing) => this.perf.trackTiming(sensor.sensorType, timing.handlerMs, timing.transportMs),
848
+ });
751
849
  const registered = this.plugins.register({
752
850
  pluginId: sensor.pluginId,
753
851
  sensorId: sensor.sensorId,
@@ -758,7 +856,8 @@ let DetectionCoordinator = class DetectionCoordinator {
758
856
  });
759
857
  if (!registered) {
760
858
  // re-push from the registry: the sensor is already live, only the spec may have changed
761
- this.plugins.updateModelSpec(sensor.sensorId, sensor.modelSpec);
859
+ if (sensor.modelSpec)
860
+ this.applyModelSpec(sensor.sensorId, sensor.sensorType, sensor.modelSpec);
762
861
  return;
763
862
  }
764
863
  this.logger.trace(`Plugin registered: ${sensor.pluginId} for ${sensor.sensorType}`);
@@ -839,7 +938,6 @@ let DetectionCoordinator = class DetectionCoordinator {
839
938
  return;
840
939
  this.logger.debug('Starting video detection loop');
841
940
  this.loopRunning = true;
842
- this.videoBackoff.reset();
843
941
  this.loopPromise = this.runDetectionLoop();
844
942
  }
845
943
  async stopVideoLoop() {
@@ -854,7 +952,7 @@ let DetectionCoordinator = class DetectionCoordinator {
854
952
  this.mainStreamActive = false;
855
953
  this.idleSince = 0;
856
954
  const doStop = async () => {
857
- await this.frameSource.stop();
955
+ await this.frameSource.detach();
858
956
  await this.loopPromise;
859
957
  this.loopPromise = undefined;
860
958
  };
@@ -881,81 +979,87 @@ let DetectionCoordinator = class DetectionCoordinator {
881
979
  }
882
980
  }
883
981
  async runDetectionLoop() {
982
+ // the source reconnects on its own, nextFrame simply waits it out
983
+ const startedAt = Date.now();
984
+ await this.frameSource.start();
985
+ let lastFrameId = -1;
986
+ let seenGeneration = -1;
987
+ let firstFrame = true;
884
988
  while (this.loopRunning) {
989
+ const tickStart = Date.now();
990
+ const snap = await this.frameSource.nextFrame(lastFrameId);
991
+ if (!snap)
992
+ break; // source stopped
993
+ if (firstFrame) {
994
+ firstFrame = false;
995
+ this.logger.debug(`Analysing after ${Date.now() - startedAt}ms`);
996
+ }
997
+ lastFrameId = snap.id;
998
+ if (this.benchmarkRunning) {
999
+ // the detector is saturated on purpose, feeding it real frames would
1000
+ // both distort the measurement and queue up behind it
1001
+ snap.frame[Symbol.dispose]?.();
1002
+ await sleep(IDLE_TICK_MS);
1003
+ continue;
1004
+ }
885
1005
  try {
886
- await this.frameSource.start();
887
- this.frameScaler.updateHardwareContext(this.frameSource.hardwareContext);
888
- // the hardware context exists only after start, re-evaluate the HQ session
889
- this.thumbnailer.sync(this.mainStreamAnalysisWanted);
890
- this.logger.debug('Stream connected, processing frames...');
891
- this.videoBackoff.reset();
892
- let frameCount = 0;
893
- let lastFrameId = -1;
894
- while (this.loopRunning) {
895
- const tickStart = Date.now();
896
- const snap = await this.frameSource.nextFrame(lastFrameId);
897
- if (!snap)
898
- break; // source ended (stop or EOF)
899
- lastFrameId = snap.id;
900
- frameCount++;
901
- // motion always reads the low stream: switching its input resolution
902
- // would reset the background model on every transition
903
- const analysis = await this.acquireAnalysisFrame(snap.frame);
904
- // debugging
905
- detectionRecord.setFrame(analysis.isMainStream ? 'main' : 'low', analysis.frame.width, analysis.frame.height);
1006
+ if (this.frameSource.generation !== seenGeneration) {
1007
+ seenGeneration = this.frameSource.generation;
1008
+ // a fresh connection brings its own hardware context and resolution
1009
+ this.frameScaler.updateHardwareContext(this.frameSource.hardwareContext);
1010
+ this.frameScaler.clearCache();
1011
+ this.thumbnailer.sync(this.mainStreamAnalysisWanted);
1012
+ }
1013
+ // motion always reads the low stream: switching its input resolution
1014
+ // would reset the background model on every transition
1015
+ const analysis = await this.acquireAnalysisFrame(snap.frame);
1016
+ // debugging
1017
+ detectionRecord.setFrame(analysis.isMainStream ? 'main' : 'low', analysis.frame.width, analysis.frame.height);
1018
+ try {
1019
+ await this.processRawFrame(analysis, snap.frame);
1020
+ }
1021
+ finally {
906
1022
  try {
907
- await this.processRawFrame(analysis, snap.frame);
908
- }
909
- finally {
910
- try {
911
- if (this.hqUpgrade) {
912
- this.hqUpgrade.frame[Symbol.dispose]?.();
913
- this.hqUpgrade = undefined;
914
- }
915
- if (analysis.frame !== snap.frame)
916
- analysis.frame[Symbol.dispose]?.();
917
- snap.frame[Symbol.dispose]?.();
918
- }
919
- catch {
920
- // best-effort
1023
+ if (this.hqUpgrade) {
1024
+ this.hqUpgrade.frame[Symbol.dispose]?.();
1025
+ this.hqUpgrade = undefined;
921
1026
  }
1027
+ if (analysis.frame !== snap.frame)
1028
+ analysis.frame[Symbol.dispose]?.();
1029
+ }
1030
+ catch {
1031
+ // ignore
922
1032
  }
923
- this.updateStreamState();
924
- // activity, not stream choice: cameras without main-stream analysis still count as active
925
- if (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment())
926
- this.perf.activeTicks++;
927
- else
928
- this.perf.idleTicks++;
929
- this.perf.report(this.logger);
930
- const remaining = (this.mainStreamActive ? ACTIVE_TICK_MS : IDLE_TICK_MS) - (Date.now() - tickStart);
931
- if (remaining > 0)
932
- await sleep(remaining);
933
- this.perf.loopMs += Date.now() - tickStart;
934
- }
935
- const streamError = this.frameSource.lastError;
936
- await this.frameSource.stop();
937
- this.frameScaler.clearCache();
938
- if (this.loopRunning && streamError) {
939
- const delay = this.videoBackoff.nextDelayMs();
940
- this.logger.warn(`Stream ended with read error, reconnecting in ${delay / 1000}s: ${streamError.message}`);
941
- await sleep(delay);
942
- }
943
- else if (this.loopRunning && frameCount === 0) {
944
- this.logger.debug('Stream ended without frames, waiting before reconnect...');
945
- await sleep(this.videoBackoff.idleDelayMs);
946
1033
  }
1034
+ this.updateStreamState();
947
1035
  }
948
1036
  catch (error) {
949
- if (!this.loopRunning)
950
- break;
951
- await this.frameSource.stop();
952
- this.frameScaler.clearCache();
953
- const delay = this.videoBackoff.nextDelayMs();
954
- this.logger.warn(`Stream error, reconnecting in ${delay / 1000}s: ${error.message}`);
955
- await sleep(delay);
1037
+ // the stream keeps running, a broken tick must not end the loop
1038
+ this.logger.error('Detection tick failed:', error);
956
1039
  }
957
- }
958
- await this.frameSource.stop();
1040
+ finally {
1041
+ try {
1042
+ snap.frame[Symbol.dispose]?.();
1043
+ }
1044
+ catch {
1045
+ // ignore
1046
+ }
1047
+ }
1048
+ const decode = this.frameSource.takeDecodeStats();
1049
+ this.perf.decodeMs += decode.ms;
1050
+ this.perf.decodedFrames += decode.frames;
1051
+ // activity, not stream choice: cameras without main-stream analysis still count as active
1052
+ if (this.worldSpans.size > 0 || this.eventManager.hasActiveSegment())
1053
+ this.perf.activeTicks++;
1054
+ else
1055
+ this.perf.idleTicks++;
1056
+ this.perf.report(this.logger);
1057
+ const remaining = (this.mainStreamActive ? ACTIVE_TICK_MS : IDLE_TICK_MS) - (Date.now() - tickStart);
1058
+ if (remaining > 0)
1059
+ await sleep(remaining);
1060
+ this.perf.loopMs += Date.now() - tickStart;
1061
+ }
1062
+ await this.frameSource.detach();
959
1063
  this.frameScaler.clearCache();
960
1064
  this.logger.debug('Detection loop ended');
961
1065
  }
@@ -974,7 +1078,7 @@ let DetectionCoordinator = class DetectionCoordinator {
974
1078
  const main = await this.thumbnailer.acquireHqFrame(0);
975
1079
  if (main) {
976
1080
  this.perf.mainFrames++;
977
- this.perf.decodeMs += Date.now() - t0;
1081
+ this.perf.mainDecodeMs += Date.now() - t0;
978
1082
  return { frame: main.frame, scaler: main.scaler, isMainStream: true };
979
1083
  }
980
1084
  }
@@ -992,7 +1096,7 @@ let DetectionCoordinator = class DetectionCoordinator {
992
1096
  const main = await this.thumbnailer.acquireHqFrame(0);
993
1097
  if (main) {
994
1098
  this.perf.mainFrames++;
995
- this.perf.decodeMs += Date.now() - t0;
1099
+ this.perf.mainDecodeMs += Date.now() - t0;
996
1100
  return { frame: main.frame, scaler: main.scaler, isMainStream: true };
997
1101
  }
998
1102
  }
@@ -1059,8 +1163,8 @@ let DetectionCoordinator = class DetectionCoordinator {
1059
1163
  try {
1060
1164
  const motionInferStart = Date.now();
1061
1165
  const result = await motionPlugin.proxy.detectMotion(motionFrame);
1062
- this.perf.inferMs += Date.now() - motionInferStart;
1063
- this.perf.inferCount++;
1166
+ this.perf.motionMs += Date.now() - motionInferStart;
1167
+ this.perf.motionCount++;
1064
1168
  if (!this.loopRunning)
1065
1169
  return;
1066
1170
  if (ptzSuppressed) {
@@ -1111,16 +1215,18 @@ let DetectionCoordinator = class DetectionCoordinator {
1111
1215
  try {
1112
1216
  const inferStart = Date.now();
1113
1217
  const result = await PromiseTimeout(objectPlugin.proxy.detectObjects(objectFrame.model), DETECT_TIMEOUT_MS, undefined, `Object detection timed out after ${DETECT_TIMEOUT_MS}ms`);
1114
- this.perf.inferMs += Date.now() - inferStart;
1115
- this.perf.inferCount++;
1218
+ this.perf.objectMs += Date.now() - inferStart;
1219
+ this.perf.objectCount++;
1116
1220
  if (!this.loopRunning)
1117
1221
  return;
1118
1222
  this.trackDetectionCadence();
1119
1223
  // run the pipeline even on empty frames so Norfair advances its
1120
1224
  // Kalman state; the pose delta keeps predictions stable across pans
1121
1225
  const poseDelta = this.ptzAutotracker.consumePoseDelta();
1122
- const detected = this.filterAllowedObjectLabels(FrameScaler.undoLetterbox(ensureDetectionBoxes(result.detections), objectFrame.geometry));
1226
+ const postStart = Date.now();
1227
+ const detected = FrameScaler.undoLetterbox(ensureDetectionBoxes(result.detections), objectFrame.geometry);
1123
1228
  const pipelineResult = this.pipeline.process(detected, poseDelta);
1229
+ this.perf.postMs += Date.now() - postStart;
1124
1230
  // the tick that opens a span analysed the low stream (the switch lands
1125
1231
  // next tick), its picture work is worth a one-off HQ decode
1126
1232
  if (this.updateWorldSpans(pipelineResult) && !analysis.isMainStream) {
@@ -1244,6 +1350,7 @@ let DetectionCoordinator = class DetectionCoordinator {
1244
1350
  if (objectDetections.length === 0)
1245
1351
  return;
1246
1352
  this.perf.objects += objectDetections.length;
1353
+ this.perf.framesWithObjects++;
1247
1354
  const secondaryStart = Date.now();
1248
1355
  await this.secondaries.detect(analysis.frame, analysis.scaler, objectDetections, results);
1249
1356
  this.perf.secondaryMs += Date.now() - secondaryStart;
@@ -1265,15 +1372,18 @@ let DetectionCoordinator = class DetectionCoordinator {
1265
1372
  }
1266
1373
  const facePlugin = this.plugins.get(SensorType.Face);
1267
1374
  if (facePlugin) {
1375
+ // the crop path reaches ingestDetectionResult directly, so the zone
1376
+ // filter that guards the plugin path has to be applied here too
1377
+ const detections = this.pipeline.runZoneFilterWithLabel(results.face.detections, 'face');
1268
1378
  // no mid-segment clear: a turned-away head yields a no-face frame and
1269
1379
  // would flap the list, departure is reflected when the segment ends
1270
- for (const detection of results.face.detections) {
1380
+ for (const detection of detections) {
1271
1381
  if (detection.identity)
1272
1382
  this.faceIdentities.add(detection.identity);
1273
1383
  }
1274
1384
  this.ingestDetectionResult(SensorType.Face, facePlugin.sensorId, {
1275
- detected: results.face.detected,
1276
- detections: results.face.detections,
1385
+ detected: detections.length > 0 && results.face.detected,
1386
+ detections,
1277
1387
  identities: [...this.faceIdentities].sort(),
1278
1388
  });
1279
1389
  }
@@ -1281,14 +1391,15 @@ let DetectionCoordinator = class DetectionCoordinator {
1281
1391
  if (results.licensePlate) {
1282
1392
  const lpdPlugin = this.plugins.get(SensorType.LicensePlate);
1283
1393
  if (lpdPlugin) {
1284
- for (const detection of results.licensePlate.detections) {
1394
+ const detections = this.pipeline.runZoneFilterWithLabel(results.licensePlate.detections, 'license_plate');
1395
+ for (const detection of detections) {
1285
1396
  const plate = detection.plateText ? normalizePlateText(detection.plateText) || detection.plateText : '';
1286
1397
  if (plate)
1287
1398
  this.platesSeen.add(plate);
1288
1399
  }
1289
1400
  this.ingestDetectionResult(SensorType.LicensePlate, lpdPlugin.sensorId, {
1290
- detected: results.licensePlate.detected,
1291
- detections: results.licensePlate.detections,
1401
+ detected: detections.length > 0 && results.licensePlate.detected,
1402
+ detections,
1292
1403
  plates: [...this.platesSeen].sort(),
1293
1404
  });
1294
1405
  }
@@ -1578,10 +1689,10 @@ let DetectionCoordinator = class DetectionCoordinator {
1578
1689
  try {
1579
1690
  const inferStart = Date.now();
1580
1691
  const result = await PromiseTimeout(assist.proxy.detectObjects(scaled.model), DETECT_TIMEOUT_MS, undefined, `Object assist timed out after ${DETECT_TIMEOUT_MS}ms`);
1581
- this.perf.inferMs += Date.now() - inferStart;
1582
- this.perf.inferCount++;
1692
+ this.perf.assistMs += Date.now() - inferStart;
1693
+ this.perf.assistCount++;
1583
1694
  const reportedLabels = new Set(reported.map((d) => d.label.toLowerCase()));
1584
- const boxed = this.filterAllowedObjectLabels(FrameScaler.undoLetterbox(ensureDetectionBoxes(result?.detections ?? []), scaled.geometry));
1695
+ const boxed = FrameScaler.undoLetterbox(ensureDetectionBoxes(result?.detections ?? []), scaled.geometry);
1585
1696
  const found = boxed.filter((d) => reportedLabels.size === 0 || reportedLabels.has(d.label.toLowerCase()));
1586
1697
  if (found.length === 0)
1587
1698
  return { detections: reported, assisted: false };