Compare commits

...

239 Commits

Author SHA1 Message Date
Koushik Dutta
dd3e7fe238 rebroadcast: improve cleanups 2025-04-11 21:38:43 -07:00
Koushik Dutta
b02c17e185 rebroadcast: beta 2025-04-11 16:23:44 -07:00
Koushik Dutta
6dee9b04df rebroadcast: update libav 2025-04-11 16:17:48 -07:00
Koushik Dutta
378fb41908 rebroadcast: update libav with new pipeline 2025-04-11 14:46:22 -07:00
Koushik Dutta
48f7208d55 Merge branch 'main' into libav 2025-04-11 14:22:23 -07:00
Koushik Dutta
d1bfed3019 install: make sure amd installer updates repos? 2025-04-10 20:35:37 -07:00
Koushik Dutta
6bf10d4aff install: update intel repos 2025-04-10 19:31:11 -07:00
Koushik Dutta
3ceef8ff87 install: update intel repos 2025-04-10 19:10:41 -07:00
Koushik Dutta
df1155cf82 postbeta 2025-04-10 10:52:51 -07:00
Koushik Dutta
453469ed98 server: implement sendStream backpressure handling 2025-04-10 10:52:42 -07:00
Koushik Dutta
2e4dbceb0e postbeta 2025-04-10 10:47:58 -07:00
Koushik Dutta
c620a4e126 server: ensure sendStream terminates on connection close 2025-04-10 10:47:48 -07:00
Koushik Dutta
5698551b7e common: fix h265 aggregation packet recency check 2025-04-09 15:33:50 -07:00
Koushik Dutta
9e655c0a53 postbeta 2025-04-09 09:37:54 -07:00
Koushik Dutta
35dadaab93 server: enable tcp keepalive for cluster 2025-04-09 09:37:45 -07:00
Koushik Dutta
71a93805ef Merge branch 'main' into libav 2025-04-09 09:13:53 -07:00
pir8radio
76487091da install: windows script requests elevation to to admin automatically (#1788)
let the script relaunch itself with admin privileges.  Can be a pain, when launching powershell as admin, you can't drag and drop the script into a powershell window.   This just makes it easier for people new to powershell.
2025-04-09 08:24:07 -07:00
Koushik Dutta
3c5b8bc940 common: fix h265 sei prefix vs suffix separation 2025-04-08 12:28:50 -07:00
Koushik Dutta
37e5c49729 github: remove router build 2025-04-08 10:45:50 -07:00
Koushik Dutta
16fc4407c1 use mime rather than send 2025-04-08 09:00:51 -07:00
Koushik Dutta
3e76c1b1d3 snapshot: remove audio feed in prebuffer request 2025-04-08 08:32:59 -07:00
Koushik Dutta
4880c18c48 Merge remote-tracking branch 'origin/libav' into libav 2025-04-08 08:32:22 -07:00
Koushik Dutta
198d4808cb Merge branch 'main' into libav 2025-04-08 08:32:04 -07:00
Koushik Dutta
ef8f4ba9b8 Merge branch 'main' into libav 2025-04-07 15:27:02 -07:00
Koushik Dutta
d6c6e3c594 install: fix macos install 2025-04-07 10:55:54 -07:00
apocaliss92
6c904da49b reolink: Add locallink endpoint (#1782)
* Add API to check wifi connectivity

* Comment removed

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-04-07 08:30:58 -07:00
Koushik Dutta
6ac790f824 webhook: prevent enable on internal types, fixup code to not require mixin existence 2025-04-07 08:17:38 -07:00
Koushik Dutta
3638f80cef mqtt: prevent enable on internal types 2025-04-07 08:17:23 -07:00
Koushik Dutta
809b632417 server: add more cluster logging 2025-04-05 14:07:35 -07:00
Koushik Dutta
f53330c861 webrtc: improve sending codec information only after a marker packet 2025-04-05 10:00:12 -07:00
Koushik Dutta
66455c8f01 rebroadcast: fix bug where stream may be started on fragmented key frame 2025-04-05 09:57:21 -07:00
Koushik Dutta
e6eb61f04f webrtc: more h265 packetizer fixes 2025-04-04 22:48:51 -07:00
Koushik Dutta
adeb3d837e Merge branch 'main' of github.com:koush/scrypted 2025-04-04 15:58:05 -07:00
Koushik Dutta
3da3f85513 webrtc: fix h265 packetizer ap resent for chrome 2025-04-04 15:58:00 -07:00
Koushik Dutta
cbe251e345 docker: bump node to 22 2025-04-04 12:18:59 -07:00
Koushik Dutta
dfa2dacde4 postbeta 2025-04-04 11:40:16 -07:00
Koushik Dutta
9395253b50 server: more express 5.0 fixes 2025-04-04 11:40:04 -07:00
Koushik Dutta
e020ee1517 server: fix breakage caused by path-to-regexp updates 2025-04-04 11:39:03 -07:00
Koushik Dutta
a7ecb9b5e5 postbeta 2025-04-04 11:39:03 -07:00
Koushik Dutta
24f9b0fca3 postbeta 2025-04-04 11:39:03 -07:00
Koushik Dutta
a78ad99f50 server: update deps 2025-04-04 11:39:03 -07:00
Koushik Dutta
c26c5e94a4 server: fix type error in createRpcIoPeer 2025-04-04 11:38:55 -07:00
Koushik Dutta
2d93a69c91 homekit/rebroadcast: fix prebuffer calculation, remove prebuffer warning 2025-04-02 22:12:12 -07:00
Koushik Dutta
1c52297e74 sdk: remove legacy flush/queued from VideoFrame 2025-04-02 20:58:25 -07:00
Koushik Dutta
5bc76642cc webrtc: fix ice restart 2025-04-02 20:57:37 -07:00
Koushik Dutta
15fa27029d webrtc: update werift with connection related hang fixes 2025-04-02 12:34:50 -07:00
Koushik Dutta
553678ed1a rebroadcast: Fix cluster addresses not being used 2025-04-02 12:33:45 -07:00
Koushik Dutta
b3b7265263 rebroadcast: wipe acodec arguments if encoder arguments are explicitly provided 2025-04-01 20:51:07 -07:00
Koushik Dutta
d87d9bb751 rebroadcast: wip libav 2025-04-01 11:09:57 -07:00
Koushik Dutta
301213fc5f rebroadcast: remove dead code 2025-03-31 11:07:28 -07:00
Koushik Dutta
da393ae4e0 rebroadcast: ensure audio mute/no audio is in sync 2025-03-31 10:57:09 -07:00
Koushik Dutta
9376fc4ba6 common: fixup codec info in packet delivery 2025-03-28 12:58:32 -07:00
Koushik Dutta
51d4aa7b3e sdk: update 2025-03-27 09:38:28 -07:00
Koushik Dutta
30334e5bd0 webrtc: support for alternateCodecs and codec switching 2025-03-26 23:58:21 -07:00
Koushik Dutta
dffc05d165 external: update werift 2025-03-26 19:40:45 -07:00
Koushik Dutta
172e5b3ccb sdk: add codec switch hint 2025-03-25 16:15:51 -07:00
Koushik Dutta
cad60e7730 webrtc/common: fix sdp construction when audio is sideband copied. maybe get rid of this process? 2025-03-25 09:08:46 -07:00
Koushik Dutta
131458576c webrtc: fix handshake negotiation resetting the sender 2025-03-24 20:41:16 -07:00
Koushik Dutta
0d7b47e1e9 webrtc: update 2025-03-22 20:56:56 -07:00
Koushik Dutta
1a33384115 webrtc: fixup h265 keyframe hunting 2025-03-21 23:51:24 -07:00
Koushik Dutta
1fa5f66b44 Merge branch 'main' of github.com:koush/scrypted 2025-03-21 23:21:24 -07:00
Koushik Dutta
1032d444fb google-home: fix build 2025-03-21 23:19:52 -07:00
Koushik Dutta
2883824690 webrtc: add alternate codecs 2025-03-21 19:32:51 -07:00
Koushik Dutta
a505394852 sdk: add audio request hints 2025-03-21 19:22:59 -07:00
Koushik Dutta
1240f401d7 alexa: clean up removed devices 2025-03-21 08:08:27 -07:00
Koushik Dutta
b49faaa033 webrtc: fix rtcp pli from browser 2025-03-20 23:24:37 -07:00
Koushik Dutta
cbb1d4533a vscode-typescript: update sample 2025-03-20 23:24:18 -07:00
Koushik Dutta
a5a027bd6d ncnn: doc flags 2025-03-20 22:30:29 -07:00
Koushik Dutta
94acd0e800 unifi-protect: Fixup codec reporting 2025-03-19 13:19:20 -07:00
Koushik Dutta
cabdd91a92 unifi-protect: update sdk 2025-03-19 13:14:36 -07:00
Koushik Dutta
93c9b62e87 common: disposable queue 2025-03-19 09:11:15 -07:00
Koushik Dutta
21c771d50f common: deferred dispose 2025-03-18 19:08:34 -07:00
Koushik Dutta
bb2ecd7bd8 reolink: fix optional chaining for mainEncType 2025-03-18 11:57:02 -07:00
apocaliss92
6c0864b883 reolink: Disable siren polling (#1774)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-03-18 11:54:33 -07:00
Koushik Dutta
3c8ef7a2cf core: publiush 2025-03-18 09:14:21 -07:00
Koushik Dutta
6afecc8185 homekit: workaround homekit requesting low resolution streams 2025-03-17 10:42:16 -07:00
Koushik Dutta
ea16381b7a homekit: build fixes 2025-03-17 10:19:55 -07:00
Koushik Dutta
09d3ac587f webrtc: publish h265 support 2025-03-16 23:31:05 -07:00
Koushik Dutta
3872cb391a rebroadcast: publish with h265 parsing fixes 2025-03-16 23:22:39 -07:00
Koushik Dutta
7d985937ca webrtc: send desired codecs 2025-03-16 21:16:09 -07:00
Koushik Dutta
cef8482b93 sdk: partial revert of ffmpeg input changes 2025-03-16 19:52:47 -07:00
Koushik Dutta
f729c76346 various: remove defunct ffmpeg args 2025-03-16 19:09:40 -07:00
Koushik Dutta
9aa9498aae sdk: video stream negotiation cleanups 2025-03-16 18:56:48 -07:00
Koushik Dutta
afe832a32a webrtc: h265 beta, requires new nvr plugin or nvr will break 2025-03-15 23:23:35 -07:00
Koushik Dutta
be6a81c9a2 common: fix h265 agg parsing 2025-03-15 22:21:41 -07:00
Koushik Dutta
964bb27d48 common: fix h265 idr seeking 2025-03-15 22:00:24 -07:00
Koushik Dutta
6bca83b338 webrtc: provide h265 hint when available 2025-03-15 17:12:23 -07:00
Koushik Dutta
11860409f1 snapshot: fix h265 prebuffer snapshot 2025-03-15 16:32:00 -07:00
Koushik Dutta
6743f76e09 webrtc: wip h265 2025-03-15 16:24:48 -07:00
Koushik Dutta
771d90ea73 rebroadcast: publish 2025-03-15 08:11:39 -07:00
Koushik Dutta
ba28899dc3 rebroadcast: beta 2025-03-15 00:52:44 -07:00
Koushik Dutta
3e57c90208 rebroadcast: fix mixin order 2025-03-15 00:52:07 -07:00
Koushik Dutta
e155584373 reolink: publish 2025-03-13 21:12:08 -07:00
Koushik Dutta
4a3968956e snapshot: publish 2025-03-13 21:11:07 -07:00
Koushik Dutta
78c259d14e hikvision: light/alarm 2025-03-13 21:10:12 -07:00
Koushik Dutta
6f4b360d2a nanokvm: persist settings from device creation 2025-03-13 15:27:39 -07:00
Koushik Dutta
642795dd9d nanokvm: ip hint 2025-03-13 15:26:39 -07:00
Koushik Dutta
4b3d37b628 nanokvm: heartbeat 2025-03-13 15:22:33 -07:00
Koushik Dutta
8e28e6d19e nanokvm: fixup keywords 2025-03-13 11:29:25 -07:00
Koushik Dutta
d13171551d nanokvm: readme 2025-03-13 11:28:16 -07:00
Koushik Dutta
ea1474c21e nanokvm: publish 2025-03-13 11:22:41 -07:00
Koushik Dutta
7abff3a91b nanokvm: fixup modifiers 2025-03-13 11:14:50 -07:00
Koushik Dutta
40f11a0053 nanokvm: remove logging 2025-03-13 10:48:42 -07:00
Koushik Dutta
1d3450455b core: publish 2025-03-13 10:13:38 -07:00
Koushik Dutta
bd3b4ac387 client: publish 2025-03-13 10:12:56 -07:00
Koushik Dutta
6787153c30 nanokvm: initial commit 2025-03-13 10:05:15 -07:00
Koushik Dutta
7024daba53 sdk: kvm input 2025-03-12 16:32:57 -07:00
Koushik Dutta
28a2e0d898 sdk: shim for es modules 2025-03-12 10:13:19 -07:00
Koushik Dutta
a7757a9a54 docker: bump node version 2025-03-12 10:03:42 -07:00
Koushik Dutta
84fc40e1e5 postbeta 2025-03-12 09:46:36 -07:00
Koushik Dutta
b3b8f6bc70 server: remove eseval 2025-03-12 09:46:28 -07:00
Koushik Dutta
7962606a2b postbeta 2025-03-12 09:32:31 -07:00
Koushik Dutta
9c2ea7d2bc server: another attempt at node/esmodule interop with tsc 2025-03-12 09:32:20 -07:00
Koushik Dutta
4e653a9942 Revert "server: fixup es-eval"
This reverts commit 51836ca59f.
2025-03-12 09:19:29 -07:00
Koushik Dutta
167db0f11a router: usbmuxd persistence 2025-03-12 09:07:04 -07:00
Koushik Dutta
49064de767 postbeta 2025-03-12 09:01:21 -07:00
Koushik Dutta
51836ca59f server: fixup es-eval 2025-03-12 09:01:12 -07:00
Koushik Dutta
d64ed629b0 router: install usbmuxd for iphone tethering 2025-03-11 23:40:01 -07:00
Koushik Dutta
83a9ad2250 rebroadcast: reorder mixin order for regular (ie, non webrtc) cameras to be first to prevent stream flapping due to mixin change noise 2025-03-11 15:24:40 -07:00
Koushik Dutta
7f9358a3b5 webrtc: fix potential webrtc camera thread leak 2025-03-11 14:45:30 -07:00
Koushik Dutta
9cf3d6c912 webrtc: leak and crash fixes 2025-03-11 14:41:51 -07:00
Koushik Dutta
5e3d1c423c common: build fixes 2025-03-11 10:05:24 -07:00
Koushik Dutta
64fa68f2d0 core: publish 2025-03-11 10:05:12 -07:00
Koushik Dutta
45cc859636 router: enable all ip forwarding, install coturn 2025-03-10 11:27:28 -07:00
Koushik Dutta
1984bb44ba router: enable all ip forwarding, install coturn 2025-03-10 11:24:37 -07:00
Koushik Dutta
1164e4b15b router: ipv6 forwarding 2025-03-10 00:43:58 -07:00
Koushik Dutta
9e5fbc5251 postbeta 2025-03-09 19:54:38 -07:00
Koushik Dutta
6f52390067 Merge branch 'main' of github.com:koush/scrypted 2025-03-09 19:54:09 -07:00
Koushik Dutta
d34afab6a4 server: ensure proper ip type checking 2025-03-09 19:53:36 -07:00
Koushik Dutta
2edc74f75b postbeta 2025-03-09 10:13:56 -07:00
Koushik Dutta
e913131f90 server: log denied address 2025-03-09 10:13:43 -07:00
Koushik Dutta
bca752addb postbeta 2025-03-09 09:55:16 -07:00
Koushik Dutta
43fc6c9fc9 server: fix address check 2025-03-09 09:54:48 -07:00
Koushik Dutta
448e2c4e6e router: fix names 2025-03-08 23:51:15 -08:00
Koushik Dutta
625ea7981e router: fix paths 2025-03-08 23:32:58 -08:00
Koushik Dutta
1be806eb8e docker: add router builds 2025-03-08 23:31:10 -08:00
Koushik Dutta
e4b71ffbd4 postbeta 2025-03-08 23:16:51 -08:00
Koushik Dutta
16f4cafea3 server: fix listen sets to listen all and reject on unauthorized address 2025-03-08 23:16:42 -08:00
Koushik Dutta
f78df27341 ncnn: initial commit 2025-03-08 14:08:53 -08:00
Koushik Dutta
57d4e4b9bd core: publish 2025-03-07 21:20:42 -08:00
Koushik Dutta
cb1c062b5e core: publish 2025-03-07 21:19:07 -08:00
Koushik Dutta
e3b996562c Merge branch 'main' of github.com:koush/scrypted 2025-03-07 21:11:22 -08:00
Koushik Dutta
c96bf237b5 core: publish 2025-03-07 21:11:20 -08:00
apocaliss92
5bde86fd15 hikvision: Ptz + presets implemented (#1764)
* Hikvision: Ptz + presets implemented

* Sweeter ptz commands

* Presets fetching

* Capabilities used to enable PTZ

* Redundant call removed

* Logs removed

* Variable moved

* Revert some async changes

* Persist hasPtz

* Move alarm and light to same block

* Log removed

* Fix ptz presets selection

* never disable ptz

* Make devices and ptz caps user-selectable

* undefined check for presets result

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-03-07 19:31:20 -08:00
Koushik Dutta
5075920308 postbeta 2025-03-05 08:34:16 -08:00
Koushik Dutta
9e4845b868 server: allow interface name in SCRYPTED_CLUSTER_ADDRESS 2025-03-05 08:34:05 -08:00
Koushik Dutta
0cab8f2faf core: fix buikd 2025-03-04 20:52:32 -08:00
Koushik Dutta
510321b7d6 postbeta 2025-03-04 19:46:46 -08:00
Koushik Dutta
efb0a39e52 server: fixup SCRYPTED_SERVER_LISTEN_HOSTNAMES 2025-03-04 19:46:13 -08:00
Koushik Dutta
467d89ccaf postbeta 2025-03-04 18:58:40 -08:00
Brett Jia
19832c9537 python: partial repl reimplementation (#1763)
* python: partial repl reimplementation

* make more readable?

* document questionable design choices
2025-03-04 14:37:55 -08:00
Koushik Dutta
0b24e57262 sdk: cleanup peer dependencies 2025-03-04 10:00:40 -08:00
Koushik Dutta
f406969140 sdk: remove col1/2 for description 2025-03-04 09:35:32 -08:00
Koushik Dutta
4db26a1779 sdk: relax types 2025-03-04 08:15:32 -08:00
Koushik Dutta
bfc82d0010 sdk: network types 2025-03-03 19:05:47 -08:00
Koushik Dutta
df3a3d279c router: fix file path 2025-03-03 10:53:33 -08:00
Koushik Dutta
bb7f2a0c9b postbeta 2025-03-03 10:38:28 -08:00
Koushik Dutta
3f83d4b8f7 server: improve plugin kill race conditions on update 2025-03-03 10:38:18 -08:00
Koushik Dutta
779fa1df9c Merge branch 'main' of github.com:koush/scrypted 2025-03-03 09:43:56 -08:00
Koushik Dutta
1c08313e8b server: cleanup runtime worker hooks 2025-03-03 09:43:51 -08:00
root
64e8dc2cc9 router: fix nft flush 2025-03-03 17:42:47 +00:00
Koushik Dutta
1914fa60ea router: file naming consistency 2025-03-03 08:39:25 -08:00
Koushik Dutta
8073a80bae router: typo 2025-03-03 08:28:26 -08:00
Koushik Dutta
cd7d45155f proxmox: fixup confusion around reset script and storage 2025-03-03 08:00:38 -08:00
Koushik Dutta
6f6ccff5b1 server: missing dhcp client 2025-03-03 07:55:48 -08:00
Koushik Dutta
4ea8049d22 server: additional service files 2025-03-03 07:55:02 -08:00
Koushik Dutta
8354564157 postbeta 2025-03-02 18:50:14 -08:00
Koushik Dutta
26518f0693 server: reduced listening address set 2025-03-02 18:48:01 -08:00
Koushik Dutta
72df40c422 postbeta 2025-03-02 15:02:45 -08:00
Koushik Dutta
fe1b677381 server: limit address binding in cluster mode 2025-03-02 14:57:56 -08:00
Koushik Dutta
16a9abeb9e Merge branch 'main' of github.com:koush/scrypted 2025-02-28 21:05:08 -08:00
Koushik Dutta
ba07aa7765 router: caddy 2025-02-28 21:05:04 -08:00
apocaliss92
35e508a01e snapshot: Allow sleeping cameras to have longer lived snapshots (#1747)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-27 22:30:30 -08:00
apocaliss92
1e709a058d reolink: Add polling to reolink discovered devices (#1744)
* Add polling to reolink discovered devices

* Don't poll battery devices

* Logic moved to forever cycle

* Poll battery devices only when they wake up

* Wrap each poll in try/catch

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-27 22:29:12 -08:00
Mehmet Bayram
f5a4bab0a8 hikvision: Improve handling of supplemental light and alarm (#1739)
* hikvision: Remove settings from alarm switch
Dynamically choose supplemental light mode
Rename floodlight to supplemental light
Prevent child device removal if no devices detected
Report devices when settings are saved
Remove unused functions

* fix: push DeviceProvider interface

* hikvision: Add README support to Alarm Switch and update interfaces

* hikvision: add device release method

* hikvision: add IP check before accessing alarm and light capabilities

* hikvision: Centralize alarm activation logic
2025-02-27 22:28:24 -08:00
Koushik Dutta
e373a3935e router: fixup policies 2025-02-26 19:15:46 -08:00
Koushik Dutta
f9f9762046 docker: router shuffling 2025-02-26 11:20:35 -08:00
Koushik Dutta
4b6751785c Merge branch 'main' of github.com:koush/scrypted 2025-02-25 21:14:20 -08:00
Koushik Dutta
133cbcf5f5 sdk: add col 2025-02-25 21:14:14 -08:00
LV Nilesh
817c171757 docker: update BASE (#1748)
https://github.com/koush/scrypted/pull/1745#issuecomment-2677176155
2025-02-25 09:04:36 -08:00
LV Nilesh
9af9359b26 docker: Update Dockerfile.full.header (#1749) 2025-02-25 09:04:17 -08:00
Koushik Dutta
b684ced629 install: nftables 2025-02-23 18:54:58 -08:00
apocaliss92
977db49f87 reolink: Add optional chain to live check (#1746)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-22 23:53:13 -08:00
Koushik Dutta
992fe98f5e core/sdk: update 2025-02-22 19:39:08 -08:00
Koushik Dutta
a74157168e sdk: update 2025-02-22 14:20:55 -08:00
Koushik Dutta
0eed5241f0 sdk: update 2025-02-22 14:20:00 -08:00
Koushik Dutta
c0680736e7 sdk: update 2025-02-21 19:25:41 -08:00
Koushik Dutta
c345f173d2 docker: set SHELL 2025-02-21 08:25:06 -08:00
Koushik Dutta
1ed10cd1cb sdk: radio support 2025-02-20 12:06:51 -08:00
Koushik Dutta
9426db12aa client/server: update deps 2025-02-19 14:56:53 -08:00
Koushik Dutta
a35e821f79 intall: router dockerfile updates 2025-02-19 14:56:39 -08:00
Koushik Dutta
983794d5d0 plugins: remove usage of builtin in favor of internal 2025-02-19 14:56:00 -08:00
Koushik Dutta
cd3e2340b8 rebroadcast: most camera plugins depend on this, so ensure snapshot and webrtc are downloaded as well 2025-02-19 14:55:32 -08:00
Koushik Dutta
bb82eb6bde sdk: Update 2025-02-19 14:28:56 -08:00
Koushik Dutta
5006bb90fc postbeta 2025-02-18 13:53:13 -08:00
Koushik Dutta
7134ef114a server: fixup staged cleanup 2025-02-18 13:53:03 -08:00
Koushik Dutta
2f5b1f6526 postbeta 2025-02-18 13:46:21 -08:00
Koushik Dutta
f88f0a25db server: try staged cleanup 2025-02-18 13:46:11 -08:00
Koushik Dutta
d2810b09ed docker: lite/systemd test 2025-02-17 23:06:33 -08:00
Koushik Dutta
f103ddf660 openvino: fix npu/gpu crashes caused by dynamic input sizes 2025-02-16 15:32:02 -08:00
Koushik Dutta
47edffa56d openvino: regenerate face embedding model 2025-02-16 11:50:05 -08:00
Koushik Dutta
86a5a73276 openvino: workaround for npu crash https://github.com/openvinotoolkit/openvino/issues/29003#issuecomment-2660865184 2025-02-15 08:17:02 -08:00
Koushik Dutta
1a47015558 openvino: more precise npu usage 2025-02-14 11:58:57 -08:00
Koushik Dutta
0de9812760 videoanalysis: fix zone names with hyphens 2025-02-14 11:46:36 -08:00
Koushik Dutta
10d16dab21 hikvision: remove dead code/ 2025-02-14 09:05:38 -08:00
Koushik Dutta
f3f4bbc77f hikvision-doorbell: fix buikd 2025-02-13 13:54:42 -08:00
Koushik Dutta
029f788407 hikvision: cleanup 2025-02-12 21:55:13 -08:00
Mehmet Bayram
89b93eb2f4 hikvision: Add Supplemental Light & Alarm Support to Hikvision Plugin (#1737)
* feat: add supplemental light control for Hikvision cameras

* Add supplemental light control as device

* Add alarm switch with audio/light alarm settings

* Get alarm trigger configuration and capabilities for audio and white light alarms

* Simplify alarm settings retrieval
2025-02-12 21:24:38 -08:00
Koushik Dutta
6fd35e54e6 openvino: rollback openvino 2025-02-12 15:36:57 -08:00
Koushik Dutta
118c404525 sdk: fix and publish 2025-02-12 15:01:49 -08:00
Koushik Dutta
833ecb721f tensorflow-lite: pass through forked flag 2025-02-12 13:39:55 -08:00
Koushik Dutta
a8f1e74278 tensorflow-lite: pass through forked flag 2025-02-12 13:38:18 -08:00
apocaliss92
1075fb4491 sdk: Sensors interface (#1731)
* Sensors interface

* Sensors as mapping type

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-12 13:02:25 -08:00
Koushik Dutta
f09a797ebf core: publish with ui fixes 2025-02-12 09:55:56 -08:00
Koushik Dutta
1e0fdee7b6 Merge branch 'main' of github.com:koush/scrypted 2025-02-12 09:29:15 -08:00
Koushik Dutta
be6375e9f4 tensorflow-lite: add cluster aware 2025-02-12 09:28:06 -08:00
apocaliss92
e5ba39f886 reolink: Forward battery cams detections (#1730)
* Reolink: Forward battery cams detections

* Used right return

* log removed

* use ability to determine events source

* Reuse same branching for events

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-11 11:43:31 -08:00
Brett Jia
6841b74a26 core: exit terminal's subprocess on generator end (#1733) 2025-02-10 16:18:21 -08:00
Koushik Dutta
a3f45e2c49 predict: favor ipv4 for file downloads 2025-02-10 09:14:34 -08:00
Koushik Dutta
b664ccd24f Update install-scrypted-proxmox.sh 2025-02-09 12:00:18 -08:00
Koushik Dutta
3f244b586f install: fix broken intel runtime installer 2025-02-09 11:24:33 -08:00
Koushik Dutta
9f828739de install: update intel libs 2025-02-09 09:08:46 -08:00
Koushik Dutta
b2cef35bc0 openvino: beta with latest 2025-02-09 09:02:02 -08:00
Koushik Dutta
3e54540db7 webrtc: update werift for chrome 132 compatibility 2025-02-08 17:51:04 -08:00
Koushik Dutta
debd7f2c40 onvif: fix onvif ptz with onvif plugin cams 2025-02-08 09:43:29 -08:00
Koushik Dutta
adbc2aaed9 onvif: add text overlays 2025-02-08 09:41:48 -08:00
Koushik Dutta
6024b4ceaf reolink/hikvision: publish 2025-02-08 08:26:28 -08:00
apocaliss92
3a065febb5 hikvision: fix get/patch osd settings (#1728)
Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-08 08:23:24 -08:00
apocaliss92
1e2c3e0ca7 reolink: Add pir sensor device (#1729)
* Reolink: Add pir sensor device

* Reolink: add siren to hub cameras

---------

Co-authored-by: Gianluca Ruocco <gianluca.ruocco@xarvio.com>
2025-02-08 08:23:07 -08:00
Koushik Dutta
61dfddeab2 Create config.yml 2025-02-07 22:02:39 -08:00
Koushik Dutta
b902873d44 hikvision: overlay support 2025-02-07 20:14:14 -08:00
Koushik Dutta
a38d803b86 reolink: overlay support 2025-02-07 13:58:25 -08:00
Koushik Dutta
9c3dab18da sdk/client/server/core/amcrest: add support for video text overlays 2025-02-07 13:07:55 -08:00
Koushik Dutta
2ceb2cd9c3 alexa: maybe fix alexa when no detection types are available 2025-02-05 12:49:04 -08:00
Koushik Dutta
a5fd1c0278 postbeta 2025-02-05 12:09:25 -08:00
Koushik Dutta
2c717cb4fc postrelease 2025-02-05 12:09:03 -08:00
Koushik Dutta
df10c4e5f2 server: fixup address, make it available on cluster manager 2025-02-05 12:08:50 -08:00
Koushik Dutta
7c51bb420e postbeta 2025-02-05 11:51:38 -08:00
Koushik Dutta
367eafff5c postrelease 2025-02-05 11:51:26 -08:00
209 changed files with 11881 additions and 3333 deletions

2
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
# disable blank issue creation
blank_issues_enabled: false

View File

@@ -8,7 +8,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: self-hosted
env:
NODE_VERSION: '20'
NODE_VERSION: '22'
strategy:
matrix:
BASE: ["noble"]

View File

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

3630
common/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -123,6 +123,9 @@ export function createAsyncQueue<T>() {
}
return {
[Symbol.dispose]() {
end(new Error('async queue disposed'));
},
get ended() {
return ended;
},

View File

@@ -7,7 +7,6 @@ const { systemManager } = sdk;
export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
hasEnabledMixin: { [id: string]: string } = {};
pluginsComponent: Promise<any>;
unshiftMixin = false;
constructor(nativeId?: string, public autoIncludeToken = 'v4') {
super(nativeId);
@@ -45,6 +44,10 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
return this.hasEnabledMixin[device.id] === this.autoIncludeToken;
}
shouldUnshiftMixin(device: ScryptedDevice) {
return false;
}
async maybeEnableMixin(device: ScryptedDevice) {
if (!device || device.mixins?.includes(this.id))
return;
@@ -61,7 +64,7 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
this.log.i('auto enabling mixin for ' + device.name)
const mixins = (device.mixins || []).slice();
if (this.unshiftMixin)
if (this.shouldUnshiftMixin(device))
mixins.unshift(this.id);
else
mixins.push(this.id);
@@ -77,5 +80,5 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
this.storage.setItem('hasEnabledMixin', JSON.stringify(this.hasEnabledMixin));
}
abstract canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[] | null | undefined | void>;
abstract canMixin(type: ScryptedDeviceType | string, interfaces: string[]): Promise<string[] | null | undefined | void>;
}

View File

@@ -1,96 +0,0 @@
export interface RefreshPromise<T> {
promise: Promise<T>;
cacheDuration: number;
}
export function singletonPromise<T>(rp: undefined | RefreshPromise<T>, method: () => Promise<T>, cacheDuration = 0) {
if (rp?.promise)
return rp;
const promise = method();
if (!rp) {
rp = {
promise,
cacheDuration,
}
}
else {
rp.promise = promise;
}
promise.finally(() => setTimeout(() => rp.promise = undefined, rp.cacheDuration));
return rp;
}
export class TimeoutError<T> extends Error {
constructor(public promise: Promise<T>) {
super('Operation Timed Out');
}
}
export function timeoutPromise<T>(timeout: number, promise: Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const t = setTimeout(() => reject(new TimeoutError(promise)), timeout);
promise
.then(v => {
clearTimeout(t);
resolve(v);
})
.catch(e => {
clearTimeout(t);
reject(e);
});
})
}
export function timeoutFunction<T>(timeout: number, f: (isTimedOut: () => boolean) => Promise<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
let isTimedOut = false;
const promise = f(() => isTimedOut);
const t = setTimeout(() => {
isTimedOut = true;
reject(new TimeoutError(promise));
}, timeout);
promise
.then(v => {
clearTimeout(t);
resolve(v);
})
.catch(e => {
clearTimeout(t);
reject(e);
});
})
}
export function createPromiseDebouncer<T>() {
let current: Promise<T>;
return (func: () => Promise<T>): Promise<T> => {
if (!current)
current = func().finally(() => current = undefined);
return current;
}
}
export function createMapPromiseDebouncer<T>() {
const map = new Map<string, Promise<T>>();
return (key: any, debounce: number, func: () => Promise<T>): Promise<T> => {
const keyStr = JSON.stringify(key);
let value = map.get(keyStr);
if (!value) {
value = func().finally(() => {
if (!debounce) {
map.delete(keyStr);
return;
}
setTimeout(() => map.delete(keyStr), debounce);
});
map.set(keyStr, value);
}
return value;
}
}

1
common/src/promise-utils.ts Symbolic link
View File

@@ -0,0 +1 @@
../../server/src/promise-utils.ts

View File

@@ -95,6 +95,9 @@ export const H265_NAL_TYPE_SPS = 33;
export const H265_NAL_TYPE_PPS = 34;
export const H265_NAL_TYPE_IDR_N = 19;
export const H265_NAL_TYPE_IDR_W = 20;
export const H265_NAL_TYPE_FU = 49;
export const H265_NAL_TYPE_SEI_PREFIX = 39;
export const H265_NAL_TYPE_SEI_SUFFIX = 40;
export function findH264NaluType(streamChunk: StreamChunk, naluType: number) {
if (streamChunk.type !== 'h264')
@@ -161,10 +164,10 @@ export function findH265NaluTypeInNalu(nalu: Buffer, naluType: number) {
return;
}
export function getNaluTypes(streamChunk: StreamChunk) {
export function getStartedH264NaluTypes(streamChunk: StreamChunk) {
if (streamChunk.type !== 'h264')
return new Set<number>();
return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12))
return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), true)
}
export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) {
@@ -205,10 +208,10 @@ export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaReq
return ret;
}
export function getH265NaluTypes(streamChunk: StreamChunk) {
export function getStartedH265NaluTypes(streamChunk: StreamChunk) {
if (streamChunk.type !== 'h265')
return new Set<number>();
return getNaluTypesInH265Nalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12))
return getNaluTypesInH265Nalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), true)
}
export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) {
@@ -216,7 +219,7 @@ export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fu
const naluType = parseH265NaluType(nalu[0]);
if (naluType === H265_NAL_TYPE_AGG) {
ret.add(H265_NAL_TYPE_AGG);
let pos = 1;
let pos = 2;
while (pos < nalu.length) {
const naluLength = nalu.readUInt16BE(pos);
pos += 2;
@@ -225,6 +228,23 @@ export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fu
pos += naluLength;
}
}
else if (naluType === H265_NAL_TYPE_FU) {
ret.add(H265_NAL_TYPE_FU);
const fuaType = nalu[2] & 0x3F; // 6 bits
if (fuaRequireStart) {
const isFuStart = !!(nalu[2] & 0x80);
if (isFuStart)
ret.add(fuaType);
}
else if (fuaRequireEnd) {
const isFuEnd = !!(nalu[2] & 0x40);
if (isFuEnd)
ret.add(fuaType);
}
else {
ret.add(fuaType);
}
}
else {
ret.add(naluType);
}
@@ -255,13 +275,13 @@ export function createRtspParser(options?: StreamParserOptions): RtspStreamParse
for (let prebufferIndex = 0; prebufferIndex < streamChunks.length; prebufferIndex++) {
const streamChunk = streamChunks[prebufferIndex];
if (streamChunk.type === 'h264') {
const naluTypes = getNaluTypes(streamChunk);
const naluTypes = getStartedH264NaluTypes(streamChunk);
if (naluTypes.has(H264_NAL_TYPE_SPS) || naluTypes.has(H264_NAL_TYPE_IDR)) {
return streamChunks.slice(prebufferIndex);
}
}
else if (streamChunk.type === 'h265') {
const naluTypes = getH265NaluTypes(streamChunk);
const naluTypes = getStartedH265NaluTypes(streamChunk);
if (naluTypes.has(H265_NAL_TYPE_VPS)
|| naluTypes.has(H265_NAL_TYPE_SPS)

View File

@@ -359,3 +359,33 @@ export function getSpsPps(
pps: Buffer.from(pps, 'base64'),
}
}
export function getSpsPpsVps(
section: {
fmtp: {
payloadType: number;
parameters: {
[key: string]: string;
};
}[]
}
) {
const parameters = section?.fmtp?.[0]?.parameters;
if (!parameters) {
return {
sps: undefined,
pps: undefined,
vps: undefined,
};
}
const sps = parameters['sprop-sps'];
const pps = parameters['sprop-pps'];
const vps = parameters['sprop-vps'];
return {
sps: sps ? Buffer.from(sps, 'base64') : undefined,
pps: pps ? Buffer.from(pps, 'base64') : undefined,
vps: vps ? Buffer.from(vps, 'base64') : undefined,
}
}

View File

@@ -23,7 +23,9 @@ export function createService<T, V>(options: ForkOptions, create: (t: Promise<T>
currentFork = sdk.fork<T>(options);
currentFork.worker.on('exit', () => currentResult = undefined);
currentResult = create(currentFork.result);
currentResult.catch(() => currentResult = undefined);
currentResult.catch(() => {
currentResult = undefined;
});
return currentResult;
},

View File

@@ -6,7 +6,7 @@
# This common file will be used by both Docker and the linux
# install script.
################################################################
ARG BASE="jammy"
ARG BASE="noble"
FROM ubuntu:${BASE} as header
ENV DEBIAN_FRONTEND=noninteractive
@@ -19,7 +19,7 @@ RUN apt-get update && apt-get -y install \
apt-get -y update && \
apt-get -y upgrade
ARG NODE_VERSION=20
ARG NODE_VERSION=22
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg

View File

@@ -10,7 +10,7 @@ RUN apt-get update && apt-get -y install \
apt-get -y update && \
apt-get -y upgrade
ARG NODE_VERSION=20
ARG NODE_VERSION=22
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
@@ -21,6 +21,7 @@ ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
ENV SCRYPTED_CAN_RESTART="true"
ENV SCRYPTED_VOLUME="/server/volume"
ENV SCRYPTED_INSTALL_PATH="/server"
ENV SHELL="/bin/bash"
RUN test -f "/usr/bin/python3" && test -f "/usr/bin/python3.12"
ENV SCRYPTED_PYTHON_PATH="/usr/bin/python3"

View File

@@ -0,0 +1,50 @@
ARG BASE="noble-lite"
FROM ghcr.io/koush/scrypted-common:${BASE}
# tools
RUN apt -y update && apt -y install nano net-tools dnsutils dnsmasq vlan bridge-utils netplan.io nftables isc-dhcp-client
RUN rm -f /etc/systemd/system/multi-user.target.wants/dnsmasq.service
RUN rm -f /etc/systemd/system/sysinit.target.wants/systemd-resolved.service
# go + caddy
RUN apt -y install golang-go
RUN apt install -y debian-keyring debian-archive-keyring apt-transport-https
RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-xcaddy-archive-keyring.gpg
RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/xcaddy/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-xcaddy.list
RUN apt -y update
RUN apt -y install xcaddy
RUN xcaddy build --with github.com/caddy-dns/cloudflare --output /usr/local/bin/caddy
# nftables
COPY ./router/scrypted-nftables.service /etc/systemd/system
RUN systemctl enable scrypted-nftables
RUN bash -c 'echo include \"/etc/nftables.d/*.conf\"\; > /etc/nftables.conf'
RUN mkdir -p /etc/nftables.d
COPY ./router/01-scrypted.conf /etc/nftables.d
# ipv6 forwarding
COPY ./router/scrypted-ip-forwarding.service /etc/systemd/system
RUN systemctl enable scrypted-ip-forwarding
# install turn server, but disable it too set it up on a per interface basis.
RUN apt -y update && apt -y install coturn && systemctl disable coturn && rm /usr/lib/systemd/system/coturn.service
# install usbmuxd for iphone tethering
# ensure the pairing info stays in persistent storage
RUN apt -y update && apt -y install usbmuxd && rm /usr/lib/systemd/system/usbmuxd.service && ln -sf /server/volume/plugins/\@scrypted/router/usbmuxd /var/lib/lockdown
WORKDIR /
# cache bust
ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache
ARG SCRYPTED_INSTALL_VERSION="latest"
RUN test -n "$SCRYPTED_INSTALL_VERSION"
RUN npx -y scrypted@latest install-server ${SCRYPTED_INSTALL_VERSION}
COPY ./router/scrypted-dhcp-watcher.service /etc/systemd/system/scrypted-dhcp-watcher.service
RUN systemctl enable scrypted-dhcp-watcher
COPY ./router/scrypted.service /etc/systemd/system/scrypted.service
RUN systemctl enable scrypted
WORKDIR /
CMD ["/sbin/init"]

View File

@@ -2,7 +2,7 @@
set -x
NODE_VERSION=20
NODE_VERSION=22
SCRYPTED_INSTALL_VERSION=beta
IMAGE_BASE=jammy
FLAVOR=full

View File

@@ -34,8 +34,10 @@ set -e
mkdir -p /tmp/amd
cd /tmp/amd
curl -O -L http://repo.radeon.com/amdgpu-install/latest/ubuntu/$distro/$FILENAME
apt -y update
apt -y install rsync
dpkg -i $FILENAME
apt -y update
amdgpu-install --usecase=opencl --no-dkms -y --accept-eula
cd /tmp
rm -rf /tmp/amd

View File

@@ -4,6 +4,25 @@ then
exit 0
fi
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
# needs either ubuntu 22.0.4 or 24.04
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
then
echo "Intel graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
exit 1
fi
if [ -n "$UBUNTU_22_04" ]
then
distro="jammy"
else
distro="noble"
fi
# no errors beyond this point
set -e
@@ -21,13 +40,26 @@ set -e
# need intel-media-va-driver-non-free, but all the other intel packages are installed from Intel github.
echo "Installing Intel graphics packages."
apt-get update && apt-get install -y gpg-agent &&
rm -f /usr/share/keyrings/intel-graphics.gpg &&
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list &&
apt-get -y update &&
apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
if [ "$distro" == "jammy" ]
then
apt-get update && apt-get install -y gpg-agent &&
rm -f /usr/share/keyrings/intel-graphics.gpg &&
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
echo "deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu $distro arc" | tee /etc/apt/sources.list.d/intel.gpu.$distro.list &&
apt-get -y update &&
apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
else
apt-get update && apt-get install -y gpg-agent &&
rm -f /usr/share/keyrings/intel-graphics.gpg &&
curl -L https://repositories.intel.com/gpu/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
echo "deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu $distro unified" | tee /etc/apt/sources.list.d/intel-gpu-$distro.list &&
apt-get -y update &&
apt-get -y install intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
fi
rm -rf /tmp/gpu && mkdir -p /tmp/gpu && cd /tmp/gpu
@@ -55,12 +87,13 @@ rm -f *.deb
# https://github.com/intel/compute-runtime/releases/tag/24.45.31740.9
# note that at time of commit, IGC supports ubuntu 24.04 only possibly due to their builder being on 24.04.
IGC_VERSION=2_2.1.12+18087_amd64
COMPUTE_VERSION=24.45.31740.9
ZERO_GPU_VERSION=1.6.31740.9_amd64
LIBIGDGMM_VERSION=22.5.2_amd64
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.1.12/intel-igc-core-$IGC_VERSION.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v2.1.12/intel-igc-opencl-$IGC_VERSION.deb
IGC_BASE_VERSION=2.5.6
IGC_VERSION=2_$IGC_BASE_VERSION+18417_amd64
COMPUTE_VERSION=24.52.32224.5
ZERO_GPU_VERSION=1.6.32224.5_amd64
LIBIGDGMM_VERSION=22.5.5_amd64
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v$IGC_BASE_VERSION/intel-igc-core-$IGC_VERSION.deb
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/v$IGC_BASE_VERSION/intel-igc-opencl-$IGC_VERSION.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu-dbgsym_$ZERO_GPU_VERSION.ddeb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-level-zero-gpu_$ZERO_GPU_VERSION.deb
curl -O -L https://github.com/intel/compute-runtime/releases/download/$COMPUTE_VERSION/intel-opencl-icd-dbgsym_"$COMPUTE_VERSION"_amd64.ddeb

View File

@@ -45,8 +45,8 @@ curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_Z
# npu driver
# https://github.com/intel/linux-npu-driver
NPU_VERSION=1.10.0
NPU_VERSION_DATE=20241107-11729849322
NPU_VERSION=1.13.0
NPU_VERSION_DATE=20250131-13074932693
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-driver-compiler-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
# firmware can only be installed on host. will cause problems inside container.
if [ -n "$INTEL_FW_NPU" ]

View File

@@ -0,0 +1,55 @@
table ip nat {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
jump postrouting_scrypted
}
chain postrouting_scrypted {
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
jump prerouting_scrypted;
}
chain prerouting_scrypted {
}
}
table ip filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump forward_scrypted
}
chain forward_scrypted {
}
}
table ip6 nat {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
jump postrouting_scrypted
}
chain postrouting_scrypted {
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
jump prerouting_scrypted;
}
chain prerouting_scrypted {
}
}
table ip6 filter {
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump forward_scrypted
}
chain forward_scrypted {
}
}

View File

@@ -0,0 +1,11 @@
[Unit]
Description=Scrypted DHCP Watcher
After=network.target
[Service]
ExecStart=/etc/dhcp/scrypted-dhcp-watcher
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,17 @@
[Unit]
Description=ipv6 forwarding
After=network.target
Conflicts=shutdown.target
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
StandardInput=null
ProtectSystem=full
ProtectHome=true
ExecStart=bash -c "sysctl -w net.ipv4.ip_forward=1 && sysctl -w net.ipv6.conf.all.forwarding=1 && sysctl -w net.ipv6.conf.default.forwarding=1"
ExecReload=bash -c "sysctl -w net.ipv4.ip_forward=1 && sysctl -w net.ipv6.conf.all.forwarding=1 && sysctl -w net.ipv6.conf.default.forwarding=1"
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,18 @@
[Unit]
Description=nftables
Documentation=man:nft(8) http://wiki.nftables.org
After=network.target
Conflicts=shutdown.target
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
StandardInput=null
ProtectSystem=full
ProtectHome=true
ExecStart=/usr/sbin/nft -f /etc/nftables.conf
ExecReload=/usr/sbin/nft -f /etc/nftables.conf
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,24 @@
[Unit]
Description=Scrypted
After=network.target
[Service]
WorkingDirectory=/server
ExecStart=/bin/sh -c "ulimit -c 0; exec npm exec scrypted-serve"
Restart=always
RestartSec=5
Environment="SCRYPTED_INSTALL_ENVIRONMENT=docker"
Environment="SCRYPTED_CAN_RESTART=true"
Environment="SCRYPTED_VOLUME=/server/volume"
Environment="SCRYPTED_INSTALL_PATH=/server"
Environment="SCRYPTED_PYTHON_PATH=/usr/bin/python3"
Environment="SCRYPTED_PYTHON312_PATH=/usr/bin/python3.12"
Environment="SCRYPTED_DOCKER_FLAVOR=lite"
Environment="DEBIAN_FRONTEND=noninteractive"
Environment="NODE_OPTIONS=--dns-result-order=ipv4first"
Environment="SCRYPTED_BASE_VERSION=20250101"
Environment="SHELL=/bin/bash"
[Install]
WantedBy=multi-user.target

View File

@@ -3,7 +3,7 @@
# This common file will be used by both Docker and the linux
# install script.
################################################################
ARG BASE="jammy"
ARG BASE="noble"
FROM ubuntu:${BASE} as header
ENV DEBIAN_FRONTEND=noninteractive
@@ -16,7 +16,7 @@ RUN apt-get update && apt-get -y install \
apt-get -y update && \
apt-get -y upgrade
ARG NODE_VERSION=20
ARG NODE_VERSION=22
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg

View File

@@ -39,12 +39,16 @@ launchctl unload ~/Library/LaunchAgents/app.scrypted.server.plist || echo ""
echo "Installing Scrypted dependencies..."
RUN_IGNORE xcode-select --install
RUN brew update
RUN_IGNORE brew install node@20
# dlib
RUN brew install cmake
# seems to be necessary for python-codecs' pycairo dependency or something?
RUN_IGNORE gobject-introspection libffi pkg-config
# in sequoia, brew node is unusable because it is not signed and can't access local network unless run as root.
# https://developer.apple.com/forums/thread/766270
# RUN_IGNORE brew install node@20
# NODE_PATH=$(brew --prefix node@20)
# NODE_BIN_PATH=$NODE_PATH/bin
RUN_IGNORE curl -L https://nodejs.org/dist/v22.14.0/node-v22.14.0.pkg -o /tmp/node.pkg
RUN_IGNORE sudo installer -pkg /tmp/node.pkg -target /
NODE_PATH=/usr/local # used to pass var test
NODE_BIN_PATH=/usr/local/bin
# gstreamer plugins
RUN_IGNORE brew install gstreamer
@@ -82,14 +86,12 @@ echo "Installing Scrypted Launch Agent..."
RUN mkdir -p ~/Library/LaunchAgents
NODE_PATH=$(brew --prefix node@20)
if [ ! -d "$NODE_PATH" ]
then
echo "Unable to determine node@20 path."
exit 1
fi
NODE_BIN_PATH=$NODE_PATH/bin
if [ ! -d "$NODE_BIN_PATH" ]
then
echo "Unable to determine node@20 bin path."
@@ -155,7 +157,7 @@ cat > ~/Library/LaunchAgents/app.scrypted.server.plist <<EOT
<key>NODE_OPTIONS</key>
<string>$NODE_OPTIONS</string>
<key>PATH</key>
<string>$NODE_BIN_PATH:$PYTHON_BIN_PATH:$BREW_PREFIX/bin:$BREW_PREFIX/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<string>$PYTHON_BIN_PATH:$NODE_BIN_PATH:$BREW_PREFIX/bin:$BREW_PREFIX/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>HOME</key>
<string>/Users/$USER</string>
<key>SCRYPTED_PYTHON_PATH</key>

View File

@@ -1,4 +1,13 @@
#Requires -RunAsAdministrator
# Check if the script is running as administrator
$IsAdmin = [System.Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()
$AdminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
if (-not $IsAdmin.IsInRole($AdminRole)) {
# If not, relaunch the script with elevated privileges
$ScriptPath = $PSCommandPath
Start-Process powershell -ArgumentList "-File `"$ScriptPath`"" -Verb RunAs
exit
}
# Set-PSDebug -Trace 1

View File

@@ -18,7 +18,7 @@ function readyn() {
}
cd /tmp
SCRYPTED_VERSION=v0.120.0
SCRYPTED_VERSION=v0.137.0
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
if [ -z "$VMID" ]
then
@@ -139,7 +139,12 @@ then
echo "#############################################################################"
echo -e "\033[32mPaste the following command into this shell to install to local-lvm instead:\033[0m"
echo ""
echo "bash $0 --storage local-lvm"
if [ -n "$SCRYPTED_RESTORE" ]
then
echo "bash $0 --storage local-lvm"
else
echo "SCRYPTED_RESTORE=true bash $0 --storage local-lvm"
fi
echo "#############################################################################"
echo ""
echo ""

View File

@@ -9,23 +9,23 @@
"version": "1.3.20",
"license": "ISC",
"dependencies": {
"@scrypted/client": "^1.3.3",
"@scrypted/types": "^0.3.30",
"engine.io-client": "^6.5.3",
"@scrypted/client": "^1.3.13",
"@scrypted/types": "^0.5.9",
"engine.io-client": "^6.6.3",
"readline-sync": "^1.4.10",
"semver": "^7.5.4",
"tslib": "^2.6.2"
"semver": "^7.7.1",
"tslib": "^2.8.1"
},
"bin": {
"scrypted": "dist/packages/cli/src/main.js"
},
"devDependencies": {
"@types/node": "^20.9.4",
"@types/node": "^22.13.10",
"@types/readline-sync": "^1.4.8",
"@types/semver": "^7.5.6",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
"@types/semver": "^7.5.8",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -44,6 +44,7 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@@ -81,30 +82,24 @@
"@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/client": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@scrypted/client/-/client-1.3.3.tgz",
"integrity": "sha512-Wuy7x02TCRy1buaDNX8NOIaL1j4ZXu4dqTTJsKHlPe3+umsBvpwbylD+YyyU8ghQJC6a40Bs5UMsvnCvNa/1fg==",
"version": "1.3.13",
"resolved": "https://registry.npmjs.org/@scrypted/client/-/client-1.3.13.tgz",
"integrity": "sha512-jxXnGCoHIwuB7PobPJyqYy9THljR2UJOILxO++HiNq/i0nqRUECYvVfU5frN/ZnP6nmQoiRKrl8ErGWVBT7ecg==",
"license": "ISC",
"dependencies": {
"@scrypted/types": "^0.3.4",
"engine.io-client": "^6.5.3",
"follow-redirects": "^1.15.4",
"rimraf": "^5.0.5"
"engine.io-client": "^6.6.3",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
},
"peerDependencies": {
"@scrypted/types": "^0.5.9"
}
},
"node_modules/@scrypted/types": {
"version": "0.3.30",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.30.tgz",
"integrity": "sha512-1k+JVSR6WSNmE/5mLdqfrTmV3uRbvZp0OwKb8ikNi39ysBuC000tQGcEdXZqhYqRgWdhDTWtxXe9XsYoAZGKmA==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.9.tgz",
"integrity": "sha512-Qt/gLdzDqYwgOArpLrEErPb91mhAOmN0NFFOFwX9G/5vSV5Xvm6ixQhPWF4f+up3G9ecPVPMPtZCsWmhxAD1hA==",
"license": "ISC"
},
"node_modules/@socket.io/component-emitter": {
@@ -137,12 +132,13 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.9.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
"integrity": "sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA==",
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~6.20.0"
}
},
"node_modules/@types/readline-sync": {
@@ -152,10 +148,11 @@
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
"integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==",
"dev": true
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true,
"license": "MIT"
},
"node_modules/acorn": {
"version": "8.8.2",
@@ -179,9 +176,10 @@
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -193,6 +191,7 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -209,12 +208,14 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -223,6 +224,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
@@ -233,7 +235,8 @@
"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=="
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
@@ -242,9 +245,10 @@
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -282,23 +286,26 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/engine.io-client": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
@@ -310,15 +317,16 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"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",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -329,11 +337,12 @@
}
},
"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.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.0",
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -344,21 +353,23 @@
}
},
"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.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz",
"integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==",
"license": "ISC",
"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"
@@ -368,6 +379,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -375,34 +387,31 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"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.1.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz",
"integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==",
"license": "BlueOak-1.0.0",
"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": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz",
"integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
"license": "ISC",
"engines": {
"node": ">=10"
"node": "20 || >=22"
}
},
"node_modules/make-error": {
@@ -412,23 +421,25 @@
"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==",
"license": "ISC",
"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==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -438,37 +449,37 @@
"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==",
"license": "BlueOak-1.0.0"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"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==",
"license": "BlueOak-1.0.0",
"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/path-scurry/node_modules/lru-cache": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.3.tgz",
"integrity": "sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==",
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/readline-sync": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
@@ -478,29 +489,29 @@
}
},
"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==",
"license": "ISC",
"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"
}
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -512,6 +523,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -523,6 +535,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -531,6 +544,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
@@ -542,6 +556,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@@ -559,6 +574,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -572,6 +588,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -579,12 +596,14 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -596,6 +615,7 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -611,6 +631,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -622,15 +643,17 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -670,15 +693,17 @@
}
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -688,10 +713,11 @@
}
},
"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==",
"dev": true
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
@@ -703,6 +729,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
@@ -717,6 +744,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@@ -734,6 +762,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -750,6 +779,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -758,6 +788,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -771,12 +802,14 @@
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -790,6 +823,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -798,15 +832,16 @@
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
@@ -818,18 +853,13 @@
}
},
"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.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View File

@@ -16,19 +16,19 @@
"author": "",
"license": "ISC",
"dependencies": {
"@scrypted/client": "^1.3.3",
"@scrypted/types": "^0.3.30",
"engine.io-client": "^6.5.3",
"@scrypted/client": "^1.3.13",
"@scrypted/types": "^0.5.9",
"engine.io-client": "^6.6.3",
"readline-sync": "^1.4.10",
"semver": "^7.5.4",
"tslib": "^2.6.2"
"semver": "^7.7.1",
"tslib": "^2.8.1"
},
"devDependencies": {
"@types/node": "^20.9.4",
"@types/node": "^22.13.10",
"@types/readline-sync": "^1.4.8",
"@types/semver": "^7.5.6",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
"@types/semver": "^7.5.8",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}

View File

@@ -166,8 +166,6 @@ async function main() {
}
}
const args = ffmpegInput.inputArguments ? [...ffmpegInput.inputArguments] : [];
if (ffmpegInput.h264FilterArguments)
args.push(...ffmpegInput.h264FilterArguments);
console.log('ffplay', ...args);
child_process.spawn('ffplay', args, {
stdio: 'inherit',

View File

@@ -7,7 +7,7 @@ export async function connectShell(sdk: ScryptedStatic, ...cmd: string[]) {
throw Error("@scrypted/core does not provide a Terminal Service");
}
const termSvcDirect = await sdk.connectRPCObject<StreamService>(termSvc);
const termSvcDirect = await sdk.connectRPCObject<StreamService<Buffer|string, Buffer>>(termSvc);
const dataQueue = createAsyncQueue<Buffer>();
const ctrlQueue = createAsyncQueue<any>();

View File

@@ -1,25 +1,27 @@
{
"name": "@scrypted/client",
"version": "1.3.10",
"version": "1.3.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/client",
"version": "1.3.10",
"version": "1.3.13",
"license": "ISC",
"dependencies": {
"@scrypted/types": "^0.3.100",
"engine.io-client": "^6.6.2",
"engine.io-client": "^6.6.3",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
},
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^22.10.7",
"@types/ws": "^8.5.13",
"@types/node": "^22.13.10",
"@types/ws": "^8.18.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
"typescript": "^5.8.2"
},
"peerDependencies": {
"@scrypted/types": "^0.5.12"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -27,6 +29,7 @@
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@@ -38,6 +41,7 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
@@ -51,78 +55,88 @@
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@scrypted/types": {
"version": "0.3.100",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.100.tgz",
"integrity": "sha512-s/07QCxjMWqODgWj2UpLehzeo2cGFrCA9X8mvpG3owT/+q+sb8v/UUcw9TLHGSN6yIriNhceg3i9WO07kEIT6A==",
"license": "ISC"
"version": "0.5.12",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.5.12.tgz",
"integrity": "sha512-nTwcMHZyH3nXThL22eNcVw7OjSyL5qoTgUay6K7y43HKz1mBnFEmIUkW8eLdyP4nbpwwA0b60MOPDKZVnssB0Q==",
"license": "ISC",
"peer": true
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/@types/ip": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz",
"integrity": "sha512-64waoJgkXFTYnCYDUWgSATJ/dXEBanVkaP5d4Sbk7P6U7cTTMhxVyROTckc6JKdwCrgnAjZMn0k3177aQxtDEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "22.10.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
"integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -130,9 +144,9 @@
}
},
"node_modules/@types/ws": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -140,10 +154,11 @@
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -152,10 +167,14 @@
}
},
"node_modules/acorn-walk": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
@@ -164,6 +183,7 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -175,6 +195,7 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"license": "MIT",
"engines": {
"node": ">=12"
},
@@ -186,17 +207,20 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -205,6 +229,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
@@ -215,13 +240,15 @@
"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=="
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
@@ -238,11 +265,12 @@
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -258,6 +286,7 @@
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
@@ -265,17 +294,19 @@
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/engine.io-client": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz",
"integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
@@ -286,9 +317,10 @@
}
},
"node_modules/engine.io-parser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
@@ -303,6 +335,7 @@
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -313,11 +346,12 @@
}
},
"node_modules/foreground-child": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.0",
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -328,9 +362,10 @@
}
},
"node_modules/glob": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
"integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz",
"integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^4.0.1",
@@ -353,6 +388,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -360,12 +396,14 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
"integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz",
"integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
@@ -377,9 +415,10 @@
}
},
"node_modules/lru-cache": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
"integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==",
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz",
"integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
@@ -388,12 +427,14 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
"dev": true,
"license": "ISC"
},
"node_modules/minimatch": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@@ -408,24 +449,28 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"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=="
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -434,6 +479,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
@@ -449,6 +495,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
"license": "ISC",
"dependencies": {
"glob": "^11.0.0",
"package-json-from-dist": "^1.0.0"
@@ -467,6 +514,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -478,6 +526,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -486,6 +535,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
@@ -497,6 +547,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
@@ -514,6 +565,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -527,6 +579,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -534,12 +587,14 @@
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -551,6 +606,7 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
@@ -566,6 +622,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -577,6 +634,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -586,6 +644,7 @@
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -625,9 +684,9 @@
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -649,12 +708,14 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
@@ -669,6 +730,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
@@ -686,6 +748,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -702,6 +765,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -710,6 +774,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -723,12 +788,14 @@
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -742,6 +809,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -753,6 +821,7 @@
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
@@ -770,9 +839,9 @@
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
@@ -782,6 +851,7 @@
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/client",
"version": "1.3.10",
"version": "1.3.13",
"description": "",
"main": "dist/packages/client/src/index.js",
"scripts": {
@@ -13,14 +13,16 @@
"license": "ISC",
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^22.10.7",
"@types/ws": "^8.5.13",
"@types/node": "^22.13.10",
"@types/ws": "^8.18.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
"typescript": "^5.8.2"
},
"peerDependencies": {
"@scrypted/types": "^0.5.12"
},
"dependencies": {
"@scrypted/types": "^0.3.100",
"engine.io-client": "^6.6.2",
"engine.io-client": "^6.6.3",
"follow-redirects": "^1.15.9",
"rimraf": "^6.0.1"
}

View File

@@ -535,7 +535,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
check.on('message', data => {
if (data.constructor === Buffer || data.constructor === ArrayBuffer) {
serializer.onMessageBuffer(Buffer.from(data));
serializer.onMessageBuffer(Buffer.from(data as string));
}
else {
serializer.onMessageFinish(JSON.parse(data as string));
@@ -683,7 +683,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
});
socket.on('message', data => {
if (data.constructor === Buffer || data.constructor === ArrayBuffer) {
serializer.onMessageBuffer(Buffer.from(data));
serializer.onMessageBuffer(Buffer.from(data as string));
}
else {
serializer.onMessageFinish(JSON.parse(data as string));

View File

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

View File

@@ -1,6 +1,21 @@
<details>
<summary>Changelog</summary>
### 0.3.6
alexa: maybe fix alexa when no detection types are available
### 0.3.4
Alexa: add option to not auto enable devices (#1615)
### 0.3.3
google-home/alexa: republish with new sdk for media converter
### 0.3.2
alexa: fix syncedDevices being undefined

View File

@@ -1,11 +1,12 @@
{
"name": "@scrypted/alexa",
"version": "0.3.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/alexa",
"version": "0.3.4",
"version": "0.3.7",
"dependencies": {
"axios": "^1.3.4",
"uuid": "^9.0.0"
@@ -14,7 +15,7 @@
"@scrypted/common": "../../common",
"@scrypted/sdk": "../../sdk",
"@types/debug": "^4.1.12",
"@types/node": "^18.4.2"
"@types/node": "^22.13.11"
}
},
"../../common": {
@@ -24,35 +25,41 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
"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.5",
"version": "0.5.10",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -64,11 +71,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
}
},
"../common": {
@@ -98,10 +103,14 @@
"dev": true
},
"node_modules/@types/node": {
"version": "18.14.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz",
"integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==",
"dev": true
"version": "22.13.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
"integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
@@ -193,6 +202,13 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
"node_modules/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
@@ -202,4 +218,4 @@
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/alexa",
"version": "0.3.4",
"version": "0.3.7",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
@@ -42,6 +42,6 @@
"@scrypted/common": "../../common",
"@scrypted/sdk": "../../sdk",
"@types/debug": "^4.1.12",
"@types/node": "^18.4.2"
"@types/node": "^22.13.11"
}
}

View File

@@ -660,17 +660,26 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
const deviceHandler = alexaDeviceHandlers.get(mapName);
if (deviceHandler) {
const getDevice = () => {
const device = systemManager.getDeviceById(directive.endpoint.endpointId);
if (!device) {
response.send(deviceErrorResponse("NO_SUCH_ENDPOINT", "The device doesn't exist in Scrypted", directive));
if (!device || !device.mixins.includes(this.id)) {
response.send(deviceErrorResponse("NO_SUCH_ENDPOINT", "The device doesn't exist in Scrypted or was removed from the Alexa Plugin", directive));
this.deleteEndpoints(directive.endpoint.endpointId).catch(() => { });
return;
}
return device;
}
if (deviceHandler) {
const device = getDevice();
if (!device)
return;
await deviceHandler.apply(this, [request, response, directive, device]);
return;
} else {
this.console.error(`no handler for: ${mapName}`);
if (!getDevice())
return;
}
// it is better to send a non-specific response than an error, as the API might get rate throttled
@@ -718,6 +727,9 @@ class HttpResponseLoggingImpl implements AlexaHttpResponse {
sendSocket(socket: any, options: HttpResponseOptions): void {
this.response.sendSocket(socket, options);
}
sendStream(stream: AsyncGenerator<Buffer, void>, options?: HttpResponseOptions): void {
this.response.sendStream(stream, options);
}
}
export default AlexaPlugin;

View File

@@ -126,68 +126,69 @@ export async function getCameraCapabilities(device: ScryptedDevice): Promise<Dis
];
if (device.interfaces.includes(ScryptedInterface.ObjectDetector)) {
const detectionTypes = await (device as any as ObjectDetector).getObjectTypes();
const classNames = detectionTypes.classes.filter(t => t !== 'ring' && t !== 'motion').map(type => type.toLowerCase());
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0",
"properties": {
"supported": [{
"name": "objectDetectionClasses"
}],
"proactivelyReported": true,
"retrievable": true
},
"configuration": {
"objectDetectionConfiguration": classNames.map(type => ({
"imageNetClass": type
}))
}
} as DiscoveryCapability
);
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.DataController",
"instance": "Camera.SmartVisionData",
"version": "1.0",
"properties": undefined,
"configuration": {
"targetCapability": {
"name": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0"
const detectionTypes = await (device as any as ObjectDetector).getObjectTypes().catch(() => {}) || undefined;
const classNames = detectionTypes?.classes?.filter(t => t !== 'ring' && t !== 'motion').map(type => type.toLowerCase()).filter(c => !!c);
if (classNames?.length) {
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0",
"properties": {
"supported": [{
"name": "objectDetectionClasses"
}],
"proactivelyReported": true,
"retrievable": true
},
"dataRetrievalSchema": {
"type": "JSON",
"schema": "SmartVisionData"
},
"supportedAccess": ["BY_IDENTIFIER", "BY_TIMESTAMP_RANGE"]
}
} as DiscoveryCapability
);
}
if (device.interfaces.includes(ScryptedInterface.MotionSensor)) {
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.MotionSensor",
"version": "3",
"properties": {
"supported": [
{
"name": "detectionState"
}
],
"proactivelyReported": true,
"retrievable": true
}
} as DiscoveryCapability
);
"configuration": {
"objectDetectionConfiguration": classNames.map(type => ({
"imageNetClass": type
}))
}
} as DiscoveryCapability
);
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.DataController",
"instance": "Camera.SmartVisionData",
"version": "1.0",
"properties": undefined,
"configuration": {
"targetCapability": {
"name": "Alexa.SmartVision.ObjectDetectionSensor",
"version": "1.0"
},
"dataRetrievalSchema": {
"type": "JSON",
"schema": "SmartVisionData"
},
"supportedAccess": ["BY_IDENTIFIER", "BY_TIMESTAMP_RANGE"]
}
} as DiscoveryCapability
);
}
if (device.interfaces.includes(ScryptedInterface.MotionSensor)) {
capabilities.push(
{
"type": "AlexaInterface",
"interface": "Alexa.MotionSensor",
"version": "3",
"properties": {
"supported": [
{
"name": "detectionState"
}
],
"proactivelyReported": true,
"retrievable": true
}
} as DiscoveryCapability
);
}
}
return capabilities;

View File

@@ -8,7 +8,7 @@ export interface SupportedType {
setState?(device: ScryptedDevice, payload: any): Promise<Partial<Report>>;
}
export const supportedTypes = new Map<ScryptedDeviceType, SupportedType>();
export const supportedTypes = new Map<ScryptedDeviceType | string, SupportedType>();
import '../handlers';
import './camera';

View File

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

View File

@@ -1,21 +1,23 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.164",
"version": "0.0.165",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.164",
"version": "0.0.165",
"license": "Apache",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"content-type": "^1.0.5"
"content-type": "^1.0.5",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/content-type": "^1.1.8",
"@types/node": "^20.11.30"
"@types/node": "^20.11.30",
"@types/xml2js": "^0.4.14"
}
},
"../../common": {
@@ -24,34 +26,40 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
"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.29",
"version": "0.3.114",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -63,11 +71,9 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
}
},
"node_modules/@scrypted/common": {
@@ -93,6 +99,16 @@
"undici-types": "~5.26.4"
}
},
"node_modules/@types/xml2js": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
@@ -101,11 +117,39 @@
"node": ">= 0.6"
}
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"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==",
"dev": true
},
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.164",
"version": "0.0.165",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",
@@ -39,10 +39,12 @@
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"content-type": "^1.0.5"
"content-type": "^1.0.5",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/content-type": "^1.1.8",
"@types/node": "^20.11.30"
"@types/node": "^20.11.30",
"@types/xml2js": "^0.4.14"
}
}

View File

@@ -1,14 +1,13 @@
import { automaticallyConfigureSettings, checkPluginNeedsAutoConfigure } from "@scrypted/common/src/autoconfigure-codecs";
import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers';
import { readLength } from "@scrypted/common/src/read-stream";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, Lock, MediaObject, MediaStreamOptions, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, Reboot, RequestPictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, Lock, MediaObject, MediaStreamOptions, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, Reboot, RequestPictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, VideoCameraConfiguration, VideoRecorder, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
import child_process, { ChildProcess } from 'child_process';
import { PassThrough, Readable, Stream } from "stream";
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
import { createRtspMediaStreamOptions, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { AmcrestCameraClient, AmcrestEvent, AmcrestEventData } from "./amcrest-api";
import { amcrestAutoConfigureSettings, autoconfigureSettings } from "./amcrest-configure";
import { group } from "console";
const { mediaManager } = sdk;
@@ -23,7 +22,7 @@ const rtspChannelSetting: Setting = {
placeholder: '1',
};
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, Lock, VideoRecorder, Reboot, ObjectDetector {
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, Lock, VideoRecorder, Reboot, ObjectDetector, VideoTextOverlays {
eventStream: Stream;
cp: ChildProcess;
client: AmcrestCameraClient;
@@ -43,6 +42,92 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
this.updateDeviceInfo();
}
async getVideoTextOverlays(): Promise<Record<string, VideoTextOverlay>> {
const client = this.getClient();
const response = await client.request({
method: "GET",
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=getConfig&name=VideoWidget`,
responseType: "text",
headers: {
"Content-Type": "application/xml",
},
});
const body: string = response.body;
if (!body.startsWith("<")) {
const encodeBlend = '.EncodeBlend';
const config: Record<string, VideoTextOverlay> = {};
for (const line of body.split(/\r?\n/).filter(l => l.includes(encodeBlend + '='))) {
const trimmed = line.trim();
if (!trimmed) continue;
const splitIndex = trimmed.indexOf("=");
if (splitIndex === -1) continue;
// remove encodeBlend
let key = trimmed.substring(0, splitIndex);
key = key.substring(0, key.length - encodeBlend.length);
config[key] = {
readonly: true,
};
}
const textValue = '.Text';
for (const line of body.split(/\r?\n/).filter(l => l.includes(textValue + '='))) {
const trimmed = line.trim();
if (!trimmed) continue;
const splitIndex = trimmed.indexOf("=");
if (splitIndex === -1) continue;
// remove encodeBlend
let key = trimmed.substring(0, splitIndex);
key = key.substring(0, key.length - textValue.length);
const text = trimmed.substring(splitIndex + 1).trim();
const c = config[key];
if (!c)
continue;
delete c.readonly;
c.text = text;
}
return config;
} else {
throw new Error('invalid response');
// const json = await xml2js.parseStringPromise(body);
// return { json, xml: body };
}
}
async setVideoTextOverlay(id: string, value: VideoTextOverlay): Promise<void> {
// trim the table. off id
if (id.startsWith('table.'))
id = id.substring('table.'.length);
const client = this.getClient();
if (value.text) {
const enableUrl = `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${id}.EncodeBlend=true&${id}.PreviewBlend=true`;
await client.request({
method: "GET",
url: enableUrl,
responseType: "text",
});
const textUrl = `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${id}.Text=${encodeURIComponent(
value.text
)}`;
await client.request({
method: "GET",
url: textUrl,
responseType: "text",
});
}
else {
const disableUrl = `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${id}.EncodeBlend=false&${id}.PreviewBlend=false`;
await client.request({
method: "GET",
url: disableUrl,
responseType: "text",
});
}
}
async reboot() {
const client = this.getClient();
await client.reboot();
@@ -631,6 +716,7 @@ class AmcrestProvider extends RtspProvider {
ScryptedInterface.Camera,
ScryptedInterface.AudioSensor,
ScryptedInterface.MotionSensor,
ScryptedInterface.VideoTextOverlays,
];
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.3.111",
"version": "0.3.120",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",
@@ -33,10 +33,6 @@
"ScryptedSettings",
"SystemSettings",
"Settings"
],
"pluginDependencies": [
"@scrypted/snapshot",
"@scrypted/webrtc"
]
},
"dependencies": {

View File

@@ -72,7 +72,6 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
container: 'rawvideo',
mediaStreamOptions: (await createVideoStreamOptions())?.[0],
inputArguments: [],
h264FilterArguments: [],
};
for (let i = 0; i < inputs.length; i++) {
@@ -102,7 +101,7 @@ function createVideoCamera(devices: VideoCamera[], console: Console): VideoCamer
let i = dim * dim - 1;
filter.push(`[${prev}][pos${i}] overlay=shortest=1:x=${curx % 1920}:y=${cury % 1080}`);
ffmpegInput.h264FilterArguments.push(
ffmpegInput.inputArguments.push(
'-filter_complex',
filter.join(' '),
);

View File

@@ -155,6 +155,8 @@ export class ClusterCore extends ScryptedDeviceBase implements Settings, Readme,
}
async getReadmeMarkdown(): Promise<string> {
return `Manage Scrypted's cluster mode. Run storage devices and compute services on separate servers.`;
return `Manage Scrypted's cluster mode. Run storage devices and compute services on separate servers.
[Read Documentation](https://docs.scrypted.app/maintenance/cluster.html).`;
}
}

View File

@@ -108,7 +108,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
(async () => {
await deviceManager.onDeviceDiscovered(
{
name: 'Cluster',
name: 'Cluster Manager',
nativeId: ClusterCoreNativeId,
interfaces: [ScryptedInterface.Settings, ScryptedInterface.Readme, ScryptedInterface.ScryptedSettings],
type: ScryptedDeviceType.Builtin,
@@ -121,7 +121,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Media Core',
nativeId: 'mediacore',
interfaces: [ScryptedInterface.DeviceProvider, ScryptedInterface.BufferConverter, ScryptedInterface.HttpRequestHandler],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -131,7 +131,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Scripts',
nativeId: ScriptCoreNativeId,
interfaces: [ScryptedInterface.ScryptedSystemDevice, ScryptedInterface.ScryptedDeviceCreator, ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -141,7 +141,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Terminal Service',
nativeId: TerminalServiceNativeId,
interfaces: [ScryptedInterface.StreamService, ScryptedInterface.TTY, ScryptedInterface.ClusterForkInterface],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -151,7 +151,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'REPL Service',
nativeId: ReplServiceNativeId,
interfaces: [ScryptedInterface.StreamService],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -161,7 +161,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Console Service',
nativeId: ConsoleServiceNativeId,
interfaces: [ScryptedInterface.StreamService],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -172,7 +172,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Automations',
nativeId: AutomationCoreNativeId,
interfaces: [ScryptedInterface.ScryptedSystemDevice, ScryptedInterface.ScryptedDeviceCreator, ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();
@@ -185,7 +185,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
ScryptedInterface.MixinProvider,
ScryptedInterface.Readme,
],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
});
(async () => {
@@ -206,7 +206,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
name: 'Scrypted Users',
nativeId: UsersNativeId,
interfaces: [ScryptedInterface.ScryptedSystemDevice, ScryptedInterface.ScryptedDeviceCreator, ScryptedInterface.DeviceProvider, ScryptedInterface.DeviceCreator, ScryptedInterface.Readme],
type: ScryptedDeviceType.Builtin,
type: ScryptedDeviceType.Internal,
},
);
})();

View File

@@ -93,7 +93,7 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
let script = new Script(nativeId);
let worker: ForkWorker;
const triggerDeviceDiscover = async (name: string, type: ScryptedDeviceType, interfaces: string[]) => {
const triggerDeviceDiscover = async (name: string, type: ScryptedDeviceType | string, interfaces: string[]) => {
const e = this.scripts.get(nativeId);
if (e?.script == script)
e.script = undefined;

View File

@@ -7,7 +7,7 @@ import { ScriptCoreNativeId } from "./script-core";
const { deviceManager } = sdk;
export class Script extends ScryptedDeviceBase implements Scriptable, Program, ScriptDeviceImpl {
constructor(nativeId: string, public triggerDeviceDiscover?: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
constructor(nativeId: string, public triggerDeviceDiscover?: (name: string, type: ScryptedDeviceType | string, interfaces: string[]) => Promise<string>) {
super(nativeId);
}

View File

@@ -278,6 +278,8 @@ export class TerminalService extends ScryptedDeviceBase implements StreamService
}
catch (e) {
this.console.log(e);
}
finally {
cp?.kill();
}
})();

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/coreml",
"version": "0.1.76",
"version": "0.1.77",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/coreml",
"version": "0.1.76",
"version": "0.1.77",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -48,5 +48,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.76"
"version": "0.1.77"
}

View File

@@ -21,6 +21,7 @@
"@scrypted/sdk": "file:../../sdk",
"@types/debug": "^4.1.5",
"@types/lodash": "^4.14.168",
"@types/node": "^22.13.11",
"@types/url-parse": "^1.4.3"
}
},
@@ -31,51 +32,55 @@
"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"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.53",
"version": "0.5.10",
"dev": true,
"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.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
"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-readme": "bin/scrypted-readme.js",
"scrypted-setup-project": "bin/scrypted-setup-project.js",
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
}
},
"../sdk": {
@@ -182,9 +187,13 @@
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
"node_modules/@types/node": {
"version": "14.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz",
"integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ=="
"version": "22.13.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
"integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/qs": {
"version": "6.9.6",
@@ -712,6 +721,12 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT"
},
"node_modules/url-parse": {
"version": "1.5.9",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.9.tgz",
@@ -778,35 +793,39 @@
"version": "file:../../common",
"requires": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"@types/node": "^16.9.0",
"http-auth-utils": "^3.0.2",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
"@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.18.6",
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"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.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"@types/node": "^22.10.1",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"stringify-object": "^3.3.0",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
}
},
"@types/aws-lambda": {
@@ -877,9 +896,12 @@
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
"@types/node": {
"version": "14.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz",
"integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ=="
"version": "22.13.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
"integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
"requires": {
"undici-types": "~6.20.0"
}
},
"@types/qs": {
"version": "6.9.6",
@@ -1259,6 +1281,11 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
},
"url-parse": {
"version": "1.5.9",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.9.tgz",

View File

@@ -47,6 +47,7 @@
"@scrypted/sdk": "file:../../sdk",
"@types/debug": "^4.1.5",
"@types/lodash": "^4.14.168",
"@types/node": "^22.13.11",
"@types/url-parse": "^1.4.3"
},
"version": "0.0.61"

View File

@@ -7,7 +7,7 @@ const { systemManager } = sdk;
export interface DummyDevice {
interfaces?: string[];
type?: ScryptedDeviceType;
type?: ScryptedDeviceType | string;
}
interface SupportedType {

View File

@@ -1,24 +1,25 @@
{
"name": "@scrypted/hikvision",
"version": "0.0.200",
"name": "@vityevato/hikvision-doorbell",
"version": "1.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/hikvision",
"version": "0.0.200",
"name": "@vityevato/hikvision-doorbell",
"version": "1.0.1",
"license": "Apache",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@scrypted/server": "file:../../server",
"@types/xml2js": "^0.4.11",
"http-auth-client": "^0.4.1",
"ip": "^1.1.8",
"lodash": "^4.17.21",
"sip": "git+https://github.com/kirm/sip.js.git",
"xml2js": "^0.6.0"
},
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@scrypted/server": "file:../../server",
"@types/ip": "^1.1.3",
"@types/node": "^18.15.11"
}
@@ -26,39 +27,43 @@
"../../common": {
"name": "@scrypted/common",
"version": "1.0.1",
"dev": true,
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
"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.5",
"dev": true,
"version": "0.3.118",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -70,67 +75,61 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"@types/node": "^22.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
}
},
"../../server": {
"name": "@scrypted/server",
"version": "0.94.0",
"dev": true,
"version": "0.138.1",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"@scrypted/types": "^0.3.4",
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.5",
"@scrypted/ffmpeg-static": "^6.1.0-build3",
"@scrypted/node-pty": "^1.0.22",
"@scrypted/types": "^0.3.108",
"adm-zip": "^0.5.16",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.5",
"engine.io": "^6.6.2",
"express": "^4.21.1",
"follow-redirects": "^1.15.9",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
"level": "^8.0.1",
"lodash": "^4.17.21",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^10.0.1",
"router": "^1.3.8",
"semver": "^7.5.4",
"sharp": "^0.33.2",
"node-gyp": "^10.2.0",
"py": "npm:@bjia56/portable-python@^0.1.112",
"semver": "^7.6.3",
"sharp": "^0.33.5",
"source-map-support": "^0.5.21",
"tar": "^6.2.0",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"tar": "^7.4.3",
"tslib": "^2.8.1",
"typescript": "^5.5.4",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.16.0"
"ws": "^8.18.0"
},
"bin": {
"scrypted-serve": "bin/scrypted-serve"
},
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.6",
"@types/express": "^4.17.21",
"@types/adm-zip": "^0.5.7",
"@types/cookie-parser": "^1.4.8",
"@types/express": "^5.0.0",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/ip": "^1.1.3",
"@types/lodash": "^4.14.202",
"@types/lodash": "^4.17.13",
"@types/node": "^22.10.1",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.5.6",
"@types/semver": "^7.5.8",
"@types/source-map-support": "^0.5.10",
"@types/tar": "^6.1.10",
"@types/whatwg-mimetype": "^3.0.2",
"@types/ws": "^8.5.10"
},
"optionalDependencies": {
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5"
"@types/ws": "^8.5.13",
"rimraf": "^6.0.1"
}
},
"../sdk": {
@@ -193,6 +192,37 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/sip": {
"version": "0.0.6",
"resolved": "git+ssh://git@github.com/kirm/sip.js.git#fd7e0c2389507b00811feb51bc5c0f6595bac53d",
"dependencies": {
"ws": "^7.4.6"
},
"engines": {
"node": ">=0.2.2"
}
},
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml2js": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
@@ -219,81 +249,83 @@
"version": "file:../../common",
"requires": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"@types/node": "^20.11.0",
"http-auth-utils": "^5.0.1",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
"typescript": "^5.5.3"
}
},
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"@types/node": "^22.10.1",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"stringify-object": "^3.3.0",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
}
},
"@scrypted/server": {
"version": "file:../../server",
"requires": {
"@mapbox/node-pre-gyp": "^1.0.11",
"@scrypted/types": "^0.3.4",
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.6",
"@types/express": "^4.17.21",
"@scrypted/ffmpeg-static": "^6.1.0-build3",
"@scrypted/node-pty": "^1.0.22",
"@scrypted/types": "^0.3.108",
"@types/adm-zip": "^0.5.7",
"@types/cookie-parser": "^1.4.8",
"@types/express": "^5.0.0",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/ip": "^1.1.3",
"@types/lodash": "^4.14.202",
"@types/lodash": "^4.17.13",
"@types/node": "^22.10.1",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.11",
"@types/semver": "^7.5.6",
"@types/semver": "^7.5.8",
"@types/source-map-support": "^0.5.10",
"@types/tar": "^6.1.10",
"@types/whatwg-mimetype": "^3.0.2",
"@types/ws": "^8.5.10",
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.5",
"@types/ws": "^8.5.13",
"adm-zip": "^0.5.16",
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.5",
"engine.io": "^6.6.2",
"express": "^4.21.1",
"follow-redirects": "^1.15.9",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
"level": "^8.0.1",
"lodash": "^4.17.21",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^10.0.1",
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5",
"router": "^1.3.8",
"semver": "^7.5.4",
"sharp": "^0.33.2",
"node-gyp": "^10.2.0",
"py": "npm:@bjia56/portable-python@^0.1.112",
"rimraf": "^6.0.1",
"semver": "^7.6.3",
"sharp": "^0.33.5",
"source-map-support": "^0.5.21",
"tar": "^6.2.0",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"tar": "^7.4.3",
"tslib": "^2.8.1",
"typescript": "^5.5.4",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.16.0"
"ws": "^8.18.0"
}
},
"@types/ip": {
@@ -338,6 +370,19 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"sip": {
"version": "git+ssh://git@github.com/kirm/sip.js.git#fd7e0c2389507b00811feb51bc5c0f6595bac53d",
"from": "sip@git+https://github.com/kirm/sip.js.git",
"requires": {
"ws": "^7.4.6"
}
},
"ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"requires": {}
},
"xml2js": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",

