Compare commits

...

185 Commits

Author SHA1 Message Date
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
106 changed files with 3168 additions and 4803 deletions

3
.gitmodules vendored
View File

@@ -23,6 +23,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;
}

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';
@@ -76,6 +76,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
localStorage: device.storage,
device,
exports: {} as any,
PanTiltZoomMovement,
SettingsMixinDeviceBase,
ScryptedMimeTypes,
ScryptedInterface,

View File

@@ -1,6 +1,6 @@
# Home Assistant Addon Configuration
name: Scrypted
version: "v0.116.0-jammy-full"
version: "v0.118.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

@@ -73,7 +73,9 @@ FROM header as base
# intel opencl gpu and npu for openvino
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# Disable NPU on docker, because level-zero crashes openvino on older systems.
# RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow 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.
@@ -77,6 +76,14 @@ services:
# Default volume for the Scrypted database. Typically should not be changed.
- ~/.scrypted/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:
@@ -120,12 +127,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

@@ -37,13 +37,17 @@ 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
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

View File

@@ -33,20 +33,20 @@ 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
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-driver-compiler-npu_1.8.0.20240916-10885588273_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
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-fw-npu_1.8.0.20240916-10885588273_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
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-level-zero-npu_1.8.0.20240916-10885588273_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
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-driver-compiler-npu_1.8.0.20240916-10885588273_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
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-fw-npu_1.8.0.20240916-10885588273_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
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.8.0/intel-level-zero-npu_1.8.0.20240916-10885588273_ubuntu24.04_amd64.deb
fi
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero_1.17.6+u22.04_amd64.deb

View File

@@ -0,0 +1,40 @@
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
set -e
# https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=24.04&target_type=deb_network
# need this apt for nvidia-utils
# needs either ubuntu 22.0.4 or 24.04
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,30 @@ 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 [ -d /dev/dri ]
then
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/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 +100,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 +113,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 +158,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

@@ -5,7 +5,9 @@ FROM header as base
# intel opencl gpu and npu for openvino
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# Disable NPU on docker, because level-zero crashes openvino on older systems.
# RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite

View File

@@ -0,0 +1,23 @@
#!/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
(apt -y --fix-broken install && (yes | dpkg --configure -a) && apt -y update && apt -y dist-upgrade) &
# foreground pull if requested.
if [ -e "volume/.pull" ]
then
rm -rf volume/.pull
docker compose pull && docker container prune -f && docker image prune -a -f
else
# always background pull in case there's a broken image.
(docker compose pull && docker container prune -f && docker image prune -a -f) &
fi
# 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

@@ -1,3 +1,11 @@
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
@@ -56,7 +64,7 @@ then
echo "'local-lvm' or 'local-zfs'."
echo ""
echo "#############################################################################"
echo "Paste the following command into this shell to install to local-lvm instead:"
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 "#############################################################################"
@@ -103,18 +111,36 @@ then
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
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 '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==\"drm\", 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 "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."

BIN
install/proxmox/lxc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,63 @@
#!/bin/bash
NVR_STORAGE=$1
DISK_TYPE="large"
if [ ! -z "$FAST_DISK" ]
then
DISK_TYPE="fast"
fi
if [ -z "$NVR_STORAGE" ]; then
echo ""
echo "Error: Proxmox Directory Disk not provided. Usage:"
echo ""
echo "bash $0 <proxmox-directory-disk>"
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
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.
STORAGE="$STORAGE/mounts/scrypted-nvr"
# create the hidden folder that can be used as a marker.
mkdir -p $STORAGE/.nvr
chmod 0777 $STORAGE
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: $STORAGE mnt/nvr/$DISK_TYPE/$NVR_STORAGE none bind,optional,create=dir" >> "$FILE"
echo "Starting Scrypted..."
pct start $VMID

View File

@@ -9,16 +9,16 @@
"version": "1.3.6",
"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.60",
"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",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
"typescript": "^5.6.2"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -74,19 +74,10 @@
"@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.60",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.60.tgz",
"integrity": "sha512-oapFYQvyHLp0odCSx//USNnGNegS9ZL6a1HFIZzjDdMj2MNszTqiucAcu/wAlBwqjgURlP4/8xeLGVHEa4S2uQ=="
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
@@ -127,12 +118,12 @@
}
},
"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/acorn": {
@@ -157,9 +148,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"
},
@@ -268,15 +259,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 +279,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 +298,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 +313,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 +348,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 +376,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 +402,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 +416,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 +610,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 +623,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 +753,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

@@ -13,14 +13,14 @@
"license": "ISC",
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^20.11.30",
"@types/node": "^22.7.4",
"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.60",
"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);
}
@@ -874,6 +880,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
authorization,
cloudAddress,
hostname,
serverId,
},
connectRPCObject,
fork: undefined,

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

@@ -1,12 +1,12 @@
{
"name": "@scrypted/cloud",
"version": "0.2.37",
"version": "0.2.47",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/cloud",
"version": "0.2.37",
"version": "0.2.47",
"dependencies": {
"@eneris/push-receiver": "^4.2.0",
"@scrypted/common": "file:../../common",

View File

@@ -53,5 +53,5 @@
"@types/node": "^22.5.2",
"ts-node": "^10.9.2"
},
"version": "0.2.37"
"version": "0.2.47"
}

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',
@@ -258,6 +258,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 +267,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 +310,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 +332,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 +359,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 +428,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 +447,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 +617,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 +632,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 +672,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 +724,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 +738,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 +746,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 +787,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 +829,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 +840,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 +897,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 +912,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 +940,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 +969,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 +1018,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 +1053,10 @@ 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 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 +1131,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 +1154,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

@@ -1,24 +1,24 @@
{
"name": "@scrypted/core",
"version": "0.3.68",
"version": "0.3.82",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.3.68",
"version": "0.3.82",
"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,22 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.45",
"version": "0.3.63",
"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.24.7",
"adm-zip": "^0.5.14",
"axios": "^1.7.3",
"babel-loader": "^9.1.3",
"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.93.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -116,11 +115,11 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"@types/node": "^22.1.0",
"@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.5"
}
},
"node_modules/@scrypted/common": {
@@ -131,19 +130,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 +153,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 +191,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 +229,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 +241,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 +253,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 +281,33 @@
"@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.24.7",
"@types/node": "^22.1.0",
"@types/stringify-object": "^4.0.5",
"adm-zip": "^0.5.14",
"axios": "^1.7.3",
"babel-loader": "^9.1.3",
"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.5",
"typescript": "^5.5.4",
"webpack": "^5.93.0",
"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 +321,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 +346,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 +383,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.82",
"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,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,7 +1,10 @@
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';
@@ -48,7 +51,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;
@@ -61,6 +98,8 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
checkLxcDependencies();
this.storageSettings.settings.releaseChannel.hide = process.env.SCRYPTED_INSTALL_ENVIRONMENT !== 'lxc-docker';
this.indexHtml = readFileAsString('dist/index.html');
(async () => {
@@ -248,6 +287,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,8 +1,8 @@
import fs from 'fs';
import sdk from '@scrypted/sdk';
import child_process from 'child_process';
import { once } from 'events';
import sdk from '@scrypted/sdk';
import { stdout } from 'process';
import fs from 'fs';
import os from 'os';
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC = 'lxc';
@@ -42,12 +42,53 @@ export async function checkLxcDependencies() {
sdk.log.a('Failed to daemon-reload systemd.');
}
try {
const output = await new Promise<string>((r, f) => child_process.exec("sh -c 'apt list --installed | grep level-zero/'", (err, stdout, stderr) => {
if (err && !stdout && !stderr)
f(err);
else
r(stdout + '\n' + stderr);
}));
const cpuModel = os.cpus()[0].model;
if (cpuModel.includes('Core') && cpuModel.includes('Ultra')) {
if (
// apt
!output.includes('level-zero/')
) {
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;
}
}
else {
// level-zero crashes openvino on older CPU due to illegal instruction.
// so ensure it is not installed if this is not a core ultra system with npu.
if (
// apt
output.includes('level-zero/')
) {
const cp = child_process.spawn('apt', ['-y', 'remove', 'level-zero']);
const [exitCode] = await once(cp, 'exit');
console.log('level-zero removed', exitCode);
needRestart = true;
}
}
}
catch (e) {
sdk.log.a('Failed to verify/install intel-driver-compiler-npu.');
}
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) => {
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
@@ -59,8 +100,10 @@ export async function checkLxcDependencies() {
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
) {
|| output.includes('Version: 24.26.30049.6')
|| output.includes('Version: 24.31.30508.7')
// current script version: 24.35.30872.22
) {
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)
@@ -73,30 +116,6 @@ export async function checkLxcDependencies() {
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.');
}

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": "scrypted-nvr",
}

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",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/diagnostics",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"sharp": "^0.33.5"
},
"devDependencies": {
"@types/node": "^22.5.4"
},
"version": "0.0.17"
},
"../../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.62",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.24.7",
"adm-zip": "^0.5.14",
"axios": "^1.7.3",
"babel-loader": "^9.1.3",
"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.93.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.1.0",
"@types/stringify-object": "^4.0.5",
"stringify-object": "^3.3.0",
"ts-node": "^10.9.2",
"typedoc": "^0.26.5"
}
},
"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.24.7",
"@types/node": "^22.1.0",
"@types/stringify-object": "^4.0.5",
"adm-zip": "^0.5.14",
"axios": "^1.7.3",
"babel-loader": "^9.1.3",
"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.5",
"typescript": "^5.5.4",
"webpack": "^5.93.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
}
},
"version": "0.0.17"
}

