Compare commits

...

637 Commits

Author SHA1 Message Date
Koushik Dutta
e163aa8153 wyze: cluster mode support 2024-12-30 21:36:52 -08:00
Koushik Dutta
268225647e postbeta 2024-12-30 21:29:24 -08:00
Koushik Dutta
93f94b0b0a server: Fixup casing 2024-12-30 21:29:15 -08:00
Koushik Dutta
db73baf4c1 postbeta 2024-12-30 21:28:28 -08:00
Koushik Dutta
404cf47d2e server: make mediaManager cluster aware 2024-12-30 21:28:06 -08:00
Koushik Dutta
b751f77b0b wyze: require linux 2024-12-30 21:01:08 -08:00
Koushik Dutta
884ce3e175 postbeta 2024-12-30 19:32:23 -08:00
Koushik Dutta
0cb0071874 postrelease 2024-12-30 19:28:35 -08:00
Koushik Dutta
d9637679bf server: verup 2024-12-30 19:27:57 -08:00
Koushik Dutta
7ea849d357 openvino: massive perf improvements via async api usage 2024-12-29 23:20:17 -08:00
Koushik Dutta
e4f01f10f4 postbeta 2024-12-29 20:02:34 -08:00
Koushik Dutta
bd61e9a5dd server: fix fs.promises. 2024-12-29 20:02:26 -08:00
Koushik Dutta
a2f8504290 postbeta 2024-12-29 19:51:15 -08:00
Koushik Dutta
928683a429 server: more cluster mode hooks 2024-12-29 19:51:05 -08:00
Koushik Dutta
4d6bd61650 postbeta 2024-12-29 14:34:29 -08:00
Koushik Dutta
9321a5e0dd Merge branch 'main' of github.com:koush/scrypted 2024-12-29 14:33:44 -08:00
Koushik Dutta
1622a0be63 server: update launch.json configs 2024-12-29 14:33:40 -08:00
Brett Jia
55cb62cb72 server: use standard-telnetlib for Python 3.13+ (#1669)
* bump portable-python to version with 3.13

* add standard-telnetlib to scrypted requirements for Python 3.13+
2024-12-29 14:22:32 -08:00
Koushik Dutta
11ea37d1c4 wyze: fix performance issues! 2024-12-28 22:22:51 -08:00
Koushik Dutta
8e1dfa8174 core: ensure lxc-docker is updated every boot 2024-12-28 20:35:44 -08:00
Koushik Dutta
0cf4802385 install: fixup missing debugpy 2024-12-28 15:39:26 -08:00
Koushik Dutta
194facb19c Revert "docker: remove pips"
This reverts commit 5f7ecc0410.
2024-12-28 15:37:46 -08:00
Koushik Dutta
6438ad1e3c tensorflow-lite: pipeline pre/post processing 2024-12-28 15:24:28 -08:00
Koushik Dutta
586f78ebc1 lxc: fix auto repair in systemd script 2024-12-28 14:50:57 -08:00
Koushik Dutta
48c5e1a5fe tensorflow-lite: quantization cleanups 2024-12-28 14:24:12 -08:00
Koushik Dutta
a6a986a8ac postbeta 2024-12-28 13:25:08 -08:00
Koushik Dutta
0b04d92131 server: use site packages in python for debugging 2024-12-28 13:24:40 -08:00
Koushik Dutta
05e9627f4a server: add debugpy to install list 2024-12-28 13:09:47 -08:00
Koushik Dutta
381c6de336 install/server: remove psutil 2024-12-28 13:08:22 -08:00
Koushik Dutta
4206ee4686 Merge branch 'main' of github.com:koush/scrypted 2024-12-27 22:40:45 -08:00
Koushik Dutta
e33a793867 tensorflow-lite: use new yolov9s model with separate outputs to fix quantization accuracy loss 2024-12-27 22:40:33 -08:00
Brett Jia
699eebaf14 docker: set default shell to bash (#1667)
* docker: change default shell to bash

* set SHELL
2024-12-26 18:50:49 -08:00
Brett Jia
45a2d5764c docker: dynamically find amdgpu deb package name (#1666) 2024-12-26 18:22:30 -08:00
Koushik Dutta
5f7ecc0410 docker: remove pips 2024-12-26 17:58:35 -08:00
Koushik Dutta
92257e41c1 webrtc: fix media conversion failure 2024-12-25 20:22:53 -08:00
Koushik Dutta
c5a703896c webrtc: improve media to signaling channel conversion 2024-12-25 19:55:16 -08:00
Koushik Dutta
51aa79956a core: publish ui updates 2024-12-25 19:29:12 -08:00
Koushik Dutta
fc1151ce8c Merge branch 'main' of github.com:koush/scrypted 2024-12-25 19:28:33 -08:00
Koushik Dutta
eaa2c37d57 tensorflow-lite: add relu models 2024-12-25 19:28:28 -08:00
Koushik Dutta
162bb7bfab proxmox: docker-compose.sh should repair dpkg first 2024-12-24 19:52:32 -08:00
Koushik Dutta
e467414704 rtp: fix leak if child process fails to spawn 2024-12-23 15:06:30 -08:00
Koushik Dutta
8ec6a25833 rebroadcast: add support for rtsp url using cluster address in case scrypted server address is not set 2024-12-23 11:16:36 -08:00
Koushik Dutta
56bc0d6a26 postbeta 2024-12-22 12:23:13 -08:00
Koushik Dutta
9098426c3b server: add rpc support for shallow serialized arrays 2024-12-22 12:16:10 -08:00
Koushik Dutta
0d9d425ef0 server: fix python search order 2024-12-22 12:05:33 -08:00
Koushik Dutta
4c6ca3b2a5 Merge branch 'main' of github.com:koush/scrypted 2024-12-22 09:01:34 -08:00
Koushik Dutta
762e058ec5 videoanalyis: reduce default confidence for smart motion sensor 2024-12-22 09:01:30 -08:00
Koushik Dutta
f02509152d docker: improve disk setup and add auto remount 2024-12-21 20:30:44 -08:00
Koushik Dutta
9d92031e4c videoanalysis: make some settings immediate 2024-12-20 11:22:02 -08:00
Koushik Dutta
6d0027d3e8 proxmox: onboot 2024-12-20 09:10:45 -08:00
Koushik Dutta
274e043c81 videoanalysis: add separate crop zone 2024-12-19 18:03:58 -08:00
Koushik Dutta
817a6f5a59 videoanalysis: fix zone normalization 2024-12-19 15:22:04 -08:00
Koushik Dutta
cbdf8873e0 Merge branch 'main' of github.com:koush/scrypted 2024-12-19 14:54:28 -08:00
Koushik Dutta
c9c9e106db videoanalaysis: fix zone persistence 2024-12-19 14:54:23 -08:00
Long Zheng
f3d7ebd2a2 Fix Windows install script NPM/Node version clash (#1662)
* Fix NPM/Node version clash

* Update install-scrypted-dependencies-win.ps1
2024-12-19 12:04:23 -08:00
Koushik Dutta
0ea6b13cb9 videoanalysis: smart occupancy sensor 2024-12-19 11:14:27 -08:00
Koushik Dutta
68cbe9a4f9 videoanalysis: smart occupancy sensor 2024-12-19 10:48:03 -08:00
Koushik Dutta
c7ab9085ff core: more ui fixes 2024-12-19 10:36:03 -08:00
Koushik Dutta
45993b3cb9 snapshot: fix nre on toImage format validaiton 2024-12-19 10:24:30 -08:00
Koushik Dutta
82ce08ab53 core: occupancy ui 2024-12-19 09:40:42 -08:00
Koushik Dutta
262fb32085 core: publish ui 2024-12-19 09:33:05 -08:00
Koushik Dutta
919d2dee85 tensorflow-lite: missing files 2024-12-18 09:40:36 -08:00
Koushik Dutta
1bb7df53c7 mqtt: publish 2024-12-18 09:40:10 -08:00
Koushik Dutta
612cf7b520 postbeta 2024-12-17 21:49:13 -08:00
Koushik Dutta
55a80f1898 server: fix env nre 2024-12-17 21:49:04 -08:00
Koushik Dutta
44ab56a888 tensorflow-lite: threshold cleanup 2024-12-17 11:38:12 -08:00
Koushik Dutta
eaae396861 tensorflow-lite: new default model 2024-12-17 09:22:25 -08:00
Koushik Dutta
cff170a508 postbeta 2024-12-16 19:55:06 -08:00
Koushik Dutta
c811109ee9 sdk/core: rebuild with cjs/es fixes 2024-12-16 19:54:04 -08:00
Koushik Dutta
c8e4502d11 sdk/server: more reliable module env detection 2024-12-16 19:51:25 -08:00
Koushik Dutta
b75c0e0ca1 sdk: ensure import.meta is undefined for webpack 2024-12-16 19:23:14 -08:00
Koushik Dutta
f64c9226a1 postbeta 2024-12-16 08:03:44 -08:00
Koushik Dutta
95dd67cd3a server: combine NODE_PATHs 2024-12-16 08:03:24 -08:00
Koushik Dutta
3ac0ca5c7a sdk: remove old node pty 2024-12-16 07:57:08 -08:00
Koushik Dutta
cd68af9796 core: remove old node pty 2024-12-16 07:54:25 -08:00
Koushik Dutta
9c1be5865b openvino: use relu face 2024-12-15 19:37:00 -08:00
Koushik Dutta
675f23235b postbeta 2024-12-15 13:12:00 -08:00
Koushik Dutta
0824136458 server: allow NODE_PATH override 2024-12-15 13:11:45 -08:00
Koushik Dutta
2b1b65d723 core: publish ui fix for extension toggling new devices 2024-12-15 12:16:26 -08:00
Koushik Dutta
16995ed9e8 core: fix aggregate device 2024-12-15 09:34:22 -08:00
Koushik Dutta
c5fb7d20a0 videoanalysis: fix motion reporting from object detector 2024-12-13 23:38:08 -08:00
Koushik Dutta
8c67f1e0ff tapo: update readme 2024-12-13 12:45:59 -08:00
Koushik Dutta
50e2ae83b4 objectdetector: fixup normalizeBox to allow scalar 2024-12-13 08:23:33 -08:00
Koushik Dutta
1eb4f6fd55 core: publish support for editing camera zones via deviceFilter param 2024-12-12 15:48:12 -08:00
Koushik Dutta
9152512679 proxmox: use restore storage for reset as well 2024-12-12 10:57:48 -08:00
Koushik Dutta
7870ed7eeb reolink: fixup probe 2024-12-12 09:11:48 -08:00
Koushik Dutta
40c0dea505 reolink: validate device info 2024-12-11 21:39:27 -08:00
Koushik Dutta
3542d327ea postbeta 2024-12-11 20:09:42 -08:00
Koushik Dutta
2ef87c21b6 server: allow cluster labels to request plugins 2024-12-11 20:09:33 -08:00
Koushik Dutta
4585f43318 openvino: disable npu for recognition with user input 2024-12-11 15:54:16 -08:00
Koushik Dutta
30da19510a openvino: disable npu for recognition 2024-12-11 15:53:40 -08:00
Koushik Dutta
5ea1c9467f proxmox: fix restore prompt install 2024-12-10 15:47:32 -08:00
Koushik Dutta
1d6eabc9e8 proxmox: restore prompt 2024-12-10 15:39:52 -08:00
Koushik Dutta
9ea4b5a29b proxmox: find a default storage device 2024-12-10 15:29:06 -08:00
Koushik Dutta
539692867b openvino: reenable npu 2024-12-10 12:22:13 -08:00
Koushik Dutta
e796404995 core: deprecate lxc 2024-12-10 10:43:37 -08:00
Koushik Dutta
54b21260d1 core: fix cluster worker rename 2024-12-10 10:23:26 -08:00
Koushik Dutta
be35fb2dc2 postbeta 2024-12-10 09:55:43 -08:00
Koushik Dutta
04065a3487 core/client: publish 2024-12-10 09:42:00 -08:00
Koushik Dutta
ac882c723a server: fix cluster dependencies 2024-12-10 09:40:16 -08:00
Koushik Dutta
02cde6382c server: revert tsconfig change 2024-12-10 09:33:04 -08:00
Koushik Dutta
f942a13e90 client: add cluster manager 2024-12-10 09:32:23 -08:00
Koushik Dutta
6299caac20 server: fix typings 2024-12-10 09:30:29 -08:00
Koushik Dutta
6f0501634f server: move cluster manager 2024-12-10 09:30:13 -08:00
Koushik Dutta
575e544c40 core: Fix nre if clusterManager does not exist 2024-12-09 21:45:24 -08:00
Koushik Dutta
ff448e9c7f core: manage cluster through ui 2024-12-09 21:19:10 -08:00
Koushik Dutta
6173d67bb0 postbeta 2024-12-09 14:18:44 -08:00
Koushik Dutta
4431158bfa server: add various controls for server node in cluster mode 2024-12-09 14:04:43 -08:00
Koushik Dutta
822054e888 videoanalysis: hide decoder option if detection provides it 2024-12-09 10:07:42 -08:00
Koushik Dutta
d7e21d1d44 mqtt: fix exports 2024-12-08 16:08:36 -08:00
Koushik Dutta
6e451a1b06 server: npm audit 2024-12-07 18:53:49 -08:00
Koushik Dutta
57eccd4ad7 Merge branch 'main' into cluster 2024-12-07 18:37:30 -08:00
Koushik Dutta
c2d45e4357 cloud: additional tunnel check 2024-12-07 18:37:16 -08:00
Koushik Dutta
698a4a4a4a postbeta 2024-12-06 12:47:06 -08:00
Koushik Dutta
01493e311d server: fix repl in cluster mode 2024-12-06 12:46:49 -08:00
Koushik Dutta
a6d62365dc postbeta 2024-12-06 11:52:53 -08:00
Koushik Dutta
9b504a280f docker: switch to noble base 2024-12-06 11:52:26 -08:00
Koushik Dutta
5df8689236 docker: update intel for legacy + latest install process. 2024-12-06 11:39:08 -08:00
Koushik Dutta
235d408f1f dockker: test noble 2024-12-06 11:27:09 -08:00
Koushik Dutta
9b4547be85 docker: prevent pip upgrade 2024-12-06 11:14:02 -08:00
Koushik Dutta
0b8bc0d0d1 docker: python3.12 2024-12-06 10:54:05 -08:00
Koushik Dutta
134d4be1b7 docker: noble base 2024-12-06 10:47:01 -08:00
Koushik Dutta
e77487ed15 docker: noble base 2024-12-06 10:46:25 -08:00
Koushik Dutta
6e60fe1c09 openvino: disable npu due to openvino bug 2024-12-06 10:07:43 -08:00
Long Zheng
a7424b3546 Update Windows install script workaround npm issue (#1654)
* Update Windows install script workaround npm issue

* Test removing RunAsAdministrator

* Revert "Test removing RunAsAdministrator"

This reverts commit 46c80964ea.

* Test removing npm fix

* Revert "Test removing npm fix"

This reverts commit 0f9adbeae6.

* Test dump daemon logs

* More test

* More tests

* More tests

* Test

* Test

* Test

* Test

* Cleanup

* Add spawn error handler

* Fix event handler

* Remove node version debug
2024-12-06 09:19:01 -08:00
Koushik Dutta
70cf3488ef openvino: add scrypted_yolov9t_relu_int8_320 2024-12-06 08:49:56 -08:00
Koushik Dutta
d74ac6fb8e server: cleanup thread peers 2024-12-05 21:00:07 -08:00
Koushik Dutta
47cad5d747 mqtt: fix break 2024-12-05 12:58:58 -08:00
Koushik Dutta
4ebb7215c0 server: fix HoL in sendStream by using a dedicated cluster connect 2024-12-05 12:24:55 -08:00
Koushik Dutta
1d55830f10 server: sendStream should also be one way 2024-12-05 11:38:40 -08:00
Koushik Dutta
0bb5c79875 postbeta 2024-12-05 11:37:54 -08:00
Koushik Dutta
e3ca09a80b server/sdk: add support for HttpResponse.sendStream 2024-12-05 11:37:27 -08:00
Koushik Dutta
ef53829ccc core: clean up device groups 2024-12-05 09:38:36 -08:00
Koushik Dutta
5f0cf6b6c2 mqtt: remove scrypted-eval fs hack 2024-12-05 09:38:00 -08:00
Koushik Dutta
59e09825ff postbeta 2024-12-05 09:24:47 -08:00
Koushik Dutta
b4aa20b4cd server: remove legacy storage event 2024-12-05 09:24:36 -08:00
Koushik Dutta
e2eba2a227 openvino: beta 2024-12-05 08:46:36 -08:00
Koushik Dutta
9370a163fd videoanalysis: improve low watermark throttling 2024-12-05 08:45:58 -08:00
Koushik Dutta
c65f38f251 postbeta 2024-12-05 08:45:13 -08:00
Koushik Dutta
83bde83a39 server: ensure cluster client service control 2024-12-05 08:45:03 -08:00
Koushik Dutta
786b4b5ed9 postbeta 2024-12-04 21:32:07 -08:00
Koushik Dutta
f9c1d7704a sdk: fix sourcemap 2024-12-04 21:31:52 -08:00
Koushik Dutta
3162d2be34 Update install-amd-graphics.sh 2024-12-04 20:48:34 -08:00
Koushik Dutta
3f0a788a6a sdk/server: add mode to workers 2024-12-04 17:51:36 -08:00
Koushik Dutta
72504286ea postbeta 2024-12-04 15:59:56 -08:00
Koushik Dutta
c664cc3b4d server/sdk: include/check sdk version for plugin forwards/backwards compat. 2024-12-04 15:59:41 -08:00
Koushik Dutta
99853906b9 postbeta 2024-12-04 11:42:23 -08:00
Koushik Dutta
ea873a527b sdk/server: clean up sdk init race conditions to allow side effect imports 2024-12-04 10:54:28 -08:00
Koushik Dutta
df0b13512a videoanalysis: fix sample history tracking to purge before measurement 2024-12-03 13:42:40 -08:00
Koushik Dutta
1651152eec videoanalysis: publish 2024-12-03 10:06:36 -08:00
Koushik Dutta
7b3ab501b2 server: remove cluster cpu tracking 2024-12-03 00:21:46 -08:00
Koushik Dutta
3a77a3398d videoanalysis: use detection fps as measurement of system load 2024-12-03 00:00:37 -08:00
Koushik Dutta
f2ece1270a openvino: update to relu+int8 models 2024-12-02 22:49:58 -08:00
Koushik Dutta
8b6d8aeae6 Merge branch 'main' into cluster 2024-12-02 22:14:01 -08:00
Koushik Dutta
578bba67f8 postbeta 2024-12-02 19:00:55 -08:00
Koushik Dutta
15fb7d86e2 postbeta 2024-12-02 18:58:00 -08:00
Koushik Dutta
9d23caa66d server: downgrade typescript 2024-12-02 18:57:51 -08:00
Koushik Dutta
c46ed2cef5 postbeta 2024-12-02 18:46:17 -08:00
Koushik Dutta
7dcfdaa98e server: prevent crash on missing cpu usage 2024-12-02 18:46:06 -08:00
Koushik Dutta
ee23c93132 postbeta 2024-12-02 15:41:50 -08:00
Koushik Dutta
b1b0dd8997 server: fix non cluster crash 2024-12-02 15:41:42 -08:00
Koushik Dutta
6cc5a0e04c postbeta 2024-12-02 15:10:52 -08:00
Koushik Dutta
a75b263141 server: cluster cpu usage monitoring 2024-12-02 15:08:56 -08:00
Koushik Dutta
d91ec68e6c sdk: find object cluster worker affinity 2024-12-02 11:02:35 -08:00
Koushik Dutta
8ccc7a6c06 postbeta 2024-12-01 20:09:18 -08:00
Koushik Dutta
a6ece48cc3 server: add cluster worker weight 2024-12-01 17:46:14 -08:00
Koushik Dutta
7398f280cc postbeta 2024-11-30 23:19:40 -08:00
Koushik Dutta
eb1d0f647a server: fix es imports on old node 2024-11-30 23:19:30 -08:00
Koushik Dutta
b5a40b27a9 postbeta 2024-11-30 22:52:37 -08:00
Koushik Dutta
84870b444c server: revert createRequire 2024-11-30 22:49:59 -08:00
Koushik Dutta
339c934dda sdk: rollup fixes 2024-11-30 22:46:12 -08:00
Koushik Dutta
4df0eec70a server: esmodule cleanups 2024-11-30 20:00:41 -08:00
Koushik Dutta
6d268ade69 server: formatting 2024-11-30 09:41:30 -08:00
Koushik Dutta
6b040954a0 esmodule: project cleanup 2024-11-30 09:38:15 -08:00
Koushik Dutta
73d2f5b408 esmodule plugins: wip 2024-11-30 09:09:38 -08:00
Koushik Dutta
71bb2ec80a postbeta 2024-11-29 14:06:02 -08:00
Koushik Dutta
92b120886c server: remove log from eval params 2024-11-29 14:05:52 -08:00
Koushik Dutta
9001d996e2 sdk: fix commonjs entry mangling debug 2024-11-28 22:13:11 -08:00
Koushik Dutta
d060a74689 docker: Update install-amd-graphics.sh 2024-11-28 20:09:51 -08:00
Koushik Dutta
8ba4c46576 docker: Update install-intel-npu.sh 2024-11-28 19:56:16 -08:00
Koushik Dutta
a77f82462d openvino: rollback openvino 2024-11-28 19:53:24 -08:00
Koushik Dutta
3a1401afbb openvino: bump openvino 2024-11-28 19:44:47 -08:00
Koushik Dutta
14ae374916 postbeta 2024-11-28 19:37:55 -08:00
Koushik Dutta
52d915cc68 server: dependency updates 2024-11-28 19:37:44 -08:00
Koushik Dutta
cb501e66c6 sdk: publush 2024-11-28 19:14:34 -08:00
Koushik Dutta
053b43128f Merge branch 'cluster' of github.com:koush/scrypted into cluster 2024-11-28 18:47:41 -08:00
Koushik Dutta
702456a40d sdk: rollup support 2024-11-28 18:25:12 -08:00
Koushik Dutta
17ebbb1656 sdk: rollup support 2024-11-28 18:24:46 -08:00
Koushik Dutta
0f79cd88ce sdk: remove dead dep 2024-11-28 12:41:07 -08:00
Koushik Dutta
76c960100c sdk: remove dead dep 2024-11-28 12:39:11 -08:00
Koushik Dutta
6d56e41651 sample: update 2024-11-28 10:56:56 -08:00
Koushik Dutta
640d66474c postbeta 2024-11-28 10:48:19 -08:00
Koushik Dutta
238c82a354 server: add cluster worker info 2024-11-28 10:42:42 -08:00
Koushik Dutta
25a369403c server: add cluster worker info 2024-11-28 10:41:04 -08:00
Koushik Dutta
b1e1f54af5 server: add hooks to get cluster worker controls 2024-11-28 10:02:08 -08:00
Koushik Dutta
8df38dbebe server: add env control 2024-11-28 09:43:22 -08:00
Koushik Dutta
229dcd3174 postbeta 2024-11-28 09:28:34 -08:00
Koushik Dutta
c3d6dcb6a2 server: pass through service control 2024-11-28 09:28:24 -08:00
Koushik Dutta
0c951519e2 postbeta 2024-11-28 08:57:46 -08:00
Koushik Dutta
1f406ae740 server: pass through service control for cluster mode 2024-11-28 08:57:23 -08:00
Koushik Dutta
8d0de7e557 postbeta 2024-11-27 18:39:51 -08:00
Koushik Dutta
e799ada9c9 sdk: fixup clusterWorkerId to be optional 2024-11-26 09:02:01 -08:00
Koushik Dutta
ed498ae418 server/sdk: make worker disposable.
todo: implement python resource pattern?
2024-11-25 11:01:07 -08:00
Koushik Dutta
5b46036b2d sdk: add clusterWorkerId option to generateVideoFrames 2024-11-25 10:53:43 -08:00
Koushik Dutta
8e888bc6a1 Update docker-compose.yml 2024-11-23 22:05:56 -08:00
Koushik Dutta
c5053008b7 docker: use relative path for volume. 2024-11-23 22:04:33 -08:00
Koushik Dutta
7bd4f4053d predict: publish 2024-11-23 21:50:38 -08:00
Koushik Dutta
f83cbfa5e7 predict: use new cluster worker labels 2024-11-23 18:39:29 -08:00
Koushik Dutta
8480713ec6 postbeta 2024-11-23 08:05:26 -08:00
Koushik Dutta
dcae7ce367 server: remove debug code causing crashes 2024-11-23 08:05:17 -08:00
Koushik Dutta
101d362260 openvino: beta 2024-11-22 21:20:09 -08:00
Koushik Dutta
73bdca1be6 postbeta 2024-11-22 21:18:51 -08:00
Koushik Dutta
c407fa0b9f server: ensure alert log goes to console as well 2024-11-22 21:10:35 -08:00
Koushik Dutta
d26c595fd6 server: clean up clustering lifecycle management 2024-11-22 20:57:34 -08:00
Koushik Dutta
38c00f5b9b openvino: fix cluster label to require x64 2024-11-22 19:01:20 -08:00
Koushik Dutta
ab4738973d predict: cluster for should enforce compute/darwin label 2024-11-22 17:08:43 -08:00
Koushik Dutta
ea065f506c predict: fix load balancer 2024-11-21 21:53:09 -08:00
Koushik Dutta
4b7b66c96b postbeta 2024-11-21 21:08:22 -08:00
Koushik Dutta
0462ad228b server: fix non cluster crash 2024-11-21 21:08:13 -08:00
Koushik Dutta
45ac7f2f4e postbeta 2024-11-21 20:54:29 -08:00
Koushik Dutta
6618129e1d server: cluster load balancing 2024-11-21 20:54:20 -08:00
Koushik Dutta
1a72eddcc8 predict: formatting 2024-11-21 15:22:35 -08:00
Koushik Dutta
1c9037dc35 predict: fix worker startup on primary server 2024-11-21 15:22:16 -08:00
Koushik Dutta
2c5b79291f server: python formatting 2024-11-21 14:53:16 -08:00
Koushik Dutta
cd0ab104ea predict: formatting 2024-11-21 14:52:01 -08:00
Koushik Dutta
c6f4c1a669 onnx: implement clustering. and cleanup coreml/openvino. 2024-11-21 14:51:31 -08:00
Koushik Dutta
9f28e38716 openvino: cluster support 2024-11-21 14:37:49 -08:00
Koushik Dutta
ba8f25fde3 postbeta 2024-11-21 14:30:14 -08:00
Koushik Dutta
9f27b2f382 Merge branch 'main' into cluster 2024-11-21 11:20:49 -08:00
Koushik Dutta
96fa6af0fc reolink: fix missing debug setting 2024-11-21 11:20:44 -08:00
Koushik Dutta
eca5fbecdc Merge branch 'main' into cluster 2024-11-21 10:04:47 -08:00
Koushik Dutta
8e0e2854e9 dev: update npm-install.sh 2024-11-21 10:04:42 -08:00
Koushik Dutta
1eb9d938e7 core: publish 2024-11-21 10:04:14 -08:00
Koushik Dutta
095f80e1f9 dev: update npm-install.sh 2024-11-21 10:04:03 -08:00
Koushik Dutta
da1f6118c8 predict: wip coreml clustering across multiple macs 2024-11-20 22:27:20 -08:00
Koushik Dutta
5060748e9d server: implement clustered plugin debugging 2024-11-20 20:53:23 -08:00
Koushik Dutta
25df4f8376 server: fix cluster logging 2024-11-20 20:51:45 -08:00
Koushik Dutta
56fdff3545 server: cluster client error logging 2024-11-20 18:30:28 -08:00
Koushik Dutta
2fbfe2cb65 server: more logging 2024-11-20 18:28:12 -08:00
Koushik Dutta
32ede1f7fe postbeta 2024-11-20 18:16:09 -08:00
Koushik Dutta
1a2ec8ab4e server: log startup 2024-11-20 18:16:01 -08:00
Koushik Dutta
53cab91b02 server: refactor runtime worker creation 2024-11-20 14:53:58 -08:00
Koushik Dutta
02a46a9202 postbeta 2024-11-20 12:38:06 -08:00
Koushik Dutta
69f4de66e9 server: exit hooks for python fork 2024-11-20 12:18:25 -08:00
Koushik Dutta
aed6e0c446 server: wip cluster mode load balancing 2024-11-20 11:39:21 -08:00
Koushik Dutta
432c178f29 sdk: more cluster fixes for python 2024-11-20 10:58:10 -08:00
Koushik Dutta
bcf698daa3 sdk: expose ClusterManager to python 2024-11-20 10:13:54 -08:00
Koushik Dutta
347a957cd3 sdk: add cluster manager 2024-11-20 10:10:47 -08:00
Koushik Dutta
459b95a0e2 server: python cluster worker routing 2024-11-18 21:33:49 -08:00
Koushik Dutta
1c18129449 postbeta 2024-11-18 21:05:37 -08:00
Koushik Dutta
21274df881 server: fix cluster check nre 2024-11-18 21:05:28 -08:00
Koushik Dutta
ca243e79bb server: apply default runtime for cluster fork 2024-11-18 21:03:00 -08:00
Koushik Dutta
924394d365 postbeta 2024-11-18 19:50:15 -08:00
Koushik Dutta
23167da88b server: fork by clusterWorkerId 2024-11-18 19:29:07 -08:00
Koushik Dutta
c1d48e1c6b sdk: add cluster worker request to fork 2024-11-18 15:47:31 -08:00
Koushik Dutta
7a22e17d84 Merge branch 'main' into cluster 2024-11-18 13:46:27 -08:00
Koushik Dutta
75b2ff22ce openvino: regenerate models 2024-11-18 13:46:19 -08:00
Koushik Dutta
edde093140 Merge branch 'main' into cluster 2024-11-18 13:03:14 -08:00
Koushik Dutta
8622934c8b coreml: cluster support 2024-11-18 13:00:24 -08:00
Koushik Dutta
153cc3ed94 server: python cleanup 2024-11-18 12:26:46 -08:00
Koushik Dutta
2ae6113750 server: wip python cluster fork 2024-11-18 11:31:52 -08:00
Koushik Dutta
4ec001c2a2 server: assign workers ids 2024-11-18 10:11:33 -08:00
Koushik Dutta
794ac6c8d2 postbeta 2024-11-18 09:38:13 -08:00
Koushik Dutta
8422ffe55a postbeta 2024-11-17 22:20:51 -08:00
Koushik Dutta
285c07e33e postbeta 2024-11-17 20:43:46 -08:00
Koushik Dutta
ef04398a79 openvino: 9c relu int8 2024-11-17 19:15:43 -08:00
Koushik Dutta
6429ea718a postbeta 2024-11-17 15:38:52 -08:00
Koushik Dutta
5f9147e720 server: fix node cluster ping 2024-11-17 15:38:42 -08:00
Koushik Dutta
ea4922d8e5 onnx: fix provider detection 2024-11-17 10:24:09 -08:00
Koushik Dutta
70293ca827 server: cluster affinity 2024-11-16 19:35:40 -08:00
Koushik Dutta
878487180e postbeta 2024-11-16 19:07:05 -08:00
Koushik Dutta
f094903ed9 server: fix cluster fork liveness leak 2024-11-16 19:06:53 -08:00
Koushik Dutta
5117e217cf core: publish 2024-11-16 11:48:13 -08:00
Koushik Dutta
07c3f2832a postbeta 2024-11-16 11:35:50 -08:00
Koushik Dutta
977666bc3c server: add worker ids 2024-11-16 11:35:39 -08:00
Koushik Dutta
2ec6760308 nvidia: notes 2024-11-16 11:07:51 -08:00
Koushik Dutta
0dbe556835 diagnostics: use a better defaults for detection plugin verification 2024-11-16 09:40:34 -08:00
Koushik Dutta
c4f76df255 onnx: fix execution provider reporting 2024-11-16 09:32:55 -08:00
Koushik Dutta
0bf0ec08ab server: plugin init cleanups 2024-11-15 23:40:38 -08:00
Koushik Dutta
1a2216a7de postbeta 2024-11-15 22:43:29 -08:00
Koushik Dutta
d868c3b3bb server: remove stats checker 2024-11-15 22:43:19 -08:00
Koushik Dutta
882709ea51 server: cluster fork tracking 2024-11-15 22:00:51 -08:00
Koushik Dutta
df249c554c common: persistent fork service 2024-11-15 21:41:07 -08:00
Koushik Dutta
fcbaeb1d1d postbeta 2024-11-15 19:54:12 -08:00
Koushik Dutta
c6dda05fa4 server: force ipv4 on cluster connect 2024-11-15 19:54:01 -08:00
Koushik Dutta
953b7812c5 server: shuffle python cluster code 2024-11-15 19:53:16 -08:00
Koushik Dutta
7434a5c4ba postbeta 2024-11-15 15:06:09 -08:00
Koushik Dutta
b240a17bb0 server: support hostnames in clustering, and auto detection of client cluster addresses for easy image cloning. 2024-11-15 15:03:32 -08:00
Koushik Dutta
aab0507805 Merge branch 'main' into cluster 2024-11-15 11:37:39 -08:00
Koushik Dutta
3e091623a8 docker: add amd kfd device to install script 2024-11-15 11:37:35 -08:00
Koushik Dutta
0871898385 videoanalysis: log filtered detections 2024-11-15 10:06:38 -08:00
Koushik Dutta
eea7e4be32 beta 2024-11-15 10:02:13 -08:00
Koushik Dutta
8167ca85bb postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
a09291114f server: possible fix for electron mac startup hang 2024-11-15 10:02:13 -08:00
Koushik Dutta
39efe0d994 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
21f56216b0 server: fix unnecessary peer creation 2024-11-15 10:02:13 -08:00
Koushik Dutta
8820bac571 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
47a683e385 server: fix ping by providing pong 2024-11-15 10:02:13 -08:00
Koushik Dutta
17f367a373 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
fad0a520ca server: simplify pong 2024-11-15 10:02:13 -08:00
Koushik Dutta
1f0d5dc3b9 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
a965f9b569 server: simplify pong 2024-11-15 10:02:13 -08:00
Koushik Dutta
eb1a388f69 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
b2cf5ac3c7 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
ce10a49f0f server: fix ping/pong 2024-11-15 10:02:13 -08:00
Koushik Dutta
5e31a0db96 server: python cleanup 2024-11-15 10:02:13 -08:00
Koushik Dutta
8f1a673db5 server: refactor cluster 2024-11-15 10:02:13 -08:00
Koushik Dutta
7405476556 server: cluster env var sanitization 2024-11-15 10:02:13 -08:00
Koushik Dutta
7dc86f59bf postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
2d7cef600d server: fix cluster connect logging 2024-11-15 10:02:13 -08:00
Koushik Dutta
5de0f8937b postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
8e8d333ea2 server: cluster logging 2024-11-15 10:02:13 -08:00
Koushik Dutta
d66a6317de zwave: cluster support 2024-11-15 10:02:13 -08:00
Koushik Dutta
49e3fc1438 predict: fix cluster labels 2024-11-15 10:02:13 -08:00
Koushik Dutta
fbe3daa072 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
670216135b server: fix python stats updater 2024-11-15 10:02:13 -08:00
Koushik Dutta
ff903fa891 rebroadcast: fix external urls with ipv6 2024-11-15 10:02:13 -08:00
Koushik Dutta
ebc6ede275 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
4de91d0673 server: fix zipapi rpc bug 2024-11-15 10:02:13 -08:00
Koushik Dutta
3da1d00f6f postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
4ff00a7753 server: fix cluster any label 2024-11-15 10:02:13 -08:00
Koushik Dutta
f245fb257d postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
b1a21a6037 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
0b9f3309a2 postbeta 2024-11-15 10:02:13 -08:00
Koushik Dutta
09b9b33bac server: working python clustering 2024-11-15 10:02:13 -08:00
Koushik Dutta
21d020919a client: update des 2024-11-15 10:02:13 -08:00
Koushik Dutta
02d090cb94 server: fix python cluster server loop missing 2024-11-15 10:02:13 -08:00
Koushik Dutta
817db34357 server: refactor cluster connect, remove dead code 2024-11-15 10:02:13 -08:00
Koushik Dutta
a3eda8cfba server: cleanup fork envs 2024-11-15 10:02:13 -08:00
Koushik Dutta
5a62fdc06b server/sdk: new cluster label format 2024-11-15 10:02:13 -08:00
Koushik Dutta
5ff8a65c4a predict: add cluster label settings to package.json 2024-11-15 10:02:13 -08:00
Koushik Dutta
719dfd2f24 server: plugin device deletion crash fix 2024-11-15 10:02:13 -08:00
Koushik Dutta
7d28d1d9d4 server: wip python clustering 2024-11-15 10:02:13 -08:00
Koushik Dutta
aaa924b9b4 core: publish 2024-11-15 10:02:13 -08:00
Koushik Dutta
f69b93c9fa server: fix consoles in clustered environment 2024-11-15 10:02:13 -08:00
Koushik Dutta
12be06adad rebroadcast: cleanup 2024-11-15 10:02:13 -08:00
Koushik Dutta
f6fa28b584 server: fix cluster host volumes 2024-11-15 10:02:13 -08:00
Koushik Dutta
fc1e5210a5 server: cleanup 2024-11-15 10:02:13 -08:00
Koushik Dutta
7601b8f0d0 server: fixup cluster clients from other addresses 2024-11-15 10:02:13 -08:00
Koushik Dutta
b0557704b2 cleanup 2024-11-15 10:02:13 -08:00
Koushik Dutta
572883ed98 server: functional cluster console 2024-11-15 10:02:13 -08:00
Koushik Dutta
92927c8b93 server: working node cluster fork 2024-11-15 10:02:13 -08:00
Koushik Dutta
11ae57b185 server: wip cluster 2024-11-15 10:02:13 -08:00
Koushik Dutta
9f55f0b32a rpc: add peer const 2024-11-15 10:02:13 -08:00
Koushik Dutta
ef52e0a723 server: cleanup import 2024-11-15 10:02:13 -08:00
Koushik Dutta
3df6af1fcd server: add tls listener 2024-11-15 10:02:13 -08:00
Koushik Dutta
a283cfb429 server: remove legacy socket rpc channel 2024-11-15 10:02:13 -08:00
Koushik Dutta
3ae2dd769a sdk: fork labels 2024-11-15 10:02:13 -08:00
Koushik Dutta
3b916e7e20 server: wip cluster 2024-11-15 10:02:13 -08:00
Koushik Dutta
d93f05a228 server: wip cluster 2024-11-15 10:02:13 -08:00
Koushik Dutta
68183775db reolink: publish 2024-11-15 10:02:02 -08:00
Koushik Dutta
a8db883661 unifi-protect: fix fingerprint on new protect 2024-11-15 10:01:49 -08:00
apocaliss92
4a51caa281 reolink: Add zoom RTSP streams to trackmix cameras (#1635)
* Add zoom RTSP streams to trackmix cameras

* update rtmp streams for POE TrackMix (#1)

* update rtmp streams for POE TrackMix

Fixing resolution of main streams too for Trackmix.

* leave in legacy bcs stream

* flv streams removed

* autotrack bcs stream restored

* additional rtsp streams added only to channel 0

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
Co-authored-by: Joshua Seidel <29733828+JoshuaSeidel@users.noreply.github.com>
2024-11-14 22:08:20 -08:00
Koushik Dutta
c3148b8ed9 server: disable nan serialization completely in python 2024-11-10 12:30:05 -08:00
Koushik Dutta
bc95a15f89 Revert "server: do not serialize python nan in rpc protocol."
This reverts commit e9d73c6faa.
2024-11-10 12:29:26 -08:00
Koushik Dutta
8954de3c93 predict: beta 2024-11-10 10:16:03 -08:00
Koushik Dutta
cbfad097db predict: sanitzation 2024-11-10 10:15:12 -08:00
Koushik Dutta
c9e83c496c openvino: rollback openvino off nightly 2024-11-10 08:51:07 -08:00
Koushik Dutta
442e1883c5 openvino: quantize test 2024-11-10 08:47:51 -08:00
Koushik Dutta
f819e6d29c unifi-protect: send fingerprint events + user id as object detections 2024-11-08 13:03:33 -08:00
Koushik Dutta
261c07f330 docker: remove gst alsa 2024-11-08 10:02:22 -08:00
Koushik Dutta
2328c9dd75 docker: remove build utils 2024-11-08 09:59:22 -08:00
Koushik Dutta
15639052c3 install: update/trim intel binaries 2024-11-08 09:59:02 -08:00
Koushik Dutta
d91c7d89b2 docker: remove pillow simd deps 2024-11-08 09:43:41 -08:00
Koushik Dutta
fbdefbe06a python-codecs: remove pillow simd 2024-11-08 09:42:52 -08:00
Koushik Dutta
832ee0180c postbeta 2024-11-08 09:35:13 -08:00
Koushik Dutta
a616e95c0e detect: use opencv-headless 2024-11-08 09:14:11 -08:00
Koushik Dutta
7ab9208203 tensorflow-lite: update project files 2024-11-08 09:14:02 -08:00
Koushik Dutta
9db6808e85 coreml: bump coremltools, use opencv headless 2024-11-08 09:13:36 -08:00
Koushik Dutta
5d48760fd8 core: publish 2024-11-08 09:13:19 -08:00
Koushik Dutta
6ce2166e0a unifi-protect: Fix unstable ids on camera sensors 2024-11-08 08:24:27 -08:00
Koushik Dutta
201dc30650 onnx: fixup project files 2024-11-07 20:57:33 -08:00
Koushik Dutta
84bb7865fe unifi-protect: fingerprint sensor fixes 2024-11-07 09:23:23 -08:00
Koushik Dutta
ab1cd379a9 unifi-protect: fingerprint sensor beta 2024-11-07 09:07:00 -08:00
Koushik Dutta
9208ca9566 Merge branch 'main' of github.com:koush/scrypted 2024-11-07 08:56:59 -08:00
Koushik Dutta
e62897e14c unifi-protect: build fixes 2024-11-07 08:56:56 -08:00
Koushik Dutta
65559e6685 install: prevent amd graphics on non x86 2024-11-06 17:37:52 -08:00
Koushik Dutta
611b7c50bf docker: add intel npu, amd gpu 2024-11-06 17:28:56 -08:00
Koushik Dutta
e983526455 install: suppress tar delete error 2024-11-06 11:09:11 -08:00
Koushik Dutta
10c167d4a3 install: Update install-intel-npu.sh 2024-11-06 11:08:10 -08:00
Koushik Dutta
0c641ccf6c common: createAsyncQueueFromGenerator should not read as fast as possible. 2024-11-06 10:07:03 -08:00
Koushik Dutta
5899ad866a openvino: invalid device recovery 2024-11-05 08:38:52 -08:00
Koushik Dutta
17ecb56259 openvino: initial yuv support 2024-11-04 12:04:18 -08:00
Koushik Dutta
ffeade08ca postbeta 2024-11-04 10:32:08 -08:00
apocaliss92
49a567fb51 reolink: floodlights (#1633)
* Reolink floodlight support

* Fix on state check

* Flashlight interval removed

* Code cleanup

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-11-03 08:49:30 -08:00
Koushik Dutta
aac104f386 install: doc amdgpu-install 2024-11-01 21:46:59 -07:00
Koushik Dutta
b4aff117ce install: add kfd for amd support 2024-11-01 21:44:39 -07:00
Koushik Dutta
13d4519a35 install: amd cleanup 2024-11-01 21:42:09 -07:00
Koushik Dutta
743102c965 install: amd graphics 2024-11-01 21:41:40 -07:00
Koushik Dutta
315e5bb6e6 proxmox: add support for explicit directories in disk setup script 2024-11-01 14:08:49 -07:00
Koushik Dutta
6ddef853ad predict: publish 2024-11-01 10:52:30 -07:00
Koushik Dutta
5848cf1e5e predict: Fix nans in payloads causing plugin crash, add support for yuv models 2024-11-01 10:34:29 -07:00
Koushik Dutta
f00f650b4f docker: add libvulkan1 2024-10-31 18:22:17 -07:00
Koushik Dutta
e9d73c6faa server: do not serialize python nan in rpc protocol.
This causes protocol failure and plugin to be killed. Javascript behavior is to convert NaN to null.
Mimicing this behavior ensures stability though all JSON dicts are recursively inspected.
2024-10-31 10:44:08 -07:00
Koushik Dutta
b6d601ebc4 sdk: add boolean to SerializableType 2024-10-31 09:20:14 -07:00
Koushik Dutta
1b58b0dd9b sdk: Image ffmpegFormats flag 2024-10-31 09:18:05 -07:00
Koushik Dutta
1b5ef3103e sdk: publish 2024-10-29 12:27:02 -07:00
Koushik Dutta
78236a54b8 postrelease 2024-10-29 12:22:35 -07:00
Koushik Dutta
ec2e4d64fd rebroadcast: set online/offline state without prebuffer requirement 2024-10-29 12:21:40 -07:00
Koushik Dutta
44644448f5 postbeta 2024-10-29 12:07:01 -07:00
Koushik Dutta
0a86d5c4ea server: disable auto transferable Buffers 2024-10-29 12:06:48 -07:00
Koushik Dutta
20282e05ea Merge branch 'main' of github.com:koush/scrypted 2024-10-29 12:05:53 -07:00
Koushik Dutta
9d6b405fa9 postbeta 2024-10-29 12:05:45 -07:00
Koushik Dutta
b82ce5ff45 server: disable auto transferable Buffers 2024-10-29 12:05:33 -07:00
apocaliss92
f461198e1e reolink: battery params supported (#1621)
* Reolink battery params supported

* Style restored

* Battery check interval incresed

* Fix battery checks

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-28 18:36:17 -07:00
Jacob McSwain
7505e6907a feat(sdk): add support for notification channel in NotifierOptions (#1625)
* feat(sdk): add support for notification channel in NotifierOptions

This change spins off from conversation at https://github.com/scryptedapp/homeassistant/pull/1 and allows consumers of the `Notifier` interface to specify a channel for notifications to be sent to. Android platforms can use this to send notifications to a specific channel, allowing the user to have fine-grained control over the audio and priority of the notifications they receive.

* chore(sdk): place Android notification channel under the android object

* fix(automation-actions): remove channel from UI
2024-10-27 01:38:56 -05:00
apocaliss92
c1046d5706 HomeKit: Add flag to not autoenable devices on creation (#1626)
* Add flag to not autoenable devices on creation

* Invert naming logic

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-27 01:38:24 -05:00
Koushik Dutta
a61c06b607 remove dev site 2024-10-26 16:19:52 -05:00
Koushik Dutta
d3df5742e6 update index 2024-10-26 15:35:44 -05:00
Koushik Dutta
68ac42ca46 sdk: new dev site 2024-10-26 11:51:20 -05:00
apocaliss92
bb7c6ef8b9 reolink: Fix check for reolink nvrs (#1624)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-23 09:35:01 -05:00
apocaliss92
446e8ed61e reolink: add nvr flag (#1620)
* Reolink - Fix name/model fetching if homehub

* Checks removed

* style change removed

* Change logic for earlier returns

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-21 14:02:20 -07:00
Koushik Dutta
80372b35f2 npu: update drivers 2024-10-21 11:29:08 -07:00
Koushik Dutta
57eff2f296 Merge branch 'main' of github.com:koush/scrypted 2024-10-21 10:39:44 -07:00
Koushik Dutta
d996088041 sdk: fix deprecation on getCloudEndpoint 2024-10-21 10:39:40 -07:00
apocaliss92
04be70019b Reolink - Fix get ability (#1616)
* Reolink - Fix get ability

* getAbility current behavior kept

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-20 15:20:26 -07:00
Koushik Dutta
51732d0dcd proxmox: preserve hostname 2024-10-19 21:48:19 -07:00
Koushik Dutta
e40bc3ddee proxmox: preserve hostname 2024-10-19 21:38:07 -07:00
Koushik Dutta
3f4409e1c3 Merge branch 'main' of github.com:koush/scrypted 2024-10-19 21:26:23 -07:00
Koushik Dutta
63b7616ab3 proxmox: preserve mac address 2024-10-19 21:26:17 -07:00
apocaliss92
29059691ce Alexa: add option to not auto enable devices (#1615)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-19 16:10:42 -07:00
apocaliss92
531a9d28dc reolink: Reolink only-token client added (#1614)
* Reolink only-token client added

* Reolink auth fixes

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-19 15:06:35 -07:00
Koushik Dutta
3314b4d9ca reolink: publish 2024-10-18 20:30:40 -07:00
Koushik Dutta
37df9810c8 diagnostics: deprecate electron-core 2024-10-18 10:39:10 -07:00
Koushik Dutta
47c1cbba3c lxc-docker: shuffle volumes for faster resets 2024-10-18 09:53:52 -07:00
Koushik Dutta
ded7e549bb lxc-docker: link to docs for mount point specifics 2024-10-18 09:25:17 -07:00
Koushik Dutta
abb2b85cec lxc-docker: fix mp restore 2024-10-18 09:16:50 -07:00
Koushik Dutta
7d157d2882 lxc-docker: fix message line length 2024-10-18 09:14:01 -07:00
Koushik Dutta
c6c0a225dd lxc-docker: update messaging 2024-10-18 09:12:22 -07:00
Koushik Dutta
276fc386ec Merge branch 'main' of github.com:koush/scrypted 2024-10-18 09:08:15 -07:00
Koushik Dutta
0b21afd193 lxc-docker: handle moving volumes in migration 2024-10-18 09:08:10 -07:00
Koushik Dutta
1032e58e3b Update config.yaml 2024-10-17 21:06:52 -07:00
Koushik Dutta
4987b01167 Update package.json 2024-10-17 19:26:55 -07:00
Koushik Dutta
28bb8c5b3c proxmox: clarify docs 2024-10-17 14:15:44 -07:00
Koushik Dutta
2160170c3a proxmox: bump to lxc-docker 2024-10-17 14:06:21 -07:00
Koushik Dutta
c0eac9053b proxmox: restore warning 2024-10-17 13:58:46 -07:00
Koushik Dutta
d57501dd42 proxmox: fix restore 2024-10-17 13:53:34 -07:00
Koushik Dutta
264cb0404f proxmox: dont show existing container warning on restore 2024-10-17 13:41:40 -07:00
Koushik Dutta
dc9f4b39a8 proxmox: storage setup docs 2024-10-17 13:38:50 -07:00
Koushik Dutta
653eeceaf2 proxmox: stop container prior to restore. 2024-10-17 13:35:57 -07:00
Koushik Dutta
3d8711947a proxmox: detect force for better error handling 2024-10-17 13:33:02 -07:00
Koushik Dutta
38038d5f30 proxmox: script language cleanup 2024-10-17 13:28:59 -07:00
Koushik Dutta
e21d9c3a0c proxmox: additional installation options if container exists 2024-10-17 13:24:53 -07:00
Koushik Dutta
7b8c014b3b mac: remove libvips 2024-10-17 12:31:06 -07:00
Koushik Dutta
55a30864fd Revert "mac: update python to use whatever is latest"
This reverts commit 4f419ff75c.
2024-10-17 12:30:50 -07:00
Koushik Dutta
4f419ff75c mac: update python to use whatever is latest 2024-10-17 12:26:25 -07:00
Koushik Dutta
638a4f28ad postbeta 2024-10-17 12:09:53 -07:00
Koushik Dutta
8970154b8f lxc-docker: restore prune if pull is requested 2024-10-17 12:05:37 -07:00
Koushik Dutta
c96debaaed lxc-docker: wait 5 minutes prior to pruning 2024-10-17 12:04:46 -07:00
Koushik Dutta
fe7b479235 lxc-docker: use docker compose --pull as appropriate 2024-10-17 11:53:09 -07:00
Koushik Dutta
aa1486e641 postrelease 2024-10-17 11:15:41 -07:00
Koushik Dutta
207f00733e lxc-docker: abort on any container exit. potentially bad with watchtower updates. 2024-10-17 09:32:03 -07:00
Koushik Dutta
bc4b2c956c postbeta 2024-10-17 09:23:34 -07:00
Koushik Dutta
e15578c9ca proxmox: intel microcode 2024-10-16 20:38:12 -07:00
Koushik Dutta
e2c5ee2400 proxmox: intel microcode 2024-10-16 20:38:04 -07:00
Koushik Dutta
dec62ddbc6 proxmox: intel microcode 2024-10-16 20:37:24 -07:00
Koushik Dutta
e7a65c4a28 Merge branch 'main' of github.com:koush/scrypted 2024-10-16 13:32:58 -07:00
Koushik Dutta
127855c57e core: bump lxc intel icd 2024-10-16 13:32:53 -07:00
Koushik Dutta
d9f35ef461 proxmox: broader udev rules 2024-10-16 10:52:00 -07:00
Koushik Dutta
787c452105 sdk: improve deeplinks 2024-10-15 09:56:19 -07:00
Koushik Dutta
3556b326f5 core: update 2024-10-15 09:42:05 -07:00
Koushik Dutta
dd42878a5f Merge branch 'main' of github.com:koush/scrypted 2024-10-15 09:41:26 -07:00
Koushik Dutta
07db378763 sdk: deeplink support 2024-10-14 18:17:55 -07:00
Koushik Dutta
df142635b8 postbeta 2024-10-11 18:32:42 -07:00
Brett Jia
24bcc32532 server: aggressively catch child process pipe errors (#1609)
* server: aggressively catch python child process pipe errors

* document error behavior

* move stdio error handling to setupWorker

* handle undefined stdio
2024-10-11 15:34:44 -07:00
Koushik Dutta
5856ad60dd webrtc: fix opus crash 2024-10-10 10:48:40 -07:00
Koushik Dutta
124da3c1b7 webrtc: max compat mode should reset 2024-10-10 10:36:53 -07:00
Koushik Dutta
005efbfe82 webrtc: add flag that forces opus audio 2024-10-10 10:27:07 -07:00
Koushik Dutta
cb955f403d lxc-docker: remove nvidia container toolkit 2024-10-09 22:43:17 -07:00
Koushik Dutta
fa38d8c560 lxc-docker: ensure a unique WATCHTOWER_HTTP_API_TOKEN is used every launch 2024-10-09 17:56:37 -07:00
Koushik Dutta
0e4a94cd6e install: SCRYPTED_LXC env automatically installs more stuff 2024-10-09 17:44:54 -07:00
Koushik Dutta
a0c3721140 docker: move container toolkit script 2024-10-09 17:43:48 -07:00
Koushik Dutta
7efffe4c51 docker: default values in case env file goes missing 2024-10-09 17:28:01 -07:00
Koushik Dutta
2ff80780f8 install: fix watchtower arg 2024-10-09 16:37:26 -07:00
Koushik Dutta
9a41e3bc29 lxc-docker: force container recreate and move watchtower auto update into modifiable env. 2024-10-09 16:29:36 -07:00
Koushik Dutta
23de4011fb lxc-docker: reenable watchtower 2024-10-09 16:08:57 -07:00
Koushik Dutta
4fba5b9484 install: generate watchtower token at install time via env 2024-10-09 15:28:41 -07:00
Koushik Dutta
0dcfa367a6 Merge branch 'main' of github.com:koush/scrypted 2024-10-09 15:15:56 -07:00
Koushik Dutta
8e63943f53 install: generate watchtower token at install time via env 2024-10-09 15:15:52 -07:00
apocaliss92
e2d78611c5 reolink: preset supports
* Reolink presets supported

* Round changed with ceil to avoid sending 0

* ptzOp detached to a specific one

* Preset command moved as first

* Return added

* Unecessary ptz condition removed

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2024-10-09 14:04:21 -07:00
Koushik Dutta
8a94c268ac nvidia: simplify with new docker image 2024-10-09 13:29:53 -07:00
Koushik Dutta
10bdef414e install: auto enable nvidia gpus and capabilities 2024-10-09 13:06:54 -07:00
Koushik Dutta
317ee80477 install: nvidia container toolkit fixes 2024-10-09 12:26:43 -07:00
Koushik Dutta
851c5caf4d install: nvidia container toolkit 2024-10-09 12:16:32 -07:00
Koushik Dutta
03cfeffca5 install: nvidia container toolkit 2024-10-09 12:16:22 -07:00
Koushik Dutta
196316ad97 install: support 24.04 containers. remove cudnn8 and cuda11. 2024-10-09 11:11:51 -07:00
Koushik Dutta
f7ccdf0795 Merge branch 'main' of github.com:koush/scrypted 2024-10-09 10:39:57 -07:00
Koushik Dutta
64d36513a0 install: support 24.04 containers. remove cudnn8 and cuda11. 2024-10-09 10:39:52 -07:00
David Montgomery
6ae8fd6d96 update broken link (#1605)
* Update install-scrypted-docker-compose.sh

* Update install-scrypted-docker-compose.sh

---------

Co-authored-by: Koushik Dutta <koush@koushikdutta.com>
2024-10-08 17:34:13 -07:00
Koushik Dutta
7448f19b78 install: Update docker-compose.sh 2024-10-07 19:53:49 -07:00
Koushik Dutta
35f0e325c1 common: strict 2024-10-05 20:53:26 -07:00
Koushik Dutta
579e188d06 diagnostics: logging 2024-10-05 20:53:21 -07:00
Koushik Dutta
09e97a2895 lxc-docker: more system maintenance on startup 2024-10-05 11:56:07 -07:00
Koushik Dutta
700856bc2e reolink: use abs movement 2024-10-05 09:55:00 -07:00
Koushik Dutta
26eb69b08a reolink: publish 2024-10-05 08:56:09 -07:00
Koushik Dutta
332d9716f9 reolink: fix double ptz move 2024-10-05 08:55:35 -07:00
Koushik Dutta
511f348cbe onvif: initial ptz preset support 2024-10-04 20:16:44 -07:00
Koushik Dutta
f26b6c3bca unifi-protect: consider host when trying to find stable ids 2024-10-04 17:23:59 -07:00
Koushik Dutta
d38abcd563 core: ptz preset/home support 2024-10-04 15:41:08 -07:00
Koushik Dutta
b71f6e8b7e onvif: preset support 2024-10-04 15:23:46 -07:00
Koushik Dutta
b492e8912e onvif: continuous movement support 2024-10-04 14:57:29 -07:00
Koushik Dutta
e663f5d3fc lxc-docker: use -e 2024-10-04 13:11:51 -07:00
Koushik Dutta
21ac653c66 core: update ui 2024-10-04 12:06:44 -07:00
Koushik Dutta
e6f80be974 core: support updates for lxc docker 2024-10-04 12:03:07 -07:00
Koushik Dutta
7b41e17981 lxc: remove welcome as it messes up dpkg 2024-10-04 11:53:48 -07:00
Koushik Dutta
530961179d lxc-docker: updates 2024-10-04 11:44:53 -07:00
Koushik Dutta
7bb1d75905 proxmox: fix writing /etc/issue 2024-10-03 14:19:26 -07:00
Koushik Dutta
337a74a170 proxmox: pull the docker compose script from repo 2024-10-03 13:45:25 -07:00
Koushik Dutta
e21facb123 proxmox: add docker compose script 2024-10-03 13:42:28 -07:00
Koushik Dutta
15ac061311 proxmox: add docker compose script 2024-10-03 13:41:31 -07:00
Koushik Dutta
f66a300082 docker: cleanup old installs 2024-10-03 13:33:35 -07:00
Koushik Dutta
24d754b5dc lxc: background the update processes 2024-10-03 13:02:43 -07:00
Koushik Dutta
0221d00a17 lxc: change docker-compose.sh path 2024-10-03 12:56:43 -07:00
Koushik Dutta
3c49501c2c proxmox: lxc icon 2024-10-03 12:50:16 -07:00
Koushik Dutta
a81e5337f0 lxc: fix script creation/chmod order 2024-10-03 11:41:33 -07:00
Koushik Dutta
675339f495 lxc: fix script erorr 2024-10-03 11:36:37 -07:00
Koushik Dutta
48c6b90e21 lxc: more docker env tweaks 2024-10-03 11:35:33 -07:00
Koushik Dutta
ccf2b8c67d lxc-docker: disable autorestart 2024-10-03 10:56:05 -07:00
Koushik Dutta
cc97d7d1c1 docker: disable init core dump anyways 2024-10-03 10:39:02 -07:00
Koushik Dutta
6af2f5eecd docker: another attempt at ulimit 2024-10-03 10:27:13 -07:00
Koushik Dutta
1df4c964d7 lxc-docker: disable watchtower 2024-10-03 10:14:29 -07:00
Koushik Dutta
24b5b36a44 docker: disable core dumps in lite container 2024-10-03 09:24:58 -07:00
Koushik Dutta
1da7d25235 Merge branch 'main' of github.com:koush/scrypted 2024-10-03 09:11:39 -07:00
Koushik Dutta
d691b21646 docker: something enabled core dumps in container. disable core dumps in container. 2024-10-03 09:11:34 -07:00
Brett Jia
d691bfcef1 core: clear local url converter cache after 10m (#1595)
* core: clear local url converter cache after 10s

* update to 10m
2024-10-02 12:15:59 -07:00
Koushik Dutta
04d5633690 nvidia: icd mount no longer necessary 2024-10-02 10:05:57 -07:00
Koushik Dutta
dffd20f978 Merge branch 'main' of github.com:koush/scrypted 2024-10-02 09:39:35 -07:00
Koushik Dutta
5fd9579423 nvidia: icd notes 2024-10-02 09:39:28 -07:00
Koushik Dutta
bd7d49833f nvidia: fix container expectations 2024-10-02 09:38:10 -07:00
Brett Jia
701ca5e1e2 objectdetector: extend retention of detection inputs to 10s (#1586) 2024-10-02 09:30:11 -07:00
Koushik Dutta
14cb5a2117 install: can't fix stupid 2024-10-02 09:23:16 -07:00
Koushik Dutta
adf4e797c1 onnx/nvidia docker: bump versions, fix cuda 12 2024-10-02 09:00:14 -07:00
Koushik Dutta
a946be88d3 onnx: docs 2024-10-01 23:56:21 -07:00
Koushik Dutta
4be844f3f7 onnx: update runtime for windows 2024-10-01 23:51:34 -07:00
Koushik Dutta
9eff813619 Merge branch 'main' of github.com:koush/scrypted 2024-10-01 09:29:36 -07:00
Koushik Dutta
e1b5eaa63f google-device-access: rebuild with new sdk 2024-10-01 09:29:29 -07:00
Koushik Dutta
b7f1efc307 diagnostics: typo 2024-09-30 18:47:42 -07:00
Koushik Dutta
12fedc53ee Merge branch 'main' of github.com:koush/scrypted 2024-09-29 15:19:47 -07:00
Koushik Dutta
1c7626c156 diagnostics: fix nre in server addr check 2024-09-29 15:19:42 -07:00
Koushik Dutta
a578623d3f nvidia: more reliable opencl setup 2024-09-28 12:15:30 -07:00
Koushik Dutta
b13d32f3ed amcrest: publish 2024-09-28 10:46:04 -07:00
Koushik Dutta
d34c3aae74 amcrest: dont fail if motion detect reset fails 2024-09-28 10:45:03 -07:00
Koushik Dutta
c27176f0f8 ring: more push updates 2024-09-26 10:07:05 -07:00
Koushik Dutta
be4f4bb6d6 install: update intel drivers 2024-09-25 21:03:52 -07:00
Koushik Dutta
26db89b811 cloud: observe connection termination 2024-09-25 20:19:44 -07:00
Koushik Dutta
6f0738ef07 diagnostics: gpu transform test 2024-09-25 13:06:34 -07:00
Koushik Dutta
1ae1eafddc diagnostics: add detection test 2024-09-25 12:27:17 -07:00
Koushik Dutta
0ecacfd974 openvino: update openvino to 2014.4.0 2024-09-25 08:56:14 -07:00
Koushik Dutta
146a648f39 snapshot: export createVipsMediaObject 2024-09-25 08:55:40 -07:00
Koushik Dutta
7015f26eee ring: heartbeat 2024-09-24 11:42:15 -07:00
Brett Jia
a4e484698d server: implement python listen + listenDevice (#1587)
* server: implement python listen + listenDevice

* fix unregister

* make functions synchronous
2024-09-23 09:34:10 -07:00
Koushik Dutta
1d659a5fb9 onvif: update onvif to support https, publish 2024-09-22 12:01:56 -07:00
Koushik Dutta
80e70b6bb8 ring: update push receiver 2024-09-22 11:28:55 -07:00
Koushik Dutta
ceec1174e3 cloud: expose server id header 2024-09-20 15:52:12 -07:00
Koushik Dutta
b658202d8a cloud: include server id in responses 2024-09-20 15:45:34 -07:00
Koushik Dutta
425ee41ab0 core: publish 2024-09-20 15:06:11 -07:00
Koushik Dutta
3f7b801ffb cloud: server id reporting 2024-09-20 15:06:07 -07:00
Koushik Dutta
ccb7ae0323 core: publish 2024-09-20 10:06:50 -07:00
Koushik Dutta
252c90fb46 cloud: remove reliance on google for login process 2024-09-19 23:11:04 -07:00
Koushik Dutta
2beceaf869 videoanalysis: provide audio 2024-09-19 20:43:25 -07:00
Koushik Dutta
1dbb9a0f63 Merge branch 'main' of github.com:koush/scrypted 2024-09-19 12:03:03 -07:00
Koushik Dutta
85c35f5fb7 amcrest: autoconfigure removes amcrest logo 2024-09-19 12:02:59 -07:00
Koushik Dutta
8966cd7c70 Update install-scrypted-proxmox.sh 2024-09-19 07:22:44 -07:00
Koushik Dutta
60aeae5336 diagnostics: codec check 2024-09-18 16:14:16 -07:00
Koushik Dutta
eef6c3ed3f cloud: use multiple signaling paths 2024-09-18 10:08:57 -07:00
Koushik Dutta
979c430303 cloud: remove dead code 2024-09-18 08:47:51 -07:00
Koushik Dutta
4892b72f37 cloud: use cloudflare as secondary signaling path 2024-09-17 09:19:14 -07:00
Koushik Dutta
3fd81b86d7 webrtc: wait before terminating thread 2024-09-15 21:51:03 -07:00
Koushik Dutta
b3a2789a1d diagnostics: cloud warn 2024-09-15 21:50:33 -07:00
Koushik Dutta
fde5cfa51e webrtc: remove webrtc api transport 2024-09-15 15:04:14 -07:00
Koushik Dutta
1ada7bb3fe common: rtp forwarder ffmpeg path 2024-09-15 13:26:25 -07:00
Koushik Dutta
2445ea909d proxmox: good grief people cant read 2024-09-15 13:26:15 -07:00
Koushik Dutta
7b16e8e969 install: make sure install is performed on proxmox host 2024-09-15 10:33:38 -07:00
Koushik Dutta
1ffe8357e3 diagnostics: print addresses 2024-09-14 12:55:55 -07:00
Koushik Dutta
c96b4730de Merge branch 'main' of github.com:koush/scrypted 2024-09-14 12:40:14 -07:00
Koushik Dutta
d89a8c6072 diagnostics: indicator for test hang/running 2024-09-14 12:40:09 -07:00
Koushik Dutta
8e47bbb153 proxmox: disk script logging 2024-09-14 10:05:38 -07:00
Koushik Dutta
b04bb414d6 proxmox: better disk granularity 2024-09-13 22:31:43 -07:00
Koushik Dutta
bd947fb934 proxmox: add disk 2024-09-13 22:30:25 -07:00
Koushik Dutta
a643960863 proxmox: disk type support 2024-09-13 22:28:14 -07:00
Koushik Dutta
83f869988d proxmox: script formatting 2024-09-13 22:23:40 -07:00
Koushik Dutta
1fc0934b4e proxmox: script formatting 2024-09-13 22:20:13 -07:00
Koushik Dutta
29b7e4151a proxmox: script formatting 2024-09-13 22:11:24 -07:00
Koushik Dutta
ad3c4f9529 proxmox: script formatting 2024-09-13 22:06:07 -07:00
Koushik Dutta
e9026372c1 Merge branch 'main' of github.com:koush/scrypted 2024-09-13 21:50:27 -07:00
Koushik Dutta
9b30064896 proxmox: dir script 2024-09-13 18:13:19 -07:00
Koushik Dutta
c73ffb30c1 proxmox: docs 2024-09-13 18:03:27 -07:00
Koushik Dutta
bae12eecae proxmox: dir sanity check 2024-09-13 18:00:52 -07:00
Koushik Dutta
1440b3811d proxmox: scripts 2024-09-13 17:58:49 -07:00
Koushik Dutta
59a3a97577 diagnostics: words 2024-09-13 10:54:03 -07:00
Koushik Dutta
07f02fe371 postbeta 2024-09-13 10:53:36 -07:00
Koushik Dutta
cf4f4b7c73 diagnostics: more checks 2024-09-12 20:36:14 -07:00
Koushik Dutta
aab0bdfc41 cloud: additional cloudflare failure handling 2024-09-12 18:47:11 -07:00
Koushik Dutta
438f61b729 lxc: mirror docker compose 2024-09-12 18:46:20 -07:00
Koushik Dutta
0b3717439f lxc: relax chown 2024-09-12 13:51:22 -07:00
Koushik Dutta
a9a145bd3e lxc: relax SCRYPTED_NONINTERACTIVE check 2024-09-12 13:29:53 -07:00
Koushik Dutta
2f97b39cbb lxc: service user is root 2024-09-12 13:28:42 -07:00
Koushik Dutta
8bc1fe1588 lxc: apparmor removal should be allowed to fail 2024-09-12 13:27:59 -07:00
Koushik Dutta
90740f3c9d lxc: remove apparmor 2024-09-12 13:25:41 -07:00
Koushik Dutta
f92a12b99c Merge branch 'main' of github.com:koush/scrypted 2024-09-12 13:23:09 -07:00
Koushik Dutta
405453da03 install: lxc docker prototype 2024-09-12 13:23:04 -07:00
Brett Jia
d01fe4310b server: python createMediaManager func (#1574)
* server: python createMediaManager func

* use api's media manager directly
2024-09-12 13:08:03 -07:00
Koushik Dutta
6bcab63e0e install: lxc docker prototype 2024-09-12 13:01:16 -07:00
Koushik Dutta
c2bf7a62ab install: lxc docker prototype 2024-09-12 12:51:51 -07:00
Koushik Dutta
bc631d6c8b diagnostics: recognize ha 2024-09-12 12:02:10 -07:00
Koushik Dutta
66877d1efa core: i am really bad at this 2024-09-12 11:13:01 -07:00
Koushik Dutta
96a4a11503 core: fix level-zero install check 2024-09-12 11:04:01 -07:00
Koushik Dutta
bd0393305b core: trigger restart after level zero removal 2024-09-12 10:51:26 -07:00
Koushik Dutta
9033eadafb docker: remove npu 2024-09-12 10:50:29 -07:00
Koushik Dutta
c42b8ecda2 openvino/lxc: fix crashing 2024-09-12 10:49:18 -07:00
Koushik Dutta
521bb62f10 openvino: better gpu failure fallback 2024-09-11 16:55:20 -07:00
Koushik Dutta
8819f0a249 install: update intel firmware 2024-09-11 16:54:54 -07:00
Koushik Dutta
341cfa1e2f diagnostics: gpu decode test 2024-09-11 12:53:01 -07:00
Koushik Dutta
96335544ad openvino: openvino 2024.2.0 is crashy 2024-09-11 12:52:38 -07:00
Koushik Dutta
ce0adb5706 diagnostics: skip dupes 2024-09-11 10:44:58 -07:00
Koushik Dutta
22fb257214 core: update ui 2024-09-11 10:28:44 -07:00
Koushik Dutta
674034fb7e openvino: GPU to AUTO fallback on error 2024-09-11 10:13:45 -07:00
Koushik Dutta
44dcfe5e12 openvino: rollback 2024-09-11 10:08:30 -07:00
Koushik Dutta
194e8532c3 core: publish 2024-09-11 09:45:40 -07:00
Koushik Dutta
797ef79080 unifi-protect: filter out unadopted cams 2024-09-11 09:28:10 -07:00
Koushik Dutta
56dbecbf3d videoanalsyis: dont allow extension to be disabled. 2024-09-11 08:57:27 -07:00
Koushik Dutta
67acb7725f openvino: fix model execution 2024-09-10 19:56:42 -07:00
Koushik Dutta
a2caad7109 diagnostics: idr check 2024-09-10 17:43:16 -07:00
Koushik Dutta
599d370f7d diagnostics: unifi protect junk snapshots 2024-09-10 15:23:56 -07:00
Koushik Dutta
be284a0df7 diagnostics: (jsonip.com) 2024-09-10 15:17:02 -07:00
Koushik Dutta
030c3432a7 diagnostics: notifier 2024-09-10 15:16:34 -07:00
Koushik Dutta
7d0d26ad31 diagnostics: install validation 2024-09-10 15:04:23 -07:00
Koushik Dutta
6ba8a1dea4 diagnostics: publish 2024-09-10 14:37:40 -07:00
Koushik Dutta
cafb6944f5 diagnostics: stream validation 2024-09-10 14:34:03 -07:00
Koushik Dutta
f800401317 diagnostics: initial commit 2024-09-10 14:17:19 -07:00
Koushik Dutta
d672e2271d sdk: update 2024-09-10 13:21:54 -07:00
Koushik Dutta
3d558e3119 postbeta 2024-09-09 12:50:33 -07:00
Brett Jia
c94945c6d5 server: allow plugins to specify portable python build tags (#1571) 2024-09-09 12:26:01 -07:00
Koushik Dutta
82afc6d53f ha: update 2024-09-08 16:43:50 -07:00
Koushik Dutta
16ba10da21 videoanalysis: deprecate electron core 2024-09-08 16:43:37 -07:00
Koushik Dutta
6f5e0700a2 postrelease 2024-09-08 16:41:04 -07:00
Koushik Dutta
11b81371b4 postrelease 2024-09-08 16:40:29 -07:00
283 changed files with 14741 additions and 14063 deletions

View File

@@ -11,7 +11,7 @@ jobs:
NODE_VERSION: '20'
strategy:
matrix:
BASE: ["jammy"]
BASE: ["noble"]
FLAVOR: ["full", "lite"]
steps:
- name: Check out the repo
@@ -83,7 +83,7 @@ jobs:
runs-on: self-hosted
strategy:
matrix:
BASE: ["jammy"]
BASE: ["noble"]
steps:
- name: Check out the repo
uses: actions/checkout@v3

View File

@@ -20,9 +20,9 @@ jobs:
strategy:
matrix:
BASE: [
["jammy-nvidia", ".s6"],
["jammy-full", ".s6"],
["jammy-lite", ""],
["noble-nvidia", ".s6"],
["noble-full", ".s6"],
["noble-lite", ""],
]
steps:
- name: Check out the repo
@@ -95,15 +95,15 @@ jobs:
push: true
tags: |
${{ format('koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
${{ matrix.BASE[0] == 'jammy-full' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-nvidia' && 'koush/scrypted:nvidia' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-full' && 'koush/scrypted:full' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-lite' && 'koush/scrypted:lite' || '' }}
${{ matrix.BASE[0] == 'noble-full' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-nvidia' && 'koush/scrypted:nvidia' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-full' && 'koush/scrypted:full' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-lite' && 'koush/scrypted:lite' || '' }}
${{ format('ghcr.io/koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
${{ matrix.BASE[0] == 'jammy-full' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-nvidia' && 'ghcr.io/koush/scrypted:nvidia' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-full' && 'ghcr.io/koush/scrypted:full' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-lite' && 'ghcr.io/koush/scrypted:lite' || '' }}
${{ matrix.BASE[0] == 'noble-full' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-nvidia' && 'ghcr.io/koush/scrypted:nvidia' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-full' && 'ghcr.io/koush/scrypted:full' || '' }}
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'noble-lite' && 'ghcr.io/koush/scrypted:lite' || '' }}
cache-from: type=gha
cache-to: type=gha,mode=max

6
.gitmodules vendored
View File

@@ -11,9 +11,6 @@
[submodule "external/werift"]
path = external/werift
url = ../../koush/werift-webrtc
[submodule "sdk/developer.scrypted.app"]
path = sdk/developer.scrypted.app
url = ../../koush/developer.scrypted.app
[submodule "plugins/sample-cameraprovider"]
path = plugins/sample-cameraprovider
url = ../../koush/scrypted-sample-cameraprovider
@@ -23,6 +20,3 @@
[submodule "plugins/wyze/docker-wyze-bridge"]
path = plugins/wyze/docker-wyze-bridge
url = ../../koush/docker-wyze-bridge.git
[submodule "plugins/onvif/onvif"]
path = plugins/onvif/onvif
url = ../../koush/onvif.git

View File

@@ -40,7 +40,7 @@ export function createAsyncQueue<T>() {
return false;
if (waiting.length) {
const deferred = waiting.shift();
const deferred = waiting.shift()!;
dequeued?.resolve();
deferred.resolve(item);
return true;
@@ -66,7 +66,7 @@ export function createAsyncQueue<T>() {
dequeued?.reject(new Error('abort'));
};
dequeued.promise.catch(() => {}).finally(() => signal.removeEventListener('abort', h));
dequeued?.promise.catch(() => {}).finally(() => signal.removeEventListener('abort', h));
signal.addEventListener('abort', h);
return true;
@@ -79,7 +79,7 @@ export function createAsyncQueue<T>() {
ended = e || new EndError();
endDeferred.resolve();
while (waiting.length) {
waiting.shift().reject(ended);
waiting.shift()!.reject(ended);
}
return true;
}
@@ -160,7 +160,7 @@ export function createAsyncQueueFromGenerator<T>(generator: AsyncGenerator<T>) {
(async() => {
try {
for await (const i of generator) {
q.submit(i);
await q.enqueue(i);
}
}
catch (e) {

View File

@@ -41,11 +41,15 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
return true;
}
checkHasEnabledMixin(device: ScryptedDevice) {
return this.hasEnabledMixin[device.id] === this.autoIncludeToken;
}
async maybeEnableMixin(device: ScryptedDevice) {
if (!device || device.mixins?.includes(this.id))
return;
if (this.hasEnabledMixin[device.id] === this.autoIncludeToken)
if (this.checkHasEnabledMixin(device))
return;
const match = await this.canMixin(device.type, device.interfaces);

View File

@@ -1,4 +1,4 @@
import sdk, { MixinDeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes } from "@scrypted/sdk";
import sdk, { LockState, MixinDeviceBase, PanTiltZoomMovement, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes } from "@scrypted/sdk";
import { SettingsMixinDeviceBase } from "@scrypted/sdk/settings-mixin";
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import fs from 'fs';
@@ -63,7 +63,6 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
const allParams = Object.assign({}, params, {
sdk,
fs: require('realfs'),
ScryptedDeviceBase,
MixinDeviceBase,
StorageSettings,
@@ -76,6 +75,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
localStorage: device.storage,
device,
exports: {} as any,
PanTiltZoomMovement,
SettingsMixinDeviceBase,
ScryptedMimeTypes,
ScryptedInterface,

View File

@@ -19,7 +19,7 @@ function isPi(model: string) {
export function isRaspberryPi() {
let cpuInfo: string;
try {
cpuInfo = require('realfs').readFileSync('/proc/cpuinfo', { encoding: 'utf8' });
cpuInfo = require('fs').readFileSync('/proc/cpuinfo', { encoding: 'utf8' });
}
catch (e) {
// if this fails, this is probably not a pi

View File

@@ -4,6 +4,41 @@ import os from 'os';
export type Zygote<T> = () => PluginFork<T>;
export function createService<T, V>(options: ForkOptions, create: (t: Promise<T>) => Promise<V>): {
getResult: () => Promise<V>,
terminate: () => void,
} {
let killed = false;
let currentResult: Promise<V>;
let currentFork: ReturnType<typeof sdk.fork<T>>;
return {
getResult() {
if (killed)
throw new Error('service terminated');
if (currentResult)
return currentResult;
currentFork = sdk.fork<T>(options);
currentFork.worker.on('exit', () => currentResult = undefined);
currentResult = create(currentFork.result);
currentResult.catch(() => currentResult = undefined);
return currentResult;
},
terminate() {
if (killed)
return;
killed = true;
currentFork.worker.terminate();
currentFork = undefined;
currentResult = undefined;
}
}
}
export function createZygote<T>(options?: ForkOptions): Zygote<T> {
let zygote = sdk.fork<T>(options);
function* next() {

View File

@@ -1,6 +1,6 @@
# Home Assistant Addon Configuration
name: Scrypted
version: "v0.116.0-jammy-full"
version: "v0.120.0-jammy-full"
slug: scrypted
description: Scrypted is a high performance home video integration and automation platform
url: "https://github.com/koush/scrypted"

View File

@@ -18,4 +18,4 @@ ENV NODE_OPTIONS="--dns-result-order=ipv4first"
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20240321"
CMD npm --prefix /server exec scrypted-serve
CMD ["/bin/sh", "-c", "ulimit -c 0; exec npm --prefix /server exec scrypted-serve"]

View File

@@ -14,12 +14,7 @@ ENV DEBIAN_FRONTEND=noninteractive
# base tools and development stuff
RUN apt-get update && apt-get -y install \
curl software-properties-common apt-utils \
build-essential \
cmake \
ffmpeg \
gcc \
libcairo2-dev \
libgirepository1.0-dev \
pkg-config && \
apt-get -y update && \
apt-get -y upgrade
@@ -40,16 +35,12 @@ RUN apt-get -y install \
python3-setuptools \
python3-wheel
# these are necessary for pillow-simd, additional on disk size is small
# but could consider removing this.
RUN echo "Installing pillow-simd dependencies."
RUN apt-get -y install \
libjpeg-dev zlib1g-dev
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
RUN echo "Installing gstreamer."
# python-codecs pygobject dependencies
RUN apt-get -y install libcairo2-dev libgirepository1.0-dev
RUN apt-get -y install \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav \
gstreamer1.0-vaapi
# python3 gstreamer bindings
@@ -60,8 +51,9 @@ RUN apt-get -y install \
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install debugpy typing_extensions psutil
# ERROR: Cannot uninstall pip 24.0, RECORD file not found. Hint: The package was installed by debian.
# RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install debugpy
################################################################
# End section generated from template/Dockerfile.full.header
@@ -71,10 +63,18 @@ RUN python3 -m pip install debugpy typing_extensions psutil
################################################################
FROM header as base
# intel opencl gpu and npu for openvino
# vulkan
RUN apt -y install libvulkan1
# intel opencl for openvino
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# NPU driver will SIGILL on openvino prior to 2024.5.0
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# amd opencl
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-amd-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
@@ -86,8 +86,8 @@ RUN add-apt-repository -y ppa:deadsnakes/ppa && \
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
RUN python3.9 -m pip install --upgrade pip
RUN python3.9 -m pip install debugpy typing_extensions psutil
# RUN python3.9 -m pip install --upgrade pip
RUN python3.9 -m pip install debugpy
# Coral Edge TPU
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
@@ -95,16 +95,20 @@ RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" |
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
RUN apt-get -y update && apt-get -y install libedgetpu1-std
# set default shell to bash
RUN chsh -s /bin/bash
ENV SHELL="/bin/bash"
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
ENV SCRYPTED_CAN_RESTART="true"
ENV SCRYPTED_VOLUME="/server/volume"
ENV SCRYPTED_INSTALL_PATH="/server"
RUN test -f "/usr/bin/ffmpeg" && test -f "/usr/bin/python3" && test -f "/usr/bin/python3.9" && test -f "/usr/bin/python3.10"
RUN test -f "/usr/bin/ffmpeg" && test -f "/usr/bin/python3" && test -f "/usr/bin/python3.9" && test -f "/usr/bin/python3.12"
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
ENV SCRYPTED_PYTHON_PATH="/usr/bin/python3"
ENV SCRYPTED_PYTHON39_PATH="/usr/bin/python3.9"
ENV SCRYPTED_PYTHON310_PATH="/usr/bin/python3.10"
ENV SCRYPTED_PYTHON312_PATH="/usr/bin/python3.12"
ENV SCRYPTED_DOCKER_FLAVOR="full"

View File

@@ -22,8 +22,8 @@ ENV SCRYPTED_CAN_RESTART="true"
ENV SCRYPTED_VOLUME="/server/volume"
ENV SCRYPTED_INSTALL_PATH="/server"
RUN test -f "/usr/bin/python3" && test -f "/usr/bin/python3.10"
RUN test -f "/usr/bin/python3" && test -f "/usr/bin/python3.12"
ENV SCRYPTED_PYTHON_PATH="/usr/bin/python3"
ENV SCRYPTED_PYTHON310_PATH="/usr/bin/python3.10"
ENV SCRYPTED_PYTHON312_PATH="/usr/bin/python3.12"
ENV SCRYPTED_DOCKER_FLAVOR="lite"

View File

@@ -1,6 +1,9 @@
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
FROM $BASE
ENV NVIDIA_DRIVER_CAPABILITIES=all
ENV NVIDIA_VISIBLE_DEVICES=all
# nvidia cudnn/libcublas etc.
# for some reason this is not provided by the nvidia container toolkit
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-graphics.sh | bash

View File

@@ -48,4 +48,4 @@ ENV NODE_OPTIONS="--dns-result-order=ipv4first"
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20240321"
CMD npm --prefix /server exec scrypted-serve
CMD ["/bin/sh", "-c", "ulimit -c 0; exec npm --prefix /server exec scrypted-serve"]

View File

@@ -19,6 +19,9 @@
services:
scrypted:
# LXC usage only
# lxc privileged: true
environment:
# Scrypted NVR Storage (Part 2 of 3)
@@ -29,30 +32,26 @@ services:
# section below.
# - SCRYPTED_NVR_VOLUME=/nvr
- SCRYPTED_WEBHOOK_UPDATE_AUTHORIZATION=Bearer SET_THIS_TO_SOME_RANDOM_TEXT
- SCRYPTED_WEBHOOK_UPDATE_AUTHORIZATION=Bearer ${WATCHTOWER_HTTP_API_TOKEN:-env_missing_fallback}
- SCRYPTED_WEBHOOK_UPDATE=http://localhost:10444/v1/update
# LXC usage only
# lxc - SCRYPTED_INSTALL_ENVIRONMENT=lxc-docker
# Avahi can be used for network discovery by passing in the host daemon
# or running the daemon inside the container. Choose one or the other.
# Uncomment next line to run avahi-daemon inside the container.
# See volumes and security_opt section below to use the host daemon.
# - SCRYPTED_DOCKER_AVAHI=true
# NVIDIA (Part 1 of 4)
# - NVIDIA_VISIBLE_DEVICES=all
# - NVIDIA_DRIVER_CAPABILITIES=all
# NVIDIA (Part 2 of 4)
# NVIDIA (Part 1 of 2)
# runtime: nvidia
# NVIDIA (Part 3 of 4) - Use NVIDIA image, and remove subsequent default image.
# NVIDIA (Part 2 of 2) - Use NVIDIA image, and remove subsequent default image.
# image: ghcr.io/koush/scrypted:nvidia
image: ghcr.io/koush/scrypted
volumes:
# NVIDIA (Part 4 of 4)
# - /etc/OpenCL/vendors/nvidia.icd:/etc/OpenCL/vendors/nvidia.icd
# Scrypted NVR Storage (Part 3 of 3)
# Modify to add the additional volume for Scrypted NVR.
@@ -76,7 +75,16 @@ services:
# - /var/run/avahi-daemon/socket:/var/run/avahi-daemon/socket
# Default volume for the Scrypted database. Typically should not be changed.
- ~/.scrypted/volume:/server/volume
# The volume will be placed relative to this docker-compose.yml.
- ./volume:/server/volume
# LXC usage only
# lxc - /var/run/docker.sock:/var/run/docker.sock
# lxc - /root/.scrypted/docker-compose.yml:/root/.scrypted/docker-compose.yml
# lxc - /root/.scrypted/docker-compose.sh:/root/.scrypted/docker-compose.sh
# lxc - /root/.scrypted/.env:/root/.scrypted/.env
# lxc - /mnt:/mnt
# Uncomment the following lines to use Avahi daemon from the host
# Without this, AppArmor will block the container's attempt to talk to Avahi via dbus
# security_opt:
@@ -91,6 +99,9 @@ services:
# hardware accelerated video decoding, opencl, etc.
# "/dev/dri:/dev/dri",
# AMD GPU
# "/dev/kfd:/dev/kfd",
# uncomment below as necessary.
# zwave usb serial device
@@ -120,12 +131,10 @@ services:
# watchtower manages updates for Scrypted.
watchtower:
environment:
- WATCHTOWER_HTTP_API_TOKEN=SET_THIS_TO_SOME_RANDOM_TEXT
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_HTTP_API_TOKEN:-env_missing_fallback}
- WATCHTOWER_HTTP_API_UPDATE=true
- WATCHTOWER_SCOPE=scrypted
# remove the following line to never allow docker to auto update.
# this is not recommended.
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=true
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=${WATCHTOWER_HTTP_API_PERIODIC_POLLS:-true}
image: containrrr/watchtower
container_name: scrypted-watchtower
restart: unless-stopped

View File

@@ -1,5 +1,9 @@
#!/bin/bash
# disable core dumps.
# this doesn't disable core dumps on the scrypted service itself, only stuff run by init.
ulimit -c 0
if [[ "${SCRYPTED_DOCKER_AVAHI}" != "true" ]]; then
echo "SCRYPTED_DOCKER_AVAHI != true, won't manage dbus nor avahi-daemon" >/dev/stderr
exit 0

View File

@@ -0,0 +1,42 @@
if [ "$(uname -m)" != "x86_64" ]
then
echo "AMD graphics will not be installed on this architecture."
exit 0
fi
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
# needs either ubuntu 22.0.4 or 24.04
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
echo "AMD graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
exit 1
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="jammy"
else
distro="noble"
fi
# https://amdgpu-install.readthedocs.io/en/latest/install-prereq.html#installing-the-installer-package
FILENAME=$(curl -s -L https://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/ | grep -o 'amdgpu-install_[^ ]*' | cut -d'"' -f1)
if [ -z "$FILENAME" ]
then
echo "AMD graphics package can not be installed. Could not find the package name."
exit 1
fi
set -e
mkdir -p /tmp/amd
cd /tmp/amd
curl -O -L http://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/$FILENAME
apt -y install rsync
dpkg -i $FILENAME
amdgpu-install --usecase=opencl --no-dkms -y --accept-eula
cd /tmp
rm -rf /tmp/amd

View File

@@ -29,23 +29,50 @@ apt-get -y update &&
apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
# manual installation
# https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
rm -rf /tmp/gpu && mkdir -p /tmp/gpu && cd /tmp/gpu
apt-get install -y ocl-icd-libopencl1
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-core_1.0.17193.4_amd64.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-opencl_1.0.17193.4_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-level-zero-gpu-dbgsym_1.3.30049.6_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-level-zero-gpu_1.3.30049.6_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd-dbgsym_24.26.30049.6_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd_24.26.30049.6_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/libigdgmm12_22.3.20_amd64.deb
# very stupid legacy + current install process conflict.
# install 24.35.30872.22 for legacy support. Then install latest.
# https://github.com/intel/compute-runtime/issues/770#issuecomment-2515166915
# https://github.com/intel/compute-runtime/releases/tag/24.35.30872.22
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-core_1.0.17537.20_amd64.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-opencl_1.0.17537.20_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-dbgsym_1.3.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1-dbgsym_1.3.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1_1.3.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu_1.3.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-dbgsym_24.35.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1-dbgsym_24.35.30872.22_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1_24.35.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd_24.35.30872.22_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/libigdgmm12_22.5.0_amd64.deb
dpkg -i *.deb
rm -f *.deb
# https://github.com/intel/compute-runtime/releases/tag/24.45.31740.9
# note that at time of commit, IGC supports ubuntu 24.04 only possibly due to their builder being on 24.04.
IGC_VERSION=2_2.1.12+18087_amd64
COMPUTE_VERSION=24.45.31740.9
ZERO_GPU_VERSION=1.6.31740.9_amd64
LIBIGDGMM_VERSION=22.5.2_amd64
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.1.12/intel-igc-core-$IGC_VERSION.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.1.12/intel-igc-opencl-$IGC_VERSION.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu-dbgsym_$ZERO_GPU_VERSION.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu_$ZERO_GPU_VERSION.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-opencl-icd-dbgsym_"$COMPUTE_VERSION"_amd64.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-opencl-icd_"$COMPUTE_VERSION"_amd64.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/libigdgmm12_$LIBIGDGMM_VERSION.deb
set +e
dpkg -i *.deb
set -e
# the legacy + latest process says this may be necessary but it does not seem to be in a clean environment.
apt-get install --fix-broken
cd /tmp && rm -rf /tmp/gpu

View File

@@ -7,7 +7,7 @@ fi
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
if [ -z "$UBUNTU_22_04" ]
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
# proxmox is compatible with ubuntu 22.04, check for /etc/pve directory
if [ -d "/etc/pve" ]
@@ -23,6 +23,13 @@ then
exit 0
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="22.04_amd64"
else
distro="24.04_amd64"
fi
dpkg --purge --force-remove-reinstreq intel-driver-compiler-npu intel-fw-npu intel-level-zero-npu
# no errors beyond this point
@@ -30,27 +37,23 @@ set -e
rm -rf /tmp/npu && mkdir -p /tmp/npu && cd /tmp/npu
# different npu downloads for ubuntu versions
if [ -n "$UBUNTU_22_04" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-driver-compiler-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
# firmware can only be installed on host. will cause problems inside container.
if [ -n "$INTEL_FW_NPU" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
fi
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
else
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-driver-compiler-npu_1.5.1.20240708-9842236399_ubuntu24.04_amd64.deb
if [ -n "$INTEL_FW_NPU" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
fi
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
fi
# level zero must also be installed
LEVEL_ZERO_VERSION=1.19.2
# https://github.com/oneapi-src/level-zero
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero_"$LEVEL_ZERO_VERSION"+u$distro.deb
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero-devel_"$LEVEL_ZERO_VERSION"+u$distro.deb
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero_1.17.6+u22.04_amd64.deb
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero-devel_1.17.6+u22.04_amd64.deb
# npu driver
# https://github.com/intel/linux-npu-driver
NPU_VERSION=1.10.0
NPU_VERSION_DATE=20241107-11729849322
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-driver-compiler-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
# firmware can only be installed on host. will cause problems inside container.
if [ -n "$INTEL_FW_NPU" ]
then
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-fw-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
fi
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-level-zero-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
apt -y update
apt -y install libtbb12

View File

@@ -0,0 +1,44 @@
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
set -e
# Install CUDA for 22.04
# https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=24.04&target_type=deb_network
# Install CUDA for 24.04
# https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=24.04&target_type=deb_network
# Do not apt install nvidia-open, must use cuda-drivers.
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
echo "NVIDIA container toolkit can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
exit 1
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="ubuntu2204"
else
distro="ubuntu2404"
fi
apt update -q \
&& apt install -y wget \
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
&& dpkg -i /cuda-keyring.deb;
# https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
apt -y update
apt -y install gpg
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --yes --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
apt -y update
# is there a way to get a versioned package automatically?
apt -y install nvidia-utils-560
apt -y install nvidia-container-toolkit
nvidia-ctk runtime configure --runtime=docker
nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place
systemctl restart docker

View File

@@ -1,14 +1,52 @@
if [ "$(uname -m)" = "x86_64" ]
then
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
# needs either ubuntu 22.0.4 or 24.04
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
echo "NVIDIA graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
exit 1
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="ubuntu2204"
else
distro="ubuntu2404"
fi
echo "Installing NVIDIA graphics packages."
apt update -q \
&& apt install -y wget \
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/$(uname -m)/cuda-keyring_1.1-1_all.deb \
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
&& dpkg -i /cuda-keyring.deb \
&& apt update -q \
&& apt install -y cuda-nvcc-11-8 libcublas-11-8 libcudnn8 cuda-libraries-11-8 \
&& apt install -y cuda-nvcc-12-4 libcublas-12-4 libcudnn8 cuda-libraries-12-4;
exit $?
&& apt install -y cuda-nvcc-12-6 libcublas-12-6 libcudnn9-cuda-12 cuda-libraries-12-6;
if [ "$?" != "0" ]
then
echo "Error: NVIDIA graphics packages failed to install."
exit 1
fi
# Update: the libnvidia-opencl.so.1 file is not present in the container image, it is
# mounted via the nvidia container runtime. This is why the following check is commented out.
# this file is present but for some reason the icd file is not created by nvidia runtime.
# if [ ! -f "/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.1" ]
# then
# echo "Error: NVIDIA OpenCL library not found."
# exit 1
# fi
# the container runtime doesn't mount this file for some reason. seems to be a bug.
# https://github.com/NVIDIA/nvidia-container-toolkit/issues/682
# but the contents are simply the .so file, which is a symlink the nvidia runtime
# will mount in.
mkdir -p /etc/OpenCL/vendors/
echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd
else
echo "NVIDIA graphics will not be installed on this architecture."
fi

View File

@@ -1,5 +1,11 @@
#!/usr/bin/env bash
if [ "$SCRYPTED_LXC" ]
then
export SERVICE_USER="root"
export SCRYPTED_NONINTERACTIVE="true"
fi
if [ -z "$SERVICE_USER" ]
then
echo "Scrypted SERVICE_USER environment variable was not specified. Service will not be installed."
@@ -7,6 +13,12 @@ then
fi
function readyn() {
if [ ! -z "$SCRYPTED_NONINTERACTIVE" ]
then
yn="y"
return
fi
while true; do
read -p "$1 (y/n) " yn
case $yn in
@@ -33,6 +45,11 @@ systemctl disable scrypted.service 2> /dev/null
USER_HOME=$(eval echo ~$SERVICE_USER)
SCRYPTED_HOME=$USER_HOME/.scrypted
mkdir -p $SCRYPTED_HOME
# remove various things from a previous local install.
rm -rf $SCRYPTED_HOME/node_modules
rm -rf $SCRYPTED_HOME/install.json
rm -rf $SCRYPTED_HOME/package.json
rm -rf $SCRYPTED_HOME/package-lock.json
set -e
cd $SCRYPTED_HOME
@@ -46,13 +63,34 @@ then
usermod -aG docker $SERVICE_USER
fi
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum)
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32)
echo "WATCHTOWER_HTTP_API_TOKEN=$WATCHTOWER_HTTP_API_TOKEN" > $SCRYPTED_HOME/.env
# remove the following line from .env to disable autoupdates.
# this is not recommended.
echo "WATCHTOWER_HTTP_API_PERIODIC_POLLS=true" >> $SCRYPTED_HOME/.env
DOCKER_COMPOSE_YML=$SCRYPTED_HOME/docker-compose.yml
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml > $DOCKER_COMPOSE_YML
echo "Created $DOCKER_COMPOSE_YML"
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml | sed s/SET_THIS_TO_SOME_RANDOM_TEXT/"$(echo $RANDOM | md5sum | head -c 32)"/g > $DOCKER_COMPOSE_YML
if [ -d /dev/dri ]
if [ -z "$SCRYPTED_LXC" ]
then
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/g' $DOCKER_COMPOSE_YML
if [ -e /dev/dri ]
then
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/g' $DOCKER_COMPOSE_YML
fi
if [ -e /dev/kfd ]
then
sed -i 's/'#' "\/dev\/kfd/"\/dev\/kfd/g' $DOCKER_COMPOSE_YML
fi
else
# uncomment lxc specific stuff
sed -i 's/'#' lxc //g' $DOCKER_COMPOSE_YML
# never restart, systemd will handle it
sed -i 's/restart: unless-stopped/restart: no/g' $DOCKER_COMPOSE_YML
sudo systemctl stop apparmor || true
sudo apt -y purge apparmor || true
fi
readyn "Install avahi-daemon? This is the recommended for reliable HomeKit discovery and pairing."
@@ -66,7 +104,7 @@ then
fi
echo "Setting permissions on $SCRYPTED_HOME"
chown -R $SERVICE_USER $SCRYPTED_HOME
chown -R $SERVICE_USER $SCRYPTED_HOME || true
set +e
@@ -79,8 +117,41 @@ set -e
echo "docker compose pull"
sudo -u $SERVICE_USER docker compose pull
echo "docker compose up -d"
sudo -u $SERVICE_USER docker compose up -d
if [ -z "$SCRYPTED_LXC" ]
then
echo "docker compose up -d"
sudo -u $SERVICE_USER docker compose up -d
else
export DOCKER_COMPOSE_SH=$SCRYPTED_HOME/docker-compose.sh
curl https://raw.githubusercontent.com/koush/scrypted/main/install/proxmox/docker-compose.sh > $DOCKER_COMPOSE_SH
chmod +x $DOCKER_COMPOSE_SH
cat > /etc/systemd/system/scrypted.service <<EOT
[Unit]
Description=Scrypted service
After=network.target
[Service]
User=root
Group=root
Type=simple
ExecStart=$DOCKER_COMPOSE_SH
Restart=always
RestartSec=3
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
EOT
systemctl daemon-reload
systemctl enable scrypted.service
systemctl restart scrypted.service
fi
echo
echo
@@ -91,5 +162,5 @@ echo "Note that it is https and that you'll be asked to approve/ignore the websi
echo
echo
echo "Optional:"
echo "Scrypted NVR Recording storage directory can be configured with an additional script:"
echo "https://docs.scrypted.app/scrypted-nvr/installation.html#docker-volume"
echo "Scrypted NVR Recording storage directory can be configured with an additional script located at:"
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#docker-volume"

View File

@@ -128,7 +128,7 @@ then
set -e
removescryptedfstab
mkdir -p /mnt/scrypted-nvr
echo "PARTLABEL=scrypted-nvr /mnt/scrypted-nvr ext4 defaults,nofail,noatime 0 0" >> /etc/fstab
echo "UUID=$UUID /mnt/scrypted-nvr ext4 defaults,nofail,noatime,x-systemd.automount 0 0" >> /etc/fstab
mount -a
systemctl daemon-reload
set +e

View File

@@ -3,10 +3,18 @@
################################################################
FROM header as base
# intel opencl gpu and npu for openvino
# vulkan
RUN apt -y install libvulkan1
# intel opencl for openvino
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# NPU driver will SIGILL on openvino prior to 2024.5.0
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# amd opencl
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-amd-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
@@ -18,8 +26,8 @@ RUN add-apt-repository -y ppa:deadsnakes/ppa && \
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
RUN python3.9 -m pip install --upgrade pip
RUN python3.9 -m pip install debugpy typing_extensions psutil
# RUN python3.9 -m pip install --upgrade pip
RUN python3.9 -m pip install debugpy
# Coral Edge TPU
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
@@ -27,16 +35,20 @@ RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" |
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
RUN apt-get -y update && apt-get -y install libedgetpu1-std
# set default shell to bash
RUN chsh -s /bin/bash
ENV SHELL="/bin/bash"
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
ENV SCRYPTED_CAN_RESTART="true"
ENV SCRYPTED_VOLUME="/server/volume"
ENV SCRYPTED_INSTALL_PATH="/server"
RUN test -f "/usr/bin/ffmpeg" && test -f "/usr/bin/python3" && test -f "/usr/bin/python3.9" && test -f "/usr/bin/python3.10"
RUN test -f "/usr/bin/ffmpeg" && test -f "/usr/bin/python3" && test -f "/usr/bin/python3.9" && test -f "/usr/bin/python3.12"
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
ENV SCRYPTED_PYTHON_PATH="/usr/bin/python3"
ENV SCRYPTED_PYTHON39_PATH="/usr/bin/python3.9"
ENV SCRYPTED_PYTHON310_PATH="/usr/bin/python3.10"
ENV SCRYPTED_PYTHON312_PATH="/usr/bin/python3.12"
ENV SCRYPTED_DOCKER_FLAVOR="full"

View File

@@ -11,12 +11,7 @@ ENV DEBIAN_FRONTEND=noninteractive
# base tools and development stuff
RUN apt-get update && apt-get -y install \
curl software-properties-common apt-utils \
build-essential \
cmake \
ffmpeg \
gcc \
libcairo2-dev \
libgirepository1.0-dev \
pkg-config && \
apt-get -y update && \
apt-get -y upgrade
@@ -37,16 +32,12 @@ RUN apt-get -y install \
python3-setuptools \
python3-wheel
# these are necessary for pillow-simd, additional on disk size is small
# but could consider removing this.
RUN echo "Installing pillow-simd dependencies."
RUN apt-get -y install \
libjpeg-dev zlib1g-dev
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
RUN echo "Installing gstreamer."
# python-codecs pygobject dependencies
RUN apt-get -y install libcairo2-dev libgirepository1.0-dev
RUN apt-get -y install \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav \
gstreamer1.0-vaapi
# python3 gstreamer bindings
@@ -57,8 +48,9 @@ RUN apt-get -y install \
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install debugpy typing_extensions psutil
# ERROR: Cannot uninstall pip 24.0, RECORD file not found. Hint: The package was installed by debian.
# RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install debugpy
################################################################
# End section generated from template/Dockerfile.full.header

View File

@@ -40,8 +40,6 @@ echo "Installing Scrypted dependencies..."
RUN_IGNORE xcode-select --install
RUN brew update
RUN_IGNORE brew install node@20
# snapshot plugin and others
RUN brew install libvips
# dlib
RUN brew install cmake
@@ -71,11 +69,14 @@ then
fi
RUN python$PYTHON_VERSION -m pip install --upgrade pip
# besides debugpy, none of these dependencies are needed anymore?
# portable python includes typing and does not need typing_extensions.
# opencv-python-headless has wheels for macos.
if [ "$PYTHON_VERSION" != "3.10" ]
then
RUN python$PYTHON_VERSION -m pip install typing
fi
RUN python$PYTHON_VERSION -m pip install debugpy typing_extensions opencv-python psutil
RUN python$PYTHON_VERSION -m pip install debugpy typing_extensions opencv-python
echo "Installing Scrypted Launch Agent..."

View File

@@ -1,3 +1,5 @@
#Requires -RunAsAdministrator
# Set-PSDebug -Trace 1
# stop existing service if any
@@ -8,7 +10,7 @@ sc.exe stop scrypted.exe
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
# Install node.js
choco upgrade -y nodejs-lts --version=20.11.1
choco upgrade -y nodejs-lts --version=20.18.0
# Install VC Redist, which is necessary for portable python
choco install -y vcredist140
@@ -22,11 +24,19 @@ $SCRYPTED_WINDOWS_PYTHON_VERSION="-3.9"
# Refresh environment variables for py and npx to work
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
# Workaround Windows Node no longer creating %APPDATA%\npm which causes npx to fail
# Fixed in newer versions of NPM but not the one bundled with Node 20
# https://github.com/nodejs/node/issues/53538
npm i -g npm
py $SCRYPTED_WINDOWS_PYTHON_VERSION -m pip install --upgrade pip
# besides debugpy, none of these dependencies are needed anymore?
# portable python includes typing and does not need typing_extensions.
# opencv-python-headless has wheels for windows.
py $SCRYPTED_WINDOWS_PYTHON_VERSION -m pip install debugpy typing_extensions typing opencv-python
$SCRYPTED_INSTALL_VERSION=[System.Environment]::GetEnvironmentVariable("SCRYPTED_INSTALL_VERSION","User")
if ($SCRYPTED_INSTALL_VERSION -eq $null) {
npx -y scrypted@latest install-server
} else {
@@ -41,6 +51,8 @@ npm install --prefix $SCRYPTED_HOME @koush/node-windows --save
$NPX_PATH = (Get-Command npx).Path
# The path needs double quotes to handle spaces in the directory path
$NPX_PATH_ESCAPED = '"' + $NPX_PATH.replace('\', '\\') + '"'
# On newer versions of NPM, the NPX might be a .ps1 file which doesn't work with child_process.spawn, change to .cmd
$NPX_PATH_ESCAPED = $NPX_PATH_ESCAPED.replace('.ps1', '.cmd')
$SERVICE_JS = @"
const fs = require('fs');
@@ -54,6 +66,8 @@ child_process.spawn('$NPX_PATH_ESCAPED', ['-y', 'scrypted', 'serve'], {
stdio: 'inherit',
// allow spawning .cmd https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
shell: true,
}).on('error', (err) => {
console.error('Error spawning child process', err);
});
"@
@@ -99,6 +113,9 @@ svc.on("install", () => {
svc.on("start", () => {
console.log("Service started");
});
svc.on("error", (err) => {
console.log("Service error", err);
});
svc.install();
"@

View File

@@ -1,120 +0,0 @@
function readyn() {
while true; do
read -p "$1 (y/n) " yn
case $yn in
[Yy]* ) break;;
[Nn]* ) break;;
* ) echo "Please answer yes or no. (y/n)";;
esac
done
}
cd /tmp
SCRYPTED_VERSION=v0.116.0
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
if [ -z "$VMID" ]
then
VMID=10443
fi
if [ -n "$SCRYPTED_RESTORE" ]
then
RESTORE_VMID=$VMID
VMID=10444
pct destroy $VMID 2>&1 > /dev/null
fi
echo "Downloading scrypted container backup."
if [ ! -f "$SCRYPTED_TAR_ZST" ]
then
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
fi
echo "Checking for existing container."
pct config $VMID
if [ "$?" == "0" ]
then
echo ""
echo "Existing container $VMID found. Run this script with --force to overwrite the existing container."
echo "This will wipe all existing data. Clone the existing container to retain the data, then reassign the owner of the scrypted volume after installation is complete."
echo ""
echo "bash $0 --force"
echo ""
fi
pct restore $VMID $SCRYPTED_TAR_ZST $@
if [ "$?" != "0" ]
then
echo ""
echo "The Scrypted container installation failed (pct restore error)."
echo ""
echo "This may be because the server's 'local' storage device is not being a valid"
echo "location for containers."
echo "Try running this script again with a different storage device like"
echo "'local-lvm' or 'local-zfs'."
echo ""
echo "#############################################################################"
echo "Paste the following command into this shell to install to local-lvm instead:"
echo ""
echo "bash $0 --storage local-lvm"
echo "#############################################################################"
echo ""
echo ""
exit 1
fi
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto
if [ "$?" != "0" ]
then
echo ""
echo "pct set network failed"
echo ""
echo "Ignoring... Please verify your container's network settings."
fi
CONF=/etc/pve/lxc/$VMID.conf
if [ -f "$CONF" ]
then
echo "onboot: 1" >> $CONF
else
echo "$CONF not found? Start on boot must be enabled manually."
fi
if [ -n "$SCRYPTED_RESTORE" ]
then
readyn "Running this script will reset Scrypted to a factory state while preserving existing data. IT IS RECOMMENDED TO CREATE A BACKUP FIRST. Are you sure you want to continue?"
if [ "$yn" != "y" ]
then
exit 1
fi
echo "Preparing rootfs reset..."
# this copies the
pct set 10444 --delete mp0 && pct set 10444 --delete unused0 && pct move-volume $RESTORE_VMID mp0 --target-vmid 10444 --target-volume mp0
rm *.tar
vzdump 10444 --dumpdir /tmp
VMID=$RESTORE_VMID
echo "Moving data volume to backup..."
pct restore $VMID *.tar $@
pct destroy 10444
fi
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
if [ "$yn" == "y" ]
then
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'KERNEL==\"renderD128\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'KERNEL==\"card0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'KERNEL==\"accel0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
udevadm control --reload-rules && udevadm trigger
fi
echo "Scrypted setup is complete and the container resources can be started."
echo "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."

View File

@@ -0,0 +1,18 @@
#!/bin/bash
cd /root/.scrypted
# always immediately upgrade everything in case there's a broken update.
# this will also be preferable for troubleshooting via lxc reboot.
export DEBIAN_FRONTEND=noninteractive
yes | dpkg --configure -a
apt -y --fix-broken install && apt -y update && apt -y dist-upgrade
# force a pull to ensure we have the latest images.
# not using --pull always cause that fails everything on network down
docker compose pull
# do not daemonize, when it exits, systemd will restart it.
# force a recreate as .env may have changed.
# furthermore force recreate gets the container back into a known state
# which is preferable in case the user has made manual changes and then restarts.
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate --abort-on-container-exit

View File

@@ -0,0 +1,302 @@
PCT=$(which pct)
if [ -z "$PCT" ]
then
echo "pct command not found. This script must be run on the Proxmox host, not a container."
echo "Installation Documentation: https://docs.scrypted.app/installation.html#proxmox-ve"
exit 1
fi
function readyn() {
while true; do
read -p "$1 (y/n) " yn
case $yn in
[Yy]* ) break;;
[Nn]* ) break;;
* ) echo "Please answer yes or no. (y/n)";;
esac
done
}
cd /tmp
SCRYPTED_VERSION=v0.120.0
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
if [ -z "$VMID" ]
then
VMID=10443
fi
SCRYPTED_BACKUP_VMID=10445
function prepareScryptedRestore() {
pct config $VMID 2>&1 > /dev/null
if [ "$?" != "0" ]
then
echo "VMID $VMID not found."
exit 1
fi
# append existing mac address.
HWADDR=",hwaddr=$(pct config $VMID | grep -oE 'hwaddr=[A-Z0-9:]+' | cut -d '=' -f 2)"
RESTORE_HOSTNAME=$(pct config $VMID | grep -oE 'hostname: [^[:space:]]+' | cut -d ':' -f 2- | tr -d ' ')
pct destroy $SCRYPTED_BACKUP_VMID 2>&1 > /dev/null
RESTORE_VMID=$VMID
VMID=$SCRYPTED_BACKUP_VMID
pct destroy $VMID 2>&1 > /dev/null
}
if [ -n "$SCRYPTED_RESTORE" ]
then
prepareScryptedRestore
fi
echo "Downloading scrypted container backup."
if [ ! -f "$SCRYPTED_TAR_ZST" ]
then
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
fi
if [[ "$@" =~ "--force" ]]
then
IGNORE_EXISTING=true
fi
if [ -n "$SCRYPTED_RESTORE" ]
then
IGNORE_EXISTING=true
fi
if [ -z "$IGNORE_EXISTING" ]
then
echo "Checking for existing container."
pct config $VMID
if [ "$?" == "0" ]
then
echo ""
echo "==============================================================="
echo "Existing container $VMID found."
echo "==============================================================="
echo ""
echo "This script can be used ro reinstall Scrypted and reset the container to a factory state."
echo "This preserves existing data. Creating a backup within Scrypted is highly recommended in case the reset fails."
echo "THIS WILL WIPE ADDITIONAL VOLUMES SUCH AS NVR STORAGE. NVR volumes will need to be readded after the restore:"
readyn "Reinstall Scrypted and and retain existing configuration?"
if [ "$yn" != "y" ]
then
echo ""
echo "1. To reinstall and reset Scrypted, run this script with --force to overwrite the existing container."
echo "THIS WILL WIPE THE EXISTING CONFIGURATION:"
echo ""
echo "VMID=$VMID bash $0 --force"
echo ""
echo "2. To reinstall Scrypted and and retain existing configuration, run this script with the environment variable SCRYPTED_RESTORE=true."
echo "This preserves existing data. Creating a backup within Scrypted is highly recommended in case the reset fails."
echo "THIS WILL WIPE ADDITIONAL VOLUMES SUCH AS NVR STORAGE. NVR volumes will need to be readded after the restore:"
echo ""
echo "SCRYPTED_RESTORE=true VMID=$VMID bash $0"
echo ""
echo "3. To install and run multiple Scrypted containers, run this script with the environment variable specifying"
echo "the new VMID=<number>. For example, to create a new LXC with VMID 12345:"
echo ""
echo "VMID=12345 bash $0"
exit 1
fi
SCRYPTED_RESTORE=true
prepareScryptedRestore
fi
fi
if [[ ! "$@" =~ "--storage" ]]
then
HAS_LOCAL_LVM=$(pvesm status | grep local-lvm | grep active)
HAS_LOCAL_ZFS=$(pvesm status | grep local-zfs | grep active)
if [ ! -z "$HAS_LOCAL_LVM" ]
then
RESTORE_STORAGE="--storage local-lvm"
elif [ ! -z "$HAS_LOCAL_ZFS" ]
then
RESTORE_STORAGE="--storage local-zfs"
else
echo "Could not determine a valid storage device. One may need to be specified manually."
fi
fi
pct stop $VMID 2>&1 > /dev/null
pct restore $VMID $SCRYPTED_TAR_ZST $RESTORE_STORAGE $@
if [ "$?" != "0" ]
then
echo ""
echo "The Scrypted container installation failed (pct restore error)."
echo ""
echo "This may be because the server's 'local' storage device is not being a valid"
echo "location for containers."
echo "Try running this script again with a different storage device like"
echo "'local-lvm' or 'local-zfs'."
echo ""
echo "#############################################################################"
echo -e "\033[32mPaste the following command into this shell to install to local-lvm instead:\033[0m"
echo ""
echo "bash $0 --storage local-lvm"
echo "#############################################################################"
echo ""
echo ""
exit 1
fi
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto$HWADDR
if [ "$?" != "0" ]
then
echo ""
echo "pct set network failed"
echo ""
echo "Ignoring... Please verify your container's network settings."
fi
if [ -n "$RESTORE_HOSTNAME" ]
then
pct set $VMID --hostname $RESTORE_HOSTNAME
if [ "$?" != "0" ]
then
echo ""
echo "pct hostname restore failed"
echo ""
echo "Ignoring... Please verify your container's dns settings."
fi
fi
CONF=/etc/pve/lxc/$VMID.conf
if [ -f "$CONF" ]
then
echo "onboot: 1" >> $CONF
else
echo "$CONF not found? Start on boot must be enabled manually."
fi
if [ -n "$SCRYPTED_RESTORE" ]
then
echo ""
echo ""
echo "This script will reset the Scrypted container to a factory state while preserving existing data."
echo "IT IS RECOMMENDED TO CREATE A BACKUP INSIDE SCRYPTED FIRST."
readyn "Are you sure you want to continue?"
if [ "$yn" != "y" ]
then
exit 1
fi
echo "Stopping scrypted..."
pct stop $RESTORE_VMID 2>&1 > /dev/null
echo "Preparing rootfs reset..."
# remove the empty data volume from the downloaded image.
pct set $SCRYPTED_BACKUP_VMID --delete mp0 && pct set $SCRYPTED_BACKUP_VMID --delete unused0
if [ "$?" != "0" ]
then
echo "Failed to remove data volume from image."
exit 1
fi
# create a backup that contains only the root disk.
rm -f *.tar
vzdump $SCRYPTED_BACKUP_VMID --dumpdir /tmp
# this moves the data volume from the current scrypted instance to the backup target to preserve it during
# the restore.
pct move-volume $RESTORE_VMID mp0 --target-vmid $SCRYPTED_BACKUP_VMID --target-volume mp0
if [ "$?" != "0" ]
then
echo "Failed to move data volume to backup."
exit 1
fi
# arguments: from to mp hide-warning
function move_volume() {
HAS_VOLUME=$(pct config $1 | grep $3:)
if [ -n "$HAS_VOLUME" ]
then
echo "Moving $3..."
# this may error and there may be recording loss. bailing at ths point is already too late.
pct move-volume $1 $3 --target-vmid $2 --target-volume $3
# volume must be inside /mnt to get into docker container
INSIDE_MNT=$(echo $HAS_VOLUME | grep /mnt)
if [ -z "$INSIDE_MNT" -a -z "$4" ]
then
echo "##################################################################"
echo "The following mount point is not visible to the"
echo "Scrypted docker container within the LXC:"
echo ""
echo "$HAS_VOLUME"
echo ""
echo "This recordings directory will be unavailable."
echo "The mount point must be updated to a path within /mnt."
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#proxmox-ve-mount-point"
echo "##################################################################"
fi
fi
}
# try moving 5 volumes, any more than that seems unlikely
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp1 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp2 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp3 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp4 hide-warning
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp5 hide-warning
VMID=$RESTORE_VMID
echo "Restoring with reset image..."
pct restore --force 1 $VMID *.tar $RESTORE_STORAGE $@
echo "Restoring volumes..."
move_volume $SCRYPTED_BACKUP_VMID $VMID mp0 hide-warning
move_volume $SCRYPTED_BACKUP_VMID $VMID mp1
move_volume $SCRYPTED_BACKUP_VMID $VMID mp2
move_volume $SCRYPTED_BACKUP_VMID $VMID mp3
move_volume $SCRYPTED_BACKUP_VMID $VMID mp4
move_volume $SCRYPTED_BACKUP_VMID $VMID mp5
pct destroy $SCRYPTED_BACKUP_VMID
fi
echo "Enabling startup on boot..."
pct set $VMID -onboot 1
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
if [ "$yn" == "y" ]
then
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"drm\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"kfd\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"accel\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
udevadm control --reload-rules && udevadm trigger
fi
# check if intel
INTEL=$(cat /proc/cpuinfo | grep GenuineIntel)
if [ ! -z "$INTEL" ]
then
readyn "Install intel-microcode package? This will update your CPU and GPU firmware."
if [ "$yn" == "y" ]
then
echo "Installing intel-microcode..."
# remove it first to allow reinsertion
sed -i 's/main contrib non-free-firmware/main/g' /etc/apt/sources.list
sed -i 's/main/main contrib non-free-firmware/g' /etc/apt/sources.list
apt update
apt install -y intel-microcode
echo "#############################"
echo "System Reboot is recommended."
echo "#############################"
fi
fi
echo "Scrypted setup is complete and the container resources can be started."
echo ""
echo "Scrypted NVR servers should run the disk setup script in the documentation to add storage prior to starting the container."

BIN
install/proxmox/lxc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,74 @@
#!/bin/bash
NVR_STORAGE=$1
NVR_STORAGE_DIRECTORY=$2
DISK_TYPE="large"
if [ ! -z "$FAST_DISK" ]
then
DISK_TYPE="fast"
fi
if [ -z "$NVR_STORAGE" ]; then
echo ""
echo "Error: Directory name not provided. Usage:"
echo ""
echo "bash $0 directory-name [/optional/path/to/storage]"
echo ""
exit 1
fi
if [ -z "$VMID" ]
then
VMID="10443"
fi
FILE="/etc/pve/lxc/$VMID.conf"
# valdiate file exists
if [ ! -f "$FILE" ]; then
echo "Error: $FILE not found."
echo "If the Scrypted container id is not 10443, please set the VMID environment variable prior to running this script."
exit 1
fi
if [ ! -z "$NVR_STORAGE_DIRECTORY" ]
then
if [ ! -d "$NVR_STORAGE_DIRECTORY" ]
then
echo ""
echo "Error: $NVR_STORAGE_DIRECTORY directory not found."
echo ""
exit 1
fi
else
STORAGE="/mnt/pve/$NVR_STORAGE"
if [ ! -d "$STORAGE" ]
then
echo "Error: $STORAGE not found."
echo "The Proxmox Directory Storage must be created using the UI prior to running this script."
exit 1
fi
# use subdirectory doesn't conflict with Proxmox storage of backups etc.
NVR_STORAGE_DIRECTORY="$STORAGE/mounts/scrypted-nvr"
fi
# create the hidden folder that can be used as a marker.
mkdir -p $NVR_STORAGE_DIRECTORY/.nvr
chmod 0777 $NVR_STORAGE_DIRECTORY
echo "Stopping Scrypted..."
pct stop "$VMID"
echo "Modifying $FILE."
if [ -z "$ADD_DISK" ]
then
echo "Removing previous $DISK_TYPE lxc.mount.entry."
sed -i "/mnt\/nvr\/$DISK_TYPE/d" "$FILE"
fi
echo "Adding new $DISK_TYPE lxc.mount.entry."
echo "lxc.mount.entry: $NVR_STORAGE_DIRECTORY mnt/nvr/$DISK_TYPE/$NVR_STORAGE none bind,optional,create=dir" >> "$FILE"
echo "Starting Scrypted..."
pct start $VMID

View File

@@ -27,7 +27,7 @@ echo "external/werift > npm install"
npm install
popd
for directory in ffmpeg-camera rtsp amcrest onvif hikvision unifi-protect webrtc homekit
for directory in rtsp amcrest onvif hikvision reolink unifi-protect webrtc homekit
do
echo "$directory > npm install"
pushd plugins/$directory

View File

@@ -1,24 +1,25 @@
{
"name": "@scrypted/client",
"version": "1.3.6",
"version": "1.3.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/client",
"version": "1.3.6",
"version": "1.3.9",
"license": "ISC",
"dependencies": {
"@scrypted/types": "^0.3.40",
"engine.io-client": "^6.5.3",
"follow-redirects": "^1.15.6",
"rimraf": "^5.0.5"
"@scrypted/types": "^0.3.92",
"engine.io-client": "^6.6.1",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
},
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^20.11.30",
"@types/node": "^22.7.4",
"@types/ws": "^8.5.13",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
"typescript": "^5.6.2"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -74,19 +75,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@scrypted/types": {
"version": "0.3.40",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.40.tgz",
"integrity": "sha512-NBjNEfoLp7zL5Tf0odzf191oReDh4FEmZexDmMj1JbKDUMB9S8xJys3vbhcFadU/aUrUkyK/FSbkXv1z87bxSw=="
"version": "0.3.92",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.92.tgz",
"integrity": "sha512-/M1Lg42/yoFWusj5+Lyp2S0JCiWDDWcmsjiUnTf1DahZ6/M2oZ3bwR/0KX3D9vJE79owWST1Gm0+Rdvpxuil9A==",
"license": "ISC"
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
@@ -127,12 +120,22 @@
}
},
"node_modules/@types/node": {
"version": "20.11.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
"version": "22.7.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~6.19.2"
}
},
"node_modules/@types/ws": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/acorn": {
@@ -157,9 +160,9 @@
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"engines": {
"node": ">=12"
},
@@ -220,9 +223,10 @@
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -268,15 +272,15 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/engine.io-client": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.0.tgz",
"integrity": "sha512-iBtCdW5Tk3CnMAnC44VO4LwxXnl+RIq9ua1qHvxf5KSq2rzFgQFdfCSSl6Yuz2hl899SWTkfaT3c+WZQ42dJ8A==",
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
"integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
@@ -288,9 +292,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
@@ -307,9 +311,9 @@
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
@@ -322,21 +326,22 @@
}
},
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
"integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.5",
"minimatch": "^9.0.1",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
"path-scurry": "^1.10.1"
"jackspeak": "^4.0.1",
"minimatch": "^10.0.0",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -356,28 +361,25 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
"integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/lru-cache": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz",
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
"integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==",
"engines": {
"node": "14 || >=16.14"
"node": "20 || >=22"
}
},
"node_modules/make-error": {
@@ -387,23 +389,23 @@
"dev": true
},
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -413,6 +415,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -422,32 +429,33 @@
}
},
"node_modules/path-scurry": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
"integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"dependencies": {
"glob": "^10.3.7"
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=14"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -615,9 +623,9 @@
}
},
"node_modules/typescript": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -628,9 +636,9 @@
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
"node_modules/v8-compile-cache-lib": {
@@ -758,9 +766,9 @@
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
"engines": {
"node": ">=0.4.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/client",
"version": "1.3.6",
"version": "1.3.9",
"description": "",
"main": "dist/packages/client/src/index.js",
"scripts": {
@@ -13,14 +13,15 @@
"license": "ISC",
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^20.11.30",
"@types/node": "^22.7.4",
"@types/ws": "^8.5.13",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
"typescript": "^5.6.2"
},
"dependencies": {
"@scrypted/types": "^0.3.40",
"engine.io-client": "^6.5.3",
"follow-redirects": "^1.15.6",
"rimraf": "^5.0.5"
"@scrypted/types": "^0.3.92",
"engine.io-client": "^6.6.1",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
}
}

View File

@@ -169,6 +169,7 @@ export async function loginScryptedClient(options: ScryptedLoginOptions) {
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
directAddress: response.headers.get('x-scrypted-direct-address'),
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
serverId: response.headers.get('x-scrypted-server-id'),
};
}
@@ -213,6 +214,7 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
directAddress: response.headers.get('x-scrypted-direct-address'),
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
serverId: response.headers.get('x-scrypted-server-id'),
};
}
@@ -227,6 +229,7 @@ export interface ScryptedClientLoginResult {
directAddress: string;
cloudAddress: string;
hostname: string;
serverId: string;
}
export class ScryptedClientLoginError extends Error {
@@ -274,6 +277,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
let cloudAddress: string;
let hostname: string;
let token: string;
let serverId: string;
console.log('@scrypted/client', packageJson.version);
@@ -299,6 +303,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
queryToken = loginResult.queryToken;
token = loginResult.token;
hostname = loginResult.hostname;
serverId = loginResult.serverId;
console.log('login result', Date.now() - start, loginResult);
}
else {
@@ -372,6 +377,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
queryToken = loginCheck.queryToken;
token = loginCheck.token;
hostname = loginCheck.hostname;
serverId = loginCheck.serverId;
console.log('login checked', Date.now() - start, loginCheck);
}
@@ -694,6 +700,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
deviceManager,
endpointManager,
mediaManager,
clusterManager,
} = scrypted;
console.log('api attached', Date.now() - start);
@@ -853,6 +860,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
connectionType,
admin,
systemManager,
clusterManager,
deviceManager,
endpointManager,
mediaManager,
@@ -874,6 +882,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
authorization,
cloudAddress,
hostname,
serverId,
},
connectRPCObject,
fork: undefined,

View File

@@ -1,12 +1,11 @@
{
"name": "@scrypted/alexa",
"version": "0.3.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/alexa",
"version": "0.3.3",
"version": "0.3.4",
"dependencies": {
"axios": "^1.3.4",
"uuid": "^9.0.0"
@@ -203,4 +202,4 @@
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/alexa",
"version": "0.3.3",
"version": "0.3.4",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",

View File

@@ -53,6 +53,12 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
title: "Pairing Key",
description: "The pairing key used to validate requests from Alexa. Clear this key or delete the plugin to allow pairing with a different Alexa login.",
},
disableAutoAdd: {
title: "Disable auto add",
description: "Disable automatic enablement of devices.",
type: 'boolean',
defaultValue: false,
},
});
accessToken: Promise<string>;
@@ -116,6 +122,10 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
if (!supportedTypes.has(device.type))
return DeviceMixinStatus.NotSupported;
if (this.storageSettings.values.disableAutoAdd) {
return DeviceMixinStatus.Skip;
}
mixins.push(this.id);
const plugins = await systemManager.getComponent('plugins');
@@ -671,7 +681,8 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
enum DeviceMixinStatus {
NotSupported = 0,
Setup = 1,
AlreadySetup = 2
AlreadySetup = 2,
Skip = 3,
}
class HttpResponseLoggingImpl implements AlexaHttpResponse {

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.162",
"version": "0.0.164",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.162",
"version": "0.0.164",
"license": "Apache",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.162",
"version": "0.0.164",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -199,6 +199,14 @@ export class AmcrestCameraClient {
return response.body;
}
async setWatermark(cameraNumber: number, enable: boolean) {
const response = await this.request({
url: `http://${this.ip}/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[${cameraNumber - 1}].PictureTitle.EncodeBlend=${enable}`,
responseType: 'text',
});
return response.body;
}
async listenEvents(): Promise<Destroyable> {
const events = new EventEmitter();
const url = `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[All]`;

View File

@@ -9,12 +9,14 @@ export const amcrestAutoConfigureSettings: Setting = {
};
export async function autoconfigureSettings(client: AmcrestCameraClient, cameraNumber: number) {
await client.setWatermark(cameraNumber, false).catch(() => { });
const audioOptions: AudioStreamConfiguration = {
codec: 'aac',
sampleRate: 8000,
};
await client.resetMotionDetection(cameraNumber);
await client.resetMotionDetection(cameraNumber).catch(() => {});
return ac(
() => client.getCodecs(cameraNumber),

View File

@@ -56,13 +56,13 @@ Scrypted Cloud automatically creates a login free tunnel for remote access.
The following steps are only necessary if you want to associate the tunnel with your existing Cloudflare account to manage it remotely.
1. Create the Tunnel in the [Cloudflare Zero Trust Dashboard](https://one.dash.cloudflare.com).
2. Copy the token shown for the tunnel shown in the `install [token]` command. For example, if you see `cloudflared service install eyJhI344aA...`, then `eyJhI344aA...` is the token you need to copy.
3. Paste the token into the Cloud Plugin Advanced Settings.
4. Add a `Public Hostname` to the tunnel.
* Choose a (sub)domain.
* Service `Type` is `HTTPS` and `URL` is `localhost:port`. Replace the port with `Forward Port` from Cloud Plugin Settings.
* Expand `Additional Application Settings` -> `TLS` menus and enable `No TLS Verify`.
1. Navigate to the Cloud Plugin's Cloudflare Settings.
2. Enter the Cloudflare subdomain, e.g. `scrypted.example.org`.
3. Open the authorization link printed in the Log in a browser.
4. Log in to Cloudflare if prompted. Then open the authorization link again.
5. Select the domain for the specified the subdomain.
6. Authorization should now be complete.
5. Reload Cloud Plugin.
6. Verify Cloudflare successfully connected by observing the `Console` Logs.
::: info
Visiting the authorization link twice as directed in the above instructions may be necessary. Cloudflare will not prompt a with a list of domains unless the browser session is already logged in.
:::

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@
]
},
"dependencies": {
"@eneris/push-receiver": "^4.2.0",
"@eneris/push-receiver": "^4.3.0",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"bpmux": "^8.2.1",
@@ -48,10 +48,9 @@
},
"devDependencies": {
"@types/http-proxy": "^1.17.15",
"@types/ip": "^1.1.3",
"@types/nat-upnp": "^1.1.5",
"@types/node": "^22.5.2",
"@types/node": "^22.10.1",
"ts-node": "^10.9.2"
},
"version": "0.2.37"
"version": "0.2.48"
}

View File

@@ -29,6 +29,7 @@ const { deviceManager, endpointManager, systemManager } = sdk;
export const DEFAULT_SENDER_ID = '827888101440';
const SCRYPTED_SERVER = localStorage.getItem('scrypted-server') || 'home.scrypted.app';
const SCRYPTED_SERVER_PORT = 4001;
const SCRYPTED_CLOUD_MESSAGE_PATH = '/_punch/cloudmessage';
@@ -147,7 +148,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
type: 'boolean',
description: 'Optional: Create a Cloudflare Tunnel to this server at a random domain name. Providing a Cloudflare token will allow usage of a custom domain name.',
defaultValue: true,
onPut: () => deviceManager.requestRestart(),
},
cloudflaredTunnelToken: {
group: 'Cloudflare',
@@ -183,6 +183,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.storageSettings.values.cloudflaredTunnelCredentials = undefined;
this.doCloudflaredLogin(nv);
},
console: true,
},
cloudflaredTunnelLoginUrl: {
group: 'Cloudflare',
@@ -258,6 +259,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.converters = [
[ScryptedMimeTypes.LocalUrl, ScryptedMimeTypes.Url],
[ScryptedMimeTypes.PushEndpoint, ScryptedMimeTypes.Url],
['*/*', ScryptedMimeTypes.ServerId],
];
// legacy cleanup
this.fromMimeType = undefined;
@@ -266,10 +268,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
devices: [],
});
this.storageSettings.settings.register.onPut = async () => {
await this.sendRegistrationId(await this.manager.registrationId);
}
this.storageSettings.settings.upnpStatus.onGet = async () => {
return {
hide: this.storageSettings.values.forwardingMode !== 'UPNP',
@@ -313,11 +311,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
};
this.storageSettings.settings.securePort.onPut = (ov, nv) => {
if (ov && ov !== nv)
this.log.a('Reload the Scrypted Cloud Plugin to apply the port change.');
};
if (!this.storageSettings.values.certificate)
this.storageSettings.values.certificate = createSelfSignedCertificate();
@@ -340,6 +333,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.refreshPortForward();
}
// auto login from electron
if (!this.storageSettings.values.token_info && process.env.SCRYPTED_CLOUD_TOKEN) {
this.storageSettings.values.token_info = process.env.SCRYPTED_CLOUD_TOKEN;
this.manager.registrationId.then(r => {
@@ -366,6 +360,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
}, 1000);
}
async getCachedRegistrationId() {
return this.manager.currentRegistrationId || this.storageSettings.values.lastPersistedRegistrationId;
}
async updatePortForward(upnpPort: number) {
this.storageSettings.values.upnpPort = upnpPort;
@@ -431,8 +429,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
if (this.storageSettings.values.lastPersistedUpnpPort !== upnpPort || ip !== this.storageSettings.values.lastPersistedIp) {
this.console.log('Registering IP and Port', ip, upnpPort);
const registrationId = await this.manager.registrationId;
const data = await this.sendRegistrationId(registrationId);
const data = await this.sendRegistrationId(await this.getCachedRegistrationId());
if (data?.error)
return;
if (ip !== 'localhost' && ip !== data.ip_address && ip !== this.cloudflareTunnelHost) {
@@ -451,11 +448,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
public: true,
});
const url = new URL(`https://${SCRYPTED_SERVER}/_punch/curl`);
let { upnp_port, hostname } = this.getAuthority();
let { port, hostname } = this.getAuthority();
// scrypted cloud will replace localhost with requesting ip
if (!hostname)
hostname = 'localhost';
url.searchParams.set('url', `https://${hostname}:${upnp_port}${pluginPath}/testPortForward`);
url.searchParams.set('url', `https://${hostname}:${port}${pluginPath}/testPortForward`);
const response = await httpFetch({
url: url.toString(),
responseType: 'json',
@@ -621,13 +618,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
if (!hostname) {
return {
upnp_port,
port: upnp_port,
};
}
return {
upnp_port,
port: upnp_port,
hostname,
}
@@ -638,6 +633,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
const q = qsstringify({
...authority,
cloudflare_hostname: this.cloudflareTunnelHost,
registration_id,
server_id: this.storageSettings.values.serverId,
server_name: this.storageSettings.values.serverName,
@@ -677,7 +673,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.console.log('registered', response.body);
this.storageSettings.values.lastPersistedRegistrationId = registration_id;
this.storageSettings.values.lastPersistedUpnpPort = authority.upnp_port;
this.storageSettings.values.lastPersistedUpnpPort = authority.port;
return response.body;
}
catch (e) {
@@ -729,10 +725,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
}
async convertMedia(data: string | Buffer | any, fromMimeType: string, toMimeType: string, options?: MediaObjectOptions): Promise<MediaObject | Buffer | any> {
if (!toMimeType.startsWith(ScryptedMimeTypes.Url))
throw new Error('unsupported cloud url conversion');
if (fromMimeType.startsWith(ScryptedMimeTypes.LocalUrl)) {
if (toMimeType.startsWith(ScryptedMimeTypes.Url) && fromMimeType.startsWith(ScryptedMimeTypes.LocalUrl)) {
// if cloudflare is enabled and the plugin isn't set up as a custom domain, try to use the cloudflare url for
// short lived urls.
if (this.cloudflareTunnel && this.storageSettings.values.forwardingMode !== 'Custom Domain') {
@@ -746,7 +739,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
}
return this.whitelist(data.toString(), 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}`);
}
else if (fromMimeType.startsWith(ScryptedMimeTypes.PushEndpoint)) {
else if (toMimeType.startsWith(ScryptedMimeTypes.Url) && fromMimeType.startsWith(ScryptedMimeTypes.PushEndpoint)) {
const validDomain = this.getSSLHostname();
if (validDomain)
return Buffer.from(`https://${validDomain}${await this.getCloudMessagePath()}/${data}`);
@@ -754,6 +747,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
const url = `http://127.0.0.1/push/${data}`;
return this.whitelist(url, 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}${SCRYPTED_CLOUD_MESSAGE_PATH}`);
}
else if (toMimeType === ScryptedMimeTypes.ServerId) {
return this.storageSettings.values.serverId;
}
throw new Error('unsupported cloud url conversion');
}
@@ -792,7 +788,8 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
const args = qsstringify({
...authority,
registration_id: await this.manager.registrationId,
registration_id: await this.getCachedRegistrationId() || 'undefined',
cloudflare_hostname: this.cloudflareTunnelHost,
registration_secret: this.storageSettings.values.registrationSecret,
server_id: this.storageSettings.values.serverId,
server_name: this.storageSettings.values.serverName,
@@ -833,7 +830,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
const query = qsparse(url.searchParams);
if (!query.callback_url && query.token_info && query.user_info) {
this.storageSettings.values.token_info = query.token_info;
this.storageSettings.values.lastPersistedRegistrationId = await this.manager.registrationId;
this.storageSettings.values.lastPersistedRegistrationId = await this.getCachedRegistrationId();
res.setHeader('Location', `https://${this.getHostname()}/endpoint/@scrypted/core/public/`);
res.writeHead(302);
res.end();
@@ -844,6 +841,17 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
return;
}
}
else if (url.pathname === '/_punch/callback') {
const query = qsparse(url.searchParams);
if (query.registration_secret === this.storageSettings.values.registrationSecret) {
res.writeHead(200);
this.serverCallback(port, SCRYPTED_SERVER_PORT, SCRYPTED_SERVER);
}
else {
res.writeHead(401);
}
res.end();
}
else if (url.pathname === '/web/') {
const validDomain = this.getSSLHostname();
if (validDomain) {
@@ -890,6 +898,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
const port = (this.server.address() as any).port;
this.console.log('scrypted cloud server listening on', port);
this.storageSettings.settings.cloudflareEnabled.onPut = () => {
this.cloudflared?.child.kill();
this.startCloudflared(port);
};
const agent = new http.Agent({ maxSockets: Number.MAX_VALUE, keepAlive: true });
this.proxy = HttpProxy.createProxy({
agent,
@@ -900,11 +913,12 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.proxy.on('proxyRes', (res, req) => {
res.headers['X-Scrypted-Cloud'] = req.headers['x-scrypted-cloud'];
res.headers['X-Scrypted-Direct-Address'] = req.headers['x-scrypted-direct-address'];
res.headers['X-Scrypted-Server-Id'] = this.storageSettings.values.serverId;
let domain = this.cloudflareTunnel;
if (!domain && this.storageSettings.values.forwardingMode === 'Custom Domain' && this.storageSettings.values.hostname)
domain = `https://${this.storageSettings.values.hostname}`;
res.headers['X-Scrypted-Cloud-Address'] = domain;
res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address, X-Scrypted-Cloud-Address';
res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address, X-Scrypted-Cloud-Address, X-Scrypted-Server-Id';
});
let backoff = 0;
@@ -927,42 +941,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
if (Date.now() < backoff + 5000)
return;
backoff = Date.now();
const random = Math.random().toString(36).substring(2);
this.console.log('scrypted server requested a connection:', random);
const registrationId = await this.manager.registrationId;
const { address } = message;
const [serverHost, serverPort] = address?.split(':') || [SCRYPTED_SERVER, 4001];
this.ensureReverseConnections(registrationId, serverPort, serverHost);
const client = tls.connect(serverPort, serverHost, {
rejectUnauthorized: false,
});
client.on('close', () => this.console.log('scrypted server connection ended:', random));
client.write(registrationId + '\n');
const mux: any = new bpmux.BPMux(client as any);
mux.on('handshake', async (socket: Duplex) => {
this.ensureReverseConnections(registrationId, serverPort, serverHost);
this.console.warn('mux connection required');
let local: any;
await new Promise(resolve => process.nextTick(resolve));
local = net.connect({
port,
host: '127.0.0.1',
});
await new Promise(resolve => process.nextTick(resolve));
socket.pipe(local).pipe(socket);
});
mux.on('error', () => {
client.destroy();
});
const [serverHost, serverPort] = address?.split(':') || [SCRYPTED_SERVER, SCRYPTED_SERVER_PORT];
this.serverCallback(port, Number(serverPort), serverHost);
}
});
@@ -989,6 +970,40 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
}
}
serverCallback(port: number, serverPort: number, serverHost: string) {
const random = Math.random().toString(36).substring(2);
this.console.log('scrypted server requested a connection:', random);
this.ensureReverseConnections(serverPort, serverHost);
const client = tls.connect(serverPort, serverHost, {
rejectUnauthorized: false,
});
client.on('close', () => this.console.log('scrypted server connection ended:', random));
client.write(this.serverIdentifier + '\n');
const mux: any = new bpmux.BPMux(client as any);
mux.on('handshake', async (socket: Duplex) => {
this.ensureReverseConnections(serverPort, serverHost);
this.console.warn('mux connection required');
let local: any;
await new Promise(resolve => process.nextTick(resolve));
local = net.connect({
port,
host: '127.0.0.1',
});
await new Promise(resolve => process.nextTick(resolve));
socket.pipe(local).pipe(socket);
});
mux.on('error', () => {
client.destroy();
});
}
async startCloudflared(quickTunnelPort: number) {
while (true) {
try {
@@ -1004,6 +1019,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
if (this.storageSettings.values.cloudflaredTunnelCredentials && this.storageSettings.values.cloudflaredTunnelCustomDomain) {
const tunnelUrl = `http://127.0.0.1:${quickTunnelPort}`;
const url = this.cloudflareTunnel = `https://${this.storageSettings.values.cloudflaredTunnelCustomDomain}`;
this.updateExternalAddresses();
this.console.log(`cloudflare url mapped ${this.cloudflareTunnel} to ${tunnelUrl}`);
const ret = await runLocallyManagedTunnel(this.storageSettings.values.cloudflaredTunnelCredentials, tunnelUrl, cloudflareD, bin);
@@ -1038,7 +1054,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
const lines = string.split('\n');
for (const line of lines) {
if ((line.includes('Unregistered tunnel connection') || line.includes('Register tunnel error'))
if ((line.includes('Unregistered tunnel connection')
|| line.includes('Connection terminated error')
|| line.includes('Register tunnel error')
|| line.includes('Failed to serve tunnel')
|| line.includes('Failed to get tunnel'))
&& deferred.finished) {
this.console.warn('Cloudflare registration failed after tunnel started. The old tunnel may be invalid. Terminating.');
cloudflareTunnel.child.kill();
@@ -1113,13 +1133,18 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
}
}
ensureReverseConnections(registrationId: string, serverPort: number, serverHost: string) {
get serverIdentifier() {
const serverIdentifier = `${this.storageSettings.values.registrationSecret}@${this.storageSettings.values.serverId}`;
return serverIdentifier;
}
ensureReverseConnections(serverPort: number, serverHost: string) {
while (this.reverseConnections.size < 10) {
this.createReverseConnection(registrationId, serverPort, serverHost);
this.createReverseConnection(serverPort, serverHost);
}
}
async createReverseConnection(registrationId: string, serverPort: number, serverHost: string) {
async createReverseConnection(serverPort: number, serverHost: string) {
const client = tls.connect(serverPort, serverHost, {
rejectUnauthorized: false,
});
@@ -1131,9 +1156,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
this.reverseConnections.delete(client);
if (claimed)
this.ensureReverseConnections(registrationId, serverPort, serverHost);
this.ensureReverseConnections(serverPort, serverHost);
});
client.write(`reverse:${registrationId}\n`);
client.write(`reverse:${this.serverIdentifier}\n`);
try {
const read = await readLine(client);

View File

@@ -10,6 +10,7 @@ export declare interface PushManager {
export class PushManager extends EventEmitter {
registrationId: Promise<string>;
currentRegistrationId: string;
constructor(public senderId: string) {
super();
@@ -47,11 +48,12 @@ export class PushManager extends EventEmitter {
}
const stopListeningToCredentials = instance.onCredentialsChanged(({ oldCredentials, newCredentials }) => {
this.currentRegistrationId = newCredentials.fcm.token;
savedConfig.credentials = newCredentials;
deferred.resolve(newCredentials.fcm.token);
this.registrationId = Promise.resolve(newCredentials.fcm.token);
deferred.resolve(this.currentRegistrationId);
this.registrationId = Promise.resolve( this.currentRegistrationId);
saveConfig();
this.emit('registrationId', newCredentials.fcm.token);
this.emit('registrationId', this.currentRegistrationId);
});
const stopListeningToNotifications = instance.onNotification(({ message }) => {

View File

@@ -1,6 +1,8 @@
export function qsstringify(dict: any) {
const params = new URLSearchParams();
for (const [k, v] of Object.entries(dict)) {
if (v == null)
continue;
params.set(k, v?.toString());
}

View File

@@ -1,3 +1,3 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-nvr",
}

View File

@@ -0,0 +1 @@
../../../../install/proxmox/docker-compose.sh

View File

@@ -1,22 +0,0 @@
[Unit]
Description=Scrypted service
After=network.target
[Service]
User=root
Group=root
Type=simple
ExecStart=/usr/bin/npx -y scrypted serve
Restart=always
RestartSec=3
Environment="NODE_OPTIONS=--dns-result-order=ipv4first"
Environment="SCRYPTED_PYTHON_PATH=/usr/bin/python3"
Environment="SCRYPTED_PYTHON39_PATH=/usr/bin/python3.9"
Environment="SCRYPTED_PYTHON310_PATH=/usr/bin/python3.10"
Environment="SCRYPTED_FFMPEG_PATH=/usr/bin/ffmpeg"
Environment="SCRYPTED_INSTALL_ENVIRONMENT=lxc"
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target

View File

@@ -1,24 +1,24 @@
{
"name": "@scrypted/core",
"version": "0.3.68",
"version": "0.3.102",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.3.68",
"version": "0.3.102",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"mime": "^3.0.0",
"mime": "^4.0.4",
"node-pty": "^1.0.0",
"router": "^1.3.8",
"typescript": "^5.4.2"
"typescript": "^5.6.2",
"yaml": "^2.5.1"
},
"devDependencies": {
"@types/mime": "^3.0.4",
"@types/node": "^20.11.26"
"@types/node": "^22.7.4"
}
},
"../../../sdk": {
@@ -88,23 +88,29 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.45",
"version": "0.3.100",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -116,11 +122,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
}
},
"node_modules/@scrypted/common": {
@@ -131,19 +135,13 @@
"resolved": "../../sdk",
"link": true
},
"node_modules/@types/mime": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
"dev": true
},
"node_modules/@types/node": {
"version": "20.11.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz",
"integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==",
"version": "22.7.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~6.19.2"
}
},
"node_modules/array-flatten": {
@@ -160,14 +158,17 @@
}
},
"node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
"funding": [
"https://github.com/sponsors/broofa"
],
"bin": {
"mime": "cli.js"
"mime": "bin/cli.js"
},
"engines": {
"node": ">=10.0.0"
"node": ">=16"
}
},
"node_modules/nan": {
@@ -195,7 +196,7 @@
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/router": {
"version": "1.3.8",
@@ -233,9 +234,9 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/typescript": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -245,9 +246,9 @@
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
"node_modules/utils-merge": {
@@ -257,6 +258,17 @@
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/yaml": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
}
},
"dependencies": {
@@ -274,40 +286,38 @@
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"@types/node": "^22.10.1",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"stringify-object": "^3.3.0",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
}
},
"@types/mime": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
"dev": true
},
"@types/node": {
"version": "20.11.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz",
"integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==",
"version": "22.7.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
"dev": true,
"requires": {
"undici-types": "~5.26.4"
"undici-types": "~6.19.2"
}
},
"array-flatten": {
@@ -321,9 +331,9 @@
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ=="
},
"nan": {
"version": "2.20.0",
@@ -346,7 +356,7 @@
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"router": {
"version": "1.3.8",
@@ -383,20 +393,25 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"typescript": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ=="
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw=="
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"yaml": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q=="
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.3.68",
"version": "0.3.102",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",
@@ -42,13 +42,13 @@
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"mime": "^3.0.0",
"mime": "^4.0.4",
"node-pty": "^1.0.0",
"router": "^1.3.8",
"typescript": "^5.4.2"
"typescript": "^5.6.2",
"yaml": "^2.5.1"
},
"devDependencies": {
"@types/mime": "^3.0.4",
"@types/node": "^20.11.26"
"@types/node": "^22.7.4"
}
}

View File

@@ -1,5 +1,5 @@
import sdk, { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting } from '@scrypted/sdk';
import { AggregateDevice, createAggregateDevice } from './aggregate';
import { AggregateDevice } from './aggregate';
const { deviceManager } = sdk;
export const AggregateCoreNativeId = 'aggregatecore';
@@ -13,24 +13,6 @@ export class AggregateCore extends ScryptedDeviceBase implements DeviceProvider,
this.systemDevice = {
deviceCreator: 'Device Group',
};
for (const nativeId of deviceManager.getNativeIds()) {
if (nativeId?.startsWith('aggregate:')) {
const aggregate = createAggregateDevice(nativeId);
this.aggregate.set(nativeId, aggregate);
this.reportAggregate(nativeId, aggregate.computeInterfaces(), aggregate.providedName);
}
}
sdk.systemManager.listen((eventSource, eventDetails, eventData) => {
if (eventDetails.eventInterface === 'Storage') {
const ids = [...this.aggregate.values()].map(a => a.id);
if (ids.includes(eventSource.id)) {
const aggregate = [...this.aggregate.values()].find(a => a.id === eventSource.id);
this.reportAggregate(aggregate.nativeId, aggregate.computeInterfaces(), aggregate.providedName);
}
}
});
}
async getReadmeMarkdown(): Promise<string> {
@@ -51,7 +33,8 @@ export class AggregateCore extends ScryptedDeviceBase implements DeviceProvider,
const { name } = settings;
const nativeId = `aggregate:${Math.random()}`;
await this.reportAggregate(nativeId, [], name?.toString());
const aggregate = createAggregateDevice(nativeId);
const aggregate = new AggregateDevice(this, nativeId);
aggregate.computeInterfaces();
this.aggregate.set(nativeId, aggregate);
return nativeId;
}
@@ -68,9 +51,17 @@ export class AggregateCore extends ScryptedDeviceBase implements DeviceProvider,
}
async getDevice(nativeId: string) {
return this.aggregate.get(nativeId);
let device = this.aggregate.get(nativeId);
if (device)
return device;
device = new AggregateDevice(this, nativeId);
device.computeInterfaces();
this.aggregate.set(nativeId, device);
return device;
}
async releaseDevice(id: string, nativeId: string): Promise<void> {
const device = this.aggregate.get(nativeId);
device?.release();
}
}

View File

@@ -1,11 +1,8 @@
import sdk, { EventListener, EventListenerRegister, FFmpegInput, LockState, MediaStreamDestination, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDevice, ScryptedDeviceBase, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera } from "@scrypted/sdk";
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import type { AggregateCore } from "./aggregate-core";
const { systemManager, mediaManager, deviceManager } = sdk;
export interface AggregateDevice extends ScryptedDeviceBase {
computeInterfaces(): string[];
}
interface Aggregator<T> {
(values: T[]): T;
}
@@ -141,143 +138,144 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
}
}
export function createAggregateDevice(nativeId: string): AggregateDevice {
class AggregateDeviceImpl extends ScryptedDeviceBase implements Settings {
listeners: EventListenerRegister[] = [];
storageSettings = new StorageSettings(this, {
deviceInterfaces: {
title: 'Selected Device Interfaces',
description: 'The components of other devices to combine into this device group.',
type: 'interface',
multiple: true,
deviceFilter: `id !== '${this.id}' && deviceInterface !== '${ScryptedInterface.Settings}'`,
export class AggregateDevice extends ScryptedDeviceBase implements Settings {
listeners: EventListenerRegister[] = [];
storageSettings = new StorageSettings(this, {
deviceInterfaces: {
title: 'Selected Device Interfaces',
description: 'The components of other devices to combine into this device group.',
type: 'interface',
multiple: true,
deviceFilter: `id !== '${this.id}' && deviceInterface !== '${ScryptedInterface.Settings}'`,
onPut: () => {
this.core.reportAggregate(this.nativeId, this.computeInterfaces(), this.providedName);
}
})
constructor() {
super(nativeId);
try {
const data = this.storage.getItem('data');
if (data) {
const { deviceInterfaces } = JSON.parse(data);
this.storageSettings.values.deviceInterfaces = deviceInterfaces;
}
}
catch (e) {
}
this.storage.removeItem('data');
}
})
getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
constructor(public core: AggregateCore, nativeId: string) {
super(nativeId);
try {
const data = this.storage.getItem('data');
if (data) {
const { deviceInterfaces } = JSON.parse(data);
this.storageSettings.values.deviceInterfaces = deviceInterfaces;
}
}
putSetting(key: string, value: SettingValue): Promise<void> {
return this.storageSettings.putSetting(key, value);
catch (e) {
}
this.storage.removeItem('data');
}
makeListener(iface: string, devices: ScryptedDevice[]) {
const aggregator = aggregators.get(iface);
if (!aggregator) {
const ds = deviceManager.getDeviceState(this.nativeId);
// if this device can't be aggregated for whatever reason, pass property through.
for (const device of devices) {
const register = device.listen({
event: iface,
watch: true,
}, (source, details, data) => {
if (details.property)
ds[details.property] = data;
});
this.listeners.push(register);
}
return;
}
const property = ScryptedInterfaceDescriptors[iface]?.properties?.[0];
if (!property) {
this.console.warn('aggregating interface with no property?', iface);
return;
}
const runAggregator = () => {
const values = devices.map(device => device[property]);
(this as any)[property] = aggregator(values);
}
const listener: EventListener = () => runAggregator();
getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
}
putSetting(key: string, value: SettingValue): Promise<void> {
return this.storageSettings.putSetting(key, value);
}
makeListener(iface: string, devices: ScryptedDevice[]) {
const aggregator = aggregators.get(iface);
if (!aggregator) {
const ds = deviceManager.getDeviceState(this.nativeId);
// if this device can't be aggregated for whatever reason, pass property through.
for (const device of devices) {
const register = device.listen({
event: iface,
watch: true,
}, listener);
}, (source, details, data) => {
if (details.property)
ds[details.property] = data;
});
this.listeners.push(register);
}
return runAggregator;
return;
}
computeInterfaces(): string[] {
this.listeners.forEach(listener => listener.removeListener());
this.listeners = [];
try {
const interfaces = new Map<string, string[]>();
for (const deviceInterface of this.storageSettings.values.deviceInterfaces as string[]) {
const parts = deviceInterface.split('#');
const id = parts[0];
const iface = parts[1];
if (!interfaces.has(iface))
interfaces.set(iface, []);
interfaces.get(iface).push(id);
}
for (const [iface, ids] of interfaces.entries()) {
const devices = ids.map(id => systemManager.getDeviceById(id));
const runAggregator = this.makeListener(iface, devices);
runAggregator?.();
}
for (const [iface, ids] of interfaces.entries()) {
const devices = ids.map(id => systemManager.getDeviceById(id));
const descriptor = ScryptedInterfaceDescriptors[iface];
if (!descriptor) {
this.console.warn(`descriptor not found for ${iface}, skipping method generation`);
continue;
}
if (iface === ScryptedInterface.VideoCamera) {
const camera = createVideoCamera(devices as any, this.console);
for (const method of descriptor.methods) {
AggregateDeviceImpl.prototype[method] = (...args: any[]) => camera[method](...args);
}
continue;
}
for (const method of descriptor.methods) {
AggregateDeviceImpl.prototype[method] = async function (...args: any[]) {
const ret: Promise<any>[] = [];
for (const device of devices) {
ret.push(device[method](...args));
}
const results = await Promise.all(ret);
return results[0];
}
}
}
return [...interfaces.keys()];
}
catch (e) {
// this.console.error('error loading aggregate device', e);
return [];
}
const property = ScryptedInterfaceDescriptors[iface]?.properties?.[0];
if (!property) {
this.console.warn('aggregating interface with no property?', iface);
return;
}
const runAggregator = () => {
const values = devices.map(device => device[property]);
(this as any)[property] = aggregator(values);
}
const listener: EventListener = () => runAggregator();
for (const device of devices) {
const register = device.listen({
event: iface,
watch: true,
}, listener);
this.listeners.push(register);
}
return runAggregator;
}
const ret = new AggregateDeviceImpl();
ret.computeInterfaces();
return new AggregateDeviceImpl();
}
release() {
this.listeners.forEach(listener => listener.removeListener());
this.listeners = [];
}
computeInterfaces(): string[] {
this.release();
try {
const interfaces = new Map<string, string[]>();
for (const deviceInterface of this.storageSettings.values.deviceInterfaces as string[]) {
const parts = deviceInterface.split('#');
const id = parts[0];
const iface = parts[1];
if (!interfaces.has(iface))
interfaces.set(iface, []);
interfaces.get(iface).push(id);
}
for (const [iface, ids] of interfaces.entries()) {
const devices = ids.map(id => systemManager.getDeviceById(id));
const runAggregator = this.makeListener(iface, devices);
runAggregator?.();
}
for (const [iface, ids] of interfaces.entries()) {
const devices = ids.map(id => systemManager.getDeviceById(id));
const descriptor = ScryptedInterfaceDescriptors[iface];
if (!descriptor) {
this.console.warn(`descriptor not found for ${iface}, skipping method generation`);
continue;
}
if (iface === ScryptedInterface.VideoCamera) {
const camera = createVideoCamera(devices as any, this.console);
for (const method of descriptor.methods) {
this[method] = (...args: any[]) => camera[method](...args);
}
continue;
}
for (const method of descriptor.methods) {
this[method] = async function (...args: any[]) {
const ret: Promise<any>[] = [];
for (const device of devices) {
ret.push(device[method](...args));
}
const results = await Promise.all(ret);
return results[0];
}
}
}
return [...interfaces.keys()];
}
catch (e) {
// this.console.error('error loading aggregate device', e);
return [];
}
}
}

160
plugins/core/src/cluster.ts Normal file
View File

@@ -0,0 +1,160 @@
import { createAsyncQueue } from "@scrypted/common/src/async-queue";
import sdk, { Readme, ScryptedDeviceBase, ScryptedInterface, ScryptedSettings, Setting, Settings } from "@scrypted/sdk";
export const ClusterCoreNativeId = 'clustercore';
export class ClusterCore extends ScryptedDeviceBase implements Settings, Readme, ScryptedSettings {
writeQueue = createAsyncQueue<() => Promise<void>>();
constructor(nativeId: string) {
super(nativeId);
(async () => {
for await (const write of this.writeQueue.queue) {
try {
await write();
}
catch (e) {
this.console.error('error writing settings', e);
}
finally {
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
}
}
})();
}
async getSettings(): Promise<Setting[]> {
const mode = sdk.clusterManager?.getClusterMode?.();
if (!mode)
return [];
const workers = await sdk.clusterManager.getClusterWorkers();
const ret: Setting[] = [];
const clientWorkers = Object.values(workers);
const clusterFork = await sdk.systemManager.getComponent('cluster-fork');
for (const worker of clientWorkers) {
const group = `Worker: ${worker.name}`;
const name: Setting = {
key: `${worker.id}:name`,
group,
title: 'Name',
description: 'The friendly name of the worker.',
value: worker.name,
};
ret.push(name);
const mode: Setting = {
key: `${worker.id}:mode`,
group,
title: 'Mode',
description: 'The mode of the worker.',
value: worker.mode,
readonly: true,
};
ret.push(mode);
const envControl = await clusterFork.getEnvControl(worker.id);
// catch in case env is coming from vscode launch.json and no .env actually exists.
const dotEnv: string = await envControl.getDotEnv().catch(() => {});
const dotEnvLines = dotEnv?.split('\n') || worker.labels;
const dotEnvParsed = dotEnvLines.map(line => {
const trimmed = line.trim();
if (trimmed.startsWith('#')) {
return { line };
}
const [key, ...value] = trimmed.split('=');
return { key, value: value.join('='), line };
});
const workerLabels = dotEnvParsed.find(line => line.key === 'SCRYPTED_CLUSTER_LABELS')?.value?.split(',') || [];
const labelChoices = new Set<string>([
...workerLabels,
'storage',
'compute',
'compute.preferred',
'@scrypted/coreml',
'@scrypted/openvino',
'@scrypted/onnx',
'@scrypted/tensorflow-lite',
]);
const labels: Setting = {
key: `${worker.id}:labels`,
group,
title: 'Labels',
description: 'The labels to apply to this worker. Modifying the labels will restart the worker. Some labels, such as the host OS and architecture, cannot be changed.',
multiple: true,
combobox: true,
choices: [...labelChoices],
value: workerLabels,
};
ret.push(labels);
}
return ret;
}
async putSetting(key: string, value: any) {
await this.writeQueue.enqueue(async () => {
const split = key.split(':');
const [workerId, setting] = split;
const workers = await sdk.clusterManager.getClusterWorkers();
const worker = workers[workerId];
if (!worker)
return;
switch (setting) {
case 'name':
case 'labels':
break;
default:
return;
}
const clusterFork = await sdk.systemManager.getComponent('cluster-fork');
const envControl = await clusterFork.getEnvControl(worker.id);
const dotEnv: string = await envControl.getDotEnv().catch(() => {});
const dotEnvLines = dotEnv?.split('\n') || worker.labels;
const dotEnvParsed = dotEnvLines.map(line => {
const trimmed = line.trim();
if (trimmed.startsWith('#')) {
return { line };
}
const [key, ...value] = trimmed.split('=');
return { key, value: value.join('='), line };
});
const updateDotEnv = async (key: string, newValue: string) => {
let entry = dotEnvParsed.find(line => line.key === key);
if (!entry) {
entry = { key, value: '', line: '' };
dotEnvParsed.push(entry);
}
entry.line = `${key}=${newValue}`;
await envControl.setDotEnv(dotEnvParsed.filter(line => line).map(line => line.line).join('\n'));
};
if (setting === 'labels') {
await updateDotEnv('SCRYPTED_CLUSTER_LABELS', value.join(','));
} else if (setting === 'name') {
await updateDotEnv('SCRYPTED_CLUSTER_WORKER_NAME', value);
}
setTimeout(async () => {
const serviceControl = await clusterFork.getServiceControl(worker.id);
await serviceControl.restart().catch(() => { });
}, 10000);
});
}
async getReadmeMarkdown(): Promise<string> {
return `Manage Scrypted's cluster mode. Run storage devices and compute services on separate servers.`;
}
}

View File

@@ -1,6 +1,5 @@
import sdk, { BufferConverter, HttpRequest, HttpRequestHandler, HttpResponse, HttpResponseOptions, MediaObject, RequestMediaObject, ScryptedDeviceBase, ScryptedMimeTypes } from "@scrypted/sdk";
import crypto from 'crypto';
import mime from "mime/lite";
import path from 'path';
const { endpointManager } = sdk;
@@ -47,18 +46,21 @@ export class BufferHost extends ScryptedDeviceBase implements HttpRequestHandler
options.headers['Content-Disposition'] = 'attachment';
}
response.send(file.data as Buffer, );
response.send(file.data as Buffer,);
}
async convert(buffer: string, fromMimeType: string, toMimeType: string): Promise<Buffer> {
const uuid = uuidv4();
const { default: mime } = await import('mime');
const endpoint = await (this.secure ? endpointManager.getPublicLocalEndpoint(this.nativeId) : endpointManager.getInsecurePublicLocalEndpoint(this.nativeId));
const extension = mime.getExtension(fromMimeType);
const filename = uuid + (extension ? `.${extension}` : '');
this.hosted.set(`/${filename}`, { data: buffer, fromMimeType, toMimeType });
setTimeout(() => this.hosted.delete(`/${filename}`), 10 * 60 * 1000); // free this resource after 10 min.
return Buffer.from(`${endpoint}${filename}`);
}
@@ -131,6 +133,7 @@ export class RequestMediaObjectHost extends ScryptedDeviceBase implements HttpRe
const endpoint = await (toMimeType === ScryptedMimeTypes.LocalUrl ? endpointManager.getPublicLocalEndpoint(this.nativeId) : endpointManager.getInsecurePublicLocalEndpoint(this.nativeId));
const data = await request();
fromMimeType = data.mimeType;
const { default: mime } = await import('mime');
const extension = mime.getExtension(fromMimeType);
const filename = uuid + (extension ? `.${extension}` : '');

View File

@@ -1,17 +1,21 @@
import { readFileAsString, tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
import sdk, { DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from '@scrypted/sdk';
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import { writeFileSync } from 'fs';
import path from 'path';
import Router from 'router';
import yaml from 'yaml';
import { getUsableNetworkAddresses } from '../../../server/src/ip';
import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
import { LauncherMixin } from './launcher-mixin';
import { MediaCore } from './media-core';
import { checkLxcDependencies } from './platform/lxc';
import { checkLegacyLxc, checkLxc } from './platform/lxc';
import { ConsoleServiceNativeId, PluginSocketService, ReplServiceNativeId } from './plugin-socket-service';
import { ScriptCore, ScriptCoreNativeId, newScript } from './script-core';
import { TerminalService, TerminalServiceNativeId } from './terminal-service';
import { UsersCore, UsersNativeId } from './user';
import { ClusterCore, ClusterCoreNativeId } from './cluster';
const { deviceManager, endpointManager } = sdk;
@@ -24,6 +28,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
publicRouter: any = Router();
mediaCore: MediaCore;
scriptCore: ScriptCore;
clusterCore: ClusterCore;
aggregateCore: AggregateCore;
automationCore: AutomationCore;
users: UsersCore;
@@ -48,7 +53,41 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
const service = await sdk.systemManager.getComponent('addresses');
service.setLocalAddresses(this.localAddresses);
},
}
},
releaseChannel: {
group: 'Advanced',
title: 'Server Release Channel',
description: 'The release channel to use for server updates. A specific version or tag can be manually entered as well. Changing this setting will update the image field in /root/.scrypted/docker-compose.yml. Invalid values may prevent the server from properly starting.',
defaultValue: 'Default',
choices: [
'Default',
'latest',
'beta',
`v${sdk.serverVersion}-jammy-full`,
],
combobox: true,
onPut: (ov, nv) => {
this.updateReleaseChannel(nv);
},
mapGet: () => {
try {
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
// @ts-ignore
const image: string = dockerCompose.contents.get('services').get('scrypted').get('image');
const label = image.split(':')[1] || undefined;
return label || 'Default';
}
catch (e) {
return 'Default';
}
}
},
pullImage: {
hide: true,
onPut: () => {
this.setPullImage();
}
},
});
indexHtml: string;
@@ -59,10 +98,23 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
settings: "General",
}
checkLxcDependencies();
checkLegacyLxc();
checkLxc();
this.storageSettings.settings.releaseChannel.hide = process.env.SCRYPTED_INSTALL_ENVIRONMENT !== 'lxc-docker';
this.indexHtml = readFileAsString('dist/index.html');
(async () => {
await deviceManager.onDeviceDiscovered(
{
name: 'Cluster',
nativeId: ClusterCoreNativeId,
interfaces: [ScryptedInterface.Settings, ScryptedInterface.Readme, ScryptedInterface.ScryptedSettings],
type: ScryptedDeviceType.Builtin,
},
);
})();
(async () => {
await deviceManager.onDeviceDiscovered(
{
@@ -175,6 +227,8 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
}
async getDevice(nativeId: string) {
if (nativeId === ClusterCoreNativeId)
return this.clusterCore ||= new ClusterCore(ClusterCoreNativeId);
if (nativeId === 'launcher')
return new LauncherMixin('launcher');
if (nativeId === 'mediacore')
@@ -248,6 +302,27 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
});
}
}
setPullImage() {
writeFileSync(path.join(process.env.SCRYPTED_VOLUME, '.pull'), '');
}
async updateReleaseChannel(releaseChannel: string) {
if (!releaseChannel || releaseChannel === 'Default')
releaseChannel = '';
else
releaseChannel = `:${releaseChannel}`;
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
// @ts-ignore
dockerCompose.contents.get('services').get('scrypted').set('image', `ghcr.io/koush/scrypted${releaseChannel}`);
yaml.stringify(dockerCompose);
writeFileSync('/root/.scrypted/docker-compose.yml', yaml.stringify(dockerCompose));
this.setPullImage();
const serviceControl = await sdk.systemManager.getComponent("service-control");
await serviceControl.exit().catch(() => { });
await serviceControl.restart();
}
}
export default ScryptedCore;

View File

@@ -1,102 +1,35 @@
import fs from 'fs';
import child_process from 'child_process';
import { once } from 'events';
import sdk from '@scrypted/sdk';
import { stdout } from 'process';
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC = 'lxc';
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC_DOCKER = 'lxc-docker';
export async function checkLxcDependencies() {
export async function checkLegacyLxc() {
if (process.env.SCRYPTED_INSTALL_ENVIRONMENT !== SCRYPTED_INSTALL_ENVIRONMENT_LXC)
return;
let needRestart = false;
if (!process.version.startsWith('v20.')) {
const cp = child_process.spawn('sh', ['-c', 'apt update -y && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt install -y nodejs']);
const [exitCode] = await once(cp, 'exit');
if (exitCode !== 0)
sdk.log.a('Failed to install Node.js 20.x.');
else
needRestart = true;
}
if (!fs.existsSync('/var/run/avahi-daemon/socket')) {
const cp = child_process.spawn('sh', ['-c', 'apt update -y && apt install -y avahi-daemon && apt upgrade -y']);
const [exitCode] = await once(cp, 'exit');
if (exitCode !== 0)
sdk.log.a('Failed to install avahi-daemon.');
else
needRestart = true;
}
const scryptedService = fs.readFileSync('lxc/scrypted.service').toString();
const installedScryptedService = fs.readFileSync('/etc/systemd/system/scrypted.service').toString();
if (installedScryptedService !== scryptedService) {
fs.writeFileSync('/etc/systemd/system/scrypted.service', scryptedService);
needRestart = true;
const cp = child_process.spawn('systemctl', ['daemon-reload']);
const [exitCode] = await once(cp, 'exit');
if (exitCode !== 0)
sdk.log.a('Failed to daemon-reload systemd.');
}
try {
// intel opencl icd is broken from their official apt repos on kernel versions 6.8, which ships with ubuntu 24.04 and proxmox 8.2.
// the intel apt repo has not been updated yet.
// the current workaround is to install the release manually.
// https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
if (err && !stdout && !stderr)
f(err);
else
r(stdout + '\n' + stderr);
}));
if (
// apt
output.includes('Version: 23')
// was installed via script at some point
|| output.includes('Version: 24.13.29138.7')
// current script version: 24.17.29377.6
) {
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash']);
const [exitCode] = await once(cp, 'exit');
if (exitCode !== 0)
sdk.log.a('Failed to install intel-opencl-icd.');
else
needRestart = true;
}
}
catch (e) {
sdk.log.a('Failed to verify/install intel-opencl-icd version.');
}
try {
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-driver-compiler-npu'", (err, stdout, stderr) => {
if (err && !stdout && !stderr)
f(err);
else
r(stdout + '\n' + stderr);
}));
if (
// apt
output.includes('No packages found')
) {
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash']);
const [exitCode] = await once(cp, 'exit');
if (exitCode !== 0)
sdk.log.a('Failed to install intel-driver-compiler-npu.');
else
needRestart = true;
}
}
catch (e) {
sdk.log.a('Failed to verify/install intel-driver-compiler-npu.');
}
if (needRestart)
sdk.log.a('A system update is pending. Please restart Scrypted to apply changes.');
sdk.log.a('This system is currently running the legacy LXC installation method and must be migrated to the new LXC manually: https://docs.scrypted.app/installation.html#proxmox-ve-container-reset');
}
const DOCKER_COMPOSE_SH_PATH = '/root/.scrypted/docker-compose.sh';
const LXC_DOCKER_COMPOSE_SH_PATH = 'lxc/docker-compose.sh';
export async function checkLxc() {
if (process.env.SCRYPTED_INSTALL_ENVIRONMENT !== SCRYPTED_INSTALL_ENVIRONMENT_LXC_DOCKER)
return;
const foundDockerComposeSh = await fs.promises.readFile(DOCKER_COMPOSE_SH_PATH, 'utf8');
const dockerComposeSh = await fs.promises.readFile(LXC_DOCKER_COMPOSE_SH_PATH, 'utf8');
if (foundDockerComposeSh === dockerComposeSh) {
// check if the file is executable
const stats = await fs.promises.stat(DOCKER_COMPOSE_SH_PATH);
if (stats.mode & 0o111)
return;
await fs.promises.chmod(DOCKER_COMPOSE_SH_PATH, 0o755);
return;
}
await fs.promises.copyFile(LXC_DOCKER_COMPOSE_SH_PATH, DOCKER_COMPOSE_SH_PATH);
await fs.promises.chmod(DOCKER_COMPOSE_SH_PATH, 0o755);
}

View File

@@ -17,9 +17,13 @@ export class PluginSocketService extends ScryptedDeviceBase implements StreamSer
throw new Error('must provide pluginId');
const plugins = await sdk.systemManager.getComponent('plugins');
const replPort: number = await plugins.getRemoteServicePort(pluginId, this.serviceName);
const servicePort = await plugins.getRemoteServicePort(pluginId, this.serviceName) as number | [number, string];
const [port, host] = Array.isArray(servicePort) ? servicePort : [servicePort, undefined];
const socket = net.connect(replPort);
const socket = net.connect({
port,
host,
});
await once(socket, 'connect');
const queue = createAsyncQueue<Buffer>();

View File

@@ -206,14 +206,7 @@ export class TerminalService extends ScryptedDeviceBase implements StreamService
if (parsed.interactive) {
let spawn: typeof ptySpawn;
try {
try {
spawn = require('node-pty-prebuilt-multiarch').spawn as typeof ptySpawn;
if (!spawn)
throw new Error();
}
catch (e) {
spawn = require('@scrypted/node-pty').spawn as typeof ptySpawn;
}
spawn = require('@scrypted/node-pty').spawn as typeof ptySpawn;
cp = new InteractiveTerminal(cmd, extraPaths, spawn);
}
catch (e) {

View File

@@ -1,6 +1,6 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-nvr",
"python.analysis.extraPaths": [
"./node_modules/@scrypted/sdk/types/scrypted_python"
]

View File

@@ -1,36 +1,35 @@
{
"name": "@scrypted/coreml",
"version": "0.1.70",
"version": "0.1.76",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/coreml",
"version": "0.1.70",
"version": "0.1.76",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.31",
"version": "0.3.77",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -42,11 +41,11 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"ts-node": "^10.9.2",
"typedoc": "^0.26.10"
}
},
"../sdk": {
@@ -61,25 +60,24 @@
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"rimraf": "^6.0.1",
"stringify-object": "^3.3.0",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2"
}
}
}

View File

@@ -35,12 +35,18 @@
"interfaces": [
"Settings",
"DeviceProvider",
"ClusterForkInterface",
"ObjectDetection",
"ObjectDetectionPreview"
]
],
"labels": {
"require": [
"@scrypted/coreml"
]
}
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.70"
"version": "0.1.76"
}

View File

@@ -69,9 +69,13 @@ def parse_labels(userDefined):
return parse_label_contents(classes)
class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProvider):
def __init__(self, nativeId: str | None = None):
super().__init__(nativeId=nativeId)
class CoreMLPlugin(
PredictPlugin,
scrypted_sdk.Settings,
scrypted_sdk.DeviceProvider,
):
def __init__(self, nativeId: str | None = None, forked: bool = False):
super().__init__(nativeId=nativeId, forked=forked)
model = self.storage.getItem("model") or "Default"
if model == "Default" or model not in availableModels:
@@ -139,7 +143,9 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
self.faceDevice = None
self.textDevice = None
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
if not self.forked:
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
async def prepareRecognitionModels(self):
try:
@@ -148,6 +154,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
"nativeId": "facerecognition",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
"interfaces": [
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
],
"name": "CoreML Face Recognition",
@@ -160,6 +167,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
"nativeId": "textrecognition",
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
"interfaces": [
scrypted_sdk.ScryptedInterface.ClusterForkInterface.value,
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
],
"name": "CoreML Text Recognition",
@@ -176,10 +184,10 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
async def getDevice(self, nativeId: str) -> Any:
if nativeId == "facerecognition":
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(nativeId)
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(self, nativeId)
return self.faceDevice
if nativeId == "textrecognition":
self.textDevice = self.textDevice or CoreMLTextRecognition(nativeId)
self.textDevice = self.textDevice or CoreMLTextRecognition(self, nativeId)
return self.textDevice
raise Exception("unknown device")
@@ -227,7 +235,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
return ret
if self.scrypted_yolo_nas:
predictions = list(out_dict.values())
predictions = list(out_dict.values())
objs = yolo.parse_yolo_nas(predictions)
ret = self.create_detection_result(objs, src_size, cvss)
return ret
@@ -250,13 +258,13 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
for r in objects:
obj = Prediction(
r["classId"].astype(float),
r["confidence"].astype(float),
r["classId"],
r["confidence"],
Rectangle(
r["xmin"].astype(float),
r["ymin"].astype(float),
r["xmax"].astype(float),
r["ymax"].astype(float),
r["xmin"],
r["ymin"],
r["xmax"],
r["ymax"],
),
)
objs.append(obj)
@@ -275,9 +283,9 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
),
)
coordinatesList = out_dict["coordinates"].astype(float)
coordinatesList = out_dict["coordinates"]
for index, confidenceList in enumerate(out_dict["confidence"].astype(float)):
for index, confidenceList in enumerate(out_dict["confidence"]):
values = confidenceList
maxConfidenceIndex = max(range(len(values)), key=values.__getitem__)
maxConfidence = confidenceList[maxConfidenceIndex]

View File

@@ -29,8 +29,8 @@ def cosine_similarity(vector_a, vector_b):
predictExecutor = concurrent.futures.ThreadPoolExecutor(8, "Vision-Predict")
class CoreMLFaceRecognition(FaceRecognizeDetection):
def __init__(self, nativeId: str | None = None):
super().__init__(nativeId=nativeId)
def __init__(self, plugin, nativeId: str):
super().__init__(plugin, nativeId)
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-face")
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-face")

View File

@@ -13,8 +13,8 @@ from predict.text_recognize import TextRecognition
class CoreMLTextRecognition(TextRecognition):
def __init__(self, nativeId: str | None = None):
super().__init__(nativeId=nativeId)
def __init__(self, plugin, nativeId: str):
super().__init__(plugin, nativeId)
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-text")
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-text")

View File

@@ -1,4 +1,8 @@
from coreml import CoreMLPlugin
import predict
def create_scrypted_plugin():
return CoreMLPlugin()
async def fork():
return predict.Fork(CoreMLPlugin)

View File

@@ -1,5 +1,3 @@
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
numpy==1.26.4
coremltools==7.2
coremltools==8.0
Pillow==10.3.0
opencv-python==4.10.0.84
opencv-python-headless==4.10.0.84

5
plugins/diagnostics/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
out/
node_modules/
dist/
.venv

View File

@@ -0,0 +1,9 @@
.DS_Store
out/
node_modules/
*.map
fs
src
.vscode
dist/*.js
.venv

23
plugins/diagnostics/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Scrypted Debugger",
"address": "${config:scrypted.debugHost}",
"port": 10081,
"request": "attach",
"skipFiles": [
"**/plugin-remote-worker.*",
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "node"
}
]
}

View File

@@ -0,0 +1,4 @@
{
"scrypted.debugHost": "koushik-winvm",
}

20
plugins/diagnostics/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "scrypted: deploy+debug",
"type": "shell",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
},
]
}

View File

@@ -0,0 +1,3 @@
# Scrypted Diagnostics Plugin
This plugin can be used to run diagnostics on the system and supported devices. The results from the diagnostics can be seen in the `Log`.

848
plugins/diagnostics/package-lock.json generated Normal file
View File

@@ -0,0 +1,848 @@
{
"name": "@scrypted/diagnostics",
"version": "0.0.19",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/diagnostics",
"version": "0.0.19",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"sharp": "^0.33.5"
},
"devDependencies": {
"@types/node": "^22.5.4"
}
},
"../../common": {
"name": "@scrypted/common",
"version": "1.0.1",
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"http-auth-utils": "^5.0.1",
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/node": "^20.11.0",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.69",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.26.0",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
"scrypted-debug": "bin/scrypted-debug.js",
"scrypted-deploy": "bin/scrypted-deploy.js",
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
"scrypted-package-json": "bin/scrypted-package-json.js",
"scrypted-setup-project": "bin/scrypted-setup-project.js",
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"stringify-object": "^3.3.0",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"cpu": [
"wasm32"
],
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.2.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
},
"node_modules/@scrypted/sdk": {
"resolved": "../../sdk",
"link": true
},
"node_modules/@types/node": {
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dev": true,
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.3",
"semver": "^7.6.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.5",
"@img/sharp-darwin-x64": "0.33.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4",
"@img/sharp-libvips-darwin-x64": "1.0.4",
"@img/sharp-libvips-linux-arm": "1.0.5",
"@img/sharp-libvips-linux-arm64": "1.0.4",
"@img/sharp-libvips-linux-s390x": "1.0.4",
"@img/sharp-libvips-linux-x64": "1.0.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
"@img/sharp-linux-arm": "0.33.5",
"@img/sharp-linux-arm64": "0.33.5",
"@img/sharp-linux-s390x": "0.33.5",
"@img/sharp-linux-x64": "0.33.5",
"@img/sharp-linuxmusl-arm64": "0.33.5",
"@img/sharp-linuxmusl-x64": "0.33.5",
"@img/sharp-wasm32": "0.33.5",
"@img/sharp-win32-ia32": "0.33.5",
"@img/sharp-win32-x64": "0.33.5"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"optional": true
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
}
},
"dependencies": {
"@emnapi/runtime": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
"optional": true,
"requires": {
"tslib": "^2.4.0"
}
},
"@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"optional": true,
"requires": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"optional": true,
"requires": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"optional": true
},
"@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"optional": true
},
"@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"optional": true
},
"@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"optional": true
},
"@img/sharp-libvips-linux-s390x": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"optional": true
},
"@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"optional": true
},
"@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"optional": true
},
"@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"optional": true
},
"@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"optional": true,
"requires": {
"@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"optional": true,
"requires": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"@img/sharp-linux-s390x": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"optional": true,
"requires": {
"@img/sharp-libvips-linux-s390x": "1.0.4"
}
},
"@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"optional": true,
"requires": {
"@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"@img/sharp-linuxmusl-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"optional": true,
"requires": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
}
},
"@img/sharp-linuxmusl-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"optional": true,
"requires": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
}
},
"@img/sharp-wasm32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"optional": true,
"requires": {
"@emnapi/runtime": "^1.2.0"
}
},
"@img/sharp-win32-ia32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"optional": true
},
"@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"optional": true
},
"@scrypted/common": {
"version": "file:../../common",
"requires": {
"@scrypted/sdk": "file:../sdk",
"@types/node": "^20.11.0",
"http-auth-utils": "^5.0.1",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
}
},
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.26.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"stringify-object": "^3.3.0",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2"
}
},
"@types/node": {
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dev": true,
"requires": {
"undici-types": "~6.19.2"
}
},
"color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"requires": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
},
"is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
},
"sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"requires": {
"@img/sharp-darwin-arm64": "0.33.5",
"@img/sharp-darwin-x64": "0.33.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4",
"@img/sharp-libvips-darwin-x64": "1.0.4",
"@img/sharp-libvips-linux-arm": "1.0.5",
"@img/sharp-libvips-linux-arm64": "1.0.4",
"@img/sharp-libvips-linux-s390x": "1.0.4",
"@img/sharp-libvips-linux-x64": "1.0.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
"@img/sharp-linux-arm": "0.33.5",
"@img/sharp-linux-arm64": "0.33.5",
"@img/sharp-linux-s390x": "0.33.5",
"@img/sharp-linux-x64": "0.33.5",
"@img/sharp-linuxmusl-arm64": "0.33.5",
"@img/sharp-linuxmusl-x64": "0.33.5",
"@img/sharp-wasm32": "0.33.5",
"@img/sharp-win32-ia32": "0.33.5",
"@img/sharp-win32-x64": "0.33.5",
"color": "^4.2.3",
"detect-libc": "^2.0.3",
"semver": "^7.6.3"
}
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"requires": {
"is-arrayish": "^0.3.1"
}
},
"tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"optional": true
},
"undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
}
}
}

