@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.
Files changed (502) hide show
  1. package/dist/interface/assets/{404-xXRqIPnI.js → 404-C0XhilKM.js} +1 -1
  2. package/dist/interface/assets/{About-DmA6YPm3.js → About-DZsTev1Z.js} +1 -1
  3. package/dist/interface/assets/Adminpanel-BFKj4r-J.js +1 -0
  4. package/dist/interface/assets/{Automation-BDETmBsr.js → Automation-DftZGCt6.js} +3 -3
  5. package/dist/interface/assets/{AutomationImport-DiBbViZF.js → AutomationImport-CWz3wK45.js} +2 -2
  6. package/dist/interface/assets/{AutomationStore-i2EAmdtd.js → AutomationStore-BDgJ0e1s.js} +2 -2
  7. package/dist/interface/assets/{AutomationStoreDetail-Cs08SdQP.js → AutomationStoreDetail-BxjQz384.js} +2 -2
  8. package/dist/interface/assets/{Automations-foLiIvMO.js → Automations-CYK5CbkM.js} +2 -2
  9. package/dist/interface/assets/Camera-C5gF7yVN.css +1 -0
  10. package/dist/interface/assets/Camera-C7TRw5N0.js +2 -0
  11. package/dist/interface/assets/{CameraConsole-ByzCncrJ.js → CameraConsole-CNULG_YE.js} +2 -2
  12. package/dist/interface/assets/Cameras-CXWCJWzR.js +652 -0
  13. package/dist/interface/assets/Camview-C09leXh7.js +1 -0
  14. package/dist/interface/assets/Camview-UCj_OW0s.css +1 -0
  15. package/dist/interface/assets/Config-DNOXV5ek.js +2 -0
  16. package/dist/interface/assets/{ConfigActionCameraControl-BXO7ipsz.js → ConfigActionCameraControl-BPfBxvxr.js} +1 -1
  17. package/dist/interface/assets/{ConfigActionDelay-DKg-B1Qg.js → ConfigActionDelay-CZeAJTj7.js} +1 -1
  18. package/dist/interface/assets/{ConfigActionHttp-DxXmT1sT.js → ConfigActionHttp-ipkZdIWF.js} +1 -1
  19. package/dist/interface/assets/{ConfigActionImageInput-Bg0PRpEM.js → ConfigActionImageInput-CvETCNjG.js} +1 -1
  20. package/dist/interface/assets/{ConfigActionMqtt-D8eQzkpn.js → ConfigActionMqtt-B8ZNU0UW.js} +1 -1
  21. package/dist/interface/assets/{ConfigActionNotification-CzwYhRsN.js → ConfigActionNotification-B-nC2J6v.js} +1 -1
  22. package/dist/interface/assets/{ConfigActionNotificationControl-B92w8_zI.js → ConfigActionNotificationControl-DjxS7_ID.js} +1 -1
  23. package/dist/interface/assets/{ConfigActionOutput-DnBrTam_.js → ConfigActionOutput-QG12dzwm.js} +1 -1
  24. package/dist/interface/assets/{ConfigActionPlugin-ZZLa_9lg.js → ConfigActionPlugin-Q2Tcpdgm.js} +1 -1
  25. package/dist/interface/assets/{ConfigActionSensor-C4vlKTMo.js → ConfigActionSensor-BTFyt4ER.js} +1 -1
  26. package/dist/interface/assets/{ConfigActionSnapshot-gv4xP_ke.js → ConfigActionSnapshot-DfnAex92.js} +1 -1
  27. package/dist/interface/assets/{ConfigActionVariable-BK20Jiwm.js → ConfigActionVariable-COrJ-lFp.js} +1 -1
  28. package/dist/interface/assets/{ConfigConditionIfElse-CMNQhZVq.js → ConfigConditionIfElse-Dpc6Cwpf.js} +1 -1
  29. package/dist/interface/assets/ConfigConditionSensorState-BQXY2c2t.js +1 -0
  30. package/dist/interface/assets/{ConfigConditionSwitch-BD_yekSb.js → ConfigConditionSwitch-BuI8_3Bp.js} +1 -1
  31. package/dist/interface/assets/{ConfigConditionTime-iYGtS79-.js → ConfigConditionTime-0Du7w_QE.js} +1 -1
  32. package/dist/interface/assets/ConfigSensorValueInput-vY0DCyRn.js +1 -0
  33. package/dist/interface/assets/{ConfigTriggerDetection-fT3Ao7ai.js → ConfigTriggerDetection-ChUtfRtm.js} +1 -1
  34. package/dist/interface/assets/{ConfigTriggerGeofence-CTovEe2t.js → ConfigTriggerGeofence-fHTNHtGp.js} +1 -1
  35. package/dist/interface/assets/{ConfigTriggerManual-_70uZCm8.js → ConfigTriggerManual-IAo9sbiS.js} +1 -1
  36. package/dist/interface/assets/{ConfigTriggerMqtt-B3i_U0C8.js → ConfigTriggerMqtt-ZeRfexN1.js} +1 -1
  37. package/dist/interface/assets/{ConfigTriggerSchedule-opMt64uR.js → ConfigTriggerSchedule-Ct_AN-xu.js} +1 -1
  38. package/dist/interface/assets/ConfigTriggerSensor-BJkKOmTg.js +1 -0
  39. package/dist/interface/assets/{ConfigTriggerSystem-l6Z8YkIP.js → ConfigTriggerSystem-DedCuH2F.js} +1 -1
  40. package/dist/interface/assets/{ConfigTriggerWebhook-BUoC_tvP.js → ConfigTriggerWebhook-DPz9NPdp.js} +1 -1
  41. package/dist/interface/assets/Console-D5hGnmUg.js +1 -0
  42. package/dist/interface/assets/CuiAutomationRunsDialog-BP-FYYXr.js +1 -0
  43. package/dist/interface/assets/{CuiBottomSheet-CD7ayDtu.js → CuiBottomSheet-B25DDhsj.js} +1 -1
  44. package/dist/interface/assets/CuiBottomSheet-de-zDc6R.js +1 -0
  45. package/dist/interface/assets/{CuiCameraPipCard-D3o29_wc.css → CuiCameraPipCard-BQKk4fxY.css} +1 -1
  46. package/dist/interface/assets/CuiCameraPipCard-CeY7Gy9_.js +1 -0
  47. package/dist/interface/assets/CuiCameraRecordings-AA-sBtb4.css +1 -0
  48. package/dist/interface/assets/CuiCameraRecordings-CqwUqjAU.js +1 -0
  49. package/dist/interface/assets/{CuiCameraShares-BNhXZZx4.js → CuiCameraShares-B5pdZrh3.js} +1 -1
  50. package/dist/interface/assets/{CuiCameraTable-IqEVI4_6.js → CuiCameraTable-BhUWRnwr.js} +2 -2
  51. package/dist/interface/assets/{CuiConsole-DQdrSaNv.js → CuiConsole-CAHUWadU.js} +2 -2
  52. package/dist/interface/assets/CuiDataTable-DcyMAPBx.js +1 -0
  53. package/dist/interface/assets/CuiLightControl-8aZiuOCv.js +1 -0
  54. package/dist/interface/assets/CuiList-psx5r57o.js +1 -0
  55. package/dist/interface/assets/CuiMarkdownContent-73RAjkHa.js +1 -0
  56. package/dist/interface/assets/{CuiMarkdownContent-wt9kwnE3.js → CuiMarkdownContent-BfJOBp3S.js} +1 -1
  57. package/dist/interface/assets/CuiPluginCard-BZPiou4m.js +2 -0
  58. package/dist/interface/assets/{CuiPluginCategoryChip-BBg1R80V.js → CuiPluginCategoryChip-1h2moEVm.js} +1 -1
  59. package/dist/interface/assets/{CuiPluginOAuthButton-CZkDLOyX.js → CuiPluginOAuthButton-CI5gdHHd.js} +2 -2
  60. package/dist/interface/assets/{CuiPluginTrustBadge-CLGD6iSl.js → CuiPluginTrustBadge-CqkEF6GV.js} +1 -1
  61. package/dist/interface/assets/{CuiQRCode-BJIth5tK.js → CuiQRCode-CQGx_k0S.js} +1 -1
  62. package/dist/interface/assets/CuiRecordingsGrid-CLNQoUbw.js +1 -0
  63. package/dist/interface/assets/CuiRecordingsGrid-Sv3uyj9a.css +1 -0
  64. package/dist/interface/assets/CuiSchema-U31deYpX.js +1 -0
  65. package/dist/interface/assets/{CuiTopNavbarItem-DuW8KecA.js → CuiTopNavbarItem-BekRyj7O.js} +1 -1
  66. package/dist/interface/assets/Faces-BEqO99qz.js +1 -0
  67. package/dist/interface/assets/FirstSteps-BTDj1olc.js +1 -0
  68. package/dist/interface/assets/Instances-DTqwb5FF.js +1 -0
  69. package/dist/interface/assets/{Logs-pqS6zmdG.js → Logs-D8LlWlQ2.js} +2 -2
  70. package/dist/interface/assets/Menu-Cb5cBA-q.js +1 -0
  71. package/dist/interface/assets/{OAuthDeviceFlow-FtU9FE8s.js → OAuthDeviceFlow-CExQxxt6.js} +1 -1
  72. package/dist/interface/assets/Plugin-BDqNeTCT.css +1 -0
  73. package/dist/interface/assets/Plugin-DtkmtFL6.js +1 -0
  74. package/dist/interface/assets/{PluginConsole-DpsjUxOt.js → PluginConsole-CktnZFCJ.js} +2 -2
  75. package/dist/interface/assets/PluginDetail-t5V-ATem.js +2 -0
  76. package/dist/interface/assets/{PluginSchema-CPbdpwfh.js → PluginSchema-DgXUaGQ2.js} +1 -1
  77. package/dist/interface/assets/PluginSearch-D8qXgkEP.js +2 -0
  78. package/dist/interface/assets/PluginSearch-DDa1Jt8s.js +1 -0
  79. package/dist/interface/assets/{PluginSearch-CLjLT9JT.css → PluginSearch-Dlzc-aJ3.css} +1 -1
  80. package/dist/interface/assets/{PluginUpdateProgress-B_vd--LX.js → PluginUpdateProgress-B9GSD9wl.js} +1 -1
  81. package/dist/interface/assets/Plugins-CMjhJaUC.js +1 -0
  82. package/dist/interface/assets/Recordings-BP3-OD7P.js +1 -0
  83. package/dist/interface/assets/SensorEdit-B-64Y4xT.js +1 -0
  84. package/dist/interface/assets/{SensorHistory-D11DdMol.js → SensorHistory-D8xQKiuE.js} +1 -1
  85. package/dist/interface/assets/Sensors-D5VXCQ6N.js +2 -0
  86. package/dist/interface/assets/Settings-B3INZjRu.js +1 -0
  87. package/dist/interface/assets/{Settings-DmZjKfDi.css → Settings-D8L1oiQx.css} +1 -1
  88. package/dist/interface/assets/SettingsAccount-BEMYyovd.js +1 -0
  89. package/dist/interface/assets/SettingsAccount-CcPLFq2z.js +1 -0
  90. package/dist/interface/assets/SettingsAppearance-BSVGF67s.js +1 -0
  91. package/dist/interface/assets/SettingsAppearance-CCf7QwkP.js +1 -0
  92. package/dist/interface/assets/SettingsBackup-DSARU1qG.js +1 -0
  93. package/dist/interface/assets/SettingsList-imzWFgaD.js +1 -0
  94. package/dist/interface/assets/SettingsMqtt-DkEqSLI4.js +1 -0
  95. package/dist/interface/assets/{SettingsNotifications-EerQSLcj.js → SettingsNotifications-CE5byZci.js} +2 -2
  96. package/dist/interface/assets/{SettingsPermissions-j1GCDQER.js → SettingsPermissions-DU1XvFFc.js} +1 -1
  97. package/dist/interface/assets/SettingsRecordings-CSCgcxrS.js +1 -0
  98. package/dist/interface/assets/SettingsRemote-DXw6kYER.js +1 -0
  99. package/dist/interface/assets/SettingsSystem-BctqXTc5.js +2 -0
  100. package/dist/interface/assets/{SettingsUsers-DSu7YkD6.js → SettingsUsers-DoPPk6w5.js} +1 -1
  101. package/dist/interface/assets/Terminal-CPOkeiZ5.js +4 -0
  102. package/dist/interface/assets/{VariableInput-D8fxy8Tr.js → VariableInput-DfDRe6bt.js} +1 -1
  103. package/dist/interface/assets/{VariableSuggestions-BmrGajtR.js → VariableSuggestions-CjlK2Uqx.js} +1 -1
  104. package/dist/interface/assets/VersionsHandler-BEWveXqP.js +7 -0
  105. package/dist/interface/assets/{VirtualSensorCreate-CY0SgW0D.js → VirtualSensorCreate-CUTyLWv-.js} +1 -1
  106. package/dist/interface/assets/Workers-B_G_FZls.js +2 -0
  107. package/dist/interface/assets/{account-CY80SLLf.js → account-DuSFtIHf.js} +1 -1
  108. package/dist/interface/assets/{alert-circle-pDGfnZ4K.js → alert-circle-C2AU59aR.js} +1 -1
  109. package/dist/interface/assets/{alert-outline-CeoXcnQc.js → alert-outline-GrD7-zHq.js} +1 -1
  110. package/dist/interface/assets/{api-QDhweZyS.js → api-BrAt3E8w.js} +1 -1
  111. package/dist/interface/assets/api-DIm7LItL.css +2 -0
  112. package/dist/interface/assets/api-Da98l_Y7.js +2 -0
  113. package/dist/interface/assets/{arrow-left-D9jDhvK5.js → arrow-left-C1N6U6kZ.js} +1 -1
  114. package/dist/interface/assets/{arrow-right-outline-DF7tkKYk.js → arrow-right-outline-C24f5mwe.js} +1 -1
  115. package/dist/interface/assets/{auth-DtXDXDTz.js → auth-BdsJrXmQ.js} +1 -1
  116. package/dist/interface/assets/{automations-BtpjXAGj.js → automations-C1EJke-z.js} +1 -1
  117. package/dist/interface/assets/{back-filled-DUUGErMw.js → back-filled-DcBDBvvP.js} +1 -1
  118. package/dist/interface/assets/{bell-ring-DV73JIre.js → bell-ring-DSX81yd4.js} +1 -1
  119. package/dist/interface/assets/cameras.schema-BWH_Q16D.js +1 -0
  120. package/dist/interface/assets/{cctv-jy5ROx0o.js → cctv-BA5DvNlE.js} +1 -1
  121. package/dist/interface/assets/{check-circle-FwoylV5-.js → check-circle-Do_mBN_A.js} +1 -1
  122. package/dist/interface/assets/{chevron-down-umZ7i0mG.js → chevron-down-D111daTn.js} +1 -1
  123. package/dist/interface/assets/{clock-outline-CbvOAMhO.js → clock-outline-DF1wLNzs.js} +1 -1
  124. package/dist/interface/assets/{close-CtaDF28n.js → close-BNAWPz0y.js} +1 -1
  125. package/dist/interface/assets/{close-circle-FZ8IxGd4.js → close-circle-zhDWvz98.js} +1 -1
  126. package/dist/interface/assets/{cloud-check-CFSukfAF.js → cloud-check-pUK1Fpt9.js} +1 -1
  127. package/dist/interface/assets/cloudHandoff-1DPEACLE.js +1 -0
  128. package/dist/interface/assets/{config-BZ9jkdkW.js → config-Bq4jYnKb.js} +1 -1
  129. package/dist/interface/assets/{copy-CFA_pDRv.js → copy-BPT84cDt.js} +1 -1
  130. package/dist/interface/assets/{cube-scan-DEuMi23T.js → cube-scan-DOe6W9mW.js} +1 -1
  131. package/dist/interface/assets/{delete-921w_nKy.js → delete-BF6keuqy.js} +1 -1
  132. package/dist/interface/assets/{deployed-code-update-CYS6eKfa.js → deployed-code-update-xHE-blDN.js} +1 -1
  133. package/dist/interface/assets/{dist-BwEXwXSw.js → dist-Yc5s0_JB.js} +1 -1
  134. package/dist/interface/assets/{download-C-LzNRmM.js → download-BCPfm6aN.js} +1 -1
  135. package/dist/interface/assets/esm-B3yVlCMw.js +1 -0
  136. package/dist/interface/assets/esm-BKQ7CQK7.js +1 -0
  137. package/dist/interface/assets/esm-BiSE05P9.js +1 -0
  138. package/dist/interface/assets/esm-BoPthGAl.js +1 -0
  139. package/dist/interface/assets/esm-C2ALrdyx.js +1 -0
  140. package/dist/interface/assets/esm-CKEGzys4.js +1 -0
  141. package/dist/interface/assets/esm-CctiCHtK.js +1 -0
  142. package/dist/interface/assets/esm-Cqq81lnk.js +1 -0
  143. package/dist/interface/assets/esm-CyrLQTu0.js +1 -0
  144. package/dist/interface/assets/esm-D6O-eZYo.js +1 -0
  145. package/dist/interface/assets/esm-DG1f0qwo.js +1 -0
  146. package/dist/interface/assets/esm-DGegIZMF.js +1 -0
  147. package/dist/interface/assets/esm-JGwpHVqo.js +1 -0
  148. package/dist/interface/assets/esm-TRigYTsA.js +1 -0
  149. package/dist/interface/assets/{eye-off-DESV3QdF.js → eye-off-CaRq7Ks2.js} +1 -1
  150. package/dist/interface/assets/{flowSchema-lxx7zedO.js → flowSchema-okQoWkh6.js} +1 -1
  151. package/dist/interface/assets/{history-60iCpGvQ.js → history-SuH8IHAj.js} +1 -1
  152. package/dist/interface/assets/{home-w5iY2RUg.js → home-CUykMSBW.js} +1 -1
  153. package/dist/interface/assets/{information-outline-CvBsGtDu.js → information-outline-BQDq8w2t.js} +1 -1
  154. package/dist/interface/assets/{lightbulb-outline-DLrnCPG5.js → lightbulb-outline-ClK17CO6.js} +1 -1
  155. package/dist/interface/assets/main-6B8o-buJ.css +1 -0
  156. package/dist/interface/assets/main-W_rUAd4i.js +3 -0
  157. package/dist/interface/assets/{microphone-DUVI-s2_.js → microphone-C0weJY9W.js} +1 -1
  158. package/dist/interface/assets/minus-RI3VlDe8.js +1 -0
  159. package/dist/interface/assets/{mqtt-DLq_vTiX.js → mqtt-DtPGnb1K.js} +1 -1
  160. package/dist/interface/assets/native-B01iGjCh.js +1 -0
  161. package/dist/interface/assets/native-D381pxlG.js +1 -0
  162. package/dist/interface/assets/{nodeDefinitions-BecZ8I2Q.js → nodeDefinitions-DQQlFmPN.js} +1 -1
  163. package/dist/interface/assets/{notifications-f-Qb41-N.js → notifications-CtZcAwQu.js} +1 -1
  164. package/dist/interface/assets/{pencil-outline-BrOrbuzc.js → pencil-outline-D7aD4cCo.js} +1 -1
  165. package/dist/interface/assets/plugin-W20ihuog.js +1 -0
  166. package/dist/interface/assets/{plugins-COTLBtoZ.js → plugins-BK7kZLJw.js} +1 -1
  167. package/dist/interface/assets/{plus-Dd-XLT76.js → plus-CA5o30dE.js} +1 -1
  168. package/dist/interface/assets/{plus-CL6KSxf8.js → plus-DHwFyXkb.js} +1 -1
  169. package/dist/interface/assets/{power-lCbm_7y7.js → power-C9UIp2oO.js} +1 -1
  170. package/dist/interface/assets/{ring-resize-CEqUkpxT.js → ring-resize-DkY5EgVl.js} +1 -1
  171. package/dist/interface/assets/{round-alt-arrow-left-bold-CwaLYdW5.js → round-alt-arrow-left-bold-BxX4ufd0.js} +1 -1
  172. package/dist/interface/assets/{round-restart-alt-CZSQhHXV.js → round-restart-alt-ctiUuhp8.js} +1 -1
  173. package/dist/interface/assets/{search-CT2BLw7G.js → search-BZkSqZO5.js} +1 -1
  174. package/dist/interface/assets/{sensorIcons-Cg62BVxo.js → sensorIcons-C9h8rHR6.js} +1 -1
  175. package/dist/interface/assets/{sensors-Dxcrwsnf.js → sensors-Bj4drsO3.js} +1 -1
  176. package/dist/interface/assets/{server-BHIoih6c.js → server-D-ogiovS.js} +1 -1
  177. package/dist/interface/assets/{settings-CzS4dvIO.js → settings-BUEZ5iRE.js} +1 -1
  178. package/dist/interface/assets/{source-branch-C6p1G3a9.js → source-branch-ztDB0e13.js} +1 -1
  179. package/dist/interface/assets/{table-2-line-_VC4NuFA.js → table-2-line-B-z0xvbG.js} +1 -1
  180. package/dist/interface/assets/{theme-u1kyGMxl.js → theme-DGxZC_Ti.js} +1 -1
  181. package/dist/interface/assets/{trash-2-DcOYT3oH.js → trash-2-DMMlSPXn.js} +1 -1
  182. package/dist/interface/assets/{trash-can-outline-cphfB8Jh.js → trash-can-outline-DLKniIWM.js} +1 -1
  183. package/dist/interface/assets/{trash-fill-CRBaUdPn.js → trash-fill-DevIK050.js} +1 -1
  184. package/dist/interface/assets/{trash2-CvhB8xOW.js → trash2-BLScx4HX.js} +1 -1
  185. package/dist/interface/assets/{types-BAbqJts5.js → types-B1G3t3XJ.js} +1 -1
  186. package/dist/interface/assets/types-BCygqese.js +1 -0
  187. package/dist/interface/assets/{types-YwmhC3Ej.js → types-vaT6wuY0.js} +1 -1
  188. package/dist/interface/assets/{upload-DeN3O-ua.js → upload-ClkX9wUX.js} +1 -1
  189. package/dist/interface/assets/{useAppVersion-BoISCpsN.js → useAppVersion-Ceg42BEw.js} +1 -1
  190. package/dist/interface/assets/useBackupRestore-BwnYTBUM.js +1 -0
  191. package/dist/interface/assets/{useCameraOptions-9POwrGtQ.js → useCameraOptions-BxVd91Ew.js} +1 -1
  192. package/dist/interface/assets/{useElectron-Cam3T7Zb.js → useElectron-BNGXeZEx.js} +1 -1
  193. package/dist/interface/assets/{useLogsSocket-Bj6kkAHI.js → useLogsSocket-0JucMhVY.js} +1 -1
  194. package/dist/interface/assets/useNavLayout-CTTy15kG.js +1 -0
  195. package/dist/interface/assets/{usePermissions-DYAyOydQ.js → usePermissions-C9kp5HQR.js} +1 -1
  196. package/dist/interface/assets/{usePluginUpdates-b3WpWgyq.js → usePluginUpdates-B9k-vTcT.js} +1 -1
  197. package/dist/interface/assets/{useServerRestart-BQh5xnO1.js → useServerRestart-D7dAWdQ6.js} +1 -1
  198. package/dist/interface/assets/{useStreamStatus-Bw80DXqa.js → useStreamStatus-BlV_fFBy.js} +1 -1
  199. package/dist/interface/assets/useUpdateChannel-BeP49Gm-.js +1 -0
  200. package/dist/interface/assets/useUserOptions-B8UiLDx4.js +1 -0
  201. package/dist/interface/assets/utils-gctUU5q_.js +1 -0
  202. package/dist/interface/assets/{variable-oLSsiEtj.js → variable-CCMnQpL2.js} +1 -1
  203. package/dist/interface/assets/{vendor-JQXDMBA-.js → vendor-BaiDFVEr.js} +17 -16
  204. package/dist/interface/assets/{vendor-cameraui-B1Jik_TX.js → vendor-cameraui-GaXYUHUd.js} +3 -3
  205. package/dist/interface/assets/{vendor-capacitor-CDwgXF4b.js → vendor-capacitor-4nn-eF9U.js} +2 -2
  206. package/dist/interface/assets/{vendor-chart-KrNcA52y.js → vendor-chart-DtwjR6yw.js} +1 -1
  207. package/dist/interface/assets/vendor-core-DNm82LOv.js +1 -0
  208. package/dist/interface/assets/{vendor-dnd-Jch9IvL6.js → vendor-dnd-ChrDrMuz.js} +1 -1
  209. package/dist/interface/assets/{vendor-draggabilly-Cn-knCpG.js → vendor-draggabilly-DSeIE_bQ.js} +1 -1
  210. package/dist/interface/assets/vendor-markdown-BDjboykr.js +14 -0
  211. package/dist/interface/assets/{vendor-primevue-d9Mn2ip-.js → vendor-primevue-CIRUBYsf.js} +76 -76
  212. package/dist/interface/assets/{vendor-vueflow-DQrTmoNS.js → vendor-vueflow-CLJNLJXc.js} +1 -1
  213. package/dist/interface/assets/web-BNRwhIOt.js +1 -0
  214. package/dist/interface/assets/web-BPkOXv4d.js +1 -0
  215. package/dist/interface/assets/web-BZIx_2d3.js +1 -0
  216. package/dist/interface/assets/web-BjKL51lK-BwChM7zd.js +1 -0
  217. package/dist/interface/assets/web-BkmtZjSt.js +1 -0
  218. package/dist/interface/assets/web-C6RgriWz.js +1 -0
  219. package/dist/interface/assets/web-CQFMLa0a.js +1 -0
  220. package/dist/interface/assets/web-CZGnd6VE.js +1 -0
  221. package/dist/interface/assets/web-Cfzay11X.js +1 -0
  222. package/dist/interface/assets/web-DAgBwKL-.js +1 -0
  223. package/dist/interface/assets/web-DgVHh5ek.js +1 -0
  224. package/dist/interface/assets/web-DlSXwZBR.js +1 -0
  225. package/dist/interface/assets/web-eDYxVGH7.js +1 -0
  226. package/dist/interface/assets/web-h4k3EoRQ.js +1 -0
  227. package/dist/interface/assets/{webhook-CMAB4nTl.js → webhook-DQiZUPv8.js} +1 -1
  228. package/dist/interface/assets/workbox-window.prod.es5-CcJiPjAS.js +1 -0
  229. package/dist/interface/assets/{zoom-in-STm8SNt2.js → zoom-in-Du1IB9oA.js} +1 -1
  230. package/dist/interface/index.html +44 -41
  231. package/dist/interface/sw.js +1 -1
  232. package/dist/server/package.json +16 -16
  233. package/dist/server/requirements.txt +2 -2
  234. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/__init__.py +6 -0
  235. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/__init__.py +0 -2
  236. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/detection.py +2 -0
  237. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/events.py +11 -47
  238. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/camera/frames.py +2 -4
  239. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/plugin/contract.py +1 -1
  240. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/plugin/notifier.py +7 -0
  241. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/classifier.py +14 -8
  242. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/face.py +23 -7
  243. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/sensor/license_plate.py +23 -7
  244. package/dist/server/src/_camera_ui_tools/camera_ui_sdk/storage/__init__.py +60 -2
  245. package/dist/server/src/api/controllers/auth.controller.js +2 -0
  246. package/dist/server/src/api/controllers/auth.controller.js.map +1 -1
  247. package/dist/server/src/api/controllers/nvr.controller.d.ts +0 -3
  248. package/dist/server/src/api/controllers/nvr.controller.js +8 -38
  249. package/dist/server/src/api/controllers/nvr.controller.js.map +1 -1
  250. package/dist/server/src/api/database/index.d.ts +3 -2
  251. package/dist/server/src/api/database/index.js +12 -2
  252. package/dist/server/src/api/database/index.js.map +1 -1
  253. package/dist/server/src/api/database/migrations/v2-0-50.js +2 -1
  254. package/dist/server/src/api/database/migrations/v2-0-50.js.map +1 -1
  255. package/dist/server/src/api/database/migrations/v2-1-1.d.ts +3 -0
  256. package/dist/server/src/api/database/migrations/v2-1-1.js +35 -0
  257. package/dist/server/src/api/database/migrations/v2-1-1.js.map +1 -0
  258. package/dist/server/src/api/database/migrations/v2-1-4.d.ts +3 -0
  259. package/dist/server/src/api/database/migrations/v2-1-4.js +32 -0
  260. package/dist/server/src/api/database/migrations/v2-1-4.js.map +1 -0
  261. package/dist/server/src/api/database/record-schemas.d.ts +3 -0
  262. package/dist/server/src/api/database/record-schemas.js +1 -0
  263. package/dist/server/src/api/database/record-schemas.js.map +1 -1
  264. package/dist/server/src/api/database/types.d.ts +6 -0
  265. package/dist/server/src/api/schemas/cameras.schema.d.ts +24 -24
  266. package/dist/server/src/api/schemas/cameras.schema.js +5 -5
  267. package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
  268. package/dist/server/src/api/schemas/config.schema.d.ts +1 -0
  269. package/dist/server/src/api/schemas/config.schema.js +1 -0
  270. package/dist/server/src/api/schemas/config.schema.js.map +1 -1
  271. package/dist/server/src/api/schemas/users.schema.d.ts +77 -1
  272. package/dist/server/src/api/schemas/users.schema.js +27 -0
  273. package/dist/server/src/api/schemas/users.schema.js.map +1 -1
  274. package/dist/server/src/api/services/auth.service.js +28 -23
  275. package/dist/server/src/api/services/auth.service.js.map +1 -1
  276. package/dist/server/src/api/services/automations.service.js +36 -36
  277. package/dist/server/src/api/services/automations.service.js.map +1 -1
  278. package/dist/server/src/api/services/backup.service.js +29 -26
  279. package/dist/server/src/api/services/backup.service.js.map +1 -1
  280. package/dist/server/src/api/services/backupScheduler.service.js +1 -2
  281. package/dist/server/src/api/services/backupScheduler.service.js.map +1 -1
  282. package/dist/server/src/api/services/cameras.service.js +222 -173
  283. package/dist/server/src/api/services/cameras.service.js.map +1 -1
  284. package/dist/server/src/api/services/mqtt.service.js +1 -2
  285. package/dist/server/src/api/services/mqtt.service.js.map +1 -1
  286. package/dist/server/src/api/services/notifications.service.d.ts +1 -0
  287. package/dist/server/src/api/services/notifications.service.js +3 -0
  288. package/dist/server/src/api/services/notifications.service.js.map +1 -1
  289. package/dist/server/src/api/services/plugins.service.js +1 -1
  290. package/dist/server/src/api/services/plugins.service.js.map +1 -1
  291. package/dist/server/src/api/services/remote.service.js +26 -25
  292. package/dist/server/src/api/services/remote.service.js.map +1 -1
  293. package/dist/server/src/api/services/server.service.js +9 -7
  294. package/dist/server/src/api/services/server.service.js.map +1 -1
  295. package/dist/server/src/api/services/shares.service.js +19 -14
  296. package/dist/server/src/api/services/shares.service.js.map +1 -1
  297. package/dist/server/src/api/services/users.service.js +161 -105
  298. package/dist/server/src/api/services/users.service.js.map +1 -1
  299. package/dist/server/src/api/services/workers.service.d.ts +1 -0
  300. package/dist/server/src/api/services/workers.service.js +55 -45
  301. package/dist/server/src/api/services/workers.service.js.map +1 -1
  302. package/dist/server/src/api/types/index.d.ts +8 -0
  303. package/dist/server/src/api/types/index.js.map +1 -1
  304. package/dist/server/src/api/utils/cert.js +8 -0
  305. package/dist/server/src/api/utils/cert.js.map +1 -1
  306. package/dist/server/src/api/websocket/nsp/server.js +1 -1
  307. package/dist/server/src/api/websocket/nsp/server.js.map +1 -1
  308. package/dist/server/src/automations/conditions/sensorState.js +9 -0
  309. package/dist/server/src/automations/conditions/sensorState.js.map +1 -1
  310. package/dist/server/src/camera/controller.d.ts +1 -1
  311. package/dist/server/src/camera/controller.js +12 -11
  312. package/dist/server/src/camera/controller.js.map +1 -1
  313. package/dist/server/src/camera/decoder/child.d.ts +1 -0
  314. package/dist/server/src/camera/decoder/child.js +14 -1
  315. package/dist/server/src/camera/decoder/child.js.map +1 -1
  316. package/dist/server/src/camera/decoder/debug/detection-record.d.ts +42 -0
  317. package/dist/server/src/camera/decoder/debug/detection-record.js +103 -0
  318. package/dist/server/src/camera/decoder/debug/detection-record.js.map +1 -0
  319. package/dist/server/src/camera/decoder/debug/perf-tracker.d.ts +20 -0
  320. package/dist/server/src/camera/decoder/debug/perf-tracker.js +82 -0
  321. package/dist/server/src/camera/decoder/debug/perf-tracker.js.map +1 -0
  322. package/dist/server/src/camera/decoder/detection-coordinator.d.ts +28 -6
  323. package/dist/server/src/camera/decoder/detection-coordinator.js +495 -94
  324. package/dist/server/src/camera/decoder/detection-coordinator.js.map +1 -1
  325. package/dist/server/src/camera/decoder/detection-pipeline.d.ts +7 -20
  326. package/dist/server/src/camera/decoder/detection-pipeline.js +67 -127
  327. package/dist/server/src/camera/decoder/detection-pipeline.js.map +1 -1
  328. package/dist/server/src/camera/decoder/event-manager.d.ts +42 -14
  329. package/dist/server/src/camera/decoder/event-manager.js +304 -217
  330. package/dist/server/src/camera/decoder/event-manager.js.map +1 -1
  331. package/dist/server/src/camera/decoder/event-thumbnailer.d.ts +5 -8
  332. package/dist/server/src/camera/decoder/event-thumbnailer.js +11 -21
  333. package/dist/server/src/camera/decoder/event-thumbnailer.js.map +1 -1
  334. package/dist/server/src/camera/decoder/frame-scaler.d.ts +2 -1
  335. package/dist/server/src/camera/decoder/frame-scaler.js +9 -0
  336. package/dist/server/src/camera/decoder/frame-scaler.js.map +1 -1
  337. package/dist/server/src/camera/decoder/moment-crop.d.ts +30 -0
  338. package/dist/server/src/camera/decoder/moment-crop.js +86 -0
  339. package/dist/server/src/camera/decoder/moment-crop.js.map +1 -0
  340. package/dist/server/src/camera/decoder/nvr-sink.d.ts +38 -0
  341. package/dist/server/src/camera/decoder/nvr-sink.js +36 -0
  342. package/dist/server/src/camera/decoder/nvr-sink.js.map +1 -0
  343. package/dist/server/src/camera/decoder/secondary-stage.d.ts +4 -2
  344. package/dist/server/src/camera/decoder/secondary-stage.js +58 -96
  345. package/dist/server/src/camera/decoder/secondary-stage.js.map +1 -1
  346. package/dist/server/src/camera/decoder/sources/analysis-source.d.ts +10 -0
  347. package/dist/server/src/camera/decoder/sources/analysis-source.js +2 -0
  348. package/dist/server/src/camera/decoder/sources/analysis-source.js.map +1 -0
  349. package/dist/server/src/camera/decoder/sources/{snapshot-source.d.ts → buffered-source.d.ts} +17 -5
  350. package/dist/server/src/camera/decoder/sources/{snapshot-source.js → buffered-source.js} +81 -24
  351. package/dist/server/src/camera/decoder/sources/buffered-source.js.map +1 -0
  352. package/dist/server/src/camera/decoder/sources/frame-source.d.ts +4 -7
  353. package/dist/server/src/camera/decoder/sources/frame-source.js +4 -7
  354. package/dist/server/src/camera/decoder/sources/frame-source.js.map +1 -1
  355. package/dist/server/src/camera/decoder/types.d.ts +1 -0
  356. package/dist/server/src/camera/decoder/types.js.map +1 -1
  357. package/dist/server/src/camera/decoder/worker.js +15 -3
  358. package/dist/server/src/camera/decoder/worker.js.map +1 -1
  359. package/dist/server/src/internal-bus.d.ts +1 -1
  360. package/dist/server/src/internal-bus.js.map +1 -1
  361. package/dist/server/src/main.js +1 -1
  362. package/dist/server/src/main.js.map +1 -1
  363. package/dist/server/src/manager/discoveryManager.js +0 -11
  364. package/dist/server/src/manager/discoveryManager.js.map +1 -1
  365. package/dist/server/src/manager/notificationManager.d.ts +1 -1
  366. package/dist/server/src/manager/notificationManager.js +60 -41
  367. package/dist/server/src/manager/notificationManager.js.map +1 -1
  368. package/dist/server/src/mqtt/bridge.d.ts +1 -0
  369. package/dist/server/src/mqtt/bridge.js +32 -28
  370. package/dist/server/src/mqtt/bridge.js.map +1 -1
  371. package/dist/server/src/plugins/nvr.d.ts +2 -0
  372. package/dist/server/src/plugins/nvr.js +17 -0
  373. package/dist/server/src/plugins/nvr.js.map +1 -0
  374. package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js +1 -1
  375. package/dist/server/src/plugins/runtime/node/proxy/sensorManager.js.map +1 -1
  376. package/dist/server/src/plugins/runtime/python/storage.py +18 -1
  377. package/dist/server/src/plugins/runtime/python/store.py +7 -1
  378. package/dist/server/src/plugins/schema.d.ts +2 -1
  379. package/dist/server/src/plugins/schema.js +66 -6
  380. package/dist/server/src/plugins/schema.js.map +1 -1
  381. package/dist/server/src/plugins/store/storeFile.js +10 -2
  382. package/dist/server/src/plugins/store/storeFile.js.map +1 -1
  383. package/dist/server/src/remote/services/index.js +1 -1
  384. package/dist/server/src/remote/services/index.js.map +1 -1
  385. package/dist/server/src/rpc/interfaces/frameworker.d.ts +1 -0
  386. package/dist/server/src/rpc/server.js +6 -0
  387. package/dist/server/src/rpc/server.js.map +1 -1
  388. package/dist/server/src/sensors/sensor.js +13 -1
  389. package/dist/server/src/sensors/sensor.js.map +1 -1
  390. package/dist/server/src/services/config/constants.js +1 -0
  391. package/dist/server/src/services/config/constants.js.map +1 -1
  392. package/dist/server/src/services/config/index.d.ts +5 -2
  393. package/dist/server/src/services/config/index.js +38 -5
  394. package/dist/server/src/services/config/index.js.map +1 -1
  395. package/dist/server/src/services/config/types.d.ts +4 -0
  396. package/dist/server/src/utils/path.d.ts +1 -0
  397. package/dist/server/src/utils/path.js +33 -0
  398. package/dist/server/src/utils/path.js.map +1 -1
  399. package/dist/server/src/workers/capabilities/frame-decoding.js +1 -1
  400. package/dist/server/src/workers/capabilities/frame-decoding.js.map +1 -1
  401. package/package.json +16 -16
  402. package/dist/interface/assets/Adminpanel-D6LLMvtn.js +0 -1
  403. package/dist/interface/assets/Camera-CFfDLaKb.js +0 -2
  404. package/dist/interface/assets/Camera-yUrhUGmO.css +0 -1
  405. package/dist/interface/assets/Cameras-Bgdc-8pX.js +0 -652
  406. package/dist/interface/assets/Camview-DxdH-H8m.js +0 -1
  407. package/dist/interface/assets/Camview-mLCaY0ld.css +0 -1
  408. package/dist/interface/assets/Config-CG2z9ZDM.js +0 -2
  409. package/dist/interface/assets/ConfigConditionSensorState-CMusMag_.js +0 -1
  410. package/dist/interface/assets/ConfigSensorValueInput-CJ71pU5t.js +0 -1
  411. package/dist/interface/assets/ConfigTriggerSensor-CEtUqINm.js +0 -1
  412. package/dist/interface/assets/Console-pO4hRXJ6.js +0 -1
  413. package/dist/interface/assets/CuiAutomationRunsDialog-C_BN8ib3.js +0 -1
  414. package/dist/interface/assets/CuiBottomSheet-CU5V52RZ.js +0 -1
  415. package/dist/interface/assets/CuiCameraPipCard-C5UckhjZ.js +0 -1
  416. package/dist/interface/assets/CuiCameraRecordings-BHj7CUE7.css +0 -1
  417. package/dist/interface/assets/CuiCameraRecordings-D4FRIiLH.js +0 -1
  418. package/dist/interface/assets/CuiDataTable-3GW8h063.js +0 -1
  419. package/dist/interface/assets/CuiLightControl-e9fXnEWM.js +0 -1
  420. package/dist/interface/assets/CuiMarkdownContent-CiT0VB6c.js +0 -1
  421. package/dist/interface/assets/CuiPluginCard-BhuhDtto.js +0 -2
  422. package/dist/interface/assets/CuiRecordingsGrid-CwuvwFQi.js +0 -1
  423. package/dist/interface/assets/CuiRecordingsGrid-DGCTfTo9.css +0 -1
  424. package/dist/interface/assets/CuiSchema-Dg3Y0q8q.js +0 -1
  425. package/dist/interface/assets/Faces-CY_8TVkm.js +0 -1
  426. package/dist/interface/assets/FirstSteps-CjetT9eW.js +0 -1
  427. package/dist/interface/assets/Instances-CEflWTSB.js +0 -1
  428. package/dist/interface/assets/Menu-voSWnkVE.js +0 -1
  429. package/dist/interface/assets/Plugin-BLqyZrJP.css +0 -1
  430. package/dist/interface/assets/Plugin-DAAyIdO6.js +0 -1
  431. package/dist/interface/assets/PluginDetail-aDecfgRt.js +0 -2
  432. package/dist/interface/assets/PluginSearch-ChiHBh7O.js +0 -2
  433. package/dist/interface/assets/PluginSearch-vCzkUW8O.js +0 -1
  434. package/dist/interface/assets/Plugins-CT0UUtSr.js +0 -1
  435. package/dist/interface/assets/Recordings-DdAO90wn.js +0 -1
  436. package/dist/interface/assets/SensorEdit-BJpoqdK8.js +0 -1
  437. package/dist/interface/assets/Sensors-C6U8ckWK.js +0 -2
  438. package/dist/interface/assets/Settings-JLdVUq7Z.js +0 -1
  439. package/dist/interface/assets/SettingsAccount-CqJAqaK5.js +0 -1
  440. package/dist/interface/assets/SettingsAccount-lem3U0i3.js +0 -1
  441. package/dist/interface/assets/SettingsAppearance-B4B_oSjk.js +0 -1
  442. package/dist/interface/assets/SettingsAppearance-DIbztL4M.js +0 -1
  443. package/dist/interface/assets/SettingsBackup-BNIufsOw.js +0 -1
  444. package/dist/interface/assets/SettingsList-D6r5S91a.js +0 -1
  445. package/dist/interface/assets/SettingsMqtt-BsfRfFaN.js +0 -1
  446. package/dist/interface/assets/SettingsRecordings-DoDsjj0I.js +0 -1
  447. package/dist/interface/assets/SettingsRemote-DmYD8t4k.js +0 -1
  448. package/dist/interface/assets/SettingsSystem-QnpE8IdB.js +0 -2
  449. package/dist/interface/assets/Terminal-DbAqY4Hb.js +0 -4
  450. package/dist/interface/assets/VersionsHandler-DVnhikOd.js +0 -7
  451. package/dist/interface/assets/Workers-C4ee5Qu6.js +0 -2
  452. package/dist/interface/assets/api-CNl5E-Z0.js +0 -2
  453. package/dist/interface/assets/api-DBqh9A2C.css +0 -2
  454. package/dist/interface/assets/cameras.schema-BD1hnsDj.js +0 -1
  455. package/dist/interface/assets/chevron-right-BOQr3ruh.js +0 -1
  456. package/dist/interface/assets/cloudHandoff-C3N4uaQY.js +0 -1
  457. package/dist/interface/assets/esm-B1LZs0VG.js +0 -1
  458. package/dist/interface/assets/esm-BB4SBaek.js +0 -1
  459. package/dist/interface/assets/esm-BGA8Ydne.js +0 -1
  460. package/dist/interface/assets/esm-BHI_KH7L.js +0 -1
  461. package/dist/interface/assets/esm-BQ0EuuZe.js +0 -1
  462. package/dist/interface/assets/esm-Bfb-tuKt.js +0 -1
  463. package/dist/interface/assets/esm-BzR_6ma5.js +0 -1
  464. package/dist/interface/assets/esm-CCIqNfsh.js +0 -1
  465. package/dist/interface/assets/esm-CSMN-8o1.js +0 -1
  466. package/dist/interface/assets/esm-CxWbrBxP.js +0 -1
  467. package/dist/interface/assets/esm-DBTrmJK6.js +0 -1
  468. package/dist/interface/assets/esm-DRswZUWf.js +0 -1
  469. package/dist/interface/assets/esm-Dk7gxPHs.js +0 -1
  470. package/dist/interface/assets/esm-P0RCkAtk.js +0 -1
  471. package/dist/interface/assets/main-DJb49Tjp.css +0 -1
  472. package/dist/interface/assets/main-nZuJbwRm.js +0 -3
  473. package/dist/interface/assets/native-Cr4dkfqy.js +0 -1
  474. package/dist/interface/assets/native-D1Vvlhds.js +0 -1
  475. package/dist/interface/assets/plugin-937qnq7E.js +0 -1
  476. package/dist/interface/assets/types-BHs28Xd0.js +0 -1
  477. package/dist/interface/assets/useBackupRestore-Sw6F6lrg.js +0 -1
  478. package/dist/interface/assets/useUserOptions-A6noG3pQ.js +0 -1
  479. package/dist/interface/assets/vendor-core-D7dApN9I.js +0 -1
  480. package/dist/interface/assets/vendor-markdown-BhFLJolW.js +0 -14
  481. package/dist/interface/assets/web-BQtDHNew.js +0 -1
  482. package/dist/interface/assets/web-BY9kjyTj.js +0 -1
  483. package/dist/interface/assets/web-BcQtDSat.js +0 -1
  484. package/dist/interface/assets/web-BgrRQzLO.js +0 -1
  485. package/dist/interface/assets/web-BjKL51lK-CJYmHsnY.js +0 -1
  486. package/dist/interface/assets/web-BxvsJylk.js +0 -1
  487. package/dist/interface/assets/web-C-3iwDhG.js +0 -1
  488. package/dist/interface/assets/web-C9XOwu07.js +0 -1
  489. package/dist/interface/assets/web-CPCxRDtw.js +0 -1
  490. package/dist/interface/assets/web-D0U5B2i2.js +0 -1
  491. package/dist/interface/assets/web-DtxjXJdS.js +0 -1
  492. package/dist/interface/assets/web-Duf-oV4e.js +0 -1
  493. package/dist/interface/assets/web-jy45nBi_.js +0 -1
  494. package/dist/interface/assets/web-vWx_-Iq5.js +0 -1
  495. package/dist/interface/assets/workbox-window.prod.es5-Dlqax_Po.js +0 -1
  496. package/dist/server/src/api/utils/moveFiles.d.ts +0 -4
  497. package/dist/server/src/api/utils/moveFiles.js +0 -37
  498. package/dist/server/src/api/utils/moveFiles.js.map +0 -1
  499. package/dist/server/src/camera/decoder/sources/snapshot-source.js.map +0 -1
  500. package/dist/server/src/camera/decoder/stationary-suppressor.d.ts +0 -38
  501. package/dist/server/src/camera/decoder/stationary-suppressor.js +0 -308
  502. 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.config.frameWorkerSettings.hqSnapshots === true);
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
- if (!this.dwell.hasActive()) {
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(settings.hqSnapshots === true);
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.add(sensor.sensorId);
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
- if (this.feedingSensors.delete(sensorId)) {
219
- this.writeSensorProperties(sensorId, { detected: false, detections: [] });
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
- // face/plate crops feed the segment attributes
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
- this.cascade.triggerMomentary(this.cascadeTimeoutSeconds);
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, { detected: false, detections: [] });
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 ? this.stationary.excludeSealed(cs.object.detections) : [],
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: Date.now(),
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
- this.cascade.triggerMomentary(this.cascadeTimeoutSeconds);
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 && detected) {
557
- // the dwell bridges single missed frames; anchored stationary tracks
558
- // don't refresh it, so parked objects can't hold events open
559
- const detections = properties.detections ?? [];
560
- if (this.stationary.evaluate(detections)) {
561
- this.dwell.refresh(sensorId, OBJECT_DWELL_SECONDS);
562
- // a confirmed object re-arms the cascade like motion does: a time-based
563
- // motion sensor (ONVIF cool-down) must not blind detection and split
564
- // the event while someone is still mid-frame
565
- if (this.cascadeEnabled) {
566
- this.cascade.triggerMomentary(this.cascadeTimeoutSeconds);
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
- else {
570
- // clear detected + buffer so no segment opens, the UI still gets
571
- // the bboxes via the property publish below
572
- properties.detected = false;
573
- if (this.currentDetectionState.object) {
574
- this.currentDetectionState.object.detected = false;
575
- this.currentDetectionState.object.detections = [];
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
- this.stationary.logSuppressedOnce(detections);
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
- // image-space anchors don't survive a camera move, they re-form after
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
- async processRawFrame(rawFrame) {
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(rawFrame);
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(rawFrame) {
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
- const motionFrame = await this.scaleForMotion(rawFrame);
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 objectFrame = await this.scaleForObject(rawFrame);
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, objectFrame.tracking, poseDelta);
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}${this.stationary.isAnchored(id) ? ' (anchored!)' : ''}`);
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
- await this.runSecondariesAndThumbnails(rawFrame, objectDetections, results);
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 thumb = await this.thumbnailer.captureEventThumbnail(rawFrame);
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(rawFrame, objectDetections, results) {
1013
- objectDetections = this.stationary.excludeSealed(objectDetections);
1223
+ async runSecondariesAndThumbnails(analysis, objectDetections, results) {
1014
1224
  if (objectDetections.length === 0)
1015
1225
  return;
1016
- await this.secondaries.detect(rawFrame, objectDetections, results);
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
- // detectors ran on the substream frame, thumbnails prefer the HQ frame
1019
- const hq = await this.thumbnailer.acquireHqFrame({ width: rawFrame.width, height: rawFrame.height });
1020
- try {
1021
- results.thumbnails = await this.secondaries.generateThumbnails(hq?.frame ?? rawFrame, hq?.scaler ?? this.frameScaler, hq !== null, objectDetections, results);
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 this.frameScaler.letterboxToSpec(rawFrame, inputSpec);
1532
+ const letterboxed = await scaler.letterboxToSpec(rawFrame, inputSpec);
1132
1533
  if (!letterboxed)
1133
1534
  return undefined;
1134
1535
  return {
1135
- model: this.frameScaler.toVideoFrameData(letterboxed.padded, 'model'),
1136
- tracking: this.frameScaler.toVideoFrameData(letterboxed.inner, 'tracking'),
1536
+ model: scaler.toVideoFrameData(letterboxed.padded, 'model'),
1537
+ tracking: scaler.toVideoFrameData(letterboxed.inner, 'tracking'),
1137
1538
  geometry: letterboxed.geometry,
1138
1539
  };
1139
1540
  }