View File

@@ -0,0 +1,37 @@
{
"name": "@scrypted/diagnostics",
"version": "0.0.17",
"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,540 @@
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',
}, 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 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 is available
await this.validate(this.console, 'GPU Passthrough', async () => {
if (!fs.existsSync('/dev/dri/renderD128'))
throw new Error('GPU device unvailable or not passed through to container.');
});
}
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 (openvinoPlugin) {
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/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

@@ -1,12 +1,12 @@
{
"name": "@scrypted/objectdetector",
"version": "0.1.43",
"version": "0.1.46",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/objectdetector",
"version": "0.1.43",
"version": "0.1.46",
"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.46",
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -334,8 +334,6 @@ 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) {
@@ -419,7 +417,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,
@@ -612,7 +610,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 +659,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;
}
@@ -1060,6 +1057,10 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
}, 10000)
}
checkHasEnabledMixin(device: ScryptedDevice): boolean {
return false;
}
pruneOldStatistics() {
const now = Date.now();
for (const [k, v] of this.objectDetectionStatistics.entries()) {

View File

@@ -4,3 +4,24 @@ This plugin adds object detection capabilities to any camera in Scrypted. Having
The ONNX Plugin should only be used if you are a Scrypted NVR user. It will provide no
benefits to HomeKit, which does its own detection processing.
# Windows Setup
Windows setup requires several installation steps and system PATH variables to be set correctly. The NVIDIA installers does not do this correctly if older CUDA or CUDNN exists.
1. Install latest NVIDIA drivers.
2. Install CUDA 12.x.
3. Install CUDNN 9.x.
4. Open a new Terminal.
5. Verify CUDA_PATH environment is set.
* The syste, CUDA_PATH can be set in Windows Advanced System Settings.
6. Verify PATH contains the path to CUDNN\bin\12.x, where the `cudnn64_9.dll` file is located. Typically it will be somewhere like: `"C:\Program Files\NVIDIA\CUDNN\v9.4\bin\12.6\cudnn64_9.dll"`.
* The system PATH can be set in Windows Advanced System Settings.
7. Exit Scrypted.
8. Reopen Scrypted.
# Linux Setup
1. Install NVIDIA drivers on host.
2. Install CUDA and CUDNN.
3. Follow the NVIDIA setup steps for the NVIDIA docker image. https://docs.scrypted.app/installation.html#linux-docker

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/onnx",
"version": "0.1.110",
"version": "0.1.113",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/onnx",
"version": "0.1.110",
"version": "0.1.113",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -42,5 +42,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.110"
"version": "0.1.113"
}

View File

@@ -4,7 +4,7 @@ numpy==1.26.4
# uncomment to require cuda 12, but most stuff is still targetting cuda 11.
# however, stuff targetted for cuda 11 can still run on cuda 12.
# --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
onnxruntime-gpu==1.18.1; 'darwin' not in sys_platform and platform_machine != 'aarch64'
onnxruntime-gpu==1.19.2; 'darwin' not in sys_platform and platform_machine != 'aarch64'
# cpu and coreml execution provider
onnxruntime; 'darwin' in sys_platform or platform_machine == 'aarch64'
# nightly?

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/onvif",
"version": "0.1.23",
"version": "0.1.28",
"description": "ONVIF Camera Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",
@@ -42,7 +42,7 @@
"@scrypted/sdk": "file:../../sdk",
"base-64": "^1.0.0",
"md5": "^2.3.0",
"onvif": "file:./onvif",
"onvif": "^0.7.4",
"xml2js": "^0.6.2"
},
"devDependencies": {

View File

@@ -20,12 +20,53 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
],
onPut: (ov, ptz: string[]) => {
this.ptzCapabilities = {
...this.ptzCapabilities,
pan: ptz.includes('Pan'),
tilt: ptz.includes('Tilt'),
zoom: ptz.includes('Zoom'),
}
}
}
},
ptzMovementType: {
title: 'PTZ Movement Type',
description: 'The type of movement to use for PTZ commands by default.',
type: 'string',
choices: [
'Default',
PanTiltZoomMovement.Absolute,
PanTiltZoomMovement.Relative,
PanTiltZoomMovement.Continuous,
],
defaultValue: 'Default',
},
presets: {
title: 'Presets',
description: 'PTZ Presets in the format "key=name". Where key is the PTZ Preset identifier and name is a friendly name.',
multiple: true,
defaultValue: [],
combobox: true,
onPut: async (ov, presets: string[]) => {
const caps = {
...this.ptzCapabilities,
presets: {},
};
for (const preset of presets) {
const [key, name] = preset.split('=');
caps.presets[key] = name;
}
this.ptzCapabilities = caps;
},
mapGet: () => {
const presets = this.ptzCapabilities?.presets || {};
return Object.entries(presets).map(([key, name]) => key + '=' + name);
},
},
cachedPresets: {
multiple: true,
hide: true,
json: true,
defaultValue: {},
},
});
constructor(options: SettingsMixinDeviceOptions<Settings>) {
@@ -33,6 +74,30 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
// force a read to set the state.
this.storageSettings.values.ptz;
this.refreshPresets();
this.storageSettings.settings.presets.onGet = async () => {
// getPresets is where the key is the name of the preset, and the value is the id.
// kind of weird and backwards.
const choices = Object.entries(this.storageSettings.values.cachedPresets).map(([name, key]) => key + '=' + name);
return {
choices,
};
};
}
async refreshPresets() {
const client = await this.getClient();
client.cam.getPresets({}, (e, result, xml) => {
if (e) {
this.console.error('failed to get presets', e);
}
else {
this.console.log('presets', result);
this.storageSettings.values.cachedPresets = result;
}
});
}
getMixinSettings(): Promise<Setting[]> {
@@ -55,19 +120,65 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
};
}
if (command.movement === PanTiltZoomMovement.Absolute) {
let movement = command.movement || this.storageSettings.values.ptzMovementType;
if (movement === PanTiltZoomMovement.Absolute) {
return new Promise<void>((r, f) => {
client.cam.absoluteMove({
x: command.pan,
y: command.tilt,
zoom: command.zoom,
speed: speed
speed: speed,
}, (e, result, xml) => {
if (e)
return f(e);
r();
});
})
}
else if (movement === PanTiltZoomMovement.Continuous) {
let x = command.pan;
let y = command.tilt;
let zoom = command.zoom;
if (command.speed?.pan)
x *= command.speed.pan;
if (command.speed?.tilt)
y *= command.speed.tilt;
if (command.speed?.zoom)
zoom *= command.speed.zoom;
return new Promise<void>((r, f) => {
client.cam.continuousMove({
x: command.pan,
y: command.tilt,
zoom: command.zoom,
timeout: command.timeout || 1000,
}, (e, result, xml) => {
if (e)
return f(e);
r();
})
})
});
}
else if (movement === PanTiltZoomMovement.Home) {
return new Promise<void>((r, f) => {
client.cam.gotoHomePosition({
speed: speed,
}, (e, result, xml) => {
if (e)
return f(e);
r();
})
});
}
else if (movement === PanTiltZoomMovement.Preset) {
return new Promise<void>((r, f) => {
client.cam.gotoPreset({
preset: command.preset,
}, (e, result, xml) => {
if (e)
return f(e);
r();
})
});
}
else {
// relative movement is default.
@@ -82,7 +193,7 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
return f(e);
r();
})
})
});
}
}