View File

@@ -0,0 +1,37 @@
{
"name": "@scrypted/diagnostics",
"version": "0.0.19",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
"build": "scrypted-webpack",
"prepublishOnly": "NODE_ENV=production scrypted-webpack",
"prescrypted-vscode-launch": "scrypted-webpack",
"scrypted-vscode-launch": "scrypted-deploy-debug",
"scrypted-deploy-debug": "scrypted-deploy-debug",
"scrypted-debug": "scrypted-debug",
"scrypted-deploy": "scrypted-deploy",
"scrypted-readme": "scrypted-readme",
"scrypted-package-json": "scrypted-package-json"
},
"keywords": [
"scrypted",
"plugin",
"diagnostics"
],
"scrypted": {
"name": "Diagnostics",
"type": "API",
"interfaces": [
"Settings"
]
},
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"sharp": "^0.33.5"
},
"devDependencies": {
"@types/node": "^22.5.4"
}
}

View File

@@ -0,0 +1,565 @@
import { Deferred } from '@scrypted/common/src/deferred';
import { safeKillFFmpeg } from '@scrypted/common/src/media-helpers';
import sdk, { Camera, FFmpegInput, Image, MediaObject, MediaStreamDestination, MotionSensor, Notifier, ObjectDetection, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, VideoCamera } from '@scrypted/sdk';
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import child_process from 'child_process';
import { once } from 'events';
import fs from 'fs';
import net from 'net';
import os from 'os';
import sharp from 'sharp';
import { httpFetch } from '../../../server/src/fetch/http-fetch';
class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings {
storageSettings = new StorageSettings(this, {
testDevice: {
group: 'Device',
title: 'Validation Device',
description: "Select a device to validate.",
type: 'device',
deviceFilter: `type === '${ScryptedDeviceType.Camera}' || type === '${ScryptedDeviceType.Doorbell}' || type === '${ScryptedDeviceType.Notifier}'`,
immediate: true,
},
validateDevice: {
console: true,
group: 'Device',
title: 'Validate Device',
description: 'Validate the device configuration.',
type: 'button',
onPut: async () => {
this.validateDevice();
},
},
validateSystem: {
console: true,
group: 'System',
title: 'Validate System',
description: 'Validate the system configuration.',
type: 'button',
onPut: () => this.validateSystem(),
},
});
loggedMotion = new Map<string, number>();
loggedButton = new Map<string, number>();
constructor(nativeId?: string) {
super(nativeId);
this.on = this.on || false;
sdk.systemManager.listen((eventSource, eventDetails, eventData) => {
if (!eventData || !eventSource?.id)
return;
if (eventDetails.eventInterface === ScryptedInterface.MotionSensor) {
this.loggedMotion.set(eventSource.id, Date.now());
return;
}
if (eventDetails.eventInterface === ScryptedInterface.BinarySensor) {
this.loggedButton.set(eventSource.id, Date.now());
return;
}
});
}
getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
}
async putSetting(key: string, value: any) {
await this.storageSettings.putSetting(key, value);
}
warnStep(console: Console, result: string) {
console.log(''.padEnd(24), `\x1b[33m${result}\x1b[0m`);
}
async validate(console: Console, stepName: string, step: Promise<any> | (() => Promise<any>)) {
try {
if (step instanceof Function)
step = step();
console.log(stepName.padEnd(24), '\x1b[34mRunning\x1b[0m');
const result = await step;
console.log(''.padEnd(24), `\x1b[32m${result || 'OK'}\x1b[0m`);
}
catch (e) {
console.error(stepName.padEnd(24), '\x1b[31m Failed\x1b[0m'.padEnd(24), (e as Error).message);
}
}
async validateDevice() {
const device = this.storageSettings.values.testDevice as ScryptedDevice & any;
const console = sdk.deviceManager.getMixinConsole(device.id);
console.log(''.padEnd(44, '='));
console.log(`Device Validation: ${device?.name}`);
console.log(''.padEnd(44, '='));
await this.validate(console, 'Device Selected', async () => {
if (!device)
throw new Error('Select a device in the Settings UI.');
});
if (!device)
return;
if (device.type === ScryptedDeviceType.Camera || device.type === ScryptedDeviceType.Doorbell) {
await this.validateCamera(device);
}
else if (device.type === ScryptedDeviceType.Notifier) {
await this.validateNotifier(device);
}
console.log(''.padEnd(44, '='));
console.log(`Device Validation Complete: ${device?.name}`);
console.log(''.padEnd(44, '='));
}
async validateNotifier(device: ScryptedDevice & Notifier) {
const console = sdk.deviceManager.getMixinConsole(device.id);
await this.validate(console, 'Test Notification', async () => {
const logo = await httpFetch({
url: 'https://home.scrypted.app/_punch/web_hi_res_512.png',
responseType: 'buffer',
});
const mo = await sdk.mediaManager.createMediaObject(logo.body, 'image/png');
await device.sendNotification('Scrypted Diagnostics', {
body: 'Body',
subtitle: 'Subtitle',
android: {
channel: 'diagnostics',
}
}, mo);
this.warnStep(console, 'Check the device for the notification.');
});
}
async validateCamera(device: ScryptedDevice & Camera & VideoCamera & MotionSensor) {
const console = sdk.deviceManager.getMixinConsole(device.id);
await this.validate(console, 'Device Capabilities', async () => {
if (!device.interfaces.includes(ScryptedInterface.MotionSensor))
throw new Error('Motion Sensor not found.');
if (device.type === ScryptedDeviceType.Doorbell && !device.interfaces.includes(ScryptedInterface.BinarySensor))
throw new Error('Doorbell button not found.');
});
await this.validate(console, 'Motion Detection', async () => {
if (!device.interfaces.includes(ScryptedInterface.MotionSensor))
throw new Error('Motion Sensor not found. Enabling a software motion sensor extension is recommended.');
if (device.providedInterfaces.includes(ScryptedInterface.MotionSensor)) {
if (device.interfaces.find(i => i.startsWith('ObjectDetection:true')))
this.warnStep(console, 'Camera hardware provides motion events, but a software motion detector is enabled. Consider disabling the software motion detector.');
}
});
if (device.interfaces.includes(ScryptedInterface.MotionSensor)) {
await this.validate(console, 'Recent Motion', async () => {
const lastMotion = this.loggedMotion.get(device.id);
if (!lastMotion)
throw new Error('No recent motion detected. Go wave your hand in front of the camera.');
if (Date.now() - lastMotion > 8 * 60 * 60 * 1000)
throw new Error('Last motion was over 8 hours ago.');
});
}
if (device.type === ScryptedDeviceType.Doorbell) {
await this.validate(console, 'Recent Button Press', async () => {
const lastButton = this.loggedButton.get(device.id);
if (!lastButton)
throw new Error('No recent button press detected. Go press the doorbell button.');
if (Date.now() - lastButton > 8 * 60 * 60 * 1000)
throw new Error('Last button press was over 8 hours ago.');
});
}
const validateMedia = async (stepName: string, mo: Promise<MediaObject>, snapshot = false, and?: () => Promise<void>) => {
await this.validate(console, stepName, async () => {
if (snapshot && !device.interfaces.includes(ScryptedInterface.Camera))
throw new Error('Snapshot not supported. Enable the Snapshot extension.');
const jpeg = await sdk.mediaManager.convertMediaObjectToBuffer(await mo, 'image/jpeg');
const metadata = await sharp(jpeg).metadata();
if (!metadata.width || !metadata.height || metadata.width < 100 || metadata.height < 100)
throw new Error('Malformed image.');
if (!and && snapshot && device.pluginId === '@scrypted/unifi-protect' && metadata.width < 1280)
this.warnStep(console, 'Unifi Protect provides low quality snapshots. Consider using Snapshot from Prebuffer for full resolution screenshots.');
await and?.();
});
};
await validateMedia('Snapshot', device.takePicture({
reason: 'event',
}), true);
await this.validate(console, 'Streams', async () => {
const vsos = await device.getVideoStreamOptions();
if (!vsos?.length)
throw new Error('Stream configuration invalid.');
if (vsos.length < 3)
this.warnStep(console, `Camera has ${vsos.length} substream. Three streams are recommended.`);
const cloudStreams = vsos.filter(vso => vso.source === 'cloud');
if (cloudStreams.length)
this.warnStep(console, `Cloud camera. Upgrade recommended.`);
const usedStreams = vsos.filter(vso => vso.destinations?.length);
if (usedStreams.length < Math.min(3, vsos.length))
this.warnStep(console, `Unused streams detected.`);
});
const getVideoStream = async (destination: MediaStreamDestination) => {
if (!device.interfaces.includes(ScryptedInterface.VideoCamera))
throw new Error('Streaming not supported.');
return await device.getVideoStream({
destination,
prebuffer: 0,
});
};
const validated = new Set<string | undefined>();
const validateMediaStream = async (stepName: string, destination: MediaStreamDestination) => {
const vsos = await device.getVideoStreamOptions();
const streamId = vsos.find(vso => vso.destinations?.includes(destination))?.id;
if (validated.has(streamId)) {
await this.validate(console, stepName, async () => "Skipped (Duplicate)");
return;
}
validated.add(streamId);
const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(await getVideoStream(destination), ScryptedMimeTypes.FFmpegInput);
if (ffmpegInput.mediaStreamOptions?.video?.codec !== 'h264')
this.warnStep(console, `Stream ${stepName} is using codec ${ffmpegInput.mediaStreamOptions?.video?.codec}. h264 is recommended.`);
await validateMedia(stepName, getVideoStream(destination));
const start = Date.now();
await validateMedia(stepName + ' (IDR)', getVideoStream(destination), false, async () => {
const end = Date.now();
if (end - start > 5000)
throw new Error(`High IDR Interval. This may cause issues with HomeKit Secure Video. Adjust codec configuration if possible.`);
});
};
await validateMediaStream('Local', 'local');
await validateMediaStream('Local Recorder', 'local-recorder');
await validateMediaStream('Remote Recorder', 'remote-recorder');
await validateMediaStream('Remote', 'remote');
await validateMediaStream('Low Resolution', 'low-resolution');
await this.validate(console, 'Audio Codecs', async () => {
const vsos = await device.getVideoStreamOptions();
let codec: string | undefined;
const codecs = new Set<string>();
for (const vso of vsos) {
if (vso.audio?.codec) {
codec = vso.audio.codec;
codecs.add(vso.audio.codec);
}
}
if (codecs.size > 1) {
this.warnStep(console, `Mismatched audio codecs detected.`);
return;
}
if (!codec)
return;
if (codec !== 'pcm_mulaw' && codec !== 'aac' && codec !== 'opus') {
this.warnStep(console, `Audio codec is ${codec}. pcm_mulaw, aac, or opus is recommended.`);
return;
}
});
}
async validateSystem() {
this.console.log(''.padEnd(44, '='));
this.console.log('System Validation');
this.console.log(''.padEnd(44, '='));
const nvrPlugin = sdk.systemManager.getDeviceById('@scrypted/nvr');
const cloudPlugin = sdk.systemManager.getDeviceById('@scrypted/cloud');
const hasCUDA = process.env.NVIDIA_VISIBLE_DEVICES && process.env.NVIDIA_DRIVER_CAPABILITIES;
const onnxPlugin = sdk.systemManager.getDeviceById<Settings & ObjectDetection>('@scrypted/onnx');
const openvinoPlugin = sdk.systemManager.getDeviceById<Settings & ObjectDetection>('@scrypted/openvino');
await this.validate(this.console, 'Scrypted Installation', async () => {
const e = process.env.SCRYPTED_INSTALL_ENVIRONMENT;
if (process.platform !== 'linux') {
if (e !== 'electron')
this.warnStep(this.console, 'Upgrading to the Scrypted Desktop application is recommened for Windows and macOS.');
return;
}
if (e !== 'docker' && e !== 'lxc' && e !== 'ha' && e !== 'lxc-docker')
throw new Error('Unrecognized Linux installation. Installation via Docker image or the official Proxmox LXC script (not tteck) is recommended: https://docs.scrypted.app/installation');
});
await this.validate(this.console, 'IPv4 (jsonip.com)', httpFetch({
url: 'https://jsonip.com',
family: 4,
responseType: 'json',
timeout: 5000,
}).then(r => r.body.ip));
await this.validate(this.console, 'IPv6 (jsonip.com)', httpFetch({
url: 'https://jsonip.com',
family: 6,
responseType: 'json',
timeout: 5000,
}).then(r => r.body.ip));
await this.validate(this.console, 'IPv4 (wtfismyip.com)', httpFetch({
url: 'https://wtfismyip.com/text',
family: 4,
responseType: 'text',
timeout: 5000,
}).then(r => r.body.trim()));
await this.validate(this.console, 'IPv6 (wtfismyip.com)', httpFetch({
url: 'https://wtfismyip.com/text',
family: 6,
responseType: 'text',
timeout: 5000,
}).then(r => r.body.trim()));
await this.validate(this.console, 'Scrypted Server Address', async () => {
const addresses = await sdk.endpointManager.getLocalAddresses();
const hasIPv4 = addresses?.find(address => net.isIPv4(address));
const hasIPv6 = addresses?.find(address => net.isIPv6(address));
if (addresses?.length)
this.warnStep(this.console, addresses.join(', '));
if (!hasIPv4)
throw new Error('Scrypted Settings IPv4 address not set.');
if (!hasIPv6)
throw new Error('Scrypted Settings IPv6 address not set.');
});
await this.validate(this.console, 'CPU Count', async () => {
if (os.cpus().length < 2)
throw new Error('CPU Count is too low. 4 CPUs are recommended.');
return os.cpus().length;
});
await this.validate(this.console, 'Memory', async () => {
if (!nvrPlugin) {
if (os.totalmem() < 8 * 1024 * 1024 * 1024)
throw new Error('Memory is too low. 8GB is recommended.');
return;
}
if (os.totalmem() < 14 * 1024 * 1024 * 1024)
throw new Error('Memory is too low. 16GB is recommended for NVR.');
return Math.floor(os.totalmem() / 1024 / 1024 / 1024) + " GB";
});
if (process.platform === 'linux' && nvrPlugin) {
// ensure /dev/dri/renderD128 or /dev/dri/renderD129 is available
await this.validate(this.console, 'GPU Passthrough', async () => {
if (!fs.existsSync('/dev/dri/renderD128') && !fs.existsSync('/dev/dri/renderD129'))
throw new Error('GPU device unvailable or not passed through to container. (/dev/dri/renderD128, /dev/dri/renderD129)');
// also check /dev/kfd for AMD CPU
const amdCPU = os.cpus().find(c => c.model.includes('AMD'));
if (amdCPU && !fs.existsSync('/dev/kfd'))
throw new Error('GPU device unvailable or not passed through to container. (/dev/kfd)');
});
}
await this.validate(this.console, 'Cloud Plugin', async () => {
if (!cloudPlugin) {
this.warnStep(this.console, 'Cloud plugin not installed. Consider installing for remote access.');
return;
}
const logo = await httpFetch({
url: 'https://home.scrypted.app/_punch/web_hi_res_512.png',
responseType: 'buffer',
});
const mo = await sdk.mediaManager.createMediaObject(logo.body, 'image/png');
const url = await sdk.mediaManager.convertMediaObjectToUrl(mo, 'image/png');
const logoCheck = await httpFetch({
url,
responseType: 'buffer',
});
if (Buffer.compare(logo.body, logoCheck.body))
throw new Error('Invalid response received.');
const shortUrl: any = await sdk.mediaManager.convertMediaObject(mo, ScryptedMimeTypes.Url + ";short-lived=true");
const shortLogoCheck = await httpFetch({
url: shortUrl.toString(),
responseType: 'buffer',
});
if (Buffer.compare(logo.body, shortLogoCheck.body))
throw new Error('Invalid response received from short lived URL.');
});
if ((hasCUDA || process.platform === 'win32') && onnxPlugin) {
await this.validate(this.console, 'ONNX Plugin', async () => {
const settings = await onnxPlugin.getSettings();
const executionDevice = settings.find(s => s.key === 'execution_device');
if (executionDevice?.value?.toString().includes('CPU'))
this.warnStep(this.console, 'GPU device unvailable or not passed through to container.');
const zidane = await sdk.mediaManager.createMediaObjectFromUrl('https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg');
const detected = await onnxPlugin.detectObjects(zidane);
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
if (!personFound)
throw new Error('Person not detected in test image.');
});
}
if (!hasCUDA && openvinoPlugin && (process.platform !== 'win32' || !onnxPlugin)) {
await this.validate(this.console, 'OpenVINO Plugin', async () => {
const settings = await openvinoPlugin.getSettings();
const availbleDevices = settings.find(s => s.key === 'available_devices');
if (!availbleDevices?.value?.toString().includes('GPU'))
this.warnStep(this.console, 'GPU device unvailable or not passed through to container.');
const zidane = await sdk.mediaManager.createMediaObjectFromUrl('https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg');
const detected = await openvinoPlugin.detectObjects(zidane);
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
if (!personFound)
throw new Error('Person not detected in test image.');
});
}
if (nvrPlugin) {
await this.validate(this.console, "GPU Decode", async () => {
const ffmpegPath = await sdk.mediaManager.getFFmpegPath();
let hasVaapi = false;
{
const args = [
'-y',
'-hwaccel', 'auto',
'-i', 'https://github.com/koush/scrypted-sample-cameraprovider/raw/main/fs/dog.mp4',
'-f', 'rawvideo',
'pipe:3',
];
const cp = child_process.spawn(ffmpegPath, args, {
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
});
const deferred = new Deferred<void>();
deferred.promise.catch(() => { }).finally(() => safeKillFFmpeg(cp));
cp.stdio[3]?.on('data', () => { });
cp.stderr!.on('data', data => {
const str = data.toString();
hasVaapi ||= str.includes('Using auto hwaccel type vaapi');
if (str.includes('nv12'))
deferred.resolve();
});
setTimeout(() => {
deferred.reject(new Error('GPU Decode timed out.'));
}, 5000);
await deferred.promise;
}
if (!hasVaapi || !openvinoPlugin)
return;
{
const args = [
'-y',
'-init_hw_device', 'vaapi=renderD128:/dev/dri/renderD128',
'-hwaccel', 'vaapi',
'-hwaccel_output_format', 'vaapi',
'-i', 'https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg',
'-vf', 'format=nv12,hwupload,scale_vaapi=w=320:h=-2,hwdownload,format=nv12',
'-f', 'mjpeg',
'-frames:v', '1',
'pipe:3',
];
const cp = child_process.spawn(ffmpegPath, args, {
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
});
let std = '';
cp.stderr!.on('data', data => {
std += data.toString();
});
cp.stdout!.on('data', data => {
std += data.toString();
});
const buffers: Buffer[] = [];
cp.stdio[3]?.on('data', buffer => {
buffers.push(buffer);
});
setTimeout(() => {
safeKillFFmpeg(cp)
}, 5000);
const [exitCode] = await once(cp, 'exit');
if (exitCode) {
this.warnStep(this.console, std);
throw new Error('GPU Transform failed.');
}
const jpeg = Buffer.concat(buffers);
const zidane = await sdk.mediaManager.createMediaObject(jpeg, 'image/jpeg');
const image = await sdk.mediaManager.convertMediaObject<Image>(zidane, ScryptedMimeTypes.Image);
if (image.width !== 320)
throw new Error('Unexpected image with from GPU transform.')
const detected = await openvinoPlugin.detectObjects(zidane);
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
if (!personFound)
throw new Error('Person not detected in test image.');
}
});
await this.validate(this.console, 'Deprecated Plugins', async () => {
const defunctPlugins = [
'@scrypted/electron-core',
'@scrypted/opencv',
'@scrypted/python-codecs',
'@scrypted/pam-diff',
];
let found = false;
for (const plugin of defunctPlugins) {
const pluginDevice = sdk.systemManager.getDeviceById(plugin);
if (pluginDevice) {
this.warnStep(this.console, `Scrypted NVR users can remove: ${plugin}`);
found = true;
}
}
if (found)
throw new Error('Deprecated plugins found.');
});
}
this.console.log(''.padEnd(44, '='));
this.console.log('System Validation Complete');
this.console.log(''.padEnd(44, '='));
}
}
export default DiagnosticsPlugin;

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2021",
"strict": true,
"resolveJsonModule": true,
"moduleResolution": "Node16",
"esModuleInterop": true,
"sourceMap": true
},
"include": [
"src/**/*"
]
}

