@camera.ui/server 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/interface/{@tanstack.BjdVg-TE.0.0.16.js → @tanstack.Y8LHKTFF.0.0.17.js} +1 -1
- package/dist/interface/{@vee-validate.BY4_2lEs.0.0.16.js → @vee-validate.BcG8e5-Z.0.0.17.js} +1 -1
- package/dist/interface/{@vueform.DFvJBWIi.0.0.16.js → @vueform.Cnqje5-g.0.0.17.js} +1 -1
- package/dist/interface/{@vueuse.CGN12qog.0.0.16.js → @vueuse.CxdA24o0.0.0.17.js} +1 -1
- package/dist/interface/Account.K0D29-KE.0.0.17.js +1 -0
- package/dist/interface/{Adminpanel.FTycvGGP.0.0.16.js → Adminpanel.CaLBYPHc.0.0.17.js} +2 -2
- package/dist/interface/{Appearance.DZdFEn29.0.0.16.js → Appearance.-L_mn_-o.0.0.17.js} +1 -1
- package/dist/interface/{Backup.C63DnD6l.0.0.16.js → Backup.DjU1SUNp.0.0.17.js} +1 -1
- package/dist/interface/{Camera.ClZr_XH5.0.0.16.js → Camera.BgoyeEmQ.0.0.17.js} +1 -1
- package/dist/interface/Cameras.CTzvFhZB.0.0.17.js +1 -0
- package/dist/interface/{Camview.Bo4mLtoT.0.0.16.js → Camview.AIHZOcpe.0.0.17.js} +1 -1
- package/dist/interface/Config.Dwreik6Q.0.0.17.js +1 -0
- package/dist/interface/{Console.8oN5H7vV.0.0.16.js → Console.65ILbQYd.0.0.17.js} +1 -1
- package/dist/interface/FirstSteps.CLRJ9qAX.0.0.17.js +1 -0
- package/dist/interface/{NewCamera.0K90OojR.0.0.16.js → NewCamera.C3vbU4C7.0.0.17.js} +1 -1
- package/dist/interface/Plugin.B5hYYBYQ.0.0.17.js +1 -0
- package/dist/interface/{Plugins.49uEjmzr.0.0.16.js → Plugins.CfRQtEq_.0.0.17.js} +1 -1
- package/dist/interface/Recordings.BbFFnSvi.0.0.17.js +1 -0
- package/dist/interface/Recordings.BfuhtdIj.0.0.17.js +1 -0
- package/dist/interface/{Settings.LCEZwyhh.0.0.16.js → Settings.BdOXqaEX.0.0.17.js} +1 -1
- package/dist/interface/System.ysu799BZ.0.0.17.js +4 -0
- package/dist/interface/User.D7asFmwE.0.0.17.js +1 -0
- package/dist/interface/{VConfigEditor.vue_vue_type_script_setup_true_lang.J5gKGR9C.0.0.16.js → VConfigEditor.vue_vue_type_script_setup_true_lang.BAxsYS8n.0.0.17.js} +2 -2
- package/dist/interface/{VConsole.BStq4qh-.0.0.16.js → VConsole.C7xGb1ES.0.0.17.js} +1 -1
- package/dist/interface/VPlayer.Dhm7j4pg.0.0.17.js +1 -0
- package/dist/interface/{VPluginCard.DR63SC8V.0.0.16.js → VPluginCard.Drmi3fPn.0.0.17.js} +1 -1
- package/dist/interface/{ace-builds.BhnhUUTY.0.0.16.js → ace-builds.BwzeyovQ.0.0.17.js} +1 -1
- package/dist/interface/assets/{Account.0.0.16.2KfYXKTQ.css → Account.0.0.17.BCEftDNE.css} +1 -1
- package/dist/interface/assets/{Cameras.0.0.16.D7bnvH42.css → Cameras.0.0.17.Djx0F2MM.css} +1 -1
- package/dist/interface/assets/{Camview.0.0.16.cgjDTjXy.css → Camview.0.0.17.Ce_V-Nxj.css} +1 -1
- package/dist/interface/assets/{Config.0.0.16.BQJF6s_V.css → Config.0.0.17.k4QpWumg.css} +1 -1
- package/dist/interface/assets/FirstSteps.0.0.17.27JF_jaE.css +1 -0
- package/dist/interface/assets/{Plugin.0.0.16.BGZE7hpr.css → Plugin.0.0.17.CY2rlLw0.css} +1 -1
- package/dist/interface/assets/User.0.0.17.DAaNwzw2.css +1 -0
- package/dist/interface/assets/{VPlayer.0.0.16.CzZme_fu.css → VPlayer.0.0.17.CY8lPcC-.css} +1 -1
- package/dist/interface/assets/{main.0.0.16.DYbE3rQA.css → main.0.0.17.CkZIZCwa.css} +1 -1
- package/dist/interface/axios.CpM4fnec.0.0.17.js +6 -0
- package/dist/interface/{dnd-core.B69q2Tfk.0.0.16.js → dnd-core.BE3BG1Ef.0.0.17.js} +1 -1
- package/dist/interface/{draggabilly.DKBJ_DRn.0.0.16.js → draggabilly.lOopoogm.0.0.17.js} +1 -1
- package/dist/interface/{engine.io-client.D47Vn3PY.0.0.16.js → engine.io-client.D9WQML-S.0.0.17.js} +1 -1
- package/dist/interface/{ev-emitter.CO554XzW.0.0.16.js → ev-emitter.DS-zFO5E.0.0.17.js} +1 -1
- package/dist/interface/{fast-deep-equal.CjfEj15C.0.0.16.js → fast-deep-equal.BEv36PJS.0.0.17.js} +1 -1
- package/dist/interface/{highlight.js.CsG_3GKA.0.0.16.js → highlight.js.7wLgKNe8.0.0.17.js} +1 -1
- package/dist/interface/index.html +33 -33
- package/dist/interface/{linkify-it.D-Re04U7.0.0.16.js → linkify-it.D8o-3IGu.0.0.17.js} +1 -1
- package/dist/interface/main.tEGILaMo.0.0.17.js +9 -0
- package/dist/interface/manifest.webmanifest +1 -1
- package/dist/interface/{markdown-it.BSj0kuDJ.0.0.16.js → markdown-it.B4HcXc4c.0.0.17.js} +1 -1
- package/dist/interface/{nprogress.CN7SpneM.0.0.16.js → nprogress.YYjE43ko.0.0.17.js} +1 -1
- package/dist/interface/{pinia.DHXyXuKc.0.0.16.js → pinia.BN10dGtz.0.0.17.js} +1 -1
- package/dist/interface/{plugins.FSy44vXn.0.0.16.js → plugins.DGem6WnK.0.0.17.js} +1 -1
- package/dist/interface/{qrcode.DjqZuH8F.0.0.16.js → qrcode.D-tn4nkj.0.0.17.js} +1 -1
- package/dist/interface/{react-dnd-touch-backend.Cecw_0Kl.0.0.16.js → react-dnd-touch-backend.BYa4rNzf.0.0.17.js} +1 -1
- package/dist/interface/{rxjs.tqVqva9T.0.0.16.js → rxjs.DfibtzOq.0.0.17.js} +1 -1
- package/dist/interface/{socket.io-client.BFAMFJUC.0.0.16.js → socket.io-client.C_uPgwFs.0.0.17.js} +1 -1
- package/dist/interface/{socket.io-parser.B5BeMqb6.0.0.16.js → socket.io-parser.BCv62i4g.0.0.17.js} +1 -1
- package/dist/interface/sw.js +1 -1
- package/dist/interface/system.QcA_yDW7.0.0.17.js +1 -0
- package/dist/interface/{ui.CcXHo2vy.0.0.16.js → ui.CsoNsn0g.0.0.17.js} +1 -1
- package/dist/interface/{unidragger.B3Aj7lJB.0.0.16.js → unidragger.CE1ihPjj.0.0.17.js} +1 -1
- package/dist/interface/{v-calendar.y5PNpUm6.0.0.16.js → v-calendar.uhLzWnBK.0.0.17.js} +1 -1
- package/dist/interface/{vee-validate.VFk2ifr-.0.0.16.js → vee-validate.Dks-nw4k.0.0.17.js} +1 -1
- package/dist/interface/{vue-i18n.BRzZ63Xi.0.0.16.js → vue-i18n.C41_xexi.0.0.17.js} +1 -1
- package/dist/interface/{vue-inline-svg.De0HvU9m.0.0.16.js → vue-inline-svg.BKM1fqUr.0.0.17.js} +1 -1
- package/dist/interface/{vue-router.Cr6qPC8Y.0.0.16.js → vue-router.BPduPoYE.0.0.17.js} +1 -1
- package/dist/interface/{vue3-apexcharts.BMb8nnby.0.0.16.js → vue3-apexcharts.CUMFs3zS.0.0.17.js} +1 -1
- package/dist/interface/{vue3-dnd.SEoyozNj.0.0.16.js → vue3-dnd.BNLgSXFo.0.0.17.js} +1 -1
- package/dist/interface/{vue3-toastify.DUisHb3e.0.0.16.js → vue3-toastify.D4rGDrYT.0.0.17.js} +1 -1
- package/dist/interface/{vuetify.DByERR9S.0.0.16.js → vuetify.C_gaTn8T.0.0.17.js} +1 -1
- package/dist/server/bin/cameraui.d.ts +4 -0
- package/dist/server/bin/cameraui.js +47 -33
- package/dist/server/bin/cameraui.js.map +1 -1
- package/dist/server/bin/installer/darwin.d.ts +4 -0
- package/dist/server/bin/installer/darwin.js +37 -29
- package/dist/server/bin/installer/darwin.js.map +1 -1
- package/dist/server/bin/installer/freebsd.d.ts +4 -5
- package/dist/server/bin/installer/freebsd.js +23 -33
- package/dist/server/bin/installer/freebsd.js.map +1 -1
- package/dist/server/bin/installer/linux.d.ts +7 -0
- package/dist/server/bin/installer/linux.js +53 -27
- package/dist/server/bin/installer/linux.js.map +1 -1
- package/dist/server/bin/installer/win32.d.ts +4 -0
- package/dist/server/bin/installer/win32.js +21 -14
- package/dist/server/bin/installer/win32.js.map +1 -1
- package/dist/server/package.json +4 -4
- package/dist/server/src/api/controllers/backup.controller.js +2 -2
- package/dist/server/src/api/controllers/backup.controller.js.map +1 -1
- package/dist/server/src/api/controllers/plugins.controller.js +42 -40
- package/dist/server/src/api/controllers/plugins.controller.js.map +1 -1
- package/dist/server/src/api/controllers/system.controller.d.ts +3 -2
- package/dist/server/src/api/controllers/system.controller.js +19 -21
- package/dist/server/src/api/controllers/system.controller.js.map +1 -1
- package/dist/server/src/api/controllers/users.controller.js +12 -1
- package/dist/server/src/api/controllers/users.controller.js.map +1 -1
- package/dist/server/src/api/database/index.js +5 -4
- package/dist/server/src/api/database/index.js.map +1 -1
- package/dist/server/src/api/database/types.d.ts +7 -2
- package/dist/server/src/api/plugins/system.plugin.js +1 -4
- package/dist/server/src/api/plugins/system.plugin.js.map +1 -1
- package/dist/server/src/api/routes/system.routes.js +2 -2
- package/dist/server/src/api/routes/system.routes.js.map +1 -1
- package/dist/server/src/api/schemas/cameras.schema.d.ts +15 -15
- package/dist/server/src/api/schemas/cameras.schema.js +2 -2
- package/dist/server/src/api/schemas/cameras.schema.js.map +1 -1
- package/dist/server/src/api/schemas/go2rtc.schema.d.ts +14 -14
- package/dist/server/src/api/schemas/users.schema.d.ts +2 -2
- package/dist/server/src/api/schemas/users.schema.js +4 -15
- package/dist/server/src/api/schemas/users.schema.js.map +1 -1
- package/dist/server/src/api/services/backup.service.js +16 -15
- package/dist/server/src/api/services/backup.service.js.map +1 -1
- package/dist/server/src/api/services/cameras.service.js +10 -2
- package/dist/server/src/api/services/cameras.service.js.map +1 -1
- package/dist/server/src/api/utils/cert.d.ts +1 -1
- package/dist/server/src/api/utils/cert.js +14 -14
- package/dist/server/src/api/utils/cert.js.map +1 -1
- package/dist/server/src/api/utils/moveFiles.js +6 -5
- package/dist/server/src/api/utils/moveFiles.js.map +1 -1
- package/dist/server/src/api/websocket/nsp/logs.js +5 -4
- package/dist/server/src/api/websocket/nsp/logs.js.map +1 -1
- package/dist/server/src/api/websocket/nsp/server.js +3 -2
- package/dist/server/src/api/websocket/nsp/server.js.map +1 -1
- package/dist/server/src/camera/index.js +2 -0
- package/dist/server/src/camera/index.js.map +1 -1
- package/dist/server/src/camera/iou.d.ts +2 -0
- package/dist/server/src/camera/iou.js +53 -0
- package/dist/server/src/camera/iou.js.map +1 -0
- package/dist/server/src/camera/streaming/peer-connection.d.ts +3 -1
- package/dist/server/src/camera/streaming/peer-connection.js +12 -7
- package/dist/server/src/camera/streaming/peer-connection.js.map +1 -1
- package/dist/server/src/camera/streaming/webrtc-connection.d.ts +2 -0
- package/dist/server/src/camera/streaming/webrtc-connection.js +28 -10
- package/dist/server/src/camera/streaming/webrtc-connection.js.map +1 -1
- package/dist/server/src/camera/types.d.ts +2 -1
- package/dist/server/src/camera/types.js.map +1 -1
- package/dist/server/src/main.js +8 -4
- package/dist/server/src/main.js.map +1 -1
- package/dist/server/src/nats/server.js +4 -3
- package/dist/server/src/nats/server.js.map +1 -1
- package/dist/server/src/plugins/index.js +12 -9
- package/dist/server/src/plugins/index.js.map +1 -1
- package/dist/server/src/plugins/plugin.js +6 -4
- package/dist/server/src/plugins/plugin.js.map +1 -1
- package/dist/server/src/plugins/worker.d.ts +1 -0
- package/dist/server/src/plugins/worker.js +24 -16
- package/dist/server/src/plugins/worker.js.map +1 -1
- package/dist/server/src/polyglot/node/decoder/child.js +16 -10
- package/dist/server/src/polyglot/node/decoder/child.js.map +1 -1
- package/dist/server/src/polyglot/node/decoder/imageUtils.js +2 -2
- package/dist/server/src/polyglot/node/decoder/imageUtils.js.map +1 -1
- package/dist/server/src/polyglot/node/plugins/cameraStorage.d.ts +0 -1
- package/dist/server/src/polyglot/node/plugins/cameraStorage.js +1 -11
- package/dist/server/src/polyglot/node/plugins/cameraStorage.js.map +1 -1
- package/dist/server/src/polyglot/node/plugins/child.js +3 -3
- package/dist/server/src/polyglot/node/plugins/child.js.map +1 -1
- package/dist/server/src/polyglot/node/plugins/configService.d.ts +1 -1
- package/dist/server/src/polyglot/node/plugins/configService.js +5 -5
- package/dist/server/src/polyglot/node/plugins/configService.js.map +1 -1
- package/dist/server/src/polyglot/node/plugins/proxy/cameraDevice.d.ts +4 -4
- package/dist/server/src/polyglot/node/plugins/proxy/cameraDevice.js +55 -46
- package/dist/server/src/polyglot/node/plugins/proxy/cameraDevice.js.map +1 -1
- package/dist/server/src/polyglot/python/camera/iou.py +80 -0
- package/dist/server/src/polyglot/python/camera_ui_types/__init__.py +15 -9
- package/dist/server/src/polyglot/python/decoder/child.py +20 -12
- package/dist/server/src/polyglot/python/plugins/camera_storage.py +7 -27
- package/dist/server/src/polyglot/python/plugins/child.py +32 -20
- package/dist/server/src/polyglot/python/plugins/config_service.py +8 -1
- package/dist/server/src/polyglot/python/plugins/proxy/camera_device.py +88 -53
- package/dist/server/src/polyglot/python/plugins/typings.py +6 -0
- package/dist/server/src/polyglot/python/utilities/subscriptions.py +6 -4
- package/dist/server/src/polyglot/python/utilities/utils.py +49 -36
- package/dist/server/src/services/config/index.js +32 -21
- package/dist/server/src/services/config/index.js.map +1 -1
- package/dist/server/src/utils/npm.js +3 -3
- package/dist/server/src/utils/npm.js.map +1 -1
- package/dist/server/src/utils/pythonInstaller.js +16 -14
- package/dist/server/src/utils/pythonInstaller.js.map +1 -1
- package/package.json +4 -4
- package/dist/interface/Account.CfJ0_AE8.0.0.16.js +0 -1
- package/dist/interface/Cameras.Du8aeeME.0.0.16.js +0 -1
- package/dist/interface/Config.XeCgFEoY.0.0.16.js +0 -1
- package/dist/interface/FirstSteps.mXqKiWM8.0.0.16.js +0 -1
- package/dist/interface/Plugin.BR9zmMja.0.0.16.js +0 -1
- package/dist/interface/Recordings.Bi4EWX-j.0.0.16.js +0 -1
- package/dist/interface/Recordings.Ce60GFcU.0.0.16.js +0 -1
- package/dist/interface/System.BLePSJ80.0.0.16.js +0 -4
- package/dist/interface/User.C2qVrMxN.0.0.16.js +0 -1
- package/dist/interface/VPlayer.B3YorBp_.0.0.16.js +0 -1
- package/dist/interface/assets/FirstSteps.0.0.16.DFSydMqk.css +0 -1
- package/dist/interface/assets/User.0.0.16.CXUSYt8i.css +0 -1
- package/dist/interface/axios.BI2i94Vb.0.0.16.js +0 -6
- package/dist/interface/main.CySWzYoy.0.0.16.js +0 -9
- package/dist/interface/system.DlYVX2qG.0.0.16.js +0 -1
- /package/dist/interface/{@babel.l0sNRNKZ.0.0.16.js → @babel.l0sNRNKZ.0.0.17.js} +0 -0
- /package/dist/interface/{@intlify.YW_pt133.0.0.16.js → @intlify.YW_pt133.0.0.17.js} +0 -0
- /package/dist/interface/{@mdi.jq8isxax.0.0.16.js → @mdi.jq8isxax.0.0.17.js} +0 -0
- /package/dist/interface/{@popperjs.BQBsAJpH.0.0.16.js → @popperjs.BQBsAJpH.0.0.17.js} +0 -0
- /package/dist/interface/{@react-dnd.CcwRs9xb.0.0.16.js → @react-dnd.CcwRs9xb.0.0.17.js} +0 -0
- /package/dist/interface/{@socket.io.Dkula2eQ.0.0.16.js → @socket.io.Dkula2eQ.0.0.17.js} +0 -0
- /package/dist/interface/{@vue.CIFymae3.0.0.16.js → @vue.CIFymae3.0.0.17.js} +0 -0
- /package/dist/interface/{@xterm.UN9r0Y94.0.0.16.js → @xterm.Cq_3w6CG.0.0.17.js} +0 -0
- /package/dist/interface/assets/{@vueform.0.0.16.h7tNX9p6.css → @vueform.0.0.17.h7tNX9p6.css} +0 -0
- /package/dist/interface/assets/{@xterm.0.0.16.CGrrDQr5.css → @xterm.0.0.17.CGrrDQr5.css} +0 -0
- /package/dist/interface/assets/{Adminpanel.0.0.16.A2JXV1Ib.css → Adminpanel.0.0.17.A2JXV1Ib.css} +0 -0
- /package/dist/interface/assets/{Backup.0.0.16.BIwaSlv3.css → Backup.0.0.17.BIwaSlv3.css} +0 -0
- /package/dist/interface/assets/{Camera.0.0.16.BDtUJ-lA.css → Camera.0.0.17.BDtUJ-lA.css} +0 -0
- /package/dist/interface/assets/{Console.0.0.16.DWrfRWs9.css → Console.0.0.17.DWrfRWs9.css} +0 -0
- /package/dist/interface/assets/{NewCamera.0.0.16.CMzSwwHn.css → NewCamera.0.0.17.CMzSwwHn.css} +0 -0
- /package/dist/interface/assets/{Plugins.0.0.16.Cwu0aGUO.css → Plugins.0.0.17.Cwu0aGUO.css} +0 -0
- /package/dist/interface/assets/{Settings.0.0.16.DTxcLhIY.css → Settings.0.0.17.DTxcLhIY.css} +0 -0
- /package/dist/interface/assets/{VConsole.0.0.16.BZT1WyS-.css → VConsole.0.0.17.BZT1WyS-.css} +0 -0
- /package/dist/interface/assets/{VPluginCard.0.0.16.C2OqX7DJ.css → VPluginCard.0.0.17.C2OqX7DJ.css} +0 -0
- /package/dist/interface/assets/{highlight.0.0.16.DgoK_Xib.css → highlight.0.0.17.DgoK_Xib.css} +0 -0
- /package/dist/interface/assets/{logo-256.0.0.16.BCQk9H1-.png → logo-256.0.0.17.BCQk9H1-.png} +0 -0
- /package/dist/interface/assets/{logo-512.0.0.16.DLKVOU2S.png → logo-512.0.0.17.DLKVOU2S.png} +0 -0
- /package/dist/interface/assets/{logo.0.0.16.DmEiRk0z.png → logo.0.0.17.DmEiRk0z.png} +0 -0
- /package/dist/interface/assets/{mode-json.0.0.16.Br7xisR1.js → mode-json.0.0.17.Br7xisR1.js} +0 -0
- /package/dist/interface/assets/{mode-yaml.0.0.16.CIYL8U3d.js → mode-yaml.0.0.17.CIYL8U3d.js} +0 -0
- /package/dist/interface/assets/{v-calendar.0.0.16.CtSuDYZU.css → v-calendar.0.0.17.CtSuDYZU.css} +0 -0
- /package/dist/interface/assets/{vue3-toastify.0.0.16.DTY1DMRs.css → vue3-toastify.0.0.17.DTY1DMRs.css} +0 -0
- /package/dist/interface/assets/{vuetify.0.0.16.DPyVfAPv.css → vuetify.0.0.17.DPyVfAPv.css} +0 -0
- /package/dist/interface/{compare-versions.DqEOhma6.0.0.16.js → compare-versions.DqEOhma6.0.0.17.js} +0 -0
- /package/dist/interface/{dijkstrajs.D_NXgYpA.0.0.16.js → dijkstrajs.D_NXgYpA.0.0.17.js} +0 -0
- /package/dist/interface/{engine.io-parser.BiEtp6m2.0.0.16.js → engine.io-parser.BiEtp6m2.0.0.17.js} +0 -0
- /package/dist/interface/{entities.C20TfXL6.0.0.16.js → entities.C20TfXL6.0.0.17.js} +0 -0
- /package/dist/interface/{get-size.D1dS-IaP.0.0.16.js → get-size.D1dS-IaP.0.0.17.js} +0 -0
- /package/dist/interface/{mdurl.k9Sl0PQj.0.0.16.js → mdurl.k9Sl0PQj.0.0.17.js} +0 -0
- /package/dist/interface/{punycode.js.Dj65hjkv.0.0.16.js → punycode.js.Dj65hjkv.0.0.17.js} +0 -0
- /package/dist/interface/{react-dnd-html5-backend.1HQjlkwI.0.0.16.js → react-dnd-html5-backend.1HQjlkwI.0.0.17.js} +0 -0
- /package/dist/interface/{redux.CuxFRhX_.0.0.16.js → redux.CuxFRhX_.0.0.17.js} +0 -0
- /package/dist/interface/{tslib.9k7AwCpt.0.0.16.js → tslib.9k7AwCpt.0.0.17.js} +0 -0
- /package/dist/interface/{uc.micro.kMc2yuuw.0.0.16.js → uc.micro.kMc2yuuw.0.0.17.js} +0 -0
- /package/dist/interface/{utils.BvINh1mj.0.0.16.js → utils.BvINh1mj.0.0.17.js} +0 -0
- /package/dist/interface/{uuid.DvF23Exx.0.0.16.js → uuid.DvF23Exx.0.0.17.js} +0 -0
- /package/dist/interface/{vue.l0sNRNKZ.0.0.16.js → vue.l0sNRNKZ.0.0.17.js} +0 -0
- /package/dist/interface/{zod.BaF2dDCd.0.0.16.js → zod.BaF2dDCd.0.0.17.js} +0 -0
|
@@ -23,6 +23,7 @@ JsonSchemaType = Literal["string", "number", "boolean", "object", "array", "butt
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class JsonBaseSchema(TypedDict):
|
|
26
|
+
type: JsonSchemaType
|
|
26
27
|
key: NotRequired[Optional[str]]
|
|
27
28
|
title: NotRequired[Optional[str]]
|
|
28
29
|
description: NotRequired[Optional[str]]
|
|
@@ -42,7 +43,7 @@ class JsonBaseSchema(TypedDict):
|
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
class JsonSchemaString(JsonBaseSchema):
|
|
45
|
-
type: Literal["string"]
|
|
46
|
+
type: Literal["string"] # type: ignore
|
|
46
47
|
format: NotRequired[
|
|
47
48
|
Literal["date-time", "date", "time", "email", "uuid", "ipv4", "ipv6", "password", "qrCode", "image"]
|
|
48
49
|
]
|
|
@@ -51,34 +52,34 @@ class JsonSchemaString(JsonBaseSchema):
|
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
class JsonSchemaNumber(JsonBaseSchema):
|
|
54
|
-
type: Literal["number"]
|
|
55
|
+
type: Literal["number"] # type: ignore
|
|
55
56
|
minimum: NotRequired[int]
|
|
56
57
|
maximum: NotRequired[int]
|
|
57
58
|
step: NotRequired[float]
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
class JsonSchemaBoolean(JsonBaseSchema):
|
|
61
|
-
type: Literal["boolean"]
|
|
62
|
+
type: Literal["boolean"] # type: ignore
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
class JsonSchemaEnum(JsonBaseSchema):
|
|
65
|
-
type: Literal["string"]
|
|
66
|
+
type: Literal["string"] # type: ignore
|
|
66
67
|
enum: list[str]
|
|
67
68
|
multiple: NotRequired[bool]
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
class JsonSchemaButton(JsonBaseSchema):
|
|
71
|
-
type: Literal["button"]
|
|
72
|
+
type: Literal["button"] # type: ignore
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
class JsonSchemaObject(JsonBaseSchema):
|
|
75
|
-
type: Literal["object"]
|
|
76
|
+
type: Literal["object"] # type: ignore
|
|
76
77
|
opened: NotRequired[bool]
|
|
77
78
|
properties: NotRequired["JsonSchemaForm"]
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
class JsonSchemaArray(JsonBaseSchema):
|
|
81
|
-
type: Literal["array"]
|
|
82
|
+
type: Literal["array"] # type: ignore
|
|
82
83
|
opened: NotRequired[bool]
|
|
83
84
|
items: NotRequired["JsonSchema"]
|
|
84
85
|
|
|
@@ -117,9 +118,14 @@ class SchemaConfig(TypedDict):
|
|
|
117
118
|
|
|
118
119
|
|
|
119
120
|
# camera object
|
|
121
|
+
class Go2RtcSource(TypedDict):
|
|
122
|
+
name: str
|
|
123
|
+
src: str
|
|
124
|
+
|
|
125
|
+
|
|
120
126
|
class Go2RtcFFMPEGSource(TypedDict):
|
|
121
|
-
aac:
|
|
122
|
-
opus:
|
|
127
|
+
aac: Go2RtcSource
|
|
128
|
+
opus: Go2RtcSource
|
|
123
129
|
|
|
124
130
|
|
|
125
131
|
class Go2RtcWSSource(TypedDict):
|
|
@@ -87,11 +87,6 @@ class FrameWorkerChild:
|
|
|
87
87
|
self.image_subscriber: Optional[ProxySubscription] = None
|
|
88
88
|
self.signal_subscriber: Optional[ProxySubscription] = None
|
|
89
89
|
|
|
90
|
-
self.max_diff_frame = 3
|
|
91
|
-
self.restart_delay = 5
|
|
92
|
-
self.stopped = False
|
|
93
|
-
self.closed = False
|
|
94
|
-
self.restarting = False
|
|
95
90
|
self.clients: dict[str, FrameWorkerClient] = {}
|
|
96
91
|
self.tasks: TaskSet = TaskSet(f"{self.config['cameraName']} FrameWorkerChild")
|
|
97
92
|
|
|
@@ -110,8 +105,15 @@ class FrameWorkerChild:
|
|
|
110
105
|
self.stdout_task: Optional[asyncio.Task[None]] = None
|
|
111
106
|
self.close_task: Optional[asyncio.Task[None]] = None
|
|
112
107
|
|
|
108
|
+
self.stopped = False
|
|
109
|
+
self.closed = False
|
|
110
|
+
self.restarting = False
|
|
111
|
+
|
|
112
|
+
self.max_diff_frame = 3
|
|
113
|
+
self.restart_delay = 5
|
|
114
|
+
|
|
113
115
|
self.restart_timeout: Optional[asyncio.TimerHandle] = None
|
|
114
|
-
self.
|
|
116
|
+
self.restart_watchdog_delay = 5
|
|
115
117
|
|
|
116
118
|
self.close_timeout: Optional[asyncio.TimerHandle] = None
|
|
117
119
|
self.close_delay = 5
|
|
@@ -236,6 +238,7 @@ class FrameWorkerChild:
|
|
|
236
238
|
self.ffmpeg_process = None
|
|
237
239
|
|
|
238
240
|
async def start_video_session(self) -> None:
|
|
241
|
+
self.reset_prebuffer()
|
|
239
242
|
await self.reset_video_session()
|
|
240
243
|
|
|
241
244
|
# await self.reset()
|
|
@@ -390,7 +393,8 @@ class FrameWorkerChild:
|
|
|
390
393
|
def restart_watchdog():
|
|
391
394
|
reset_restart_watchdog()
|
|
392
395
|
self.restart_timeout = asyncio.get_event_loop().call_later(
|
|
393
|
-
self.
|
|
396
|
+
self.restart_watchdog_delay,
|
|
397
|
+
lambda: asyncio.create_task(restart_ffmpeg(), name="FrameWorker Restart FFmpeg"),
|
|
394
398
|
)
|
|
395
399
|
|
|
396
400
|
async def restart_ffmpeg():
|
|
@@ -555,9 +559,13 @@ class FrameWorkerChild:
|
|
|
555
559
|
|
|
556
560
|
self.logger.trace(f"{self.log_prefix} New client {self.client_identifier(plugin_id, request_id)}")
|
|
557
561
|
|
|
562
|
+
if self.close_timeout:
|
|
563
|
+
self.close_timeout.cancel()
|
|
564
|
+
self.close_timeout = None
|
|
565
|
+
|
|
558
566
|
await self.send("updateClient", {"pluginId": client.plugin_id, "requestIds": client.request_ids})
|
|
559
567
|
|
|
560
|
-
if len(self.clients)
|
|
568
|
+
if len(self.clients) > 0 and self.ffmpeg_process is None and not self.restarting:
|
|
561
569
|
await self.start()
|
|
562
570
|
|
|
563
571
|
async def remove_client(self, plugin_id: str, request_id: str) -> None:
|
|
@@ -584,7 +592,7 @@ class FrameWorkerChild:
|
|
|
584
592
|
self.close_timeout.cancel()
|
|
585
593
|
|
|
586
594
|
self.close_timeout = asyncio.get_event_loop().call_later(
|
|
587
|
-
self.close_delay, lambda: asyncio.create_task(self.stop())
|
|
595
|
+
self.close_delay, lambda: asyncio.create_task(self.stop(), name="FrameWorker Stop")
|
|
588
596
|
)
|
|
589
597
|
|
|
590
598
|
async def recover_clients(self, clients: list[RecoveredClient]) -> None:
|
|
@@ -647,7 +655,7 @@ class FrameWorkerChild:
|
|
|
647
655
|
try:
|
|
648
656
|
message: PingMessage = {"type": "ping", "requestId": request_id}
|
|
649
657
|
|
|
650
|
-
# self.logger.trace(f"{self.log_prefix} Sending ping to {
|
|
658
|
+
# self.logger.trace(f"{self.log_prefix} Sending ping to {self.client_identifier(plugin_id, request_id)}")
|
|
651
659
|
|
|
652
660
|
response: PongMessage = await self.signaling_publisher.request(
|
|
653
661
|
ping_target_subject,
|
|
@@ -661,13 +669,13 @@ class FrameWorkerChild:
|
|
|
661
669
|
if response["type"] != "pong":
|
|
662
670
|
raise Exception("Invalid response to ping")
|
|
663
671
|
|
|
664
|
-
# self.logger.trace(f"{self.log_prefix} Received pong from {
|
|
672
|
+
# self.logger.trace(f"{self.log_prefix} Received pong from {self.client_identifier(plugin_id, request_id)}")
|
|
665
673
|
|
|
666
674
|
await asyncio.sleep(ping_interval)
|
|
667
675
|
except (Exception, NoRespondersError, TimeoutError) as e:
|
|
668
676
|
if request_id_exists():
|
|
669
677
|
if isinstance(e, (NoRespondersError, TimeoutError)):
|
|
670
|
-
self.logger.trace(f"{self.log_prefix} Ping/Pong timeout for {
|
|
678
|
+
# self.logger.trace(f"{self.log_prefix} Ping/Pong timeout for {self.client_identifier(plugin_id, request_id)}")
|
|
671
679
|
await self.remove_client(plugin_id, request_id)
|
|
672
680
|
else:
|
|
673
681
|
self.logger.error(f"{self.log_prefix} Error during ping/pong:", e)
|
|
@@ -45,7 +45,6 @@ class CameraStorage:
|
|
|
45
45
|
self.schema: JsonSchemaForm = schema or {}
|
|
46
46
|
self.values: dict[str, Any] = {}
|
|
47
47
|
|
|
48
|
-
self.__api.device_manager.once("cameraDeselected", self.__deselected)
|
|
49
48
|
self.__api.once("shutdown", self.__close)
|
|
50
49
|
|
|
51
50
|
def initializeStorage(self) -> None:
|
|
@@ -54,17 +53,10 @@ class CameraStorage:
|
|
|
54
53
|
This will load the configuration from the database.
|
|
55
54
|
"""
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
try:
|
|
60
|
-
config: Any = self.__plugin_db.get("config") or {}
|
|
61
|
-
camera_config = config.get(self.__camera_id, {})
|
|
62
|
-
except: # noqa: E722
|
|
63
|
-
pass
|
|
64
|
-
|
|
56
|
+
config: Any = self.__plugin_db.get("config") or {}
|
|
57
|
+
camera_config = config.get(self.__camera_id, {})
|
|
65
58
|
schema_config = generate_json_from_root_schema({"schema": self.schema})
|
|
66
|
-
|
|
67
|
-
self.values = merge_with(schema_config, camera_config, merge, [])
|
|
59
|
+
self.values = merge_with(schema_config, camera_config, merge)
|
|
68
60
|
|
|
69
61
|
self.__save_db()
|
|
70
62
|
|
|
@@ -198,7 +190,7 @@ class CameraStorage:
|
|
|
198
190
|
elif isinstance(schema_or_path, dict) and schema is None:
|
|
199
191
|
new_schema = schema_or_path
|
|
200
192
|
|
|
201
|
-
self.schema = merge_with(self.schema, new_schema, merge
|
|
193
|
+
self.schema = merge_with(self.schema, new_schema, merge)
|
|
202
194
|
|
|
203
195
|
await self.__resolve_on_get_functions(new_schema)
|
|
204
196
|
|
|
@@ -228,7 +220,7 @@ class CameraStorage:
|
|
|
228
220
|
ObjectPath.delete(self.values, path)
|
|
229
221
|
|
|
230
222
|
if self.__contains_storable_schema(schema):
|
|
231
|
-
self.
|
|
223
|
+
self.__save_db()
|
|
232
224
|
|
|
233
225
|
async def changeSchema(self, path: str, new_schema: dict[str, Any]) -> None:
|
|
234
226
|
"""
|
|
@@ -244,7 +236,7 @@ class CameraStorage:
|
|
|
244
236
|
schema: Optional[JsonSchema] = ObjectPath.get(self.schema, schema_path) or None
|
|
245
237
|
|
|
246
238
|
if schema:
|
|
247
|
-
merge_with(schema, new_schema, merge
|
|
239
|
+
merge_with(schema, new_schema, merge)
|
|
248
240
|
ObjectPath.set(self.schema, schema_path, schema)
|
|
249
241
|
|
|
250
242
|
await self.__resolve_on_get_functions(schema, schema_path)
|
|
@@ -332,7 +324,7 @@ class CameraStorage:
|
|
|
332
324
|
schema_path = config_path_to_schema_path(new_path)
|
|
333
325
|
schema_item: Optional[JsonBaseSchema] = get_value_by_path(self.schema, schema_path)
|
|
334
326
|
|
|
335
|
-
if schema_item:
|
|
327
|
+
if schema_item and schema_item["type"] != "button":
|
|
336
328
|
on_set_function = schema_item.get("onSet", None)
|
|
337
329
|
if on_set_function:
|
|
338
330
|
if inspect.iscoroutinefunction(on_set_function):
|
|
@@ -394,18 +386,6 @@ class CameraStorage:
|
|
|
394
386
|
|
|
395
387
|
self.__plugin_db.update({"config": config})
|
|
396
388
|
|
|
397
|
-
def __remove_db(self) -> None:
|
|
398
|
-
config: Any = self.__plugin_db.get("config") or {}
|
|
399
|
-
|
|
400
|
-
if self.__camera_id in config:
|
|
401
|
-
del config[self.__camera_id]
|
|
402
|
-
self.__plugin_db.update(config)
|
|
403
|
-
|
|
404
|
-
def __deselected(self, id: str) -> None:
|
|
405
|
-
if id == self.__camera_id:
|
|
406
|
-
self.__remove_db()
|
|
407
|
-
|
|
408
389
|
def __close(self) -> None:
|
|
409
390
|
with suppress(Exception):
|
|
410
391
|
self.__save_db()
|
|
411
|
-
self.__plugin_db.close()
|
|
@@ -108,6 +108,7 @@ class PluginChild:
|
|
|
108
108
|
self.plugin: Optional[BasePlugin] = None
|
|
109
109
|
|
|
110
110
|
self.stopped = False
|
|
111
|
+
self.closed = False
|
|
111
112
|
|
|
112
113
|
async def run(self):
|
|
113
114
|
self.shutdown_event = asyncio.Event()
|
|
@@ -194,6 +195,9 @@ class PluginChild:
|
|
|
194
195
|
|
|
195
196
|
self.stopped = True
|
|
196
197
|
|
|
198
|
+
if self.api:
|
|
199
|
+
self.api.emit("shutdown")
|
|
200
|
+
|
|
197
201
|
if self.message_queue:
|
|
198
202
|
self.message_queue.abort_queue()
|
|
199
203
|
|
|
@@ -201,12 +205,10 @@ class PluginChild:
|
|
|
201
205
|
with suppress(Exception):
|
|
202
206
|
await self.publisher.close()
|
|
203
207
|
|
|
204
|
-
if self.api:
|
|
205
|
-
self.api.emit("shutdown")
|
|
206
|
-
|
|
207
208
|
if self.plugin_db:
|
|
208
|
-
await asyncio.sleep(
|
|
209
|
-
|
|
209
|
+
await asyncio.sleep(0.5)
|
|
210
|
+
with suppress(Exception):
|
|
211
|
+
self.plugin_db.close()
|
|
210
212
|
|
|
211
213
|
self.shutdown_event.set()
|
|
212
214
|
|
|
@@ -291,9 +293,27 @@ class PluginChild:
|
|
|
291
293
|
|
|
292
294
|
if "type" in message:
|
|
293
295
|
if message["type"] == PLUGIN_COMMAND.START.value and "data" in message:
|
|
294
|
-
self.
|
|
296
|
+
await self.start(message["data"])
|
|
295
297
|
elif message["type"] == PLUGIN_COMMAND.STOP.value:
|
|
296
|
-
self.
|
|
298
|
+
await self.stop()
|
|
299
|
+
|
|
300
|
+
async def gracefully_close(self, signame: str, reason: Any = "unknown"):
|
|
301
|
+
if self.stopped or self.closed:
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
self.closed = True
|
|
305
|
+
|
|
306
|
+
self.logger.log(
|
|
307
|
+
f"{self.display_name} is gracefully closing from signal='{signame}' reason='{reason}'..."
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
stop_task = asyncio.create_task(self.stop(), name=f"{self.display_name} Stop")
|
|
312
|
+
await asyncio.wait_for(stop_task, timeout=2)
|
|
313
|
+
os.kill(os.getpid(), signal.SIGKILL)
|
|
314
|
+
except asyncio.TimeoutError:
|
|
315
|
+
self.logger.warn("Failed to gracefully close before timeout, force quit!")
|
|
316
|
+
os.kill(os.getpid(), signal.SIGKILL)
|
|
297
317
|
|
|
298
318
|
def setup_signal_handlers(self):
|
|
299
319
|
loop = asyncio.get_running_loop()
|
|
@@ -302,20 +322,12 @@ class PluginChild:
|
|
|
302
322
|
for signame in ("SIGINT", "SIGTERM"):
|
|
303
323
|
loop.add_signal_handler(
|
|
304
324
|
getattr(signal, signame),
|
|
305
|
-
lambda signame=signame:
|
|
325
|
+
lambda signame=signame: asyncio.create_task(
|
|
326
|
+
self.gracefully_close(signame, getattr(signal, signame)),
|
|
327
|
+
name=f"{self.display_name} Signal",
|
|
328
|
+
),
|
|
306
329
|
)
|
|
307
330
|
|
|
308
|
-
def gracefully_close(self, signame: str, reason: Any = "unknown"):
|
|
309
|
-
if self.stopped:
|
|
310
|
-
return
|
|
311
|
-
|
|
312
|
-
self.logger.log(
|
|
313
|
-
f"{self.display_name} is gracefully closing from signal='{signame}' reason='{reason}'..."
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
self.tasks.add(self.stop())
|
|
317
|
-
self.shutdown_event.set()
|
|
318
|
-
|
|
319
331
|
def handle_exception(self, loop: AbstractEventLoop, context: Any):
|
|
320
332
|
print("Exception context:", context)
|
|
321
333
|
traceback.print_exc()
|
|
@@ -331,7 +343,7 @@ class PluginChild:
|
|
|
331
343
|
self.logger.error(f"Caught exception: {msg}")
|
|
332
344
|
|
|
333
345
|
if loop.is_running():
|
|
334
|
-
self.gracefully_close("uncaughtException", msg)
|
|
346
|
+
asyncio.create_task(self.gracefully_close("uncaughtException", msg))
|
|
335
347
|
|
|
336
348
|
|
|
337
349
|
if __name__ == "__main__":
|
|
@@ -163,7 +163,14 @@ class PluginConfigService:
|
|
|
163
163
|
self.set(path_segments[0], self.__config[path_segments[0]], write)
|
|
164
164
|
|
|
165
165
|
def __init(self) -> None:
|
|
166
|
-
os.
|
|
166
|
+
directory = os.path.dirname(self.__config_file)
|
|
167
|
+
|
|
168
|
+
if not os.path.exists(directory):
|
|
169
|
+
os.makedirs(directory)
|
|
170
|
+
|
|
171
|
+
if not os.path.exists(self.__config_file):
|
|
172
|
+
open(self.__config_file, "a").close()
|
|
173
|
+
|
|
167
174
|
self.__read()
|
|
168
175
|
|
|
169
176
|
def __read(self) -> None:
|
|
@@ -10,6 +10,7 @@ from uuid import uuid4
|
|
|
10
10
|
|
|
11
11
|
import aiohttp
|
|
12
12
|
from cachetools import LRUCache, cached
|
|
13
|
+
from camera.iou import merge_detections
|
|
13
14
|
from camera.polygon import are_motion_detections_valid, are_object_detections_valid
|
|
14
15
|
from camera.video_frame import MotionFrame, VideoFrame
|
|
15
16
|
from camera_ui_types import (
|
|
@@ -68,6 +69,7 @@ from typings import (
|
|
|
68
69
|
ClientType,
|
|
69
70
|
Delegates,
|
|
70
71
|
DeserializedError,
|
|
72
|
+
FrameSubscribtion,
|
|
71
73
|
PluginInfo,
|
|
72
74
|
ProxyMessageStructure,
|
|
73
75
|
)
|
|
@@ -190,13 +192,13 @@ class CameraDeviceProxy:
|
|
|
190
192
|
self.camera_subscriber: Optional[ProxySubscription] = None
|
|
191
193
|
self.camera_plugin_subscriber: Optional[ProxySubscription] = None
|
|
192
194
|
self.signaling_subscriber: Optional[ProxySubscription] = None
|
|
193
|
-
self.
|
|
194
|
-
self.
|
|
195
|
+
self.frame_subscribers: list[FrameSubscribtion] = []
|
|
196
|
+
self.motion_frame_subscribers: list[FrameSubscribtion] = []
|
|
195
197
|
|
|
196
198
|
self.tasks: TaskSet = TaskSet(f"{self.plugin['name']} - {self.name} - CameraDeviceProxy")
|
|
197
199
|
self.message_queue: Optional[MessageQueue] = None
|
|
198
200
|
self.active_requests: set[str] = set()
|
|
199
|
-
self.active_motion_requests =
|
|
201
|
+
self.active_motion_requests: set[str] = set()
|
|
200
202
|
|
|
201
203
|
# print(("INIT camera device with clientSubject", self.subject))
|
|
202
204
|
|
|
@@ -425,24 +427,24 @@ class CameraDeviceProxy:
|
|
|
425
427
|
|
|
426
428
|
if prebuffer_duration > 0:
|
|
427
429
|
request_type = "prebuffer"
|
|
428
|
-
prebuffer_duration =
|
|
430
|
+
prebuffer_duration = min(prebuffer_duration, 5)
|
|
429
431
|
|
|
430
432
|
request_id = f"{str(uuid4())}-{request_type}"
|
|
431
433
|
|
|
432
434
|
try:
|
|
433
435
|
metadata = await self.__connect_to_decoder(request_id)
|
|
436
|
+
frame_subscriber = await self.frame_publisher.subscribe(self.frame_subject)
|
|
434
437
|
|
|
435
|
-
|
|
436
|
-
self.frame_subscriber = await self.frame_publisher.subscribe(self.frame_subject)
|
|
438
|
+
self.frame_subscribers.append({"request_id": request_id, "subscriber": frame_subscriber})
|
|
437
439
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
+
try:
|
|
441
|
+
async for msg in frame_subscriber.subscriber.messages:
|
|
440
442
|
if (
|
|
441
443
|
not self.initialized
|
|
442
444
|
or len(self.active_requests) == 0
|
|
443
445
|
or request_id not in self.active_requests
|
|
444
446
|
):
|
|
445
|
-
|
|
447
|
+
return
|
|
446
448
|
|
|
447
449
|
if len(msg.data) == 0:
|
|
448
450
|
continue
|
|
@@ -450,35 +452,39 @@ class CameraDeviceProxy:
|
|
|
450
452
|
frame_data: FrameData = unpack(msg.data)
|
|
451
453
|
|
|
452
454
|
yield VideoFrame(self.id, self.frame_publisher, frame_data, metadata, prebuffer_duration)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
else:
|
|
457
|
-
break
|
|
455
|
+
|
|
456
|
+
except GeneratorExit:
|
|
457
|
+
return
|
|
458
458
|
|
|
459
459
|
except asyncio.CancelledError:
|
|
460
|
-
|
|
460
|
+
return
|
|
461
461
|
except Exception as e:
|
|
462
462
|
if self.initialized:
|
|
463
463
|
self.logger.error("Error while generating frames:", e)
|
|
464
|
+
raise
|
|
464
465
|
finally:
|
|
465
|
-
self.
|
|
466
|
+
await self.__disconnect_from_decoder(request_id)
|
|
466
467
|
|
|
467
468
|
async def get_motion_frames(self) -> AsyncGenerator[MotionFrame, None]:
|
|
468
469
|
if not self.motion_frame_publisher:
|
|
469
470
|
raise Exception("Motion Frame Publisher is not initialized")
|
|
470
471
|
|
|
471
|
-
|
|
472
|
+
request_id = f"{str(uuid4())}-motion"
|
|
472
473
|
|
|
473
474
|
try:
|
|
474
|
-
|
|
475
|
-
self.motion_frame_subscriber = await self.motion_frame_publisher.subscribe(
|
|
476
|
-
self.motion_frame_subject
|
|
477
|
-
)
|
|
475
|
+
motion_frame_subscriber = await self.motion_frame_publisher.subscribe(self.motion_frame_subject)
|
|
478
476
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
477
|
+
self.motion_frame_subscribers.append(
|
|
478
|
+
{"request_id": request_id, "subscriber": motion_frame_subscriber}
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
async for msg in motion_frame_subscriber.subscriber.messages:
|
|
483
|
+
if (
|
|
484
|
+
not self.initialized
|
|
485
|
+
or len(self.active_motion_requests) == 0
|
|
486
|
+
or request_id not in self.active_motion_requests
|
|
487
|
+
):
|
|
482
488
|
break
|
|
483
489
|
|
|
484
490
|
if not msg or not msg.data or len(msg.data) == 0:
|
|
@@ -499,22 +505,30 @@ class CameraDeviceProxy:
|
|
|
499
505
|
motion_state,
|
|
500
506
|
motion_frame["metadata"],
|
|
501
507
|
)
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
else:
|
|
506
|
-
break
|
|
508
|
+
|
|
509
|
+
except GeneratorExit:
|
|
510
|
+
return
|
|
507
511
|
|
|
508
512
|
except asyncio.CancelledError:
|
|
509
513
|
pass
|
|
510
514
|
except Exception as e:
|
|
511
515
|
if self.initialized:
|
|
512
516
|
self.logger.error("Error while generating motion frames:", e)
|
|
517
|
+
raise
|
|
513
518
|
finally:
|
|
514
|
-
self.active_motion_requests
|
|
519
|
+
if request_id in self.active_motion_requests:
|
|
520
|
+
self.active_motion_requests.remove(request_id)
|
|
521
|
+
|
|
522
|
+
motion_frame_subscribtion = next(
|
|
523
|
+
(sub for sub in self.motion_frame_subscribers if sub["request_id"] == request_id), None
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
if motion_frame_subscribtion:
|
|
527
|
+
self.motion_frame_subscribers.remove(motion_frame_subscribtion)
|
|
528
|
+
await motion_frame_subscribtion["subscriber"].close()
|
|
515
529
|
|
|
516
|
-
if self.
|
|
517
|
-
self.
|
|
530
|
+
if len(self.motion_frame_subscribers) == 0:
|
|
531
|
+
await self.__close_motion_frame_subscribers()
|
|
518
532
|
|
|
519
533
|
@overload
|
|
520
534
|
def get_value(self, state_name: Literal["light"]) -> LightState: ...
|
|
@@ -913,6 +927,8 @@ class CameraDeviceProxy:
|
|
|
913
927
|
det["boundingBox"] = cast(tuple[int, int, int, int], tuple(det["boundingBox"]))
|
|
914
928
|
det["confidence"] = float(det["confidence"])
|
|
915
929
|
|
|
930
|
+
motion_event["detections"] = merge_detections(motion_event["detections"])
|
|
931
|
+
|
|
916
932
|
motion_event["detections"] = are_motion_detections_valid(
|
|
917
933
|
motion_event["detections"], self.activity_zones, self.activity_settings
|
|
918
934
|
)
|
|
@@ -1433,37 +1449,48 @@ class CameraDeviceProxy:
|
|
|
1433
1449
|
metadata = metadata_response.get("metadata")
|
|
1434
1450
|
|
|
1435
1451
|
return metadata
|
|
1436
|
-
except
|
|
1437
|
-
self.logger.error("Failed to get metadata! Frame Worker FFmpeg process might not be running")
|
|
1452
|
+
except Exception as e:
|
|
1453
|
+
self.logger.error("Failed to get metadata! Frame Worker FFmpeg process might not be running", e)
|
|
1438
1454
|
|
|
1439
1455
|
async def __disconnect_from_decoder(self, request_id: str) -> None:
|
|
1440
1456
|
if request_id in self.active_requests:
|
|
1441
1457
|
self.active_requests.remove(request_id)
|
|
1442
1458
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1459
|
+
frame_subscription = next(
|
|
1460
|
+
(sub for sub in self.frame_subscribers if sub["request_id"] == request_id), None
|
|
1461
|
+
)
|
|
1445
1462
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
"
|
|
1449
|
-
"cameraId": self.id,
|
|
1450
|
-
"pluginId": self.plugin["id"],
|
|
1451
|
-
}
|
|
1463
|
+
if frame_subscription:
|
|
1464
|
+
self.frame_subscribers.remove(frame_subscription)
|
|
1465
|
+
await frame_subscription["subscriber"].close()
|
|
1452
1466
|
|
|
1453
|
-
|
|
1467
|
+
if self.signaling_publisher:
|
|
1468
|
+
message: SignalingRequest = {
|
|
1469
|
+
"type": "disconnect",
|
|
1470
|
+
"requestId": request_id,
|
|
1471
|
+
"cameraId": self.id,
|
|
1472
|
+
"pluginId": self.plugin["id"],
|
|
1473
|
+
}
|
|
1454
1474
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1475
|
+
await self.signaling_publisher.publish(self.signaling_target_subject, message)
|
|
1476
|
+
|
|
1477
|
+
async def __close_frame_subscribers(self) -> None:
|
|
1478
|
+
self.active_requests.clear()
|
|
1479
|
+
|
|
1480
|
+
for sub in self.frame_subscribers:
|
|
1481
|
+
with suppress(Exception):
|
|
1482
|
+
await sub["subscriber"].close()
|
|
1460
1483
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1484
|
+
self.frame_subscribers.clear()
|
|
1485
|
+
|
|
1486
|
+
async def __close_motion_frame_subscribers(self) -> None:
|
|
1487
|
+
self.active_motion_requests.clear()
|
|
1488
|
+
|
|
1489
|
+
for sub in self.motion_frame_subscribers:
|
|
1463
1490
|
with suppress(Exception):
|
|
1464
|
-
await
|
|
1491
|
+
await sub["subscriber"].close()
|
|
1465
1492
|
|
|
1466
|
-
|
|
1493
|
+
self.motion_frame_subscribers.clear()
|
|
1467
1494
|
|
|
1468
1495
|
async def __request_handler(self, message: ProxyMessageStructure) -> None:
|
|
1469
1496
|
if not self.message_queue:
|
|
@@ -1565,8 +1592,16 @@ class CameraDeviceProxy:
|
|
|
1565
1592
|
if message["requestId"] in self.active_requests:
|
|
1566
1593
|
self.active_requests.remove(message["requestId"])
|
|
1567
1594
|
|
|
1595
|
+
frame_subscribtion = next(
|
|
1596
|
+
(sub for sub in self.frame_subscribers if sub["request_id"] == message["requestId"]), None
|
|
1597
|
+
)
|
|
1598
|
+
|
|
1599
|
+
if frame_subscribtion:
|
|
1600
|
+
self.frame_subscribers.remove(frame_subscribtion)
|
|
1601
|
+
await frame_subscribtion["subscriber"].close()
|
|
1602
|
+
|
|
1568
1603
|
if len(self.active_requests) == 0:
|
|
1569
|
-
self.tasks.add(self.
|
|
1604
|
+
self.tasks.add(self.__close_frame_subscribers())
|
|
1570
1605
|
|
|
1571
1606
|
async def __respond_to_signaling_message(self, msg: Msg, message: PingMessage) -> None:
|
|
1572
1607
|
try:
|
|
@@ -25,6 +25,7 @@ from camera_ui_types import (
|
|
|
25
25
|
StateValues,
|
|
26
26
|
)
|
|
27
27
|
from typing_extensions import Literal, NotRequired, TypedDict
|
|
28
|
+
from utilities.subscriptions import ProxySubscription
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class PLUGIN_STATUS(Enum):
|
|
@@ -245,3 +246,8 @@ Delegates = Literal[
|
|
|
245
246
|
"prebufferDelegate",
|
|
246
247
|
"ptzDelegate",
|
|
247
248
|
]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class FrameSubscribtion(TypedDict):
|
|
252
|
+
request_id: str
|
|
253
|
+
subscriber: ProxySubscription
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
1
3
|
from nats.aio.subscription import Subscription as NatsSubscription
|
|
2
4
|
from pyeecui.asyncio import AsyncIOEventEmitter
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class ProxySubscription(AsyncIOEventEmitter):
|
|
6
|
-
def __init__(self, subscriber: NatsSubscription) -> None:
|
|
8
|
+
def __init__(self, subscriber: NatsSubscription, max_concurrent: int = 25) -> None:
|
|
7
9
|
super().__init__()
|
|
8
|
-
|
|
9
10
|
self.subscriber = subscriber
|
|
11
|
+
self.semaphore = asyncio.Semaphore(max_concurrent)
|
|
10
12
|
|
|
11
13
|
async def listen(self):
|
|
12
14
|
async for msg in self.subscriber.messages:
|
|
13
15
|
if len(msg.data) == 0:
|
|
14
16
|
continue
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
async with self.semaphore:
|
|
18
|
+
self.emit("message", msg)
|
|
17
19
|
|
|
18
20
|
async def close(self):
|
|
19
21
|
self.remove_all_listeners()
|