mirror of
https://github.com/koush/scrypted.git
synced 2026-02-03 22:23:27 +00:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
544531122d | ||
|
|
778f0b7ad1 | ||
|
|
35e8a86593 | ||
|
|
c370773af4 | ||
|
|
184f293b92 | ||
|
|
6e10172f7e | ||
|
|
c5ae2cd539 | ||
|
|
e40566e89c | ||
|
|
59ccd4e4d8 | ||
|
|
ae80eb7727 | ||
|
|
f054172dcf | ||
|
|
0d7fb9e13c | ||
|
|
a526816b07 | ||
|
|
563e16b08f | ||
|
|
fd56990d64 | ||
|
|
d7aaf57e8f | ||
|
|
a2d50d54d5 | ||
|
|
1f86745252 | ||
|
|
1f4343ba2e | ||
|
|
3ad311898f | ||
|
|
799e5b53c7 | ||
|
|
833e5b34ab | ||
|
|
c99ac28e89 | ||
|
|
841475cb97 | ||
|
|
4b03a3a458 | ||
|
|
d686dd815c | ||
|
|
e0386a8922 | ||
|
|
9ef3478c88 | ||
|
|
690d160f33 | ||
|
|
59ff987bca | ||
|
|
1669f17c96 | ||
|
|
b0bfd4e05e | ||
|
|
7152671913 | ||
|
|
537c178699 | ||
|
|
77ecee110b | ||
|
|
29b163a7d8 | ||
|
|
5d74e80e90 | ||
|
|
764b6441d5 | ||
|
|
e2c43cb4ff | ||
|
|
7b6d094e8c | ||
|
|
3dfb2db02a | ||
|
|
e5a549db6a | ||
|
|
d500c815fe | ||
|
|
5f71c59b5a | ||
|
|
27407942a5 | ||
|
|
11b6963744 | ||
|
|
b9ee8866f0 | ||
|
|
bc80d31eaa | ||
|
|
327688232c | ||
|
|
2883a4ce46 | ||
|
|
1ad2fb915d | ||
|
|
fb701a32b7 | ||
|
|
7a8c661bb3 | ||
|
|
54d72fb371 | ||
|
|
e48812cec7 | ||
|
|
6c2db072c4 | ||
|
|
4bf2c0b614 | ||
|
|
a93cdb0ae4 | ||
|
|
ff85b7abc6 | ||
|
|
46dfb8d98e | ||
|
|
5240200f0f | ||
|
|
3bcb94fc6b | ||
|
|
a596bc712c | ||
|
|
f6d2dc456e | ||
|
|
441cce239e | ||
|
|
3016df32d1 | ||
|
|
5bd8ed0b1a | ||
|
|
79286a5138 | ||
|
|
8874e01072 | ||
|
|
0223a9f0f6 | ||
|
|
890c2667d0 | ||
|
|
ca14764e17 | ||
|
|
1030d7d03c | ||
|
|
2d40320868 | ||
|
|
3e32c3d019 | ||
|
|
1f9fa3966f | ||
|
|
c2d86237d6 | ||
|
|
5cfcfafc00 |
6
.github/workflows/docker-common.yml
vendored
6
.github/workflows/docker-common.yml
vendored
@@ -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:
|
||||
|
||||
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
2
external/werift
vendored
2
external/werift
vendored
Submodule external/werift updated: 25be131232...a0070297a4
@@ -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"
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 .
|
||||
|
||||
16
install/docker/install-intel-graphics.sh
Normal file
16
install/docker/install-intel-graphics.sh
Normal 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
|
||||
@@ -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" ]
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
66
install/local/install-scrypted-proxmox.sh
Normal file
66
install/local/install-scrypted-proxmox.sh
Normal 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."
|
||||
|
||||
4
plugins/core/.vscode/launch.json
vendored
4
plugins/core/.vscode/launch.json
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export class Timer {
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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];
|
||||
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
10
plugins/openvino/.vscode/settings.json
vendored
10
plugins/openvino/.vscode/settings.json
vendored
@@ -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",
|
||||
|
||||
4
plugins/openvino/package-lock.json
generated
4
plugins/openvino/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
4
plugins/prebuffer-mixin/package-lock.json
generated
4
plugins/prebuffer-mixin/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
4
plugins/reolink/package-lock.json
generated
4
plugins/reolink/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/reolink",
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.53",
|
||||
"description": "Reolink Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
249
plugins/snapshot/package-lock.json
generated
249
plugins/snapshot/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
4
plugins/tensorflow-lite/package-lock.json
generated
4
plugins/tensorflow-lite/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
2
plugins/wyze/.vscode/settings.json
vendored
2
plugins/wyze/.vscode/settings.json
vendored
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
Submodule plugins/wyze/docker-wyze-bridge updated: ff09cca5b8...90a4c5003a
5
plugins/wyze/package-lock.json
generated
5
plugins/wyze/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -33,5 +33,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.11"
|
||||
"version": "0.0.50"
|
||||
}
|
||||
|
||||
@@ -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
4
sdk/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"description": "",
|
||||
"main": "dist/src/index.js",
|
||||
"exports": {
|
||||
|
||||
4
sdk/types/package-lock.json
generated
4
sdk/types/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"author": "",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
3
server/.vscode/launch.json
vendored
3
server/.vscode/launch.json
vendored
@@ -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
692
server/package-lock.json
generated
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user