camera.ui3 0.0.30-alpha.0 → 0.0.30-alpha.1
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/@camera.ui.KA9bRSoR.0.0.30-alpha.0.js +3 -0
- package/dist/interface/@tanstack.CTzdjGxD.0.0.30-alpha.0.js +1 -0
- package/dist/interface/{@vee-validate.0Cv0x9aN.0.0.30-alpha.0.js → @vee-validate.BJKTyKHK.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/@vue.BeJw0wEm.0.0.30-alpha.0.js +17 -0
- package/dist/interface/{@vueform.Du_niMHk.0.0.30-alpha.0.js → @vueform.DIw88mcs.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{@vueuse.Dv9U068T.0.0.30-alpha.0.js → @vueuse.DK7Mz0BT.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/Account.D23CSQgK.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Adminpanel.CbDtQWxn.0.0.30-alpha.0.js +2 -0
- package/dist/interface/Appearance.az1S-8rV.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Backup.BxjbY7JO.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Camera.CWxxie6C.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Cameras.Cs1dXd-W.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Camview.uyIq-pxm.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Config.DnuL5NIv.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Console.C1dI5f9x.0.0.30-alpha.0.js +4 -0
- package/dist/interface/FirstSteps.C_TTm4lX.0.0.30-alpha.0.js +1 -0
- package/dist/interface/{NewCamera.Kn9biZIb.0.0.30-alpha.0.js → NewCamera.DjMFd-OB.0.0.30-alpha.0.js} +2 -2
- package/dist/interface/Plugin.rLO6u13g.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Plugins.Dr-sUa9o.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Recordings.4iG-hK8g.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Recordings.Dxh2d-IQ.0.0.30-alpha.0.js +1 -0
- package/dist/interface/Settings.DAletWZO.0.0.30-alpha.0.js +1 -0
- package/dist/interface/System.Pv01yz8u.0.0.30-alpha.0.js +4 -0
- package/dist/interface/User.VLcbTFm_.0.0.30-alpha.0.js +1 -0
- package/dist/interface/{VConfigEditor.vue_vue_type_script_setup_true_lang.Br68bfQJ.0.0.30-alpha.0.js → VConfigEditor.vue_vue_type_script_setup_true_lang.pXHIBxVl.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{VConsole.BT0UxZb1.0.0.30-alpha.0.js → VConsole.BUZLhyeb.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/VPlayer.DW5rNren.0.0.30-alpha.0.js +1 -0
- package/dist/interface/{VPluginCard.Bz3GmzAE.0.0.30-alpha.0.js → VPluginCard.D5f85QIk.0.0.30-alpha.0.js} +2 -2
- package/dist/interface/index.html +15 -14
- package/dist/interface/main.CNzh1Rg5.0.0.30-alpha.0.js +2 -0
- package/dist/interface/{pinia.DRjp4GU7.0.0.30-alpha.0.js → pinia.BPnxDxQ3.0.0.30-alpha.0.js} +2 -2
- package/dist/interface/{plugins.Ur_7V4x8.0.0.30-alpha.0.js → plugins.DIEspWyc.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/sw.js +1 -1
- package/dist/interface/{system.CRn9Cy3C.0.0.30-alpha.0.js → system.23ZKsQ9W.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{ui.C-XbEPHk.0.0.30-alpha.0.js → ui.9t-Av5-L.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{v-calendar.CapJoEkJ.0.0.30-alpha.0.js → v-calendar.u5cKUC4g.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{vee-validate.DcgOEpak.0.0.30-alpha.0.js → vee-validate.DLoDjJ2f.0.0.30-alpha.0.js} +2 -2
- package/dist/interface/{vue-i18n.D2pqkMP3.0.0.30-alpha.0.js → vue-i18n.1W5jhc7l.0.0.30-alpha.0.js} +2 -2
- package/dist/interface/{vue-inline-svg.DorOTPIf.0.0.30-alpha.0.js → vue-inline-svg.Cc6uL4nI.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{vue-router.BVs4qaVs.0.0.30-alpha.0.js → vue-router.CgUDt9kL.0.0.30-alpha.0.js} +2 -2
- package/dist/interface/{vue3-apexcharts.CBV9x5vk.0.0.30-alpha.0.js → vue3-apexcharts.D8ONfVUq.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{vue3-dnd.CXMddGIZ.0.0.30-alpha.0.js → vue3-dnd.V9l5pvP2.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{vue3-toastify.DE7YmDSX.0.0.30-alpha.0.js → vue3-toastify.CjNgo6aC.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/{vuetify.CYxxHEVN.0.0.30-alpha.0.js → vuetify.Box3fVLm.0.0.30-alpha.0.js} +1 -1
- package/dist/interface/zod.DnW1_kUe.0.0.30-alpha.0.js +1 -0
- package/dist/server/bin/cameraui.js +10 -7
- package/dist/server/bin/cameraui.js.map +1 -1
- package/dist/server/bin/fork.js +2 -2
- package/dist/server/bin/fork.js.map +1 -1
- package/dist/server/bin/installer/darwin.js +1 -1
- package/dist/server/bin/installer/darwin.js.map +1 -1
- package/dist/server/bin/installer/linux.js +1 -1
- package/dist/server/bin/installer/linux.js.map +1 -1
- package/dist/server/package.json +11 -15
- package/dist/server/src/api/controllers/backup.controller.js.map +1 -1
- package/dist/server/src/api/controllers/cameras.controller.js +1 -1
- package/dist/server/src/api/controllers/cameras.controller.js.map +1 -1
- package/dist/server/src/api/controllers/frameWorkers.controller.js +1 -1
- package/dist/server/src/api/controllers/frameWorkers.controller.js.map +1 -1
- package/dist/server/src/api/controllers/plugins.controller.js +1 -2
- package/dist/server/src/api/controllers/plugins.controller.js.map +1 -1
- package/dist/server/src/api/controllers/system.controller.js +1 -3
- package/dist/server/src/api/controllers/system.controller.js.map +1 -1
- package/dist/server/src/api/controllers/users.controller.js +2 -2
- package/dist/server/src/api/controllers/users.controller.js.map +1 -1
- package/dist/server/src/api/database/constants.d.ts +8 -0
- package/dist/server/src/api/database/constants.js +9 -0
- package/dist/server/src/api/database/constants.js.map +1 -0
- package/dist/server/src/api/database/index.d.ts +0 -8
- package/dist/server/src/api/database/index.js +14 -21
- package/dist/server/src/api/database/index.js.map +1 -1
- package/dist/server/src/api/database/migration.js +5 -4
- package/dist/server/src/api/database/migration.js.map +1 -1
- package/dist/server/src/api/database/types.d.ts +6 -6
- package/dist/server/src/api/go2rtc/api/application.js +1 -1
- package/dist/server/src/api/go2rtc/api/application.js.map +1 -1
- package/dist/server/src/api/index.js.map +1 -1
- package/dist/server/src/api/plugins/logger.plugin.js.map +1 -1
- package/dist/server/src/api/schemas/cameras.schema.d.ts +71 -71
- package/dist/server/src/api/schemas/config.schema.d.ts +6 -6
- package/dist/server/src/api/schemas/go2rtc.schema.d.ts +98 -98
- package/dist/server/src/api/schemas/users.schema.d.ts +116 -116
- package/dist/server/src/api/services/auth.service.js +1 -1
- package/dist/server/src/api/services/auth.service.js.map +1 -1
- package/dist/server/src/api/services/backup.service.js.map +1 -1
- package/dist/server/src/api/services/cameras.service.js +1 -1
- package/dist/server/src/api/services/cameras.service.js.map +1 -1
- package/dist/server/src/api/services/system.service.js +1 -1
- package/dist/server/src/api/services/system.service.js.map +1 -1
- package/dist/server/src/api/services/users.service.js +1 -1
- package/dist/server/src/api/services/users.service.js.map +1 -1
- package/dist/server/src/api/types/index.d.ts +2 -2
- package/dist/server/src/api/types/index.js.map +1 -1
- package/dist/server/src/api/utils/cert.js.map +1 -1
- package/dist/server/src/api/websocket/index.js.map +1 -1
- package/dist/server/src/api/websocket/nsp/notifications.js.map +1 -1
- package/dist/server/src/api/websocket/nsp/server.js +1 -1
- package/dist/server/src/api/websocket/nsp/server.js.map +1 -1
- package/dist/server/src/api.d.ts +1 -7
- package/dist/server/src/api.js.map +1 -1
- package/dist/server/src/camera/controller.d.ts +4 -6
- package/dist/server/src/camera/controller.js +5 -8
- package/dist/server/src/camera/controller.js.map +1 -1
- package/dist/server/src/camera/types.d.ts +140 -13
- package/dist/server/src/camera/types.js.map +1 -1
- package/dist/server/src/decoder/index.js.map +1 -1
- package/dist/server/src/decoder/types.d.ts +1 -18
- package/dist/server/src/decoder/worker.d.ts +3 -2
- package/dist/server/src/decoder/worker.js +10 -22
- package/dist/server/src/decoder/worker.js.map +1 -1
- package/dist/server/src/go2rtc/index.js +2 -3
- package/dist/server/src/go2rtc/index.js.map +1 -1
- package/dist/server/src/main.js +3 -3
- package/dist/server/src/main.js.map +1 -1
- package/dist/server/src/nats/index.d.ts +3 -4
- package/dist/server/src/nats/index.js +1 -3
- package/dist/server/src/nats/index.js.map +1 -1
- package/dist/server/src/nats/proxy/cameraDevice.d.ts +2 -2
- package/dist/server/src/nats/proxy/coreManager.d.ts +4 -2
- package/dist/server/src/nats/proxy/coreManager.js +7 -0
- package/dist/server/src/nats/proxy/coreManager.js.map +1 -1
- package/dist/server/src/nats/proxy/deviceManager.d.ts +5 -5
- package/dist/server/src/nats/proxy/deviceManager.js +4 -2
- package/dist/server/src/nats/proxy/deviceManager.js.map +1 -1
- package/dist/server/src/nats/server.js +2 -3
- package/dist/server/src/nats/server.js.map +1 -1
- package/dist/server/src/nats/types.d.ts +19 -32
- package/dist/server/src/nats/utils.d.ts +1 -1
- package/dist/server/src/nats/websocket.js.map +1 -1
- package/dist/server/src/plugins/index.js +11 -9
- package/dist/server/src/plugins/index.js.map +1 -1
- package/dist/server/src/plugins/interfaces/base.d.ts +1 -1
- package/dist/server/src/plugins/plugin.d.ts +1 -1
- package/dist/server/src/plugins/plugin.js +4 -4
- package/dist/server/src/plugins/plugin.js.map +1 -1
- package/dist/server/src/plugins/types.d.ts +36 -3
- package/dist/server/src/plugins/types.js.map +1 -1
- package/dist/server/src/plugins/worker.js +7 -16
- package/dist/server/src/plugins/worker.js.map +1 -1
- package/dist/server/src/services/config/constants.js +2 -2
- package/dist/server/src/services/config/constants.js.map +1 -1
- package/dist/server/src/services/config/index.d.ts +2 -0
- package/dist/server/src/services/config/index.js +10 -2
- package/dist/server/src/services/config/index.js.map +1 -1
- package/dist/server/src/services/logger/index.d.ts +4 -16
- package/dist/server/src/services/logger/index.js +4 -83
- package/dist/server/src/services/logger/index.js.map +1 -1
- package/dist/server/src/services/logger/types.d.ts +8 -0
- package/dist/server/src/services/logger/types.js +2 -0
- package/dist/server/src/services/logger/types.js.map +1 -0
- package/localdeps.txt +4 -0
- package/package.json +11 -15
- package/dist/interface/@tanstack.DYQ7Jj8U.0.0.30-alpha.0.js +0 -1
- package/dist/interface/@vue.CvjRlaU6.0.0.30-alpha.0.js +0 -17
- package/dist/interface/Account.Dm2FouAU.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Adminpanel.B2Aiobnt.0.0.30-alpha.0.js +0 -2
- package/dist/interface/Appearance.EfH0tMdM.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Backup.D8SRclhB.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Camera.D-cOf4sS.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Cameras.oolxbmXU.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Camview.BHw8Pe3v.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Config.DmPTI5iz.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Console.Dq4khHFF.0.0.30-alpha.0.js +0 -4
- package/dist/interface/FirstSteps.Bo467abx.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Plugin.BLTVcTt1.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Plugins.Cp8mK__o.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Recordings.B_JIFk_G.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Recordings.gMbvwmk_.0.0.30-alpha.0.js +0 -1
- package/dist/interface/Settings.EhHJjGrl.0.0.30-alpha.0.js +0 -1
- package/dist/interface/System.VodoK0U6.0.0.30-alpha.0.js +0 -4
- package/dist/interface/User.DpcN7DEC.0.0.30-alpha.0.js +0 -1
- package/dist/interface/VPlayer.DYua3Eh_.0.0.30-alpha.0.js +0 -1
- package/dist/interface/main.19pRAqPU.0.0.30-alpha.0.js +0 -4
- package/dist/interface/zod.BaF2dDCd.0.0.30-alpha.0.js +0 -1
- package/dist/server/src/camera/device.d.ts +0 -29
- package/dist/server/src/camera/device.js +0 -20
- package/dist/server/src/camera/device.js.map +0 -1
- package/dist/server/src/camera/index.d.ts +0 -90
- package/dist/server/src/camera/index.js +0 -581
- package/dist/server/src/camera/index.js.map +0 -1
- package/dist/server/src/camera/interfaces/camera.d.ts +0 -10
- package/dist/server/src/camera/interfaces/camera.js +0 -23
- package/dist/server/src/camera/interfaces/camera.js.map +0 -1
- package/dist/server/src/camera/interfaces/prebuffer.d.ts +0 -11
- package/dist/server/src/camera/interfaces/prebuffer.js +0 -31
- package/dist/server/src/camera/interfaces/prebuffer.js.map +0 -1
- package/dist/server/src/camera/interfaces/ptz.d.ts +0 -13
- package/dist/server/src/camera/interfaces/ptz.js +0 -47
- package/dist/server/src/camera/interfaces/ptz.js.map +0 -1
- package/dist/server/src/camera/iou.d.ts +0 -2
- package/dist/server/src/camera/iou.js +0 -53
- package/dist/server/src/camera/iou.js.map +0 -1
- package/dist/server/src/camera/polygon.d.ts +0 -6
- package/dist/server/src/camera/polygon.js +0 -151
- package/dist/server/src/camera/polygon.js.map +0 -1
- package/dist/server/src/camera/streaming/peer-connection.d.ts +0 -55
- package/dist/server/src/camera/streaming/peer-connection.js +0 -203
- package/dist/server/src/camera/streaming/peer-connection.js.map +0 -1
- package/dist/server/src/camera/streaming/webrtc-connection.d.ts +0 -35
- package/dist/server/src/camera/streaming/webrtc-connection.js +0 -170
- package/dist/server/src/camera/streaming/webrtc-connection.js.map +0 -1
- package/dist/server/src/camera/streaming/werift-session.d.ts +0 -41
- package/dist/server/src/camera/streaming/werift-session.js +0 -179
- package/dist/server/src/camera/streaming/werift-session.js.map +0 -1
- package/dist/server/src/camera/videoFrame.d.ts +0 -24
- package/dist/server/src/camera/videoFrame.js +0 -104
- package/dist/server/src/camera/videoFrame.js.map +0 -1
- package/dist/server/src/index.d.ts +0 -18
- package/dist/server/src/index.js +0 -5
- package/dist/server/src/index.js.map +0 -1
- package/dist/server/src/nats/connection.d.ts +0 -17
- package/dist/server/src/nats/connection.js +0 -66
- package/dist/server/src/nats/connection.js.map +0 -1
- package/dist/server/src/nats/constants.d.ts +0 -1
- package/dist/server/src/nats/constants.js +0 -2
- package/dist/server/src/nats/constants.js.map +0 -1
- package/dist/server/src/nats/error.d.ts +0 -9
- package/dist/server/src/nats/error.js +0 -11
- package/dist/server/src/nats/error.js.map +0 -1
- package/dist/server/src/nats/messageQueue.d.ts +0 -20
- package/dist/server/src/nats/messageQueue.js +0 -132
- package/dist/server/src/nats/messageQueue.js.map +0 -1
- package/dist/server/src/nats/subscription.d.ts +0 -12
- package/dist/server/src/nats/subscription.js +0 -21
- package/dist/server/src/nats/subscription.js.map +0 -1
- package/dist/server/src/polyglot/node/decoder/child.d.ts +0 -1
- package/dist/server/src/polyglot/node/decoder/child.js +0 -644
- package/dist/server/src/polyglot/node/decoder/child.js.map +0 -1
- package/dist/server/src/polyglot/node/decoder/imageUtils.d.ts +0 -24
- package/dist/server/src/polyglot/node/decoder/imageUtils.js +0 -205
- package/dist/server/src/polyglot/node/decoder/imageUtils.js.map +0 -1
- package/dist/server/src/polyglot/node/decoder/wasm/build/decoder.d.ts +0 -144
- package/dist/server/src/polyglot/node/decoder/wasm/build/decoder.js +0 -65
- package/dist/server/src/polyglot/node/decoder/wasm/build/decoder.wasm +0 -0
- package/dist/server/src/polyglot/node/decoder/wasm/test/imageUtils.py +0 -106
- package/dist/server/src/polyglot/node/plugins/cameraStorage.d.ts +0 -107
- package/dist/server/src/polyglot/node/plugins/cameraStorage.js +0 -292
- package/dist/server/src/polyglot/node/plugins/cameraStorage.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/child.d.ts +0 -28
- package/dist/server/src/polyglot/node/plugins/child.js +0 -222
- package/dist/server/src/polyglot/node/plugins/child.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/configService.d.ts +0 -104
- package/dist/server/src/polyglot/node/plugins/configService.js +0 -213
- package/dist/server/src/polyglot/node/plugins/configService.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/pluginApi.d.ts +0 -35
- package/dist/server/src/polyglot/node/plugins/pluginApi.js +0 -23
- package/dist/server/src/polyglot/node/plugins/pluginApi.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/pluginLogger.d.ts +0 -18
- package/dist/server/src/polyglot/node/plugins/pluginLogger.js +0 -89
- package/dist/server/src/polyglot/node/plugins/pluginLogger.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/proxy/cameraDevice.d.ts +0 -69
- package/dist/server/src/polyglot/node/plugins/proxy/cameraDevice.js +0 -423
- package/dist/server/src/polyglot/node/plugins/proxy/cameraDevice.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/proxy/coreManager.d.ts +0 -40
- package/dist/server/src/polyglot/node/plugins/proxy/coreManager.js +0 -105
- package/dist/server/src/polyglot/node/plugins/proxy/coreManager.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/proxy/deviceManager.d.ts +0 -47
- package/dist/server/src/polyglot/node/plugins/proxy/deviceManager.js +0 -164
- package/dist/server/src/polyglot/node/plugins/proxy/deviceManager.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/schema.d.ts +0 -293
- package/dist/server/src/polyglot/node/plugins/schema.js +0 -740
- package/dist/server/src/polyglot/node/plugins/schema.js.map +0 -1
- package/dist/server/src/polyglot/node/plugins/storageController.d.ts +0 -30
- package/dist/server/src/polyglot/node/plugins/storageController.js +0 -40
- package/dist/server/src/polyglot/node/plugins/storageController.js.map +0 -1
- package/dist/server/src/polyglot/python/camera/interfaces/camera.py +0 -20
- package/dist/server/src/polyglot/python/camera/interfaces/prebuffer.py +0 -28
- package/dist/server/src/polyglot/python/camera/interfaces/ptz.py +0 -38
- package/dist/server/src/polyglot/python/camera/iou.py +0 -80
- package/dist/server/src/polyglot/python/camera/polygon.py +0 -173
- package/dist/server/src/polyglot/python/camera/video_frame.py +0 -188
- package/dist/server/src/polyglot/python/camera_ui_types/__init__.py +0 -1200
- package/dist/server/src/polyglot/python/decoder/child.py +0 -882
- package/dist/server/src/polyglot/python/decoder/image_utils.py +0 -149
- package/dist/server/src/polyglot/python/decoder/typings.py +0 -99
- package/dist/server/src/polyglot/python/plugins/camera_storage.py +0 -391
- package/dist/server/src/polyglot/python/plugins/child.py +0 -394
- package/dist/server/src/polyglot/python/plugins/config_service.py +0 -187
- package/dist/server/src/polyglot/python/plugins/constants.py +0 -1
- package/dist/server/src/polyglot/python/plugins/message_queue.py +0 -182
- package/dist/server/src/polyglot/python/plugins/plugin_api.py +0 -34
- package/dist/server/src/polyglot/python/plugins/plugin_logger.py +0 -111
- package/dist/server/src/polyglot/python/plugins/proxy/camera_device.py +0 -1599
- package/dist/server/src/polyglot/python/plugins/proxy/core_manager.py +0 -145
- package/dist/server/src/polyglot/python/plugins/proxy/device_manager.py +0 -226
- package/dist/server/src/polyglot/python/plugins/schema.py +0 -181
- package/dist/server/src/polyglot/python/plugins/storage_controller.py +0 -58
- package/dist/server/src/polyglot/python/plugins/typings.py +0 -253
- package/dist/server/src/polyglot/python/utilities/connection.py +0 -86
- package/dist/server/src/polyglot/python/utilities/json_lmdb.py +0 -19
- package/dist/server/src/polyglot/python/utilities/object_path.py +0 -195
- package/dist/server/src/polyglot/python/utilities/packer.py +0 -11
- package/dist/server/src/polyglot/python/utilities/subscriptions.py +0 -22
- package/dist/server/src/polyglot/python/utilities/task.py +0 -35
- package/dist/server/src/polyglot/python/utilities/thread.py +0 -10
- package/dist/server/src/polyglot/python/utilities/utils.py +0 -73
- package/dist/server/src/utils/ffmpeg.d.ts +0 -2
- package/dist/server/src/utils/ffmpeg.js +0 -61
- package/dist/server/src/utils/ffmpeg.js.map +0 -1
- package/dist/server/src/utils/network.d.ts +0 -12
- package/dist/server/src/utils/network.js +0 -92
- package/dist/server/src/utils/network.js.map +0 -1
- package/dist/server/src/utils/npm.d.ts +0 -4
- package/dist/server/src/utils/npm.js +0 -128
- package/dist/server/src/utils/npm.js.map +0 -1
- package/dist/server/src/utils/packer.d.ts +0 -2
- package/dist/server/src/utils/packer.js +0 -17
- package/dist/server/src/utils/packer.js.map +0 -1
- package/dist/server/src/utils/pythonInstaller.d.ts +0 -48
- package/dist/server/src/utils/pythonInstaller.js +0 -494
- package/dist/server/src/utils/pythonInstaller.js.map +0 -1
- package/dist/server/src/utils/reader.d.ts +0 -5
- package/dist/server/src/utils/reader.js +0 -41
- package/dist/server/src/utils/reader.js.map +0 -1
- package/dist/server/src/utils/subscribed.d.ts +0 -9
- package/dist/server/src/utils/subscribed.js +0 -17
- package/dist/server/src/utils/subscribed.js.map +0 -1
- package/dist/server/src/utils/utils.d.ts +0 -10
- package/dist/server/src/utils/utils.js +0 -165
- package/dist/server/src/utils/utils.js.map +0 -1
|
@@ -1,882 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import os
|
|
3
|
-
import random
|
|
4
|
-
import re
|
|
5
|
-
import signal
|
|
6
|
-
import string
|
|
7
|
-
import traceback
|
|
8
|
-
from contextlib import suppress
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
from typing import Any, Literal, Optional, Union, cast
|
|
11
|
-
|
|
12
|
-
from camera_ui_types import DecoderFormat, FrameData, FrameMetadata, ImageOptions
|
|
13
|
-
from image_utils import process_image_threaded
|
|
14
|
-
from nats.aio.msg import Msg
|
|
15
|
-
from nats.errors import NoRespondersError, TimeoutError
|
|
16
|
-
from plugins.plugin_logger import PluginLogger
|
|
17
|
-
from plugins.typings import ProcessPrebufferedImageRequest
|
|
18
|
-
from typings import (
|
|
19
|
-
BaseFrameWorkerConfig,
|
|
20
|
-
FfmpegArgs,
|
|
21
|
-
FfmpegArgsResponse,
|
|
22
|
-
FrameWorkerClient,
|
|
23
|
-
MainToWorkerMessage,
|
|
24
|
-
MainToWorkerResponse,
|
|
25
|
-
PingMessage,
|
|
26
|
-
PongMessage,
|
|
27
|
-
PrebufferContainer,
|
|
28
|
-
RecoveredClient,
|
|
29
|
-
SignalingRequest,
|
|
30
|
-
SignalingResponse,
|
|
31
|
-
WorkerMessage,
|
|
32
|
-
WorkerToMainMessage,
|
|
33
|
-
WorkerToMainResponse,
|
|
34
|
-
)
|
|
35
|
-
from utilities.connection import ProxyConnection
|
|
36
|
-
from utilities.packer import pack, unpack
|
|
37
|
-
from utilities.subscriptions import ProxySubscription
|
|
38
|
-
from utilities.task import TaskSet
|
|
39
|
-
|
|
40
|
-
PIX_FMT: Literal["yuv420p", "rgb24"] = "yuv420p"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class FrameWorkerChild:
|
|
44
|
-
def __init__(self):
|
|
45
|
-
self.config: BaseFrameWorkerConfig = {
|
|
46
|
-
"frameWorkerId": os.environ["FRAME_WORKER_ID"],
|
|
47
|
-
"cameraId": os.environ["CAMERA_ID"],
|
|
48
|
-
"cameraName": os.environ["CAMERA_NAME"],
|
|
49
|
-
"ffmpegPath": os.environ["FFMPEG_PATH"],
|
|
50
|
-
"logLevel": os.environ.get("LOGGER_LEVEL") or "debug",
|
|
51
|
-
"disableTimestamps": os.environ.get("LOGGER_DISABLE_TIMESTAMPS") == "true",
|
|
52
|
-
"auth": {
|
|
53
|
-
"user": os.environ["PROXY_USER"],
|
|
54
|
-
"password": os.environ["PROXY_PASSWORD"],
|
|
55
|
-
},
|
|
56
|
-
"proxyEndpoints": [endpoint for endpoint in os.environ["PROXY_ENDPOINTS"].split(",")],
|
|
57
|
-
"fps": int(os.environ["FPS"]) if "FPS" in os.environ else 10,
|
|
58
|
-
"resolution": int(os.environ["RESOLUTION"]) if "RESOLUTION" in os.environ else 640,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
del os.environ["FRAME_WORKER_ID"]
|
|
62
|
-
del os.environ["CAMERA_ID"]
|
|
63
|
-
del os.environ["CAMERA_NAME"]
|
|
64
|
-
del os.environ["FFMPEG_PATH"]
|
|
65
|
-
del os.environ["PROXY_USER"]
|
|
66
|
-
del os.environ["PROXY_PASSWORD"]
|
|
67
|
-
del os.environ["PROXY_ENDPOINTS"]
|
|
68
|
-
del os.environ["FPS"]
|
|
69
|
-
del os.environ["RESOLUTION"]
|
|
70
|
-
|
|
71
|
-
self.logger = PluginLogger("Frame Worker", self.config["disableTimestamps"])
|
|
72
|
-
self.logger.debug_enabled = self.config["logLevel"] == "debug" or self.config["logLevel"] == "trace"
|
|
73
|
-
self.logger.trace_enabled = self.config["logLevel"] == "trace"
|
|
74
|
-
|
|
75
|
-
self.subject = f"{self.config['frameWorkerId']}.frameWorkerChild"
|
|
76
|
-
self.ipc_subject = f"{self.config['frameWorkerId']}.frameWorker"
|
|
77
|
-
self.image_subject = f"{self.config['cameraId']}.frameGenerator"
|
|
78
|
-
self.signaling_subject = f"{self.config['cameraId']}.frameGenerator.signaling"
|
|
79
|
-
self.frame_subject = f"{self.config['cameraId']}.frameReceiver"
|
|
80
|
-
|
|
81
|
-
self.publisher: Optional[ProxyConnection] = None
|
|
82
|
-
self.signaling_publisher: Optional[ProxyConnection] = None
|
|
83
|
-
self.image_publisher: Optional[ProxyConnection] = None
|
|
84
|
-
self.frame_publisher: Optional[ProxyConnection] = None
|
|
85
|
-
|
|
86
|
-
self.subscriber: Optional[ProxySubscription] = None
|
|
87
|
-
self.image_subscriber: Optional[ProxySubscription] = None
|
|
88
|
-
self.signal_subscriber: Optional[ProxySubscription] = None
|
|
89
|
-
|
|
90
|
-
self.clients: dict[str, FrameWorkerClient] = {}
|
|
91
|
-
self.tasks: TaskSet = TaskSet(f"{self.config['cameraName']} FrameWorkerChild")
|
|
92
|
-
|
|
93
|
-
self.prebuffer_full = False
|
|
94
|
-
self.prebuffer_duration = 7
|
|
95
|
-
self.prebuffer_container: list[PrebufferContainer] = []
|
|
96
|
-
|
|
97
|
-
self.shutdown_event = asyncio.Event()
|
|
98
|
-
self.loop = asyncio.get_event_loop()
|
|
99
|
-
|
|
100
|
-
self._metadata: Optional[FrameMetadata] = None
|
|
101
|
-
self.metadata_event = asyncio.Event()
|
|
102
|
-
self.ffmpeg_process = None
|
|
103
|
-
|
|
104
|
-
self.stderr_task: Optional[asyncio.Task[None]] = None
|
|
105
|
-
self.stdout_task: Optional[asyncio.Task[None]] = None
|
|
106
|
-
self.close_task: Optional[asyncio.Task[None]] = None
|
|
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
|
-
|
|
115
|
-
self.restart_timeout: Optional[asyncio.TimerHandle] = None
|
|
116
|
-
self.restart_watchdog_delay = 5
|
|
117
|
-
|
|
118
|
-
self.close_timeout: Optional[asyncio.TimerHandle] = None
|
|
119
|
-
self.close_delay = 5
|
|
120
|
-
|
|
121
|
-
@property
|
|
122
|
-
def log_prefix(self) -> str:
|
|
123
|
-
return f"{self.config['cameraName']}:"
|
|
124
|
-
|
|
125
|
-
@property
|
|
126
|
-
async def metadata(self) -> FrameMetadata:
|
|
127
|
-
if not self._metadata:
|
|
128
|
-
await self.metadata_event.wait()
|
|
129
|
-
|
|
130
|
-
return cast(FrameMetadata, self._metadata)
|
|
131
|
-
|
|
132
|
-
async def listen(self) -> None:
|
|
133
|
-
self.setup_signal_handlers()
|
|
134
|
-
|
|
135
|
-
servers = [endpoint for endpoint in self.config["proxyEndpoints"] if endpoint.startswith("nats://")]
|
|
136
|
-
|
|
137
|
-
self.publisher = ProxyConnection(self.subject, servers, self.config["auth"])
|
|
138
|
-
await self.publisher.connect()
|
|
139
|
-
|
|
140
|
-
self.image_publisher = ProxyConnection(self.image_subject, servers, self.config["auth"])
|
|
141
|
-
await self.image_publisher.connect()
|
|
142
|
-
|
|
143
|
-
self.frame_publisher = ProxyConnection(self.frame_subject, servers, self.config["auth"])
|
|
144
|
-
await self.frame_publisher.connect()
|
|
145
|
-
|
|
146
|
-
self.signaling_publisher = ProxyConnection(self.signaling_subject, servers, self.config["auth"])
|
|
147
|
-
await self.signaling_publisher.connect()
|
|
148
|
-
|
|
149
|
-
self.subscriber = await self.publisher.subscribe(subject=self.subject)
|
|
150
|
-
self.subscriber.on("message", self.on_worker_message)
|
|
151
|
-
|
|
152
|
-
self.image_subscriber = await self.image_publisher.subscribe(subject=self.image_subject)
|
|
153
|
-
self.image_subscriber.on("message", self.on_image_request)
|
|
154
|
-
|
|
155
|
-
self.signal_subscriber = await self.signaling_publisher.subscribe(subject=self.signaling_subject)
|
|
156
|
-
self.signal_subscriber.on("message", self.on_signaling_message)
|
|
157
|
-
|
|
158
|
-
await self.send("started")
|
|
159
|
-
await self.shutdown_event.wait()
|
|
160
|
-
|
|
161
|
-
async def close(self) -> None:
|
|
162
|
-
self.closed = True
|
|
163
|
-
# await self.stop()
|
|
164
|
-
|
|
165
|
-
with suppress(Exception):
|
|
166
|
-
if self.publisher:
|
|
167
|
-
await self.publisher.close()
|
|
168
|
-
|
|
169
|
-
if self.image_publisher:
|
|
170
|
-
await self.image_publisher.close()
|
|
171
|
-
|
|
172
|
-
if self.signaling_publisher:
|
|
173
|
-
await self.signaling_publisher.close()
|
|
174
|
-
|
|
175
|
-
if self.frame_publisher:
|
|
176
|
-
await self.frame_publisher.close()
|
|
177
|
-
|
|
178
|
-
self.shutdown_event.set()
|
|
179
|
-
|
|
180
|
-
async def start(self) -> None:
|
|
181
|
-
try:
|
|
182
|
-
self.closed = False
|
|
183
|
-
self.restarting = False
|
|
184
|
-
self.stopped = False
|
|
185
|
-
|
|
186
|
-
if self.close_timeout:
|
|
187
|
-
self.close_timeout.cancel()
|
|
188
|
-
self.close_timeout = None
|
|
189
|
-
|
|
190
|
-
await self.start_video_session()
|
|
191
|
-
except Exception as e:
|
|
192
|
-
self.logger.error(f"{self.log_prefix} An error occurred while starting frame session:", e)
|
|
193
|
-
await self.restart_ffmpeg()
|
|
194
|
-
|
|
195
|
-
async def stop(self) -> None:
|
|
196
|
-
self.stopped = True
|
|
197
|
-
await self.disconnect_all_clients()
|
|
198
|
-
await self.reset()
|
|
199
|
-
|
|
200
|
-
async def reset(self) -> None:
|
|
201
|
-
self.reset_prebuffer()
|
|
202
|
-
self.reset_metadata()
|
|
203
|
-
await self.reset_video_session()
|
|
204
|
-
|
|
205
|
-
def reset_prebuffer(self) -> None:
|
|
206
|
-
self.prebuffer_full = False
|
|
207
|
-
self.prebuffer_container = []
|
|
208
|
-
|
|
209
|
-
def reset_metadata(self) -> None:
|
|
210
|
-
self.metadata_event.clear()
|
|
211
|
-
self._metadata = None
|
|
212
|
-
|
|
213
|
-
async def reset_video_session(self) -> None:
|
|
214
|
-
if self.close_timeout:
|
|
215
|
-
self.close_timeout.cancel()
|
|
216
|
-
self.close_timeout = None
|
|
217
|
-
|
|
218
|
-
if self.restart_timeout:
|
|
219
|
-
self.restart_timeout.cancel()
|
|
220
|
-
self.restart_timeout = None
|
|
221
|
-
|
|
222
|
-
if self.stderr_task:
|
|
223
|
-
self.stderr_task.cancel()
|
|
224
|
-
self.stderr_task = None
|
|
225
|
-
|
|
226
|
-
if self.stdout_task:
|
|
227
|
-
self.stdout_task.cancel()
|
|
228
|
-
self.stdout_task = None
|
|
229
|
-
|
|
230
|
-
if self.close_task:
|
|
231
|
-
self.close_task.cancel()
|
|
232
|
-
self.close_task = None
|
|
233
|
-
|
|
234
|
-
if self.ffmpeg_process:
|
|
235
|
-
self.logger.debug(f"{self.log_prefix} Stopping FFmpeg process...")
|
|
236
|
-
self.ffmpeg_process.kill()
|
|
237
|
-
await self.ffmpeg_process.wait()
|
|
238
|
-
self.ffmpeg_process = None
|
|
239
|
-
|
|
240
|
-
async def start_video_session(self) -> None:
|
|
241
|
-
self.reset_prebuffer()
|
|
242
|
-
await self.reset_video_session()
|
|
243
|
-
|
|
244
|
-
# await self.reset()
|
|
245
|
-
self.stopped = False
|
|
246
|
-
|
|
247
|
-
try:
|
|
248
|
-
self.tasks.add(self.spawn_ffmpeg())
|
|
249
|
-
await asyncio.wait_for(self.metadata_event.wait(), timeout=15)
|
|
250
|
-
self.logger.debug(f"{self.log_prefix} Frame session started")
|
|
251
|
-
except asyncio.TimeoutError:
|
|
252
|
-
raise Exception("FFmpeg process timed out") from None
|
|
253
|
-
|
|
254
|
-
def build_ffmpeg_args(self, ffmpeg_path: str, camera_source: str, ffmpeg_args: FfmpegArgs) -> list[str]:
|
|
255
|
-
return [
|
|
256
|
-
ffmpeg_path,
|
|
257
|
-
"-hide_banner",
|
|
258
|
-
# "-vsync",
|
|
259
|
-
# "0",
|
|
260
|
-
"-hwaccel",
|
|
261
|
-
ffmpeg_args["hwaccel"],
|
|
262
|
-
*ffmpeg_args["hwaccelArgs"],
|
|
263
|
-
"-fflags",
|
|
264
|
-
"nobuffer",
|
|
265
|
-
"-flags",
|
|
266
|
-
"low_delay",
|
|
267
|
-
"-analyzeduration",
|
|
268
|
-
"0",
|
|
269
|
-
"-probesize",
|
|
270
|
-
"500000",
|
|
271
|
-
"-reorder_queue_size",
|
|
272
|
-
"0",
|
|
273
|
-
"-user_agent",
|
|
274
|
-
"camera.ui/decoder",
|
|
275
|
-
"-rtsp_transport",
|
|
276
|
-
"tcp",
|
|
277
|
-
"-i",
|
|
278
|
-
camera_source,
|
|
279
|
-
"-threads",
|
|
280
|
-
ffmpeg_args["threads"],
|
|
281
|
-
"-vf",
|
|
282
|
-
f"showinfo=checksum=0,{ffmpeg_args['hwaccelFilter'].replace('640', str(self.config['resolution']))},fps=fps={self.config['fps']}",
|
|
283
|
-
"-an",
|
|
284
|
-
"-dn",
|
|
285
|
-
"-sn",
|
|
286
|
-
"-vcodec",
|
|
287
|
-
"rawvideo",
|
|
288
|
-
"-f",
|
|
289
|
-
"rawvideo",
|
|
290
|
-
"-pix_fmt",
|
|
291
|
-
PIX_FMT,
|
|
292
|
-
"-",
|
|
293
|
-
]
|
|
294
|
-
|
|
295
|
-
async def spawn_ffmpeg(self) -> None:
|
|
296
|
-
response = cast(Union[FfmpegArgsResponse, None], await self.send("getFFmpegArgs"))
|
|
297
|
-
|
|
298
|
-
if not response:
|
|
299
|
-
raise Exception("Error getting FFmpeg args from main process")
|
|
300
|
-
|
|
301
|
-
args = self.build_ffmpeg_args(
|
|
302
|
-
self.config["ffmpegPath"], response["cameraSource"], response["ffmpegArgs"]
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
self.logger.debug(f"{self.log_prefix} Starting FFmpeg process: {' '.join(args)}")
|
|
306
|
-
|
|
307
|
-
self.ffmpeg_process = await asyncio.create_subprocess_exec(
|
|
308
|
-
*args,
|
|
309
|
-
stdout=asyncio.subprocess.PIPE,
|
|
310
|
-
stderr=asyncio.subprocess.PIPE,
|
|
311
|
-
limit=10**8,
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
self.stderr_task = self.tasks.add(self.handle_ffmpeg_stderr())
|
|
315
|
-
self.stdout_task = self.tasks.add(self.handle_ffmpeg_stdout())
|
|
316
|
-
self.close_task = self.tasks.add(self.handle_ffmpeg_close())
|
|
317
|
-
|
|
318
|
-
async def restart_ffmpeg(self) -> None:
|
|
319
|
-
if not self.stopped and not self.closed and not self.restarting and len(self.clients) > 0:
|
|
320
|
-
self.restarting = True
|
|
321
|
-
|
|
322
|
-
self.logger.log(f"{self.log_prefix} Restarting FFmpeg process in {self.restart_delay} seconds...")
|
|
323
|
-
|
|
324
|
-
with suppress(asyncio.TimeoutError):
|
|
325
|
-
await asyncio.wait_for(asyncio.sleep(self.restart_delay), timeout=self.restart_delay)
|
|
326
|
-
|
|
327
|
-
if not self.stopped and not self.closed and len(self.clients) > 0:
|
|
328
|
-
await self.start()
|
|
329
|
-
else:
|
|
330
|
-
self.restarting = False
|
|
331
|
-
|
|
332
|
-
async def handle_ffmpeg_stderr(self) -> None:
|
|
333
|
-
try:
|
|
334
|
-
|
|
335
|
-
def is_closed() -> bool:
|
|
336
|
-
return (
|
|
337
|
-
self.stopped
|
|
338
|
-
or self.closed
|
|
339
|
-
or not self.ffmpeg_process
|
|
340
|
-
or not self.ffmpeg_process.stderr
|
|
341
|
-
or self.ffmpeg_process.returncode is not None
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
while not is_closed():
|
|
345
|
-
try:
|
|
346
|
-
line = await self.ffmpeg_process.stderr.readline() # type: ignore
|
|
347
|
-
|
|
348
|
-
if is_closed():
|
|
349
|
-
break
|
|
350
|
-
|
|
351
|
-
if not line or len(line) == 0:
|
|
352
|
-
continue
|
|
353
|
-
|
|
354
|
-
line = line.decode("utf-8").strip()
|
|
355
|
-
metadata = self.parse_frame_info(line)
|
|
356
|
-
|
|
357
|
-
if metadata:
|
|
358
|
-
self.logger.debug(f"{self.log_prefix} Metadata:", metadata)
|
|
359
|
-
self._metadata = metadata
|
|
360
|
-
self.metadata_event.set()
|
|
361
|
-
break
|
|
362
|
-
|
|
363
|
-
except Exception as e:
|
|
364
|
-
self.logger.error(
|
|
365
|
-
f"{self.log_prefix} An error occurred while reading ffmpeg stderr:",
|
|
366
|
-
e,
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
break
|
|
370
|
-
except asyncio.CancelledError:
|
|
371
|
-
pass
|
|
372
|
-
|
|
373
|
-
async def handle_ffmpeg_stdout(self) -> None:
|
|
374
|
-
try:
|
|
375
|
-
metadata = (await self.metadata).copy()
|
|
376
|
-
warned = False
|
|
377
|
-
|
|
378
|
-
def is_closed() -> bool:
|
|
379
|
-
return (
|
|
380
|
-
self.stopped
|
|
381
|
-
or self.closed
|
|
382
|
-
or not self.frame_publisher
|
|
383
|
-
or not self.ffmpeg_process
|
|
384
|
-
or not self.ffmpeg_process.stdout
|
|
385
|
-
or self.ffmpeg_process.returncode is not None
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
def reset_restart_watchdog():
|
|
389
|
-
if self.restart_timeout:
|
|
390
|
-
self.restart_timeout.cancel()
|
|
391
|
-
self.restart_timeout = None
|
|
392
|
-
|
|
393
|
-
def restart_watchdog():
|
|
394
|
-
reset_restart_watchdog()
|
|
395
|
-
self.restart_timeout = asyncio.get_event_loop().call_later(
|
|
396
|
-
self.restart_watchdog_delay,
|
|
397
|
-
lambda: asyncio.create_task(restart_ffmpeg(), name="FrameWorker Restart FFmpeg"),
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
async def restart_ffmpeg():
|
|
401
|
-
if not self.stopped and not self.closed and not self.restarting:
|
|
402
|
-
message = "FFmpeg process is not producing frames, terminating..."
|
|
403
|
-
|
|
404
|
-
nonlocal warned
|
|
405
|
-
if warned is False:
|
|
406
|
-
self.logger.debug(f"{self.log_prefix} {message}")
|
|
407
|
-
warned = True
|
|
408
|
-
else:
|
|
409
|
-
self.logger.error(f"{self.log_prefix} {message}")
|
|
410
|
-
|
|
411
|
-
await self.reset()
|
|
412
|
-
await self.restart_ffmpeg()
|
|
413
|
-
|
|
414
|
-
while not is_closed():
|
|
415
|
-
try:
|
|
416
|
-
restart_watchdog()
|
|
417
|
-
|
|
418
|
-
frame = await self.ffmpeg_process.stdout.readexactly( # type: ignore
|
|
419
|
-
int(metadata["frameSize"]),
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
warned = False
|
|
423
|
-
|
|
424
|
-
if is_closed():
|
|
425
|
-
reset_restart_watchdog()
|
|
426
|
-
break
|
|
427
|
-
|
|
428
|
-
frame_id = self.generate_id()
|
|
429
|
-
now = int(datetime.now().timestamp() * 1000)
|
|
430
|
-
|
|
431
|
-
self.prebuffer_container.append(
|
|
432
|
-
{
|
|
433
|
-
"frame_id": frame_id,
|
|
434
|
-
"time": now,
|
|
435
|
-
"frame": frame,
|
|
436
|
-
}
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
if (
|
|
440
|
-
len(self.prebuffer_container) > 0
|
|
441
|
-
and now - self.prebuffer_container[0]["time"] >= (self.prebuffer_duration * 1000)
|
|
442
|
-
and not self.prebuffer_full
|
|
443
|
-
):
|
|
444
|
-
self.logger.debug(f"{self.log_prefix} Prebuffer is full")
|
|
445
|
-
self.prebuffer_full = True
|
|
446
|
-
|
|
447
|
-
while len(self.prebuffer_container) > 0 and self.prebuffer_container[0]["time"] < now - (
|
|
448
|
-
self.prebuffer_duration * 1000
|
|
449
|
-
):
|
|
450
|
-
self.prebuffer_container.pop(0)
|
|
451
|
-
|
|
452
|
-
if self.prebuffer_full:
|
|
453
|
-
frame_data: FrameData = {"frameId": frame_id, "timestamp": now}
|
|
454
|
-
|
|
455
|
-
await self.frame_publisher.publish(self.frame_subject, frame_data) # type: ignore
|
|
456
|
-
|
|
457
|
-
except asyncio.IncompleteReadError as e:
|
|
458
|
-
if not is_closed() and not self.restarting:
|
|
459
|
-
self.logger.error(f"{self.log_prefix} Incomplete read error:", e)
|
|
460
|
-
await asyncio.sleep(0.1)
|
|
461
|
-
|
|
462
|
-
except Exception as e:
|
|
463
|
-
if not is_closed() and not self.restarting:
|
|
464
|
-
self.logger.error(
|
|
465
|
-
f"{self.log_prefix} An error occurred while reading ffmpeg stdout:",
|
|
466
|
-
e,
|
|
467
|
-
)
|
|
468
|
-
break
|
|
469
|
-
finally:
|
|
470
|
-
reset_restart_watchdog()
|
|
471
|
-
except asyncio.CancelledError:
|
|
472
|
-
pass
|
|
473
|
-
|
|
474
|
-
async def handle_ffmpeg_close(self) -> None:
|
|
475
|
-
try:
|
|
476
|
-
if self.ffmpeg_process:
|
|
477
|
-
code = await self.ffmpeg_process.wait()
|
|
478
|
-
|
|
479
|
-
if code != 0 and code != -9 or not self.stopped:
|
|
480
|
-
self.logger.error(
|
|
481
|
-
self.log_prefix,
|
|
482
|
-
f"FFmpeg process has exited with code {code} (unexpected)",
|
|
483
|
-
)
|
|
484
|
-
else:
|
|
485
|
-
self.logger.debug(
|
|
486
|
-
f"{self.log_prefix} FFmpeg process has exited with code {code} (expected)"
|
|
487
|
-
)
|
|
488
|
-
|
|
489
|
-
self.ffmpeg_process = None
|
|
490
|
-
|
|
491
|
-
await self.reset()
|
|
492
|
-
await self.restart_ffmpeg()
|
|
493
|
-
except asyncio.CancelledError:
|
|
494
|
-
pass
|
|
495
|
-
|
|
496
|
-
def parse_frame_info(self, stderr: str) -> Optional[FrameMetadata]:
|
|
497
|
-
patterns = {
|
|
498
|
-
"s": r"s:\s*(\d+x\d+)",
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
matches: dict[str, str] = {}
|
|
502
|
-
|
|
503
|
-
for key, pattern in patterns.items():
|
|
504
|
-
match = re.search(pattern, stderr)
|
|
505
|
-
if match:
|
|
506
|
-
matches[key] = match.group(1)
|
|
507
|
-
|
|
508
|
-
if "s" not in matches:
|
|
509
|
-
return None
|
|
510
|
-
|
|
511
|
-
width, height = map(int, matches["s"].split("x"))
|
|
512
|
-
new_width = self.config["resolution"]
|
|
513
|
-
new_height = round((new_width * height) / width)
|
|
514
|
-
format: DecoderFormat = "yuv" if PIX_FMT == "yuv420p" else "rgb"
|
|
515
|
-
bytes_per_pixel = 1.5 if format == "yuv" else 3
|
|
516
|
-
frame_size = new_width * new_height * bytes_per_pixel
|
|
517
|
-
|
|
518
|
-
metadata: FrameMetadata = {
|
|
519
|
-
"format": format,
|
|
520
|
-
"frameSize": frame_size,
|
|
521
|
-
"width": new_width,
|
|
522
|
-
"height": new_height,
|
|
523
|
-
"origHeight": height,
|
|
524
|
-
"origWidth": width,
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return metadata
|
|
528
|
-
|
|
529
|
-
async def send(
|
|
530
|
-
self,
|
|
531
|
-
message: WorkerMessage,
|
|
532
|
-
data: Optional[dict[str, Any]] = None,
|
|
533
|
-
) -> Union[dict[str, Any], None]:
|
|
534
|
-
if self.publisher is None:
|
|
535
|
-
raise Exception("Publisher not initialized")
|
|
536
|
-
|
|
537
|
-
if data is None:
|
|
538
|
-
data = {}
|
|
539
|
-
|
|
540
|
-
worker_message: WorkerToMainMessage = {
|
|
541
|
-
"message": message,
|
|
542
|
-
"data": data,
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
response: MainToWorkerResponse = await self.publisher.request(
|
|
546
|
-
self.ipc_subject, worker_message, timeout=30
|
|
547
|
-
)
|
|
548
|
-
|
|
549
|
-
return response.get("response")
|
|
550
|
-
|
|
551
|
-
async def add_client(self, plugin_id: str, request_id: str) -> None:
|
|
552
|
-
if not self.clients.get(plugin_id):
|
|
553
|
-
self.clients[plugin_id] = FrameWorkerClient(plugin_id, [request_id])
|
|
554
|
-
|
|
555
|
-
client = self.clients[plugin_id]
|
|
556
|
-
|
|
557
|
-
if request_id not in client.request_ids:
|
|
558
|
-
client.request_ids.append(request_id)
|
|
559
|
-
|
|
560
|
-
self.logger.trace(f"{self.log_prefix} New client {self.client_identifier(plugin_id, request_id)}")
|
|
561
|
-
|
|
562
|
-
if self.close_timeout:
|
|
563
|
-
self.close_timeout.cancel()
|
|
564
|
-
self.close_timeout = None
|
|
565
|
-
|
|
566
|
-
await self.send("updateClient", {"pluginId": client.plugin_id, "requestIds": client.request_ids})
|
|
567
|
-
|
|
568
|
-
if len(self.clients) > 0 and self.ffmpeg_process is None and not self.restarting:
|
|
569
|
-
await self.start()
|
|
570
|
-
|
|
571
|
-
async def remove_client(self, plugin_id: str, request_id: str) -> None:
|
|
572
|
-
client = self.clients[plugin_id]
|
|
573
|
-
|
|
574
|
-
if not client or request_id not in client.request_ids:
|
|
575
|
-
return
|
|
576
|
-
|
|
577
|
-
self.logger.trace(
|
|
578
|
-
f"{self.log_prefix} Removing client {self.client_identifier(plugin_id, request_id)}"
|
|
579
|
-
)
|
|
580
|
-
|
|
581
|
-
if request_id in client.request_ids:
|
|
582
|
-
client.request_ids.remove(request_id)
|
|
583
|
-
|
|
584
|
-
if len(client.request_ids) == 0:
|
|
585
|
-
del self.clients[plugin_id]
|
|
586
|
-
await self.send("removeClient", {"pluginId": client.plugin_id, "requestIds": client.request_ids})
|
|
587
|
-
else:
|
|
588
|
-
await self.send("updateClient", {"pluginId": client.plugin_id, "requestIds": client.request_ids})
|
|
589
|
-
|
|
590
|
-
if len(self.clients) == 0:
|
|
591
|
-
if self.close_timeout:
|
|
592
|
-
self.close_timeout.cancel()
|
|
593
|
-
|
|
594
|
-
self.close_timeout = asyncio.get_event_loop().call_later(
|
|
595
|
-
self.close_delay, lambda: asyncio.create_task(self.stop(), name="FrameWorker Stop")
|
|
596
|
-
)
|
|
597
|
-
|
|
598
|
-
async def recover_clients(self, clients: list[RecoveredClient]) -> None:
|
|
599
|
-
for client in clients:
|
|
600
|
-
for request_id in client["requestIds"]:
|
|
601
|
-
self.logger.trace(
|
|
602
|
-
f"{self.log_prefix} Recovering client {self.client_identifier(client['pluginId'], request_id)}"
|
|
603
|
-
)
|
|
604
|
-
|
|
605
|
-
self.clients[client["pluginId"]] = FrameWorkerClient(client["pluginId"], client["requestIds"])
|
|
606
|
-
|
|
607
|
-
if len(self.clients) > 0:
|
|
608
|
-
await self.start()
|
|
609
|
-
|
|
610
|
-
async def disconnect_client(self, camera_id: str, plugin_id: str, request_id: str) -> None:
|
|
611
|
-
client = self.clients.get(plugin_id)
|
|
612
|
-
|
|
613
|
-
if not self.signaling_publisher or not client or request_id not in client.request_ids:
|
|
614
|
-
return
|
|
615
|
-
|
|
616
|
-
ping_target_subject = f"{plugin_id}.camera.{camera_id}.signaling"
|
|
617
|
-
|
|
618
|
-
response: SignalingResponse = {"type": "disconnect", "requestId": request_id}
|
|
619
|
-
|
|
620
|
-
await self.signaling_publisher.publish(ping_target_subject, response)
|
|
621
|
-
await self.remove_client(plugin_id, request_id)
|
|
622
|
-
|
|
623
|
-
async def disconnect_all_clients(self) -> None:
|
|
624
|
-
for client in self.clients.values():
|
|
625
|
-
for request_id in client.request_ids:
|
|
626
|
-
await self.disconnect_client(self.config["cameraId"], client.plugin_id, request_id)
|
|
627
|
-
|
|
628
|
-
async def handle_connect_message(self, message: SignalingRequest) -> None:
|
|
629
|
-
await self.add_client(message["pluginId"], message["requestId"])
|
|
630
|
-
self.tasks.add(self.start_ping_pong(message["cameraId"], message["pluginId"], message["requestId"]))
|
|
631
|
-
|
|
632
|
-
async def handle_disconnect_message(self, message: SignalingRequest) -> None:
|
|
633
|
-
await self.remove_client(message["pluginId"], message["requestId"])
|
|
634
|
-
|
|
635
|
-
async def handle_metadata_message(self, msg: Msg, message: SignalingRequest) -> None:
|
|
636
|
-
metadata = (await self.metadata).copy()
|
|
637
|
-
|
|
638
|
-
response: SignalingResponse = {
|
|
639
|
-
"type": "metadata",
|
|
640
|
-
"requestId": message["requestId"],
|
|
641
|
-
"metadata": metadata,
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
await msg.respond(pack(response))
|
|
645
|
-
|
|
646
|
-
async def start_ping_pong(self, camera_id: str, plugin_id: str, request_id: str) -> None:
|
|
647
|
-
ping_target_subject = f"{plugin_id}.camera.{camera_id}.signaling"
|
|
648
|
-
ping_interval = 3
|
|
649
|
-
ping_timeout = 2
|
|
650
|
-
|
|
651
|
-
def request_id_exists() -> bool:
|
|
652
|
-
return self.clients.get(plugin_id, False) and request_id in self.clients[plugin_id].request_ids
|
|
653
|
-
|
|
654
|
-
while self.signaling_publisher and request_id_exists():
|
|
655
|
-
try:
|
|
656
|
-
message: PingMessage = {"type": "ping", "requestId": request_id}
|
|
657
|
-
|
|
658
|
-
# self.logger.trace(f"{self.log_prefix} Sending ping to {self.client_identifier(plugin_id, request_id)}")
|
|
659
|
-
|
|
660
|
-
response: PongMessage = await self.signaling_publisher.request(
|
|
661
|
-
ping_target_subject,
|
|
662
|
-
message,
|
|
663
|
-
timeout=ping_timeout,
|
|
664
|
-
)
|
|
665
|
-
|
|
666
|
-
if not request_id_exists():
|
|
667
|
-
break
|
|
668
|
-
|
|
669
|
-
if response["type"] != "pong":
|
|
670
|
-
raise Exception("Invalid response to ping")
|
|
671
|
-
|
|
672
|
-
# self.logger.trace(f"{self.log_prefix} Received pong from {self.client_identifier(plugin_id, request_id)}")
|
|
673
|
-
|
|
674
|
-
await asyncio.sleep(ping_interval)
|
|
675
|
-
except (Exception, NoRespondersError, TimeoutError) as e:
|
|
676
|
-
if request_id_exists():
|
|
677
|
-
if isinstance(e, (NoRespondersError, TimeoutError)):
|
|
678
|
-
# self.logger.trace(f"{self.log_prefix} Ping/Pong timeout for {self.client_identifier(plugin_id, request_id)}")
|
|
679
|
-
await self.remove_client(plugin_id, request_id)
|
|
680
|
-
else:
|
|
681
|
-
self.logger.error(f"{self.log_prefix} Error during ping/pong:", e)
|
|
682
|
-
|
|
683
|
-
break
|
|
684
|
-
|
|
685
|
-
async def on_worker_message(self, msg: Msg) -> None:
|
|
686
|
-
if self.closed:
|
|
687
|
-
return
|
|
688
|
-
|
|
689
|
-
self.tasks.add(self.respond_on_ipc_message(msg))
|
|
690
|
-
|
|
691
|
-
async def on_signaling_message(self, msg: Msg) -> None:
|
|
692
|
-
if self.closed:
|
|
693
|
-
return
|
|
694
|
-
|
|
695
|
-
self.tasks.add(self.respond_on_signaling_message(msg))
|
|
696
|
-
|
|
697
|
-
async def on_image_request(self, msg: Msg) -> None:
|
|
698
|
-
if self.closed:
|
|
699
|
-
return
|
|
700
|
-
|
|
701
|
-
self.tasks.add(self.respond_on_prebuffered_image_message(msg))
|
|
702
|
-
|
|
703
|
-
async def respond_on_ipc_message(self, msg: Msg) -> None:
|
|
704
|
-
try:
|
|
705
|
-
main_message: MainToWorkerMessage = unpack(msg.data)
|
|
706
|
-
response: WorkerToMainResponse = {}
|
|
707
|
-
message = main_message["message"]
|
|
708
|
-
|
|
709
|
-
if message == "setCameraName":
|
|
710
|
-
self.config["cameraName"] = main_message["data"]["cameraName"]
|
|
711
|
-
elif message == "recoverClients":
|
|
712
|
-
await self.recover_clients(main_message["data"]["clients"])
|
|
713
|
-
|
|
714
|
-
await msg.respond(pack(response))
|
|
715
|
-
except Exception as e:
|
|
716
|
-
self.logger.error(f"{self.log_prefix} Error while processing IPC respond:", e)
|
|
717
|
-
|
|
718
|
-
async def respond_on_signaling_message(self, msg: Msg) -> None:
|
|
719
|
-
try:
|
|
720
|
-
message: SignalingRequest = unpack(msg.data)
|
|
721
|
-
|
|
722
|
-
if message["type"] == "connect":
|
|
723
|
-
await self.handle_connect_message(message)
|
|
724
|
-
elif message["type"] == "disconnect":
|
|
725
|
-
await self.handle_disconnect_message(message)
|
|
726
|
-
elif message["type"] == "metadata":
|
|
727
|
-
await self.handle_metadata_message(msg, message)
|
|
728
|
-
except Exception as e:
|
|
729
|
-
self.logger.error(f"{self.log_prefix} Error while processing signaling respond:", e)
|
|
730
|
-
|
|
731
|
-
async def respond_on_prebuffered_image_message(self, msg: Msg) -> None:
|
|
732
|
-
try:
|
|
733
|
-
message: ProcessPrebufferedImageRequest = unpack(msg.data)
|
|
734
|
-
metadata = message["metadata"]
|
|
735
|
-
frame_data = message["frameData"]
|
|
736
|
-
options = message["options"]
|
|
737
|
-
prebuffer_duration = message["prebufferDuration"]
|
|
738
|
-
width = metadata["width"]
|
|
739
|
-
height = metadata["height"]
|
|
740
|
-
format = metadata["format"]
|
|
741
|
-
|
|
742
|
-
target_frame = self.find_target_frame(frame_data, prebuffer_duration)
|
|
743
|
-
|
|
744
|
-
if not target_frame:
|
|
745
|
-
self.log_frame_not_found()
|
|
746
|
-
else:
|
|
747
|
-
processed_image = await self.process_frame(
|
|
748
|
-
target_frame["frame"], width, height, format, options
|
|
749
|
-
)
|
|
750
|
-
await msg.respond(pack(processed_image))
|
|
751
|
-
return
|
|
752
|
-
|
|
753
|
-
await msg.respond(pack(b""))
|
|
754
|
-
except Exception as e:
|
|
755
|
-
self.logger.error(f"{self.log_prefix} Error while processing image respond:", e)
|
|
756
|
-
|
|
757
|
-
def find_target_frame(
|
|
758
|
-
self, frame_data: FrameData, prebuffer_duration: int
|
|
759
|
-
) -> Optional[PrebufferContainer]:
|
|
760
|
-
target_index = next(
|
|
761
|
-
(
|
|
762
|
-
i
|
|
763
|
-
for i, frame in enumerate(self.prebuffer_container)
|
|
764
|
-
if frame["frame_id"] == frame_data["frameId"]
|
|
765
|
-
),
|
|
766
|
-
-1,
|
|
767
|
-
)
|
|
768
|
-
|
|
769
|
-
if target_index == -1:
|
|
770
|
-
return self.find_nearest_frame(frame_data)
|
|
771
|
-
|
|
772
|
-
if prebuffer_duration > 0:
|
|
773
|
-
target_time = self.prebuffer_container[target_index]["time"] - prebuffer_duration * 1000
|
|
774
|
-
result = self.find_nearest_frame_by_time(target_time)
|
|
775
|
-
if result is not None:
|
|
776
|
-
return result
|
|
777
|
-
|
|
778
|
-
return self.prebuffer_container[target_index]
|
|
779
|
-
|
|
780
|
-
def find_nearest_frame(self, frame_data: FrameData) -> Optional[PrebufferContainer]:
|
|
781
|
-
max_time_diff = self.max_diff_frame * (1000 / self.config["fps"])
|
|
782
|
-
|
|
783
|
-
nearest_frame = None
|
|
784
|
-
min_time_diff = float("inf")
|
|
785
|
-
|
|
786
|
-
for frame in self.prebuffer_container:
|
|
787
|
-
current_time_diff = abs(frame["time"] - frame_data["timestamp"])
|
|
788
|
-
|
|
789
|
-
if current_time_diff <= max_time_diff and (
|
|
790
|
-
nearest_frame is None or current_time_diff < min_time_diff
|
|
791
|
-
):
|
|
792
|
-
nearest_frame = frame
|
|
793
|
-
min_time_diff = current_time_diff
|
|
794
|
-
|
|
795
|
-
return nearest_frame
|
|
796
|
-
|
|
797
|
-
def find_nearest_frame_by_time(self, target_time: float) -> Optional[PrebufferContainer]:
|
|
798
|
-
max_time_diff = (self.max_diff_frame / self.config["fps"]) * 1000
|
|
799
|
-
nearest_frame = None
|
|
800
|
-
min_diff = float("inf")
|
|
801
|
-
|
|
802
|
-
for frame in self.prebuffer_container:
|
|
803
|
-
if frame["time"] <= target_time:
|
|
804
|
-
time_diff = abs(frame["time"] - target_time)
|
|
805
|
-
if time_diff <= max_time_diff and time_diff < min_diff:
|
|
806
|
-
nearest_frame = frame
|
|
807
|
-
min_diff = time_diff
|
|
808
|
-
|
|
809
|
-
return nearest_frame
|
|
810
|
-
|
|
811
|
-
async def process_frame(
|
|
812
|
-
self,
|
|
813
|
-
frame: bytes,
|
|
814
|
-
width: int,
|
|
815
|
-
height: int,
|
|
816
|
-
format: Union[DecoderFormat, Literal["rgba", "gray"]],
|
|
817
|
-
options: ImageOptions,
|
|
818
|
-
) -> bytes:
|
|
819
|
-
return await process_image_threaded(
|
|
820
|
-
frame,
|
|
821
|
-
width,
|
|
822
|
-
height,
|
|
823
|
-
format,
|
|
824
|
-
options,
|
|
825
|
-
)
|
|
826
|
-
|
|
827
|
-
def log_frame_not_found(self):
|
|
828
|
-
if len(self.prebuffer_container) > 0:
|
|
829
|
-
self.logger.warn(f"{self.log_prefix} Frame not found in prebuffer")
|
|
830
|
-
else:
|
|
831
|
-
self.logger.warn(f"{self.log_prefix} Prebuffer is empty! FFmpeg is not producing frames?")
|
|
832
|
-
|
|
833
|
-
def client_identifier(self, plugin_id: str, request_id: str) -> str:
|
|
834
|
-
return f"{plugin_id}:{request_id}"
|
|
835
|
-
|
|
836
|
-
def generate_id(self, length: int = 9) -> str:
|
|
837
|
-
characters = string.ascii_letters + string.digits
|
|
838
|
-
return "".join(random.choice(characters) for _ in range(length))
|
|
839
|
-
|
|
840
|
-
def setup_signal_handlers(self) -> None:
|
|
841
|
-
loop = asyncio.get_running_loop()
|
|
842
|
-
loop.set_exception_handler(self.handle_exception)
|
|
843
|
-
|
|
844
|
-
for signame in ("SIGINT", "SIGTERM"):
|
|
845
|
-
loop.add_signal_handler(
|
|
846
|
-
getattr(signal, signame),
|
|
847
|
-
lambda signame=signame: self.gracefully_close(signame, getattr(signal, signame)),
|
|
848
|
-
)
|
|
849
|
-
|
|
850
|
-
def gracefully_close(self, signame: str, reason: Any = "unknown") -> None:
|
|
851
|
-
if self.stopped:
|
|
852
|
-
return
|
|
853
|
-
|
|
854
|
-
self.logger.debug(
|
|
855
|
-
f"{self.log_prefix} Frame Worker is gracefully closing from signal='{signame}' reason='{reason}'..."
|
|
856
|
-
)
|
|
857
|
-
|
|
858
|
-
self.tasks.add(self.close())
|
|
859
|
-
self.shutdown_event.set()
|
|
860
|
-
|
|
861
|
-
def handle_exception(self, loop: asyncio.AbstractEventLoop, context: Any) -> None:
|
|
862
|
-
print(f"{self.log_prefix} Exception context:", context)
|
|
863
|
-
traceback.print_exc()
|
|
864
|
-
|
|
865
|
-
msg = context.get("message", "Unknown error")
|
|
866
|
-
exception = context.get("exception")
|
|
867
|
-
|
|
868
|
-
if exception:
|
|
869
|
-
tb_str = "".join(traceback.format_exception(type(exception), exception, exception.__traceback__)) # type: ignore
|
|
870
|
-
|
|
871
|
-
self.logger.error(f"{self.log_prefix} Caught exception: {msg}\n{tb_str}")
|
|
872
|
-
else:
|
|
873
|
-
self.logger.error(f"{self.log_prefix} Caught exception: {msg}")
|
|
874
|
-
|
|
875
|
-
if loop.is_running():
|
|
876
|
-
self.gracefully_close("uncaughtException", msg)
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
if __name__ == "__main__":
|
|
880
|
-
worker = FrameWorkerChild()
|
|
881
|
-
asyncio.run(worker.listen())
|
|
882
|
-
# asyncio.run(worker.listen(), debug=True)
|