View File

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

View File

@@ -10,7 +10,7 @@
"port": 10081,
"request": "attach",
"skipFiles": [
"**/plugin-remote-worker.*",
"**/plugin-console.*",
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/hikvision",
"version": "0.0.162",
"version": "0.0.165",
"description": "Hikvision Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -0,0 +1,57 @@
import { OnOff, Readme, ScryptedDeviceBase } from "@scrypted/sdk";
import type { HikvisionCamera } from "./main";
export class HikvisionAlarmSwitch extends ScryptedDeviceBase implements OnOff, Readme {
on: boolean = false;
constructor(public camera: HikvisionCamera, nativeId: string) {
super(nativeId);
this.on = false;
}
async turnOn() {
this.on = true;
await this.setAlarm(true);
}
async turnOff() {
this.on = false;
await this.setAlarm(false);
}
private async setAlarm(state: boolean): Promise<void> {
const api = this.camera.getClient();
await api.setAlarm(state);
}
async getReadmeMarkdown(): Promise<string> {
return `
## **Alarm Switch**
This switch triggers the camera's alarm input.
### **Enabling Alarm Linkages**
To link the alarm to the camera's equipped features like strobe light, or audio alarm:
1. Log in to the cameras web interface.
2. Go to *Configuration > Event > Event and Detection (or Basic Event)*.
3. Select Alarm Input.
4. Edit under Operation(pencil icon) (or Linkage Method).
4. Set Linkage Actions
- Audible Warning (siren)
- Alarm (strobe light)
When the alarm is switched on, the linkages will activate.
### **Strobe Light and Audio Alarm Settings**
To configure the strobe light and audio alarm:
1. Log in to the cameras web interface.
2. Navigate to *Configuration > Event > Alarm Setting (or Basic Event)*.
3. **For Strobe Light**:
- Select 'Flashing Alarm Light Output'.
**For Audio Alarm**:
- Select 'Audible Alarm Output'.
`;
}
}

View File

@@ -399,3 +399,140 @@ export interface AudioCompressionType {
export interface GeneratedType35 {
opt: string
}
export interface PtzCapabilitiesRoot {
PTZChanelCap: PTZChanelCap;
}
export interface PTZChanelCap {
AbsolutePanTiltPositionSpace: AbsolutePanTiltPositionSpaceClass;
AbsoluteZoomPositionSpace: AbsoluteZoomPositionSpaceClass;
ContinuousPanTiltSpace: AbsolutePanTiltPositionSpaceClass;
ContinuousZoomSpace: AbsoluteZoomPositionSpaceClass;
maxPresetNum: string;
maxPatrolNum: string;
maxPatternNum: string;
maxLimitesNum: string;
maxTimeTaskNum: string;
controlProtocol: ControlProtocol;
controlAddress: string;
PTZRs485Para: PTZRs485Para;
PresetNameCap: PresetNameCap;
wiperStatusSupport: string;
isSupportPosition3D: string;
manualControlSpeed: ManualControlSpeed;
isSpportPtzlimiteds: string;
oneKeyParkAction: string;
oneKeyMenu: string;
ParkAction: ParkAction;
TimeTaskList: TimeTaskList;
TrackInitPosition: TrackInitPosition;
LockPT: string;
LFPositionCap: LFPositionCap;
isSupportManualWiper: string;
_xmlns: string;
_version: string;
}
export interface AbsolutePanTiltPositionSpaceClass {
XRange: Range;
YRange: Range;
}
export interface Range {
Min: string;
Max: string;
}
export interface AbsoluteZoomPositionSpaceClass {
ZRange: Range;
}
export interface LFPositionCap {
elevation: AbsoluteZoom;
azimuth: AbsoluteZoom;
absoluteZoom: AbsoluteZoom;
}
export interface AbsoluteZoom {
_min: string;
_max: string;
__text: string;
}
export interface PTZRs485Para {
baudRate: ControlProtocol;
dataBits: ControlProtocol;
parityType: ControlProtocol;
stopBits: ControlProtocol;
flowCtrl: ControlProtocol;
_xmlns: string;
_version: string;
}
export interface ControlProtocol {
_opt: string;
__text: string;
}
export interface ParkAction {
enabled: ControlProtocol;
Parktime: AbsoluteZoom;
Action: Action;
_xmlns: string;
_version: string;
}
export interface Action {
ActionType: ControlProtocol;
ActionNum: AbsoluteZoom;
}
export interface PresetNameCap {
presetNameSupport: string;
maxPresetNameLen: MaxPresetNameLen;
specialNo: ManualControlSpeed;
_xmlns: string;
_version: string;
}
export interface MaxPresetNameLen {
_max: string;
}
export interface ManualControlSpeed {
_opt: string;
}
export interface TimeTaskList {
enabled: ControlProtocol;
TimeTaskBlock: TimeTaskBlock[];
_xmlns: string;
_version: string;
}
export interface TimeTaskBlock {
dayOfWeek: AbsoluteZoom;
TimeTaskRange: TimeTaskRange[];
_xmlns: string;
_version: string;
}
export interface TimeTaskRange {
TaskID: AbsoluteZoom;
Task: Task;
}
export interface Task {
TaskType: ControlProtocol;
presetTaskNum: AbsoluteZoom;
}
export interface TrackInitPosition {
slaveCameraID: SlaveCameraID;
}
export interface SlaveCameraID {
_min: string;
_max: string;
}

View File

@@ -1,7 +1,10 @@
import { HttpFetchOptions } from '@scrypted/common/src/http-auth-fetch';
import { MediaStreamConfiguration, MediaStreamOptions } from '@scrypted/sdk';
import { MediaStreamConfiguration, MediaStreamOptions, PanTiltZoomCommand } from '@scrypted/sdk';
import { Readable } from 'stream';
import { Destroyable } from '../../rtsp/src/rtsp';
import { PtzPresetsRoot, TextOverlayRoot, VideoOverlayRoot } from './hikvision-overlay';
import { SupplementLightRoot } from './hikvision-xml-types';
import { PtzCapabilitiesRoot } from './hikvision-api-capabilities';
export interface HikvisionCameraStreamSetup {
videoCodecType: string;
@@ -16,10 +19,33 @@ export interface HikvisionAPI {
checkTwoWayAudio(): Promise<boolean>;
checkDeviceModel(): Promise<string>;
checkIsOldModel(): Promise<boolean>;
checkStreamSetup(channel: string, isOld: boolean): Promise<HikvisionCameraStreamSetup>;
jpegSnapshot(channel: string, timeout: number): Promise<Buffer>;
listenEvents(): Promise<Destroyable>;
putVcaResource(channel: string, resource: 'smart' | 'facesnap' | 'close'): Promise<boolean>;
getCodecs(camNumber: string): Promise<MediaStreamOptions[]>;
configureCodecs(camNumber: string, channelNumber: string, options: MediaStreamOptions): Promise<MediaStreamConfiguration>;
getOverlay(): Promise<{
json: VideoOverlayRoot;
xml: any;
}>;
getOverlayText(overlayId: string): Promise<{
json: TextOverlayRoot;
xml: any;
}>;
updateOverlayText(overlayId: string, entry: TextOverlayRoot): Promise<void>;
getSupplementLight(): Promise<{json: SupplementLightRoot; xml: any }>;
setSupplementLight(params: { on?: boolean, brightness?: number, mode?: 'auto' | 'manual' }): Promise<void>;
getAlarmCapabilities(): Promise<{ json: any; xml: string }>;
getAlarm(port: string): Promise<{ json: any; xml: string }>;
setAlarm(isOn: boolean): Promise<{ json: any; xml: string }>;
getPtzCapabilities(): Promise<{ json: PtzCapabilitiesRoot; xml: string }>;
ptzCommand(command: PanTiltZoomCommand): Promise<any>;
getPresets(): Promise<{
json: PtzPresetsRoot;
xml: any;
}>;
}

View File

@@ -1,16 +1,18 @@
import { AuthFetchCredentialState, HttpFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
import { readLine } from '@scrypted/common/src/read-stream';
import { parseHeaders, readBody, readMessage } from '@scrypted/common/src/rtsp-server';
import { MediaStreamConfiguration, MediaStreamOptions } from "@scrypted/sdk";
import { MediaStreamConfiguration, MediaStreamOptions, PanTiltZoomCommand } from "@scrypted/sdk";
import contentType from 'content-type';
import { IncomingMessage } from 'http';
import { EventEmitter, Readable } from 'stream';
import xml2js from 'xml2js';
import { Destroyable } from '../../rtsp/src/rtsp';
import { CapabiltiesResponse } from './hikvision-api-capabilities';
import { CapabiltiesResponse, PtzCapabilitiesRoot } from './hikvision-api-capabilities';
import { HikvisionAPI, HikvisionCameraStreamSetup } from "./hikvision-api-channels";
import { ChannelResponse, ChannelsResponse } from './hikvision-xml-types';
import { ChannelResponse, ChannelsResponse, SupplementLightRoot } from './hikvision-xml-types';
import { getDeviceInfo } from './probe';
import { PtzPresetsRoot, TextOverlayRoot, VideoOverlayRoot } from './hikvision-overlay';
import { sleep } from '@scrypted/common/src/sleep';
export const detectionMap = {
human: 'person',
@@ -127,31 +129,6 @@ export class HikvisionCameraAPI implements HikvisionAPI {
return !!oldModels.find(oldModel => model?.match(oldModel));
}
async checkStreamSetup(channel: string, isOld: boolean): Promise<HikvisionCameraStreamSetup> {
if (isOld) {
this.console.error('NVR is old version. Defaulting camera capabilities to H.264/AAC');
return {
videoCodecType: "H.264",
audioCodecType: "AAC",
}
}
const response = await this.request({
url: `http://${this.ip}/ISAPI/Streaming/channels/${getChannel(channel)}/capabilities`,
responseType: 'text',
});
// this is bad:
// <videoCodecType opt="H.264,H.265">H.265</videoCodecType>
const vcodec = response.body.match(/>(.*?)<\/videoCodecType>/);
const acodec = response.body.match(/>(.*?)<\/audioCompressionType>/);
return {
videoCodecType: vcodec?.[1],
audioCodecType: acodec?.[1],
}
}
async jpegSnapshot(channel: string, timeout = 10000): Promise<Buffer> {
const url = `http://${this.ip}/ISAPI/Streaming/channels/${getChannel(channel)}/picture?snapShotImageType=JPEG`
@@ -482,4 +459,257 @@ export class HikvisionCameraAPI implements HikvisionAPI {
return [...defaultMap.values()];
}
}
}
async getOverlay() {
const response = await this.request({
method: 'GET',
url: `http://${this.ip}/ISAPI/System/Video/inputs/channels/1/overlays`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
});
const json = await xml2js.parseStringPromise(response.body) as VideoOverlayRoot;
return { json, xml: response.body };
}
async getOverlayText(overlayId: string) {
const response = await this.request({
method: 'GET',
url: `http://${this.ip}//ISAPI/System/Video/inputs/channels/1/overlays/text/${overlayId}`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
});
const json = await xml2js.parseStringPromise(response.body) as TextOverlayRoot;
return { json, xml: response.body };
}
async updateOverlayText(overlayId: string, entry: TextOverlayRoot) {
const builder = new xml2js.Builder();
const xml = builder.buildObject(entry);
await this.request({
method: 'PUT',
url: `http://${this.ip}//ISAPI/System/Video/inputs/channels/1/overlays/text/${overlayId}`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
body: xml
});
}
async getSupplementLight(): Promise<{ json: SupplementLightRoot | any; xml: string }> {
const response = await this.request({
method: 'GET',
url: `http://${this.ip}/ISAPI/Image/channels/1/supplementLight/capabilities`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
});
const xml = response.body;
const json = await xml2js.parseStringPromise(xml, {
explicitArray: false,
mergeAttrs: true,
});
return { json, xml };
}
async setSupplementLight(params: { on?: boolean, brightness?: number, mode?: 'auto' | 'manual' }): Promise<void> {
const { json } = await this.getSupplementLight();
if (json.ResponseStatus) {
throw new Error("Supplemental light is not supported on this device.");
}
const supp: any = json.SupplementLight;
if (!supp) {
throw new Error("Supplemental light configuration not available.");
}
if (supp.supplementLightMode && supp.supplementLightMode.opt) {
const availableModes = supp.supplementLightMode.opt.split(',').map(s => s.trim());
const selectedMode = params.on
? (availableModes.find(mode => mode.toLowerCase() !== 'close') || 'close')
: 'close';
supp.supplementLightMode = [selectedMode];
}
if (params.mode) {
supp.mixedLightBrightnessRegulatMode = [params.mode];
} else if (params.on !== undefined) {
supp.mixedLightBrightnessRegulatMode = [params.on ? "manual" : "auto"];
}
if (params.brightness !== undefined) {
let brightness = Math.max(0, Math.min(100, params.brightness));
supp.whiteLightBrightness = [brightness.toString()];
}
const builder = new xml2js.Builder({
headless: true,
renderOpts: { pretty: false },
});
const newXml = builder.buildObject({ SupplementLight: supp });
await this.request({
method: 'PUT',
url: `http://${this.ip}/ISAPI/Image/channels/1/supplementLight`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
body: newXml,
});
}
async getAlarmCapabilities(): Promise<{ json: any; xml: string }> {
const response = await this.request({
method: 'GET',
url: `http://${this.ip}/ISAPI/System/IO/inputs`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
});
const xml = response.body;
const json = await xml2js.parseStringPromise(xml, {
explicitArray: false,
mergeAttrs: true,
});
return { json, xml };
}
async getAlarm(port: string): Promise<{ json: any; xml: string }> {
const response = await this.request({
method: 'GET',
url: `http://${this.ip}/ISAPI/Event/triggers/IO-${port}`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
});
const xml = response.body;
const parsed = await xml2js.parseStringPromise(xml, { explicitArray: true });
return { json: parsed.EventTrigger, xml };
}
async setAlarm(isOn: boolean): Promise<{ json: any; xml: string }> {
const data = `<IOPortData>
<enabled>${isOn ? 'true' : 'false'}</enabled>
<triggering>${isOn ? 'low' : 'high'}</triggering>
</IOPortData>`;
const response = await this.request({
method: 'PUT',
url: `http://${this.ip}/ISAPI/System/IO/inputs/1`,
responseType: 'text',
headers: { 'Content-Type': 'application/xml' },
body: data
});
const xml = response.body;
let json = {};
try {
json = await xml2js.parseStringPromise(xml);
} catch (error) {
console.error("Failed to parse XML response for setAlarmInput:", error);
}
return { json, xml };
}
async getPtzCapabilities(): Promise<{ json: PtzCapabilitiesRoot; xml: string }> {
const response = await this.request({
method: 'GET',
url: `http://${this.ip}/ISAPI/PTZCtrl/channels/1/capabilities`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
});
const xml = response.body;
const json = await xml2js.parseStringPromise(xml, {
explicitArray: false,
mergeAttrs: true,
});
return { json, xml };
}
async setPtzPreset(presetId: string) {
try {
await this.request({
method: 'PUT',
url: `http://${this.ip}/ISAPI/PTZCtrl/channels/1/presets/${presetId}/goto`,
responseType: 'text',
headers: { 'Content-Type': 'application/xml' },
});
} catch (e) {
this.console.error('Error during setPtzPreset', e);
}
}
async ptzCommand(command: PanTiltZoomCommand) {
let startCommandData: string;
let endCommandData: string;
const movement = 40;
if (command.preset) {
await this.setPtzPreset(command.preset);
} else if (command.pan < 0 || command.pan > 0) {
startCommandData = `<?xml version: "1.0" encoding="UTF-8"?><PTZData><pan>${command.pan > 0 ? movement : -movement}</pan><tilt>0</tilt></PTZData>`;
endCommandData = `<?xml version: "1.0" encoding="UTF-8"?><PTZData><pan>0</pan><tilt>0</tilt></PTZData>`;
} else if (command.tilt < 0 || command.tilt > 0) {
startCommandData = `<?xml version: "1.0" encoding="UTF-8"?><PTZData><pan>0</pan><tilt>${command.tilt > 0 ? movement : -movement}</tilt></PTZData>`;
endCommandData = `<?xml version: "1.0" encoding="UTF-8"?><PTZData><pan>0</pan><tilt>0</tilt></PTZData>`;
} else if (command.zoom < 0 || command.zoom > 0) {
startCommandData = `<?xml version: "1.0" encoding="UTF-8"?><PTZData><zoom>${command.zoom > 0 ? movement : -movement}</zoom></PTZData>`;
endCommandData = `<?xml version: "1.0" encoding="UTF-8"?><PTZData><zoom>0</zoom></PTZData>`;
}
if (!startCommandData || !endCommandData) {
return;
}
try {
await this.request({
method: 'PUT',
url: `http://${this.ip}/ISAPI/PTZCtrl/channels/1/continuous`,
responseType: 'text',
headers: { 'Content-Type': 'application/xml' },
body: startCommandData
});
await sleep(500);
await this.request({
method: 'PUT',
url: `http://${this.ip}/ISAPI/PTZCtrl/channels/1/continuous`,
responseType: 'text',
headers: { 'Content-Type': 'application/xml' },
body: endCommandData
});
} catch (e) {
this.console.error('Error during PTZ command', e);
}
}
async getPresets() {
const response = await this.request({
method: 'GET',
url: `http://${this.ip}/ISAPI/PTZCtrl/channels/1/presets`,
responseType: 'text',
headers: {
'Content-Type': 'application/xml',
},
});
const json = await xml2js.parseStringPromise(response.body) as PtzPresetsRoot;
return { json, xml: response.body };
}
}

View File

@@ -0,0 +1,112 @@
export interface VideoOverlayRoot {
VideoOverlay: VideoOverlay;
}
export interface VideoOverlay {
$: VideoOverlayClass;
normalizedScreenSize: NormalizedScreenSize[];
attribute: Attribute[];
fontSize: string[];
TextOverlayList: TextOverlayListElement[];
DateTimeOverlay: DateTimeOverlay[];
channelNameOverlay: ChannelNameOverlay[];
frontColorMode: string[];
frontColor: string[];
alignment: string[];
boundary: string[];
upDownboundary: string[];
leftRightboundary: string[];
}
export interface VideoOverlayClass {
version: string;
xmlns: string;
}
export interface DateTimeOverlay {
enabled: string[];
positionX: string[];
positionY: string[];
dateStyle: string[];
timeStyle: string[];
displayWeek: string[];
}
export interface TextOverlayListElement {
$: TextOverlayList;
TextOverlay: TextOverlay[];
}
export interface TextOverlayList {
size: string;
}
export interface TextOverlay {
id: string[];
enabled: string[];
positionX: string[];
positionY: string[];
displayText: string[];
isPersistentText: string[];
}
export interface Attribute {
transparent: string[];
flashing: string[];
}
export interface ChannelNameOverlay {
$: VideoOverlayClass;
enabled: string[];
positionX: string[];
positionY: string[];
}
export interface NormalizedScreenSize {
normalizedScreenWidth: string[];
normalizedScreenHeight: string[];
}
export interface TextOverlayRoot {
TextOverlay: TextOverlay;
}
export interface TextOverlay {
$: Empty;
id: string[];
enabled: string[];
positionX: string[];
positionY: string[];
displayText: string[];
directAngle: string[];
}
export interface Empty {
version: string;
xmlns: string;
}
export interface PtzPresetsRoot {
PTZPresetList: PTZPresetList;
}
export interface PTZPresetList {
PTZPreset: PTZPreset[];
_xmlns: string;
_version: string;
}
export interface PTZPreset {
enabled: string;
id: string;
presetName: string;
AbsoluteHigh: AbsoluteHigh;
}
export interface AbsoluteHigh {
elevation: string;
azimuth: string;
absoluteZoom: string;
}

View File

@@ -172,4 +172,49 @@ export interface ChannelResponse {
// ],
// },
// ],
// }
// }
export interface ValueWithRange {
_: string;
$: {
min: string;
max: string;
};
}
export interface SupplementLightRoot {
SupplementLight: SupplementLight;
}
export interface SupplementLight {
mode: string[];
Schedule?: Schedule[];
brightnessLimit: ValueWithRange[];
supplementLightMode: string[];
whiteLightBrightness?: ValueWithRange[];
}
export interface Schedule {
TimeRange: TimeRange[];
}
export interface TimeRange {
beginTime: string[];
endTime: string[];
}
export interface BrightnessLimit {
_: string;
$: {
min: string;
max: string;
};
}
export interface LightBrightness {
_: string;
$: {
min: string;
max: string;
}
}

View File

@@ -1,5 +1,5 @@
import { automaticallyConfigureSettings, checkPluginNeedsAutoConfigure } from "@scrypted/common/src/autoconfigure-codecs";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, ObjectDetectionResult, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, Reboot, RequestPictureOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, VideoCameraConfiguration } from "@scrypted/sdk";
import sdk, { Camera, Device, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, ObjectDetectionResult, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, PanTiltZoom, PanTiltZoomCommand, Reboot, RequestPictureOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, VideoCameraConfiguration, VideoTextOverlay, VideoTextOverlays } from "@scrypted/sdk";
import crypto from 'crypto';
import { PassThrough } from "stream";
import xml2js from 'xml2js';
@@ -10,6 +10,8 @@ import { startRtpForwarderProcess } from '../../webrtc/src/rtp-forwarders';
import { HikvisionAPI } from "./hikvision-api-channels";
import { autoconfigureSettings, hikvisionAutoConfigureSettings } from "./hikvision-autoconfigure";
import { detectionMap, HikvisionCameraAPI, HikvisionCameraEvent } from "./hikvision-camera-api";
import { HikvisionSupplementalLight } from "./supplemental-light";
import { HikvisionAlarmSwitch } from "./alarm-switch";
const rtspChannelSetting: Setting = {
subgroup: 'Advanced',
@@ -27,11 +29,14 @@ function channelToCameraNumber(channel: string) {
return channel.substring(0, channel.length - 2);
}
export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboot, ObjectDetector, VideoCameraConfiguration {
export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom, Reboot, ObjectDetector, VideoCameraConfiguration, VideoTextOverlays, PanTiltZoom {
detectedChannels: Promise<Map<string, MediaStreamOptions>>;
onvifIntercom = new OnvifIntercom(this);
activeIntercom: Awaited<ReturnType<typeof startRtpForwarderProcess>>;
hasSmartDetection: boolean;
supplementLight: HikvisionSupplementalLight;
alarm: HikvisionAlarmSwitch;
ptzPresets: string[];
client: HikvisionAPI;
@@ -39,8 +44,43 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
super(nativeId, provider);
this.hasSmartDetection = this.storage.getItem('hasSmartDetection') === 'true';
this.ptzPresets = JSON.parse(this.storage.getItem('storedPtzPresets') ?? '[]');
this.updateDevice();
this.updateDeviceInfo();
this.reportDevices();
if (this.interfaces.includes(ScryptedInterface.PanTiltZoom)) {
const ptzCapabilities = JSON.parse(this.storage.getItem('ptzCapabilities') ?? '[]');
this.fetchPtzPresets().catch(this.console.error);
this.updatePtzCaps(ptzCapabilities);
}
}
async getVideoTextOverlays(): Promise<Record<string, VideoTextOverlay>> {
const client = this.getClient();
const overlays = await client.getOverlay();
const ret: Record<string, VideoTextOverlay> = {};
for (const to of overlays.json.VideoOverlay.TextOverlayList?.[0]?.TextOverlay) {
ret[to.id[0]] = {
text: to.displayText[0],
}
}
return ret;
}
async setVideoTextOverlay(id: string, value: VideoTextOverlay): Promise<void> {
const client = this.getClient();
const overlays = await client.getOverlay();
// find the overlay by id
const overlay = overlays.json.VideoOverlay.TextOverlayList?.[0]?.TextOverlay.find(o => o.id[0] === id);
overlay.enabled[0] = value.text ? 'true' : 'false';
if (typeof value.text === 'string')
overlay.displayText = [value.text];
client.updateOverlayText(id, {
TextOverlay: overlay,
});
}
async reboot() {
@@ -77,6 +117,11 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
this.info = info;
}
async ptzCommand(command: PanTiltZoomCommand): Promise<void> {
const client = this.getClient();
await client.ptzCommand(command);
}
async listenEvents() {
let motionTimeout: NodeJS.Timeout;
const api = (this.provider as HikvisionProvider).createSharedClient(this.getHttpAddress(), this.getUsername(), this.getPassword());
@@ -380,6 +425,9 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
|| this.storage.getItem('twoWayAudio') === 'ONVIF'
|| this.storage.getItem('twoWayAudio') === 'Hikvision';
const providedDevices = JSON.parse(this.storage.getItem('providedDevices') || '[]') as string[];
const ptzCapabilities = JSON.parse(this.storage.getItem('ptzCapabilities') || '[]') as string[];
const interfaces = this.provider.getInterfaces();
let type: ScryptedDeviceType = undefined;
if (isDoorbell) {
@@ -393,10 +441,64 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
if (this.hasSmartDetection)
interfaces.push(ScryptedInterface.ObjectDetector);
if (!!providedDevices?.length) {
interfaces.push(ScryptedInterface.DeviceProvider);
}
if (!!ptzCapabilities?.length) {
interfaces.push(ScryptedInterface.PanTiltZoom);
}
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
}
async putSetting(key: string, value: string) {
async fetchPtzPresets() {
try {
const client = this.getClient();
const cameraPresets: string[] = [];
const presets = await client.getPresets();
const allPresets = presets.json?.PTZPresetList?.PTZPreset ?? [];
for (const to of allPresets) {
if (to.enabled?.[0] === 'true') {
cameraPresets.push(`${to.id?.[0]}=${to.presetName?.[0]}`);
}
}
this.storage.setItem('storedPtzPresets', JSON.stringify(cameraPresets));
this.ptzPresets = cameraPresets;
} catch (e) {
this.console.error('Error in fetchPtzPresets', e);
this.ptzPresets = [];
}
}
async updatePtzCaps(cameraPtzCapabilities: string[]) {
this.ptzCapabilities = {
...this.ptzCapabilities,
pan: cameraPtzCapabilities?.includes('Pan'),
tilt: cameraPtzCapabilities?.includes('Tilt'),
zoom: cameraPtzCapabilities?.includes('Zoom'),
}
}
async updatePtzPresets(ptzPresets: string[]) {
const presets: Record<string, string> = {};
ptzPresets.forEach(preset => {
const parts = preset.split('=');
if (parts.length === 2) {
presets[parts[0]] = parts[1];
}
});
this.ptzCapabilities = {
...this.ptzCapabilities,
presets
};
}
async putSetting(key: string, value: string | string[]) {
if (key === automaticallyConfigureSettings.key) {
const client = this.getClient();
autoconfigureSettings(client, this.getCameraNumber() || '1')
@@ -408,14 +510,20 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
this.console.error('error autoconfiguring', e);
});
return;
} else if (key === 'ptzPresets') {
await this.updatePtzPresets(value as string[]);
} else if (key === 'ptzCapabilities' && !!value?.length) {
await this.fetchPtzPresets();
this.updatePtzCaps(value as string[]);
}
this.client = undefined;
this.detectedChannels = undefined;
super.putSetting(key, value);
super.putSetting(key, typeof value === 'string' ? value : JSON.stringify(value));
this.updateDevice();
this.updateDeviceInfo();
this.reportDevices();
}
async getOtherSettings(): Promise<Setting[]> {
@@ -439,6 +547,9 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
if (!twoWayAudio)
twoWayAudio = isDoorbell ? 'Hikvision' : 'None';
const providedDevices = JSON.parse(this.storage.getItem('providedDevices') || '[]') as string[];
const ptzCapabilities = JSON.parse(this.storage.getItem('ptzCapabilities') || '[]') as string[];
ret.push(
{
subgroup: 'Advanced',
@@ -456,8 +567,47 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
description: 'Hikvision cameras may support both Hikvision and ONVIF two way audio protocols. ONVIF generally performs better when supported.',
choices,
},
{
key: 'providedDevices',
subgroup: 'Advanced',
title: 'Provided devices',
value: providedDevices,
choices: [
'Alarm',
'SupplementLight',
],
multiple: true,
},
{
key: 'ptzCapabilities',
subgroup: 'Advanced',
value: ptzCapabilities,
title: 'PTZ Capabilities',
choices: [
'Pan',
'Tilt',
'Zoom',
],
multiple: true,
},
);
if (this.interfaces.includes(ScryptedInterface.PanTiltZoom)) {
const ptzPresets = JSON.parse(this.storage.getItem('ptzPresets') || '[]');
ret.push(
{
key: 'ptzPresets',
subgroup: 'Advanced',
value: ptzPresets,
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,
combobox: true,
choices: this.ptzPresets,
}
);
}
const ac = {
...automaticallyConfigureSettings,
subgroup: 'Advanced',
@@ -472,6 +622,70 @@ export class HikvisionCamera extends RtspSmartCamera implements Camera, Intercom
return ret;
}
reportDevices() {
const providedDevices = JSON.parse(this.storage.getItem('providedDevices') || '[]') as string[];
const devices: Device[] = [];
if (providedDevices?.includes('Alarm')) {
const alarmNativeId = `${this.nativeId}-alarm`;
const alarmDevice: Device = {
providerNativeId: this.nativeId,
name: `${this.name} Alarm`,
nativeId: alarmNativeId,
info: {
...this.info,
},
interfaces: [
ScryptedInterface.OnOff,
ScryptedInterface.Readme,
],
type: ScryptedDeviceType.Switch,
};
devices.push(alarmDevice);
}
if (providedDevices?.includes('SupplementLight')) {
const supplementLightNativeId = `${this.nativeId}-supplementlight`;
const supplementLightDevice: Device = {
providerNativeId: this.nativeId,
name: `${this.name} Supplemental Light`,
nativeId: supplementLightNativeId,
info: {
...this.info,
},
interfaces: [
ScryptedInterface.OnOff,
ScryptedInterface.Brightness,
ScryptedInterface.Settings,
],
type: ScryptedDeviceType.Light,
};
devices.push(supplementLightDevice);
}
sdk.deviceManager.onDevicesChanged({
providerNativeId: this.nativeId,
devices,
});
}
async getDevice(nativeId: string): Promise<any> {
if (nativeId.endsWith('-supplementlight')) {
this.supplementLight ||= new HikvisionSupplementalLight(this, nativeId);
return this.supplementLight;
}
if (nativeId.endsWith('-alarm')) {
this.alarm ||= new HikvisionAlarmSwitch(this, nativeId);
return this.alarm;
}
}
async releaseDevice(id: string, nativeId: string) {
if (nativeId.endsWith('-supplementlight'))
delete this.supplementLight;
else if (nativeId.endsWith('-alarm'))
delete this.alarm;
}
async startIntercom(media: MediaObject): Promise<void> {
if (this.storage.getItem('twoWayAudio') === 'ONVIF') {
@@ -626,6 +840,7 @@ class HikvisionProvider extends RtspProvider {
ScryptedInterface.Reboot,
ScryptedInterface.Camera,
ScryptedInterface.MotionSensor,
ScryptedInterface.VideoTextOverlays,
];
}

View File

@@ -0,0 +1,66 @@
import { Brightness, OnOff, ScryptedDeviceBase, Setting, Settings, SettingValue } from "@scrypted/sdk";
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import type { HikvisionCamera } from "./main";
export class HikvisionSupplementalLight extends ScryptedDeviceBase implements OnOff, Brightness, Settings {
storageSettings = new StorageSettings(this, {
mode: {
title: 'Mode',
description: 'Choose "auto" for automatic brightness control or "manual" for custom brightness.',
defaultValue: 'auto',
type: 'string',
choices: ['auto', 'manual'],
onPut: async () => {
await this.updateSupplementalLight();
},
},
brightness: {
title: 'Manual Brightness',
description: 'Set brightness (0100) when in manual mode.',
defaultValue: 100,
type: 'number',
placeholder: '0-100',
immediate: true,
onPut: async () => {
await this.updateSupplementalLight();
},
},
});
on: boolean = false;
brightness: number = 100;
constructor(public camera: HikvisionCamera, nativeId: string) {
super(nativeId);
}
async turnOn(): Promise<void> {
this.on = true;
await this.updateSupplementalLight();
}
async turnOff(): Promise<void> {
this.on = false;
await this.updateSupplementalLight();
}
async setBrightness(brightness: number): Promise<void> {
this.brightness = brightness;
await this.updateSupplementalLight();
}
private async updateSupplementalLight(): Promise<void> {
const api = this.camera.getClient();
const mode = this.storageSettings.values.mode;
const brightness = this.storageSettings.values.brightness;
await api.setSupplementLight({ on: this.on, brightness: brightness, mode });
}
async getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
}
async putSetting(key: string, value: SettingValue): Promise<void> {
await this.storageSettings.putSetting(key, value);
}
}

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/homekit",
"version": "1.2.63",
"version": "1.2.65",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/homekit",
"version": "1.2.63",
"version": "1.2.65",
"dependencies": {
"@koush/werift-src": "file:../../external/werift",
"check-disk-space": "^3.4.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/homekit",
"version": "1.2.63",
"version": "1.2.65",
"description": "HomeKit Plugin for Scrypted",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",

View File

@@ -8,7 +8,7 @@ const { systemManager, deviceManager, log } = sdk;
export const defaultObjectDetectionContactSensorTimeout = 60;
export function canCameraMixin(type: ScryptedDeviceType, interfaces: string[]) {
export function canCameraMixin(type: ScryptedDeviceType | string, interfaces: string[]) {
return (type === ScryptedDeviceType.Camera || type === ScryptedDeviceType.Doorbell)
&& interfaces.includes(ScryptedInterface.VideoCamera);
}

View File

@@ -5,7 +5,7 @@ import type { HomeKitPlugin } from "./main";
export interface DummyDevice {
interfaces?: string[];
type?: ScryptedDeviceType;
type?: ScryptedDeviceType | string;
}
export interface SnapshotThrottle {

View File

@@ -24,7 +24,7 @@ export function getHAPUUID(storage: Storage) {
return uuid;
}
export function typeToCategory(type: ScryptedDeviceType): Categories {
export function typeToCategory(type: ScryptedDeviceType | string): Categories {
switch (type) {
case ScryptedDeviceType.Camera:
return Categories.CAMERA;

View File

@@ -407,7 +407,7 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
});
}
async canMixin(type: ScryptedDeviceType, interfaces: string[]) {
async canMixin(type: ScryptedDeviceType | string, interfaces: string[]) {
const supportedType = supportedTypes[type];
if (!supportedType?.probe({
interfaces,

View File

@@ -66,6 +66,9 @@ addSupportedType({
profiles: [H264Profile.MAIN],
},
// HomeKit will request a resolution from this list, but it seems to do it
// stupidly. For example, local network macOS on a 6k monitor requests the 480p stream?
// so using the resolution for device fingerprinting can't be trusted.
resolutions: [
// 3840x2160@30 (4k).
[3840, 2160, 30],

View File

@@ -141,18 +141,12 @@ export async function* handleFragmentsRequests(streamId: number, device: Scrypte
container: 'mp4',
});
const ffmpegInput = JSON.parse((await mediaManager.convertMediaObjectToBuffer(media, ScryptedMimeTypes.FFmpegInput)).toString()) as FFmpegInput;
if (!ffmpegInput.mediaStreamOptions?.prebuffer) {
log.a(`${device.name} is not prebuffered. Please install and enable the Rebroadcast plugin.`);
}
const noAudio = ffmpegInput.mediaStreamOptions && ffmpegInput.mediaStreamOptions.audio === null;
const audioCodec = ffmpegInput.mediaStreamOptions?.audio?.codec;
const videoCodec = ffmpegInput.mediaStreamOptions?.video?.codec;
const isDefinitelyNotAAC = !audioCodec || audioCodec.toLowerCase().indexOf('aac') === -1;
const transcodeRecording = !!ffmpegInput.h264EncoderArguments?.length || !!ffmpegInput.h264FilterArguments?.length;
const needsFFmpeg = debugMode.video || debugMode.video
|| !ffmpegInput.url.startsWith('tcp://')
|| transcodeRecording
|| ffmpegInput.container !== 'mp4'
|| noAudio;
@@ -182,9 +176,6 @@ export async function* handleFragmentsRequests(streamId: number, device: Scrypte
}
}
if (ffmpegInput.videoDecoderArguments?.length)
inputArguments.push(...ffmpegInput.videoDecoderArguments);
inputArguments.push(...ffmpegInput.inputArguments);
if (noAudio) {
@@ -194,8 +185,8 @@ export async function* handleFragmentsRequests(streamId: number, device: Scrypte
}
let audioArgs: string[];
if (!noAudio && (transcodeRecording || isDefinitelyNotAAC || debugMode.audio)) {
if (!(transcodeRecording || debugMode.audio))
if (!noAudio && (isDefinitelyNotAAC || debugMode.audio)) {
if (!debugMode.audio)
console.warn('Recording audio is not explicitly AAC, forcing transcoding. Setting audio output to AAC is recommended.', audioCodec);
let aacLowEncoder = 'aac';
@@ -224,14 +215,11 @@ export async function* handleFragmentsRequests(streamId: number, device: Scrypte
];
}
const videoArgs = ffmpegInput.h264FilterArguments?.slice() || [];
if (debugMode.video || transcodeRecording) {
if (debugMode.video || !ffmpegInput.h264EncoderArguments) {
const videoArgs: string[] = [];
if (debugMode.video) {
if (debugMode.video) {
videoArgs.push(...getDebugModeH264EncoderArgs());
}
else {
videoArgs.push(...ffmpegInput.h264EncoderArguments);
}
const videoRecordingFilter = `scale=w='min(${configuration.videoCodec.resolution[0]},iw)':h=-2`;
addVideoFilterArguments(videoArgs, videoRecordingFilter);
videoArgs.push(

View File

@@ -29,7 +29,7 @@ export async function startCameraStreamFfmpeg(device: ScryptedDevice & VideoCame
const audioKey = Buffer.concat([session.prepareRequest.audio.srtp_key, session.prepareRequest.audio.srtp_salt]);
const mso = ffmpegInput.mediaStreamOptions;
const videoArgs = ffmpegInput.h264FilterArguments?.slice() || [];
const videoArgs: string[] = [];
const audioArgs: string[] = [];
const debugMode = getDebugMode(storage);
@@ -37,27 +37,21 @@ export async function startCameraStreamFfmpeg(device: ScryptedDevice & VideoCame
transcodingDebugModeWarning();
const videoCodec = ffmpegInput.mediaStreamOptions?.video?.codec;
const transcodeStreaming = !!ffmpegInput.h264EncoderArguments?.length
|| !!ffmpegInput.h264FilterArguments?.length;
const needsFFmpeg = debugMode.video
|| transcodeStreaming
|| ffmpegInput.container !== 'rtsp';
if (ffmpegInput.mediaStreamOptions?.oobCodecParameters)
videoArgs.push("-bsf:v", "dump_extra");
// encoder args
if (debugMode.video || transcodeStreaming) {
if (debugMode.video || !ffmpegInput.h264EncoderArguments) {
if (debugMode.video) {
if (debugMode.video) {
videoArgs.push(...getDebugModeH264EncoderArgs());
}
else {
videoArgs.push(...ffmpegInput.h264EncoderArguments);
}
const videoRecordingFilter = `scale=w='min(${request.video.width},iw)':h=-2`;
addVideoFilterArguments(videoArgs, videoRecordingFilter);
const bitrate = ffmpegInput.destinationVideoBitrate || (request.video.max_bit_rate * 1000);
const bitrate = request.video.max_bit_rate * 1000;
videoArgs.push(
"-b:v", bitrate.toString(),
"-bufsize", (2 * bitrate).toString(),

View File

@@ -60,9 +60,22 @@ export async function getStreamingConfiguration(device: ScryptedDevice & VideoCa
// AAC-ELD (Remote): 60
const isLowBandwidth = request.audio.packet_time >= 60;
// watch is 448x368 and requests 320x240, everything else is > ~1280...
// Apple Watch Series 1 to 3
// 38mm: 320 x 360 pixels
// 42mm: 378 x 448 pixels
// Apple Watch Series 4 to 6
// 40mm: 360 x 448 pixels
// 44mm: 408 x 496 pixels
// Apple Watch Series 7 and 8
// 41mm: 396 x 484 pixels
// 45mm: 428 x 528 pixels
// Apple Watch SE
// 40mm: 360 x 448 pixels
// 44mm: 408 x 496 pixels
// Apple Watch Ultra
// 49mm: 484 x 568 pixels
// future proof-ish for higher resolution watch.
const isWatch = request.video.width <= 640;
const isWatch = request.video.width < 640;
const destination: MediaStreamDestination = isWatch
? 'low-resolution'

View File

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

View File

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

View File

@@ -4,12 +4,14 @@ export function isPublishable(type: ScryptedDeviceType, interfaces: string[]): b
switch (type) {
case ScryptedDeviceType.API:
case ScryptedDeviceType.Builtin:
case ScryptedDeviceType.Internal:
case ScryptedDeviceType.DataSource:
case ScryptedDeviceType.Unknown:
return false;
}
const set = new Set(interfaces);
set.delete(ScryptedInterface.ObjectDetection);
set.delete(ScryptedInterface.DeviceProvider);
set.delete(ScryptedInterface.DeviceDiscovery);
set.delete(ScryptedInterface.DeviceCreator);
set.delete(ScryptedInterface.DeviceProvider);
@@ -24,5 +26,13 @@ export function isPublishable(type: ScryptedDeviceType, interfaces: string[]): b
set.delete(ScryptedInterface.OauthClient);
set.delete(ScryptedInterface.OauthClient);
set.delete(ScryptedInterface.LauncherApplication);
set.delete(ScryptedInterface.ScryptedSystemDevice);
set.delete(ScryptedInterface.ScryptedDeviceCreator);
set.delete(ScryptedInterface.ScryptedUser);
set.delete(ScryptedInterface.Camera);
set.delete(ScryptedInterface.RTCSignalingChannel);
set.delete(ScryptedInterface.StreamService);
set.delete(ScryptedInterface.Settings);
set.delete(ScryptedInterface.Notifier);
return !!set.size;
}

5
plugins/nanokvm/.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/nanokvm/.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-console.*",
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "node"
}
]
}

4
plugins/nanokvm/.vscode/settings.json vendored Normal file
View File

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

20
plugins/nanokvm/.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 @@
# NanoKVM Plugin
This plugin allows viewing and connecting to [NanoKVM](https://github.com/sipeed/NanoKVM) dongles from within the Scrypted Management Console.

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

229
plugins/nanokvm/package-lock.json generated Normal file
View File

@@ -0,0 +1,229 @@
{
"name": "@scrypted/nanokvm",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/nanokvm",
"dependencies": {
"@scrypted/common": "../../common",
"@scrypted/sdk": "../../sdk",
"crypto-js": "^4.2.0",
"ws": "^8.18.1"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",
"@types/node": "^22.13.10",
"@types/ws": "^8.18.0"
},
"version": "0.0.7"
},
"../../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.5.8",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
"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.10.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.11"
}
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
},
"node_modules/@scrypted/sdk": {
"resolved": "../../sdk",
"link": true
},
"node_modules/@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true
},
"node_modules/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
},
"dependencies": {
"@scrypted/common": {
"version": "file:../../common",
"requires": {
"@scrypted/sdk": "file:../sdk",
"@types/node": "^20.11.0",
"http-auth-utils": "^5.0.1",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
}
},
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.26.0",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.1",
"@rollup/plugin-virtual": "^3.0.2",
"@types/node": "^22.10.1",
"adm-zip": "^0.5.16",
"axios": "^1.7.8",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^6.0.1",
"rollup": "^4.27.4",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-bundle-analyzer": "^4.10.2"
}
},
"@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
"dev": true
},
"@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"dev": true,
"requires": {
"undici-types": "~6.20.0"
}
},
"@types/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true
},
"ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
"requires": {}
}
},
"version": "0.0.7"
}

View File

@@ -0,0 +1,44 @@
{
"name": "@scrypted/nanokvm",
"version": "0.0.7",
"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"
},
"scrypted": {
"babel": true,
"name": "NanoKVM",
"type": "DeviceProvider",
"interfaces": [
"DeviceProvider",
"DeviceCreator"
]
},
"keywords": [
"scrypted",
"plugin",
"kvm",
"nanokvm",
"sipeed"
],
"dependencies": {
"@scrypted/common": "../../common",
"@scrypted/sdk": "../../sdk",
"crypto-js": "^4.2.0",
"ws": "^8.18.1"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",
"@types/node": "^22.13.10",
"@types/ws": "^8.18.0"
}
}

335
plugins/nanokvm/src/main.ts Normal file
View File

@@ -0,0 +1,335 @@
import fs from 'fs';
import crypto from 'crypto';
import sdk, { Camera, DeviceCreator, DeviceCreatorSettings, DeviceProvider, KvmKeyEvent, KvmMouseEvent, MediaObject, OnOff, RequestPictureOptions, ResponsePictureOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingOptions, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting, Settings, SettingValue, StreamService } from '@scrypted/sdk';
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import { connectRTCSignalingClients } from '@scrypted/common/src/rtc-signaling';
import { WebSocket } from 'ws';
import { Deferred } from '@scrypted/common/src/deferred';
import { createAsyncQueue } from '@scrypted/common/src/async-queue';
import { once } from 'events';
import { VirtualKeyboard } from './nanokvm/virtual-keyboard';
import { encrypt } from './nanokvm/encrypt';
class NanoKVMSessionControl implements RTCSessionControl {
constructor(public h264: WebSocket) {
}
async getRefreshAt(): Promise<void> {
}
async extendSession(): Promise<void> {
}
async endSession(): Promise<void> {
this.h264.close();
}
async setPlayback(options: { audio: boolean; video: boolean; }): Promise<void> {
}
}
class NanoKVMDevice extends ScryptedDeviceBase implements Settings, RTCSignalingChannel, StreamService<(KvmKeyEvent | KvmMouseEvent)[], void>, Camera {
cookie: Promise<string> | undefined;
storageSettings = new StorageSettings(this, {
host: {
title: 'Host',
type: 'string',
placeholder: '192.168.2.222',
onPut: () => {
this.info = {
...this.info,
ip: this.storageSettings.values.host,
};
}
},
username: {
title: 'Username',
type: 'string',
defaultValue: 'admin',
},
password: {
title: 'Password',
type: 'password',
},
});
constructor(nativeId: string) {
super(nativeId);
}
async getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
}
async putSetting(key: string, value: SettingValue) {
await this.storageSettings.putSetting(key, value);
}
async* dummyConnectStream(): AsyncGenerator<void, void, any> {
}
async getCookie() {
if (this.cookie)
return this.cookie;
this.cookie = (async () => {
const { host, password } = this.storageSettings.values;
if (!host || !password)
throw new Error('host and password are required');
const url = new URL(`http://${host}/api/auth/login`);
// {"username":"admin","password":"encrypted-password"}
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: this.storageSettings.values.username || 'admin',
password: encrypt(password),
}),
});
const body = await response.json();
const token = body.data.token;
return token;
})()
.catch(e => {
this.cookie = undefined;
throw e;
});
return this.cookie;
}
async connectStream(input?: AsyncGenerator<(KvmKeyEvent | KvmMouseEvent)[], void, any> | undefined, options?: any): Promise<AsyncGenerator<void, void, any>> {
const { host } = this.storageSettings.values;
const wsUrl = new URL(`ws://${host}/api/ws`);
const ws = new WebSocket(wsUrl, {
headers: {
cookie: `nano-kvm-token=${await this.getCookie()}`,
},
});
await once(ws, 'open');
const heartbeatTimer = setInterval(() => {
ws.send(JSON.stringify({ event: 'heartbeat', data: '' }));
}, 60 * 1000);
ws.on('close', () => clearInterval(heartbeatTimer));
const virtualKeyboard = new VirtualKeyboard(this.console, ws);
(async () => {
for await (const events of input!) {
for (const event of events) {
if (event.event === 'mousedown') {
let button: number | undefined;
if (event.button === 0)
button = 1;
else if (event.button === 2)
button = 2;
if (button) {
ws.send(JSON.stringify([2, 1, button, 0, 0]));
}
}
else if (event.event === 'mouseup') {
ws.send(JSON.stringify([2, 1, 0, 0, 0]));
}
else if (event.event === 'mousemove') {
// 0-1 is scaled to 0-2^15
const x = Math.round(event.x * 32767);
const y = Math.round(event.y * 32767);
ws.send(JSON.stringify([2, 2, 0, x, y]));
}
else if (event.event === 'keyup') {
virtualKeyboard.onKeyReleased(event.code);
}
else if (event.event === 'keydown') {
virtualKeyboard.onKeyPress(event.code);
}
}
}
})()
.catch(e => {
this.console.error('error in input stream', e);
})
.finally(() => {
this.console.log('websocket closed');
ws.close();
})
return this.dummyConnectStream();
}
async startRTCSignalingSession(session: RTCSignalingSession): Promise<RTCSessionControl> {
const options: RTCSignalingOptions = {
requiresOffer: true,
};
const { host } = this.storageSettings.values;
const h264Url = new URL(`ws://${host}/api/stream/h264`);
const h264 = new WebSocket(h264Url, {
headers: {
cookie: `nano-kvm-token=${await this.getCookie()}`,
},
});
const answerSdp = new Deferred<RTCSessionDescriptionInit>();
const candidateQueue = createAsyncQueue<RTCIceCandidateInit>();
h264.on('message', (data) => {
const message = JSON.parse(data.toString());
if (message.event === 'answer') {
// unsubscribe
const answer = JSON.parse(message.data) as RTCSessionDescriptionInit;
answerSdp.resolve(answer);
}
else if (message.event === 'candidate') {
const candidate = JSON.parse(message.data) as RTCIceCandidateInit;
candidateQueue.enqueue(candidate);
}
});
await once(h264, 'open');
h264.on('error', (e) => {
this.console.error('websocket error', e);
answerSdp.reject(e);
});
h264.on('close', () => {
this.console.log('websocket closed');
answerSdp.reject(new Error('websocket closed'));
});
await connectRTCSignalingClients(this.console, session,
{
type: 'offer',
audio: {
direction: 'recvonly',
},
video: {
direction: 'recvonly',
},
},
{
__proxy_props: {
options,
},
options,
createLocalDescription: async (type: 'offer' | 'answer', setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate) => {
if (type !== 'answer')
throw new Error('NanoKVM endpoint only supports RTC answer');
const answer = await answerSdp.promise;
if (sendIceCandidate) {
process.nextTick(() => candidateQueue.pipe(sendIceCandidate));
}
else {
// wait for all? doesnt seem to send a null event.
}
return {
type: 'answer',
sdp: answer.sdp,
};
},
setRemoteDescription: async (description: RTCSessionDescriptionInit, setup: RTCAVSignalingSetup) => {
if (description.type !== 'offer')
throw new Error('NanoKVM endpoint only supports RTC answer');
const message = {
event: 'offer',
data: JSON.stringify(description),
}
h264.send(JSON.stringify(message));
},
addIceCandidate: async (candidate: RTCIceCandidateInit) => {
const message = {
event: 'candidate',
data: JSON.stringify(candidate),
};
h264.send(JSON.stringify(message));
},
getOptions: async () => options,
}, {});
return new NanoKVMSessionControl(h264);
}
async getPictureOptions(): Promise<ResponsePictureOptions[]> {
return [];
}
async takePicture(options?: RequestPictureOptions): Promise<MediaObject> {
const black = fs.promises.readFile('black.png');
const mo = await sdk.mediaManager.createMediaObject(black, 'image/png');
return mo;
}
}
class NanoKVMPlugin extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
constructor(nativeId?: string) {
super(nativeId);
}
async getCreateDeviceSettings(): Promise<Setting[]> {
const settings = [
{
key: 'name',
title: 'Name',
placeholder: 'NanoKVM',
},
{
title: 'Host',
key: 'host',
placeholder: '192.168.2.222',
},
{
title: 'Username',
key: 'username',
placeholder: 'admin',
value: 'admin',
},
{
title: 'Password',
key: 'password',
type: 'password',
},
] satisfies Setting[];
return settings;
}
async getDevice(nativeId: string): Promise<ScryptedDeviceBase> {
return new NanoKVMDevice(nativeId);
}
async createDevice(settings: DeviceCreatorSettings): Promise<string> {
const nativeId = crypto.randomBytes(8).toString('hex');
const id = await sdk.deviceManager.onDeviceDiscovered({
nativeId,
name: settings.name as string || 'NanoKVM',
interfaces: [
ScryptedInterface.Settings,
ScryptedInterface.RTCSignalingChannel,
ScryptedInterface.Camera,
ScryptedInterface.StreamService,
],
type: ScryptedDeviceType.RemoteDesktop,
info: {
manufacturer: 'SiPEED',
model: 'NanoKVM',
ip: settings.host as string,
}
});
const device = await this.getDevice(nativeId) as NanoKVMDevice;
device.storageSettings.values.host = settings.host as string;
device.storageSettings.values.username = settings.username as string || 'admin';
device.storageSettings.values.password = settings.password as string;
return id;
}
async releaseDevice(id: string, nativeId: ScryptedNativeId): Promise<void> {
}
}
export default NanoKVMPlugin;

View File

@@ -0,0 +1,9 @@
import AES from 'crypto-js/aes';
// This key is only used to prevent the data from being transmitted in plaintext.
const SECRET_KEY = 'nanokvm-sipeed-2024';
export function encrypt(data: string) {
const dataEncrypt = AES.encrypt(data, SECRET_KEY).toString();
return encodeURIComponent(dataEncrypt);
}

View File

@@ -0,0 +1,131 @@
export const KeyboardCodes: Map<string, number> = new Map([
['KeyA', 4],
['KeyB', 5],
['KeyC', 6],
['KeyD', 7],
['KeyE', 8],
['KeyF', 9],
['KeyG', 10],
['KeyH', 11],
['KeyI', 12],
['KeyJ', 13],
['KeyK', 14],
['KeyL', 15],
['KeyM', 16],
['KeyN', 17],
['KeyO', 18],
['KeyP', 19],
['KeyQ', 20],
['KeyR', 21],
['KeyS', 22],
['KeyT', 23],
['KeyU', 24],
['KeyV', 25],
['KeyW', 26],
['KeyX', 27],
['KeyY', 28],
['KeyZ', 29],
['Digit1', 30],
['Digit2', 31],
['Digit3', 32],
['Digit4', 33],
['Digit5', 34],
['Digit6', 35],
['Digit7', 36],
['Digit8', 37],
['Digit9', 38],
['Digit0', 39],
['Enter', 40],
['Escape', 41],
['Backspace', 42],
['Tab', 43],
['Space', 44],
['Minus', 45],
['Equal', 46],
['BracketLeft', 47],
['BracketRight', 48],
['Backslash', 49],
['IntlBackslash', 49],
['Semicolon', 51],
['Quote', 52],
['Backquote', 53],
['KeyTilde', 53],
['Comma', 54],
['Period', 55],
['KeyDot', 55],
['Slash', 56],
['CapsLock', 57],
['F1', 58],
['F2', 59],
['F3', 60],
['F4', 61],
['F5', 62],
['F6', 63],
['F7', 64],
['F8', 65],
['F9', 66],
['F10', 67],
['F11', 68],
['F12', 69],
['F13', 70],
['PrintScreen', 70],
['ScrollLock', 71],
['Pause', 72],
['Insert', 73],
['Home', 74],
['PageUp', 75],
['Delete', 76],
['End', 77],
['PageDown', 78],
['ArrowRight', 79],
['ArrowLeft', 80],
['ArrowDown', 81],
['ArrowUp', 82],
['NumLock', 83],
['NumpadDivide', 84],
['NumpadMultiply', 85],
['NumpadSubtract', 86],
['NumpadAdd', 87],
['NumpadEnter', 88],
['Numpad1', 89],
['Numpad2', 90],
['Numpad3', 91],
['Numpad4', 92],
['Numpad5', 93],
['Numpad6', 94],
['Numpad7', 95],
['Numpad8', 96],
['Numpad9', 97],
['Numpad0', 98],
['NumpadDecimal', 99],
['KeyKpDot', 99],
['Menu', 118],
['ControlLeft', 224],
['ShiftLeft', 225],
['AltLeft', 226],
['MetaLeft', 227],
['ControlRight', 228],
['ShiftRight', 229],
['AltRight', 230],
['MetaRight', 231]
]);
export const ModifierCodes: Map<string, number> = new Map([
['ControlLeft', 1],
['ShiftLeft', 2],
['AltLeft', 4],
['MetaLeft', 8],
['ControlRight', 16],
['ShiftRight', 32],
['AltRight', 64],
['MetaRight', 128]
]);

View File

@@ -0,0 +1,8 @@
import { modifierKeys, specialKeyMap } from "./virtual-keys";
export const fixedSpecialKeys = new Map<string, string>();
for (const [key, value] of specialKeyMap) {
fixedSpecialKeys.set(value, value);
}
export const fixedModifierKeys = modifierKeys.map(key => specialKeyMap.get(key));

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