View File

@@ -6,7 +6,7 @@
"configurations": [
{
"name": "Scrypted Debugger",
"type": "python",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "${config:scrypted.debugHost}",

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/openvino",
"version": "0.1.111",
"version": "0.1.118",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/openvino",
"version": "0.1.111",
"version": "0.1.118",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -42,5 +42,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.111"
"version": "0.1.118"
}

View File

@@ -125,7 +125,7 @@ class OpenVINOPlugin(
except:
pass
mode = self.storage.getItem("mode")
mode = self.storage.getItem("mode") or "Default"
if mode == "Default":
mode = "AUTO"
@@ -136,6 +136,7 @@ class OpenVINOPlugin(
mode = f"AUTO:NPU,CPU"
elif len(dgpus):
mode = f"AUTO:{','.join(dgpus)},CPU"
# forcing GPU can cause crashes on older GPU.
elif gpu:
mode = f"GPU"
@@ -164,8 +165,6 @@ class OpenVINOPlugin(
self.sigmoid = model == "yolo-v4-tiny-tf"
self.modelName = model
print(f"model/mode/precision: {model}/{mode}/{precision}")
ovmodel = "best" if self.scrypted_model else model
model_version = "v5"
@@ -202,19 +201,28 @@ class OpenVINOPlugin(
try:
self.compiled_model = self.core.compile_model(xmlFile, mode)
print(
"EXECUTION_DEVICES",
self.compiled_model.get_property("EXECUTION_DEVICES"),
)
except:
import traceback
traceback.print_exc()
print("Reverting all settings.")
self.storage.removeItem("mode")
self.storage.removeItem("model")
self.storage.removeItem("precision")
self.requestRestart()
if mode == "GPU":
try:
print("GPU mode failed, reverting to AUTO.")
mode = "AUTO"
self.mode = mode
self.compiled_model = self.core.compile_model(xmlFile, mode)
except:
print("Reverting all settings.")
self.storage.removeItem("mode")
self.storage.removeItem("model")
self.storage.removeItem("precision")
self.requestRestart()
print(
"EXECUTION_DEVICES",
self.compiled_model.get_property("EXECUTION_DEVICES"),
)
print(f"model/mode/precision: {model}/{mode}/{precision}")
# mobilenet 1,300,300,3
# yolov3/4 1,416,416,3

View File

@@ -2,6 +2,6 @@ import concurrent.futures
def create_executors(name: str):
prepare = concurrent.futures.ThreadPoolExecutor(1, "OpenVINO-{f}Prepare")
predict = concurrent.futures.ThreadPoolExecutor(1, "OpenVINO-{f}}Predict")
prepare = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Prepare")
predict = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Predict")
return prepare, predict

View File

@@ -1,5 +1,7 @@
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
numpy==1.26.4
openvino==2024.3.0
# openvino 2024.3.0 crashes on older CPU (J4105 and older) if level-zero is installed via apt.
# openvino 2024.2.0 and older crashes on arc dGPU.
openvino==2024.4.0
Pillow==10.3.0
opencv-python==4.10.0.84

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/reolink",
"version": "0.0.94",
"version": "0.0.96",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/reolink",
"version": "0.0.94",
"version": "0.0.96",
"license": "Apache",
"dependencies": {
"@scrypted/common": "file:../../common",
@@ -35,24 +35,23 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.46",
"version": "0.3.65",
"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.24.7",
"adm-zip": "^0.5.14",
"axios": "^1.7.3",
"babel-loader": "^9.1.3",
"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.93.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -64,11 +63,11 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"@types/node": "^22.1.0",
"@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.5"
}
},
"../onvif/onvif": {

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/reolink",
"version": "0.0.94",
"version": "0.0.97",
"description": "Reolink Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -94,6 +94,35 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
this.updatePtzCaps();
},
},
presets: {
subgroup: 'Advanced',
title: 'Presets',
description: 'PTZ Presets in the format "id=name". Where id is the PTZ Preset identifier and name is a friendly name.',
multiple: true,
defaultValue: [],
combobox: true,
onPut: async (ov, presets: string[]) => {
const caps = {
...this.ptzCapabilities,
presets: {},
};
for (const preset of presets) {
const [key, name] = preset.split('=');
caps.presets[key] = name;
}
this.ptzCapabilities = caps;
},
mapGet: () => {
const presets = this.ptzCapabilities?.presets || {};
return Object.entries(presets).map(([key, name]) => key + '=' + name);
},
},
cachedPresets: {
multiple: true,
hide: true,
json: true,
defaultValue: [],
},
deviceInfo: {
json: true,
hide: true
@@ -134,9 +163,21 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
}
};
this.storageSettings.settings.presets.onGet = async () => {
const choices = this.storageSettings.values.cachedPresets.map((preset) => preset.id + '=' + preset.name);
return {
choices,
};
};
this.updateDeviceInfo();
(async () => {
this.updatePtzCaps();
try {
await this.getPresets();
} catch (e) {
this.console.log('Fail fetching presets', e);
}
const api = this.getClient();
const deviceInfo = await api.getDeviceInfo();
this.storageSettings.values.deviceInfo = deviceInfo;
@@ -160,12 +201,20 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
updatePtzCaps() {
const { ptz } = this.storageSettings.values;
this.ptzCapabilities = {
...this.ptzCapabilities,
pan: ptz?.includes('Pan'),
tilt: ptz?.includes('Tilt'),
zoom: ptz?.includes('Zoom'),
}
}
async getPresets() {
const client = this.getClient();
const ptzPresets = await client.getPtzPresets();
this.console.log(`Presets: ${JSON.stringify(ptzPresets)}`)
this.storageSettings.values.cachedPresets = ptzPresets;
}
async updateAbilities() {
const api = this.getClient();
const abilities = await api.getAbility();

View File

@@ -39,6 +39,11 @@ export type SirenResponse = {
rspCode: number;
}
export interface PtzPreset {
id: number;
name: string;
}
export class ReolinkCameraClient {
credential: AuthFetchCredentialState;
parameters: Record<string, string>;
@@ -61,6 +66,13 @@ export class ReolinkCameraClient {
return response;
}
private createReadable = (data: any) => {
const pt = new PassThrough();
pt.write(Buffer.from(JSON.stringify(data)));
pt.end();
return pt;
}
async login() {
if (this.tokenLease > Date.now()) {
return;
@@ -201,23 +213,37 @@ export class ReolinkCameraClient {
return response.body?.[0]?.value?.DevInfo;
}
private async ptzOp(op: string, speed: number) {
async getPtzPresets(): Promise<PtzPreset[]> {
const url = new URL(`http://${this.host}/api.cgi`);
const params = url.searchParams;
params.set('cmd', 'GetPtzPreset');
const body = [
{
cmd: "GetPtzPreset",
action: 1,
param: {
channel: this.channelId
}
}
];
const response = await this.requestWithLogin({
url,
responseType: 'json',
method: 'POST'
}, this.createReadable(body));
return response.body?.[0]?.value?.PtzPreset?.filter(preset => preset.enable === 1);
}
private async ptzOp(op: string, speed: number, id?: number) {
const url = new URL(`http://${this.host}/api.cgi`);
const params = url.searchParams;
params.set('cmd', 'PtzCtrl');
const createReadable = (data: any) => {
const pt = new PassThrough();
pt.write(Buffer.from(JSON.stringify(data)));
pt.end();
return pt;
}
const c1 = this.requestWithLogin({
url,
method: 'POST',
responseType: 'text',
}, createReadable([
}, this.createReadable([
{
cmd: "PtzCtrl",
param: {
@@ -225,6 +251,7 @@ export class ReolinkCameraClient {
op,
speed,
timeout: 1,
id
}
},
]));
@@ -234,7 +261,7 @@ export class ReolinkCameraClient {
const c2 = this.requestWithLogin({
url,
method: 'POST',
}, createReadable([
}, this.createReadable([
{
cmd: "PtzCtrl",
param: {
@@ -248,7 +275,37 @@ export class ReolinkCameraClient {
this.console.log(await c2);
}
private async presetOp(speed: number, id: number) {
const url = new URL(`http://${this.host}/api.cgi`);
const params = url.searchParams;
params.set('cmd', 'PtzCtrl');
const c1 = this.requestWithLogin({
url,
method: 'POST',
responseType: 'text',
}, this.createReadable([
{
cmd: "PtzCtrl",
param: {
channel: this.channelId,
op: 'ToPos',
speed,
id
}
},
]));
}
async ptz(command: PanTiltZoomCommand) {
// reolink doesnt accept signed values to ptz
// in favor of explicit direction.
// so we need to convert the signed values to abs explicit direction.
if (command.preset && !Number.isNaN(Number(command.preset))) {
await this.presetOp(1, Number(command.preset));
return;
}
let op = '';
if (command.pan < 0)
op += 'Left';
@@ -260,16 +317,17 @@ export class ReolinkCameraClient {
op += 'Up';
if (op) {
await this.ptzOp(op, Math.round((command?.pan || command?.tilt || 1) * 10));
await this.ptzOp(op, Math.ceil(Math.abs(command?.pan || command?.tilt || 1) * 10));
}
op = undefined;
if (command.zoom < 0)
op = 'ZoomDec';
else if (command.zoom > 0)
op = 'ZoomInc';
if (op) {
await this.ptzOp(op, Math.round((command?.zoom || 1) * 10));
await this.ptzOp(op, Math.ceil(Math.abs(command?.zoom || 1) * 10));
}
}
@@ -277,12 +335,6 @@ export class ReolinkCameraClient {
const url = new URL(`http://${this.host}/api.cgi`);
const params = url.searchParams;
params.set('cmd', 'AudioAlarmPlay');
const createReadable = (data: any) => {
const pt = new PassThrough();
pt.write(Buffer.from(JSON.stringify(data)));
pt.end();
return pt;
}
let alarmMode;
if (duration) {
@@ -302,7 +354,7 @@ export class ReolinkCameraClient {
url,
method: 'POST',
responseType: 'json',
}, createReadable([
}, this.createReadable([
{
cmd: "AudioAlarmPlay",
action: 0,

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/ring",
"version": "0.0.140",
"version": "0.0.144",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/ring",
"version": "0.0.140",
"version": "0.0.144",
"dependencies": {
"@koush/ring-client-api": "file:../../external/ring-client-api",
"@scrypted/common": "file:../../common",
@@ -26,13 +26,13 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"http-auth-utils": "^3.0.2",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
"http-auth-utils": "^5.0.1",
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
"@types/node": "^20.11.0",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2"
}
},
"../../external/ring-client-api": {
@@ -42,30 +42,29 @@
],
"devDependencies": {
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@swc-node/register": "^1.6.2",
"turbo": "^1.8.5"
"@changesets/cli": "^2.26.2",
"@swc-node/register": "^1.6.6",
"turbo": "^1.10.12"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.101",
"version": "0.3.62",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.24.7",
"adm-zip": "^0.5.14",
"axios": "^1.7.3",
"babel-loader": "^9.1.3",
"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.93.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -77,11 +76,11 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"@types/node": "^22.1.0",
"@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.5"
}
},
"node_modules/@koush/ring-client-api": {

View File

@@ -45,5 +45,5 @@
"got": "11.8.6",
"socket.io-client": "^2.5.0"
},
"version": "0.0.140"
"version": "0.0.144"
}

View File

@@ -3,14 +3,14 @@ import { RefreshPromise } from "@scrypted/common/src/promise-utils";
import { connectRTCSignalingClients } from '@scrypted/common/src/rtc-signaling';
import { RtspServer } from '@scrypted/common/src/rtsp-server';
import { addTrackControls, parseSdp, replacePorts } from '@scrypted/common/src/sdp-utils';
import sdk, { BinarySensor, Camera, Device, DeviceProvider, FFmpegInput, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
import sdk, { BinarySensor, Camera, Device, DeviceProvider, FFmpegInput, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingOptions, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
import child_process, { ChildProcess } from 'child_process';
import dgram from 'dgram';
import { RtcpReceiverInfo, RtcpRrPacket } from '../../../external/werift/packages/rtp/src/rtcp/rr';
import { RtpPacket } from '../../../external/werift/packages/rtp/src/rtp/rtp';
import { ProtectionProfileAes128CmHmacSha1_80 } from '../../../external/werift/packages/rtp/src/srtp/const';
import { SrtcpSession } from '../../../external/werift/packages/rtp/src/srtp/srtcp';
import { VideoSearchResult, BasicPeerConnection, CameraData, clientApi, isStunMessage, RingBaseApi, RingCamera, RtpDescription, rxjs, SimpleWebRtcSession, SipSession, StreamingSession } from './ring-client-api';
import { BasicPeerConnection, CameraData, clientApi, isStunMessage, RingBaseApi, RingCamera, RtpDescription, rxjs, SimpleWebRtcSession, SipSession, StreamingSession, VideoSearchResult } from './ring-client-api';
import { encodeSrtpOptions, getPayloadType, getSequenceNumber, isRtpMessagePayloadType } from './srtp-utils';
const STREAM_TIMEOUT = 120000;
@@ -101,10 +101,6 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
this.console.log(camera.name, 'onDoorbellPressed', e);
this.triggerBinaryState();
});
camera.onDoorbellPressedPolling.subscribe(async e => {
this.console.log(camera.name, 'onDoorbellPressed', e);
this.triggerBinaryState();
});
let motionTimeout: NodeJS.Timeout;
const resetTimeout = () => {
clearTimeout(motionTimeout);
@@ -120,16 +116,6 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
}
this.motionDetected = motionDetected;
});
camera.onMotionDetectedPolling?.subscribe(async motionDetected => {
if (motionDetected) {
this.console.log(camera.name, 'onMotionDetected');
resetTimeout();
}
else {
clearTimeout(motionTimeout);
}
this.motionDetected = motionDetected;
});
camera.onBatteryLevel?.subscribe(async () => {
this.batteryLevel = camera.batteryLevel;
});
@@ -279,7 +265,7 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
this.stopSession();
const { clientPromise: playbackPromise, port: playbackPort, url: clientUrl } = await listenZeroSingleClient();
const { clientPromise: playbackPromise, port: playbackPort, url: clientUrl } = await listenZeroSingleClient('127.0.0.1');
const useRtsp = this.useRtsp;
@@ -527,6 +513,11 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
let answerSdp: string;
const simple = this.camera.createSimpleWebRtcSession();
const options: RTCSignalingOptions = {
requiresOffer: true,
disableTrickle: true,
};
await connectRTCSignalingClients(this.console, session, {
type: 'offer',
audio: {
@@ -537,6 +528,10 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
},
getUserMediaSafariHack: true,
}, {
__proxy_props: {
options,
},
options,
createLocalDescription: async (type: 'offer' | 'answer', setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate) => {
if (type !== 'answer')
throw new Error('Ring Camera default endpoint only supports RTC answer');
@@ -639,7 +634,7 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
createPeerConnection: () => basicPc,
});
ringSession.connection.onMessage.subscribe(message => this.console.log('incoming message', message));
ringSession.onCallEnded.subscribe(() => this.console.error('call ended', ringSession.sessionId));
ringSession.onCallEnded.subscribe(() => this.console.error('call ended'));
sessionControl = new RingWebSocketRTCSessionControl(ringSession, onConnectionState);

View File

@@ -69,12 +69,6 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings,
description: 'Optional: If supplied will on show this locationID.',
hide: true,
},
cameraDingsPollingSeconds: {
title: 'Poll Interval',
type: 'number',
description: 'Optional: Change the default polling interval for motion and doorbell events.',
defaultValue: 5,
},
nightModeBypassAlarmState: {
title: 'Night Mode Bypass Alarm State',
description: 'Set this to enable the "Night" option on the alarm panel. When arming in "Night" mode, all open sensors will be bypassed and the alarm will be armed to the selected option.',
@@ -96,12 +90,6 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings,
constructor() {
super();
this.settingsStorage.settings.cameraDingsPollingSeconds.onGet = async () => {
return {
hide: !this.settingsStorage.values.polling,
};
}
this.discoverDevices()
.catch(e => this.console.error('discovery failure', e));
}
@@ -172,7 +160,7 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings,
ffmpegPath: await mediaManager.getFFmpegPath(),
locationIds,
cameraStatusPollingSeconds: this.settingsStorage.values.polling ? cameraStatusPollingSeconds : undefined,
cameraDingsPollingSeconds: this.settingsStorage.values.polling ? this.settingsStorage.values.cameraDingsPollingSeconds : undefined,
locationModePollingSeconds: this.settingsStorage.values.polling ? cameraStatusPollingSeconds : undefined,
systemId: this.settingsStorage.values.systemId,
}, {
createPeerConnection: () => {

View File

@@ -20,7 +20,7 @@ export function loadSharp() {
export const ImageReaderNativeId = 'imagereader';
async function createVipsMediaObject(image: VipsImage): Promise<Image & MediaObject> {
export async function createVipsMediaObject(image: VipsImage): Promise<Image & MediaObject> {
const ret: Image & MediaObject = await sdk.mediaManager.createMediaObject(image, ScryptedMimeTypes.Image, {
sourceId: image.sourceId,
width: image.width,

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/unifi-protect",
"version": "0.0.154",
"version": "0.0.156",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/unifi-protect",
"version": "0.0.154",
"version": "0.0.156",
"license": "Apache",
"dependencies": {
"@koush/unifi-protect": "file:../../external/unifi-protect",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/unifi-protect",
"version": "0.0.154",
"version": "0.0.156",
"description": "Unifi Protect Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -321,6 +321,10 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
this.console.log('skipping camera that is adopted by another nvr', camera.id, camera.name);
continue;
}
if (!camera.isAdopted) {
this.console.log('skipping camera that is not adopted', camera.id, camera.name);
continue;
}
let needUpdate = false;
for (const channel of camera.channels) {
@@ -598,6 +602,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
anonymousDeviceId: {},
id: {},
nativeId: {},
host: {},
},
}
});
@@ -607,14 +612,15 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
return this.storageSettings.values.idMaps.nativeId?.[nativeId] || nativeId;
}
getNativeId(device: { id?: string, mac?: string; anonymousDeviceId?: string }, update: boolean) {
const { id, mac, anonymousDeviceId } = device;
getNativeId(device: { id?: string, mac?: string; anonymousDeviceId?: string, host?: string }, update: boolean) {
const { id, mac, anonymousDeviceId,host } = device;
const idMaps = this.storageSettings.values.idMaps;
// try to find an existing nativeId given the mac and anonymous device id
const found = (mac && idMaps.mac[mac])
|| (anonymousDeviceId && idMaps.anonymousDeviceId[anonymousDeviceId])
|| (id && idMaps.id[id])
|| (host && idMaps.host[host])
;
// use the found id if one exists (device got provisioned a new id), otherwise use the id provided by the device.
@@ -623,7 +629,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
if (!update)
return nativeId;
// map the mac and anonymous device id to the native id.
// map the mac, host, and anonymous device id to the native id.
if (mac) {
idMaps.mac ||= {};
idMaps.mac[mac] = nativeId;
@@ -632,6 +638,10 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
idMaps.anonymousDeviceId ||= {};
idMaps.anonymousDeviceId[anonymousDeviceId] = nativeId;
}
if (host) {
idMaps.host ||= {};
idMaps.host[host] = nativeId;
}
// map the id and native id to each other.
idMaps.id ||= {};

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/webrtc",
"version": "0.2.51",
"version": "0.2.55",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/webrtc",
"version": "0.2.51",
"version": "0.2.55",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/webrtc",
"version": "0.2.51",
"version": "0.2.55",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
@@ -23,7 +23,6 @@
"name": "WebRTC Plugin",
"type": "API",
"interfaces": [
"EngineIOHandler",
"Settings",
"MediaConverter",
"MixinProvider",

View File

@@ -3,7 +3,7 @@ import { MediaStreamTrack, PeerConfig, RTCPeerConnection, RTCRtpCodecParameters,
import { Deferred } from "@scrypted/common/src/deferred";
import sdk, { FFmpegInput, FFmpegTranscodeStream, Intercom, MediaObject, MediaStreamDestination, MediaStreamFeedback, RequestMediaStream, RTCAVSignalingSetup, RTCConnectionManagement, RTCInputMediaObjectTrack, RTCOutputMediaObjectTrack, RTCSignalingOptions, RTCSignalingSession, ScryptedDevice, ScryptedMimeTypes } from "@scrypted/sdk";
import { ScryptedSessionControl } from "./session-control";
import { requiredAudioCodecs, requiredVideoCodec } from "./webrtc-required-codecs";
import { opusAudioCodecOnly, requiredAudioCodecs, requiredVideoCodec } from "./webrtc-required-codecs";
import { logIsLocalIceTransport } from "./werift-util";
import { addVideoFilterArguments } from "@scrypted/common/src/ffmpeg-helpers";
@@ -481,6 +481,7 @@ export class WebRTCConnectionManagement implements RTCConnectionManagement {
closed = false;
constructor(public console: Console, public clientSession: RTCSignalingSession,
public requireOpus: boolean,
public maximumCompatibilityMode: boolean,
public clientOptions: RTCSignalingOptions,
public options: {
@@ -494,7 +495,7 @@ export class WebRTCConnectionManagement implements RTCConnectionManagement {
// the cameras and alexa targets will also provide externally reachable addresses.
codecs: {
audio: [
...requiredAudioCodecs,
...(requireOpus ? opusAudioCodecOnly : requiredAudioCodecs),
],
video: [
requiredVideoCodec,
@@ -660,6 +661,7 @@ export async function createRTCPeerConnectionSink(
console: Console,
intercom: ScryptedDevice & Intercom,
mo: MediaObject,
requireOpus: boolean,
maximumCompatibilityMode: boolean,
configuration: RTCConfiguration,
weriftConfiguration: Partial<PeerConfig>,
@@ -668,7 +670,7 @@ export async function createRTCPeerConnectionSink(
const clientOptions = await legacyGetSignalingSessionOptions(clientSignalingSession);
// console.log('remote options', clientOptions);
const connection = new WebRTCConnectionManagement(console, clientSignalingSession, maximumCompatibilityMode, clientOptions, {
const connection = new WebRTCConnectionManagement(console, clientSignalingSession, requireOpus, maximumCompatibilityMode, clientOptions, {
configuration,
weriftConfiguration,
});

View File

@@ -1,12 +1,10 @@
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
import { Deferred } from '@scrypted/common/src/deferred';
import { listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
import { timeoutPromise } from '@scrypted/common/src/promise-utils';
import { createBrowserSignalingSession } from "@scrypted/common/src/rtc-connect";
import { legacyGetSignalingSessionOptions } from '@scrypted/common/src/rtc-signaling';
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from '@scrypted/common/src/settings-mixin';
import { createZygote } from '@scrypted/common/src/zygote';
import sdk, { ConnectOptions, DeviceCreator, DeviceCreatorSettings, DeviceProvider, FFmpegInput, HttpRequest, Intercom, MediaConverter, MediaObject, MediaObjectOptions, MixinProvider, RTCSessionControl, RTCSignalingChannel, RTCSignalingClient, RTCSignalingOptions, RTCSignalingSession, RequestMediaStream, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, SettingValue, Settings, VideoCamera, WritableDeviceState } from '@scrypted/sdk';
import sdk, { DeviceCreator, DeviceCreatorSettings, DeviceProvider, FFmpegInput, Intercom, MediaConverter, MediaObject, MediaObjectOptions, MixinProvider, RTCSessionControl, RTCSignalingChannel, RTCSignalingClient, RTCSignalingOptions, RTCSignalingSession, RequestMediaStream, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, SettingValue, Settings, VideoCamera, WritableDeviceState } from '@scrypted/sdk';
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import crypto from 'crypto';
import ip from 'ip';
@@ -56,6 +54,7 @@ class WebRTCMixin extends SettingsMixinDeviceBase<RTCSignalingClient & VideoCame
this.console,
undefined,
media,
this.plugin.storageSettings.values.requireOpus,
this.plugin.storageSettings.values.maximumCompatibilityMode,
this.plugin.getRTCConfiguration(),
await this.plugin.getWeriftConfiguration(),
@@ -129,6 +128,7 @@ class WebRTCMixin extends SettingsMixinDeviceBase<RTCSignalingClient & VideoCame
this.console,
hasIntercom ? device : undefined,
mo,
this.plugin.storageSettings.values.requireOpus,
this.plugin.storageSettings.values.maximumCompatibilityMode,
this.plugin.getRTCConfiguration(),
await this.plugin.getWeriftConfiguration(options?.disableTurn),
@@ -204,10 +204,15 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
],
defaultValue: 'Default',
},
requireOpus: {
group: 'Advanced',
title: 'Require Opus Audio Codec',
type: 'boolean',
},
maximumCompatibilityMode: {
group: 'Advanced',
title: 'Maximum Compatibility Mode',
description: 'Enables maximum compatibility with WebRTC clients by using the most conservative transcode options.',
description: 'Debug: Enables maximum compatibility with WebRTC clients by transcoding to known safe reference codecs. This setting will automatically reset when the plugin or Scrypted restarts.',
defaultValue: false,
type: 'boolean',
},
@@ -228,20 +233,6 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
}
},
},
rtcConfiguration: {
title: "Custom Client RTC Configuration",
type: 'textarea',
description: "RTCConfiguration that can be used to specify custom TURN and STUN servers. https://gist.github.com/koush/f7dafec7dbca04982a76db8243abc57e",
},
weriftConfiguration: {
title: "Custom Server RTC Configuration",
type: 'textarea',
description: "RTCConfiguration that can be used to specify custom TURN and STUN servers. https://gist.github.com/koush/631d38ac8647a86baaac7b22d863f010",
},
debugLog: {
title: 'Debug Log',
type: 'boolean',
},
ipv4Ban: {
group: 'Advanced',
title: '6to4 Ban',
@@ -254,7 +245,24 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
],
combobox: true,
multiple: true,
}
},
debugLog: {
group: 'Advanced',
title: 'Debug Log',
type: 'boolean',
},
rtcConfiguration: {
group: 'Advanced',
title: "Custom Client RTC Configuration",
type: 'textarea',
description: "RTCConfiguration that can be used to specify custom TURN and STUN servers. https://gist.github.com/koush/f7dafec7dbca04982a76db8243abc57e",
},
weriftConfiguration: {
group: 'Advanced',
title: "Custom Server RTC Configuration",
type: 'textarea',
description: "RTCConfiguration that can be used to specify custom TURN and STUN servers. https://gist.github.com/koush/631d38ac8647a86baaac7b22d863f010",
},
});
activeConnections = 0;
@@ -262,6 +270,9 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
super();
this.unshiftMixin = true;
// never want this on, should only be used for debugging.
this.storageSettings.values.maximumCompatibilityMode = false;
this.converters = [
["*/*", ScryptedMimeTypes.RTCSignalingChannel],
[ScryptedMimeTypes.RTCSignalingSession, ScryptedMimeTypes.RTCConnectionManagement],
@@ -293,6 +304,7 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
return createRTCPeerConnectionSink(session, console,
undefined,
mo,
plugin.storageSettings.values.requireOpus,
plugin.storageSettings.values.maximumCompatibilityMode,
plugin.getRTCConfiguration(),
await plugin.getWeriftConfiguration(),
@@ -310,6 +322,7 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
return createRTCPeerConnectionSink(session, console,
undefined,
mo,
plugin.storageSettings.values.requireOpus,
plugin.storageSettings.values.maximumCompatibilityMode,
plugin.getRTCConfiguration(),
await plugin.getWeriftConfiguration(),
@@ -334,6 +347,7 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
try {
const { createConnection } = await result.result;
connection = await createConnection({}, undefined, session,
this.storageSettings.values.requireOpus,
maximumCompatibilityMode,
clientOptions,
{
@@ -392,7 +406,8 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
const result = createTrackedFork();
try {
const connection = await timeoutPromise(2 * 60 * 1000, this.convertToRTCConnectionManagement(result, data, fromMimeType, toMimeType, options));
connection.waitClosed().finally(() => result.worker.terminate());
// wait a bit to allow ffmpegs to get terminated by the thread.
connection.waitClosed().finally(() => setTimeout(() => result.worker.terminate(), 30000));
return connection;
}
catch (e) {
@@ -586,111 +601,6 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
...ret,
};
}
async onConnection(request: HttpRequest, webSocketUrl: string) {
const weriftConfiguration = await this.getWeriftConfiguration();
const cleanup = new Deferred<string>();
cleanup.promise.then(e => this.console.log('cleaning up rtc connection:', e));
try {
const ws = new WebSocket(webSocketUrl);
cleanup.promise.finally(() => ws.close());
if (request.isPublicEndpoint) {
cleanup.resolve('public endpoint not supported');
return;
}
const client = await listenZeroSingleClient('127.0.0.1');
cleanup.promise.finally(() => {
client.cancel();
client.clientPromise.then(cp => cp.destroy()).catch(() => { });
});
const message = await new Promise<{
connectionManagementId: string,
updateSessionId: string,
} & ConnectOptions>((resolve, reject) => {
const close = () => {
const str = 'Connection closed while waiting for message';
reject(new Error(str));
cleanup.resolve(str);
};
ws.addEventListener('close', close);
ws.onmessage = message => {
ws.removeEventListener('close', close);
resolve(JSON.parse(message.data));
}
});
message.username = request.username;
const { connectionManagementId, updateSessionId } = message;
if (connectionManagementId) {
cleanup.promise.finally(async () => {
const plugins = await systemManager.getComponent('plugins');
plugins.setHostParam('@scrypted/webrtc', connectionManagementId);
});
}
if (updateSessionId) {
cleanup.promise.finally(async () => {
const plugins = await systemManager.getComponent('plugins');
plugins.setHostParam('@scrypted/webrtc', updateSessionId);
});
}
const session = await createBrowserSignalingSession(ws, '@scrypted/webrtc', 'remote');
const clientOptions = await legacyGetSignalingSessionOptions(session);
const result = zygote();
this.activeConnections++;
result.worker.on('exit', () => {
this.activeConnections--;
cleanup.resolve('worker exited (onConnection)');
});
let connection: WebRTCConnectionManagement;
try {
const { createConnection } = await result.result;
connection = await createConnection(message, client.port, session,
this.storageSettings.values.maximumCompatibilityMode, clientOptions, {
configuration: this.getRTCConfiguration(),
weriftConfiguration,
ipv4Ban: this.storageSettings.values.ipv4Ban,
});
}
catch (e) {
result.worker.terminate();
throw e;
}
handleCleanupConnection(cleanup, connection, result);
timeoutPromise(60000, connection.waitConnected())
.catch(() => {
cleanup.resolve('timeout');
});
await connection.negotiateRTCSignalingSession();
const cp = await client.clientPromise;
cp.on('close', () => cleanup.resolve('socket client closed'));
sdk.connect(cp, message);
}
catch (e) {
console.error("error negotiating browser RTCC signaling", e);
cleanup.resolve('error');
}
}
}
function handleCleanupConnection(cleanup: Deferred<string>, connection: WebRTCConnectionManagement, result: ReturnType<typeof zygote>) {
cleanup.promise.finally(() => {
connection.close().catch(() => { });
setTimeout(() => result.worker.terminate(), 30000)
});
connection.waitClosed().finally(() => cleanup.resolve('peer connection closed'));
}
export async function fork() {
@@ -715,6 +625,7 @@ export async function fork() {
async createConnection(message: any,
port: number,
clientSession: RTCSignalingSession,
requireOpus,
maximumCompatibilityMode: boolean,
clientOptions: RTCSignalingOptions,
options: {
@@ -759,7 +670,7 @@ export async function fork() {
const cleanup = new Deferred<string>();
cleanup.promise.catch(e => this.console.log('cleaning up rtc connection:', e.message));
const connection = new WebRTCConnectionManagement(console, clientSession, maximumCompatibilityMode, clientOptions, options);
const connection = new WebRTCConnectionManagement(console, clientSession, requireOpus, maximumCompatibilityMode, clientOptions, options);
cleanup.promise.finally(() => connection.close().catch(() => { }));
const { pc } = connection;
waitClosed(pc).then(() => cleanup.resolve('peer connection closed'));

View File

@@ -111,6 +111,7 @@ function isCodecCopy(desiredCodec: RtpCodecCopy, checkCodec: string) {
export type RtpForwarderProcess = Awaited<ReturnType<typeof startRtpForwarderProcess>>;
export async function startRtpForwarderProcess(console: Console, ffmpegInput: FFmpegInput, rtpTracks: RtpTracks, options?: {
ffmpegPath?: string,
rtspClientForceTcp?: boolean,
rtspMode?: 'udp' | 'tcp' | 'pull',
onRtspClient?: (rtspClient: RtspClient, optionsResponse: RtspServerResponse) => Promise<boolean>,
@@ -150,7 +151,7 @@ export async function startRtpForwarderProcess(console: Console, ffmpegInput: FF
rtpTracks = Object.assign({}, rtpTracks);
const videoCodec = video?.codecCopy;
const audioCodec = audio?.codecCopy;
const ffmpegPath = await mediaManager.getFFmpegPath();
const ffmpegPath = options?.ffmpegPath || await mediaManager.getFFmpegPath();
const isRtsp = ffmpegInput.container?.startsWith('rtsp');

View File

@@ -41,6 +41,14 @@ export const requiredAudioCodecs = [
}),
];
export const opusAudioCodecOnly = [
new RTCRtpCodecParameters({
mimeType: "audio/opus",
clockRate: 48000,
channels: 2,
payloadType: 111,
}),
];
export function getAudioCodec(outputCodecParameters: RTCRtpCodecParameters) {
if (outputCodecParameters.name === 'PCMA') {

4
sdk/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/sdk",
"version": "0.3.61",
"version": "0.3.67",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/sdk",
"version": "0.3.61",
"version": "0.3.67",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.24.7",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/sdk",
"version": "0.3.61",
"version": "0.3.67",
"description": "",
"main": "dist/src/index.js",
"exports": {

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/types",
"version": "0.3.57",
"version": "0.3.62",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/types",
"version": "0.3.57",
"version": "0.3.62",
"license": "ISC",
"devDependencies": {
"@types/node": "^22.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/types",
"version": "0.3.57",
"version": "0.3.62",
"description": "",
"main": "dist/index.js",
"author": "",

View File

@@ -64,6 +64,9 @@ class MediaPlayerState(str, Enum):
class PanTiltZoomMovement(str, Enum):
Absolute = "Absolute"
Continuous = "Continuous"
Home = "Home"
Preset = "Preset"
Relative = "Relative"
class ScryptedDeviceType(str, Enum):
@@ -214,6 +217,7 @@ class ScryptedMimeTypes(str, Enum):
RequestMediaObject = "x-scrypted/x-scrypted-request-media-object"
RequestMediaStream = "x-scrypted/x-scrypted-request-stream"
SchemePrefix = "x-scrypted/x-scrypted-scheme-"
ServerId = "text/x-server-id"
Url = "text/x-uri"
class SecuritySystemMode(str, Enum):
@@ -492,6 +496,7 @@ class DeviceCreatorSettings(TypedDict):
class DeviceInformation(TypedDict):
deeplink: Any
firmware: str
ip: str
mac: str
@@ -702,15 +707,18 @@ class ObjectsDetected(TypedDict):
class PanTiltZoomCapabilities(TypedDict):
pan: bool
presets: Any # Preset id mapped to friendly name.
tilt: bool
zoom: bool
class PanTiltZoomCommand(TypedDict):
movement: PanTiltZoomMovement # Specify the movement origin. If unspecified, the movement will be relative to the current position.
movement: PanTiltZoomMovement # Specify the movement type. If unspecified, the movement will be relative to the current position.
pan: float # Ranges between -1 and 1.
preset: str # The preset to move to.
speed: Any # The speed of the movement.
tilt: float # Ranges between -1 and 1.
timeout: float # The duration of the movement in milliseconds.
zoom: float # Ranges between 0 and 1 for max zoom.
class Position(TypedDict):
@@ -852,10 +860,11 @@ class Setting(TypedDict):
choices: list[str]
combobox: bool
console: bool # Flag that hte UI should open the console.
description: str
deviceFilter: str
group: str
immediate: bool # Flat that the UI should immediately apply this setting.
immediate: bool # Flag that the UI should immediately apply this setting.
key: str
multiple: bool
placeholder: str

View File

@@ -949,12 +949,15 @@ export interface VideoCameraMask {
export enum PanTiltZoomMovement {
Absolute = "Absolute",
Relative = "Relative"
Relative = "Relative",
Continuous = "Continuous",
Preset = "Preset",
Home = 'Home',
}
export interface PanTiltZoomCommand {
/**
* Specify the movement origin. If unspecified, the movement will be relative to the current position.
* Specify the movement type. If unspecified, the movement will be relative to the current position.
*/
movement?: PanTiltZoomMovement;
/**
@@ -985,13 +988,27 @@ export interface PanTiltZoomCommand {
* Ranges between 0 and 1 for max zoom.
*/
zoom?: number;
}
};
/**
* The duration of the movement in milliseconds.
*/
timeout?: number;
/**
* The preset to move to.
*/
preset?: string;
}
export interface PanTiltZoomCapabilities {
pan?: boolean;
tilt?: boolean;
zoom?: boolean;
/**
* Preset id mapped to friendly name.
*/
presets?: {
[key: string]: string;
};
}
export interface PanTiltZoom {
ptzCapabilities?: PanTiltZoomCapabilities;
@@ -1876,6 +1893,10 @@ export interface DeviceInformation {
mac?: string;
metadata?: any;
managementUrl?: string;
deeplink?: {
apple?: string;
android?: string;
};
}
/**
* Device objects are created by DeviceProviders when new devices are discover and synced to Scrypted via the DeviceManager.
@@ -2197,9 +2218,13 @@ export interface Setting {
deviceFilter?: string;
multiple?: boolean;
/**
* Flat that the UI should immediately apply this setting.
* Flag that the UI should immediately apply this setting.
*/
immediate?: boolean;
/**
* Flag that hte UI should open the console.
*/
console?: boolean;
value?: SettingValue;
}
@@ -2474,6 +2499,7 @@ export enum ScryptedMimeTypes {
Url = 'text/x-uri',
InsecureLocalUrl = 'text/x-insecure-local-uri',
LocalUrl = 'text/x-local-uri',
ServerId = 'text/x-server-id',
PushEndpoint = 'text/x-push-endpoint',

349
server/package-lock.json generated
View File

@@ -1,24 +1,24 @@
{
"name": "@scrypted/server",
"version": "0.117.3",
"version": "0.119.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.117.3",
"version": "0.119.3",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"@scrypted/ffmpeg-static": "^6.1.0-build1",
"@scrypted/node-pty": "^1.0.18",
"@scrypted/types": "^0.3.57",
"@scrypted/types": "^0.3.62",
"adm-zip": "^0.5.16",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.5",
"engine.io": "^6.6.0",
"express": "^4.19.2",
"engine.io": "^6.6.2",
"express": "^4.21.1",
"follow-redirects": "^1.15.9",
"http-auth": "^4.2.0",
"level": "^8.0.1",
@@ -26,13 +26,13 @@
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^10.2.0",
"py": "npm:@bjia56/portable-python@^0.1.74",
"py": "npm:@bjia56/portable-python@^0.1.94",
"semver": "^7.6.3",
"sharp": "^0.33.5",
"source-map-support": "^0.5.21",
"tar": "^7.4.3",
"tslib": "^2.7.0",
"typescript": "^5.5.4",
"tslib": "^2.8.0",
"typescript": "^5.6.3",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.18.0"
},
@@ -42,11 +42,11 @@
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^4.17.21",
"@types/express": "^5.0.0",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/lodash": "^4.17.7",
"@types/node": "^22.5.4",
"@types/lodash": "^4.17.10",
"@types/node": "^22.7.6",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.5.8",
@@ -557,9 +557,9 @@
}
},
"node_modules/@scrypted/types": {
"version": "0.3.57",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.57.tgz",
"integrity": "sha512-XHE8RL2r8m5NGp+bsoUbeZ1zTHkGh5exXnrQFj2grUivZ9RY/D5wWxWieO+KHLSLstL7H71gMAuMxw6QQpnDXg=="
"version": "0.3.62",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.62.tgz",
"integrity": "sha512-tJHpCS8B0K7h33plkbajOAHUfOpqrEJOwYAojdOTc/DGf6+xKOUMKXl+wnEHmtjtUmlSNjeBQJxJ8ua813gS6Q=="
},
"node_modules/@types/adm-zip": {
"version": "0.5.5",
@@ -612,21 +612,21 @@
}
},
"node_modules/@types/express": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
"integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/express-serve-static-core": "^5.0.0",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.35",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz",
"integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz",
"integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==",
"dev": true,
"dependencies": {
"@types/node": "*",
@@ -654,9 +654,9 @@
}
},
"node_modules/@types/lodash": {
"version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==",
"dev": true
},
"node_modules/@types/mime": {
@@ -666,9 +666,9 @@
"dev": 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==",
"version": "22.7.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz",
"integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==",
"dependencies": {
"undici-types": "~6.19.2"
}
@@ -689,15 +689,15 @@
}
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"version": "6.9.16",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
"integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
"dev": true
},
"node_modules/@types/range-parser": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true
},
"node_modules/@types/semver": {
@@ -707,9 +707,9 @@
"dev": true
},
"node_modules/@types/send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
"integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==",
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true,
"dependencies": {
"@types/mime": "^1",
@@ -717,9 +717,9 @@
}
},
"node_modules/@types/send/node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true
},
"node_modules/@types/serve-static": {
@@ -919,9 +919,9 @@
}
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@@ -931,7 +931,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@@ -1143,12 +1143,18 @@
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -1251,19 +1257,19 @@
}
},
"node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
"dependencies": {
"cookie": "0.4.1",
"cookie": "0.7.2",
"cookie-signature": "1.0.6"
},
"engines": {
@@ -1343,6 +1349,22 @@
"node": ">=4.0.0"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1395,9 +1417,9 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
@@ -1432,16 +1454,16 @@
}
},
"node_modules/engine.io": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.0.tgz",
"integrity": "sha512-+ky8JKEyy2WqFkzwp8ntm8EFZAW/o5YfTi2pQEoByAAFCtXiXhbBNpBi1HqLGPCjPHCqyKMlyLvc7GMNM8/1/w==",
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
"integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
@@ -1492,6 +1514,17 @@
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
@@ -1527,36 +1560,36 @@
"integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw=="
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@@ -1568,9 +1601,9 @@
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -1589,12 +1622,12 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@@ -1803,15 +1836,37 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"engines": {
"node": ">= 0.4"
},
@@ -2124,9 +2179,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
@@ -2136,6 +2194,17 @@
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -2529,9 +2598,12 @@
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -2607,9 +2679,9 @@
}
},
"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": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/prebuild-install": {
"name": "@scrypted/prebuild-install",
@@ -2680,19 +2752,19 @@
},
"node_modules/py": {
"name": "@bjia56/portable-python",
"version": "0.1.74",
"resolved": "https://registry.npmjs.org/@bjia56/portable-python/-/portable-python-0.1.74.tgz",
"integrity": "sha512-gg8t3/5d5c/anPb9NTvf68CSZXMXKJ/9ITycpCquMRP7A0YfHn4l493ifQY+d8/XCooSs9qWSisGufZaAb21UA==",
"version": "0.1.94",
"resolved": "https://registry.npmjs.org/@bjia56/portable-python/-/portable-python-0.1.94.tgz",
"integrity": "sha512-dACd5jj4gJiv6Gyf2Ne4dwXR9DRn3s/S0sg6pe64JVhX/3jT18HBZ4oxgN+IFXJ2bjnYkp2LCtpOCEsYl3El0g==",
"dependencies": {
"adm-zip": "^0.5.10"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@@ -2854,9 +2926,9 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@@ -2889,15 +2961,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/send/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">=4"
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
@@ -2906,19 +2975,35 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -2982,13 +3067,17 @@
}
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
"call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -3283,9 +3372,9 @@
}
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA=="
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
@@ -3311,9 +3400,9 @@
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -1,17 +1,17 @@
{
"name": "@scrypted/server",
"version": "0.118.0",
"version": "0.119.4",
"description": "",
"dependencies": {
"@scrypted/ffmpeg-static": "^6.1.0-build1",
"@scrypted/node-pty": "^1.0.18",
"@scrypted/types": "^0.3.57",
"@scrypted/types": "^0.3.62",
"adm-zip": "^0.5.16",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.5",
"engine.io": "^6.6.0",
"express": "^4.19.2",
"engine.io": "^6.6.2",
"express": "^4.21.1",
"follow-redirects": "^1.15.9",
"http-auth": "^4.2.0",
"level": "^8.0.1",
@@ -19,24 +19,24 @@
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^10.2.0",
"py": "npm:@bjia56/portable-python@^0.1.74",
"py": "npm:@bjia56/portable-python@^0.1.94",
"semver": "^7.6.3",
"sharp": "^0.33.5",
"source-map-support": "^0.5.21",
"tar": "^7.4.3",
"tslib": "^2.7.0",
"typescript": "^5.5.4",
"tslib": "^2.8.0",
"typescript": "^5.6.3",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.18.0"
},
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^4.17.21",
"@types/express": "^5.0.0",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/lodash": "^4.17.7",
"@types/node": "^22.5.4",
"@types/lodash": "^4.17.10",
"@types/node": "^22.7.6",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.5.8",

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