Compare commits

...

78 Commits

Author SHA1 Message Date
Koushik Dutta
544531122d server: stop console spam if mixin is deleted 2024-01-02 22:13:13 -08:00
Koushik Dutta
778f0b7ad1 proxmox: update script 2024-01-02 14:03:17 -08:00
Koushik Dutta
35e8a86593 snapshot: warn qemu cpu 2024-01-02 13:55:17 -08:00
Koushik Dutta
c370773af4 common: missing file 2024-01-02 12:12:51 -08:00
Koushik Dutta
184f293b92 core: fix new script creation 2024-01-02 09:13:06 -08:00
Koushik Dutta
6e10172f7e Merge branch 'main' of github.com:koush/scrypted 2024-01-01 21:53:12 -08:00
Koushik Dutta
c5ae2cd539 onvif: forgotten file re motion sensor reset 2024-01-01 21:52:54 -08:00
Koushik Dutta
e40566e89c reolink: ptz 2024-01-01 21:52:43 -08:00
Koushik Dutta
59ccd4e4d8 docker: remove armv7 2024-01-01 18:24:51 -08:00
Koushik Dutta
ae80eb7727 docker: remove armv7 2024-01-01 18:24:35 -08:00
Koushik Dutta
f054172dcf postrelease 2024-01-01 15:43:52 -08:00
Koushik Dutta
0d7fb9e13c postrelease 2024-01-01 14:46:06 -08:00
Koushik Dutta
a526816b07 sdk/server: add mechanism for requesting device refresh 2024-01-01 14:44:00 -08:00
Koushik Dutta
563e16b08f sdk: update 2024-01-01 14:42:33 -08:00
Koushik Dutta
fd56990d64 core: watch for script worker exits 2024-01-01 14:12:27 -08:00
Koushik Dutta
d7aaf57e8f core: move scripts into their own workers. 2024-01-01 14:10:17 -08:00
Koushik Dutta
a2d50d54d5 proxmox: delete coral, make user install it. 2024-01-01 00:21:59 -08:00
Koushik Dutta
1f86745252 proxmox: install script 2024-01-01 00:15:09 -08:00
Koushik Dutta
1f4343ba2e proxmox: install script 2024-01-01 00:14:54 -08:00
Koushik Dutta
3ad311898f docker: pillow simd 2023-12-31 22:12:54 -08:00
Koushik Dutta
799e5b53c7 Merge branch 'main' of github.com:koush/scrypted 2023-12-31 21:57:08 -08:00
Koushik Dutta
833e5b34ab docker/lxc: update 2023-12-31 21:57:03 -08:00
Koushik Dutta
c99ac28e89 wyze: write_eof may fail? 2023-12-30 18:53:53 -08:00
Koushik Dutta
841475cb97 wyze: better ffmpeg kill method 2023-12-30 18:53:15 -08:00
Koushik Dutta
4b03a3a458 reolink: debounce motion on motion end 2023-12-28 13:32:14 -08:00
Koushik Dutta
d686dd815c videoanalysis: fix bug with modified default classes 2023-12-28 12:20:28 -08:00
Koushik Dutta
e0386a8922 tensorflow-lite: remove python codecs dependency 2023-12-28 12:13:36 -08:00
Koushik Dutta
9ef3478c88 Merge branch 'main' of github.com:koush/scrypted 2023-12-28 12:08:55 -08:00
Koushik Dutta
690d160f33 openvino: update openvino dependency 2023-12-28 12:08:37 -08:00
Koushik Dutta
59ff987bca server: update deps 2023-12-28 09:56:42 -08:00
Koushik Dutta
1669f17c96 postbeta 2023-12-27 22:38:23 -08:00
Koushik Dutta
b0bfd4e05e wyze: update dwb 2023-12-27 21:56:32 -08:00
Koushik Dutta
7152671913 wyze: update dwb 2023-12-27 21:55:46 -08:00
Koushik Dutta
537c178699 postbeta 2023-12-27 21:51:37 -08:00
Koushik Dutta
77ecee110b videoanalysis: fixup detection types for nvr 2023-12-27 21:40:50 -08:00
Koushik Dutta
29b163a7d8 wyze: update dwb 2023-12-27 21:01:51 -08:00
Koushik Dutta
5d74e80e90 postbeta 2023-12-27 20:13:10 -08:00
Koushik Dutta
764b6441d5 postbeta 2023-12-27 20:12:58 -08:00
Koushik Dutta
e2c43cb4ff onvif: missing file 2023-12-27 20:12:34 -08:00
Koushik Dutta
7b6d094e8c wyze: improve cpu usage 2023-12-27 20:12:06 -08:00
Koushik Dutta
3dfb2db02a snapshot: update deps 2023-12-27 20:11:45 -08:00
Koushik Dutta
e5a549db6a update werift 2023-12-27 19:31:34 -08:00
Koushik Dutta
d500c815fe local: add support for intel and tflite installation 2023-12-26 16:23:59 -08:00
Koushik Dutta
5f71c59b5a local: add support for intel and tflite installation 2023-12-26 16:15:05 -08:00
Koushik Dutta
27407942a5 local: add support for intel and tflite installation 2023-12-26 16:14:55 -08:00
Koushik Dutta
11b6963744 docker: remove libvips 2023-12-26 15:28:39 -08:00
Koushik Dutta
b9ee8866f0 docker: logging 2023-12-26 15:20:21 -08:00
Koushik Dutta
bc80d31eaa docker: add logging 2023-12-26 15:14:19 -08:00
Koushik Dutta
327688232c docker: more prompt fixups 2023-12-26 14:25:30 -08:00
Koushik Dutta
2883a4ce46 docker: more prompt fixups 2023-12-26 14:24:04 -08:00
Koushik Dutta
1ad2fb915d docker: more prompt fixups 2023-12-26 14:23:28 -08:00
Koushik Dutta
fb701a32b7 docker: intel graphics script 2023-12-26 14:19:56 -08:00
Koushik Dutta
7a8c661bb3 docker: remove bash invocation 2023-12-26 14:06:53 -08:00
Koushik Dutta
54d72fb371 docker: gpg should overwrite 2023-12-26 13:56:44 -08:00
Koushik Dutta
e48812cec7 linux: SERVICE_USER_ROOT flag 2023-12-26 13:53:46 -08:00
Koushik Dutta
6c2db072c4 linux: warn root install 2023-12-26 13:51:33 -08:00
Koushik Dutta
4bf2c0b614 docker: add node gpg 2023-12-26 13:49:13 -08:00
Koushik Dutta
a93cdb0ae4 docker: fix quoting 2023-12-26 13:21:24 -08:00
Koushik Dutta
ff85b7abc6 docker: update all node install scripts 2023-12-26 13:17:32 -08:00
Koushik Dutta
46dfb8d98e docker: Fix node version arg 2023-12-26 13:11:51 -08:00
Koushik Dutta
5240200f0f docker: update node install script 2023-12-26 13:04:56 -08:00
Koushik Dutta
3bcb94fc6b reolink: increase motion timeout 2023-12-25 12:03:14 -08:00
Koushik Dutta
a596bc712c reolink: add support for native object detection 2023-12-25 12:02:25 -08:00
Koushik Dutta
f6d2dc456e server: fix bug with constantly reissuing certs 2023-12-24 21:49:08 -08:00
Koushik Dutta
441cce239e wyze: set video timeout to 5s 2023-12-23 19:15:27 -08:00
Koushik Dutta
3016df32d1 Merge branch 'main' of github.com:koush/scrypted 2023-12-23 18:38:11 -08:00
Koushik Dutta
5bd8ed0b1a wyze: fix video inactivity leaks 2023-12-23 18:38:07 -08:00
Koushik Dutta
79286a5138 docker: allow root install 2023-12-22 22:56:09 -08:00
Koushik Dutta
8874e01072 wyze: fix broken setting 2023-12-22 15:39:28 -08:00
Koushik Dutta
0223a9f0f6 wyze: use correct fps when fetching frames 2023-12-22 11:31:58 -08:00
Koushik Dutta
890c2667d0 wyze: implement ptz 2023-12-22 10:34:07 -08:00
Koushik Dutta
ca14764e17 wyze: suppress ffmpeg 2023-12-22 09:13:32 -08:00
Koushik Dutta
1030d7d03c rebroadcast: fix rtsp server with RFC4571 parser 2023-12-22 08:23:43 -08:00
Koushik Dutta
2d40320868 ha: update 2023-12-21 22:53:01 -08:00
Koushik Dutta
3e32c3d019 werift: update 2023-12-21 22:50:32 -08:00
Koushik Dutta
1f9fa3966f wyze: send codec info on startup 2023-12-21 22:49:59 -08:00
Koushik Dutta
c2d86237d6 wyze: refactor for multiprocessing 2023-12-21 21:55:34 -08:00
Koushik Dutta
5cfcfafc00 postrelease 2023-12-21 21:51:42 -08:00
65 changed files with 1750 additions and 1066 deletions

View File

@@ -26,12 +26,6 @@ jobs:
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:

View File

@@ -52,12 +52,6 @@ jobs:
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:

View File

