mirror of
https://github.com/koush/scrypted.git
synced 2026-02-03 14:13:28 +00:00
Compare commits
239 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec2e4d64fd | ||
|
|
44644448f5 | ||
|
|
0a86d5c4ea | ||
|
|
20282e05ea | ||
|
|
9d6b405fa9 | ||
|
|
b82ce5ff45 | ||
|
|
f461198e1e | ||
|
|
7505e6907a | ||
|
|
c1046d5706 | ||
|
|
a61c06b607 | ||
|
|
d3df5742e6 | ||
|
|
68ac42ca46 | ||
|
|
bb7c6ef8b9 | ||
|
|
446e8ed61e | ||
|
|
80372b35f2 | ||
|
|
57eff2f296 | ||
|
|
d996088041 | ||
|
|
04be70019b | ||
|
|
51732d0dcd | ||
|
|
e40bc3ddee | ||
|
|
3f4409e1c3 | ||
|
|
63b7616ab3 | ||
|
|
29059691ce | ||
|
|
531a9d28dc | ||
|
|
3314b4d9ca | ||
|
|
37df9810c8 | ||
|
|
47c1cbba3c | ||
|
|
ded7e549bb | ||
|
|
abb2b85cec | ||
|
|
7d157d2882 | ||
|
|
c6c0a225dd | ||
|
|
276fc386ec | ||
|
|
0b21afd193 | ||
|
|
1032e58e3b | ||
|
|
4987b01167 | ||
|
|
28bb8c5b3c | ||
|
|
2160170c3a | ||
|
|
c0eac9053b | ||
|
|
d57501dd42 | ||
|
|
264cb0404f | ||
|
|
dc9f4b39a8 | ||
|
|
653eeceaf2 | ||
|
|
3d8711947a | ||
|
|
38038d5f30 | ||
|
|
e21d9c3a0c | ||
|
|
7b8c014b3b | ||
|
|
55a30864fd | ||
|
|
4f419ff75c | ||
|
|
638a4f28ad | ||
|
|
8970154b8f | ||
|
|
c96debaaed | ||
|
|
fe7b479235 | ||
|
|
aa1486e641 | ||
|
|
207f00733e | ||
|
|
bc4b2c956c | ||
|
|
e15578c9ca | ||
|
|
e2c5ee2400 | ||
|
|
dec62ddbc6 | ||
|
|
e7a65c4a28 | ||
|
|
127855c57e | ||
|
|
d9f35ef461 | ||
|
|
787c452105 | ||
|
|
3556b326f5 | ||
|
|
dd42878a5f | ||
|
|
07db378763 | ||
|
|
df142635b8 | ||
|
|
24bcc32532 | ||
|
|
5856ad60dd | ||
|
|
124da3c1b7 | ||
|
|
005efbfe82 | ||
|
|
cb955f403d | ||
|
|
fa38d8c560 | ||
|
|
0e4a94cd6e | ||
|
|
a0c3721140 | ||
|
|
7efffe4c51 | ||
|
|
2ff80780f8 | ||
|
|
9a41e3bc29 | ||
|
|
23de4011fb | ||
|
|
4fba5b9484 | ||
|
|
0dcfa367a6 | ||
|
|
8e63943f53 | ||
|
|
e2d78611c5 | ||
|
|
8a94c268ac | ||
|
|
10bdef414e | ||
|
|
317ee80477 | ||
|
|
851c5caf4d | ||
|
|
03cfeffca5 | ||
|
|
196316ad97 | ||
|
|
f7ccdf0795 | ||
|
|
64d36513a0 | ||
|
|
6ae8fd6d96 | ||
|
|
7448f19b78 | ||
|
|
35f0e325c1 | ||
|
|
579e188d06 | ||
|
|
09e97a2895 | ||
|
|
700856bc2e | ||
|
|
26eb69b08a | ||
|
|
332d9716f9 | ||
|
|
511f348cbe | ||
|
|
f26b6c3bca | ||
|
|
d38abcd563 | ||
|
|
b71f6e8b7e | ||
|
|
b492e8912e | ||
|
|
e663f5d3fc | ||
|
|
21ac653c66 | ||
|
|
e6f80be974 | ||
|
|
7b41e17981 | ||
|
|
530961179d | ||
|
|
7bb1d75905 | ||
|
|
337a74a170 | ||
|
|
e21facb123 | ||
|
|
15ac061311 | ||
|
|
f66a300082 | ||
|
|
24d754b5dc | ||
|
|
0221d00a17 | ||
|
|
3c49501c2c | ||
|
|
a81e5337f0 | ||
|
|
675339f495 | ||
|
|
48c6b90e21 | ||
|
|
ccf2b8c67d | ||
|
|
cc97d7d1c1 | ||
|
|
6af2f5eecd | ||
|
|
1df4c964d7 | ||
|
|
24b5b36a44 | ||
|
|
1da7d25235 | ||
|
|
d691b21646 | ||
|
|
d691bfcef1 | ||
|
|
04d5633690 | ||
|
|
dffd20f978 | ||
|
|
5fd9579423 | ||
|
|
bd7d49833f | ||
|
|
701ca5e1e2 | ||
|
|
14cb5a2117 | ||
|
|
adf4e797c1 | ||
|
|
a946be88d3 | ||
|
|
4be844f3f7 | ||
|
|
9eff813619 | ||
|
|
e1b5eaa63f | ||
|
|
b7f1efc307 | ||
|
|
12fedc53ee | ||
|
|
1c7626c156 | ||
|
|
a578623d3f | ||
|
|
b13d32f3ed | ||
|
|
d34c3aae74 | ||
|
|
c27176f0f8 | ||
|
|
be4f4bb6d6 | ||
|
|
26db89b811 | ||
|
|
6f0738ef07 | ||
|
|
1ae1eafddc | ||
|
|
0ecacfd974 | ||
|
|
146a648f39 | ||
|
|
7015f26eee | ||
|
|
a4e484698d | ||
|
|
1d659a5fb9 | ||
|
|
80e70b6bb8 | ||
|
|
ceec1174e3 | ||
|
|
b658202d8a | ||
|
|
425ee41ab0 | ||
|
|
3f7b801ffb | ||
|
|
ccb7ae0323 | ||
|
|
252c90fb46 | ||
|
|
2beceaf869 | ||
|
|
1dbb9a0f63 | ||
|
|
85c35f5fb7 | ||
|
|
8966cd7c70 | ||
|
|
60aeae5336 | ||
|
|
eef6c3ed3f | ||
|
|
979c430303 | ||
|
|
4892b72f37 | ||
|
|
3fd81b86d7 | ||
|
|
b3a2789a1d | ||
|
|
fde5cfa51e | ||
|
|
1ada7bb3fe | ||
|
|
2445ea909d | ||
|
|
7b16e8e969 | ||
|
|
1ffe8357e3 | ||
|
|
c96b4730de | ||
|
|
d89a8c6072 | ||
|
|
8e47bbb153 | ||
|
|
b04bb414d6 | ||
|
|
bd947fb934 | ||
|
|
a643960863 | ||
|
|
83f869988d | ||
|
|
1fc0934b4e | ||
|
|
29b7e4151a | ||
|
|
ad3c4f9529 | ||
|
|
e9026372c1 | ||
|
|
9b30064896 | ||
|
|
c73ffb30c1 | ||
|
|
bae12eecae | ||
|
|
1440b3811d | ||
|
|
59a3a97577 | ||
|
|
07f02fe371 | ||
|
|
cf4f4b7c73 | ||
|
|
aab0bdfc41 | ||
|
|
438f61b729 | ||
|
|
0b3717439f | ||
|
|
a9a145bd3e | ||
|
|
2f97b39cbb | ||
|
|
8bc1fe1588 | ||
|
|
90740f3c9d | ||
|
|
f92a12b99c | ||
|
|
405453da03 | ||
|
|
d01fe4310b | ||
|
|
6bcab63e0e | ||
|
|
c2bf7a62ab | ||
|
|
bc631d6c8b | ||
|
|
66877d1efa | ||
|
|
96a4a11503 | ||
|
|
bd0393305b | ||
|
|
9033eadafb | ||
|
|
c42b8ecda2 | ||
|
|
521bb62f10 | ||
|
|
8819f0a249 | ||
|
|
341cfa1e2f | ||
|
|
96335544ad | ||
|
|
ce0adb5706 | ||
|
|
22fb257214 | ||
|
|
674034fb7e | ||
|
|
44dcfe5e12 | ||
|
|
194e8532c3 | ||
|
|
797ef79080 | ||
|
|
56dbecbf3d | ||
|
|
67acb7725f | ||
|
|
a2caad7109 | ||
|
|
599d370f7d | ||
|
|
be284a0df7 | ||
|
|
030c3432a7 | ||
|
|
7d0d26ad31 | ||
|
|
6ba8a1dea4 | ||
|
|
cafb6944f5 | ||
|
|
f800401317 | ||
|
|
d672e2271d | ||
|
|
3d558e3119 | ||
|
|
c94945c6d5 | ||
|
|
82afc6d53f | ||
|
|
16ba10da21 | ||
|
|
6f5e0700a2 | ||
|
|
11b81371b4 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -11,9 +11,6 @@
|
||||
[submodule "external/werift"]
|
||||
path = external/werift
|
||||
url = ../../koush/werift-webrtc
|
||||
[submodule "sdk/developer.scrypted.app"]
|
||||
path = sdk/developer.scrypted.app
|
||||
url = ../../koush/developer.scrypted.app
|
||||
[submodule "plugins/sample-cameraprovider"]
|
||||
path = plugins/sample-cameraprovider
|
||||
url = ../../koush/scrypted-sample-cameraprovider
|
||||
@@ -23,6 +20,3 @@
|
||||
[submodule "plugins/wyze/docker-wyze-bridge"]
|
||||
path = plugins/wyze/docker-wyze-bridge
|
||||
url = ../../koush/docker-wyze-bridge.git
|
||||
[submodule "plugins/onvif/onvif"]
|
||||
path = plugins/onvif/onvif
|
||||
url = ../../koush/onvif.git
|
||||
|
||||
@@ -40,7 +40,7 @@ export function createAsyncQueue<T>() {
|
||||
return false;
|
||||
|
||||
if (waiting.length) {
|
||||
const deferred = waiting.shift();
|
||||
const deferred = waiting.shift()!;
|
||||
dequeued?.resolve();
|
||||
deferred.resolve(item);
|
||||
return true;
|
||||
@@ -66,7 +66,7 @@ export function createAsyncQueue<T>() {
|
||||
dequeued?.reject(new Error('abort'));
|
||||
};
|
||||
|
||||
dequeued.promise.catch(() => {}).finally(() => signal.removeEventListener('abort', h));
|
||||
dequeued?.promise.catch(() => {}).finally(() => signal.removeEventListener('abort', h));
|
||||
signal.addEventListener('abort', h);
|
||||
|
||||
return true;
|
||||
@@ -79,7 +79,7 @@ export function createAsyncQueue<T>() {
|
||||
ended = e || new EndError();
|
||||
endDeferred.resolve();
|
||||
while (waiting.length) {
|
||||
waiting.shift().reject(ended);
|
||||
waiting.shift()!.reject(ended);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,11 +41,15 @@ export abstract class AutoenableMixinProvider extends ScryptedDeviceBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
checkHasEnabledMixin(device: ScryptedDevice) {
|
||||
return this.hasEnabledMixin[device.id] === this.autoIncludeToken;
|
||||
}
|
||||
|
||||
async maybeEnableMixin(device: ScryptedDevice) {
|
||||
if (!device || device.mixins?.includes(this.id))
|
||||
return;
|
||||
|
||||
if (this.hasEnabledMixin[device.id] === this.autoIncludeToken)
|
||||
if (this.checkHasEnabledMixin(device))
|
||||
return;
|
||||
|
||||
const match = await this.canMixin(device.type, device.interfaces);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import sdk, { MixinDeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes } from "@scrypted/sdk";
|
||||
import sdk, { LockState, MixinDeviceBase, PanTiltZoomMovement, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors, ScryptedMimeTypes } from "@scrypted/sdk";
|
||||
import { SettingsMixinDeviceBase } from "@scrypted/sdk/settings-mixin";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import fs from 'fs';
|
||||
@@ -76,6 +76,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
localStorage: device.storage,
|
||||
device,
|
||||
exports: {} as any,
|
||||
PanTiltZoomMovement,
|
||||
SettingsMixinDeviceBase,
|
||||
ScryptedMimeTypes,
|
||||
ScryptedInterface,
|
||||
|
||||
2
external/ring-client-api
vendored
2
external/ring-client-api
vendored
Submodule external/ring-client-api updated: 3797e311ed...d9f51b8b6d
@@ -1,6 +1,6 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "v0.116.0-jammy-full"
|
||||
version: "v0.120.0-jammy-full"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
|
||||
@@ -18,4 +18,4 @@ ENV NODE_OPTIONS="--dns-result-order=ipv4first"
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20240321"
|
||||
|
||||
CMD npm --prefix /server exec scrypted-serve
|
||||
CMD ["/bin/sh", "-c", "ulimit -c 0; exec npm --prefix /server exec scrypted-serve"]
|
||||
|
||||
@@ -73,7 +73,9 @@ FROM header as base
|
||||
|
||||
# intel opencl gpu and npu for openvino
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
|
||||
|
||||
# Disable NPU on docker, because level-zero crashes openvino on older systems.
|
||||
# RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
|
||||
FROM $BASE
|
||||
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES=all
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||
|
||||
# nvidia cudnn/libcublas etc.
|
||||
# for some reason this is not provided by the nvidia container toolkit
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-graphics.sh | bash
|
||||
|
||||
@@ -48,4 +48,4 @@ ENV NODE_OPTIONS="--dns-result-order=ipv4first"
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION="20240321"
|
||||
|
||||
CMD npm --prefix /server exec scrypted-serve
|
||||
CMD ["/bin/sh", "-c", "ulimit -c 0; exec npm --prefix /server exec scrypted-serve"]
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
services:
|
||||
scrypted:
|
||||
# LXC usage only
|
||||
# lxc privileged: true
|
||||
|
||||
environment:
|
||||
# Scrypted NVR Storage (Part 2 of 3)
|
||||
|
||||
@@ -29,30 +32,26 @@ services:
|
||||
# section below.
|
||||
# - SCRYPTED_NVR_VOLUME=/nvr
|
||||
|
||||
- SCRYPTED_WEBHOOK_UPDATE_AUTHORIZATION=Bearer SET_THIS_TO_SOME_RANDOM_TEXT
|
||||
- SCRYPTED_WEBHOOK_UPDATE_AUTHORIZATION=Bearer ${WATCHTOWER_HTTP_API_TOKEN:-env_missing_fallback}
|
||||
- SCRYPTED_WEBHOOK_UPDATE=http://localhost:10444/v1/update
|
||||
|
||||
# LXC usage only
|
||||
# lxc - SCRYPTED_INSTALL_ENVIRONMENT=lxc-docker
|
||||
|
||||
# Avahi can be used for network discovery by passing in the host daemon
|
||||
# or running the daemon inside the container. Choose one or the other.
|
||||
# Uncomment next line to run avahi-daemon inside the container.
|
||||
# See volumes and security_opt section below to use the host daemon.
|
||||
# - SCRYPTED_DOCKER_AVAHI=true
|
||||
|
||||
# NVIDIA (Part 1 of 4)
|
||||
# - NVIDIA_VISIBLE_DEVICES=all
|
||||
# - NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
# NVIDIA (Part 2 of 4)
|
||||
# NVIDIA (Part 1 of 2)
|
||||
# runtime: nvidia
|
||||
|
||||
# NVIDIA (Part 3 of 4) - Use NVIDIA image, and remove subsequent default image.
|
||||
# NVIDIA (Part 2 of 2) - Use NVIDIA image, and remove subsequent default image.
|
||||
# image: ghcr.io/koush/scrypted:nvidia
|
||||
image: ghcr.io/koush/scrypted
|
||||
|
||||
volumes:
|
||||
# NVIDIA (Part 4 of 4)
|
||||
# - /etc/OpenCL/vendors/nvidia.icd:/etc/OpenCL/vendors/nvidia.icd
|
||||
|
||||
# Scrypted NVR Storage (Part 3 of 3)
|
||||
|
||||
# Modify to add the additional volume for Scrypted NVR.
|
||||
@@ -77,6 +76,14 @@ services:
|
||||
|
||||
# Default volume for the Scrypted database. Typically should not be changed.
|
||||
- ~/.scrypted/volume:/server/volume
|
||||
|
||||
# LXC usage only
|
||||
# lxc - /var/run/docker.sock:/var/run/docker.sock
|
||||
# lxc - /root/.scrypted/docker-compose.yml:/root/.scrypted/docker-compose.yml
|
||||
# lxc - /root/.scrypted/docker-compose.sh:/root/.scrypted/docker-compose.sh
|
||||
# lxc - /root/.scrypted/.env:/root/.scrypted/.env
|
||||
# lxc - /mnt:/mnt
|
||||
|
||||
# Uncomment the following lines to use Avahi daemon from the host
|
||||
# Without this, AppArmor will block the container's attempt to talk to Avahi via dbus
|
||||
# security_opt:
|
||||
@@ -120,12 +127,10 @@ services:
|
||||
# watchtower manages updates for Scrypted.
|
||||
watchtower:
|
||||
environment:
|
||||
- WATCHTOWER_HTTP_API_TOKEN=SET_THIS_TO_SOME_RANDOM_TEXT
|
||||
- WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_HTTP_API_TOKEN:-env_missing_fallback}
|
||||
- WATCHTOWER_HTTP_API_UPDATE=true
|
||||
- WATCHTOWER_SCOPE=scrypted
|
||||
# remove the following line to never allow docker to auto update.
|
||||
# this is not recommended.
|
||||
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=true
|
||||
- WATCHTOWER_HTTP_API_PERIODIC_POLLS=${WATCHTOWER_HTTP_API_PERIODIC_POLLS:-true}
|
||||
image: containrrr/watchtower
|
||||
container_name: scrypted-watchtower
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# disable core dumps.
|
||||
# this doesn't disable core dumps on the scrypted service itself, only stuff run by init.
|
||||
ulimit -c 0
|
||||
|
||||
if [[ "${SCRYPTED_DOCKER_AVAHI}" != "true" ]]; then
|
||||
echo "SCRYPTED_DOCKER_AVAHI != true, won't manage dbus nor avahi-daemon" >/dev/stderr
|
||||
exit 0
|
||||
|
||||
@@ -30,20 +30,24 @@ apt-get -y install intel-media-va-driver-non-free &&
|
||||
apt-get -y dist-upgrade;
|
||||
|
||||
# manual installation
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.35.30872.22
|
||||
# these debs are seemingly ubuntu 22.04 only.
|
||||
|
||||
rm -rf /tmp/gpu && mkdir -p /tmp/gpu && cd /tmp/gpu
|
||||
|
||||
apt-get install -y ocl-icd-libopencl1
|
||||
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-core_1.0.17193.4_amd64.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-opencl_1.0.17193.4_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-level-zero-gpu-dbgsym_1.3.30049.6_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-level-zero-gpu_1.3.30049.6_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd-dbgsym_24.26.30049.6_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd_24.26.30049.6_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/libigdgmm12_22.3.20_amd64.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-core_1.0.17537.20_amd64.deb
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.20/intel-igc-opencl_1.0.17537.20_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-dbgsym_1.3.30872.22_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1-dbgsym_1.3.30872.22_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu-legacy1_1.3.30872.22_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-level-zero-gpu_1.3.30872.22_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-dbgsym_24.35.30872.22_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1-dbgsym_24.35.30872.22_amd64.ddeb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd-legacy1_24.35.30872.22_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/intel-opencl-icd_24.35.30872.22_amd64.deb
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.35.30872.22/libigdgmm12_22.5.0_amd64.deb
|
||||
|
||||
dpkg -i *.deb
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ fi
|
||||
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
|
||||
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
|
||||
|
||||
if [ -z "$UBUNTU_22_04" ]
|
||||
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
|
||||
then
|
||||
# proxmox is compatible with ubuntu 22.04, check for /etc/pve directory
|
||||
if [ -d "/etc/pve" ]
|
||||
@@ -23,6 +23,13 @@ then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
distro="22.04_amd64"
|
||||
else
|
||||
distro="24.04_amd64"
|
||||
fi
|
||||
|
||||
dpkg --purge --force-remove-reinstreq intel-driver-compiler-npu intel-fw-npu intel-level-zero-npu
|
||||
|
||||
# no errors beyond this point
|
||||
@@ -30,27 +37,23 @@ set -e
|
||||
|
||||
rm -rf /tmp/npu && mkdir -p /tmp/npu && cd /tmp/npu
|
||||
|
||||
# different npu downloads for ubuntu versions
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-driver-compiler-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
# firmware can only be installed on host. will cause problems inside container.
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu22.04_amd64.deb
|
||||
else
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.5.1/intel-driver-compiler-npu_1.5.1.20240708-9842236399_ubuntu24.04_amd64.deb
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-fw-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v1.6.0/intel-level-zero-npu_1.6.0.20240814-10390978568_ubuntu24.04_amd64.deb
|
||||
fi
|
||||
# level zero must also be installed
|
||||
LEVEL_ZERO_VERSION=1.18.3
|
||||
# https://github.com/oneapi-src/level-zero
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero_"$LEVEL_ZERO_VERSION"+u$distro.deb
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v"$LEVEL_ZERO_VERSION"/level-zero-devel_"$LEVEL_ZERO_VERSION"+u$distro.deb
|
||||
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero_1.17.6+u22.04_amd64.deb
|
||||
curl -O -L https://github.com/oneapi-src/level-zero/releases/download/v1.17.6/level-zero-devel_1.17.6+u22.04_amd64.deb
|
||||
# npu driver
|
||||
# https://github.com/intel/linux-npu-driver
|
||||
NPU_VERSION=1.8.0
|
||||
NPU_VERSION_DATE=20240916-10885588273
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-driver-compiler-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
|
||||
# firmware can only be installed on host. will cause problems inside container.
|
||||
if [ -n "$INTEL_FW_NPU" ]
|
||||
then
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-fw-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
|
||||
fi
|
||||
curl -O -L https://github.com/intel/linux-npu-driver/releases/download/v"$NPU_VERSION"/intel-level-zero-npu_$NPU_VERSION."$NPU_VERSION_DATE"_ubuntu$distro.deb
|
||||
|
||||
apt -y update
|
||||
apt -y install libtbb12
|
||||
|
||||
40
install/docker/install-nvidia-container-toolkit.sh
Normal file
40
install/docker/install-nvidia-container-toolkit.sh
Normal file
@@ -0,0 +1,40 @@
|
||||
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
|
||||
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
|
||||
|
||||
set -e
|
||||
# https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=24.04&target_type=deb_network
|
||||
# need this apt for nvidia-utils
|
||||
# needs either ubuntu 22.0.4 or 24.04
|
||||
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
|
||||
then
|
||||
echo "NVIDIA container toolkit can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
distro="ubuntu2204"
|
||||
else
|
||||
distro="ubuntu2404"
|
||||
fi
|
||||
|
||||
apt update -q \
|
||||
&& apt install -y wget \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& dpkg -i /cuda-keyring.deb;
|
||||
|
||||
# https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
||||
apt -y update
|
||||
apt -y install gpg
|
||||
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --yes --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
|
||||
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
|
||||
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
|
||||
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
|
||||
apt -y update
|
||||
# is there a way to get a versioned package automatically?
|
||||
apt -y install nvidia-utils-560
|
||||
apt -y install nvidia-container-toolkit
|
||||
|
||||
nvidia-ctk runtime configure --runtime=docker
|
||||
nvidia-ctk config --set nvidia-container-cli.no-cgroups --in-place
|
||||
systemctl restart docker
|
||||
@@ -1,14 +1,52 @@
|
||||
if [ "$(uname -m)" = "x86_64" ]
|
||||
then
|
||||
UBUNTU_22_04=$(lsb_release -r | grep "22.04")
|
||||
UBUNTU_24_04=$(lsb_release -r | grep "24.04")
|
||||
|
||||
# needs either ubuntu 22.0.4 or 24.04
|
||||
if [ -z "$UBUNTU_22_04" ] && [ -z "$UBUNTU_24_04" ]
|
||||
then
|
||||
echo "NVIDIA graphics package can not be installed. Ubuntu version could not be detected when checking lsb-release and /etc/os-release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$UBUNTU_22_04" ]
|
||||
then
|
||||
distro="ubuntu2204"
|
||||
else
|
||||
distro="ubuntu2404"
|
||||
fi
|
||||
|
||||
echo "Installing NVIDIA graphics packages."
|
||||
apt update -q \
|
||||
&& apt install -y wget \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$distro/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& dpkg -i /cuda-keyring.deb \
|
||||
&& apt update -q \
|
||||
&& apt install -y cuda-nvcc-11-8 libcublas-11-8 libcudnn8 cuda-libraries-11-8 \
|
||||
&& apt install -y cuda-nvcc-12-4 libcublas-12-4 libcudnn8 cuda-libraries-12-4;
|
||||
exit $?
|
||||
&& apt install -y cuda-nvcc-12-6 libcublas-12-6 libcudnn9-cuda-12 cuda-libraries-12-6;
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "Error: NVIDIA graphics packages failed to install."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Update: the libnvidia-opencl.so.1 file is not present in the container image, it is
|
||||
# mounted via the nvidia container runtime. This is why the following check is commented out.
|
||||
# this file is present but for some reason the icd file is not created by nvidia runtime.
|
||||
# if [ ! -f "/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.1" ]
|
||||
# then
|
||||
# echo "Error: NVIDIA OpenCL library not found."
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# the container runtime doesn't mount this file for some reason. seems to be a bug.
|
||||
# https://github.com/NVIDIA/nvidia-container-toolkit/issues/682
|
||||
# but the contents are simply the .so file, which is a symlink the nvidia runtime
|
||||
# will mount in.
|
||||
mkdir -p /etc/OpenCL/vendors/
|
||||
echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd
|
||||
else
|
||||
echo "NVIDIA graphics will not be installed on this architecture."
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$SCRYPTED_LXC" ]
|
||||
then
|
||||
export SERVICE_USER="root"
|
||||
export SCRYPTED_NONINTERACTIVE="true"
|
||||
fi
|
||||
|
||||
if [ -z "$SERVICE_USER" ]
|
||||
then
|
||||
echo "Scrypted SERVICE_USER environment variable was not specified. Service will not be installed."
|
||||
@@ -7,6 +13,12 @@ then
|
||||
fi
|
||||
|
||||
function readyn() {
|
||||
if [ ! -z "$SCRYPTED_NONINTERACTIVE" ]
|
||||
then
|
||||
yn="y"
|
||||
return
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
@@ -33,6 +45,11 @@ systemctl disable scrypted.service 2> /dev/null
|
||||
USER_HOME=$(eval echo ~$SERVICE_USER)
|
||||
SCRYPTED_HOME=$USER_HOME/.scrypted
|
||||
mkdir -p $SCRYPTED_HOME
|
||||
# remove various things from a previous local install.
|
||||
rm -rf $SCRYPTED_HOME/node_modules
|
||||
rm -rf $SCRYPTED_HOME/install.json
|
||||
rm -rf $SCRYPTED_HOME/package.json
|
||||
rm -rf $SCRYPTED_HOME/package-lock.json
|
||||
|
||||
set -e
|
||||
cd $SCRYPTED_HOME
|
||||
@@ -46,13 +63,30 @@ then
|
||||
usermod -aG docker $SERVICE_USER
|
||||
fi
|
||||
|
||||
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum)
|
||||
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32)
|
||||
echo "WATCHTOWER_HTTP_API_TOKEN=$WATCHTOWER_HTTP_API_TOKEN" > $SCRYPTED_HOME/.env
|
||||
# remove the following line from .env to disable autoupdates.
|
||||
# this is not recommended.
|
||||
echo "WATCHTOWER_HTTP_API_PERIODIC_POLLS=true" >> $SCRYPTED_HOME/.env
|
||||
|
||||
DOCKER_COMPOSE_YML=$SCRYPTED_HOME/docker-compose.yml
|
||||
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml > $DOCKER_COMPOSE_YML
|
||||
echo "Created $DOCKER_COMPOSE_YML"
|
||||
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml | sed s/SET_THIS_TO_SOME_RANDOM_TEXT/"$(echo $RANDOM | md5sum | head -c 32)"/g > $DOCKER_COMPOSE_YML
|
||||
if [ -d /dev/dri ]
|
||||
|
||||
if [ -z "$SCRYPTED_LXC" ]
|
||||
then
|
||||
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/g' $DOCKER_COMPOSE_YML
|
||||
if [ -d /dev/dri ]
|
||||
then
|
||||
sed -i 's/'#' "\/dev\/dri/"\/dev\/dri/g' $DOCKER_COMPOSE_YML
|
||||
fi
|
||||
else
|
||||
# uncomment lxc specific stuff
|
||||
sed -i 's/'#' lxc //g' $DOCKER_COMPOSE_YML
|
||||
# never restart, systemd will handle it
|
||||
sed -i 's/restart: unless-stopped/restart: no/g' $DOCKER_COMPOSE_YML
|
||||
|
||||
sudo systemctl stop apparmor || true
|
||||
sudo apt -y purge apparmor || true
|
||||
fi
|
||||
|
||||
readyn "Install avahi-daemon? This is the recommended for reliable HomeKit discovery and pairing."
|
||||
@@ -66,7 +100,7 @@ then
|
||||
fi
|
||||
|
||||
echo "Setting permissions on $SCRYPTED_HOME"
|
||||
chown -R $SERVICE_USER $SCRYPTED_HOME
|
||||
chown -R $SERVICE_USER $SCRYPTED_HOME || true
|
||||
|
||||
set +e
|
||||
|
||||
@@ -79,8 +113,41 @@ set -e
|
||||
|
||||
echo "docker compose pull"
|
||||
sudo -u $SERVICE_USER docker compose pull
|
||||
echo "docker compose up -d"
|
||||
sudo -u $SERVICE_USER docker compose up -d
|
||||
|
||||
if [ -z "$SCRYPTED_LXC" ]
|
||||
then
|
||||
echo "docker compose up -d"
|
||||
sudo -u $SERVICE_USER docker compose up -d
|
||||
else
|
||||
export DOCKER_COMPOSE_SH=$SCRYPTED_HOME/docker-compose.sh
|
||||
|
||||
curl https://raw.githubusercontent.com/koush/scrypted/main/install/proxmox/docker-compose.sh > $DOCKER_COMPOSE_SH
|
||||
|
||||
chmod +x $DOCKER_COMPOSE_SH
|
||||
|
||||
cat > /etc/systemd/system/scrypted.service <<EOT
|
||||
[Unit]
|
||||
Description=Scrypted service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
Type=simple
|
||||
ExecStart=$DOCKER_COMPOSE_SH
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOT
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable scrypted.service
|
||||
systemctl restart scrypted.service
|
||||
fi
|
||||
|
||||
echo
|
||||
echo
|
||||
@@ -91,5 +158,5 @@ echo "Note that it is https and that you'll be asked to approve/ignore the websi
|
||||
echo
|
||||
echo
|
||||
echo "Optional:"
|
||||
echo "Scrypted NVR Recording storage directory can be configured with an additional script:"
|
||||
echo "https://docs.scrypted.app/scrypted-nvr/installation.html#docker-volume"
|
||||
echo "Scrypted NVR Recording storage directory can be configured with an additional script located at:"
|
||||
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#docker-volume"
|
||||
|
||||
@@ -5,7 +5,9 @@ FROM header as base
|
||||
|
||||
# intel opencl gpu and npu for openvino
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
|
||||
|
||||
# Disable NPU on docker, because level-zero crashes openvino on older systems.
|
||||
# RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
|
||||
@@ -40,8 +40,6 @@ echo "Installing Scrypted dependencies..."
|
||||
RUN_IGNORE xcode-select --install
|
||||
RUN brew update
|
||||
RUN_IGNORE brew install node@20
|
||||
# snapshot plugin and others
|
||||
RUN brew install libvips
|
||||
# dlib
|
||||
RUN brew install cmake
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
function readyn() {
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
[Yy]* ) break;;
|
||||
[Nn]* ) break;;
|
||||
* ) echo "Please answer yes or no. (y/n)";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.116.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
VMID=10443
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
RESTORE_VMID=$VMID
|
||||
VMID=10444
|
||||
pct destroy $VMID 2>&1 > /dev/null
|
||||
fi
|
||||
|
||||
echo "Downloading scrypted container backup."
|
||||
if [ ! -f "$SCRYPTED_TAR_ZST" ]
|
||||
then
|
||||
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
|
||||
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
|
||||
fi
|
||||
|
||||
echo "Checking for existing container."
|
||||
pct config $VMID
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "Existing container $VMID found. Run this script with --force to overwrite the existing container."
|
||||
echo "This will wipe all existing data. Clone the existing container to retain the data, then reassign the owner of the scrypted volume after installation is complete."
|
||||
echo ""
|
||||
echo "bash $0 --force"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
pct restore $VMID $SCRYPTED_TAR_ZST $@
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "The Scrypted container installation failed (pct restore error)."
|
||||
echo ""
|
||||
echo "This may be because the server's 'local' storage device is not being a valid"
|
||||
echo "location for containers."
|
||||
echo "Try running this script again with a different storage device like"
|
||||
echo "'local-lvm' or 'local-zfs'."
|
||||
echo ""
|
||||
echo "#############################################################################"
|
||||
echo "Paste the following command into this shell to install to local-lvm instead:"
|
||||
echo ""
|
||||
echo "bash $0 --storage local-lvm"
|
||||
echo "#############################################################################"
|
||||
echo ""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "pct set network failed"
|
||||
echo ""
|
||||
echo "Ignoring... Please verify your container's network settings."
|
||||
fi
|
||||
|
||||
CONF=/etc/pve/lxc/$VMID.conf
|
||||
if [ -f "$CONF" ]
|
||||
then
|
||||
echo "onboot: 1" >> $CONF
|
||||
else
|
||||
echo "$CONF not found? Start on boot must be enabled manually."
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
readyn "Running this script will reset Scrypted to a factory state while preserving existing data. IT IS RECOMMENDED TO CREATE A BACKUP FIRST. Are you sure you want to continue?"
|
||||
if [ "$yn" != "y" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Preparing rootfs reset..."
|
||||
# this copies the
|
||||
pct set 10444 --delete mp0 && pct set 10444 --delete unused0 && pct move-volume $RESTORE_VMID mp0 --target-vmid 10444 --target-volume mp0
|
||||
|
||||
rm *.tar
|
||||
vzdump 10444 --dumpdir /tmp
|
||||
VMID=$RESTORE_VMID
|
||||
echo "Moving data volume to backup..."
|
||||
pct restore $VMID *.tar $@
|
||||
|
||||
pct destroy 10444
|
||||
fi
|
||||
|
||||
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
|
||||
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"renderD128\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"card0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'KERNEL==\"accel0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
fi
|
||||
|
||||
echo "Scrypted setup is complete and the container resources can be started."
|
||||
echo "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."
|
||||
24
install/proxmox/docker-compose.sh
Normal file
24
install/proxmox/docker-compose.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
cd /root/.scrypted
|
||||
|
||||
# always immediately upgrade everything in case there's a broken update.
|
||||
# this will also be preferable for troubleshooting via lxc reboot.
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
(apt -y --fix-broken install && (yes | dpkg --configure -a) && apt -y update && apt -y dist-upgrade) &
|
||||
|
||||
# foreground pull if requested.
|
||||
if [ -e "volume/.pull" ]
|
||||
then
|
||||
rm -rf volume/.pull
|
||||
PULL="--pull"
|
||||
(sleep 300 && docker container prune -f && docker image prune -a -f) &
|
||||
else
|
||||
# always background pull in case there's a broken image.
|
||||
(sleep 300 && docker compose pull && docker container prune -f && docker image prune -a -f) &
|
||||
fi
|
||||
|
||||
# do not daemonize, when it exits, systemd will restart it.
|
||||
# force a recreate as .env may have changed.
|
||||
# furthermore force recreate gets the container back into a known state
|
||||
# which is preferable in case the user has made manual changes and then restarts.
|
||||
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum | head -c 32) docker compose up --force-recreate --abort-on-container-exit $PULL
|
||||
269
install/proxmox/install-scrypted-proxmox.sh
Normal file
269
install/proxmox/install-scrypted-proxmox.sh
Normal file
@@ -0,0 +1,269 @@
|
||||
PCT=$(which pct)
|
||||
if [ -z "$PCT" ]
|
||||
then
|
||||
echo "pct command not found. This script must be run on the Proxmox host, not a container."
|
||||
echo "Installation Documentation: https://docs.scrypted.app/installation.html#proxmox-ve"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function readyn() {
|
||||
while true; do
|
||||
read -p "$1 (y/n) " yn
|
||||
case $yn in
|
||||
[Yy]* ) break;;
|
||||
[Nn]* ) break;;
|
||||
* ) echo "Please answer yes or no. (y/n)";;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
cd /tmp
|
||||
SCRYPTED_VERSION=v0.120.0
|
||||
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
VMID=10443
|
||||
fi
|
||||
|
||||
SCRYPTED_BACKUP_VMID=10445
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
pct config $VMID 2>&1 > /dev/null
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "VMID $VMID not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# append existing mac address.
|
||||
HWADDR=",hwaddr=$(pct config $VMID | grep -oE 'hwaddr=[A-Z0-9:]+' | cut -d '=' -f 2)"
|
||||
RESTORE_HOSTNAME=$(pct config $VMID | grep -oE 'hostname: [^[:space:]]+' | cut -d ':' -f 2- | tr -d ' ')
|
||||
|
||||
pct destroy $SCRYPTED_BACKUP_VMID 2>&1 > /dev/null
|
||||
RESTORE_VMID=$VMID
|
||||
VMID=$SCRYPTED_BACKUP_VMID
|
||||
pct destroy $VMID 2>&1 > /dev/null
|
||||
fi
|
||||
|
||||
echo "Downloading scrypted container backup."
|
||||
if [ ! -f "$SCRYPTED_TAR_ZST" ]
|
||||
then
|
||||
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
|
||||
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
|
||||
fi
|
||||
|
||||
if [[ "$@" =~ "--force" ]]
|
||||
then
|
||||
IGNORE_EXISTING=true
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
IGNORE_EXISTING=true
|
||||
fi
|
||||
|
||||
if [ -z "$IGNORE_EXISTING" ]
|
||||
then
|
||||
echo "Checking for existing container."
|
||||
pct config $VMID
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "==============================================================="
|
||||
echo "Existing container $VMID found."
|
||||
echo "Please choose from the following options to resolve this error."
|
||||
echo "==============================================================="
|
||||
echo ""
|
||||
echo "1. To reinstall and reset Scrypted, run this script with --force to overwrite the existing container."
|
||||
echo "THIS WILL WIPE THE EXISTING CONFIGURATION:"
|
||||
echo ""
|
||||
echo "VMID=$VMID bash $0 --force"
|
||||
echo ""
|
||||
echo "2. To reinstall Scrypted and and retain existing configuration, run this script with the environment variable SCRYPTED_RESTORE=true."
|
||||
echo "This preserves existing data. Creating a backup within Scrypted is highly recommended in case the reset fails."
|
||||
echo "THIS WILL WIPE ADDITIONAL VOLUMES SUCH AS NVR STORAGE. NVR volumes will need to be readded after the restore:"
|
||||
echo ""
|
||||
echo "SCRYPTED_RESTORE=true VMID=$VMID bash $0"
|
||||
echo ""
|
||||
echo "3. To install and run multiple Scrypted containers, run this script with the environment variable specifying"
|
||||
echo "the new VMID=<number>. For example, to create a new LXC with VMID 12345:"
|
||||
echo ""
|
||||
echo "VMID=12345 bash $0"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
pct stop $VMID 2>&1 > /dev/null
|
||||
pct restore $VMID $SCRYPTED_TAR_ZST $@
|
||||
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "The Scrypted container installation failed (pct restore error)."
|
||||
echo ""
|
||||
echo "This may be because the server's 'local' storage device is not being a valid"
|
||||
echo "location for containers."
|
||||
echo "Try running this script again with a different storage device like"
|
||||
echo "'local-lvm' or 'local-zfs'."
|
||||
echo ""
|
||||
echo "#############################################################################"
|
||||
echo -e "\033[32mPaste the following command into this shell to install to local-lvm instead:\033[0m"
|
||||
echo ""
|
||||
echo "bash $0 --storage local-lvm"
|
||||
echo "#############################################################################"
|
||||
echo ""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto$HWADDR
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "pct set network failed"
|
||||
echo ""
|
||||
echo "Ignoring... Please verify your container's network settings."
|
||||
fi
|
||||
|
||||
if [ -n "$RESTORE_HOSTNAME" ]
|
||||
then
|
||||
pct set $VMID --hostname $RESTORE_HOSTNAME
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo ""
|
||||
echo "pct hostname restore failed"
|
||||
echo ""
|
||||
echo "Ignoring... Please verify your container's dns settings."
|
||||
fi
|
||||
fi
|
||||
|
||||
CONF=/etc/pve/lxc/$VMID.conf
|
||||
if [ -f "$CONF" ]
|
||||
then
|
||||
echo "onboot: 1" >> $CONF
|
||||
else
|
||||
echo "$CONF not found? Start on boot must be enabled manually."
|
||||
fi
|
||||
|
||||
if [ -n "$SCRYPTED_RESTORE" ]
|
||||
then
|
||||
echo ""
|
||||
echo ""
|
||||
echo "Running this script will reset the Scrypted container to a factory state while preserving existing data."
|
||||
echo "IT IS RECOMMENDED TO CREATE A BACKUP INSIDE SCRYPTED FIRST."
|
||||
readyn "Are you sure you want to continue?"
|
||||
if [ "$yn" != "y" ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Stopping scrypted..."
|
||||
pct stop $RESTORE_VMID 2>&1 > /dev/null
|
||||
|
||||
echo "Preparing rootfs reset..."
|
||||
|
||||
# remove the empty data volume from the downloaded image.
|
||||
pct set $SCRYPTED_BACKUP_VMID --delete mp0 && pct set $SCRYPTED_BACKUP_VMID --delete unused0
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "Failed to remove data volume from image."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# create a backup that contains only the root disk.
|
||||
rm *.tar
|
||||
vzdump $SCRYPTED_BACKUP_VMID --dumpdir /tmp
|
||||
|
||||
# this moves the data volume from the current scrypted instance to the backup target to preserve it during
|
||||
# the restore.
|
||||
pct move-volume $RESTORE_VMID mp0 --target-vmid $SCRYPTED_BACKUP_VMID --target-volume mp0
|
||||
if [ "$?" != "0" ]
|
||||
then
|
||||
echo "Failed to move data volume to backup."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# arguments: from to mp hide-warning
|
||||
function move_volume() {
|
||||
HAS_VOLUME=$(pct config $1 | grep $3:)
|
||||
if [ -n "$HAS_VOLUME" ]
|
||||
then
|
||||
echo "Moving $3..."
|
||||
# this may error and there may be recording loss. bailing at ths point is already too late.
|
||||
pct move-volume $1 $3 --target-vmid $2 --target-volume $3
|
||||
|
||||
# volume must be inside /mnt to get into docker container
|
||||
INSIDE_MNT=$(echo $HAS_VOLUME | grep /mnt)
|
||||
if [ -z "$INSIDE_MNT" -a -z "$4" ]
|
||||
then
|
||||
echo "##################################################################"
|
||||
echo "The following mount point is not visible to the"
|
||||
echo "Scrypted docker container within the LXC:"
|
||||
echo ""
|
||||
echo "$HAS_VOLUME"
|
||||
echo ""
|
||||
echo "This recordings directory will be unavailable."
|
||||
echo "The mount point must be updated to a path within /mnt."
|
||||
echo "https://docs.scrypted.app/scrypted-nvr/recording-storage.html#proxmox-ve-mount-point"
|
||||
echo "##################################################################"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# try moving 5 volumes, any more than that seems unlikely
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp1 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp2 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp3 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp4 hide-warning
|
||||
move_volume $RESTORE_VMID $SCRYPTED_BACKUP_VMID mp5 hide-warning
|
||||
|
||||
VMID=$RESTORE_VMID
|
||||
echo "Restoring with reset image..."
|
||||
pct restore --force 1 $VMID *.tar $@
|
||||
|
||||
echo "Restoring volumes..."
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp0 hide-warning
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp1
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp2
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp3
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp4
|
||||
move_volume $SCRYPTED_BACKUP_VMID $VMID mp5
|
||||
|
||||
pct destroy $SCRYPTED_BACKUP_VMID
|
||||
fi
|
||||
|
||||
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"drm\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"accel\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
fi
|
||||
|
||||
# check if intel
|
||||
INTEL=$(cat /proc/cpuinfo | grep GenuineIntel)
|
||||
if [ ! -z "$INTEL" ]
|
||||
then
|
||||
readyn "Install intel-microcode package? This will update your CPU and GPU firmware."
|
||||
if [ "$yn" == "y" ]
|
||||
then
|
||||
echo "Installing intel-microcode..."
|
||||
# remove it first to allow reinsertion
|
||||
sed -i 's/main contrib non-free-firmware/main/g' /etc/apt/sources.list
|
||||
sed -i 's/main/main contrib non-free-firmware/g' /etc/apt/sources.list
|
||||
apt update
|
||||
apt install -y intel-microcode
|
||||
echo "#############################"
|
||||
echo "System Reboot is recommended."
|
||||
echo "#############################"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Scrypted setup is complete and the container resources can be started."
|
||||
echo ""
|
||||
echo "Scrypted NVR servers should run the disk setup script in the documentation to add storage prior to starting the container."
|
||||
BIN
install/proxmox/lxc.png
Normal file
BIN
install/proxmox/lxc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
63
install/proxmox/setup-scrypted-nvr-volume.sh
Normal file
63
install/proxmox/setup-scrypted-nvr-volume.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
NVR_STORAGE=$1
|
||||
|
||||
DISK_TYPE="large"
|
||||
if [ ! -z "$FAST_DISK" ]
|
||||
then
|
||||
DISK_TYPE="fast"
|
||||
fi
|
||||
|
||||
if [ -z "$NVR_STORAGE" ]; then
|
||||
echo ""
|
||||
echo "Error: Proxmox Directory Disk not provided. Usage:"
|
||||
echo ""
|
||||
echo "bash $0 <proxmox-directory-disk>"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$VMID" ]
|
||||
then
|
||||
VMID="10443"
|
||||
fi
|
||||
FILE="/etc/pve/lxc/$VMID.conf"
|
||||
|
||||
# valdiate file exists
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "Error: $FILE not found."
|
||||
echo "If the Scrypted container id is not 10443, please set the VMID environment variable prior to running this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STORAGE="/mnt/pve/$NVR_STORAGE"
|
||||
|
||||
if [ ! -d "$STORAGE" ]
|
||||
then
|
||||
echo "Error: $STORAGE not found."
|
||||
echo "The Proxmox Directory Storage must be created using the UI prior to running this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# use subdirectory doesn't conflict with Proxmox storage of backups etc.
|
||||
STORAGE="$STORAGE/mounts/scrypted-nvr"
|
||||
# create the hidden folder that can be used as a marker.
|
||||
mkdir -p $STORAGE/.nvr
|
||||
chmod 0777 $STORAGE
|
||||
|
||||
echo "Stopping Scrypted..."
|
||||
pct stop "$VMID"
|
||||
|
||||
echo "Modifying $FILE."
|
||||
|
||||
if [ -z "$ADD_DISK" ]
|
||||
then
|
||||
echo "Removing previous $DISK_TYPE lxc.mount.entry."
|
||||
sed -i "/mnt\/nvr\/$DISK_TYPE/d" "$FILE"
|
||||
fi
|
||||
|
||||
echo "Adding new $DISK_TYPE lxc.mount.entry."
|
||||
echo "lxc.mount.entry: $STORAGE mnt/nvr/$DISK_TYPE/$NVR_STORAGE none bind,optional,create=dir" >> "$FILE"
|
||||
|
||||
echo "Starting Scrypted..."
|
||||
pct start $VMID
|
||||
157
packages/client/package-lock.json
generated
157
packages/client/package-lock.json
generated
@@ -9,16 +9,16 @@
|
||||
"version": "1.3.6",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.3.40",
|
||||
"engine.io-client": "^6.5.3",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"rimraf": "^5.0.5"
|
||||
"@scrypted/types": "^0.3.60",
|
||||
"engine.io-client": "^6.6.1",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"rimraf": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/node": "^22.7.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3"
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -74,19 +74,10 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.3.40",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.40.tgz",
|
||||
"integrity": "sha512-NBjNEfoLp7zL5Tf0odzf191oReDh4FEmZexDmMj1JbKDUMB9S8xJys3vbhcFadU/aUrUkyK/FSbkXv1z87bxSw=="
|
||||
"version": "0.3.60",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.60.tgz",
|
||||
"integrity": "sha512-oapFYQvyHLp0odCSx//USNnGNegS9ZL6a1HFIZzjDdMj2MNszTqiucAcu/wAlBwqjgURlP4/8xeLGVHEa4S2uQ=="
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
@@ -127,12 +118,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
|
||||
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
@@ -157,9 +148,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -268,15 +259,15 @@
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.0.tgz",
|
||||
"integrity": "sha512-iBtCdW5Tk3CnMAnC44VO4LwxXnl+RIq9ua1qHvxf5KSq2rzFgQFdfCSSl6Yuz2hl899SWTkfaT3c+WZQ42dJ8A==",
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
|
||||
"integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
"xmlhttprequest-ssl": "~2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
@@ -288,9 +279,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -307,9 +298,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
|
||||
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"signal-exit": "^4.0.1"
|
||||
@@ -322,21 +313,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.3.10",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
|
||||
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
|
||||
"integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.3.5",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
|
||||
"path-scurry": "^1.10.1"
|
||||
"jackspeak": "^4.0.1",
|
||||
"minimatch": "^10.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -356,28 +348,25 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
|
||||
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
|
||||
"integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz",
|
||||
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
|
||||
"integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==",
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
@@ -387,23 +376,23 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
|
||||
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
|
||||
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
@@ -413,6 +402,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -422,32 +416,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
|
||||
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
|
||||
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^9.1.1 || ^10.0.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
|
||||
"integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz",
|
||||
"integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==",
|
||||
"dependencies": {
|
||||
"glob": "^10.3.7"
|
||||
"glob": "^11.0.0",
|
||||
"package-json-from-dist": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -615,9 +610,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
|
||||
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -628,9 +623,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
@@ -758,9 +753,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
|
||||
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.3",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/node": "^22.7.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.3"
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.3.40",
|
||||
"engine.io-client": "^6.5.3",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"rimraf": "^5.0.5"
|
||||
"@scrypted/types": "^0.3.60",
|
||||
"engine.io-client": "^6.6.1",
|
||||
"follow-redirects": "^1.15.9",
|
||||
"rimraf": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ export async function loginScryptedClient(options: ScryptedLoginOptions) {
|
||||
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
|
||||
directAddress: response.headers.get('x-scrypted-direct-address'),
|
||||
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
|
||||
serverId: response.headers.get('x-scrypted-server-id'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -213,6 +214,7 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
|
||||
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
|
||||
directAddress: response.headers.get('x-scrypted-direct-address'),
|
||||
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
|
||||
serverId: response.headers.get('x-scrypted-server-id'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -227,6 +229,7 @@ export interface ScryptedClientLoginResult {
|
||||
directAddress: string;
|
||||
cloudAddress: string;
|
||||
hostname: string;
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
export class ScryptedClientLoginError extends Error {
|
||||
@@ -274,6 +277,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
let cloudAddress: string;
|
||||
let hostname: string;
|
||||
let token: string;
|
||||
let serverId: string;
|
||||
|
||||
console.log('@scrypted/client', packageJson.version);
|
||||
|
||||
@@ -299,6 +303,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
queryToken = loginResult.queryToken;
|
||||
token = loginResult.token;
|
||||
hostname = loginResult.hostname;
|
||||
serverId = loginResult.serverId;
|
||||
console.log('login result', Date.now() - start, loginResult);
|
||||
}
|
||||
else {
|
||||
@@ -372,6 +377,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
queryToken = loginCheck.queryToken;
|
||||
token = loginCheck.token;
|
||||
hostname = loginCheck.hostname;
|
||||
serverId = loginCheck.serverId;
|
||||
console.log('login checked', Date.now() - start, loginCheck);
|
||||
}
|
||||
|
||||
@@ -874,6 +880,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
authorization,
|
||||
cloudAddress,
|
||||
hostname,
|
||||
serverId,
|
||||
},
|
||||
connectRPCObject,
|
||||
fork: undefined,
|
||||
|
||||
5
plugins/alexa/package-lock.json
generated
5
plugins/alexa/package-lock.json
generated
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"uuid": "^9.0.0"
|
||||
@@ -203,4 +202,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
|
||||
@@ -53,6 +53,12 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
title: "Pairing Key",
|
||||
description: "The pairing key used to validate requests from Alexa. Clear this key or delete the plugin to allow pairing with a different Alexa login.",
|
||||
},
|
||||
disableAutoAdd: {
|
||||
title: "Disable auto add",
|
||||
description: "Disable automatic enablement of devices.",
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
accessToken: Promise<string>;
|
||||
@@ -116,6 +122,10 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
if (!supportedTypes.has(device.type))
|
||||
return DeviceMixinStatus.NotSupported;
|
||||
|
||||
if (this.storageSettings.values.disableAutoAdd) {
|
||||
return DeviceMixinStatus.Skip;
|
||||
}
|
||||
|
||||
mixins.push(this.id);
|
||||
|
||||
const plugins = await systemManager.getComponent('plugins');
|
||||
@@ -671,7 +681,8 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
|
||||
enum DeviceMixinStatus {
|
||||
NotSupported = 0,
|
||||
Setup = 1,
|
||||
AlreadySetup = 2
|
||||
AlreadySetup = 2,
|
||||
Skip = 3,
|
||||
}
|
||||
|
||||
class HttpResponseLoggingImpl implements AlexaHttpResponse {
|
||||
|
||||
4
plugins/amcrest/package-lock.json
generated
4
plugins/amcrest/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.162",
|
||||
"version": "0.0.164",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.162",
|
||||
"version": "0.0.164",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.162",
|
||||
"version": "0.0.164",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -199,6 +199,14 @@ export class AmcrestCameraClient {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async setWatermark(cameraNumber: number, enable: boolean) {
|
||||
const response = await this.request({
|
||||
url: `http://${this.ip}/cgi-bin/configManager.cgi?action=setConfig&VideoWidget[${cameraNumber - 1}].PictureTitle.EncodeBlend=${enable}`,
|
||||
responseType: 'text',
|
||||
});
|
||||
return response.body;
|
||||
}
|
||||
|
||||
async listenEvents(): Promise<Destroyable> {
|
||||
const events = new EventEmitter();
|
||||
const url = `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[All]`;
|
||||
|
||||
@@ -9,12 +9,14 @@ export const amcrestAutoConfigureSettings: Setting = {
|
||||
};
|
||||
|
||||
export async function autoconfigureSettings(client: AmcrestCameraClient, cameraNumber: number) {
|
||||
await client.setWatermark(cameraNumber, false).catch(() => { });
|
||||
|
||||
const audioOptions: AudioStreamConfiguration = {
|
||||
codec: 'aac',
|
||||
sampleRate: 8000,
|
||||
};
|
||||
|
||||
await client.resetMotionDetection(cameraNumber);
|
||||
await client.resetMotionDetection(cameraNumber).catch(() => {});
|
||||
|
||||
return ac(
|
||||
() => client.getCodecs(cameraNumber),
|
||||
|
||||
4
plugins/cloud/package-lock.json
generated
4
plugins/cloud/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.2.37",
|
||||
"version": "0.2.47",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.2.37",
|
||||
"version": "0.2.47",
|
||||
"dependencies": {
|
||||
"@eneris/push-receiver": "^4.2.0",
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -53,5 +53,5 @@
|
||||
"@types/node": "^22.5.2",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"version": "0.2.37"
|
||||
"version": "0.2.47"
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const { deviceManager, endpointManager, systemManager } = sdk;
|
||||
|
||||
export const DEFAULT_SENDER_ID = '827888101440';
|
||||
const SCRYPTED_SERVER = localStorage.getItem('scrypted-server') || 'home.scrypted.app';
|
||||
const SCRYPTED_SERVER_PORT = 4001;
|
||||
|
||||
const SCRYPTED_CLOUD_MESSAGE_PATH = '/_punch/cloudmessage';
|
||||
|
||||
@@ -147,7 +148,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
type: 'boolean',
|
||||
description: 'Optional: Create a Cloudflare Tunnel to this server at a random domain name. Providing a Cloudflare token will allow usage of a custom domain name.',
|
||||
defaultValue: true,
|
||||
onPut: () => deviceManager.requestRestart(),
|
||||
},
|
||||
cloudflaredTunnelToken: {
|
||||
group: 'Cloudflare',
|
||||
@@ -258,6 +258,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.converters = [
|
||||
[ScryptedMimeTypes.LocalUrl, ScryptedMimeTypes.Url],
|
||||
[ScryptedMimeTypes.PushEndpoint, ScryptedMimeTypes.Url],
|
||||
['*/*', ScryptedMimeTypes.ServerId],
|
||||
];
|
||||
// legacy cleanup
|
||||
this.fromMimeType = undefined;
|
||||
@@ -266,10 +267,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
devices: [],
|
||||
});
|
||||
|
||||
this.storageSettings.settings.register.onPut = async () => {
|
||||
await this.sendRegistrationId(await this.manager.registrationId);
|
||||
}
|
||||
|
||||
this.storageSettings.settings.upnpStatus.onGet = async () => {
|
||||
return {
|
||||
hide: this.storageSettings.values.forwardingMode !== 'UPNP',
|
||||
@@ -313,11 +310,6 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
};
|
||||
|
||||
|
||||
this.storageSettings.settings.securePort.onPut = (ov, nv) => {
|
||||
if (ov && ov !== nv)
|
||||
this.log.a('Reload the Scrypted Cloud Plugin to apply the port change.');
|
||||
};
|
||||
|
||||
if (!this.storageSettings.values.certificate)
|
||||
this.storageSettings.values.certificate = createSelfSignedCertificate();
|
||||
|
||||
@@ -340,6 +332,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.refreshPortForward();
|
||||
}
|
||||
|
||||
// auto login from electron
|
||||
if (!this.storageSettings.values.token_info && process.env.SCRYPTED_CLOUD_TOKEN) {
|
||||
this.storageSettings.values.token_info = process.env.SCRYPTED_CLOUD_TOKEN;
|
||||
this.manager.registrationId.then(r => {
|
||||
@@ -366,6 +359,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async getCachedRegistrationId() {
|
||||
return this.manager.currentRegistrationId || this.storageSettings.values.lastPersistedRegistrationId;
|
||||
}
|
||||
|
||||
async updatePortForward(upnpPort: number) {
|
||||
this.storageSettings.values.upnpPort = upnpPort;
|
||||
|
||||
@@ -431,8 +428,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
if (this.storageSettings.values.lastPersistedUpnpPort !== upnpPort || ip !== this.storageSettings.values.lastPersistedIp) {
|
||||
this.console.log('Registering IP and Port', ip, upnpPort);
|
||||
|
||||
const registrationId = await this.manager.registrationId;
|
||||
const data = await this.sendRegistrationId(registrationId);
|
||||
const data = await this.sendRegistrationId(await this.getCachedRegistrationId());
|
||||
if (data?.error)
|
||||
return;
|
||||
if (ip !== 'localhost' && ip !== data.ip_address && ip !== this.cloudflareTunnelHost) {
|
||||
@@ -451,11 +447,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
public: true,
|
||||
});
|
||||
const url = new URL(`https://${SCRYPTED_SERVER}/_punch/curl`);
|
||||
let { upnp_port, hostname } = this.getAuthority();
|
||||
let { port, hostname } = this.getAuthority();
|
||||
// scrypted cloud will replace localhost with requesting ip
|
||||
if (!hostname)
|
||||
hostname = 'localhost';
|
||||
url.searchParams.set('url', `https://${hostname}:${upnp_port}${pluginPath}/testPortForward`);
|
||||
url.searchParams.set('url', `https://${hostname}:${port}${pluginPath}/testPortForward`);
|
||||
const response = await httpFetch({
|
||||
url: url.toString(),
|
||||
responseType: 'json',
|
||||
@@ -621,13 +617,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
if (!hostname) {
|
||||
return {
|
||||
upnp_port,
|
||||
port: upnp_port,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
upnp_port,
|
||||
port: upnp_port,
|
||||
hostname,
|
||||
}
|
||||
@@ -638,6 +632,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
const q = qsstringify({
|
||||
...authority,
|
||||
cloudflare_hostname: this.cloudflareTunnelHost,
|
||||
registration_id,
|
||||
server_id: this.storageSettings.values.serverId,
|
||||
server_name: this.storageSettings.values.serverName,
|
||||
@@ -677,7 +672,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
this.console.log('registered', response.body);
|
||||
this.storageSettings.values.lastPersistedRegistrationId = registration_id;
|
||||
this.storageSettings.values.lastPersistedUpnpPort = authority.upnp_port;
|
||||
this.storageSettings.values.lastPersistedUpnpPort = authority.port;
|
||||
return response.body;
|
||||
}
|
||||
catch (e) {
|
||||
@@ -729,10 +724,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
|
||||
async convertMedia(data: string | Buffer | any, fromMimeType: string, toMimeType: string, options?: MediaObjectOptions): Promise<MediaObject | Buffer | any> {
|
||||
if (!toMimeType.startsWith(ScryptedMimeTypes.Url))
|
||||
throw new Error('unsupported cloud url conversion');
|
||||
|
||||
if (fromMimeType.startsWith(ScryptedMimeTypes.LocalUrl)) {
|
||||
if (toMimeType.startsWith(ScryptedMimeTypes.Url) && fromMimeType.startsWith(ScryptedMimeTypes.LocalUrl)) {
|
||||
// if cloudflare is enabled and the plugin isn't set up as a custom domain, try to use the cloudflare url for
|
||||
// short lived urls.
|
||||
if (this.cloudflareTunnel && this.storageSettings.values.forwardingMode !== 'Custom Domain') {
|
||||
@@ -746,7 +738,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
return this.whitelist(data.toString(), 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}`);
|
||||
}
|
||||
else if (fromMimeType.startsWith(ScryptedMimeTypes.PushEndpoint)) {
|
||||
else if (toMimeType.startsWith(ScryptedMimeTypes.Url) && fromMimeType.startsWith(ScryptedMimeTypes.PushEndpoint)) {
|
||||
const validDomain = this.getSSLHostname();
|
||||
if (validDomain)
|
||||
return Buffer.from(`https://${validDomain}${await this.getCloudMessagePath()}/${data}`);
|
||||
@@ -754,6 +746,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const url = `http://127.0.0.1/push/${data}`;
|
||||
return this.whitelist(url, 10 * 365 * 24 * 60 * 60 * 1000, `https://${this.getHostname()}${SCRYPTED_CLOUD_MESSAGE_PATH}`);
|
||||
}
|
||||
else if (toMimeType === ScryptedMimeTypes.ServerId) {
|
||||
return this.storageSettings.values.serverId;
|
||||
}
|
||||
|
||||
throw new Error('unsupported cloud url conversion');
|
||||
}
|
||||
@@ -792,7 +787,8 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const args = qsstringify({
|
||||
...authority,
|
||||
|
||||
registration_id: await this.manager.registrationId,
|
||||
registration_id: await this.getCachedRegistrationId() || 'undefined',
|
||||
cloudflare_hostname: this.cloudflareTunnelHost,
|
||||
registration_secret: this.storageSettings.values.registrationSecret,
|
||||
server_id: this.storageSettings.values.serverId,
|
||||
server_name: this.storageSettings.values.serverName,
|
||||
@@ -833,7 +829,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const query = qsparse(url.searchParams);
|
||||
if (!query.callback_url && query.token_info && query.user_info) {
|
||||
this.storageSettings.values.token_info = query.token_info;
|
||||
this.storageSettings.values.lastPersistedRegistrationId = await this.manager.registrationId;
|
||||
this.storageSettings.values.lastPersistedRegistrationId = await this.getCachedRegistrationId();
|
||||
res.setHeader('Location', `https://${this.getHostname()}/endpoint/@scrypted/core/public/`);
|
||||
res.writeHead(302);
|
||||
res.end();
|
||||
@@ -844,6 +840,17 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (url.pathname === '/_punch/callback') {
|
||||
const query = qsparse(url.searchParams);
|
||||
if (query.registration_secret === this.storageSettings.values.registrationSecret) {
|
||||
res.writeHead(200);
|
||||
this.serverCallback(port, SCRYPTED_SERVER_PORT, SCRYPTED_SERVER);
|
||||
}
|
||||
else {
|
||||
res.writeHead(401);
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
else if (url.pathname === '/web/') {
|
||||
const validDomain = this.getSSLHostname();
|
||||
if (validDomain) {
|
||||
@@ -890,6 +897,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
const port = (this.server.address() as any).port;
|
||||
this.console.log('scrypted cloud server listening on', port);
|
||||
|
||||
this.storageSettings.settings.cloudflareEnabled.onPut = () => {
|
||||
this.cloudflared?.child.kill();
|
||||
this.startCloudflared(port);
|
||||
};
|
||||
|
||||
const agent = new http.Agent({ maxSockets: Number.MAX_VALUE, keepAlive: true });
|
||||
this.proxy = HttpProxy.createProxy({
|
||||
agent,
|
||||
@@ -900,11 +912,12 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.proxy.on('proxyRes', (res, req) => {
|
||||
res.headers['X-Scrypted-Cloud'] = req.headers['x-scrypted-cloud'];
|
||||
res.headers['X-Scrypted-Direct-Address'] = req.headers['x-scrypted-direct-address'];
|
||||
res.headers['X-Scrypted-Server-Id'] = this.storageSettings.values.serverId;
|
||||
let domain = this.cloudflareTunnel;
|
||||
if (!domain && this.storageSettings.values.forwardingMode === 'Custom Domain' && this.storageSettings.values.hostname)
|
||||
domain = `https://${this.storageSettings.values.hostname}`;
|
||||
res.headers['X-Scrypted-Cloud-Address'] = domain;
|
||||
res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address, X-Scrypted-Cloud-Address';
|
||||
res.headers['Access-Control-Expose-Headers'] = 'X-Scrypted-Cloud, X-Scrypted-Direct-Address, X-Scrypted-Cloud-Address, X-Scrypted-Server-Id';
|
||||
});
|
||||
|
||||
let backoff = 0;
|
||||
@@ -927,42 +940,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
if (Date.now() < backoff + 5000)
|
||||
return;
|
||||
backoff = Date.now();
|
||||
const random = Math.random().toString(36).substring(2);
|
||||
this.console.log('scrypted server requested a connection:', random);
|
||||
|
||||
const registrationId = await this.manager.registrationId;
|
||||
|
||||
const { address } = message;
|
||||
const [serverHost, serverPort] = address?.split(':') || [SCRYPTED_SERVER, 4001];
|
||||
|
||||
this.ensureReverseConnections(registrationId, serverPort, serverHost);
|
||||
|
||||
const client = tls.connect(serverPort, serverHost, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
client.on('close', () => this.console.log('scrypted server connection ended:', random));
|
||||
client.write(registrationId + '\n');
|
||||
const mux: any = new bpmux.BPMux(client as any);
|
||||
mux.on('handshake', async (socket: Duplex) => {
|
||||
this.ensureReverseConnections(registrationId, serverPort, serverHost);
|
||||
|
||||
this.console.warn('mux connection required');
|
||||
|
||||
let local: any;
|
||||
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
local = net.connect({
|
||||
port,
|
||||
host: '127.0.0.1',
|
||||
});
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
socket.pipe(local).pipe(socket);
|
||||
});
|
||||
mux.on('error', () => {
|
||||
client.destroy();
|
||||
});
|
||||
const [serverHost, serverPort] = address?.split(':') || [SCRYPTED_SERVER, SCRYPTED_SERVER_PORT];
|
||||
this.serverCallback(port, Number(serverPort), serverHost);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -989,6 +969,40 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
}
|
||||
|
||||
serverCallback(port: number, serverPort: number, serverHost: string) {
|
||||
const random = Math.random().toString(36).substring(2);
|
||||
this.console.log('scrypted server requested a connection:', random);
|
||||
|
||||
this.ensureReverseConnections(serverPort, serverHost);
|
||||
|
||||
const client = tls.connect(serverPort, serverHost, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
client.on('close', () => this.console.log('scrypted server connection ended:', random));
|
||||
client.write(this.serverIdentifier + '\n');
|
||||
const mux: any = new bpmux.BPMux(client as any);
|
||||
mux.on('handshake', async (socket: Duplex) => {
|
||||
this.ensureReverseConnections(serverPort, serverHost);
|
||||
|
||||
this.console.warn('mux connection required');
|
||||
|
||||
let local: any;
|
||||
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
local = net.connect({
|
||||
port,
|
||||
host: '127.0.0.1',
|
||||
});
|
||||
await new Promise(resolve => process.nextTick(resolve));
|
||||
|
||||
socket.pipe(local).pipe(socket);
|
||||
});
|
||||
mux.on('error', () => {
|
||||
client.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
async startCloudflared(quickTunnelPort: number) {
|
||||
while (true) {
|
||||
try {
|
||||
@@ -1004,6 +1018,7 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
if (this.storageSettings.values.cloudflaredTunnelCredentials && this.storageSettings.values.cloudflaredTunnelCustomDomain) {
|
||||
const tunnelUrl = `http://127.0.0.1:${quickTunnelPort}`;
|
||||
const url = this.cloudflareTunnel = `https://${this.storageSettings.values.cloudflaredTunnelCustomDomain}`;
|
||||
this.updateExternalAddresses();
|
||||
this.console.log(`cloudflare url mapped ${this.cloudflareTunnel} to ${tunnelUrl}`);
|
||||
|
||||
const ret = await runLocallyManagedTunnel(this.storageSettings.values.cloudflaredTunnelCredentials, tunnelUrl, cloudflareD, bin);
|
||||
@@ -1038,7 +1053,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
const lines = string.split('\n');
|
||||
for (const line of lines) {
|
||||
if ((line.includes('Unregistered tunnel connection') || line.includes('Register tunnel error'))
|
||||
if ((line.includes('Unregistered tunnel connection')
|
||||
|| line.includes('Connection terminated error')
|
||||
|| line.includes('Register tunnel error')
|
||||
|| line.includes('Failed to get tunnel'))
|
||||
&& deferred.finished) {
|
||||
this.console.warn('Cloudflare registration failed after tunnel started. The old tunnel may be invalid. Terminating.');
|
||||
cloudflareTunnel.child.kill();
|
||||
@@ -1113,13 +1131,18 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
}
|
||||
}
|
||||
|
||||
ensureReverseConnections(registrationId: string, serverPort: number, serverHost: string) {
|
||||
get serverIdentifier() {
|
||||
const serverIdentifier = `${this.storageSettings.values.registrationSecret}@${this.storageSettings.values.serverId}`;
|
||||
return serverIdentifier;
|
||||
}
|
||||
|
||||
ensureReverseConnections(serverPort: number, serverHost: string) {
|
||||
while (this.reverseConnections.size < 10) {
|
||||
this.createReverseConnection(registrationId, serverPort, serverHost);
|
||||
this.createReverseConnection(serverPort, serverHost);
|
||||
}
|
||||
}
|
||||
|
||||
async createReverseConnection(registrationId: string, serverPort: number, serverHost: string) {
|
||||
async createReverseConnection(serverPort: number, serverHost: string) {
|
||||
const client = tls.connect(serverPort, serverHost, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
@@ -1131,9 +1154,10 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
this.reverseConnections.delete(client);
|
||||
|
||||
if (claimed)
|
||||
this.ensureReverseConnections(registrationId, serverPort, serverHost);
|
||||
this.ensureReverseConnections(serverPort, serverHost);
|
||||
});
|
||||
client.write(`reverse:${registrationId}\n`);
|
||||
|
||||
client.write(`reverse:${this.serverIdentifier}\n`);
|
||||
|
||||
try {
|
||||
const read = await readLine(client);
|
||||
|
||||
@@ -10,6 +10,7 @@ export declare interface PushManager {
|
||||
|
||||
export class PushManager extends EventEmitter {
|
||||
registrationId: Promise<string>;
|
||||
currentRegistrationId: string;
|
||||
|
||||
constructor(public senderId: string) {
|
||||
super();
|
||||
@@ -47,11 +48,12 @@ export class PushManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const stopListeningToCredentials = instance.onCredentialsChanged(({ oldCredentials, newCredentials }) => {
|
||||
this.currentRegistrationId = newCredentials.fcm.token;
|
||||
savedConfig.credentials = newCredentials;
|
||||
deferred.resolve(newCredentials.fcm.token);
|
||||
this.registrationId = Promise.resolve(newCredentials.fcm.token);
|
||||
deferred.resolve(this.currentRegistrationId);
|
||||
this.registrationId = Promise.resolve( this.currentRegistrationId);
|
||||
saveConfig();
|
||||
this.emit('registrationId', newCredentials.fcm.token);
|
||||
this.emit('registrationId', this.currentRegistrationId);
|
||||
});
|
||||
|
||||
const stopListeningToNotifications = instance.onNotification(({ message }) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export function qsstringify(dict: any) {
|
||||
const params = new URLSearchParams();
|
||||
for (const [k, v] of Object.entries(dict)) {
|
||||
if (v == null)
|
||||
continue;
|
||||
params.set(k, v?.toString());
|
||||
}
|
||||
|
||||
|
||||
2
plugins/core/.vscode/settings.json
vendored
2
plugins/core/.vscode/settings.json
vendored
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
167
plugins/core/package-lock.json
generated
167
plugins/core/package-lock.json
generated
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.68",
|
||||
"version": "0.3.82",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.68",
|
||||
"version": "0.3.82",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"mime": "^3.0.0",
|
||||
"mime": "^4.0.4",
|
||||
"node-pty": "^1.0.0",
|
||||
"router": "^1.3.8",
|
||||
"typescript": "^5.4.2"
|
||||
"typescript": "^5.6.2",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node": "^20.11.26"
|
||||
"@types/node": "^22.7.4"
|
||||
}
|
||||
},
|
||||
"../../../sdk": {
|
||||
@@ -88,23 +88,22 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.45",
|
||||
"version": "0.3.63",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"rimraf": "^6.0.1",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
@@ -116,11 +115,11 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
@@ -131,19 +130,13 @@
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
|
||||
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz",
|
||||
"integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==",
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
@@ -160,14 +153,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
|
||||
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa"
|
||||
],
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
"mime": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
@@ -195,7 +191,7 @@
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "1.3.8",
|
||||
@@ -233,9 +229,9 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
|
||||
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -245,9 +241,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
@@ -257,6 +253,17 @@
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -274,40 +281,33 @@
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
|
||||
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "20.11.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz",
|
||||
"integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==",
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~5.26.4"
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
@@ -321,9 +321,9 @@
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
|
||||
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.20.0",
|
||||
@@ -346,7 +346,7 @@
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"router": {
|
||||
"version": "1.3.8",
|
||||
@@ -383,20 +383,25 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
|
||||
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ=="
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.68",
|
||||
"version": "0.3.82",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
@@ -42,13 +42,13 @@
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"mime": "^3.0.0",
|
||||
"mime": "^4.0.4",
|
||||
"node-pty": "^1.0.0",
|
||||
"router": "^1.3.8",
|
||||
"typescript": "^5.4.2"
|
||||
"typescript": "^5.6.2",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime": "^3.0.4",
|
||||
"@types/node": "^20.11.26"
|
||||
"@types/node": "^22.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import sdk, { BufferConverter, HttpRequest, HttpRequestHandler, HttpResponse, HttpResponseOptions, MediaObject, RequestMediaObject, ScryptedDeviceBase, ScryptedMimeTypes } from "@scrypted/sdk";
|
||||
import crypto from 'crypto';
|
||||
import mime from "mime/lite";
|
||||
import path from 'path';
|
||||
|
||||
const { endpointManager } = sdk;
|
||||
@@ -47,18 +46,21 @@ export class BufferHost extends ScryptedDeviceBase implements HttpRequestHandler
|
||||
options.headers['Content-Disposition'] = 'attachment';
|
||||
}
|
||||
|
||||
response.send(file.data as Buffer, );
|
||||
response.send(file.data as Buffer,);
|
||||
}
|
||||
|
||||
async convert(buffer: string, fromMimeType: string, toMimeType: string): Promise<Buffer> {
|
||||
const uuid = uuidv4();
|
||||
|
||||
const { default: mime } = await import('mime');
|
||||
|
||||
const endpoint = await (this.secure ? endpointManager.getPublicLocalEndpoint(this.nativeId) : endpointManager.getInsecurePublicLocalEndpoint(this.nativeId));
|
||||
const extension = mime.getExtension(fromMimeType);
|
||||
|
||||
const filename = uuid + (extension ? `.${extension}` : '');
|
||||
|
||||
this.hosted.set(`/${filename}`, { data: buffer, fromMimeType, toMimeType });
|
||||
setTimeout(() => this.hosted.delete(`/${filename}`), 10 * 60 * 1000); // free this resource after 10 min.
|
||||
|
||||
return Buffer.from(`${endpoint}${filename}`);
|
||||
}
|
||||
@@ -131,6 +133,7 @@ export class RequestMediaObjectHost extends ScryptedDeviceBase implements HttpRe
|
||||
const endpoint = await (toMimeType === ScryptedMimeTypes.LocalUrl ? endpointManager.getPublicLocalEndpoint(this.nativeId) : endpointManager.getInsecurePublicLocalEndpoint(this.nativeId));
|
||||
const data = await request();
|
||||
fromMimeType = data.mimeType;
|
||||
const { default: mime } = await import('mime');
|
||||
const extension = mime.getExtension(fromMimeType);
|
||||
|
||||
const filename = uuid + (extension ? `.${extension}` : '');
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { readFileAsString, tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
|
||||
import sdk, { DeviceProvider, HttpRequest, HttpRequestHandler, HttpResponse, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from '@scrypted/sdk';
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
import { writeFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import Router from 'router';
|
||||
import yaml from 'yaml';
|
||||
import { getUsableNetworkAddresses } from '../../../server/src/ip';
|
||||
import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
|
||||
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
|
||||
@@ -48,7 +51,41 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
service.setLocalAddresses(this.localAddresses);
|
||||
},
|
||||
}
|
||||
},
|
||||
releaseChannel: {
|
||||
group: 'Advanced',
|
||||
title: 'Server Release Channel',
|
||||
description: 'The release channel to use for server updates. A specific version or tag can be manually entered as well. Changing this setting will update the image field in /root/.scrypted/docker-compose.yml. Invalid values may prevent the server from properly starting.',
|
||||
defaultValue: 'Default',
|
||||
choices: [
|
||||
'Default',
|
||||
'latest',
|
||||
'beta',
|
||||
`v${sdk.serverVersion}-jammy-full`,
|
||||
],
|
||||
combobox: true,
|
||||
onPut: (ov, nv) => {
|
||||
this.updateReleaseChannel(nv);
|
||||
},
|
||||
mapGet: () => {
|
||||
try {
|
||||
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
|
||||
// @ts-ignore
|
||||
const image: string = dockerCompose.contents.get('services').get('scrypted').get('image');
|
||||
const label = image.split(':')[1] || undefined;
|
||||
return label || 'Default';
|
||||
}
|
||||
catch (e) {
|
||||
return 'Default';
|
||||
}
|
||||
}
|
||||
},
|
||||
pullImage: {
|
||||
hide: true,
|
||||
onPut: () => {
|
||||
this.setPullImage();
|
||||
}
|
||||
},
|
||||
});
|
||||
indexHtml: string;
|
||||
|
||||
@@ -61,6 +98,8 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
|
||||
|
||||
checkLxcDependencies();
|
||||
|
||||
this.storageSettings.settings.releaseChannel.hide = process.env.SCRYPTED_INSTALL_ENVIRONMENT !== 'lxc-docker';
|
||||
|
||||
this.indexHtml = readFileAsString('dist/index.html');
|
||||
|
||||
(async () => {
|
||||
@@ -248,6 +287,27 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Dev
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setPullImage() {
|
||||
writeFileSync(path.join(process.env.SCRYPTED_VOLUME, '.pull'), '');
|
||||
}
|
||||
|
||||
async updateReleaseChannel(releaseChannel: string) {
|
||||
if (!releaseChannel || releaseChannel === 'Default')
|
||||
releaseChannel = '';
|
||||
else
|
||||
releaseChannel = `:${releaseChannel}`;
|
||||
const dockerCompose = yaml.parseDocument(readFileAsString('/root/.scrypted/docker-compose.yml'));
|
||||
// @ts-ignore
|
||||
dockerCompose.contents.get('services').get('scrypted').set('image', `ghcr.io/koush/scrypted${releaseChannel}`);
|
||||
yaml.stringify(dockerCompose);
|
||||
writeFileSync('/root/.scrypted/docker-compose.yml', yaml.stringify(dockerCompose));
|
||||
this.setPullImage();
|
||||
|
||||
const serviceControl = await sdk.systemManager.getComponent("service-control");
|
||||
await serviceControl.exit().catch(() => { });
|
||||
await serviceControl.restart();
|
||||
}
|
||||
}
|
||||
|
||||
export default ScryptedCore;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import child_process from 'child_process';
|
||||
import { once } from 'events';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import { stdout } from 'process';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
|
||||
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC = 'lxc';
|
||||
|
||||
@@ -42,12 +42,53 @@ export async function checkLxcDependencies() {
|
||||
sdk.log.a('Failed to daemon-reload systemd.');
|
||||
}
|
||||
|
||||
try {
|
||||
const output = await new Promise<string>((r, f) => child_process.exec("sh -c 'apt list --installed | grep level-zero/'", (err, stdout, stderr) => {
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
}));
|
||||
|
||||
const cpuModel = os.cpus()[0].model;
|
||||
if (cpuModel.includes('Core') && cpuModel.includes('Ultra')) {
|
||||
if (
|
||||
// apt
|
||||
!output.includes('level-zero/')
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install intel-driver-compiler-npu.');
|
||||
else
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// level-zero crashes openvino on older CPU due to illegal instruction.
|
||||
// so ensure it is not installed if this is not a core ultra system with npu.
|
||||
if (
|
||||
// apt
|
||||
output.includes('level-zero/')
|
||||
) {
|
||||
const cp = child_process.spawn('apt', ['-y', 'remove', 'level-zero']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
console.log('level-zero removed', exitCode);
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (e) {
|
||||
sdk.log.a('Failed to verify/install intel-driver-compiler-npu.');
|
||||
}
|
||||
|
||||
try {
|
||||
// intel opencl icd is broken from their official apt repos on kernel versions 6.8, which ships with ubuntu 24.04 and proxmox 8.2.
|
||||
// the intel apt repo has not been updated yet.
|
||||
// the current workaround is to install the release manually.
|
||||
// https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
|
||||
const output = await new Promise<string>((r, f) => child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
@@ -59,8 +100,10 @@ export async function checkLxcDependencies() {
|
||||
output.includes('Version: 23')
|
||||
// was installed via script at some point
|
||||
|| output.includes('Version: 24.13.29138.7')
|
||||
// current script version: 24.17.29377.6
|
||||
) {
|
||||
|| output.includes('Version: 24.26.30049.6')
|
||||
|| output.includes('Version: 24.31.30508.7')
|
||||
// current script version: 24.35.30872.22
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
@@ -73,30 +116,6 @@ export async function checkLxcDependencies() {
|
||||
sdk.log.a('Failed to verify/install intel-opencl-icd version.');
|
||||
}
|
||||
|
||||
try {
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-driver-compiler-npu'", (err, stdout, stderr) => {
|
||||
if (err && !stdout && !stderr)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
}));
|
||||
|
||||
if (
|
||||
// apt
|
||||
output.includes('No packages found')
|
||||
) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-npu.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install intel-driver-compiler-npu.');
|
||||
else
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
sdk.log.a('Failed to verify/install intel-driver-compiler-npu.');
|
||||
}
|
||||
|
||||
if (needRestart)
|
||||
sdk.log.a('A system update is pending. Please restart Scrypted to apply changes.');
|
||||
}
|
||||
|
||||
5
plugins/diagnostics/.gitignore
vendored
Normal file
5
plugins/diagnostics/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/
|
||||
.venv
|
||||
9
plugins/diagnostics/.npmignore
Normal file
9
plugins/diagnostics/.npmignore
Normal file
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
*.map
|
||||
fs
|
||||
src
|
||||
.vscode
|
||||
dist/*.js
|
||||
.venv
|
||||
23
plugins/diagnostics/.vscode/launch.json
vendored
Normal file
23
plugins/diagnostics/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"address": "${config:scrypted.debugHost}",
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"**/plugin-remote-worker.*",
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"sourceMaps": true,
|
||||
"localRoot": "${workspaceFolder}/out",
|
||||
"remoteRoot": "/plugin/",
|
||||
"type": "node"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugins/diagnostics/.vscode/settings.json
vendored
Normal file
4
plugins/diagnostics/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
20
plugins/diagnostics/.vscode/tasks.json
vendored
Normal file
20
plugins/diagnostics/.vscode/tasks.json
vendored
Normal 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}",
|
||||
},
|
||||
]
|
||||
}
|
||||
3
plugins/diagnostics/README.md
Normal file
3
plugins/diagnostics/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Scrypted Diagnostics Plugin
|
||||
|
||||
This plugin can be used to run diagnostics on the system and supported devices. The results from the diagnostics can be seen in the `Log`.
|
||||
848
plugins/diagnostics/package-lock.json
generated
Normal file
848
plugins/diagnostics/package-lock.json
generated
Normal file
@@ -0,0 +1,848 @@
|
||||
{
|
||||
"name": "@scrypted/diagnostics",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/diagnostics",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
},
|
||||
"version": "0.0.18"
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.62",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
"scrypted-deploy": "bin/scrypted-deploy.js",
|
||||
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
|
||||
"scrypted-package-json": "bin/scrypted-package-json.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
||||
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
|
||||
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
||||
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
|
||||
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
|
||||
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
|
||||
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.5",
|
||||
"@img/sharp-darwin-x64": "0.33.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||
"@img/sharp-linux-arm": "0.33.5",
|
||||
"@img/sharp-linux-arm64": "0.33.5",
|
||||
"@img/sharp-linux-s390x": "0.33.5",
|
||||
"@img/sharp-linux-x64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||
"@img/sharp-wasm32": "0.33.5",
|
||||
"@img/sharp-win32-ia32": "0.33.5",
|
||||
"@img/sharp-win32-x64": "0.33.5"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
"integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-darwin-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
||||
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
|
||||
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
|
||||
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
|
||||
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-linux-arm": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
||||
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-s390x": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
|
||||
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linux-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
|
||||
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
|
||||
}
|
||||
},
|
||||
"@img/sharp-wasm32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
|
||||
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@emnapi/runtime": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@img/sharp-win32-ia32": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
|
||||
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
|
||||
"optional": true
|
||||
},
|
||||
"@img/sharp-win32-x64": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
||||
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
||||
"optional": true
|
||||
},
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@types/node": "^20.11.0",
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "22.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||
},
|
||||
"sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
|
||||
"requires": {
|
||||
"@img/sharp-darwin-arm64": "0.33.5",
|
||||
"@img/sharp-darwin-x64": "0.33.5",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.5",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.4",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
|
||||
"@img/sharp-linux-arm": "0.33.5",
|
||||
"@img/sharp-linux-arm64": "0.33.5",
|
||||
"@img/sharp-linux-s390x": "0.33.5",
|
||||
"@img/sharp-linux-x64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.5",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
||||
"@img/sharp-wasm32": "0.33.5",
|
||||
"@img/sharp-win32-ia32": "0.33.5",
|
||||
"@img/sharp-win32-x64": "0.33.5",
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.3",
|
||||
"semver": "^7.6.3"
|
||||
}
|
||||
},
|
||||
"simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"requires": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"optional": true
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"version": "0.0.18"
|
||||
}
|
||||
37
plugins/diagnostics/package.json
Normal file
37
plugins/diagnostics/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@scrypted/diagnostics",
|
||||
"version": "0.0.18",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
"build": "scrypted-webpack",
|
||||
"prepublishOnly": "NODE_ENV=production scrypted-webpack",
|
||||
"prescrypted-vscode-launch": "scrypted-webpack",
|
||||
"scrypted-vscode-launch": "scrypted-deploy-debug",
|
||||
"scrypted-deploy-debug": "scrypted-deploy-debug",
|
||||
"scrypted-debug": "scrypted-debug",
|
||||
"scrypted-deploy": "scrypted-deploy",
|
||||
"scrypted-readme": "scrypted-readme",
|
||||
"scrypted-package-json": "scrypted-package-json"
|
||||
},
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"diagnostics"
|
||||
],
|
||||
"scrypted": {
|
||||
"name": "Diagnostics",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"Settings"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.4"
|
||||
}
|
||||
}
|
||||
544
plugins/diagnostics/src/main.ts
Normal file
544
plugins/diagnostics/src/main.ts
Normal file
@@ -0,0 +1,544 @@
|
||||
import { Deferred } from '@scrypted/common/src/deferred';
|
||||
import { safeKillFFmpeg } from '@scrypted/common/src/media-helpers';
|
||||
import sdk, { Camera, FFmpegInput, Image, MediaObject, MediaStreamDestination, MotionSensor, Notifier, ObjectDetection, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, VideoCamera } from '@scrypted/sdk';
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import child_process from 'child_process';
|
||||
import { once } from 'events';
|
||||
import fs from 'fs';
|
||||
import net from 'net';
|
||||
import os from 'os';
|
||||
import sharp from 'sharp';
|
||||
import { httpFetch } from '../../../server/src/fetch/http-fetch';
|
||||
class DiagnosticsPlugin extends ScryptedDeviceBase implements Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
testDevice: {
|
||||
group: 'Device',
|
||||
title: 'Validation Device',
|
||||
description: "Select a device to validate.",
|
||||
type: 'device',
|
||||
deviceFilter: `type === '${ScryptedDeviceType.Camera}' || type === '${ScryptedDeviceType.Doorbell}' || type === '${ScryptedDeviceType.Notifier}'`,
|
||||
immediate: true,
|
||||
},
|
||||
validateDevice: {
|
||||
console: true,
|
||||
group: 'Device',
|
||||
title: 'Validate Device',
|
||||
description: 'Validate the device configuration.',
|
||||
type: 'button',
|
||||
onPut: async () => {
|
||||
this.validateDevice();
|
||||
},
|
||||
},
|
||||
validateSystem: {
|
||||
console: true,
|
||||
group: 'System',
|
||||
title: 'Validate System',
|
||||
description: 'Validate the system configuration.',
|
||||
type: 'button',
|
||||
onPut: () => this.validateSystem(),
|
||||
},
|
||||
});
|
||||
|
||||
loggedMotion = new Map<string, number>();
|
||||
loggedButton = new Map<string, number>();
|
||||
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
this.on = this.on || false;
|
||||
|
||||
sdk.systemManager.listen((eventSource, eventDetails, eventData) => {
|
||||
if (!eventData || !eventSource?.id)
|
||||
return;
|
||||
|
||||
if (eventDetails.eventInterface === ScryptedInterface.MotionSensor) {
|
||||
this.loggedMotion.set(eventSource.id, Date.now());
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventDetails.eventInterface === ScryptedInterface.BinarySensor) {
|
||||
this.loggedButton.set(eventSource.id, Date.now());
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: any) {
|
||||
await this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
warnStep(console: Console, result: string) {
|
||||
console.log(''.padEnd(24), `\x1b[33m${result}\x1b[0m`);
|
||||
}
|
||||
|
||||
async validate(console: Console, stepName: string, step: Promise<any> | (() => Promise<any>)) {
|
||||
try {
|
||||
if (step instanceof Function)
|
||||
step = step();
|
||||
console.log(stepName.padEnd(24), '\x1b[34mRunning\x1b[0m');
|
||||
const result = await step;
|
||||
console.log(''.padEnd(24), `\x1b[32m${result || 'OK'}\x1b[0m`);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(stepName.padEnd(24), '\x1b[31m Failed\x1b[0m'.padEnd(24), (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async validateDevice() {
|
||||
const device = this.storageSettings.values.testDevice as ScryptedDevice & any;
|
||||
const console = sdk.deviceManager.getMixinConsole(device.id);
|
||||
|
||||
console.log(''.padEnd(44, '='));
|
||||
console.log(`Device Validation: ${device?.name}`);
|
||||
console.log(''.padEnd(44, '='));
|
||||
|
||||
await this.validate(console, 'Device Selected', async () => {
|
||||
if (!device)
|
||||
throw new Error('Select a device in the Settings UI.');
|
||||
});
|
||||
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
if (device.type === ScryptedDeviceType.Camera || device.type === ScryptedDeviceType.Doorbell) {
|
||||
await this.validateCamera(device);
|
||||
}
|
||||
else if (device.type === ScryptedDeviceType.Notifier) {
|
||||
await this.validateNotifier(device);
|
||||
}
|
||||
|
||||
console.log(''.padEnd(44, '='));
|
||||
console.log(`Device Validation Complete: ${device?.name}`);
|
||||
console.log(''.padEnd(44, '='));
|
||||
}
|
||||
|
||||
async validateNotifier(device: ScryptedDevice & Notifier) {
|
||||
const console = sdk.deviceManager.getMixinConsole(device.id);
|
||||
|
||||
await this.validate(console, 'Test Notification', async () => {
|
||||
const logo = await httpFetch({
|
||||
url: 'https://home.scrypted.app/_punch/web_hi_res_512.png',
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
const mo = await sdk.mediaManager.createMediaObject(logo.body, 'image/png');
|
||||
await device.sendNotification('Scrypted Diagnostics', {
|
||||
body: 'Body',
|
||||
subtitle: 'Subtitle',
|
||||
android: {
|
||||
channel: 'diagnostics',
|
||||
}
|
||||
}, mo);
|
||||
|
||||
this.warnStep(console, 'Check the device for the notification.');
|
||||
});
|
||||
}
|
||||
|
||||
async validateCamera(device: ScryptedDevice & Camera & VideoCamera & MotionSensor) {
|
||||
const console = sdk.deviceManager.getMixinConsole(device.id);
|
||||
|
||||
await this.validate(console, 'Device Capabilities', async () => {
|
||||
if (!device.interfaces.includes(ScryptedInterface.MotionSensor))
|
||||
throw new Error('Motion Sensor not found.');
|
||||
|
||||
if (device.type === ScryptedDeviceType.Doorbell && !device.interfaces.includes(ScryptedInterface.BinarySensor))
|
||||
throw new Error('Doorbell button not found.');
|
||||
});
|
||||
|
||||
await this.validate(console, 'Motion Detection', async () => {
|
||||
if (!device.interfaces.includes(ScryptedInterface.MotionSensor))
|
||||
throw new Error('Motion Sensor not found. Enabling a software motion sensor extension is recommended.');
|
||||
|
||||
if (device.providedInterfaces.includes(ScryptedInterface.MotionSensor)) {
|
||||
if (device.interfaces.find(i => i.startsWith('ObjectDetection:true')))
|
||||
this.warnStep(console, 'Camera hardware provides motion events, but a software motion detector is enabled. Consider disabling the software motion detector.');
|
||||
}
|
||||
});
|
||||
|
||||
if (device.interfaces.includes(ScryptedInterface.MotionSensor)) {
|
||||
await this.validate(console, 'Recent Motion', async () => {
|
||||
|
||||
const lastMotion = this.loggedMotion.get(device.id);
|
||||
if (!lastMotion)
|
||||
throw new Error('No recent motion detected. Go wave your hand in front of the camera.');
|
||||
if (Date.now() - lastMotion > 8 * 60 * 60 * 1000)
|
||||
throw new Error('Last motion was over 8 hours ago.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (device.type === ScryptedDeviceType.Doorbell) {
|
||||
await this.validate(console, 'Recent Button Press', async () => {
|
||||
const lastButton = this.loggedButton.get(device.id);
|
||||
if (!lastButton)
|
||||
throw new Error('No recent button press detected. Go press the doorbell button.');
|
||||
if (Date.now() - lastButton > 8 * 60 * 60 * 1000)
|
||||
throw new Error('Last button press was over 8 hours ago.');
|
||||
});
|
||||
}
|
||||
|
||||
const validateMedia = async (stepName: string, mo: Promise<MediaObject>, snapshot = false, and?: () => Promise<void>) => {
|
||||
await this.validate(console, stepName, async () => {
|
||||
if (snapshot && !device.interfaces.includes(ScryptedInterface.Camera))
|
||||
throw new Error('Snapshot not supported. Enable the Snapshot extension.');
|
||||
const jpeg = await sdk.mediaManager.convertMediaObjectToBuffer(await mo, 'image/jpeg');
|
||||
const metadata = await sharp(jpeg).metadata();
|
||||
if (!metadata.width || !metadata.height || metadata.width < 100 || metadata.height < 100)
|
||||
throw new Error('Malformed image.');
|
||||
if (!and && snapshot && device.pluginId === '@scrypted/unifi-protect' && metadata.width < 1280)
|
||||
this.warnStep(console, 'Unifi Protect provides low quality snapshots. Consider using Snapshot from Prebuffer for full resolution screenshots.');
|
||||
await and?.();
|
||||
});
|
||||
};
|
||||
|
||||
await validateMedia('Snapshot', device.takePicture({
|
||||
reason: 'event',
|
||||
}), true);
|
||||
|
||||
await this.validate(console, 'Streams', async () => {
|
||||
const vsos = await device.getVideoStreamOptions();
|
||||
|
||||
if (!vsos?.length)
|
||||
throw new Error('Stream configuration invalid.');
|
||||
|
||||
if (vsos.length < 3)
|
||||
this.warnStep(console, `Camera has ${vsos.length} substream. Three streams are recommended.`);
|
||||
|
||||
const cloudStreams = vsos.filter(vso => vso.source === 'cloud');
|
||||
if (cloudStreams.length)
|
||||
this.warnStep(console, `Cloud camera. Upgrade recommended.`);
|
||||
|
||||
const usedStreams = vsos.filter(vso => vso.destinations?.length);
|
||||
if (usedStreams.length < Math.min(3, vsos.length))
|
||||
this.warnStep(console, `Unused streams detected.`);
|
||||
});
|
||||
|
||||
const getVideoStream = async (destination: MediaStreamDestination) => {
|
||||
if (!device.interfaces.includes(ScryptedInterface.VideoCamera))
|
||||
throw new Error('Streaming not supported.');
|
||||
return await device.getVideoStream({
|
||||
destination,
|
||||
prebuffer: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const validated = new Set<string | undefined>();
|
||||
const validateMediaStream = async (stepName: string, destination: MediaStreamDestination) => {
|
||||
const vsos = await device.getVideoStreamOptions();
|
||||
const streamId = vsos.find(vso => vso.destinations?.includes(destination))?.id;
|
||||
|
||||
if (validated.has(streamId)) {
|
||||
await this.validate(console, stepName, async () => "Skipped (Duplicate)");
|
||||
return;
|
||||
}
|
||||
|
||||
validated.add(streamId);
|
||||
|
||||
const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(await getVideoStream(destination), ScryptedMimeTypes.FFmpegInput);
|
||||
if (ffmpegInput.mediaStreamOptions?.video?.codec !== 'h264')
|
||||
this.warnStep(console, `Stream ${stepName} is using codec ${ffmpegInput.mediaStreamOptions?.video?.codec}. h264 is recommended.`);
|
||||
|
||||
await validateMedia(stepName, getVideoStream(destination));
|
||||
const start = Date.now();
|
||||
await validateMedia(stepName + ' (IDR)', getVideoStream(destination), false, async () => {
|
||||
const end = Date.now();
|
||||
if (end - start > 5000)
|
||||
throw new Error(`High IDR Interval. This may cause issues with HomeKit Secure Video. Adjust codec configuration if possible.`);
|
||||
});
|
||||
};
|
||||
|
||||
await validateMediaStream('Local', 'local');
|
||||
|
||||
await validateMediaStream('Local Recorder', 'local-recorder');
|
||||
|
||||
await validateMediaStream('Remote Recorder', 'remote-recorder');
|
||||
|
||||
await validateMediaStream('Remote', 'remote');
|
||||
|
||||
await validateMediaStream('Low Resolution', 'low-resolution');
|
||||
|
||||
await this.validate(console, 'Audio Codecs', async () => {
|
||||
const vsos = await device.getVideoStreamOptions();
|
||||
|
||||
let codec: string | undefined;
|
||||
const codecs = new Set<string>();
|
||||
for (const vso of vsos) {
|
||||
if (vso.audio?.codec) {
|
||||
codec = vso.audio.codec;
|
||||
codecs.add(vso.audio.codec);
|
||||
}
|
||||
}
|
||||
|
||||
if (codecs.size > 1) {
|
||||
this.warnStep(console, `Mismatched audio codecs detected.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!codec)
|
||||
return;
|
||||
|
||||
if (codec !== 'pcm_mulaw' && codec !== 'aac' && codec !== 'opus') {
|
||||
this.warnStep(console, `Audio codec is ${codec}. pcm_mulaw, aac, or opus is recommended.`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async validateSystem() {
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
this.console.log('System Validation');
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
|
||||
const nvrPlugin = sdk.systemManager.getDeviceById('@scrypted/nvr');
|
||||
const cloudPlugin = sdk.systemManager.getDeviceById('@scrypted/cloud');
|
||||
const openvinoPlugin = sdk.systemManager.getDeviceById<Settings & ObjectDetection>('@scrypted/openvino');
|
||||
|
||||
await this.validate(this.console, 'Scrypted Installation', async () => {
|
||||
const e = process.env.SCRYPTED_INSTALL_ENVIRONMENT;
|
||||
if (process.platform !== 'linux') {
|
||||
if (e !== 'electron')
|
||||
this.warnStep(this.console, 'Upgrading to the Scrypted Desktop application is recommened for Windows and macOS.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (e !== 'docker' && e !== 'lxc' && e !== 'ha' && e !== 'lxc-docker')
|
||||
throw new Error('Unrecognized Linux installation. Installation via Docker image or the official Proxmox LXC script (not tteck) is recommended: https://docs.scrypted.app/installation');
|
||||
});
|
||||
|
||||
await this.validate(this.console, 'IPv4 (jsonip.com)', httpFetch({
|
||||
url: 'https://jsonip.com',
|
||||
family: 4,
|
||||
responseType: 'json',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.ip));
|
||||
|
||||
await this.validate(this.console, 'IPv6 (jsonip.com)', httpFetch({
|
||||
url: 'https://jsonip.com',
|
||||
family: 6,
|
||||
responseType: 'json',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.ip));
|
||||
|
||||
await this.validate(this.console, 'IPv4 (wtfismyip.com)', httpFetch({
|
||||
url: 'https://wtfismyip.com/text',
|
||||
family: 4,
|
||||
responseType: 'text',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.trim()));
|
||||
|
||||
await this.validate(this.console, 'IPv6 (wtfismyip.com)', httpFetch({
|
||||
url: 'https://wtfismyip.com/text',
|
||||
family: 6,
|
||||
responseType: 'text',
|
||||
timeout: 5000,
|
||||
}).then(r => r.body.trim()));
|
||||
|
||||
await this.validate(this.console, 'Scrypted Server Address', async () => {
|
||||
const addresses = await sdk.endpointManager.getLocalAddresses();
|
||||
const hasIPv4 = addresses?.find(address => net.isIPv4(address));
|
||||
const hasIPv6 = addresses?.find(address => net.isIPv6(address));
|
||||
if (addresses?.length)
|
||||
this.warnStep(this.console, addresses.join(', '));
|
||||
if (!hasIPv4)
|
||||
throw new Error('Scrypted Settings IPv4 address not set.');
|
||||
if (!hasIPv6)
|
||||
throw new Error('Scrypted Settings IPv6 address not set.');
|
||||
});
|
||||
|
||||
await this.validate(this.console, 'CPU Count', async () => {
|
||||
if (os.cpus().length < 2)
|
||||
throw new Error('CPU Count is too low. 4 CPUs are recommended.');
|
||||
return os.cpus().length;
|
||||
});
|
||||
|
||||
await this.validate(this.console, 'Memory', async () => {
|
||||
if (!nvrPlugin) {
|
||||
if (os.totalmem() < 8 * 1024 * 1024 * 1024)
|
||||
throw new Error('Memory is too low. 8GB is recommended.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (os.totalmem() < 14 * 1024 * 1024 * 1024)
|
||||
throw new Error('Memory is too low. 16GB is recommended for NVR.');
|
||||
return Math.floor(os.totalmem() / 1024 / 1024 / 1024) + " GB";
|
||||
});
|
||||
|
||||
if (process.platform === 'linux' && nvrPlugin) {
|
||||
// ensure /dev/dri/renderD128 is available
|
||||
await this.validate(this.console, 'GPU Passthrough', async () => {
|
||||
if (!fs.existsSync('/dev/dri/renderD128'))
|
||||
throw new Error('GPU device unvailable or not passed through to container.');
|
||||
});
|
||||
}
|
||||
|
||||
await this.validate(this.console, 'Cloud Plugin', async () => {
|
||||
if (!cloudPlugin) {
|
||||
this.warnStep(this.console, 'Cloud plugin not installed. Consider installing for remote access.');
|
||||
return;
|
||||
}
|
||||
|
||||
const logo = await httpFetch({
|
||||
url: 'https://home.scrypted.app/_punch/web_hi_res_512.png',
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
const mo = await sdk.mediaManager.createMediaObject(logo.body, 'image/png');
|
||||
const url = await sdk.mediaManager.convertMediaObjectToUrl(mo, 'image/png');
|
||||
|
||||
const logoCheck = await httpFetch({
|
||||
url,
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
if (Buffer.compare(logo.body, logoCheck.body))
|
||||
throw new Error('Invalid response received.');
|
||||
|
||||
const shortUrl: any = await sdk.mediaManager.convertMediaObject(mo, ScryptedMimeTypes.Url + ";short-lived=true");
|
||||
const shortLogoCheck = await httpFetch({
|
||||
url: shortUrl.toString(),
|
||||
responseType: 'buffer',
|
||||
});
|
||||
|
||||
if (Buffer.compare(logo.body, shortLogoCheck.body))
|
||||
throw new Error('Invalid response received from short lived URL.');
|
||||
});
|
||||
|
||||
if (openvinoPlugin) {
|
||||
await this.validate(this.console, 'OpenVINO Plugin', async () => {
|
||||
const settings = await openvinoPlugin.getSettings();
|
||||
const availbleDevices = settings.find(s => s.key === 'available_devices');
|
||||
if (!availbleDevices?.value?.toString().includes('GPU'))
|
||||
this.warnStep(this.console, 'GPU device unvailable or not passed through to container.');
|
||||
|
||||
const zidane = await sdk.mediaManager.createMediaObjectFromUrl('https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg');
|
||||
const detected = await openvinoPlugin.detectObjects(zidane);
|
||||
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
|
||||
if (!personFound)
|
||||
throw new Error('Person not detected in test image.');
|
||||
});
|
||||
}
|
||||
|
||||
if (nvrPlugin) {
|
||||
await this.validate(this.console, "GPU Decode", async () => {
|
||||
const ffmpegPath = await sdk.mediaManager.getFFmpegPath();
|
||||
let hasVaapi = false;
|
||||
{
|
||||
const args = [
|
||||
'-y',
|
||||
'-hwaccel', 'auto',
|
||||
'-i', 'https://github.com/koush/scrypted-sample-cameraprovider/raw/main/fs/dog.mp4',
|
||||
'-f', 'rawvideo',
|
||||
'pipe:3',
|
||||
];
|
||||
const cp = child_process.spawn(ffmpegPath, args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const deferred = new Deferred<void>();
|
||||
deferred.promise.catch(() => { }).finally(() => safeKillFFmpeg(cp));
|
||||
cp.stdio[3]?.on('data', () => { });
|
||||
|
||||
cp.stderr!.on('data', data => {
|
||||
const str = data.toString();
|
||||
hasVaapi ||= str.includes('Using auto hwaccel type vaapi');
|
||||
|
||||
if (str.includes('nv12'))
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
deferred.reject(new Error('GPU Decode timed out.'));
|
||||
}, 5000);
|
||||
|
||||
await deferred.promise;
|
||||
}
|
||||
|
||||
if (!hasVaapi || !openvinoPlugin)
|
||||
return;
|
||||
|
||||
{
|
||||
const args = [
|
||||
'-y',
|
||||
'-init_hw_device', 'vaapi=renderD128:/dev/dri/renderD128',
|
||||
'-hwaccel', 'vaapi',
|
||||
'-hwaccel_output_format', 'vaapi',
|
||||
'-i', 'https://docs.scrypted.app/img/scrypted-nvr/troubleshooting/zidane.jpg',
|
||||
'-vf', 'format=nv12,hwupload,scale_vaapi=w=320:h=-2,hwdownload,format=nv12',
|
||||
'-f', 'mjpeg',
|
||||
'-frames:v', '1',
|
||||
'pipe:3',
|
||||
];
|
||||
|
||||
const cp = child_process.spawn(ffmpegPath, args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
let std = '';
|
||||
cp.stderr!.on('data', data => {
|
||||
std += data.toString();
|
||||
});
|
||||
|
||||
cp.stdout!.on('data', data => {
|
||||
std += data.toString();
|
||||
});
|
||||
|
||||
const buffers: Buffer[] = [];
|
||||
cp.stdio[3]?.on('data', buffer => {
|
||||
buffers.push(buffer);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
safeKillFFmpeg(cp)
|
||||
}, 5000);
|
||||
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode) {
|
||||
this.warnStep(this.console, std);
|
||||
throw new Error('GPU Transform failed.');
|
||||
}
|
||||
|
||||
const jpeg = Buffer.concat(buffers);
|
||||
const zidane = await sdk.mediaManager.createMediaObject(jpeg, 'image/jpeg');
|
||||
const image = await sdk.mediaManager.convertMediaObject<Image>(zidane, ScryptedMimeTypes.Image);
|
||||
if (image.width !== 320)
|
||||
throw new Error('Unexpected image with from GPU transform.')
|
||||
const detected = await openvinoPlugin.detectObjects(zidane);
|
||||
const personFound = detected.detections!.find(d => d.className === 'person' && d.score > .9);
|
||||
if (!personFound)
|
||||
throw new Error('Person not detected in test image.');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
await this.validate(this.console, 'Deprecated Plugins', async () => {
|
||||
const defunctPlugins = [
|
||||
'@scrypted/electron-core',
|
||||
'@scrypted/opencv',
|
||||
'@scrypted/python-codecs',
|
||||
'@scrypted/pam-diff',
|
||||
];
|
||||
let found = false;
|
||||
|
||||
for (const plugin of defunctPlugins) {
|
||||
const pluginDevice = sdk.systemManager.getDeviceById(plugin);
|
||||
if (pluginDevice) {
|
||||
this.warnStep(this.console, `Scrypted NVR users can remove: ${plugin}`);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
throw new Error('Deprecated plugins found.');
|
||||
});
|
||||
}
|
||||
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
this.console.log('System Validation Complete');
|
||||
this.console.log(''.padEnd(44, '='));
|
||||
}
|
||||
}
|
||||
|
||||
export default DiagnosticsPlugin;
|
||||
14
plugins/diagnostics/tsconfig.json
Normal file
14
plugins/diagnostics/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"target": "ES2021",
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "scrypted-server",
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
}
|
||||
4
plugins/google-device-access/package-lock.json
generated
4
plugins/google-device-access/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/google-device-access",
|
||||
"version": "0.0.98",
|
||||
"version": "0.0.99",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/google-device-access",
|
||||
"version": "0.0.98",
|
||||
"version": "0.0.99",
|
||||
"dependencies": {
|
||||
"@googleapis/smartdevicemanagement": "^1.0.0",
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -49,5 +49,5 @@
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/node": "^20.4.1"
|
||||
},
|
||||
"version": "0.0.98"
|
||||
"version": "0.0.99"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"module": "Node16",
|
||||
"target": "ES2021",
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
|
||||
@@ -111,6 +111,12 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
|
||||
hide: true,
|
||||
description: 'The last home hub to request a recording. Internally used to determine if a streaming request is coming from remote wifi.',
|
||||
},
|
||||
autoAdd: {
|
||||
title: "Auto enable",
|
||||
description: "Automatically enable this mixin on new devices.",
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
});
|
||||
mergedDevices = new Set<string>();
|
||||
|
||||
@@ -218,7 +224,8 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
|
||||
|
||||
try {
|
||||
const mixins = (device.mixins || []).slice();
|
||||
if (!mixins.includes(this.id)) {
|
||||
const autoAdd = this.storageSettings.values.autoAdd ?? true;
|
||||
if (!mixins.includes(this.id) && autoAdd) {
|
||||
// don't sync this by default, as it's solely for automations
|
||||
if (device.type === ScryptedDeviceType.Notifier)
|
||||
continue;
|
||||
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.43",
|
||||
"version": "0.1.46",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.43",
|
||||
"version": "0.1.46",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.43",
|
||||
"version": "0.1.46",
|
||||
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -334,8 +334,6 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
const stream = await this.cameraDevice.getVideoStream({
|
||||
prebuffer: this.model.prebuffer,
|
||||
destination,
|
||||
// ask rebroadcast to mute audio, not needed.
|
||||
audio: null,
|
||||
});
|
||||
|
||||
if (this.model.decoder) {
|
||||
@@ -419,7 +417,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
const frameGenerator = this.model.decoder ? undefined : this.getFrameGenerator();
|
||||
for await (const detected of
|
||||
await sdk.connectRPCObject(
|
||||
await this.objectDetection.generateObjectDetections(
|
||||
await this.objectDetection.generateObjectDetections(
|
||||
await this.createFrameGenerator(
|
||||
frameGenerator,
|
||||
options,
|
||||
@@ -612,7 +610,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
this.detections.set(detectionId, detectionInput);
|
||||
setTimeout(() => {
|
||||
this.detections.delete(detectionId);
|
||||
}, 2000);
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
async getNativeObjectTypes(): Promise<ObjectDetectionTypes> {
|
||||
@@ -661,12 +659,11 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
frameGenerator = this.plugin.storageSettings.values.defaultDecoder || 'Default';
|
||||
|
||||
const pipelines = getAllDevices().filter(d => d.interfaces.includes(ScryptedInterface.VideoFrameGenerator));
|
||||
const webcodec = process.env.SCRYPTED_INSTALL_ENVIRONMENT === 'electron' ? sdk.systemManager.getDeviceById('@scrypted/electron-core', 'webcodec') : undefined;
|
||||
const webassembly = sdk.systemManager.getDeviceById('@scrypted/nvr', 'decoder') || undefined;
|
||||
const gstreamer = sdk.systemManager.getDeviceById('@scrypted/python-codecs', 'gstreamer') || undefined;
|
||||
const libav = sdk.systemManager.getDeviceById('@scrypted/python-codecs', 'libav') || undefined;
|
||||
const ffmpeg = sdk.systemManager.getDeviceById('@scrypted/objectdetector', 'ffmpeg') || undefined;
|
||||
const use = pipelines.find(p => p.name === frameGenerator) || webcodec || webassembly || gstreamer || libav || ffmpeg;
|
||||
const use = pipelines.find(p => p.name === frameGenerator) || webassembly || gstreamer || libav || ffmpeg;
|
||||
return use.id;
|
||||
}
|
||||
|
||||
@@ -1060,6 +1057,10 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
checkHasEnabledMixin(device: ScryptedDevice): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
pruneOldStatistics() {
|
||||
const now = Date.now();
|
||||
for (const [k, v] of this.objectDetectionStatistics.entries()) {
|
||||
|
||||
@@ -4,3 +4,24 @@ This plugin adds object detection capabilities to any camera in Scrypted. Having
|
||||
|
||||
The ONNX Plugin should only be used if you are a Scrypted NVR user. It will provide no
|
||||
benefits to HomeKit, which does its own detection processing.
|
||||
|
||||
# Windows Setup
|
||||
|
||||
Windows setup requires several installation steps and system PATH variables to be set correctly. The NVIDIA installers does not do this correctly if older CUDA or CUDNN exists.
|
||||
|
||||
1. Install latest NVIDIA drivers.
|
||||
2. Install CUDA 12.x.
|
||||
3. Install CUDNN 9.x.
|
||||
4. Open a new Terminal.
|
||||
5. Verify CUDA_PATH environment is set.
|
||||
* The syste, CUDA_PATH can be set in Windows Advanced System Settings.
|
||||
6. Verify PATH contains the path to CUDNN\bin\12.x, where the `cudnn64_9.dll` file is located. Typically it will be somewhere like: `"C:\Program Files\NVIDIA\CUDNN\v9.4\bin\12.6\cudnn64_9.dll"`.
|
||||
* The system PATH can be set in Windows Advanced System Settings.
|
||||
7. Exit Scrypted.
|
||||
8. Reopen Scrypted.
|
||||
|
||||
# Linux Setup
|
||||
|
||||
1. Install NVIDIA drivers on host.
|
||||
2. Install CUDA and CUDNN.
|
||||
3. Follow the NVIDIA setup steps for the NVIDIA docker image. https://docs.scrypted.app/installation.html#linux-docker
|
||||
4
plugins/onnx/package-lock.json
generated
4
plugins/onnx/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.113",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/onnx",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.113",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.110"
|
||||
"version": "0.1.113"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ numpy==1.26.4
|
||||
# uncomment to require cuda 12, but most stuff is still targetting cuda 11.
|
||||
# however, stuff targetted for cuda 11 can still run on cuda 12.
|
||||
# --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
|
||||
onnxruntime-gpu==1.18.1; 'darwin' not in sys_platform and platform_machine != 'aarch64'
|
||||
onnxruntime-gpu==1.19.2; 'darwin' not in sys_platform and platform_machine != 'aarch64'
|
||||
# cpu and coreml execution provider
|
||||
onnxruntime; 'darwin' in sys_platform or platform_machine == 'aarch64'
|
||||
# nightly?
|
||||
|
||||
2
plugins/onvif/.vscode/settings.json
vendored
2
plugins/onvif/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
Submodule plugins/onvif/onvif deleted from 5641b572c4
4021
plugins/onvif/package-lock.json
generated
4021
plugins/onvif/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/onvif",
|
||||
"version": "0.1.23",
|
||||
"version": "0.1.28",
|
||||
"description": "ONVIF Camera Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"base-64": "^1.0.0",
|
||||
"md5": "^2.3.0",
|
||||
"onvif": "file:./onvif",
|
||||
"onvif": "^0.7.4",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -20,12 +20,53 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
],
|
||||
onPut: (ov, ptz: string[]) => {
|
||||
this.ptzCapabilities = {
|
||||
...this.ptzCapabilities,
|
||||
pan: ptz.includes('Pan'),
|
||||
tilt: ptz.includes('Tilt'),
|
||||
zoom: ptz.includes('Zoom'),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ptzMovementType: {
|
||||
title: 'PTZ Movement Type',
|
||||
description: 'The type of movement to use for PTZ commands by default.',
|
||||
type: 'string',
|
||||
choices: [
|
||||
'Default',
|
||||
PanTiltZoomMovement.Absolute,
|
||||
PanTiltZoomMovement.Relative,
|
||||
PanTiltZoomMovement.Continuous,
|
||||
],
|
||||
defaultValue: 'Default',
|
||||
},
|
||||
presets: {
|
||||
title: 'Presets',
|
||||
description: 'PTZ Presets in the format "key=name". Where key is the PTZ Preset identifier and name is a friendly name.',
|
||||
multiple: true,
|
||||
defaultValue: [],
|
||||
combobox: true,
|
||||
onPut: async (ov, presets: string[]) => {
|
||||
const caps = {
|
||||
...this.ptzCapabilities,
|
||||
presets: {},
|
||||
};
|
||||
for (const preset of presets) {
|
||||
const [key, name] = preset.split('=');
|
||||
caps.presets[key] = name;
|
||||
}
|
||||
this.ptzCapabilities = caps;
|
||||
},
|
||||
mapGet: () => {
|
||||
const presets = this.ptzCapabilities?.presets || {};
|
||||
return Object.entries(presets).map(([key, name]) => key + '=' + name);
|
||||
},
|
||||
},
|
||||
cachedPresets: {
|
||||
multiple: true,
|
||||
hide: true,
|
||||
json: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
});
|
||||
|
||||
constructor(options: SettingsMixinDeviceOptions<Settings>) {
|
||||
@@ -33,6 +74,30 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
|
||||
// force a read to set the state.
|
||||
this.storageSettings.values.ptz;
|
||||
|
||||
this.refreshPresets();
|
||||
|
||||
this.storageSettings.settings.presets.onGet = async () => {
|
||||
// getPresets is where the key is the name of the preset, and the value is the id.
|
||||
// kind of weird and backwards.
|
||||
const choices = Object.entries(this.storageSettings.values.cachedPresets).map(([name, key]) => key + '=' + name);
|
||||
return {
|
||||
choices,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
async refreshPresets() {
|
||||
const client = await this.getClient();
|
||||
client.cam.getPresets({}, (e, result, xml) => {
|
||||
if (e) {
|
||||
this.console.error('failed to get presets', e);
|
||||
}
|
||||
else {
|
||||
this.console.log('presets', result);
|
||||
this.storageSettings.values.cachedPresets = result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getMixinSettings(): Promise<Setting[]> {
|
||||
@@ -55,19 +120,65 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
};
|
||||
}
|
||||
|
||||
if (command.movement === PanTiltZoomMovement.Absolute) {
|
||||
let movement = command.movement || this.storageSettings.values.ptzMovementType;
|
||||
if (movement === PanTiltZoomMovement.Absolute) {
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.absoluteMove({
|
||||
x: command.pan,
|
||||
y: command.tilt,
|
||||
zoom: command.zoom,
|
||||
speed: speed
|
||||
speed: speed,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
});
|
||||
})
|
||||
}
|
||||
else if (movement === PanTiltZoomMovement.Continuous) {
|
||||
let x = command.pan;
|
||||
let y = command.tilt;
|
||||
let zoom = command.zoom;
|
||||
if (command.speed?.pan)
|
||||
x *= command.speed.pan;
|
||||
if (command.speed?.tilt)
|
||||
y *= command.speed.tilt;
|
||||
if (command.speed?.zoom)
|
||||
zoom *= command.speed.zoom;
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.continuousMove({
|
||||
x: command.pan,
|
||||
y: command.tilt,
|
||||
zoom: command.zoom,
|
||||
timeout: command.timeout || 1000,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
else if (movement === PanTiltZoomMovement.Home) {
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.gotoHomePosition({
|
||||
speed: speed,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
});
|
||||
}
|
||||
else if (movement === PanTiltZoomMovement.Preset) {
|
||||
return new Promise<void>((r, f) => {
|
||||
client.cam.gotoPreset({
|
||||
preset: command.preset,
|
||||
}, (e, result, xml) => {
|
||||
if (e)
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
});
|
||||
}
|
||||
else {
|
||||
// relative movement is default.
|
||||
@@ -82,7 +193,7 @@ export class OnvifPtzMixin extends SettingsMixinDeviceBase<Settings> implements
|
||||
return f(e);
|
||||
r();
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
plugins/openvino/.vscode/launch.json
vendored
2
plugins/openvino/.vscode/launch.json
vendored
@@ -6,7 +6,7 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "${config:scrypted.debugHost}",
|
||||
|
||||
4
plugins/openvino/package-lock.json
generated
4
plugins/openvino/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.111",
|
||||
"version": "0.1.118",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.111",
|
||||
"version": "0.1.118",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.111"
|
||||
"version": "0.1.118"
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class OpenVINOPlugin(
|
||||
except:
|
||||
pass
|
||||
|
||||
mode = self.storage.getItem("mode")
|
||||
mode = self.storage.getItem("mode") or "Default"
|
||||
if mode == "Default":
|
||||
mode = "AUTO"
|
||||
|
||||
@@ -136,6 +136,7 @@ class OpenVINOPlugin(
|
||||
mode = f"AUTO:NPU,CPU"
|
||||
elif len(dgpus):
|
||||
mode = f"AUTO:{','.join(dgpus)},CPU"
|
||||
# forcing GPU can cause crashes on older GPU.
|
||||
elif gpu:
|
||||
mode = f"GPU"
|
||||
|
||||
@@ -164,8 +165,6 @@ class OpenVINOPlugin(
|
||||
self.sigmoid = model == "yolo-v4-tiny-tf"
|
||||
self.modelName = model
|
||||
|
||||
print(f"model/mode/precision: {model}/{mode}/{precision}")
|
||||
|
||||
ovmodel = "best" if self.scrypted_model else model
|
||||
|
||||
model_version = "v5"
|
||||
@@ -202,19 +201,28 @@ class OpenVINOPlugin(
|
||||
|
||||
try:
|
||||
self.compiled_model = self.core.compile_model(xmlFile, mode)
|
||||
print(
|
||||
"EXECUTION_DEVICES",
|
||||
self.compiled_model.get_property("EXECUTION_DEVICES"),
|
||||
)
|
||||
except:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
print("Reverting all settings.")
|
||||
self.storage.removeItem("mode")
|
||||
self.storage.removeItem("model")
|
||||
self.storage.removeItem("precision")
|
||||
self.requestRestart()
|
||||
|
||||
if mode == "GPU":
|
||||
try:
|
||||
print("GPU mode failed, reverting to AUTO.")
|
||||
mode = "AUTO"
|
||||
self.mode = mode
|
||||
self.compiled_model = self.core.compile_model(xmlFile, mode)
|
||||
except:
|
||||
print("Reverting all settings.")
|
||||
self.storage.removeItem("mode")
|
||||
self.storage.removeItem("model")
|
||||
self.storage.removeItem("precision")
|
||||
self.requestRestart()
|
||||
|
||||
print(
|
||||
"EXECUTION_DEVICES",
|
||||
self.compiled_model.get_property("EXECUTION_DEVICES"),
|
||||
)
|
||||
print(f"model/mode/precision: {model}/{mode}/{precision}")
|
||||
|
||||
# mobilenet 1,300,300,3
|
||||
# yolov3/4 1,416,416,3
|
||||
|
||||
@@ -2,6 +2,6 @@ import concurrent.futures
|
||||
|
||||
|
||||
def create_executors(name: str):
|
||||
prepare = concurrent.futures.ThreadPoolExecutor(1, "OpenVINO-{f}Prepare")
|
||||
predict = concurrent.futures.ThreadPoolExecutor(1, "OpenVINO-{f}}Predict")
|
||||
prepare = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Prepare")
|
||||
predict = concurrent.futures.ThreadPoolExecutor(1, f"OpenVINO-{name}Predict")
|
||||
return prepare, predict
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# must ensure numpy is pinned to prevent dependencies with an unpinned numpy from pulling numpy>=2.0.
|
||||
numpy==1.26.4
|
||||
openvino==2024.3.0
|
||||
# openvino 2024.3.0 crashes on older CPU (J4105 and older) if level-zero is installed via apt.
|
||||
# openvino 2024.2.0 and older crashes on arc dGPU.
|
||||
openvino==2024.4.0
|
||||
Pillow==10.3.0
|
||||
opencv-python==4.10.0.84
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
4
plugins/prebuffer-mixin/package-lock.json
generated
4
plugins/prebuffer-mixin/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.36",
|
||||
"version": "0.10.37",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.36",
|
||||
"version": "0.10.37",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.36",
|
||||
"version": "0.10.37",
|
||||
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -196,13 +196,33 @@ class PrebufferSession {
|
||||
return;
|
||||
this.console.log(this.streamName, 'prebuffer session started');
|
||||
this.parserSessionPromise = this.startPrebufferSession();
|
||||
this.parserSessionPromise.then(pso => pso.killed.finally(() => {
|
||||
this.console.error(this.streamName, 'prebuffer session ended');
|
||||
this.parserSessionPromise = undefined;
|
||||
}))
|
||||
let active = false;
|
||||
this.parserSessionPromise.then(pso => {
|
||||
pso.once('rtsp', () => {
|
||||
active = true;
|
||||
if (!this.mixin.online)
|
||||
this.mixin.online = true;
|
||||
});
|
||||
|
||||
pso.killed.finally(() => {
|
||||
this.console.error(this.streamName, 'prebuffer session ended');
|
||||
this.parserSessionPromise = undefined;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
this.console.error(this.streamName, 'prebuffer session ended with error', e);
|
||||
this.parserSessionPromise = undefined;
|
||||
|
||||
if (!active) {
|
||||
// find sessions that arent this one, and check their prebuffers to see if any data has been received.
|
||||
// if there's no data, then consider this camera offline.
|
||||
const others = [...this.mixin.sessions.values()].filter(s => s !== this);
|
||||
if (others.length) {
|
||||
const hasData = others.some(s => s.rtspPrebuffer.length);
|
||||
if (!hasData && this.mixin.online)
|
||||
this.mixin.online = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1415,11 +1435,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
||||
}
|
||||
}
|
||||
|
||||
if (!enabledIds.length)
|
||||
this.online = true;
|
||||
|
||||
let active = 0;
|
||||
|
||||
// figure out the default stream and streams that may have been removed due to
|
||||
// a config change.
|
||||
const toRemove = new Set(this.sessions.keys());
|
||||
@@ -1462,23 +1477,13 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
||||
}
|
||||
|
||||
session.ensurePrebufferSession();
|
||||
let wasActive = false;
|
||||
try {
|
||||
this.console.log(name, 'prebuffer session starting');
|
||||
const ps = await session.parserSessionPromise;
|
||||
active++;
|
||||
wasActive = true;
|
||||
this.online = !!active;
|
||||
await ps.killed;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
finally {
|
||||
if (wasActive)
|
||||
active--;
|
||||
wasActive = false;
|
||||
this.online = !!active;
|
||||
}
|
||||
this.console.log(this.name, 'restarting prebuffer session in 5 seconds');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
}
|
||||
|
||||
37
plugins/reolink/package-lock.json
generated
37
plugins/reolink/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.94",
|
||||
"version": "0.0.98",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.94",
|
||||
"version": "0.0.98",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
@@ -35,24 +35,23 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.46",
|
||||
"version": "0.3.67",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"rimraf": "^6.0.1",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
@@ -64,11 +63,11 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5"
|
||||
}
|
||||
},
|
||||
"../onvif/onvif": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.94",
|
||||
"version": "0.0.98",
|
||||
"description": "Reolink Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -52,11 +52,13 @@ class ReolinkCameraSiren extends ScryptedDeviceBase implements OnOff {
|
||||
|
||||
class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, Reboot, Intercom, ObjectDetector, PanTiltZoom {
|
||||
client: ReolinkCameraClient;
|
||||
clientWithToken: ReolinkCameraClient;
|
||||
onvifClient: OnvifCameraAPI;
|
||||
onvifIntercom = new OnvifIntercom(this);
|
||||
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
|
||||
motionTimeout: NodeJS.Timeout;
|
||||
siren: ReolinkCameraSiren;
|
||||
batteryTimeout: NodeJS.Timeout;
|
||||
|
||||
storageSettings = new StorageSettings(this, {
|
||||
doorbell: {
|
||||
@@ -94,6 +96,35 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
this.updatePtzCaps();
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
subgroup: 'Advanced',
|
||||
title: 'Presets',
|
||||
description: 'PTZ Presets in the format "id=name". Where id is the PTZ Preset identifier and name is a friendly name.',
|
||||
multiple: true,
|
||||
defaultValue: [],
|
||||
combobox: true,
|
||||
onPut: async (ov, presets: string[]) => {
|
||||
const caps = {
|
||||
...this.ptzCapabilities,
|
||||
presets: {},
|
||||
};
|
||||
for (const preset of presets) {
|
||||
const [key, name] = preset.split('=');
|
||||
caps.presets[key] = name;
|
||||
}
|
||||
this.ptzCapabilities = caps;
|
||||
},
|
||||
mapGet: () => {
|
||||
const presets = this.ptzCapabilities?.presets || {};
|
||||
return Object.entries(presets).map(([key, name]) => key + '=' + name);
|
||||
},
|
||||
},
|
||||
cachedPresets: {
|
||||
multiple: true,
|
||||
hide: true,
|
||||
json: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
deviceInfo: {
|
||||
json: true,
|
||||
hide: true
|
||||
@@ -134,11 +165,24 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
}
|
||||
};
|
||||
|
||||
this.storageSettings.settings.presets.onGet = async () => {
|
||||
const choices = this.storageSettings.values.cachedPresets.map((preset) => preset.id + '=' + preset.name);
|
||||
return {
|
||||
choices,
|
||||
};
|
||||
};
|
||||
|
||||
this.updateDeviceInfo();
|
||||
(async () => {
|
||||
this.updatePtzCaps();
|
||||
try {
|
||||
await this.getPresets();
|
||||
} catch (e) {
|
||||
this.console.log('Fail fetching presets', e);
|
||||
}
|
||||
const api = this.getClient();
|
||||
const deviceInfo = await api.getDeviceInfo();
|
||||
this.console.log('deviceInfo', JSON.stringify(deviceInfo));
|
||||
this.storageSettings.values.deviceInfo = deviceInfo;
|
||||
await this.updateAbilities();
|
||||
await this.updateDevice();
|
||||
@@ -160,15 +204,29 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
updatePtzCaps() {
|
||||
const { ptz } = this.storageSettings.values;
|
||||
this.ptzCapabilities = {
|
||||
...this.ptzCapabilities,
|
||||
pan: ptz?.includes('Pan'),
|
||||
tilt: ptz?.includes('Tilt'),
|
||||
zoom: ptz?.includes('Zoom'),
|
||||
}
|
||||
}
|
||||
|
||||
async getPresets() {
|
||||
const client = this.getClient();
|
||||
const ptzPresets = await client.getPtzPresets();
|
||||
this.console.log(`Presets: ${JSON.stringify(ptzPresets)}`)
|
||||
this.storageSettings.values.cachedPresets = ptzPresets;
|
||||
}
|
||||
|
||||
async updateAbilities() {
|
||||
const api = this.getClient();
|
||||
const abilities = await api.getAbility();
|
||||
const apiWithToken = this.getClientWithToken();
|
||||
let abilities;
|
||||
try {
|
||||
abilities = await api.getAbility();
|
||||
} catch (e) {
|
||||
abilities = await apiWithToken.getAbility();
|
||||
}
|
||||
this.storageSettings.values.abilities = abilities;
|
||||
this.console.log('getAbility', JSON.stringify(abilities));
|
||||
}
|
||||
@@ -237,6 +295,11 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
&& this.storageSettings.values.abilities?.value?.Ability?.supportAudioAlarm?.ver !== 0;
|
||||
}
|
||||
|
||||
hasBattery() {
|
||||
const batteryConfigVer = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()]?.battery?.ver ?? 0;
|
||||
return batteryConfigVer > 0;
|
||||
}
|
||||
|
||||
async updateDevice() {
|
||||
const interfaces = this.provider.getInterfaces();
|
||||
let type = ScryptedDeviceType.Camera;
|
||||
@@ -262,8 +325,31 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
}
|
||||
if (this.hasSiren())
|
||||
interfaces.push(ScryptedInterface.DeviceProvider);
|
||||
if (this.hasBattery()) {
|
||||
interfaces.push(ScryptedInterface.Battery, ScryptedInterface.Online);
|
||||
this.startBatteryCheckInterval();
|
||||
}
|
||||
|
||||
await this.provider.updateDevice(this.nativeId, name, interfaces, type);
|
||||
await this.provider.updateDevice(this.nativeId, this.name ?? name, interfaces, type);
|
||||
}
|
||||
|
||||
startBatteryCheckInterval() {
|
||||
if (this.batteryTimeout) {
|
||||
clearInterval(this.batteryTimeout);
|
||||
}
|
||||
|
||||
this.batteryTimeout = setInterval(async () => {
|
||||
const api = this.getClientWithToken();
|
||||
|
||||
try {
|
||||
const { batteryPercent, sleep } = await api.getBatteryInfo();
|
||||
this.batteryLevel = batteryPercent;
|
||||
this.online = !sleep;
|
||||
}
|
||||
catch (e) {
|
||||
this.console.log('Error in getting battery info', e);
|
||||
}
|
||||
}, 1000 * 60 * 30);
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
@@ -292,6 +378,12 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
return this.client;
|
||||
}
|
||||
|
||||
getClientWithToken() {
|
||||
if (!this.clientWithToken)
|
||||
this.clientWithToken = new ReolinkCameraClient(this.getHttpAddress(), this.getUsername(), this.getPassword(), this.getRtspChannel(), this.console, true);
|
||||
return this.clientWithToken;
|
||||
}
|
||||
|
||||
async getOnvifClient() {
|
||||
if (!this.onvifClient)
|
||||
this.onvifClient = await this.createOnvifClient();
|
||||
@@ -563,7 +655,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
// 1: support main/extern/sub stream
|
||||
// 2: support main/sub stream
|
||||
|
||||
const live = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[0].live?.ver;
|
||||
const live = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()].live?.ver;
|
||||
const [rtmpMain, rtmpExt, rtmpSub, rtspMain, rtspSub] = streams;
|
||||
streams.splice(0, streams.length);
|
||||
|
||||
@@ -572,7 +664,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
|
||||
// 1: main stream enc type is H265
|
||||
|
||||
// anecdotally, encoders of type h265 do not have a working RTMP main stream.
|
||||
const mainEncType = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[0].mainEncType?.ver;
|
||||
const mainEncType = this.storageSettings.values.abilities?.value?.Ability?.abilityChn?.[this.getRtspChannel()].mainEncType?.ver;
|
||||
|
||||
if (live === 2) {
|
||||
if (mainEncType === 1) {
|
||||
@@ -741,6 +833,7 @@ class ReolinkProvider extends RtspProvider {
|
||||
const rtspChannel = parseInt(settings.rtspChannel?.toString()) || 0;
|
||||
if (!skipValidate) {
|
||||
const api = new ReolinkCameraClient(httpAddress, username, password, rtspChannel, this.console);
|
||||
const apiWithToken = new ReolinkCameraClient(httpAddress, username, password, rtspChannel, this.console, true);
|
||||
try {
|
||||
await api.jpegSnapshot();
|
||||
}
|
||||
@@ -754,7 +847,11 @@ class ReolinkProvider extends RtspProvider {
|
||||
doorbell = deviceInfo.type === 'BELL';
|
||||
name = deviceInfo.name ?? 'Reolink Camera';
|
||||
ai = await api.getAiState();
|
||||
abilities = await api.getAbility();
|
||||
try {
|
||||
abilities = await api.getAbility();
|
||||
} catch (e) {
|
||||
abilities = await apiWithToken.getAbility();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.console.error('Reolink camera does not support AI events', e);
|
||||
|
||||
@@ -46,25 +46,27 @@ async function getDeviceInfo(host: string, username: string, password: string):
|
||||
return response.body?.[0]?.value?.DevInfo;
|
||||
}
|
||||
|
||||
export async function getLoginParameters(host: string, username: string, password: string) {
|
||||
try {
|
||||
await getDeviceInfo(host, username, password);
|
||||
return {
|
||||
parameters: {
|
||||
user: username,
|
||||
password,
|
||||
},
|
||||
leaseTimeSeconds: Infinity,
|
||||
export async function getLoginParameters(host: string, username: string, password: string, forceToken?: boolean) {
|
||||
if (!forceToken) {
|
||||
try {
|
||||
await getDeviceInfo(host, username, password);
|
||||
return {
|
||||
parameters: {
|
||||
user: username,
|
||||
password,
|
||||
},
|
||||
leaseTimeSeconds: Infinity,
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(`http://${host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'Login');
|
||||
|
||||
|
||||
const response = await httpFetch({
|
||||
url,
|
||||
method: 'POST',
|
||||
@@ -83,7 +85,7 @@ export async function getLoginParameters(host: string, username: string, passwor
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
const token = response.body?.[0]?.value?.Token?.name || response.body?.value?.Token?.name;
|
||||
if (!token)
|
||||
throw new Error('unable to login');
|
||||
|
||||
@@ -39,12 +39,17 @@ export type SirenResponse = {
|
||||
rspCode: number;
|
||||
}
|
||||
|
||||
export interface PtzPreset {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class ReolinkCameraClient {
|
||||
credential: AuthFetchCredentialState;
|
||||
parameters: Record<string, string>;
|
||||
tokenLease: number;
|
||||
|
||||
constructor(public host: string, public username: string, public password: string, public channelId: number, public console: Console) {
|
||||
constructor(public host: string, public username: string, public password: string, public channelId: number, public console: Console, public readonly forceToken?: boolean) {
|
||||
this.credential = {
|
||||
username,
|
||||
password,
|
||||
@@ -61,6 +66,13 @@ export class ReolinkCameraClient {
|
||||
return response;
|
||||
}
|
||||
|
||||
private createReadable = (data: any) => {
|
||||
const pt = new PassThrough();
|
||||
pt.write(Buffer.from(JSON.stringify(data)));
|
||||
pt.end();
|
||||
return pt;
|
||||
}
|
||||
|
||||
async login() {
|
||||
if (this.tokenLease > Date.now()) {
|
||||
return;
|
||||
@@ -68,7 +80,7 @@ export class ReolinkCameraClient {
|
||||
|
||||
this.console.log(`token expired at ${this.tokenLease}, renewing...`);
|
||||
|
||||
const { parameters, leaseTimeSeconds } = await getLoginParameters(this.host, this.username, this.password);
|
||||
const { parameters, leaseTimeSeconds } = await getLoginParameters(this.host, this.username, this.password, this.forceToken);
|
||||
this.parameters = parameters
|
||||
this.tokenLease = Date.now() + 1000 * leaseTimeSeconds;
|
||||
}
|
||||
@@ -141,15 +153,37 @@ export class ReolinkCameraClient {
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'GetAbility');
|
||||
params.set('channel', this.channelId.toString());
|
||||
const response = await this.requestWithLogin({
|
||||
let response = await this.requestWithLogin({
|
||||
url,
|
||||
responseType: 'json',
|
||||
});
|
||||
const error = response.body?.[0]?.error;
|
||||
let error = response.body?.[0]?.error;
|
||||
if (error) {
|
||||
this.console.error('error during call to getAbility', error);
|
||||
throw new Error('error during call to getAbility');
|
||||
this.console.error('error during call to getAbility GET, Trying with POST', error);
|
||||
|
||||
url.search = '';
|
||||
|
||||
const body = [
|
||||
{
|
||||
cmd: "GetAbility",
|
||||
action: 0,
|
||||
param: { User: { userName: this.username } }
|
||||
}
|
||||
];
|
||||
|
||||
response = await this.requestWithLogin({
|
||||
url,
|
||||
responseType: 'json',
|
||||
method: 'POST',
|
||||
}, this.createReadable(body));
|
||||
|
||||
error = response.body?.[0]?.error;
|
||||
if (error) {
|
||||
this.console.error('error during call to getAbility GET, Trying with POST', error);
|
||||
throw new Error('error during call to getAbility');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value: response.body?.[0]?.value || response.body?.value,
|
||||
data: response.body,
|
||||
@@ -198,26 +232,79 @@ export class ReolinkCameraClient {
|
||||
this.console.error('error during call to getDeviceInfo', error);
|
||||
throw new Error('error during call to getDeviceInfo');
|
||||
}
|
||||
return response.body?.[0]?.value?.DevInfo;
|
||||
|
||||
const deviceInfo: DevInfo = await response.body?.[0]?.value?.DevInfo;
|
||||
|
||||
// Will need to check if it's valid for NVR and NVR_WIFI
|
||||
if (!['HOMEHUB', 'NVR', 'NVR_WIFI'].includes(deviceInfo.exactType)) {
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
// If the device is listed as homehub, fetch the channel specific information
|
||||
url.search = '';
|
||||
const body = [
|
||||
{ cmd: "GetChnTypeInfo", action: 0, param: { channel: this.channelId } },
|
||||
{ cmd: "GetChannelstatus", action: 0, param: {} },
|
||||
]
|
||||
|
||||
const additionalInfoResponse = await this.requestWithLogin({
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'json'
|
||||
}, this.createReadable(body));
|
||||
|
||||
const chnTypeInfo = additionalInfoResponse?.body?.find(elem => elem.cmd === 'GetChnTypeInfo');
|
||||
const chnStatus = additionalInfoResponse?.body?.find(elem => elem.cmd === 'GetChannelstatus');
|
||||
|
||||
if (chnTypeInfo?.value) {
|
||||
deviceInfo.firmVer = chnTypeInfo.value.firmVer;
|
||||
deviceInfo.model = chnTypeInfo.value.typeInfo;
|
||||
deviceInfo.pakSuffix = chnTypeInfo.value.pakSuffix;
|
||||
}
|
||||
|
||||
if (chnStatus?.value) {
|
||||
const specificChannelStatus = chnStatus.value?.status?.find(elem => elem.channel === this.channelId);
|
||||
|
||||
if (specificChannelStatus) {
|
||||
deviceInfo.name = specificChannelStatus.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
private async ptzOp(op: string, speed: number) {
|
||||
async getPtzPresets(): Promise<PtzPreset[]> {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'GetPtzPreset');
|
||||
const body = [
|
||||
{
|
||||
cmd: "GetPtzPreset",
|
||||
action: 1,
|
||||
param: {
|
||||
channel: this.channelId
|
||||
}
|
||||
}
|
||||
];
|
||||
const response = await this.requestWithLogin({
|
||||
url,
|
||||
responseType: 'json',
|
||||
method: 'POST'
|
||||
}, this.createReadable(body));
|
||||
return response.body?.[0]?.value?.PtzPreset?.filter(preset => preset.enable === 1);
|
||||
}
|
||||
|
||||
private async ptzOp(op: string, speed: number, id?: number) {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'PtzCtrl');
|
||||
|
||||
const createReadable = (data: any) => {
|
||||
const pt = new PassThrough();
|
||||
pt.write(Buffer.from(JSON.stringify(data)));
|
||||
pt.end();
|
||||
return pt;
|
||||
}
|
||||
|
||||
const c1 = this.requestWithLogin({
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'text',
|
||||
}, createReadable([
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
@@ -225,6 +312,7 @@ export class ReolinkCameraClient {
|
||||
op,
|
||||
speed,
|
||||
timeout: 1,
|
||||
id
|
||||
}
|
||||
},
|
||||
]));
|
||||
@@ -234,7 +322,7 @@ export class ReolinkCameraClient {
|
||||
const c2 = this.requestWithLogin({
|
||||
url,
|
||||
method: 'POST',
|
||||
}, createReadable([
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
@@ -248,7 +336,37 @@ export class ReolinkCameraClient {
|
||||
this.console.log(await c2);
|
||||
}
|
||||
|
||||
private async presetOp(speed: number, id: number) {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'PtzCtrl');
|
||||
|
||||
const c1 = this.requestWithLogin({
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'text',
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "PtzCtrl",
|
||||
param: {
|
||||
channel: this.channelId,
|
||||
op: 'ToPos',
|
||||
speed,
|
||||
id
|
||||
}
|
||||
},
|
||||
]));
|
||||
}
|
||||
|
||||
async ptz(command: PanTiltZoomCommand) {
|
||||
// reolink doesnt accept signed values to ptz
|
||||
// in favor of explicit direction.
|
||||
// so we need to convert the signed values to abs explicit direction.
|
||||
if (command.preset && !Number.isNaN(Number(command.preset))) {
|
||||
await this.presetOp(1, Number(command.preset));
|
||||
return;
|
||||
}
|
||||
|
||||
let op = '';
|
||||
if (command.pan < 0)
|
||||
op += 'Left';
|
||||
@@ -260,16 +378,17 @@ export class ReolinkCameraClient {
|
||||
op += 'Up';
|
||||
|
||||
if (op) {
|
||||
await this.ptzOp(op, Math.round((command?.pan || command?.tilt || 1) * 10));
|
||||
await this.ptzOp(op, Math.ceil(Math.abs(command?.pan || command?.tilt || 1) * 10));
|
||||
}
|
||||
|
||||
op = undefined;
|
||||
if (command.zoom < 0)
|
||||
op = 'ZoomDec';
|
||||
else if (command.zoom > 0)
|
||||
op = 'ZoomInc';
|
||||
|
||||
if (op) {
|
||||
await this.ptzOp(op, Math.round((command?.zoom || 1) * 10));
|
||||
await this.ptzOp(op, Math.ceil(Math.abs(command?.zoom || 1) * 10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,12 +396,6 @@ export class ReolinkCameraClient {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
const params = url.searchParams;
|
||||
params.set('cmd', 'AudioAlarmPlay');
|
||||
const createReadable = (data: any) => {
|
||||
const pt = new PassThrough();
|
||||
pt.write(Buffer.from(JSON.stringify(data)));
|
||||
pt.end();
|
||||
return pt;
|
||||
}
|
||||
|
||||
let alarmMode;
|
||||
if (duration) {
|
||||
@@ -302,7 +415,7 @@ export class ReolinkCameraClient {
|
||||
url,
|
||||
method: 'POST',
|
||||
responseType: 'json',
|
||||
}, createReadable([
|
||||
}, this.createReadable([
|
||||
{
|
||||
cmd: "AudioAlarmPlay",
|
||||
action: 0,
|
||||
@@ -317,4 +430,39 @@ export class ReolinkCameraClient {
|
||||
data: response.body,
|
||||
};
|
||||
}
|
||||
|
||||
async getBatteryInfo() {
|
||||
const url = new URL(`http://${this.host}/api.cgi`);
|
||||
|
||||
const body = [
|
||||
{
|
||||
cmd: "GetBatteryInfo",
|
||||
action: 0,
|
||||
param: { channel: this.channelId }
|
||||
},
|
||||
{
|
||||
cmd: "GetChannelstatus",
|
||||
}
|
||||
];
|
||||
|
||||
const response = await this.requestWithLogin({
|
||||
url,
|
||||
responseType: 'json',
|
||||
method: 'POST',
|
||||
}, this.createReadable(body));
|
||||
|
||||
const error = response.body?.find(elem => elem.error)?.error;
|
||||
if (error) {
|
||||
this.console.error('error during call to getBatteryInfo', error);
|
||||
}
|
||||
|
||||
const batteryInfoEntry = response.body.find(entry => entry.cmd === 'GetBatteryInfo')?.value?.Battery;
|
||||
const channelStatusEntry = response.body.find(entry => entry.cmd === 'GetChannelstatus')?.value?.status
|
||||
?.find(chStatus => chStatus.channel === this.channelId)
|
||||
|
||||
return {
|
||||
batteryPercent: batteryInfoEntry?.batteryPercent,
|
||||
sleep: channelStatusEntry?.sleep === 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
plugins/ring/.vscode/settings.json
vendored
2
plugins/ring/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
53
plugins/ring/package-lock.json
generated
53
plugins/ring/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/ring",
|
||||
"version": "0.0.140",
|
||||
"version": "0.0.144",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/ring",
|
||||
"version": "0.0.140",
|
||||
"version": "0.0.144",
|
||||
"dependencies": {
|
||||
"@koush/ring-client-api": "file:../../external/ring-client-api",
|
||||
"@scrypted/common": "file:../../common",
|
||||
@@ -26,13 +26,13 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
"http-auth-utils": "^5.0.1",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@types/node": "^20.11.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
},
|
||||
"../../external/ring-client-api": {
|
||||
@@ -42,30 +42,29 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.4.8",
|
||||
"@changesets/cli": "^2.26.1",
|
||||
"@swc-node/register": "^1.6.2",
|
||||
"turbo": "^1.8.5"
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"@swc-node/register": "^1.6.6",
|
||||
"turbo": "^1.10.12"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.101",
|
||||
"version": "0.3.62",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"adm-zip": "^0.5.14",
|
||||
"axios": "^1.7.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-const-enum": "^1.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"rimraf": "^6.0.1",
|
||||
"tmp": "^0.2.3",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.5.4",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
@@ -77,11 +76,11 @@
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"@types/node": "^22.1.0",
|
||||
"@types/stringify-object": "^4.0.5",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
"ts-node": "^10.9.2",
|
||||
"typedoc": "^0.26.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/ring-client-api": {
|
||||
|
||||
@@ -45,5 +45,5 @@
|
||||
"got": "11.8.6",
|
||||
"socket.io-client": "^2.5.0"
|
||||
},
|
||||
"version": "0.0.140"
|
||||
"version": "0.0.144"
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import { RefreshPromise } from "@scrypted/common/src/promise-utils";
|
||||
import { connectRTCSignalingClients } from '@scrypted/common/src/rtc-signaling';
|
||||
import { RtspServer } from '@scrypted/common/src/rtsp-server';
|
||||
import { addTrackControls, parseSdp, replacePorts } from '@scrypted/common/src/sdp-utils';
|
||||
import sdk, { BinarySensor, Camera, Device, DeviceProvider, FFmpegInput, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
|
||||
import sdk, { BinarySensor, Camera, Device, DeviceProvider, FFmpegInput, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingOptions, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
|
||||
import child_process, { ChildProcess } from 'child_process';
|
||||
import dgram from 'dgram';
|
||||
import { RtcpReceiverInfo, RtcpRrPacket } from '../../../external/werift/packages/rtp/src/rtcp/rr';
|
||||
import { RtpPacket } from '../../../external/werift/packages/rtp/src/rtp/rtp';
|
||||
import { ProtectionProfileAes128CmHmacSha1_80 } from '../../../external/werift/packages/rtp/src/srtp/const';
|
||||
import { SrtcpSession } from '../../../external/werift/packages/rtp/src/srtp/srtcp';
|
||||
import { VideoSearchResult, BasicPeerConnection, CameraData, clientApi, isStunMessage, RingBaseApi, RingCamera, RtpDescription, rxjs, SimpleWebRtcSession, SipSession, StreamingSession } from './ring-client-api';
|
||||
import { BasicPeerConnection, CameraData, clientApi, isStunMessage, RingBaseApi, RingCamera, RtpDescription, rxjs, SimpleWebRtcSession, SipSession, StreamingSession, VideoSearchResult } from './ring-client-api';
|
||||
import { encodeSrtpOptions, getPayloadType, getSequenceNumber, isRtpMessagePayloadType } from './srtp-utils';
|
||||
|
||||
const STREAM_TIMEOUT = 120000;
|
||||
@@ -101,10 +101,6 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
|
||||
this.console.log(camera.name, 'onDoorbellPressed', e);
|
||||
this.triggerBinaryState();
|
||||
});
|
||||
camera.onDoorbellPressedPolling.subscribe(async e => {
|
||||
this.console.log(camera.name, 'onDoorbellPressed', e);
|
||||
this.triggerBinaryState();
|
||||
});
|
||||
let motionTimeout: NodeJS.Timeout;
|
||||
const resetTimeout = () => {
|
||||
clearTimeout(motionTimeout);
|
||||
@@ -120,16 +116,6 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
|
||||
}
|
||||
this.motionDetected = motionDetected;
|
||||
});
|
||||
camera.onMotionDetectedPolling?.subscribe(async motionDetected => {
|
||||
if (motionDetected) {
|
||||
this.console.log(camera.name, 'onMotionDetected');
|
||||
resetTimeout();
|
||||
}
|
||||
else {
|
||||
clearTimeout(motionTimeout);
|
||||
}
|
||||
this.motionDetected = motionDetected;
|
||||
});
|
||||
camera.onBatteryLevel?.subscribe(async () => {
|
||||
this.batteryLevel = camera.batteryLevel;
|
||||
});
|
||||
@@ -279,7 +265,7 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
|
||||
this.stopSession();
|
||||
|
||||
|
||||
const { clientPromise: playbackPromise, port: playbackPort, url: clientUrl } = await listenZeroSingleClient();
|
||||
const { clientPromise: playbackPromise, port: playbackPort, url: clientUrl } = await listenZeroSingleClient('127.0.0.1');
|
||||
|
||||
const useRtsp = this.useRtsp;
|
||||
|
||||
@@ -527,6 +513,11 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
|
||||
let answerSdp: string;
|
||||
const simple = this.camera.createSimpleWebRtcSession();
|
||||
|
||||
const options: RTCSignalingOptions = {
|
||||
requiresOffer: true,
|
||||
disableTrickle: true,
|
||||
};
|
||||
|
||||
await connectRTCSignalingClients(this.console, session, {
|
||||
type: 'offer',
|
||||
audio: {
|
||||
@@ -537,6 +528,10 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
|
||||
},
|
||||
getUserMediaSafariHack: true,
|
||||
}, {
|
||||
__proxy_props: {
|
||||
options,
|
||||
},
|
||||
options,
|
||||
createLocalDescription: async (type: 'offer' | 'answer', setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate) => {
|
||||
if (type !== 'answer')
|
||||
throw new Error('Ring Camera default endpoint only supports RTC answer');
|
||||
@@ -639,7 +634,7 @@ export class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvid
|
||||
createPeerConnection: () => basicPc,
|
||||
});
|
||||
ringSession.connection.onMessage.subscribe(message => this.console.log('incoming message', message));
|
||||
ringSession.onCallEnded.subscribe(() => this.console.error('call ended', ringSession.sessionId));
|
||||
ringSession.onCallEnded.subscribe(() => this.console.error('call ended'));
|
||||
|
||||
sessionControl = new RingWebSocketRTCSessionControl(ringSession, onConnectionState);
|
||||
|
||||
|
||||
@@ -69,12 +69,6 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings,
|
||||
description: 'Optional: If supplied will on show this locationID.',
|
||||
hide: true,
|
||||
},
|
||||
cameraDingsPollingSeconds: {
|
||||
title: 'Poll Interval',
|
||||
type: 'number',
|
||||
description: 'Optional: Change the default polling interval for motion and doorbell events.',
|
||||
defaultValue: 5,
|
||||
},
|
||||
nightModeBypassAlarmState: {
|
||||
title: 'Night Mode Bypass Alarm State',
|
||||
description: 'Set this to enable the "Night" option on the alarm panel. When arming in "Night" mode, all open sensors will be bypassed and the alarm will be armed to the selected option.',
|
||||
@@ -96,12 +90,6 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings,
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.settingsStorage.settings.cameraDingsPollingSeconds.onGet = async () => {
|
||||
return {
|
||||
hide: !this.settingsStorage.values.polling,
|
||||
};
|
||||
}
|
||||
|
||||
this.discoverDevices()
|
||||
.catch(e => this.console.error('discovery failure', e));
|
||||
}
|
||||
@@ -172,7 +160,7 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings,
|
||||
ffmpegPath: await mediaManager.getFFmpegPath(),
|
||||
locationIds,
|
||||
cameraStatusPollingSeconds: this.settingsStorage.values.polling ? cameraStatusPollingSeconds : undefined,
|
||||
cameraDingsPollingSeconds: this.settingsStorage.values.polling ? this.settingsStorage.values.cameraDingsPollingSeconds : undefined,
|
||||
locationModePollingSeconds: this.settingsStorage.values.polling ? cameraStatusPollingSeconds : undefined,
|
||||
systemId: this.settingsStorage.values.systemId,
|
||||
}, {
|
||||
createPeerConnection: () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ export function loadSharp() {
|
||||
|
||||
export const ImageReaderNativeId = 'imagereader';
|
||||
|
||||
async function createVipsMediaObject(image: VipsImage): Promise<Image & MediaObject> {
|
||||
export async function createVipsMediaObject(image: VipsImage): Promise<Image & MediaObject> {
|
||||
const ret: Image & MediaObject = await sdk.mediaManager.createMediaObject(image, ScryptedMimeTypes.Image, {
|
||||
sourceId: image.sourceId,
|
||||
width: image.width,
|
||||
|
||||
2
plugins/unifi-protect/.vscode/settings.json
vendored
2
plugins/unifi-protect/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
4
plugins/unifi-protect/package-lock.json
generated
4
plugins/unifi-protect/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/unifi-protect",
|
||||
"version": "0.0.154",
|
||||
"version": "0.0.156",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/unifi-protect",
|
||||
"version": "0.0.154",
|
||||
"version": "0.0.156",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/unifi-protect": "file:../../external/unifi-protect",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/unifi-protect",
|
||||
"version": "0.0.154",
|
||||
"version": "0.0.156",
|
||||
"description": "Unifi Protect Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -321,6 +321,10 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
|
||||
this.console.log('skipping camera that is adopted by another nvr', camera.id, camera.name);
|
||||
continue;
|
||||
}
|
||||
if (!camera.isAdopted) {
|
||||
this.console.log('skipping camera that is not adopted', camera.id, camera.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let needUpdate = false;
|
||||
for (const channel of camera.channels) {
|
||||
@@ -598,6 +602,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
|
||||
anonymousDeviceId: {},
|
||||
id: {},
|
||||
nativeId: {},
|
||||
host: {},
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -607,14 +612,15 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
|
||||
return this.storageSettings.values.idMaps.nativeId?.[nativeId] || nativeId;
|
||||
}
|
||||
|
||||
getNativeId(device: { id?: string, mac?: string; anonymousDeviceId?: string }, update: boolean) {
|
||||
const { id, mac, anonymousDeviceId } = device;
|
||||
getNativeId(device: { id?: string, mac?: string; anonymousDeviceId?: string, host?: string }, update: boolean) {
|
||||
const { id, mac, anonymousDeviceId,host } = device;
|
||||
const idMaps = this.storageSettings.values.idMaps;
|
||||
|
||||
// try to find an existing nativeId given the mac and anonymous device id
|
||||
const found = (mac && idMaps.mac[mac])
|
||||
|| (anonymousDeviceId && idMaps.anonymousDeviceId[anonymousDeviceId])
|
||||
|| (id && idMaps.id[id])
|
||||
|| (host && idMaps.host[host])
|
||||
;
|
||||
|
||||
// use the found id if one exists (device got provisioned a new id), otherwise use the id provided by the device.
|
||||
@@ -623,7 +629,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
|
||||
if (!update)
|
||||
return nativeId;
|
||||
|
||||
// map the mac and anonymous device id to the native id.
|
||||
// map the mac, host, and anonymous device id to the native id.
|
||||
if (mac) {
|
||||
idMaps.mac ||= {};
|
||||
idMaps.mac[mac] = nativeId;
|
||||
@@ -632,6 +638,10 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
|
||||
idMaps.anonymousDeviceId ||= {};
|
||||
idMaps.anonymousDeviceId[anonymousDeviceId] = nativeId;
|
||||
}
|
||||
if (host) {
|
||||
idMaps.host ||= {};
|
||||
idMaps.host[host] = nativeId;
|
||||
}
|
||||
|
||||
// map the id and native id to each other.
|
||||
idMaps.id ||= {};
|
||||
|
||||
2
plugins/webrtc/.vscode/settings.json
vendored
2
plugins/webrtc/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "scrypted-nvr",
|
||||
}
|
||||
4
plugins/webrtc/package-lock.json
generated
4
plugins/webrtc/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.55",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.55",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.2.51",
|
||||
"version": "0.2.55",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
@@ -23,7 +23,6 @@
|
||||
"name": "WebRTC Plugin",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"EngineIOHandler",
|
||||
"Settings",
|
||||
"MediaConverter",
|
||||
"MixinProvider",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user