View File

@@ -1,4 +1,4 @@
{
"scrypted.debugHost": "scrypted-server",
"scrypted.debugHost": "127.0.0.1",
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/google-device-access",
"version": "0.0.98",
"version": "0.0.99",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/google-device-access",
"version": "0.0.98",
"version": "0.0.99",
"dependencies": {
"@googleapis/smartdevicemanagement": "^1.0.0",
"@scrypted/common": "file:../../common",

View File

@@ -49,5 +49,5 @@
"@types/lodash": "^4.14.195",
"@types/node": "^20.4.1"
},
"version": "0.0.98"
"version": "0.0.99"
}

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "Node16",
"target": "ES2021",
"resolveJsonModule": true,
"moduleResolution": "Node16",

View File

@@ -111,6 +111,12 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
hide: true,
description: 'The last home hub to request a recording. Internally used to determine if a streaming request is coming from remote wifi.',
},
autoAdd: {
title: "Auto enable",
description: "Automatically enable this mixin on new devices.",
type: 'boolean',
defaultValue: true,
},
});
mergedDevices = new Set<string>();
@@ -218,7 +224,8 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
try {
const mixins = (device.mixins || []).slice();
if (!mixins.includes(this.id)) {
const autoAdd = this.storageSettings.values.autoAdd ?? true;
if (!mixins.includes(this.id) && autoAdd) {
// don't sync this by default, as it's solely for automations
if (device.type === ScryptedDeviceType.Notifier)
continue;

View File

@@ -1,4 +1,4 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-nvr",
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/mqtt",
"version": "0.0.82",
"version": "0.0.86",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/mqtt",
"version": "0.0.82",
"version": "0.0.86",
"dependencies": {
"aedes": "^0.46.1",
"axios": "^0.23.0",

View File

@@ -43,5 +43,5 @@
"@types/node": "^18.4.2",
"@types/nunjucks": "^3.2.0"
},
"version": "0.0.82"
"version": "0.0.86"
}

View File

@@ -1,3 +1,63 @@
import type { MqttClient } from "./mqtt-client";
import type { ScryptedDeviceBase } from "@scrypted/sdk";
import type { MqttClient, MqttEvent, MqttSubscriptions } from "./mqtt-client";
declare const device: ScryptedDeviceBase;
export declare const mqtt: MqttClient;
export function createSensor(options: {
type: string,
topic: string,
when: (message: MqttEvent) => boolean;
set: (value: boolean) => void,
delay?: number;
}) {
const subscriptions: MqttSubscriptions = {};
let timeout: NodeJS.Timeout;
subscriptions[options.topic] = message => {
const detected = options.when(message);
if (!options.delay) {
options.set(detected);
return;
}
if (!detected)
return;
options.set(true);
clearTimeout(timeout);
timeout = setTimeout(() => options.set(false), options.delay * 1000);
};
mqtt.subscribe(subscriptions);
mqtt.handleTypes(options.type);
}
export function createMotionSensor(options: {
topic: string,
when: (message: MqttEvent) => boolean;
delay?: number;
}) {
return createSensor({
type: "MotionSensor",
topic: options.topic,
set: (value: boolean) => device.motionDetected = value,
when: options.when,
delay: options.delay,
})
}
export function createBinarySensor(options: {
topic: string,
when: (message: MqttEvent) => boolean;
delay?: number;
}) {
return createSensor({
type: "BinarySensor",
topic: options.topic,
set: (value: boolean) => device.binaryState = value,
when: options.when,
delay: options.delay,
})
}

View File

@@ -3,7 +3,7 @@ import { ScriptDeviceImpl, scryptedEval as scryptedEvalBase } from "@scrypted/co
const util = require("!!raw-loader!./api/util.ts").default;
const libs = {
util,
util: util.replace('export', ''),
};
export async function scryptedEval(device: ScryptedDeviceBase, script: string, params: { [name: string]: any }) {

View File

@@ -1,3 +1,3 @@
{
"scrypted.debugHost": "127.0.0.1",
"scrypted.debugHost": "scrypted-nvr",
}

View File

@@ -13,4 +13,8 @@ benefits to HomeKit, which does its own detection processing.
## Smart Motion Sensors
This plugin can be used to create smart motion sensors that trigger when a specific type of object (car, person, dog, etc) triggers movement on a camera. Created sensors can then be synced to other platforms such as HomeKit, Google Home, Alexa, or Home Assistant for use in automations. This feature requires cameras with hardware or software object detection capability.
This plugin can be used to create smart motion sensors that trigger when a specific type of object (vehicle, person, animal, etc) triggers movement on a camera. Created sensors can then be synced to other platforms such as HomeKit, Google Home, Alexa, or Home Assistant for use in automations. This Sensor requires cameras with hardware or software object detection capability.
## Smart Occupancy Sensors
This plugin can be used to create smart occupancy sensors remains triggered when a specific type of object (vehicle, person, animal, etc) is detected on a camera. Created sensors can then be synced to other platforms such as HomeKit, Google Home, Alexa, or Home Assistant for use in automations. This Sensor requires an object detector plugin such as Scrypted NVR, OpenVINO, CoreML, ONNX, or Tensorflow-lite.

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/objectdetector",
"version": "0.1.43",
"version": "0.1.60",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/objectdetector",
"version": "0.1.43",
"version": "0.1.60",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/objectdetector",
"version": "0.1.43",
"version": "0.1.60",
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -1,48 +0,0 @@
import { CpuInfo, cpus } from 'os';
function getIdleTotal(cpu: CpuInfo) {
const t = cpu.times;
const total = t.user + t.nice + t.sys + t.idle + t.irq;
const idle = t.idle;
return {
idle,
total,
}
}
export class CpuTimer {
previousSample: ReturnType<typeof cpus>;
maxSpeed = 0;
sample(): number {
const sample = cpus();
const previousSample = this.previousSample;
this.previousSample = sample;
// can cpu count change at runtime, who knows
if (!previousSample || previousSample.length !== sample.length)
return 0;
// cpu may be throttled in low power mode, so observe total speed to scale
let totalSpeed = 0;
const times = sample.map((v, i) => {
totalSpeed += v.speed;
const c = getIdleTotal(v);
const p = getIdleTotal(previousSample[i]);
const total = c.total - p.total;
const idle = c.idle - p.idle;
return 1 - idle / total;
});
this.maxSpeed = Math.max(this.maxSpeed, totalSpeed);
// will return a value between 0 and 1, where 1 is full cpu speed
// the cpu usage is scaled by the clock speed
// so if the cpu is running at 1ghz out of 3ghz, the cpu usage is scaled by 1/3
const clockScale = totalSpeed / this.maxSpeed;
const total = times.reduce((p, c) => p + c, 0);
return total / sample.length * clockScale;
}
}

View File

@@ -1,14 +1,14 @@
import { Deferred } from '@scrypted/common/src/deferred';
import { sleep } from '@scrypted/common/src/sleep';
import sdk, { Camera, DeviceCreator, DeviceCreatorSettings, DeviceProvider, DeviceState, EventListenerRegister, MediaObject, MediaStreamDestination, MixinDeviceBase, MixinProvider, MotionSensor, ObjectDetection, ObjectDetectionModel, ObjectDetectionTypes, ObjectDetectionZone, ObjectDetector, ObjectsDetected, Point, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, SettingValue, Settings, VideoCamera, VideoFrame, VideoFrameGenerator, WritableDeviceState } from '@scrypted/sdk';
import sdk, { Camera, DeviceCreator, DeviceCreatorSettings, DeviceProvider, EventListenerRegister, MediaObject, MediaStreamDestination, MixinDeviceBase, MixinProvider, MotionSensor, ObjectDetection, ObjectDetectionModel, ObjectDetectionTypes, ObjectDetectionZone, ObjectDetector, ObjectsDetected, Point, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, SettingValue, Settings, VideoCamera, VideoFrame, VideoFrameGenerator, WritableDeviceState } from '@scrypted/sdk';
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import crypto from 'crypto';
import { AutoenableMixinProvider } from "../../../common/src/autoenable-mixin-provider";
import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin";
import { CpuTimer } from './cpu-timer';
import { FFmpegVideoFrameGenerator } from './ffmpeg-videoframes';
import { insidePolygon, normalizeBox, polygonOverlap } from './polygon';
import { SMART_MOTIONSENSOR_PREFIX, SmartMotionSensor, createObjectDetectorStorageSetting } from './smart-motionsensor';
import { fixLegacyClipPath, insidePolygon, normalizeBoxToClipPath, polygonOverlap } from './polygon';
import { SMART_MOTIONSENSOR_PREFIX, SmartMotionSensor } from './smart-motionsensor';
import { SMART_OCCUPANCYSENSOR_PREFIX, SmartOccupancySensor } from './smart-occupancy-sensor';
import { getAllDevices, safeParseJson } from './util';
@@ -20,6 +20,17 @@ const defaultMotionDuration = 30;
const BUILTIN_MOTION_SENSOR_ASSIST = 'Assist';
const BUILTIN_MOTION_SENSOR_REPLACE = 'Replace';
// at 5fps object detection speed, the camera is considered throttled.
// throttling may be due to cpu, gpu, npu or whatever.
// regardless, purging low fps object detection sessions will likely
// restore performance.
const fpsKillWaterMark = 5
const fpsLowWaterMark = 7;
// cameras may have low performance due to low framerate or intensive tasks such as
// LPR and face recognition. if multiple cams are in low performance mode, then
// the system may be struggling.
const lowPerformanceMinThreshold = 2;
const objectDetectionPrefix = `${ScryptedInterface.ObjectDetection}:`;
type ClipPath = Point[];
@@ -85,6 +96,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
...getAllDevices().filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator)).map(d => d.name),
];
return {
hide: this.model?.decoder,
choices,
}
},
@@ -103,6 +115,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
analyzeStop: number;
detectorSignal = new Deferred<void>().resolve();
released = false;
sampleHistory: number[] = [];
// settings: Setting[];
get detectorRunning() {
@@ -162,7 +175,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
getCurrentSettings() {
const settings = this.model.settings;
if (!settings)
return { id : this.id };
return { id: this.id };
const ret: { [key: string]: any } = {};
for (const setting of settings) {
@@ -174,6 +187,8 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
}
else {
value = this.storage.getItem(setting.key);
if (setting.type === 'number')
value = parseFloat(value);
}
value ||= setting.value;
@@ -334,13 +349,11 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
const stream = await this.cameraDevice.getVideoStream({
prebuffer: this.model.prebuffer,
destination,
// ask rebroadcast to mute audio, not needed.
audio: null,
});
if (this.model.decoder) {
if (!options?.suppress)
this.console.log(this.objectDetection.name, '(with builtin decoder)');
this.console.log(this.objectDetection.name, '(with builtin decoder)');
return stream;
}
@@ -419,7 +432,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
const frameGenerator = this.model.decoder ? undefined : this.getFrameGenerator();
for await (const detected of
await sdk.connectRPCObject(
await this.objectDetection.generateObjectDetections(
await this.objectDetection.generateObjectDetections(
await this.createFrameGenerator(
frameGenerator,
options,
@@ -436,13 +449,18 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
break;
}
const now = Date.now();
// stop when analyze period ends.
if (!this.hasMotionType && this.analyzeStop && Date.now() > this.analyzeStop) {
if (!this.hasMotionType && this.analyzeStop && now > this.analyzeStop) {
this.analyzeStop = undefined;
break;
}
if (!longObjectDetectionWarning && !this.hasMotionType && Date.now() - start > 5 * 60 * 1000) {
this.purgeSampleHistory(now);
this.sampleHistory.push(now);
if (!longObjectDetectionWarning && !this.hasMotionType && now - start > 5 * 60 * 1000) {
longObjectDetectionWarning = true;
this.console.warn('Camera has been performing object detection for 5 minutes due to persistent motion. This may adversely affect system performance. Read the Optimizing System Performance guide for tips and tricks. https://github.com/koush/nvr.scrypted.app/wiki/Optimizing-System-Performance')
}
@@ -453,21 +471,18 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
const zonedDetections = this.applyZones(detected.detected);
detected.detected.detections = zonedDetections;
// this.console.warn('dps', detections / (Date.now() - start) * 1000);
if (!this.hasMotionType) {
this.plugin.trackDetection();
// const numZonedDetections = zonedDetections.filter(d => d.className !== 'motion').length;
// const numOriginalDetections = originalDetections.filter(d => d.className !== 'motion').length;
// if (numZonedDetections !== numOriginalDetections)
// this.console.log('Zone filtered detections:', numZonedDetections - numOriginalDetections);
const numZonedDetections = zonedDetections.filter(d => d.className !== 'motion').length;
const numOriginalDetections = originalDetections.filter(d => d.className !== 'motion').length;
if (numZonedDetections !== numOriginalDetections)
currentDetections.set('filtered', (currentDetections.get('filtered') || 0) + 1);
for (const d of detected.detected.detections) {
currentDetections.set(d.className, Math.max(currentDetections.get(d.className) || 0, d.score));
}
const now = Date.now();
if (now > lastReport + 10000) {
const found = [...currentDetections.entries()].map(([className, score]) => `${className} (${score})`);
if (!found.length)
@@ -480,23 +495,20 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
if (detected.detected.detectionId) {
updatePipelineStatus('creating jpeg');
// const start = Date.now();
let { image } = detected.videoFrame;
image = await sdk.connectRPCObject(image);
const jpeg = await image.toBuffer({
format: 'jpg',
});
const mo = await sdk.mediaManager.createMediaObject(jpeg, 'image/jpeg');
// this.console.log('retain took', Date.now() -start);
this.setDetection(detected.detected, mo);
// this.console.log('image saved', detected.detected.detections);
}
const motionFound = this.reportObjectDetections(detected.detected);
if (this.hasMotionType) {
// if motion is detected, stop processing and exit loop allowing it to sleep.
if (motionFound) {
// however, when running in analyze mode, continue to allow viewing motion boxes for test purposes.
if (!this.analyzeStop || Date.now() > this.analyzeStop) {
if (!this.analyzeStop || now > this.analyzeStop) {
this.analyzeStop = undefined;
clearInterval(interval);
return true;
@@ -505,10 +517,25 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
await sleep(250);
}
updatePipelineStatus('waiting result');
// this.handleDetectionEvent(detected.detected);
}
}
purgeSampleHistory(now: number) {
while (this.sampleHistory.length && now - this.sampleHistory[0] > 10000) {
this.sampleHistory.shift();
}
}
get detectionFps() {
const now = Date.now();
this.purgeSampleHistory(now);
const first = this.sampleHistory[0];
// require at least 5 seconds of samples.
if (!first || (now - first) < 8000)
return Infinity;
return this.sampleHistory.length / ((now - first) / 1000);
}
applyZones(detection: ObjectsDetected) {
// determine zones of the objects, if configured.
if (!detection.detections)
@@ -518,7 +545,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
if (!o.boundingBox)
continue;
const box = normalizeBox(o.boundingBox, detection.inputDimensions);
const box = normalizeBoxToClipPath(o.boundingBox, detection.inputDimensions);
let included: boolean;
// need a way to explicitly include package zone.
@@ -526,12 +553,17 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
included = true;
else
o.zones = [];
for (const [zone, zoneValue] of Object.entries(this.zones)) {
for (let [zone, zoneValue] of Object.entries(this.zones)) {
zoneValue = fixLegacyClipPath(zoneValue);
if (zoneValue.length < 3) {
// this.console.warn(zone, 'Zone is unconfigured, skipping.');
continue;
}
// object detection may report motion, don't filter these at all.
if (!this.hasMotionType && o.className === 'motion')
continue;
const zoneInfo = this.zoneInfos[zone];
const exclusion = zoneInfo?.filterMode ? zoneInfo.filterMode === 'exclude' : zoneInfo?.exclusion;
// track if there are any inclusion zones
@@ -571,7 +603,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
// use a default inclusion zone that crops the top and bottom to
// prevents errant motion from the on screen time changing every second.
if (this.hasMotionType && included === undefined) {
const defaultInclusionZone: ClipPath = [[0, 10], [100, 10], [100, 90], [0, 90]];
const defaultInclusionZone: ClipPath = [[0, .1], [1, .1], [1, .9], [0, .9]];
included = polygonOverlap(box, defaultInclusionZone);
}
@@ -612,7 +644,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
this.detections.set(detectionId, detectionInput);
setTimeout(() => {
this.detections.delete(detectionId);
}, 2000);
}, 10000);
}
async getNativeObjectTypes(): Promise<ObjectDetectionTypes> {
@@ -661,12 +693,11 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
frameGenerator = this.plugin.storageSettings.values.defaultDecoder || 'Default';
const pipelines = getAllDevices().filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator));
const webcodec = process.env.SCRYPTED_INSTALL_ENVIRONMENT === 'electron' ? sdk.systemManager.getDeviceById('@scrypted/electron-core', 'webcodec') : undefined;
const webassembly = sdk.systemManager.getDeviceById('@scrypted/nvr', 'decoder') || undefined;
const gstreamer = sdk.systemManager.getDeviceById('@scrypted/python-codecs', 'gstreamer') || undefined;
const libav = sdk.systemManager.getDeviceById('@scrypted/python-codecs', 'libav') || undefined;
const ffmpeg = sdk.systemManager.getDeviceById('@scrypted/objectdetector', 'ffmpeg') || undefined;
const use = pipelines.find(p => p.name === frameGenerator) || webcodec || webassembly || gstreamer || libav || ffmpeg;
const use = pipelines.find(p => p.name === frameGenerator) || webassembly || gstreamer || libav || ffmpeg;
return use.id;
}
@@ -820,7 +851,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
if (key.startsWith('zone-')) {
const zoneName = key.substring('zone-'.length);
if (this.zones[zoneName]) {
this.zones[zoneName] = JSON.parse(vs);
this.zones[zoneName] = Array.isArray(value) ? value : JSON.parse(vs);
this.storage.setItem('zones', JSON.stringify(this.zones));
}
return;
@@ -1010,14 +1041,12 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
},
});
devices = new Map<string, any>();
cpuTimer = new CpuTimer();
cpuUsage = 0;
constructor(nativeId?: ScryptedNativeId) {
super(nativeId, 'v5');
this.systemDevice = {
deviceCreator: 'Smart Motion Sensor',
deviceCreator: 'Smart Sensor',
};
process.nextTick(() => {
@@ -1031,19 +1060,28 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
})
});
// on an interval check to see if system load allows squelched detectors to start up.
setInterval(() => {
this.cpuUsage = this.cpuTimer.sample();
// this.console.log('cpu usage', Math.round(this.cpuUsage * 100));
const runningDetections = this.runningObjectDetections;
// don't allow too many cams to start up at once if resuming from a low performance state.
let allowStart = 2;
// always allow 2 cameras to push past cpu throttling
if (runningDetections.length > 2) {
const cpuPerDetector = this.cpuUsage / runningDetections.length;
allowStart = Math.ceil(1 / cpuPerDetector) - runningDetections.length;
if (allowStart <= 0)
// allow minimum amount of concurrent cameras regardless of system specs
if (runningDetections.length > lowPerformanceMinThreshold) {
// if anything is below the kill threshold, do not start
const killable = runningDetections.filter(o => o.detectionFps < fpsKillWaterMark && !o.analyzeStop);
if (killable.length > lowPerformanceMinThreshold) {
const cameraNames = runningDetections.map(o => `${o.name} ${o.detectionFps}`).join(', ');
const first = killable[0];
first.console.warn(`System at capacity. Ending object detection.`, cameraNames);
first.endObjectDetection();
return;
}
const lowWatermark = runningDetections.filter(o => o.detectionFps < fpsLowWaterMark);
if (lowWatermark.length > lowPerformanceMinThreshold)
allowStart = 1;
}
const idleDetectors = [...this.currentMixins.values()]
@@ -1057,7 +1095,11 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
return;
}
}
}, 10000)
}, 5000)
}
checkHasEnabledMixin(device: ScryptedDevice): boolean {
return false;
}
pruneOldStatistics() {
@@ -1080,26 +1122,28 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
if (runningDetections.find(o => o.id === mixin.id))
return false;
// always allow 2 cameras to push past cpu throttling
if (runningDetections.length < 2)
// allow minimum amount of concurrent cameras regardless of system specs
if (runningDetections.length < lowPerformanceMinThreshold)
return true;
const cpuPerDetector = this.cpuUsage / runningDetections.length;
const cpuPercent = Math.round(this.cpuUsage * 100);
if (cpuPerDetector * (runningDetections.length + 1) > .9) {
const [first] = runningDetections;
// find any cameras struggling with a with low detection fps.
const lowWatermark = runningDetections.filter(o => o.detectionFps < fpsLowWaterMark);
if (lowWatermark.length > lowPerformanceMinThreshold) {
const [first] = lowWatermark;
// if cameras have been detecting enough to catch the activity, kill it for new camera.
const cameraNames = runningDetections.map(o => `${o.name} ${o.detectionFps}`).join(', ');
if (Date.now() - first.detectionStartTime > 30000) {
first.console.warn(`CPU is at capacity: ${cpuPercent} with ${runningDetections.length} cameras. Ending object detection to process activity on ${mixin.name}.`);
first.console.warn(`System at capacity. Ending object detection to process activity on ${mixin.name}.`, cameraNames);
first.endObjectDetection();
mixin.console.warn(`CPU is at capacity: ${cpuPercent} with ${runningDetections.length} cameras. Ending object detection on ${first.name} to process activity.`);
mixin.console.warn(`System at capacity. Ending object detection on ${first.name} to process activity.`, cameraNames);
return true;
}
mixin.console.warn(`CPU is at capacity: ${cpuPercent} with ${runningDetections.length} cameras. Not starting object detection to continue processing recent activity on ${first.name}.`);
mixin.console.warn(`System at capacity. Not starting object detection to continue processing recent activity on ${first.name}.`, cameraNames);
return false;
}
// CPU capacity is fine
// System capacity is fine. Start the detection.
return true;
}
@@ -1154,6 +1198,8 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
ret = this.devices.get(nativeId) || new FFmpegVideoFrameGenerator('ffmpeg');
if (nativeId?.startsWith(SMART_MOTIONSENSOR_PREFIX))
ret = this.devices.get(nativeId) || new SmartMotionSensor(this, nativeId);
if (nativeId?.startsWith(SMART_OCCUPANCYSENSOR_PREFIX))
ret = this.devices.get(nativeId) || new SmartOccupancySensor(this, nativeId);
if (ret)
this.devices.set(nativeId, ret);
@@ -1164,6 +1210,13 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
if (nativeId?.startsWith(SMART_MOTIONSENSOR_PREFIX)) {
const smart = this.devices.get(nativeId) as SmartMotionSensor;
smart?.detectionListener?.removeListener();
smart?.resetMotionTimeout();
}
if (nativeId?.startsWith(SMART_OCCUPANCYSENSOR_PREFIX)) {
const smart = this.devices.get(nativeId) as SmartOccupancySensor;
smart?.detectionListener?.removeListener();
smart?.resetOccupiedTimeout();
smart?.clearOccupancyInterval();
}
}
@@ -1199,32 +1252,71 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
async getCreateDeviceSettings(): Promise<Setting[]> {
return [
createObjectDetectorStorageSetting(),
{
key: 'sensorType',
title: 'Sensor Type',
description: 'Select the type of sensor to create.',
choices: [
'Smart Motion Sensor',
'Smart Occupancy Sensor',
],
},
{
key: 'camera',
title: 'Camera',
description: 'Select a camera or doorbell.',
type: 'device',
deviceFilter: `type === '${ScryptedDeviceType.Doorbell}' || type === '${ScryptedDeviceType.Camera}'`,
},
];
}
async createDevice(settings: DeviceCreatorSettings): Promise<string> {
const nativeId = SMART_MOTIONSENSOR_PREFIX + crypto.randomBytes(8).toString('hex');
const objectDetector = sdk.systemManager.getDeviceById(settings.objectDetector as string);
let name = objectDetector.name || 'New';
name += ' Smart Motion Sensor'
const sensorType = settings.sensorType;
const camera = sdk.systemManager.getDeviceById(settings.camera as string);
if (sensorType === 'Smart Motion Sensor') {
const nativeId = SMART_MOTIONSENSOR_PREFIX + crypto.randomBytes(8).toString('hex');
let name = camera.name || 'New';
name += ' Smart Motion Sensor'
const id = await sdk.deviceManager.onDeviceDiscovered({
nativeId,
name,
type: ScryptedDeviceType.Sensor,
interfaces: [
ScryptedInterface.Camera,
ScryptedInterface.MotionSensor,
ScryptedInterface.Settings,
ScryptedInterface.Readme,
]
});
const id = await sdk.deviceManager.onDeviceDiscovered({
nativeId,
name,
type: ScryptedDeviceType.Sensor,
interfaces: [
ScryptedInterface.Camera,
ScryptedInterface.MotionSensor,
ScryptedInterface.Settings,
ScryptedInterface.Readme,
]
});
const sensor = new SmartMotionSensor(this, nativeId);
sensor.storageSettings.values.objectDetector = objectDetector?.id;
const sensor = new SmartMotionSensor(this, nativeId);
sensor.storageSettings.values.objectDetector = camera?.id;
return id;
return id;
}
else if (sensorType === 'Smart Occupancy Sensor') {
const nativeId = SMART_OCCUPANCYSENSOR_PREFIX + crypto.randomBytes(8).toString('hex');
let name = camera.name || 'New';
name += ' Smart Occupancy Sensor'
const id = await sdk.deviceManager.onDeviceDiscovered({
nativeId,
name,
type: ScryptedDeviceType.Sensor,
interfaces: [
ScryptedInterface.OccupancySensor,
ScryptedInterface.Settings,
ScryptedInterface.Readme,
]
});
const sensor = new SmartOccupancySensor(this, nativeId);
sensor.storageSettings.values.camera = camera?.id;
return id;
}
}
}

View File

@@ -1,4 +1,4 @@
import { Point } from '@scrypted/sdk';
import type { ClipPath, Point } from '@scrypted/sdk';
import polygonClipping from 'polygon-clipping';
// const polygonOverlap = require('polygon-overlap');
@@ -14,15 +14,36 @@ export function insidePolygon(point: Point, polygon: Point[]) {
return !!intersect.length;
}
export function normalizeBox(boundingBox: [number, number, number, number], inputDimensions: [number, number]): [Point, Point, Point, Point] {
export function fixLegacyClipPath(clipPath: ClipPath): ClipPath {
if (!clipPath)
return;
// if any value is over abs 2, then divide by 100.
// this is a workaround for the old scrypted bug where the path was not normalized.
// this is a temporary workaround until the path is normalized in the UI.
let needNormalize = false;
for (const p of clipPath) {
for (const c of p) {
if (Math.abs(c) >= 2)
needNormalize = true;
}
}
if (!needNormalize)
return clipPath;
return clipPath.map(p => p.map(c => c / 100)) as ClipPath;
}
export function normalizeBoxToClipPath(boundingBox: [number, number, number, number], inputDimensions: [number, number]): [Point, Point, Point, Point] {
let [x, y, width, height] = boundingBox;
let x2 = x + width;
let y2 = y + height;
// the zones are point paths in percentage format
x = x * 100 / inputDimensions[0];
y = y * 100 / inputDimensions[1];
x2 = x2 * 100 / inputDimensions[0];
y2 = y2 * 100 / inputDimensions[1];
x = x / inputDimensions[0];
y = y / inputDimensions[1];
x2 = x2 / inputDimensions[0];
y2 = y2 / inputDimensions[1];
return [[x, y], [x2, y], [x2, y2], [x, y2]];
}

View File

@@ -1,24 +1,18 @@
import sdk, { Camera, EventListenerRegister, MediaObject, MotionSensor, ObjectDetector, ObjectsDetected, Readme, RequestPictureOptions, ResponsePictureOptions, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, SettingValue, Settings } from "@scrypted/sdk";
import { StorageSetting, StorageSettings } from "@scrypted/sdk/storage-settings";
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import { levenshteinDistance } from "./edit-distance";
import type { ObjectDetectionPlugin } from "./main";
export const SMART_MOTIONSENSOR_PREFIX = 'smart-motionsensor-';
export const SMART_OCCUPANCYSENSOR_PREFIX = 'smart-occupancysensor-';
export function createObjectDetectorStorageSetting(): StorageSetting {
return {
key: 'objectDetector',
title: 'Object Detector',
description: 'Select the camera or doorbell that provides smart detection event.',
type: 'device',
deviceFilter: `(type === '${ScryptedDeviceType.Doorbell}' || type === '${ScryptedDeviceType.Camera}') && interfaces.includes('${ScryptedInterface.ObjectDetector}')`,
};
}
export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, Readme, MotionSensor, Camera {
storageSettings = new StorageSettings(this, {
objectDetector: createObjectDetectorStorageSetting(),
objectDetector: {
title: 'Camera',
description: 'Select a camera or doorbell that provides smart detection events.',
type: 'device',
deviceFilter: `(type === '${ScryptedDeviceType.Doorbell}' || type === '${ScryptedDeviceType.Camera}') && interfaces.includes('${ScryptedInterface.ObjectDetector}')`,
},
detections: {
title: 'Detections',
description: 'The detections that will trigger this smart motion sensor.',
@@ -145,13 +139,13 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
return;
}
resetTrigger() {
resetMotionTimeout() {
clearTimeout(this.timeout);
this.timeout = undefined;
}
trigger() {
this.resetTrigger();
this.resetMotionTimeout();
this.motionDetected = true;
const duration: number = this.storageSettings.values.detectionTimeout;
if (!duration)
@@ -167,7 +161,7 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
this.detectionListener = undefined;
this.motionListener?.removeListener();
this.motionListener = undefined;
this.resetTrigger();
this.resetMotionTimeout();
const objectDetector: ObjectDetector & MotionSensor & ScryptedDevice = this.storageSettings.values.objectDetector;
@@ -178,8 +172,6 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
if (!detections?.length)
return;
const console = sdk.deviceManager.getMixinConsole(objectDetector.id, this.nativeId);
this.motionListener = objectDetector.listen({
event: ScryptedInterface.MotionSensor,
watch: true,
@@ -258,7 +250,7 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
if (match) {
if (!this.motionDetected)
console.log('Smart Motion Sensor triggered on', match);
this.console.log('Smart Motion Sensor triggered on', match);
if (detected.detectionId)
this.lastPicture = objectDetector.getDetectionInput(detected.detectionId, details.eventId);
this.trigger();
@@ -278,6 +270,6 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
return `
## Smart Motion Sensor
This Smart Motion Sensor can trigger when a specific type of object (car, person, dog, etc) triggers movement on a camera. The sensor can then be synced to other platforms such as HomeKit, Google Home, Alexa, or Home Assistant for use in automations. This Sensor requires a camera with hardware or software object detection capability.`;
This Smart Motion Sensor can trigger when a specific type of object (vehicle, person, animal, etc) triggers movement on a camera. The sensor can then be synced to other platforms such as HomeKit, Google Home, Alexa, or Home Assistant for use in automations. This Sensor requires a camera with hardware or software object detection capability.`;
}
}

View File

@@ -0,0 +1,316 @@
import sdk, { Camera, ClipPath, EventListenerRegister, Image, ObjectDetection, ObjectDetector, ObjectsDetected, OccupancySensor, Readme, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, SettingValue, Settings } from "@scrypted/sdk";
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import { levenshteinDistance } from "./edit-distance";
import type { ObjectDetectionPlugin } from "./main";
import { normalizeBoxToClipPath, polygonOverlap } from "./polygon";
export const SMART_OCCUPANCYSENSOR_PREFIX = 'smart-occupancysensor-';
const nvrAcceleratedMotionSensorId = sdk.systemManager.getDeviceById('@scrypted/nvr', 'motion')?.id;
export class SmartOccupancySensor extends ScryptedDeviceBase implements Settings, Readme, OccupancySensor {
storageSettings = new StorageSettings(this, {
camera: {
title: 'Camera',
description: 'Select the camera or doorbell image to analyze periodically.',
type: 'device',
deviceFilter: `(type === '${ScryptedDeviceType.Doorbell}' || type === '${ScryptedDeviceType.Camera}') && interfaces.includes('${ScryptedInterface.Camera}')`,
immediate: true,
},
objectDetection: {
title: 'Object Detector',
description: 'Select the object detection plugin to use for detecting objects.',
type: 'device',
deviceFilter: `interfaces.includes('ObjectDetectionPreview') && id !== '${nvrAcceleratedMotionSensorId}'`,
immediate: true,
},
detections: {
title: 'Detections',
description: 'The detections that will trigger this occupancy sensor.',
multiple: true,
choices: [],
},
occupancyInterval: {
title: 'Occupancy Check Interval',
description: 'The interval in minutes that the sensor will check for occupancy.',
type: 'number',
defaultValue: 60,
// save and restore in seconds for consistency.
mapPut(oldValue, newValue) {
return newValue * 60;
},
mapGet(value) {
return value / 60;
},
},
zone: {
title: 'Edit Intersect Zone',
description: 'Optional: Configure the intersect zone for the occupancy check. Objects intersecting this zone will trigger the occupancy sensor.',
type: 'clippath',
},
captureZone: {
title: 'Edit Crop Zone',
description: 'Optional: Configure the capture zone for the occupancy check. The image will be cropped to this zone before detection. Cropping to desired location will improve detection performance.',
type: 'clippath',
},
minScore: {
title: 'Minimum Score',
description: 'The minimum score required for a detection to trigger the occupancy sensor.',
type: 'number',
defaultValue: 0.4,
},
labels: {
group: 'Recognition',
title: 'Labels',
description: 'The labels (license numbers, names) that will trigger this smart occupancy sensor.',
multiple: true,
combobox: true,
choices: [],
},
labelDistance: {
group: 'Recognition',
title: 'Label Distance',
description: 'The maximum edit distance between the detected label and the desired label. Ie, a distance of 1 will match "abcde" to "abcbe" or "abcd".',
type: 'number',
defaultValue: 2,
},
labelScore: {
group: 'Recognition',
title: 'Label Score',
description: 'The minimum score required for a label to trigger the occupancy sensor.',
type: 'number',
defaultValue: 0,
}
});
detectionListener: EventListenerRegister;
occupancyTimeout: NodeJS.Timeout;
occupancyInterval: NodeJS.Timeout;
constructor(public plugin: ObjectDetectionPlugin, nativeId?: ScryptedNativeId) {
super(nativeId);
this.storageSettings.settings.zone.onGet = async () => {
return {
deviceFilter: this.storageSettings.values.camera?.id,
}
};
this.storageSettings.settings.captureZone.onGet = async () => {
return {
deviceFilter: this.storageSettings.values.camera?.id,
}
};
this.storageSettings.settings.detections.onGet = async () => {
const objectDetection: ObjectDetection = this.storageSettings.values.objectDetection;
const choices = (await objectDetection?.getDetectionModel())?.classes || [];
return {
hide: !objectDetection,
choices,
};
};
this.storageSettings.settings.detections.onPut = () => this.rebind();
this.storageSettings.settings.objectDetection.onPut = () => this.rebind();
this.storageSettings.settings.zone.onPut = () => this.rebind();
this.storageSettings.settings.captureZone.onPut = () => this.rebind();
this.rebind();
}
resetOccupiedTimeout() {
clearTimeout(this.occupancyTimeout);
this.occupancyTimeout = undefined;
}
clearOccupancyInterval() {
clearInterval(this.occupancyInterval);
this.occupancyInterval = undefined;
}
trigger() {
this.resetOccupiedTimeout();
this.occupied = true;
const duration: number = this.storageSettings.values.occupancyInterval;
if (!duration)
return;
this.occupancyTimeout = setTimeout(() => {
this.occupied = false;
}, duration * 60000 + 10000);
}
checkDetection(detections: string[], labels: string[], labelDistance: number, labelScore: number, detected: ObjectsDetected) {
const match = detected.detections?.find(d => {
if (d.score && d.score < this.storageSettings.values.minScore)
return false;
if (!detections?.includes(d.className))
return false;
const zone: ClipPath = this.storageSettings.values.zone;
if (zone?.length >= 3) {
if (!d.boundingBox)
return false;
const detectionBoxPath = normalizeBoxToClipPath(d.boundingBox, detected.inputDimensions);
if (!polygonOverlap(detectionBoxPath, zone))
return false;
}
if (!labels?.length)
return true;
if (!d.label)
return false;
for (const label of labels) {
if (label === d.label) {
if (!labelScore || d.labelScore >= labelScore)
return true;
this.console.log('Label score too low.', d.labelScore);
continue;
}
if (!labelDistance)
continue;
if (levenshteinDistance(label, d.label) > labelDistance) {
this.console.log('Label does not match.', label, d.label, d.labelScore);
continue;
}
if (!labelScore || d.labelScore >= labelScore)
return true;
this.console.log('Label score too low.', d.labelScore);
}
return false;
});
if (match) {
if (!this.occupied)
this.console.log('Occupancy Sensor triggered on', match);
this.trigger();
}
}
async runDetection() {
try {
const objectDetection: ObjectDetection = this.storageSettings.values.objectDetection;
if (!objectDetection) {
this.console.error('no object detection plugin selected');
return;
}
const camera: ScryptedDevice & Camera = this.storageSettings.values.camera;
if (!camera) {
this.console.error('no camera selected');
return;
}
const picture = await camera.takePicture({
reason: 'event',
});
const zone: ClipPath = this.storageSettings.values.captureZone;
let detected: ObjectsDetected;
if (zone?.length >= 3) {
const image = await sdk.mediaManager.convertMediaObject<Image>(picture, ScryptedMimeTypes.Image);
let left = image.width;
let top = image.height;
let right = 0;
let bottom = 0;
for (const point of zone) {
left = Math.min(left, point[0]);
top = Math.min(top, point[1]);
right = Math.max(right, point[0]);
bottom = Math.max(bottom, point[1]);
}
left = left * image.width;
top = top * image.height;
right = right * image.width;
bottom = bottom * image.height;
let width = right - left;
let height = bottom - top;
// square it for standard detection
width = height = Math.max(width, height);
// recenter it
left = left + (right - left - width) / 2;
top = top + (bottom - top - height) / 2;
// ensure bounds are within image.
left = Math.max(0, left);
top = Math.max(0, top);
width = Math.min(width, image.width - left);
height = Math.min(height, image.height - top);
const cropped = await image.toImage({
crop: {
left,
top,
width,
height,
},
});
detected = await objectDetection.detectObjects(cropped);
// adjust the origin of the bounding boxes for the crop.
for (const d of detected.detections) {
d.boundingBox[0] += left;
d.boundingBox[1] += top;
}
detected.inputDimensions = [image.width, image.height];
}
else {
detected = await objectDetection.detectObjects(picture);
}
this.checkDetection(this.storageSettings.values.detections, this.storageSettings.values.labels, this.storageSettings.values.labelDistance, this.storageSettings.values.labelScore, detected);
}
catch (e) {
this.console.error('failed to take picture', e);
}
}
rebind() {
this.occupied = false;
this.detectionListener?.removeListener();
this.detectionListener = undefined;
this.resetOccupiedTimeout();
this.clearOccupancyInterval();
this.runDetection();
this.occupancyInterval = setInterval(() => {
this.runDetection();
}, this.storageSettings.values.occupancyInterval * 60000);
// camera may have an object detector that can also be observed for occupancy for free.
const objectDetector: ObjectDetector & ScryptedDevice = this.storageSettings.values.camera;
if (!objectDetector)
return;
const detections: string[] = this.storageSettings.values.detections;
if (!detections?.length)
return;
const { labels, labelDistance, labelScore } = this.storageSettings.values;
this.detectionListener = objectDetector.listen(ScryptedInterface.ObjectDetector, (source, details, data) => {
const detected: ObjectsDetected = data;
this.checkDetection(detections, labels, labelDistance, labelScore, detected);
});
}
async getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
}
putSetting(key: string, value: SettingValue): Promise<void> {
return this.storageSettings.putSetting(key, value);
}
async getReadmeMarkdown(): Promise<string> {
return `
## Smart Occupancy Sensor
This Occupancy Sensor remains triggered while specified objects (vehicle, person, animal, etc) are detected on a camera. The sensor can then be synced to other platforms such as HomeKit, Google Home, Alexa, or Home Assistant for use in automations. This Sensor requires an object detector plugin such as Scrypted NVR, OpenVINO, CoreML, ONNX, or Tensorflow-lite.`;
}
}

Some files were not shown because too many files have changed in this diff Show More