@@ -58,18 +58,13 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
worker.worker.terminate();
}
const smProxy = new SystemManagerImpl();
smProxy.state = systemManager.getSystemState();
const apiProxy = new PluginAPIProxy(sdk.pluginHostAPI);
smProxy.api = apiProxy;
const allParams = Object.assign({}, params, {
sdk,
fs: require('realfs'),
fetch,
ScryptedDeviceBase,
MixinDeviceBase,
systemManager: smProxy,
systemManager,
deviceManager,
endpointManager,
mediaManager,
@@ -104,7 +99,6 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
return {
value,
defaultExport,
apiProxy,
};
}
catch (e) {

View File

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

View File

@@ -25,10 +25,14 @@ RUN apt-get update && apt-get -y install \
apt-get -y upgrade
ARG NODE_VERSION=18
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
# python native
RUN echo "Installing python."
RUN apt-get -y install \
python3 \
python3-dev \
@@ -38,36 +42,21 @@ RUN apt-get -y install \
# these are necessary for pillow-simd, additional on disk size is small
# but could consider removing this.
RUN echo "Installing pillow-simd dependencies."
RUN apt-get -y install \
libjpeg-dev zlib1g-dev
# plugins support fallback to pillow, but vips is faster.
RUN apt-get -y install \
libvips
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
RUN echo "Installing gstreamer."
RUN apt-get -y install \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
gstreamer1.0-vaapi
# python3 gstreamer bindings
RUN echo "Installing gstreamer bindings."
RUN apt-get -y install \
python3-gst-1.0
# armv7l does not have wheels for any of these
# and compile times would forever, if it works at all.
# furthermore, it's possible to run 32bit docker on 64bit arm,
# which causes weird behavior in python which looks at the arch version
# which still reports 64bit, even if running in 32bit docker.
# this scenario is not supported and will be reported at runtime.
# this bit is not necessary on amd64, but leaving it for consistency.
RUN apt-get -y install \
python3-matplotlib \
python3-numpy \
python3-opencv \
python3-pil \
python3-skimage
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
@@ -87,20 +76,11 @@ RUN python3 -m pip install debugpy typing_extensions psutil
FROM header as base
# intel opencl gpu for openvino
RUN bash -c "if [ \"$(uname -m)\" == \"x86_64\" ]; \
then \
apt-get update && apt-get install -y gpg-agent && \
rm -f /usr/share/keyrings/intel-graphics.gpg && \
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg && \
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list && \
apt-get -y update && \
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free && \
apt-get -y dist-upgrade; \
fi"
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository ppa:deadsnakes/ppa && \
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
apt-get -y install \
python3.9 \
python3.9-dev \

View File

@@ -17,7 +17,10 @@ RUN apt-get update && apt-get -y install \
apt-get -y upgrade
ARG NODE_VERSION=18
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
# python native

View File

@@ -9,7 +9,7 @@ RUN apt-get -y update && \
# switch to nvm?
ARG NODE_VERSION=18
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && apt-get update && apt-get install -y nodejs
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
ENV SCRYPTED_CAN_RESTART="true"

View File

@@ -1,4 +1,4 @@
./template/generate-dockerfile.sh
docker build -t koush/scrypted-common -f Dockerfile.common . && \
docker build -t koush/scrypted-common -f Dockerfile.full . && \
docker build -t koush/scrypted -f Dockerfile.local .

View File

@@ -0,0 +1,16 @@
if [ "$(uname -m)" = "x86_64" ]
then
echo "Installing Intel graphics packages."
apt-get update && apt-get install -y gpg-agent &&
rm -f /usr/share/keyrings/intel-graphics.gpg &&
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --yes --output /usr/share/keyrings/intel-graphics.gpg &&
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list &&
apt-get -y update &&
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free &&
apt-get -y dist-upgrade;
exit $?
else
echo "Intel graphics will not be installed on this architecture."
fi
exit 0

View File

@@ -6,19 +6,6 @@ then
exit 0
fi
if [ "$SERVICE_USER" == "root" ]
then
echo "Scrypted SERVICE_USER root is not allowed."
exit 1
fi
USER_HOME=$(eval echo ~$SERVICE_USER)
SCRYPTED_HOME=$USER_HOME/.scrypted
mkdir -p $SCRYPTED_HOME
set -e
cd $SCRYPTED_HOME
function readyn() {
while true; do
read -p "$1 (y/n) " yn
@@ -30,6 +17,22 @@ function readyn() {
done
}
if [ "$SERVICE_USER" == "root" ]
then
readyn "Scrypted will store its files in the root user home directory. Running as a non-root user is recommended. Are you sure?"
if [ "$yn" == "n" ]
then
exit 1
fi
fi
USER_HOME=$(eval echo ~$SERVICE_USER)
SCRYPTED_HOME=$USER_HOME/.scrypted
mkdir -p $SCRYPTED_HOME
set -e
cd $SCRYPTED_HOME
readyn "Install Docker?"
if [ "$yn" == "y" ]

View File

@@ -4,20 +4,11 @@
FROM header as base
# intel opencl gpu for openvino
RUN bash -c "if [ \"$(uname -m)\" == \"x86_64\" ]; \
then \
apt-get update && apt-get install -y gpg-agent && \
rm -f /usr/share/keyrings/intel-graphics.gpg && \
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg && \
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list && \
apt-get -y update && \
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free && \
apt-get -y dist-upgrade; \
fi"
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository ppa:deadsnakes/ppa && \
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
apt-get -y install \
python3.9 \
python3.9-dev \

View File

@@ -22,10 +22,14 @@ RUN apt-get update && apt-get -y install \
apt-get -y upgrade
ARG NODE_VERSION=18
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
# python native
RUN echo "Installing python."
RUN apt-get -y install \
python3 \
python3-dev \
@@ -35,36 +39,21 @@ RUN apt-get -y install \
# these are necessary for pillow-simd, additional on disk size is small
# but could consider removing this.
RUN echo "Installing pillow-simd dependencies."
RUN apt-get -y install \
libjpeg-dev zlib1g-dev
# plugins support fallback to pillow, but vips is faster.
RUN apt-get -y install \
libvips
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
RUN echo "Installing gstreamer."
RUN apt-get -y install \
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
gstreamer1.0-vaapi
# python3 gstreamer bindings
RUN echo "Installing gstreamer bindings."
RUN apt-get -y install \
python3-gst-1.0
# armv7l does not have wheels for any of these
# and compile times would forever, if it works at all.
# furthermore, it's possible to run 32bit docker on 64bit arm,
# which causes weird behavior in python which looks at the arch version
# which still reports 64bit, even if running in 32bit docker.
# this scenario is not supported and will be reported at runtime.
# this bit is not necessary on amd64, but leaving it for consistency.
RUN apt-get -y install \
python3-matplotlib \
python3-numpy \
python3-opencv \
python3-pil \
python3-skimage
# allow pip to install to system
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED

View File

@@ -12,6 +12,26 @@ then
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
}
if [ "$SERVICE_USER" = "root" ] && [ -z "$SERVICE_USER_ROOT" ]
then
readyn "Scrypted will store its files in the root user home directory. Running as a non-root user is recommended. Are you sure?"
if [ "$yn" == "n" ]
then
exit 1
fi
fi
echo "Stopping existing service if it is running..."
systemctl stop scrypted.service
@@ -49,6 +69,14 @@ ENV() {
}
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/template/Dockerfile.full.header)
if [ -z "$SCRYPTED_INSTALL_ENVIRONMENT" ]
then
SCRYPTED_INSTALL_ENVIRONMENT=local
fi
if [ "$SCRYPTED_INSTALL_ENVIRONMENT" = "lxc" ]
then
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/template/Dockerfile.full.footer)
fi
if [ -z "$SERVICE_USER" ]
then
@@ -56,12 +84,6 @@ then
exit 0
fi
if [ "$SERVICE_USER" == "root" ]
then
echo "Scrypted SERVICE_USER root is not allowed."
exit 1
fi
# this is not RUN as we do not care about the result
USER_HOME=$(eval echo ~$SERVICE_USER)
echo "Setting permissions on $USER_HOME/.scrypted"
@@ -84,6 +106,7 @@ ExecStart=/usr/bin/npx -y scrypted serve
Restart=on-failure
RestartSec=3
Environment="NODE_OPTIONS=$NODE_OPTIONS"
Environment="SCRYPTED_INSTALL_ENVIRONMENT=$SCRYPTED_INSTALL_ENVIRONMENT"
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,66 @@
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.72.0
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
if [ -z "$VMID" ]
then
VMID=10443
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 "pct restore failed"
echo ""
echo "This may be caused by the server's 'local' storage not supporting containers."
echo "Try running this script again with a different storage device (local-lvm, local-zfs). For example:"
echo ""
echo "bash $0 --storage local-lvm"
echo ""
exit 1
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"
udevadm control --reload-rules && udevadm trigger
fi
echo "Scrypted setup is complete and the container resources can be started."
echo "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."

View File

@@ -10,14 +10,14 @@
"port": 10081,
"request": "attach",
"skipFiles": [
"**/plugin-remote-worker.*",
"**/plugin-console.*",
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "pwa-node"
"type": "node"
}
]
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.1.150",
"version": "0.2.3",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -1,3 +0,0 @@
export class Timer {
}

View File

@@ -9,14 +9,12 @@ import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
import { LauncherMixin } from './launcher-mixin';
import { MediaCore } from './media-core';
import { ScriptCore, ScriptCoreNativeId } from './script-core';
import { UsersCore, UsersNativeId } from './user';
import { newScript, ScriptCore, ScriptCoreNativeId } from './script-core';
import { TerminalService, TerminalServiceNativeId } from './terminal-service';
import { UsersCore, UsersNativeId } from './user';
const { systemManager, deviceManager, endpointManager } = sdk;
const indexHtml = fs.readFileSync('dist/index.html').toString();
export function getAddresses() {
const addresses: string[] = [];
for (const [iface, nif] of Object.entries(os.networkInterfaces())) {
@@ -61,10 +59,14 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
},
}
});
indexHtml: string;
constructor() {
super();
this.indexHtml = fs.readFileSync('dist/index.html').toString();
(async () => {
await deviceManager.onDeviceDiscovered(
{
@@ -226,7 +228,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
const endpoint = await endpointManager.getPublicCloudEndpoint();
const u = new URL(endpoint);
const rewritten = indexHtml
const rewritten = this.indexHtml
.replace('href="manifest.json"', `href="manifest.json${u.search}"`)
.replace('href="img/icons/apple-touch-icon-152x152.png"', `href="img/icons/apple-touch-icon-152x152.png${u.search}"`)
.replace('href="img/icons/safari-pinned-tab.svg"', `href="img/icons/safari-pinned-tab.svg${u.search}"`)
@@ -269,5 +271,6 @@ export default ScryptedCore;
export async function fork() {
return {
tsCompile,
newScript,
}
}
}

View File

@@ -1,33 +1,23 @@
import { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
import { Script } from "./script";
import sdk from '@scrypted/sdk';
import sdk, { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting } from '@scrypted/sdk';
import { randomBytes } from "crypto";
import fs from 'fs';
import path from "path/posix";
import { Worker } from "worker_threads";
import { Script } from "./script";
const { deviceManager } = sdk;
export const ScriptCoreNativeId = 'scriptcore';
interface ScriptWorker {
script: Script;
worker: Worker;
}
export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator, Readme {
scripts = new Map<string, Promise<Script>>();
scripts = new Map<string, ScriptWorker>();
constructor() {
super(ScriptCoreNativeId);
for (const nativeId of deviceManager.getNativeIds()) {
if (nativeId?.startsWith('script:')) {
const script = new Script(nativeId);
this.scripts.set(nativeId, (async () => {
if (script.providedInterfaces.length > 2) {
await script.run();
}
else {
this.reportScript(nativeId);
}
return script;
})());
}
}
}
async getCreateDeviceSettings(): Promise<Setting[]> {
@@ -51,6 +41,10 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
const nativeId = 'script:' + randomBytes(8).toString('hex');
await this.reportScript(nativeId, name?.toString());
const script = new Script(nativeId);
this.scripts.set(nativeId, {
script,
worker: undefined,
});
if (template) {
try {
await script.saveScript({
@@ -65,7 +59,6 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
catch (e) {
}
}
this.scripts.set(nativeId, Promise.resolve(script));
return nativeId;
}
@@ -84,10 +77,75 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
return await deviceManager.onDeviceDiscovered(device);
}
getDevice(nativeId: string) {
return this.scripts.get(nativeId);
async getDevice(nativeId: string) {
const e = this.scripts.get(nativeId);
if (e) {
if (e.script)
return e.script;
e.worker?.terminate();
this.scripts.delete(nativeId);
}
let script = new Script(nativeId);
let worker: Worker;
const triggerDeviceDiscover = async (name: string, type: ScryptedDeviceType, interfaces: string[]) => {
const e = this.scripts.get(nativeId);
if (e?.script == script)
e.script = undefined;
const device: Device = {
providerNativeId: this.nativeId,
name,
nativeId,
type,
interfaces,
refresh: true,
};
return await deviceManager.onDeviceDiscovered(device);
};
if (script.providedInterfaces.length > 2) {
const fork = sdk.fork<{
newScript: typeof newScript,
}>();
worker = fork.worker;
try {
script = await (await fork.result).newScript(nativeId, triggerDeviceDiscover);
}
catch (e) {
worker.terminate();
throw e;
}
}
worker?.on('exit', () => {
if (this.scripts.get(nativeId)?.worker === worker) {
this.scripts.delete(nativeId);
// notify the system that the device needs to be refreshed.
if (deviceManager.getNativeIds().includes(nativeId)) {
const script = new Script(nativeId);
triggerDeviceDiscover(script.providedName, script.providedType, script.providedInterfaces);
}
}
});
this.scripts.set(nativeId, {
script,
worker,
});
return script;
}
async releaseDevice(id: string, nativeId: string): Promise<void> {
const worker = this.scripts.get(nativeId)?.worker;
this.scripts.delete(nativeId);
worker?.terminate();
}
}
export async function newScript(nativeId: ScryptedNativeId, triggerDeviceDiscover: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
const script = new Script(nativeId, triggerDeviceDiscover);
await script.run();
return script;
}

View File

@@ -5,12 +5,10 @@ import { createScriptDevice, ScriptDeviceImpl } from "@scrypted/common/src/eval/
import { ScriptCoreNativeId } from "./script-core";
import { PluginAPIProxy } from "../../../server/src/plugin/plugin-api";
const { log, deviceManager, systemManager } = sdk;
const { deviceManager } = sdk;
export class Script extends ScryptedDeviceBase implements Scriptable, Program, ScriptDeviceImpl {
apiProxy: PluginAPIProxy;
constructor(nativeId: string) {
constructor(nativeId: string, public triggerDeviceDiscover?: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
super(nativeId);
}
@@ -18,6 +16,8 @@ export class Script extends ScryptedDeviceBase implements Scriptable, Program, S
this.storage.setItem('data', JSON.stringify({
'script.ts': source.script,
}));
this.triggerDeviceDiscover?.(this.providedName, this.providedType, this.providedInterfaces);
}
async loadScripts(): Promise<{ [filename: string]: ScriptSource; }> {
@@ -70,46 +70,38 @@ export class Script extends ScryptedDeviceBase implements Scriptable, Program, S
}
prepareScript() {
this.apiProxy?.removeListeners();
Object.assign(this, createScriptDevice([
ScryptedInterface.Scriptable,
ScryptedInterface.Program,
]));
}
async run(variables?: { [name: string]: any; }): Promise<any> {
async runInternal(script: string, variables?: { [name: string]: any; }): Promise<any> {
this.prepareScript();
try {
const data = JSON.parse(this.storage.getItem('data'));
const { value, defaultExport, apiProxy } = await scryptedEval(this, data['script.ts'], Object.assign({
const { value, defaultExport } = await scryptedEval(this, script, Object.assign({
device: this,
}, variables));
this.apiProxy = apiProxy;
await this.postRunScript(defaultExport);
return value;
}
catch (e) {
this.console.error('error loading script', e);
this.console.error('error evaluating script', e);
throw e;
}
}
async run(variables?: { [name: string]: any; }): Promise<any> {
const data = JSON.parse(this.storage.getItem('data'));
return this.runInternal(data['script.ts'], variables)
}
async eval(source: ScriptSource, variables?: { [name: string]: any }) {
this.prepareScript();
const { value, defaultExport, apiProxy } = await scryptedEval(this, source.script, Object.assign({
device: this,
}, variables));
this.apiProxy = apiProxy;
await this.postRunScript(defaultExport);
return value;
return this.runInternal(source.script, variables);
}
// will be done at runtime

View File

@@ -169,7 +169,7 @@ export default {
const fs = 20;
const box = `<rect x="${x}" y="${y}" width="${w}" height="${h}" stroke="${s}" stroke-width="2" fill="none" />
const box = `<rect x="${x}" y="${y}" width="${w}" height="${h}" stroke="${s}" stroke-width="1px" fill="none" />
<text x="${x}" y="${y}" font-size="${fs}" dx="0.05em" dy="0.05em" fill="black">${t}</text>
<text x="${x}" y="${y}" font-size="${fs}" fill="white">${t}</text>
`;

View File

@@ -45,7 +45,7 @@ export default {
for (const detection of this.lastDetection.detections || []) {
if (!detection.boundingBox) continue;
const svgScale = this.svgWidth / 1080;
const sw = 6 * svgScale;
const sw = 1;
const s = "red";
const x = detection.boundingBox[0];
const y = detection.boundingBox[1];

View File

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

View File

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

View File

@@ -104,12 +104,13 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
analyzeStop: number;
detectorSignal = new Deferred<void>().resolve();
released = false;
// settings: Setting[];
get detectorRunning() {
return !this.detectorSignal.finished;
}
constructor(public plugin: ObjectDetectionPlugin, mixinDevice: VideoCamera & Camera & MotionSensor & ObjectDetector & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string, public objectDetection: ObjectDetection & ScryptedDevice, public model: ObjectDetectionModel, group: string, public hasMotionType: boolean, public settings: Setting[]) {
constructor(public plugin: ObjectDetectionPlugin, mixinDevice: VideoCamera & Camera & MotionSensor & ObjectDetector & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string, public objectDetection: ObjectDetection & ScryptedDevice, public model: ObjectDetectionModel, group: string, public hasMotionType: boolean) {
super({
mixinDevice, mixinDeviceState,
mixinProviderNativeId: providerNativeId,
@@ -157,11 +158,12 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
}
getCurrentSettings() {
if (!this.settings)
const settings = this.model.settings;
if (!settings)
return;
const ret: { [key: string]: any } = {};
for (const setting of this.settings) {
for (const setting of settings) {
let value: any;
if (setting.multiple) {
value = safeParseJson(this.storage.getItem(setting.key));
@@ -209,8 +211,6 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
}
async register() {
const model = await this.objectDetection.getDetectionModel();
if (!this.hasMotionType) {
this.motionListener = this.cameraDevice.listen(ScryptedInterface.MotionSensor, async () => {
if (!this.cameraDevice.motionDetected) {
@@ -300,6 +300,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
async runPipelineAnalysisLoop(signal: Deferred<void>, options: {
suppress?: boolean,
}) {
await this.updateModel();
while (!signal.finished) {
if (options.suppress) {
this.console.log('Resuming motion processing after active motion timeout.');
@@ -536,8 +537,9 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
match = polygonOverlap(box, zoneValue);
}
if (match && zoneInfo?.classes?.length) {
match = zoneInfo.classes.includes(o.className);
const classes = zoneInfo?.classes?.length ? zoneInfo?.classes : this.model?.classes || [];
if (match && classes.length) {
match = classes.includes(o.className);
}
if (match) {
o.zones.push(zone);
@@ -611,7 +613,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
const ret = await this.getNativeObjectTypes();
if (!ret.classes)
ret.classes = [];
ret.classes.push(...(await this.objectDetection.getDetectionModel()).classes);
ret.classes.push(...(await this.objectDetection.getDetectionModel(this.getCurrentSettings())).classes);
return ret;
}
@@ -656,17 +658,22 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
return use.id;
}
async getMixinSettings(): Promise<Setting[]> {
const settings: Setting[] = [];
async updateModel() {
try {
this.settings = (await this.objectDetection.getDetectionModel(this.getCurrentSettings())).settings;
this.model = await this.objectDetection.getDetectionModel(this.getCurrentSettings());
}
catch (e) {
}
}
if (this.settings) {
settings.push(...this.settings.map(setting => {
async getMixinSettings(): Promise<Setting[]> {
const settings: Setting[] = [];
await this.updateModel();
const modelSettings = this.model.settings;
if (modelSettings) {
settings.push(...modelSettings.map(setting => {
let value: any;
if (setting.multiple) {
value = safeParseJson(this.storage.getItem(setting.key));
@@ -737,14 +744,15 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
});
if (!this.hasMotionType) {
const classes = this.model.classes;
settings.push(
{
subgroup,
key: `zoneinfo-classes-${name}`,
title: `Detection Classes`,
description: 'The detection classes to match inside this zone. An empty list will match all classes.',
choices: (await this.getObjectTypes())?.classes || [],
value: zi?.classes || [],
description: 'The detection classes to match inside this zone.',
choices: classes || [],
value: zi?.classes?.length ? zi?.classes : classes || [],
multiple: true,
},
);
@@ -817,7 +825,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
return this.storageSettings.putSetting(key, value);
}
if (value && this.settings?.find(s => s.key === key)?.multiple) {
if (value && this.model.settings?.find(s => s.key === key)?.multiple) {
vs = JSON.stringify(value);
}
@@ -895,9 +903,7 @@ class ObjectDetectorMixin extends MixinDeviceBase<ObjectDetection> implements Mi
const group = hasMotionType ? 'Motion Detection' : 'Object Detection';
// const group = objectDetection.name.replace('Plugin', '').trim();
const settings = this.model.settings;
const ret = new ObjectDetectionMixin(this.plugin, mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.mixinProviderNativeId, objectDetection, this.model, group, hasMotionType, settings);
const ret = new ObjectDetectionMixin(this.plugin, mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.mixinProviderNativeId, objectDetection, this.model, group, hasMotionType);
this.currentMixins.add(ret);
return ret;
}

View File

@@ -2,10 +2,16 @@ import { ObjectsDetected, ScryptedDevice, ScryptedDeviceBase, ScryptedInterface
import { OnvifCameraAPI, OnvifEvent } from "./onvif-api";
import { Destroyable } from "../../rtsp/src/rtsp";
export async function listenEvents(thisDevice: ScryptedDeviceBase, client: OnvifCameraAPI) {
export async function listenEvents(thisDevice: ScryptedDeviceBase, client: OnvifCameraAPI, motionTimeoutMs = 30000) {
let motionTimeout: NodeJS.Timeout;
let binaryTimeout: NodeJS.Timeout;
const triggerMotion = () => {
thisDevice.motionDetected = true;
clearTimeout(motionTimeout);
motionTimeout = setTimeout(() => thisDevice.motionDetected = false, motionTimeoutMs);
};
try {
await client.supportsEvents();
}
@@ -17,22 +23,31 @@ export async function listenEvents(thisDevice: ScryptedDeviceBase, client: Onvif
const events = client.listenEvents();
events.on('event', (event, className) => {
if (event === OnvifEvent.MotionBuggy) {
thisDevice.motionDetected = true;
clearTimeout(motionTimeout);
motionTimeout = setTimeout(() => thisDevice.motionDetected = false, 30000);
// some onvif cameras have motion with no associated motion end event.
triggerMotion();
return;
}
if (event === OnvifEvent.BinaryRingEvent) {
thisDevice.binaryState = true;
clearTimeout(binaryTimeout);
binaryTimeout = setTimeout(() => thisDevice.binaryState = false, 30000);
binaryTimeout = setTimeout(() => thisDevice.binaryState = false, motionTimeoutMs);
return;
}
if (event === OnvifEvent.MotionStart)
thisDevice.motionDetected = true;
else if (event === OnvifEvent.MotionStop)
thisDevice.motionDetected = false;
if (event === OnvifEvent.MotionStart) {
// some onvif cameras (like the reolink doorbell) have very short duration motion
// events.
// furthermore, cameras are not guaranteed to send motion stop events, which makes.
// for the sake of providing normalized motion durations through scrypted, debounce the motion.
triggerMotion();
// thisDevice.motionDetected = true;
}
else if (event === OnvifEvent.MotionStop) {
// reset the trigger to debounce per above.
triggerMotion();
// thisDevice.motionDetected = false;
}
else if (event === OnvifEvent.AudioStart)
thisDevice.audioDetected = true;
else if (event === OnvifEvent.AudioStop)
@@ -55,8 +70,9 @@ export async function listenEvents(thisDevice: ScryptedDeviceBase, client: Onvif
}
});
const ret: Destroyable = {
const ret = {
destroy() {
clearTimeout(motionTimeout);
try {
client.unsubscribe();
}
@@ -70,6 +86,7 @@ export async function listenEvents(thisDevice: ScryptedDeviceBase, client: Onvif
emit(eventName: string | symbol, ...args: any[]) {
return events.emit(eventName, ...args);
},
triggerMotion,
};
return ret;

View File

@@ -1,17 +1,17 @@
{
// docker installation
// "scrypted.debugHost": "koushik-ubuntu",
// "scrypted.serverRoot": "/server",
"scrypted.debugHost": "koushik-ubuntu",
"scrypted.serverRoot": "/server",
// pi local installation
// "scrypted.debugHost": "192.168.2.119",
// "scrypted.serverRoot": "/home/pi/.scrypted",
// local checkout
"scrypted.debugHost": "127.0.0.1",
"scrypted.serverRoot": "/Users/koush/.scrypted",
// "scrypted.debugHost": "koushik-windows",
// "scrypted.debugHost": "127.0.0.1",
// "scrypted.serverRoot": "/Users/koush/.scrypted",
// // "scrypted.debugHost": "koushik-windows",
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",

View File

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

View File

@@ -28,8 +28,7 @@
"scrypted": {
"name": "OpenVINO Object Detection",
"pluginDependencies": [
"@scrypted/objectdetector",
"@scrypted/python-codecs"
"@scrypted/objectdetector"
],
"runtime": "python",
"type": "API",
@@ -42,5 +41,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.45"
"version": "0.1.46"
}

View File

@@ -1,4 +1,4 @@
openvino==2023.0.2
openvino==2023.2.0
# pillow for anything not intel linux, pillow-simd is available on x64 linux
Pillow>=5.4.1; sys_platform != 'linux' or platform_machine != 'x86_64'

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.8",
"version": "0.10.11",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.8",
"version": "0.10.11",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/prebuffer-mixin",
"version": "0.10.8",
"version": "0.10.11",
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -469,7 +469,8 @@ class PrebufferSession {
this.usingScryptedParser = true;
this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser')
const json = await mediaManager.convertMediaObjectToJSON<any>(mo, 'x-scrypted/x-rfc4571');
const { url, sdp, mediaStreamOptions } = json;
let { url, sdp, mediaStreamOptions } = json;
sdp = addTrackControls(sdp);
sessionMso = mediaStreamOptions;
const rtspParser = createRtspParser();

View File

@@ -176,7 +176,8 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
}
events.emit('rtsp', chunk);
resetActivityTimer();
if (chunk.type === inputVideoCodec)
resetActivityTimer();
}
})
.catch(e => {

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/reolink",
"version": "0.0.49",
"version": "0.0.53",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/reolink",
"version": "0.0.49",
"version": "0.0.53",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",

View File

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

View File

@@ -1,18 +1,19 @@
import { sleep } from '@scrypted/common/src/sleep';
import { Camera, DeviceCreatorSettings, DeviceInformation, Intercom, MediaObject, PictureOptions, Reboot, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, Intercom, MediaObject, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, PanTiltZoom, PanTiltZoomCommand, PictureOptions, Reboot, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
import { StorageSettings } from '@scrypted/sdk/storage-settings';
import { EventEmitter } from "stream";
import { Destroyable, RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { OnvifCameraAPI, connectCameraAPI } from './onvif-api';
import { listenEvents } from './onvif-events';
import { OnvifIntercom } from './onvif-intercom';
import { DevInfo, Enc, ReolinkCameraClient } from './reolink-api';
import { AIState, DevInfo, Enc, ReolinkCameraClient } from './reolink-api';
class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom {
class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom, ObjectDetector, PanTiltZoom {
client: ReolinkCameraClient;
onvifClient: OnvifCameraAPI;
onvifIntercom = new OnvifIntercom(this);
videoStreamOptions: Promise<UrlMediaStreamOptions[]>;
motionTimeout: NodeJS.Timeout;
storageSettings = new StorageSettings(this, {
doorbell: {
@@ -25,6 +26,29 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
title: 'RTMP Port Override',
placeholder: '1935',
type: 'number',
},
motionTimeout: {
group: 'Advanced',
title: 'Motion Timeout',
defaultValue: 20,
type: 'number',
},
hasObjectDetector: {
json: true,
hide: true,
},
ptz: {
title: 'PTZ Capabilities',
choices: [
'Pan',
'Tilt',
'Zoom',
],
multiple: true,
onPut: async () => {
await this.updateDevice();
this.updatePtzCaps();
},
}
});
@@ -33,6 +57,49 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
this.updateDeviceInfo();
this.updateDevice();
this.updatePtzCaps();
}
updatePtzCaps() {
const { ptz } = this.storageSettings.values;
this.ptzCapabilities = {
pan: ptz?.includes('Pan'),
tilt: ptz?.includes('Tilt'),
zoom: ptz?.includes('Zoom'),
}
}
async getDetectionInput(detectionId: string, eventId?: any): Promise<MediaObject> {
return;
}
async ptzCommand(command: PanTiltZoomCommand): Promise<void> {
const client = this.getClient();
client.ptz(command);
}
async getObjectTypes(): Promise<ObjectDetectionTypes> {
try {
const ai: AIState = this.storageSettings.values.hasObjectDetector[0]?.value;
const classes: string[] = [];
for (const key of Object.keys(ai)) {
if (key === 'channel')
continue;
const { alarm_state, support } = ai[key];
if (support)
classes.push(key);
}
return {
classes,
};
}
catch (e) {
return {
classes: [],
};
}
}
async startIntercom(media: MediaObject): Promise<void> {
@@ -48,7 +115,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
return this.onvifIntercom.stopIntercom();
}
updateDevice() {
async updateDevice() {
const interfaces = this.provider.getInterfaces();
let type = ScryptedDeviceType.Camera;
let name = 'Reolink Camera';
@@ -60,7 +127,13 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
type = ScryptedDeviceType.Doorbell;
name = 'Reolink Doorbell';
}
this.provider.updateDevice(this.nativeId, name, interfaces, type);
if (this.storageSettings.values.ptz?.length) {
interfaces.push(ScryptedInterface.PanTiltZoom);
}
if (this.storageSettings.values.hasObjectDetector) {
interfaces.push(ScryptedInterface.ObjectDetector);
}
await this.provider.updateDevice(this.nativeId, name, interfaces, type);
}
async reboot() {
@@ -97,11 +170,70 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
}
async listenEvents() {
if (this.storageSettings.values.doorbell)
return listenEvents(this, await this.createOnvifClient());
const client = this.getClient();
let killed = false;
const client = this.getClient();
// reolink ai might not trigger motion if objects are detected, weird.
const startAI = async (ret: Destroyable, triggerMotion: () => void) => {
let hasSucceeded = false;
while (!killed) {
try {
const ai = await client.getAiState();
ret.emit('data', JSON.stringify(ai.data));
const classes: string[] = [];
for (const key of Object.keys(ai.value)) {
if (key === 'channel')
continue;
const { alarm_state, support } = ai.value[key];
if (support)
classes.push(key);
}
if (!classes.length)
return;
hasSucceeded = true;
if (!this.storageSettings.values.hasObjectDetector) {
this.storageSettings.values.hasObjectDetector = ai.data;
this.updateDevice();
}
const od: ObjectsDetected = {
timestamp: Date.now(),
detections: [],
};
for (const c of classes) {
const { alarm_state } = ai.value[c];
if (alarm_state) {
od.detections.push({
className: c,
score: 1,
});
}
}
if (od.detections.length) {
triggerMotion();
sdk.deviceManager.onDeviceEvent(this.nativeId, ScryptedInterface.ObjectDetector, od);
}
}
catch (e) {
if (!hasSucceeded)
return;
ret.emit('error', e);
}
await sleep(1000);
}
}
if (this.storageSettings.values.doorbell) {
const ret = await listenEvents(this, await this.createOnvifClient(), this.storageSettings.values.motionTimeout * 1000);
ret.on('close', () => killed = true);
ret.on('error', () => killed = true);
startAI(ret, ret.triggerMotion);
return ret;
}
const events = new EventEmitter();
const ret: Destroyable = {
on: function (eventName: string | symbol, listener: (...args: any[]) => void): void {
@@ -115,13 +247,17 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
}
};
const triggerMotion = () => {
this.motionDetected = true;
clearTimeout(this.motionTimeout);
this.motionTimeout = setTimeout(() => this.motionDetected = false, this.storageSettings.values.motionTimeout * 1000);
};
(async () => {
while (!killed) {
try {
// const ai = await client.getAiState();
// ret.emit('data', JSON.stringify(ai));
const { value, data } = await client.getMotionState();
this.motionDetected = value;
if (value)
triggerMotion();
ret.emit('data', JSON.stringify(data));
}
catch (e) {
@@ -130,6 +266,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
await sleep(1000);
}
})();
startAI(ret, triggerMotion);
return ret;
}
@@ -228,7 +365,7 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
}
];
if (deviceInfo?.model == "Reolink TrackMix PoE"){
if (deviceInfo?.model == "Reolink TrackMix PoE") {
streams.push({
name: '',
id: 'autotrack.bcs',
@@ -283,12 +420,17 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, Reboot, Intercom
}
return streams;
}
async putSetting(key: string, value: string) {
this.client = undefined;
super.putSetting(key, value);
if (this.storageSettings.keys[key]) {
await this.storageSettings.putSetting(key, value);
}
else {
await super.putSetting(key, value);
}
this.updateDevice();
this.updateDeviceInfo();
}

View File

@@ -1,46 +1,59 @@
import AxiosDigestAuth from "@koush/axios-digest-auth";
import { getMotionState, reolinkHttpsAgent } from './probe';
import { PanTiltZoomCommand } from "@scrypted/sdk";
import { sleep } from "@scrypted/common/src/sleep";
export interface Enc {
audio: number;
channel: number;
audio: number;
channel: number;
mainStream: Stream;
subStream: Stream;
subStream: Stream;
}
export interface Stream {
bitRate: number;
bitRate: number;
frameRate: number;
gop: number;
height: number;
profile: string;
size: string;
vType: string;
width: number;
gop: number;
height: number;
profile: string;
size: string;
vType: string;
width: number;
}
export interface DevInfo {
B485: number;
IOInputNum: number;
IOOutputNum: number;
audioNum: number;
buildDay: string;
cfgVer: string;
channelNum: number;
detail: string;
diskNum: number;
exactType: string;
firmVer: string;
B485: number;
IOInputNum: number;
IOOutputNum: number;
audioNum: number;
buildDay: string;
cfgVer: string;
channelNum: number;
detail: string;
diskNum: number;
exactType: string;
firmVer: string;
frameworkVer: number;
hardVer: string;
model: string;
name: string;
pakSuffix: string;
serial: string;
type: string;
wifi: number;
hardVer: string;
model: string;
name: string;
pakSuffix: string;
serial: string;
type: string;
wifi: number;
}
export interface AIDetectionState {
alarm_state: number;
support: number;
}
export type AIState = {
[key: string]: AIDetectionState;
} & {
channel: number;
};
export class ReolinkCameraClient {
digestAuth: AxiosDigestAuth;
@@ -92,7 +105,7 @@ export class ReolinkCameraClient {
httpsAgent: reolinkHttpsAgent,
});
return {
value: !!response.data?.[0]?.value?.state,
value: response.data?.[0]?.value as AIState,
data: response.data,
};
}
@@ -145,4 +158,62 @@ export class ReolinkCameraClient {
return response.data?.[0]?.value?.DevInfo;
}
async ptz(command: PanTiltZoomCommand) {
let op = '';
if (command.pan < 0)
op += 'Left';
else if (command.pan > 0)
op += 'Right'
if (command.tilt < 0)
op += 'Down';
else if (command.tilt > 0)
op += 'Up';
if (!op)
return;
const url = new URL(`http://${this.host}/api.cgi`);
const params = url.searchParams;
params.set('cmd', 'PtzCtrl');
params.set('user', this.username);
params.set('password', this.password);
const c1 = this.digestAuth.request({
method: 'POST',
url: url.toString(),
httpsAgent: reolinkHttpsAgent,
data: [
{
cmd: "PtzCtrl",
param: {
channel: this.channelId,
op,
speed: 10,
timeout: 1,
}
},
]
});
await sleep(500);
const c2 = this.digestAuth.request({
method: 'POST',
url: url.toString(),
httpsAgent: reolinkHttpsAgent,
data: [
{
cmd: "PtzCtrl",
param: {
channel: this.channelId,
op: "Stop"
}
},
]
});
this.console.log(await c1);
this.console.log(await c2);
}
}

View File

@@ -1,23 +1,23 @@
{
"name": "@scrypted/snapshot",
"version": "0.2.22",
"version": "0.2.30",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/snapshot",
"version": "0.2.22",
"version": "0.2.30",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",
"@types/node": "^18.16.18",
"axios": "^1.4.0",
"@types/node": "^20.10.6",
"axios": "^0.21.4",
"sharp": "^0.32.6",
"whatwg-mimetype": "^3.0.0"
"whatwg-mimetype": "^4.0.0"
},
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/whatwg-mimetype": "^3.0.0"
"@types/whatwg-mimetype": "^3.0.2"
}
},
"../../common": {
@@ -38,7 +38,7 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.108",
"version": "0.3.4",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -75,22 +75,14 @@
}
},
"node_modules/@koush/axios-digest-auth": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.6.tgz",
"integrity": "sha512-e/XKs7/BYpPQkces0Cm4dUmhT9hR0rjvnNZAVRyRnNWdQ8cyCMFWS9HIrMWOdzAocKDNBXi1vKjJ8CywrW5xgQ==",
"dependencies": {
"auth-header": "^1.0.0",
"axios": "^0.21.4"
}
},
"node_modules/@koush/axios-digest-auth/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
@@ -100,34 +92,30 @@
"link": true
},
"node_modules/@types/node": {
"version": "18.16.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz",
"integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw=="
"version": "20.10.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz",
"integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/whatwg-mimetype": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
"integrity": "sha512-xHFOhd41VpUR6Y0k8ZinlyFv5cyhC/r2zghJgWWN8oNxqNo45Nf0qCBInJsFeifLeoHcIF4voEfap4A2GYHWkw==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
"dev": true
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/auth-header": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
},
"node_modules/axios": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
"follow-redirects": "^1.14.0"
}
},
"node_modules/b4a": {
@@ -229,17 +217,6 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -262,14 +239,6 @@
"node": ">=4.0.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
@@ -318,19 +287,6 @@
}
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -386,25 +342,6 @@
"node": ">=10"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -435,9 +372,9 @@
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"node_modules/node-abi": {
"version": "3.51.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz",
"integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==",
"version": "3.52.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.52.0.tgz",
"integrity": "sha512-JJ98b02z16ILv7859irtXn4oUaFWADtvkzy2c0IAatNVX2Mc9Yoh8z6hZInn3QwvMEYhHuQloYi+TTQy67SIdQ==",
"dependencies": {
"semver": "^7.3.5"
},
@@ -509,11 +446,6 @@
"node": ">=6"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -662,9 +594,9 @@
}
},
"node_modules/streamx": {
"version": "2.15.5",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz",
"integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==",
"version": "2.15.6",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz",
"integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==",
"dependencies": {
"fast-fifo": "^1.1.0",
"queue-tick": "^1.0.1"
@@ -717,17 +649,22 @@
"node": "*"
}
},
"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=="
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/whatwg-mimetype": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/wrappy": {
@@ -743,22 +680,12 @@
},
"dependencies": {
"@koush/axios-digest-auth": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.6.tgz",
"integrity": "sha512-e/XKs7/BYpPQkces0Cm4dUmhT9hR0rjvnNZAVRyRnNWdQ8cyCMFWS9HIrMWOdzAocKDNBXi1vKjJ8CywrW5xgQ==",
"requires": {
"auth-header": "^1.0.0",
"axios": "^0.21.4"
},
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
}
}
}
},
"@scrypted/common": {
@@ -797,34 +724,30 @@
}
},
"@types/node": {
"version": "18.16.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz",
"integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw=="
"version": "20.10.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz",
"integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==",
"requires": {
"undici-types": "~5.26.4"
}
},
"@types/whatwg-mimetype": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
"integrity": "sha512-xHFOhd41VpUR6Y0k8ZinlyFv5cyhC/r2zghJgWWN8oNxqNo45Nf0qCBInJsFeifLeoHcIF4voEfap4A2GYHWkw==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
"dev": true
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"auth-header": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
},
"axios": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
"follow-redirects": "^1.14.0"
}
},
"b4a": {
@@ -892,14 +815,6 @@
"simple-swizzle": "^0.2.2"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -913,11 +828,6 @@
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"detect-libc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
@@ -946,16 +856,6 @@
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -994,19 +894,6 @@
"yallist": "^4.0.0"
}
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@@ -1028,9 +915,9 @@
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"node-abi": {
"version": "3.51.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz",
"integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==",
"version": "3.52.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.52.0.tgz",
"integrity": "sha512-JJ98b02z16ILv7859irtXn4oUaFWADtvkzy2c0IAatNVX2Mc9Yoh8z6hZInn3QwvMEYhHuQloYi+TTQy67SIdQ==",
"requires": {
"semver": "^7.3.5"
}
@@ -1092,11 +979,6 @@
}
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -1184,9 +1066,9 @@
}
},
"streamx": {
"version": "2.15.5",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz",
"integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==",
"version": "2.15.6",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz",
"integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==",
"requires": {
"fast-fifo": "^1.1.0",
"queue-tick": "^1.0.1"
@@ -1233,15 +1115,20 @@
"safe-buffer": "^5.0.1"
}
},
"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=="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"whatwg-mimetype": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="
},
"wrappy": {
"version": "1.0.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/snapshot",
"version": "0.2.22",
"version": "0.2.30",
"description": "Snapshot Plugin for Scrypted",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
@@ -35,14 +35,14 @@
},
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",
"@types/node": "^18.16.18",
"axios": "^1.4.0",
"@types/node": "^20.10.6",
"axios": "^0.21.4",
"sharp": "^0.32.6",
"whatwg-mimetype": "^3.0.0"
"whatwg-mimetype": "^4.0.0"
},
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/whatwg-mimetype": "^3.0.0"
"@types/whatwg-mimetype": "^3.0.2"
}
}

View File

@@ -8,6 +8,8 @@ export function loadSharp() {
hasLoadedSharp = true;
try {
sharpInstance = require('sharp');
// not exposed by sharp but it exists.
(sharpInstance.kernel as any).linear = 'linear';
console.log('sharp loaded');
}
catch (e) {
@@ -58,8 +60,27 @@ export class VipsImage implements Image {
});
}
if (options?.resize) {
let kernel: string;
switch (options?.resize.filter) {
case 'bilinear':
kernel = 'linear';
break;
case 'lanczos':
kernel = 'lanczos2';
break;
case 'mitchell':
kernel = 'mitchell';
break;
case 'nearest':
kernel = 'nearest';
break;
default:
kernel = 'linear';
break
}
transformed.resize(typeof options.resize.width === 'number' ? Math.floor(options.resize.width) : undefined, typeof options.resize.height === 'number' ? Math.floor(options.resize.height) : undefined, {
fit: "cover",
kernel: kernel as any,
});
}

View File

@@ -12,8 +12,12 @@ import { ffmpegFilterImage, ffmpegFilterImageBuffer } from './ffmpeg-image-filte
import { ImageConverter, ImageConverterNativeId } from './image-converter';
import { ImageReader, ImageReaderNativeId, loadSharp, loadVipsImage } from './image-reader';
import { ImageWriter, ImageWriterNativeId } from './image-writer';
import os from 'os';
const { mediaManager, systemManager } = sdk;
if (os.cpus().find(cpu => cpu.model?.toLowerCase().includes('qemu'))) {
sdk.log.a('QEMU CPU detected. Set your CPU model to host.');
}
const httpsAgent = new https.Agent({
rejectUnauthorized: false
@@ -300,7 +304,7 @@ class SnapshotMixin extends SettingsMixinDeviceBase<Camera> implements Camera {
picture = this.currentPicture;
}
const needSoftwareResize = !!(options?.picture?.width || options?.picture?.height);
const needSoftwareResize = !!(options?.picture?.width || options?.picture?.height) && this.storageSettings.values.snapshotResolution !== 'Full Resolution';
if (needSoftwareResize) {
try {
picture = await this.snapshotDebouncer({
@@ -669,6 +673,9 @@ export class SnapshotPlugin extends AutoenableMixinProvider implements MixinProv
}
};
if (mixin.storageSettings.values.snapshotResolution === 'Full Resolution')
delete rpo.picture;
if (mixin && iface === ScryptedInterface.Camera) {
buffer = await mixin.takePictureRaw(rpo)
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/tensorflow-lite",
"version": "0.1.44",
"version": "0.1.45",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/tensorflow-lite",
"version": "0.1.44",
"version": "0.1.45",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}

View File

@@ -31,8 +31,7 @@
"scrypted": {
"name": "Tensorflow Lite Object Detection",
"pluginDependencies": [
"@scrypted/objectdetector",
"@scrypted/python-codecs"
"@scrypted/objectdetector"
],
"runtime": "python",
"pythonVersion": {
@@ -54,5 +53,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.44"
"version": "0.1.45"
}

View File

@@ -9,7 +9,7 @@
// "scrypted.serverRoot": "/home/pi/.scrypted",
// local checkout
"scrypted.debugHost": "koushik-ubuntu",
"scrypted.debugHost": "192.168.2.109",
"scrypted.serverRoot": "/server",
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",

View File

@@ -3,3 +3,5 @@
This plugin must be installed inside a Scrypted Docker installation. Mac and Windows are not supported.
The Wyze plugin requires an [API Key](https://developer-api-console.wyze.com/#/apikey/view).
Wyze Cameras must still install a Motion Detection plugin for HomeKit Secure Video support. Scrypted NVR Users should use the accelerated WebAssembly Motion Detector. All other users may use OpenCV.

View File

@@ -1,17 +1,18 @@
{
"name": "@scrypted/wyze",
"version": "0.0.11",
"version": "0.0.50",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/wyze",
"version": "0.0.11",
"version": "0.0.50",
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.3",
"dev": true,
"license": "ISC",

View File

@@ -33,5 +33,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.0.11"
"version": "0.0.50"
}

View File

@@ -1,50 +1,42 @@
from __future__ import annotations
from typing import Any, Coroutine, List, Dict, Callable, Iterator, MutableSet
import scrypted_sdk
import asyncio
import urllib.request
import base64
import concurrent.futures
import json
import os
import urllib
import sys
import platform
import struct
import sys
import threading
import time
import traceback
import urllib
import urllib.request
from ctypes import c_int
from typing import Any, Coroutine, Dict, List
import scrypted_sdk
from requests import HTTPError, RequestException
from scrypted_sdk.other import MediaObject
from scrypted_sdk.types import (DeviceProvider, PanTiltZoom,
RequestMediaStreamOptions,
ResponseMediaStreamOptions, ScryptedDeviceType,
ScryptedInterface, Setting, Settings,
VideoCamera)
import wyzecam
import wyzecam.api_models
import json
import threading
import queue
import traceback
from ctypes import c_int
import concurrent.futures
import subprocess
import base64
import struct
from wyzecam.tutk.tutk import (
FRAME_SIZE_2K,
FRAME_SIZE_1080P,
FRAME_SIZE_360P,
)
from scrypted_sdk.types import (
DeviceProvider,
RequestMediaStreamOptions,
ResponseMediaStreamOptions,
VideoCamera,
ScryptedDeviceType,
ScryptedInterface,
Settings,
Setting,
)
from wyzecam import tutk_protocol
from wyzecam.api import RateLimitError, post_v2_device
from wyzecam.tutk.tutk import FRAME_SIZE_2K, FRAME_SIZE_360P, FRAME_SIZE_1080P
os.environ["TUTK_PROJECT_ROOT"] = os.path.join(
os.environ["SCRYPTED_PLUGIN_VOLUME"], "zip/unzipped/fs"
)
sdkKey = "AQAAAIZ44fijz5pURQiNw4xpEfV9ZysFH8LYBPDxiONQlbLKaDeb7n26TSOPSGHftbRVo25k3uz5of06iGNB4pSfmvsCvm/tTlmML6HKS0vVxZnzEuK95TPGEGt+aE15m6fjtRXQKnUav59VSRHwRj9Z1Kjm1ClfkSPUF5NfUvsb3IAbai0WlzZE1yYCtks7NFRMbTXUMq3bFtNhEERD/7oc504b"
toThreadExecutor = concurrent.futures.ThreadPoolExecutor(
max_workers=2, thread_name_prefix="image"
)
toThreadExecutor = concurrent.futures.ThreadPoolExecutor(thread_name_prefix="probe")
codecMap = {
"mulaw": "PCMU",
@@ -55,6 +47,15 @@ codecMap = {
}
def print_exception(print, e):
for line in traceback.format_exception(e):
print(line)
def format_exception(e):
return "\n".join(traceback.format_exception(e))
async def to_thread(f):
loop = asyncio.get_running_loop()
return await loop.run_in_executor(toThreadExecutor, f)
@@ -79,16 +80,7 @@ class CodecInfo:
self.audioSampleRate = audioSampleRate
class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
camera: wyzecam.WyzeCamera
plugin: WyzePlugin
streams: MutableSet[wyzecam.WyzeIOTCSession]
activeStream: wyzecam.WyzeIOTCSession
audioQueues: MutableSet[queue.Queue[tuple[bytes, Any]]]
main: CodecInfo
sub: CodecInfo
class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings, PanTiltZoom):
def __init__(
self, nativeId: str | None, plugin: WyzePlugin, camera: wyzecam.WyzeCamera
):
@@ -98,16 +90,12 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
self.streams = set()
self.activeStream = None
self.audioQueues = set()
self.main = None
self.sub = None
self.main: CodecInfo = None
self.sub: CodecInfo = None
self.mainFrameSize = FRAME_SIZE_2K if camera.is_2k else FRAME_SIZE_1080P
self.subByterate = 30
self.subByteRate = 30
self.ptzQueue = asyncio.Queue[scrypted_sdk.PanTiltZoomCommand]()
self.mainServer = asyncio.ensure_future(self.ensureServer(self.handleClientHD))
self.subServer = asyncio.ensure_future(self.ensureServer(self.handleClientSD))
self.audioServer = asyncio.ensure_future(
self.ensureServer(self.handleAudioClient)
)
self.rfcServer = asyncio.ensure_future(
self.ensureServer(self.handleMainRfcClient)
)
@@ -115,12 +103,24 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
self.ensureServer(self.handleSubRfcClient)
)
if camera.is_pan_cam:
self.ptzCapabilities = {
"pan": True,
"tilt": True,
}
async def ptzCommand(self, command: scrypted_sdk.PanTiltZoomCommand) -> None:
await self.ptzQueue.put(command)
def safeParseJsonStorage(self, key: str):
try:
return json.loads(self.storage.getItem(key))
except:
return None
def getMuted(self):
return False
def getMainByteRate(self, default=False):
try:
bit = int(self.safeParseJsonStorage("bitrate"))
@@ -148,10 +148,11 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
"value": str(self.getMainByteRate(True)),
"choices": [
"Default",
"480",
"960",
"1440",
"1920",
"500",
"750",
"1000",
"1200",
"1400",
],
}
)
@@ -168,44 +169,6 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
self.nativeId, ScryptedInterface.VideoCamera.value, None
)
async def handleClientHD(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
):
return await self.handleClient(
self.plugin.account.model_copy(),
self.mainFrameSize,
self.getMainByteRate(),
reader,
writer,
)
async def handleClientSD(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
):
account = self.plugin.account.model_copy()
# wyze cams will disconnect first stream if the phone id requests a second stream.
# use a different substream phone id, similar to how docker wyze bridge does it.
account.phone_id = account.phone_id[2:]
return await self.handleClient(
account,
FRAME_SIZE_360P,
self.subByterate,
reader,
writer,
)
def receiveAudioData(self):
q: queue.Queue[tuple[bytes, Any]] = queue.Queue()
self.audioQueues.add(q)
try:
while True:
b, info = q.get()
if not b:
return
yield b, info
finally:
self.audioQueues.remove(q)
async def handleMainRfcClient(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
):
@@ -223,16 +186,12 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
writer: asyncio.StreamWriter,
):
info = self.sub if substream else self.main
ffmpeg = await scrypted_sdk.mediaManager.getFFmpegPath()
loop = asyncio.get_event_loop()
port = await self.subServer if substream else await self.mainServer
audioPort = await self.audioServer
class Protocol:
def __init__(self, pt: int) -> None:
self.pt = pt
class RFC4571Writer:
def connection_made(self, transport):
self.transport = transport
pass
def datagram_received(self, data, addr):
l = len(data)
@@ -240,44 +199,47 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
writer.write(len_data)
writer.write(data)
ffmpeg = await scrypted_sdk.mediaManager.getFFmpegPath()
loop = asyncio.get_event_loop()
vt, vp = await loop.create_datagram_endpoint(
lambda: Protocol(96), local_addr=("127.0.0.1", 0)
lambda: RFC4571Writer(), local_addr=("127.0.0.1", 0)
)
vhost, vport = vt._sock.getsockname()
vprocess = subprocess.Popen(
[
ffmpeg,
"-analyzeduration",
"0",
"-probesize",
"100k",
"-f",
"h264",
"-i",
f"tcp://127.0.0.1:{port}",
"-vcodec",
"copy",
"-an",
"-f",
"rtp",
"-payload_type",
"96",
f"rtp://127.0.0.1:{vport}?pkt_size=1300",
]
vprocess = await asyncio.create_subprocess_exec(
ffmpeg,
"-analyzeduration",
"0",
"-probesize",
"100k",
"-f",
"h264",
"-i",
"pipe:0",
"-vcodec",
"copy",
"-an",
"-f",
"rtp",
"-payload_type",
"96",
f"rtp://127.0.0.1:{vport}?pkt_size=1300",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
vprocess.stdin.write(b"\x00\x00\x00\x01")
vprocess.stdin.write(info.videoCodecInfo[0])
vprocess.stdin.write(b"\x00\x00\x00\x01")
vprocess.stdin.write(info.videoCodecInfo[1])
at, ap = await loop.create_datagram_endpoint(
lambda: Protocol(97), local_addr=("127.0.0.1", 0)
)
aprocess: asyncio.subprocess.Process = None
if not self.getMuted():
at, ap = await loop.create_datagram_endpoint(
lambda: RFC4571Writer(), local_addr=("127.0.0.1", 0)
)
ahost, aport = at._sock.getsockname()
ahost, aport = at._sock.getsockname()
aprocess = subprocess.Popen(
[
aprocess = await asyncio.create_subprocess_exec(
ffmpeg,
"-analyzeduration",
"0",
@@ -288,7 +250,7 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
"-ar",
f"{info.audioSampleRate}",
"-i",
f"tcp://127.0.0.1:{audioPort}",
"pipe:0",
"-acodec",
"copy",
"-vn",
@@ -297,159 +259,38 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
"-payload_type",
"97",
f"rtp://127.0.0.1:{aport}?pkt_size=1300",
]
)
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
try:
while True:
buffer = await reader.read()
if not len(buffer):
return
except Exception as e:
traceback.print_exception(e)
finally:
self.print("rfc reader closed")
# aprocess.stdin.write("q\n")
aprocess.terminate()
# vprocess.stdin.write("q\n")
vprocess.terminate()
async def handleAudioClient(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
):
loop = asyncio.get_event_loop()
closed = False
q = queue.Queue()
async def write():
nonlocal closed
d = q.get()
if closed:
pass
if not d or closed:
closed = True
writer.close()
else:
writer.write(d)
def run():
def pkill(p: asyncio.subprocess.Process):
try:
for frame, frame_info in self.receiveAudioData():
if closed:
return
q.put(frame)
asyncio.run_coroutine_threadsafe(write(), loop=loop)
except Exception as e:
traceback.print_exception(e)
finally:
self.print("audio session closed")
q.put(None)
thread = threading.Thread(target=run)
thread.start()
try:
while True:
buffer = await reader.read()
if not len(buffer):
return
except Exception as e:
traceback.print_exception(e)
finally:
self.print("audio reader closed")
closed = True
async def handleClient(
self,
account: wyzecam.WyzeAccount,
frameSize,
bitrate,
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
):
loop = asyncio.get_event_loop()
closed = False
q = queue.Queue()
async def write():
nonlocal closed
d = q.get()
if closed:
p.stdin.write_eof()
except:
pass
if not d or closed:
closed = True
writer.close()
else:
writer.write(d)
s = wyzecam.WyzeIOTCSession(
self.plugin.wyze_iotc.tutk_platform_lib,
account,
self.camera,
frame_size=frameSize,
bitrate=bitrate,
# CONNECTING?
stream_state=c_int(2),
)
self.streams.add(s)
startedAudio = False
if not self.activeStream:
self.activeStream = s
def runAudio():
for frame, frame_info in s.recv_audio_data():
for q in self.audioQueues:
q.put((frame, frame_info))
def checkStartAudio():
nonlocal startedAudio
if not startedAudio and self.activeStream == s:
startedAudio = True
thread = threading.Thread(target=runAudio)
thread.start()
def run():
try:
with s as sess:
checkStartAudio()
for frame, frame_info in sess.recv_video_data():
if closed:
return
q.put(frame)
asyncio.run_coroutine_threadsafe(write(), loop=loop)
checkStartAudio()
except Exception as e:
traceback.print_exception(e)
finally:
self.print("session closed")
q.put(None)
thread = threading.Thread(target=run)
thread.start()
loop.call_later(5, lambda: p.terminate())
loop.call_later(10, lambda: p.kill())
try:
while not closed:
buffer = await reader.read()
if not len(buffer):
forked, gen = self.forkAndStream(substream)
async for audio, data, codec, sampleRate in gen:
if writer.is_closing():
return
p = aprocess if audio else vprocess
if p:
p.stdin.write(data)
await p.stdin.drain()
except Exception as e:
traceback.print_exception(e)
print_exception(self.print, e)
finally:
self.streams.remove(s)
if self.activeStream == s:
# promote new audio stream to active
self.activeStream = None
for next in self.streams:
self.activeStream = next
break
self.print("reader closed")
closed = True
forked.worker.terminate()
writer.close()
self.print("rfc reader closed")
pkill(vprocess)
if aprocess:
pkill(aprocess)
async def ensureServer(self, cb) -> int:
server = await asyncio.start_server(cb, "127.0.0.1", 0)
@@ -458,69 +299,116 @@ class WyzeCamera(scrypted_sdk.ScryptedDeviceBase, VideoCamera, Settings):
asyncio.ensure_future(server.serve_forever())
return port
def probeCodec(self, account, frameSize, bitrate):
with wyzecam.WyzeIOTCSession(
self.plugin.wyze_iotc.tutk_platform_lib,
account,
self.camera,
frame_size=frameSize,
bitrate=bitrate,
# CONNECTING?
stream_state=c_int(2),
) as sess:
audioCodec = sess.get_audio_codec()
for data, frame_info in sess.recv_video_data():
nals = data.split(b"\x00\x00\x00\x01")
sps = nals[1]
pps = nals[2]
return audioCodec + (sps, pps)
async def probeCodec(self, substream: bool):
sps: bytes = None
pps: bytes = None
audioCodec: str = None
audioSampleRate: int = None
forked, gen = self.forkAndStream(substream)
try:
async for audio, data, codec, sampleRate in gen:
if not audio and not sps and len(data):
nals = data.split(b"\x00\x00\x00\x01")
sps = nals[1]
pps = nals[2]
def probeMainCodec(self):
return self.probeCodec(
self.plugin.account.model_copy(),
self.mainFrameSize,
self.getMainByteRate(),
)
if audio and not self.getMuted():
audioCodec = codec
audioSampleRate = sampleRate
def probeSubCodec(self):
if sps and (audioCodec or self.getMuted()):
return (audioCodec, audioSampleRate, sps, pps)
finally:
forked.worker.terminate()
def forkAndStream(self, substream: bool):
frameSize = FRAME_SIZE_360P if substream else self.mainFrameSize
bitrate = self.subByteRate if substream else self.getMainByteRate()
account = self.plugin.account.model_copy()
account.phone_id = account.phone_id[2:]
return self.probeCodec(
account,
FRAME_SIZE_360P,
self.subByterate,
)
if substream:
account.phone_id = account.phone_id[2:]
forked = scrypted_sdk.fork()
activity = time.time()
done = False
loop = asyncio.get_event_loop()
def reset_timer():
if done:
return
nonlocal activity
if time.time() - activity > 15:
forked.worker.terminate()
else:
loop.call_later(1, reset_timer)
loop.call_later(30, reset_timer)
async def gen():
nonlocal activity
try:
wyzeFork: WyzeFork = await forked.result
async for payload in await wyzeFork.open_stream(
self.plugin.tutk_platform_lib,
account.model_dump(),
self.camera.model_dump(),
frameSize,
bitrate,
self.getMuted(),
self.ptzQueue,
):
audio: bool = payload["audio"]
data: bytes = payload["data"]
codec: bytes = payload["codec"]
sampleRate: bytes = payload["sampleRate"]
if not audio and len(data):
activity = time.time()
yield audio, data, codec, sampleRate
finally:
nonlocal done
done = True
forked.worker.terminate()
return forked, gen()
async def getVideoStream(
self, options: RequestMediaStreamOptions = None
) -> Coroutine[Any, Any, MediaObject]:
substream = options and options.get("id") == "substream"
if substream:
if not self.sub:
codec, sampleRate, sps, pps = await to_thread(self.probeSubCodec)
self.sub = CodecInfo("h264", (sps, pps), codec, sampleRate)
info = self.sub
try:
if substream:
if not self.sub:
self.print("fetching sub codec info")
codec, sampleRate, sps, pps = await self.probeCodec(True)
self.sub = CodecInfo("h264", (sps, pps), codec, sampleRate)
self.print("sub codec info", len(sps), len(pps))
info = self.sub
if not substream:
if not self.main:
codec, sampleRate, sps, pps = await to_thread(self.probeMainCodec)
self.main = CodecInfo("h264", (sps, pps), codec, sampleRate)
info = self.main
else:
if not self.main:
self.print("fetching main codec info")
codec, sampleRate, sps, pps = await self.probeCodec(False)
self.main = CodecInfo("h264", (sps, pps), codec, sampleRate)
self.print("main codec info", len(sps), len(pps))
info = self.main
except Exception as e:
self.print("Error retrieving codec info")
print_exception(self.print, e)
raise
port = await self.subServer if substream else await self.mainServer
audioPort = await self.audioServer
rfcPort = await self.rfcSubServer if substream else await self.rfcServer
msos = self.getVideoStreamOptionsInternal()
mso = msos[1] if substream else msos[0]
mso["audio"]["sampleRate"] = info.audioSampleRate
if not self.getMuted():
mso["audio"]["sampleRate"] = info.audioSampleRate
if True:
sps = base64.b64encode(info.videoCodecInfo[0]).decode()
pps = base64.b64encode(info.videoCodecInfo[1]).decode()
audioCodecName = codecMap.get(info.audioCodec)
sdp = f"""v=0
sps = base64.b64encode(info.videoCodecInfo[0]).decode()
pps = base64.b64encode(info.videoCodecInfo[1]).decode()
audioCodecName = codecMap.get(info.audioCodec)
sdp = f"""v=0
o=- 0 0 IN IP4 0.0.0.0
s=No Name
t=0 0
@@ -528,50 +416,23 @@ m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets={sps},{pps}; profile-level-id=4D0029
"""
if not self.getMuted():
sdp += f"""
m=audio 0 RTP/AVP 97
c=IN IP4 0.0.0.0
b=AS:128
a=rtpmap:97 {audioCodecName}/{info.audioSampleRate}/1
"""
rfc = {
"url": f"tcp://127.0.0.1:{rfcPort}",
"sdp": sdp,
"mediaStreamOptions": mso,
}
jsonString = json.dumps(rfc)
mo = await scrypted_sdk.mediaManager.createMediaObject(
jsonString.encode(),
"x-scrypted/x-rfc4571",
{
"sourceId": self.id,
},
)
return mo
ffmpegInput: scrypted_sdk.FFmpegInput = {
"container": "ffmpeg",
rfc = {
"url": f"tcp://127.0.0.1:{rfcPort}",
"sdp": sdp,
"mediaStreamOptions": mso,
"inputArguments": [
"-analyzeduration",
"0",
"-probesize",
"100k",
"-f",
"h264",
"-i",
f"tcp://127.0.0.1:{port}",
"-f",
info.audioCodec,
"-ar",
f"{info.audioBitrate}",
"-ac",
"1",
"-i",
f"tcp://127.0.0.1:{audioPort}",
],
}
mo = await scrypted_sdk.mediaManager.createFFmpegMediaObject(
ffmpegInput,
jsonString = json.dumps(rfc)
mo = await scrypted_sdk.mediaManager.createMediaObject(
jsonString.encode(),
"x-scrypted/x-rfc4571",
{
"sourceId": self.id,
},
@@ -589,7 +450,7 @@ a=rtpmap:97 {audioCodecName}/{info.audioSampleRate}/1
"width": 2560 if self.camera.is_2k else 1920,
"height": 1440 if self.camera.is_2k else 1080,
},
"audio": {},
"audio": None if self.getMuted() else {},
}
)
# not all wyze can substream, need to create an exhaustive list?
@@ -604,7 +465,7 @@ a=rtpmap:97 {audioCodecName}/{info.audioSampleRate}/1
"width": 640,
"height": 360,
},
"audio": {},
"audio": None if self.getMuted() else {},
}
)
return ret
@@ -614,14 +475,14 @@ a=rtpmap:97 {audioCodecName}/{info.audioSampleRate}/1
class WyzePlugin(scrypted_sdk.ScryptedDeviceBase, DeviceProvider):
cameras: Dict[str, wyzecam.WyzeCamera]
account: wyzecam.WyzeAccount
tutk_platform_lib: str
def __init__(self):
super().__init__()
self.cameras = {}
self.account = None
self.authInfo: wyzecam.WyzeCredential = None
self.cameras: Dict[str, wyzecam.WyzeCamera] = {}
self.account: wyzecam.WyzeAccount = None
self.tutk_platform_lib: str = None
self.wyze_iotc: wyzecam.WyzeIOTC = None
self.last_ts = 0
if sys.platform != "linux":
self.print("Wyze plugin must be installed under Scrypted for Linux.")
@@ -674,6 +535,26 @@ class WyzePlugin(scrypted_sdk.ScryptedDeviceBase, DeviceProvider):
except:
return None
async def pollEvents(self):
current_ms = int(time.time() + 60) * 1000
params = {
"count": 20,
"order_by": 1,
"begin_time": max((self.last_ts + 1) * 1_000, (current_ms - 1_000_000)),
"end_time": current_ms,
"device_mac_list": [],
}
try:
resp = post_v2_device(self.authInfo, "get_event_list", params)
return time.time(), resp["event_list"]
except RateLimitError as ex:
self.print(f"[EVENTS] RateLimitError: {ex}, cooling down.")
return ex.reset_by, []
except (HTTPError, RequestException) as ex:
self.print(f"[EVENTS] HTTPError: {ex}, cooling down.")
return time.time() + 60, []
async def refreshDevices(self):
print("refreshing")
@@ -687,8 +568,10 @@ class WyzePlugin(scrypted_sdk.ScryptedDeviceBase, DeviceProvider):
return
auth_info = wyzecam.login(email, password, api_key=apiKey, key_id=keyId)
self.authInfo = auth_info
self.account = wyzecam.get_user_info(auth_info)
cameras = wyzecam.get_camera_list(auth_info)
# await self.pollEvents()
manifest: scrypted_sdk.DeviceManifest = {"devices": []}
for camera in cameras:
self.cameras[camera.p2p_id] = camera
@@ -776,3 +659,172 @@ class WyzePlugin(scrypted_sdk.ScryptedDeviceBase, DeviceProvider):
def create_scrypted_plugin():
return WyzePlugin()
class WyzeFork:
async def open_stream(
self,
tutk_platform_lib: str,
account_json,
camera_json,
frameSize: int,
bitrate: int,
muted: bool,
ptzQueue: asyncio.Queue[scrypted_sdk.PanTiltZoomCommand],
):
account = wyzecam.WyzeAccount(**account_json)
camera = wyzecam.WyzeCamera(**camera_json)
wyze_iotc = wyzecam.WyzeIOTC(
tutk_platform_lib=tutk_platform_lib,
sdk_key=sdkKey,
max_num_av_channels=32,
)
wyze_iotc.initialize()
loop = asyncio.get_event_loop()
aq: asyncio.Queue[tuple[bool, bytes, Any]] = asyncio.Queue()
closed = False
def run():
with wyzecam.WyzeIOTCSession(
wyze_iotc.tutk_platform_lib,
account,
camera,
frame_size=frameSize,
bitrate=bitrate,
enable_audio=not muted,
# CONNECTING?
stream_state=c_int(2),
) as sess:
nonlocal closed
async def ptzRunner():
while not closed:
command = await ptzQueue.get()
try:
movement = command.get(
"movement",
scrypted_sdk.PanTiltZoomMovement.Relative.value,
)
pan = command.get("pan", 0)
tilt = command.get("tilt", 0)
speed = command.get("speed", 1)
if (
movement
== scrypted_sdk.PanTiltZoomMovement.Absolute.value
):
pan = round(max(0, min(350, pan * 350)))
tilt = round(max(0, min(40, tilt * 40)))
message = tutk_protocol.K11018SetPTZPosition(tilt, pan)
with sess.iotctrl_mux() as mux:
mux.send_ioctl(message)
elif (
movement
== scrypted_sdk.PanTiltZoomMovement.Relative.value
):
# this is range which turns in a full rotation.
scalar = 3072
# speed is 1-9 inclusive
speed = round(max(0, min(8, speed * 8)))
speed += 1
pan = round(max(-scalar, min(scalar, pan * scalar)))
tilt = round(max(-scalar, min(scalar, tilt * scalar)))
message = tutk_protocol.K11000SetRotaryByDegree(
pan, tilt, speed
)
with sess.iotctrl_mux() as mux:
mux.send_ioctl(message)
else:
raise Exception(
"Unknown PTZ cmmand: " + command["movement"]
)
except Exception as e:
print_exception(print, e)
asyncio.ensure_future(ptzRunner(), loop=loop)
if not muted:
def runAudio():
nonlocal closed
try:
rate = sess.get_audio_sample_rate()
codec: str = None
for frame, frame_info in sess.recv_audio_data():
if closed:
return
if not codec:
codec, rate = sess.get_audio_codec_from_codec_id(
frame_info.codec_id
)
asyncio.run_coroutine_threadsafe(
aq.put((True, frame, codec, rate, frame_info)),
loop=loop,
)
except Exception as e:
asyncio.run_coroutine_threadsafe(
aq.put((True, None, None, None, format_exception(e))),
loop=loop,
)
finally:
asyncio.run_coroutine_threadsafe(
aq.put((True, None, None, None, None)), loop=loop
)
closed = True
athread = threading.Thread(
target=runAudio, name="audio-" + camera.p2p_id
)
athread.start()
else:
athread = None
try:
videoParm = sess.camera.camera_info.get("videoParm")
fps = int((videoParm and videoParm.get("fps", 20)) or 20)
for frame in sess.recv_bridge_frame(fps=fps):
if closed:
return
asyncio.run_coroutine_threadsafe(
aq.put((False, frame, None, None, None)), loop=loop
)
except Exception as e:
asyncio.run_coroutine_threadsafe(
aq.put((False, None, None, None, format_exception(e))),
loop=loop,
)
finally:
asyncio.run_coroutine_threadsafe(
aq.put((False, None, None, None, None)), loop=loop
)
closed = True
if athread:
athread.join()
vthread = threading.Thread(target=run, name="video-" + camera.p2p_id)
vthread.start()
try:
while not closed:
payload = await aq.get()
audio, data, codec, sampleRate, info = payload
if data == None:
return
yield {
"__json_copy_serialize_children": True,
"data": data,
"audio": audio,
"codec": codec,
"sampleRate": sampleRate,
}
finally:
closed = True
async def fork():
return WyzeFork()

4
sdk/package-lock.json generated
View File

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

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/types",
"version": "0.3.3",
"version": "0.3.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/types",
"version": "0.3.3",
"version": "0.3.4",
"license": "ISC",
"devDependencies": {
"@types/rimraf": "^3.0.2",

View File

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

View File

@@ -431,6 +431,7 @@ class Device(TypedDict):
name: str
nativeId: str # The native id that is used by the DeviceProvider used to internally identify provided devices.
providerNativeId: str # The native id of the hub or discovery DeviceProvider that manages this device.
refresh: bool # Directs Scrypted to purge any previously returned instances of the device and call getDevice on the DeviceProvider.
room: str
type: ScryptedDeviceType

View File

@@ -1672,6 +1672,11 @@ export interface Device {
*/
providerNativeId?: ScryptedNativeId;
room?: string;
/**
* Directs Scrypted to purge any previously returned instances of the device and call getDevice on the DeviceProvider.
*/
refresh?: boolean;
}
export interface EndpointAccessControlAllowOrigin {

View File

@@ -27,9 +27,10 @@
"${workspaceFolder}/**/*.js"
],
"env": {
// "SCRYPTED_DEFAULT_AUTHENTICATION": "demo"
// force usage of system python because brew python is 3.11
// which has no wheels for coreml tools or tflite-runtime
"SCRYPTED_PYTHON_PATH": "/usr/bin/python3",
// "SCRYPTED_PYTHON_PATH": "/usr/bin/python3",
// "SCRYPTED_SHARED_WORKER": "true",
// "SCRYPTED_DISABLE_AUTHENTICATION": "true",
// "DEBUG": "*",

692
server/package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "@scrypted/server",
"version": "0.72.0",
"version": "0.79.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.72.0",
"version": "0.79.0",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"@scrypted/types": "^0.2.99",
"@scrypted/types": "^0.3.4",
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
@@ -18,7 +18,7 @@
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.3",
"follow-redirects": "^1.15.4",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
@@ -32,13 +32,13 @@
"node-gyp": "^10.0.1",
"router": "^1.3.8",
"semver": "^7.5.4",
"sharp": "^0.32.6",
"sharp": "^0.33.1",
"source-map-support": "^0.5.21",
"tar": "^6.2.0",
"tslib": "^2.6.2",
"typescript": "^5.3.2",
"typescript": "^5.3.3",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.14.2"
"ws": "^8.16.0"
},
"bin": {
"scrypted-serve": "bin/scrypted-serve"
@@ -80,6 +80,446 @@
"node": ">=6.0.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "0.44.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz",
"integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz",
"integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.0"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz",
"integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.0"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz",
"integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"macos": ">=11",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz",
"integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"macos": ">=10.13",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz",
"integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz",
"integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz",
"integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz",
"integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz",
"integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz",
"integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz",
"integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.0"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz",
"integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.0"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz",
"integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.28",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.0"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz",
"integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"glibc": ">=2.26",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.0"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz",
"integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz",
"integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"musl": ">=1.2.2",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.0"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz",
"integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==",
"cpu": [
"wasm32"
],
"optional": true,
"dependencies": {
"@emnapi/runtime": "^0.44.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz",
"integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz",
"integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"npm": ">=9.6.5",
"pnpm": ">=7.1.0",
"yarn": ">=3.2.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -220,9 +660,9 @@
}
},
"node_modules/@scrypted/types": {
"version": "0.2.99",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.99.tgz",
"integrity": "sha512-2J1FH7tpAW5X3rgA70gJ+z0HFM90c/tBA+JXdP1vI1d/0yVmh9TSxnHoCuADN4R2NQXHmoZ6Nbds9kKAQ/25XQ=="
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.4.tgz",
"integrity": "sha512-k/YMx8lIWOkePgXfKW9POr12mb+erFU2JKxO7TW92GyW8ojUWw9VOc0PK6O9bybi0vhsEnvMFkO6pO6bAonsVA=="
},
"node_modules/@types/adm-zip": {
"version": "0.5.5",
@@ -589,11 +1029,6 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/b4a": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz",
"integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -635,6 +1070,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"optional": true,
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
@@ -659,6 +1095,7 @@
"url": "https://feross.org/support"
}
],
"optional": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
@@ -1101,6 +1538,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"optional": true,
"engines": {
"node": ">=4.0.0"
}
@@ -1183,6 +1621,7 @@
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"optional": true,
"dependencies": {
"once": "^1.4.0"
}
@@ -1265,6 +1704,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"optional": true,
"engines": {
"node": ">=6"
}
@@ -1379,11 +1819,6 @@
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"peer": true
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
},
"node_modules/ffmpeg-static": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz",
@@ -1430,9 +1865,9 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/follow-redirects": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [
{
"type": "individual",
@@ -1493,7 +1928,8 @@
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"optional": true
},
"node_modules/fs-minipass": {
"version": "2.1.0",
@@ -1563,7 +1999,8 @@
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"optional": true
},
"node_modules/glob": {
"version": "7.2.3",
@@ -1780,7 +2217,8 @@
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"optional": true
},
"node_modules/ip": {
"version": "1.1.8",
@@ -2109,6 +2547,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"optional": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -2271,7 +2710,8 @@
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"optional": true
},
"node_modules/module-error": {
"version": "1.0.2",
@@ -2294,7 +2734,8 @@
"node_modules/napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
"optional": true
},
"node_modules/napi-macros": {
"version": "2.2.2",
@@ -2327,11 +2768,6 @@
"semver": "bin/semver"
}
},
"node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
},
"node_modules/node-dijkstra": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/node-dijkstra/-/node-dijkstra-2.5.0.tgz",
@@ -2831,6 +3267,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"optional": true,
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -2869,11 +3306,6 @@
}
]
},
"node_modules/queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
},
"node_modules/quill-delta": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
@@ -2914,6 +3346,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"optional": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@@ -3151,146 +3584,42 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/sharp": {
"version": "0.32.6",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz",
"integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==",
"version": "0.33.1",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz",
"integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.1",
"semver": "^7.5.4",
"simple-get": "^4.0.1",
"tar-fs": "^3.0.4",
"tunnel-agent": "^0.6.0"
"semver": "^7.5.4"
},
"engines": {
"node": ">=14.15.0"
"libvips": ">=8.15.0",
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/sharp/node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"node_modules/sharp/node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sharp/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sharp/node_modules/node-abi": {
"version": "3.51.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz",
"integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp/node_modules/prebuild-install": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^1.0.1",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp/node_modules/prebuild-install/node_modules/tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/sharp/node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/sharp/node_modules/tar-fs": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz",
"integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==",
"dependencies": {
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
}
},
"node_modules/sharp/node_modules/tar-fs/node_modules/tar-stream": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz",
"integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.1",
"@img/sharp-darwin-x64": "0.33.1",
"@img/sharp-libvips-darwin-arm64": "1.0.0",
"@img/sharp-libvips-darwin-x64": "1.0.0",
"@img/sharp-libvips-linux-arm": "1.0.0",
"@img/sharp-libvips-linux-arm64": "1.0.0",
"@img/sharp-libvips-linux-s390x": "1.0.0",
"@img/sharp-libvips-linux-x64": "1.0.0",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
"@img/sharp-linux-arm": "0.33.1",
"@img/sharp-linux-arm64": "0.33.1",
"@img/sharp-linux-s390x": "0.33.1",
"@img/sharp-linux-x64": "0.33.1",
"@img/sharp-linuxmusl-arm64": "0.33.1",
"@img/sharp-linuxmusl-x64": "0.33.1",
"@img/sharp-wasm32": "0.33.1",
"@img/sharp-win32-ia32": "0.33.1",
"@img/sharp-win32-x64": "0.33.1"
}
},
"node_modules/shebang-command": {
@@ -3347,7 +3676,8 @@
"type": "consulting",
"url": "https://feross.org/support"
}
]
],
"optional": true
},
"node_modules/simple-get": {
"version": "3.1.1",
@@ -3463,15 +3793,6 @@
"node": ">= 0.8"
}
},
"node_modules/streamx": {
"version": "2.15.5",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.5.tgz",
"integrity": "sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==",
"dependencies": {
"fast-fifo": "^1.1.0",
"queue-tick": "^1.0.1"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -3534,6 +3855,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3576,6 +3898,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"optional": true,
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
@@ -3628,6 +3951,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"optional": true,
"dependencies": {
"safe-buffer": "^5.0.1"
},
@@ -3653,9 +3977,9 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/typescript": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3871,9 +4195,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.14.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"engines": {
"node": ">=10.0.0"
},

View File

@@ -1,10 +1,10 @@
{
"name": "@scrypted/server",
"version": "0.72.0",
"version": "0.79.0",
"description": "",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"@scrypted/types": "^0.2.99",
"@scrypted/types": "^0.3.4",
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
@@ -12,7 +12,7 @@
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.3",
"follow-redirects": "^1.15.4",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^8.0.0",
@@ -26,13 +26,13 @@
"node-gyp": "^10.0.1",
"router": "^1.3.8",
"semver": "^7.5.4",
"sharp": "^0.32.6",
"sharp": "^0.33.1",
"source-map-support": "^0.5.21",
"tar": "^6.2.0",
"tslib": "^2.6.2",
"typescript": "^5.3.2",
"typescript": "^5.3.3",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.14.2"
"ws": "^8.16.0"
},
"devDependencies": {
"@types/adm-zip": "^0.5.5",

View File

@@ -6,37 +6,56 @@ const { pki } = forge;
export const CURRENT_SELF_SIGNED_CERTIFICATE_VERSION = 'v2';
const SIXTY_DAYS_MS = 60 * 24 * 60 * 60 * 1000;
export function createSelfSignedCertificate(serviceKey?: string) {
let privateKey: ReturnType<typeof pki.privateKeyFromPem>;
const cert = pki.createCertificate();
export interface SelfSignedCertificate {
serviceKey: string;
certificate: string;
version: string,
};
if (serviceKey) {
privateKey = pki.privateKeyFromPem(serviceKey);
cert.publicKey = pki.rsa.setPublicKey(privateKey.n, privateKey.e);
export function createSelfSignedCertificate(existing?: SelfSignedCertificate): SelfSignedCertificate {
let serviceKey: ReturnType<typeof pki.privateKeyFromPem>;
// check if existing key is usable
if (existing?.certificate && existing?.serviceKey && existing?.version === CURRENT_SELF_SIGNED_CERTIFICATE_VERSION) {
try {
const certificate = pki.certificateFromPem(existing.certificate);
if (certificate.validity.notAfter.getTime() > Date.now() + SIXTY_DAYS_MS)
return existing;
serviceKey = pki.privateKeyFromPem(existing.serviceKey);
}
catch (e) {
}
}
const certificate = pki.createCertificate();
if (existing?.serviceKey) {
certificate.publicKey = pki.rsa.setPublicKey(serviceKey.n, serviceKey.e);
}
else {
// generate a keypair and create an X.509v3 certificate
const keys = pki.rsa.generateKeyPair(2048);
privateKey = keys.privateKey;
cert.publicKey = keys.publicKey;
serviceKey = keys.privateKey;
certificate.publicKey = keys.publicKey;
}
// NOTE: serialNumber is the hex encoded value of an ASN.1 INTEGER.
// Conforming CAs should ensure serialNumber is:
// - no more than 20 octets
// - non-negative (prefix a '00' if your value starts with a '1' bit)
cert.serialNumber = '01' + crypto.randomBytes(19).toString("hex"); // 1 octet = 8 bits = 1 byte = 2 hex chars
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); // adding 1 year of validity from now
certificate.serialNumber = '01' + crypto.randomBytes(19).toString("hex"); // 1 octet = 8 bits = 1 byte = 2 hex chars
certificate.validity.notBefore = new Date();
certificate.validity.notAfter = new Date();
certificate.validity.notAfter.setFullYear(certificate.validity.notBefore.getFullYear() + 5); // adding 5 years of validity from now
const attrs = [{
name: 'commonName',
value: 'localhost'
}];
cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.setExtensions([{
certificate.setSubject(attrs);
certificate.setIssuer(attrs);
certificate.setExtensions([{
name: 'basicConstraints',
cA: true
}, {
@@ -73,10 +92,10 @@ export function createSelfSignedCertificate(serviceKey?: string) {
}]);
// self-sign certificate
cert.sign(privateKey);
certificate.sign(serviceKey);
return {
serviceKey: pki.privateKeyToPem(privateKey),
certificate: pki.certificateToPem(cert),
serviceKey: pki.privateKeyToPem(serviceKey),
certificate: pki.certificateToPem(certificate),
version: CURRENT_SELF_SIGNED_CERTIFICATE_VERSION,
};
}

View File

@@ -20,7 +20,7 @@ export interface StdPassThroughs {
}
export function getConsole(hook: (stdout: PassThrough, stderr: PassThrough) => Promise<void>,
also?: Console, alsoPrefix?: string) {
also?: Console, alsoPrefix?: string) {
const stdout = new PassThrough();
const stderr = new PassThrough();
@@ -63,7 +63,7 @@ export function getConsole(hook: (stdout: PassThrough, stderr: PassThrough) => P
export function prepareConsoles(getConsoleName: () => string, systemManager: () => SystemManager, deviceManager: () => DeviceManager, getPlugins: () => Promise<any>) {
const deviceConsoles = new Map<string, Console>();
function getDeviceConsole (nativeId?: ScryptedNativeId) {
function getDeviceConsole(nativeId?: ScryptedNativeId) {
// the the plugin console is simply the default console
// and gets read from stderr/stdout.
if (!nativeId)
@@ -126,13 +126,16 @@ export function prepareConsoles(getConsoleName: () => string, systemManager: ()
const connect = async () => {
const ds = deviceManager().getDeviceState(nativeId);
if (!ds) {
// deleted?
// device deleted
if (!ds)
return;
}
const plugins = await getPlugins();
const { pluginId, nativeId: mixinNativeId } = await plugins.getDeviceInfo(mixinId);
const mixin = systemManager().getDeviceById(mixinId);
// mixin deleted
if (!mixin)
return;
const { pluginId, nativeId: mixinNativeId } = mixin;
const port = await plugins.getRemoteServicePort(pluginId, 'console-writer');
const socket = net.connect(port);
socket.write(mixinNativeId + '\n');
@@ -161,7 +164,7 @@ export function prepareConsoles(getConsoleName: () => string, systemManager: ()
nativeIdConsoles.set(mixinId, ret);
return ret;
}
return {
getDeviceConsole,
getMixinConsole,

View File

@@ -93,7 +93,7 @@ export class PluginHost {
// do this on next tick, after this call has returned an id, so the plugin can handle
// any subsequent requests.
process.nextTick(async () => {
let needInvalidate = interfacesChanged;
let needInvalidate = interfacesChanged || upsert.refresh;
if (!needInvalidate) {
// may also need to invalidate if the the plugin did not previously return a device
// because it had not yet completed the discovery process.

View File

@@ -1,12 +1,13 @@
import ip, { isV4Format } from 'ip';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import crypto from 'crypto';
import { once } from 'events';
import express, { Request } from 'express';
import fs from 'fs';
import http from 'http';
import httpAuth from 'http-auth';
import https from 'https';
import ip, { isV4Format } from 'ip';
import net from 'net';
import os from 'os';
import path from 'path';
@@ -15,19 +16,16 @@ import semver from 'semver';
import { install as installSourceMapSupport } from 'source-map-support';
import { createSelfSignedCertificate, CURRENT_SELF_SIGNED_CERTIFICATE_VERSION } from './cert';
import { Plugin, ScryptedUser, Settings } from './db-types';
import { getNpmPackageInfo } from './http-fetch-helpers';
import Level from './level';
import { PluginError } from './plugin/plugin-error';
import { getScryptedVolume } from './plugin/plugin-volume';
import { RPCResultError } from './rpc';
import { ScryptedRuntime } from './runtime';
import { getHostAddresses, SCRYPTED_DEBUG_PORT, SCRYPTED_INSECURE_PORT, SCRYPTED_SECURE_PORT } from './server-settings';
import { Info } from './services/info';
import { setScryptedUserPassword } from './services/users';
import { sleep } from './sleep';
import { ONE_DAY_MILLISECONDS, UserToken } from './usertoken';
import { once } from 'events';
import util from 'util';
import { getNpmPackageInfo } from './http-fetch-helpers';
export type Runtime = ScryptedRuntime;
@@ -128,15 +126,14 @@ async function start(mainFilename: string, options?: {
if (certSetting?.value?.version !== CURRENT_SELF_SIGNED_CERTIFICATE_VERSION) {
keyPair = createSelfSignedCertificate();
certSetting = new Settings();
certSetting._id = 'certificate';
certSetting.value = keyPair;
certSetting = await db.upsert(certSetting);
}
else {
keyPair = createSelfSignedCertificate(keyPair.serviceKey);
keyPair = createSelfSignedCertificate(keyPair);
}
certSetting = new Settings();
certSetting._id = 'certificate';
certSetting.value = keyPair;
certSetting = await db.upsert(certSetting);
const basicAuth = httpAuth.basic({
realm: 'Scrypted',
@@ -191,8 +188,10 @@ async function start(mainFilename: string, options?: {
}
app.use(async (req, res, next) => {
if (process.env.SCRYPTED_DISABLE_AUTHENTICATION === 'true') {
res.locals.username = 'anonymous';
const defaultAuthentication = getDefaultAuthentication(req);
if (defaultAuthentication) {
res.locals.username = defaultAuthentication._id;
res.locals.aclId = defaultAuthentication.aclId;
next();
return;
}
@@ -584,6 +583,24 @@ async function start(mainFilename: string, options?: {
}
}
const getDefaultAuthentication = (req: Request) => {
const defaultAuthentication = !req.query.disableDefaultAuthentication && process.env.SCRYPTED_DEFAULT_AUTHENTICATION;
if (defaultAuthentication) {
const referer = req.headers.referer;
if (referer) {
try {
const u = new URL(referer);
if (u.searchParams.has('disableDefaultAuthentication'))
return;
}
catch (e) {
// no/invalid referer, allow the default auth
}
}
return scrypted.usersService.users.get(defaultAuthentication);
}
}
app.get('/login', async (req, res) => {
await checkResetLogin();
@@ -607,14 +624,19 @@ async function start(mainFilename: string, options?: {
return;
}
// env based anon admin login
if (process.env.SCRYPTED_DISABLE_AUTHENTICATION === 'true') {
// env based anon user login
const defaultAuthentication = getDefaultAuthentication(req);
if (defaultAuthentication) {
const userToken = new UserToken(defaultAuthentication._id, defaultAuthentication.aclId, Date.now());
res.send({
...createTokens(userToken),
expiration: ONE_DAY_MILLISECONDS,
username: 'anonymous',
username: defaultAuthentication,
// TODO: do not return the token from a short term auth mechanism?
token: defaultAuthentication?.token,
...alternateAddresses,
hostname,
})
});
return;
}