mirror of
https://github.com/koush/scrypted.git
synced 2026-02-05 23:22:13 +00:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1445933bd4 | ||
|
|
508f31c254 | ||
|
|
fd1aa10a2a | ||
|
|
fceed68d75 | ||
|
|
955e780c64 | ||
|
|
452fe20e8f | ||
|
|
9083e16cdb | ||
|
|
840a278e5d | ||
|
|
6d036dbd60 | ||
|
|
d5ba6f34d6 | ||
|
|
0321846c22 | ||
|
|
714747fcee | ||
|
|
e3906da3c4 | ||
|
|
820ef70033 | ||
|
|
0c95f5c052 | ||
|
|
4cfd7c4362 | ||
|
|
1e8126dec8 | ||
|
|
d3fbc58736 | ||
|
|
46113744b3 | ||
|
|
3947624ae0 | ||
|
|
4ac5ded012 | ||
|
|
aadfacf50a | ||
|
|
bb1e0ac82b | ||
|
|
23a15a1533 | ||
|
|
01dd480c01 | ||
|
|
364cae3273 | ||
|
|
8a986ab707 | ||
|
|
ca96959de8 | ||
|
|
2f0ae9ef50 | ||
|
|
8b84bac2c2 | ||
|
|
976ed7f1a5 | ||
|
|
b4e6821da8 | ||
|
|
540b990a08 | ||
|
|
ce75b072da | ||
|
|
5bca9b7156 | ||
|
|
ae4914346b | ||
|
|
b593209558 | ||
|
|
9df399708f | ||
|
|
ea25682488 | ||
|
|
06e25e6a16 | ||
|
|
10847ef3f2 | ||
|
|
78184390ac | ||
|
|
9a0c88ac61 | ||
|
|
646dd3613c | ||
|
|
ab87abb859 | ||
|
|
5ce1a2b406 | ||
|
|
1abda3b425 | ||
|
|
c759becac6 | ||
|
|
b3a16c0000 | ||
|
|
0163a804cd | ||
|
|
ab157b16f1 | ||
|
|
905a9aec21 | ||
|
|
8e63dcdb15 | ||
|
|
05cad811e8 | ||
|
|
69a3e1138b | ||
|
|
9c9e29068b | ||
|
|
b8bb6dfa61 | ||
|
|
809956a2a4 | ||
|
|
0be72a70a5 | ||
|
|
9d03566246 | ||
|
|
7c023dbdf6 | ||
|
|
1f2187fd6a | ||
|
|
83b60b7b2b | ||
|
|
edfdd5c1a8 | ||
|
|
cdd350f52b | ||
|
|
1594364194 | ||
|
|
8dac20ed1c | ||
|
|
20beacb746 | ||
|
|
ac51fa6355 | ||
|
|
05a60831e6 | ||
|
|
dd13fee049 | ||
|
|
31fd833873 | ||
|
|
a0e5dd4c89 | ||
|
|
215daf5af7 | ||
|
|
a82972d967 | ||
|
|
6fd6c7af14 | ||
|
|
6d1cf5d3c1 | ||
|
|
0cfef48954 | ||
|
|
e9722d3875 | ||
|
|
fa8d17bec9 | ||
|
|
d69ec69038 | ||
|
|
106fc1bf58 | ||
|
|
4b055f55e1 | ||
|
|
3a70625308 | ||
|
|
7a382a8eba | ||
|
|
6d520dc4b2 | ||
|
|
40c7132ec0 | ||
|
|
4d2a038f19 | ||
|
|
a8bfdb6610 | ||
|
|
9817b0144e | ||
|
|
f662bd7de4 | ||
|
|
de52cec190 | ||
|
|
9a8e48e3c4 | ||
|
|
0560d857c1 | ||
|
|
4ee72cd074 | ||
|
|
7120ff430f | ||
|
|
167c66f8d6 | ||
|
|
4d98ccf86b | ||
|
|
ff2d1d5f97 | ||
|
|
ebe19532fc | ||
|
|
1294fc291a | ||
|
|
39c637a95f | ||
|
|
2fb6331e7b | ||
|
|
e7fd88bf2a | ||
|
|
96455dc38e | ||
|
|
4301911e86 | ||
|
|
1ddbe2fac8 |
97
.github/workflows/docker-common.yml
vendored
97
.github/workflows/docker-common.yml
vendored
@@ -7,13 +7,10 @@ jobs:
|
||||
build:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: self-hosted
|
||||
# runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
strategy:
|
||||
matrix:
|
||||
NODE_VERSION: [
|
||||
# "18",
|
||||
"20"
|
||||
]
|
||||
BASE: ["jammy"]
|
||||
FLAVOR: ["full", "lite"]
|
||||
steps:
|
||||
@@ -23,12 +20,26 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
@@ -54,14 +65,84 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: |
|
||||
NODE_VERSION=${{ matrix.NODE_VERSION }}
|
||||
NODE_VERSION=${{ env.NODE_VERSION }}
|
||||
BASE=${{ matrix.BASE }}
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile.${{ matrix.FLAVOR }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
build-nvidia:
|
||||
name: Push NVIDIA Docker image to Docker Hub
|
||||
needs: build
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
BASE: ["jammy"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/arm64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
platforms: linux/arm64
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image (scrypted-common)
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: |
|
||||
BASE=ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-full
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile.nvidia
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted-common:${{ matrix.BASE }}-nvidia
|
||||
ghcr.io/koush/scrypted-common:${{ matrix.BASE }}-nvidia
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
44
.github/workflows/docker.yml
vendored
44
.github/workflows/docker.yml
vendored
@@ -20,10 +20,10 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
BASE: [
|
||||
"20-jammy-full",
|
||||
"20-jammy-lite",
|
||||
["jammy-nvidia", ".s6"],
|
||||
["jammy-full", ".s6"],
|
||||
["jammy-lite", ""],
|
||||
]
|
||||
SUPERVISOR: ["", ".s6"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
@@ -42,12 +42,26 @@ jobs:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_AMD64 }}
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
@@ -73,23 +87,23 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: |
|
||||
BASE=${{ matrix.BASE }}
|
||||
BASE=${{ matrix.BASE[0] }}
|
||||
SCRYPTED_INSTALL_VERSION=${{ steps.package-version.outputs.NPM_VERSION }}
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile${{ matrix.SUPERVISOR }}
|
||||
file: install/docker/Dockerfile${{ matrix.BASE[1] }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ format('koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '.s6' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '' && 'koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '' && 'koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '.s6' && 'koush/scrypted:lite-s6' || '' }}
|
||||
${{ format('koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE[0] == 'jammy-full' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-nvidia' && 'koush/scrypted:nvidia' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-full' && 'koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-lite' && 'koush/scrypted:lite' || '' }}
|
||||
|
||||
${{ format('ghcr.io/koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '.s6' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-full' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '20-jammy-lite' && matrix.SUPERVISOR == '.s6' && 'ghcr.io/koush/scrypted:lite-s6' || '' }}
|
||||
${{ format('ghcr.io/koush/scrypted:v{1}-{0}', matrix.BASE[0], github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE[0] == 'jammy-full' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-nvidia' && 'ghcr.io/koush/scrypted:nvidia' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-full' && 'ghcr.io/koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE[0] == 'jammy-lite' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "20-jammy-full.s6-v0.97.0"
|
||||
version: "v0.102.0-jammy-full"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
arch:
|
||||
- amd64
|
||||
- aarch64
|
||||
- armv7
|
||||
init: false
|
||||
ingress: true
|
||||
ingress_port: 11080
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
FROM ghcr.io/koush/scrypted:20-jammy-full.s6
|
||||
ARG BASE="ghcr.io/koush/scrypted-common:20-jammy-full"
|
||||
FROM $BASE
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install miniconda
|
||||
ENV CONDA_DIR /opt/conda
|
||||
RUN apt update -y && apt -y install wget && wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \
|
||||
/bin/bash ~/miniconda.sh -b -p /opt/conda
|
||||
# Put conda in path so we can use conda activate
|
||||
ENV PATH=$CONDA_DIR/bin:$PATH
|
||||
|
||||
RUN conda -y install -c conda-forge cudatoolkit cudnn
|
||||
ENV CONDA_PREFIX=/opt/conda
|
||||
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CONDA_PREFIX/lib/
|
||||
# nvidia cudnn/libcublas etc.
|
||||
# for some reason this is not provided by the nvidia container toolkit
|
||||
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-nvidia-graphics.sh | bash
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
version: "3.5"
|
||||
|
||||
# The Scrypted docker-compose.yml file typically resides at:
|
||||
# ~/.scrypted/docker-compose.yml
|
||||
|
||||
@@ -40,14 +38,21 @@ services:
|
||||
# See volumes section below to use the host daemon.
|
||||
# - SCRYPTED_DOCKER_AVAHI=true
|
||||
|
||||
# Uncomment next 3 lines for Nvidia GPU support.
|
||||
# NVIDIA (Part 1 of 4)
|
||||
# - NVIDIA_VISIBLE_DEVICES=all
|
||||
# - NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
# NVIDIA (Part 2 of 4)
|
||||
# runtime: nvidia
|
||||
# Necessary to communicate with host dbus for avahi-daemon.
|
||||
security_opt:
|
||||
- apparmor:unconfined
|
||||
|
||||
# NVIDIA (Part 3 of 4) - Use NVIDIA image, and remove subsequent default image.
|
||||
# image: ghcr.io/koush/scrypted:nvidia
|
||||
image: ghcr.io/koush/scrypted
|
||||
|
||||
volumes:
|
||||
# NVIDIA (Part 4 of 4)
|
||||
# - /etc/OpenCL/vendors/nvidia.icd:/etc/OpenCL/vendors/nvidia.icd
|
||||
|
||||
# Scrypted NVR Storage (Part 3 of 3)
|
||||
|
||||
# Modify to add the additional volume for Scrypted NVR.
|
||||
@@ -94,15 +99,16 @@ services:
|
||||
container_name: scrypted
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
image: ghcr.io/koush/scrypted
|
||||
|
||||
# logging is noisy and will unnecessarily wear on flash storage.
|
||||
# scrypted has per device in memory logging that is preferred.
|
||||
# enable the log file if enhanced debugging is necessary.
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "10"
|
||||
driver: "none"
|
||||
# driver: "json-file"
|
||||
# options:
|
||||
# max-size: "10m"
|
||||
# max-file: "10"
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.scope=scrypted"
|
||||
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
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 &&
|
||||
# this script previvously apt install intel-media-va-driver-non-free, but that seems to no longer be necessary.
|
||||
|
||||
# the intel provided script is disabled since it does not work with the 6.8 kernel in Ubuntu 24.04 or Proxmox 8.2.
|
||||
# manual installation of the Intel graphics stuff is required.
|
||||
|
||||
# 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 &&
|
||||
# apt-get -y dist-upgrade;
|
||||
|
||||
# manual installation
|
||||
# https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
|
||||
rm -rf /tmp/neo && mkdir -p /tmp/neo && cd /tmp/neo &&
|
||||
apt-get install -y ocl-icd-libopencl1 &&
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.16510.2/intel-igc-core_1.0.16510.2_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.16510.2/intel-igc-opencl_1.0.16510.2_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.13.29138.7/intel-level-zero-gpu-dbgsym_1.3.29138.7_amd64.ddeb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.13.29138.7/intel-level-zero-gpu_1.3.29138.7_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.13.29138.7/intel-opencl-icd-dbgsym_24.13.29138.7_amd64.ddeb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.13.29138.7/intel-opencl-icd_24.13.29138.7_amd64.deb &&
|
||||
curl -O -L https://github.com/intel/compute-runtime/releases/download/24.13.29138.7/libigdgmm12_22.3.18_amd64.deb &&
|
||||
dpkg -i *.deb &&
|
||||
cd /tmp && rm -rf /tmp/neo &&
|
||||
apt-get -y dist-upgrade;
|
||||
|
||||
exit $?
|
||||
else
|
||||
echo "Intel graphics will not be installed on this architecture."
|
||||
|
||||
16
install/docker/install-nvidia-graphics.sh
Normal file
16
install/docker/install-nvidia-graphics.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
if [ "$(uname -m)" = "x86_64" ]
|
||||
then
|
||||
echo "Installing NVIDIA graphics packages."
|
||||
apt update -q \
|
||||
&& apt install -y wget \
|
||||
&& wget -qO /cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/$(uname -m)/cuda-keyring_1.1-1_all.deb \
|
||||
&& dpkg -i /cuda-keyring.deb \
|
||||
&& apt update -q \
|
||||
&& apt install -y cuda-nvcc-11-8 libcublas-11-8 libcudnn8 cuda-libraries-11-8 \
|
||||
&& apt install -y cuda-nvcc-12-4 libcublas-12-4 libcudnn8 cuda-libraries-12-4;
|
||||
exit $?
|
||||
else
|
||||
echo "NVIDIA graphics will not be installed on this architecture."
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -72,6 +72,7 @@ function removescryptedfstab() {
|
||||
grep -v "scrypted-nvr" /etc/fstab > /tmp/fstab && cp /tmp/fstab /etc/fstab
|
||||
# ensure newline
|
||||
sed -i -e '$a\' /etc/fstab
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
BLOCK_DEVICE="/dev/$1"
|
||||
@@ -119,6 +120,7 @@ then
|
||||
mkdir -p /mnt/scrypted-nvr
|
||||
echo "PARTLABEL=scrypted-nvr /mnt/scrypted-nvr ext4 defaults,nofail 0 0" >> /etc/fstab
|
||||
mount -a
|
||||
systemctl daemon-reload
|
||||
set +e
|
||||
|
||||
DIR="/mnt/scrypted-nvr"
|
||||
|
||||
@@ -110,10 +110,12 @@ User=$SERVICE_USER
|
||||
Group=$SERVICE_USER
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/npx -y scrypted serve
|
||||
Restart=on-failure
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
Environment="NODE_OPTIONS=$NODE_OPTIONS"
|
||||
Environment="SCRYPTED_INSTALL_ENVIRONMENT=$SCRYPTED_INSTALL_ENVIRONMENT"
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -11,7 +11,7 @@ iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/in
|
||||
choco upgrade -y nodejs-lts --version=20.11.1
|
||||
|
||||
# Install VC Redist, which is necessary for portable python
|
||||
choco install vcredist140
|
||||
choco install -y vcredist140
|
||||
|
||||
# TODO: remove python install, and use portable python
|
||||
# Install Python
|
||||
@@ -34,7 +34,8 @@ $SCRYPTED_HOME_ESCAPED_PATH = $SCRYPTED_HOME.replace('\', '\\')
|
||||
npm install --prefix $SCRYPTED_HOME @koush/node-windows --save
|
||||
|
||||
$NPX_PATH = (Get-Command npx).Path
|
||||
$NPX_PATH_ESCAPED = $NPX_PATH.replace('\', '\\')
|
||||
# The path needs double quotes to handle spaces in the directory path
|
||||
$NPX_PATH_ESCAPED = '"' + $NPX_PATH.replace('\', '\\') + '"'
|
||||
|
||||
$SERVICE_JS = @"
|
||||
const fs = require('fs');
|
||||
@@ -44,8 +45,10 @@ try {
|
||||
catch (e) {
|
||||
}
|
||||
const child_process = require('child_process');
|
||||
child_process.spawn('$($NPX_PATH_ESCAPED)', ['-y', 'scrypted', 'serve'], {
|
||||
child_process.spawn('$NPX_PATH_ESCAPED', ['-y', 'scrypted', 'serve'], {
|
||||
stdio: 'inherit',
|
||||
// allow spawning .cmd https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
|
||||
shell: true,
|
||||
});
|
||||
"@
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
#!/bin/bash
|
||||
echo 'if (!process.version.startsWith("v18")) throw new Error("Node 18 is required. Install Node Version Manager (nvm) for versioned node installations. See https://github.com/koush/scrypted/pull/498#issuecomment-1373854020")' | node
|
||||
if [ "$?" != 0 ]
|
||||
then
|
||||
exit
|
||||
fi
|
||||
|
||||
echo ######################################
|
||||
echo "Setting up popular plugins."
|
||||
echo "Additional will need npm install manually."
|
||||
@@ -15,7 +9,7 @@ cd $(dirname $0)
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
for directory in sdk common server packages/client packages/auth-fetch
|
||||
for directory in sdk server common packages/client packages/auth-fetch
|
||||
do
|
||||
echo "$directory > npm install"
|
||||
pushd $directory
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, fetcher, getFetchMethod, setDefaultHttpFetchAccept } from '../../../server/src/fetch';
|
||||
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, createHeadersArray, fetcher, getFetchMethod, hasHeader, setDefaultHttpFetchAccept, setHeader } from '../../../server/src/fetch';
|
||||
|
||||
export interface AuthFetchCredentialState {
|
||||
username: string;
|
||||
@@ -74,15 +74,15 @@ export function createAuthFetch<B, M>(
|
||||
) {
|
||||
const authHttpFetch = async <T extends HttpFetchOptions<B>>(options: T & AuthFetchOptions): ReturnType<typeof h<T>> => {
|
||||
const method = getFetchMethod(options);
|
||||
const headers = new Headers(options.headers);
|
||||
const headers = createHeadersArray(options.headers);
|
||||
options.headers = headers;
|
||||
setDefaultHttpFetchAccept(headers, options.responseType);
|
||||
|
||||
const initialHeader = await getAuth(options, options.url, method);
|
||||
// try to provide an authorization if a session exists, but don't override Authorization if provided already.
|
||||
// 401 will trigger a proper auth.
|
||||
if (initialHeader && !headers.has('Authorization'))
|
||||
headers.set('Authorization', initialHeader);
|
||||
if (initialHeader && !hasHeader(headers, 'Authorization'))
|
||||
setHeader(headers, 'Authorization', initialHeader);
|
||||
|
||||
const initialResponse = await h({
|
||||
...options,
|
||||
@@ -126,7 +126,7 @@ export function createAuthFetch<B, M>(
|
||||
|
||||
const header = await getAuth(options, options.url, method);
|
||||
if (header)
|
||||
headers.set('Authorization', header);
|
||||
setHeader(headers, 'Authorization', header);
|
||||
|
||||
return h(options);
|
||||
}
|
||||
|
||||
4
packages/cli/package-lock.json
generated
4
packages/cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "scrypted",
|
||||
"version": "1.3.14",
|
||||
"version": "1.3.15",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "scrypted",
|
||||
"version": "1.3.14",
|
||||
"version": "1.3.15",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/client": "^1.3.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "scrypted",
|
||||
"version": "1.3.14",
|
||||
"version": "1.3.15",
|
||||
"description": "",
|
||||
"main": "./dist/packages/cli/src/main.js",
|
||||
"bin": {
|
||||
|
||||
@@ -24,6 +24,8 @@ async function runCommand(command: string, ...args: string[]) {
|
||||
// https://github.com/lovell/sharp/blob/eefaa998725cf345227d94b40615e090495c6d09/lib/libvips.js#L115C19-L115C46
|
||||
SHARP_IGNORE_GLOBAL_LIBVIPS: 'true',
|
||||
},
|
||||
// allow spawning .cmd https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2
|
||||
shell: os.platform() === 'win32' ? true : undefined,
|
||||
});
|
||||
await once(cp, 'exit');
|
||||
if (cp.exitCode)
|
||||
|
||||
510
plugins/bticino/package-lock.json
generated
510
plugins/bticino/package-lock.json
generated
@@ -1,29 +1,26 @@
|
||||
{
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.16",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.16",
|
||||
"dependencies": {
|
||||
"@slyoldfox/sip": "^0.0.6-1",
|
||||
"sdp": "^3.0.3",
|
||||
"stun": "^2.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
"stun": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
@@ -39,8 +36,7 @@
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.14",
|
||||
"version": "0.3.29",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -89,18 +85,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
@@ -130,9 +126,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
@@ -148,27 +144,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.18.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz",
|
||||
"integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"version": "16.18.96",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz",
|
||||
"integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -178,9 +168,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
@@ -229,12 +219,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -365,6 +361,22 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@@ -382,6 +394,25 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
@@ -402,9 +433,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
@@ -415,13 +449,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -432,15 +482,26 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
@@ -454,6 +515,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
@@ -468,9 +540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||
},
|
||||
"node_modules/ip2buf": {
|
||||
"version": "2.0.0",
|
||||
@@ -486,11 +558,11 @@
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||
"dependencies": {
|
||||
"has": "^1.0.3"
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -671,9 +743,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -789,11 +861,11 @@
|
||||
"integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg=="
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
|
||||
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
|
||||
"version": "6.12.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
|
||||
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@@ -865,11 +937,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.9.0",
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
@@ -912,6 +984,22 @@
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -934,13 +1022,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -961,9 +1053,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-exceptions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
|
||||
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="
|
||||
},
|
||||
"node_modules/spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
@@ -975,9 +1067,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.13",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
|
||||
"integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w=="
|
||||
"version": "3.0.17",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
|
||||
"integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg=="
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "1.1.0",
|
||||
@@ -1054,9 +1146,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
@@ -1105,9 +1197,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
|
||||
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
@@ -1115,7 +1207,7 @@
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
@@ -1126,14 +1218,6 @@
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
@@ -1193,15 +1277,15 @@
|
||||
}
|
||||
},
|
||||
"@jridgewell/resolve-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
@@ -1255,9 +1339,9 @@
|
||||
"integrity": "sha512-PJBIAKS3aMsFTHeQLfAtVpZOduAqGNZZAEH6Kb15htGUcSJWHZ9r2LAjxm3fD4yWT9plYlO0CthcEVnlrrwQLA=="
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node12": {
|
||||
@@ -1273,33 +1357,27 @@
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node16": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.18.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz",
|
||||
"integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"version": "16.18.96",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz",
|
||||
"integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
|
||||
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
|
||||
"dev": true
|
||||
},
|
||||
"arg": {
|
||||
@@ -1336,12 +1414,15 @@
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
@@ -1427,6 +1508,16 @@
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
|
||||
},
|
||||
"define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"requires": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@@ -1441,6 +1532,19 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
@@ -1455,9 +1559,9 @@
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.3.1",
|
||||
@@ -1468,13 +1572,23 @@
|
||||
}
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
@@ -1482,19 +1596,32 @@
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
"es-define-property": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q=="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
@@ -1506,9 +1633,9 @@
|
||||
"integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ=="
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||
},
|
||||
"ip2buf": {
|
||||
"version": "2.0.0",
|
||||
@@ -1521,11 +1648,11 @@
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
|
||||
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
"hasown": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-plain-obj": {
|
||||
@@ -1669,9 +1796,9 @@
|
||||
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g=="
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ=="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "1.3.0",
|
||||
@@ -1760,11 +1887,11 @@
|
||||
"integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
|
||||
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
|
||||
"version": "6.12.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz",
|
||||
"integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
"side-channel": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"query-string": {
|
||||
@@ -1812,11 +1939,11 @@
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"requires": {
|
||||
"is-core-module": "^2.9.0",
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
@@ -1836,6 +1963,19 @@
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
|
||||
},
|
||||
"set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"requires": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -1852,13 +1992,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
@@ -1876,9 +2017,9 @@
|
||||
}
|
||||
},
|
||||
"spdx-exceptions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
|
||||
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A=="
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="
|
||||
},
|
||||
"spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
@@ -1890,9 +2031,9 @@
|
||||
}
|
||||
},
|
||||
"spdx-license-ids": {
|
||||
"version": "3.0.13",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz",
|
||||
"integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w=="
|
||||
"version": "3.0.17",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
|
||||
"integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg=="
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.1.0",
|
||||
@@ -1942,9 +2083,9 @@
|
||||
"integrity": "sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA=="
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
@@ -1968,9 +2109,9 @@
|
||||
"integrity": "sha512-8yyRd1ZdNp+AQLGqi3lTaA2k81JjlIZOyFQEsi7GQWBgirnQOxjqVtDEbYHM2Z4yFdJ5AQw0fxBLLnDCl6RXoQ=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
|
||||
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
@@ -1979,11 +2120,6 @@
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/bticino",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.16",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
@@ -34,14 +34,12 @@
|
||||
"dependencies": {
|
||||
"@slyoldfox/sip": "^0.0.6-1",
|
||||
"sdp": "^3.0.3",
|
||||
"stun": "^2.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
"stun": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^16.9.6",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { closeQuiet, createBindUdp, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
||||
import { createBindUdp, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
import { RtspServer } from '@scrypted/common/src/rtsp-server';
|
||||
import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
|
||||
import sdk, { BinarySensor, Camera, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, Intercom, MediaObject, MediaStreamUrl, MotionSensor, PictureOptions, Reboot, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
|
||||
import sdk, { BinarySensor, Camera, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, Intercom, MediaObject, MediaStreamUrl, MotionSensor, PictureOptions, Reboot, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
|
||||
import { SipCallSession } from '../../sip/src/sip-call-session';
|
||||
import { RtpDescription, getPayloadType, getSequenceNumber, isRtpMessagePayloadType, isStunMessage } from '../../sip/src/rtp-utils';
|
||||
import { VoicemailHandler } from './bticino-voicemailHandler';
|
||||
import { CompositeSipMessageHandler } from '../../sip/src/compositeSipMessageHandler';
|
||||
import { SipHelper } from './sip-helper';
|
||||
import child_process, { ChildProcess } from 'child_process';
|
||||
import dgram from 'dgram';
|
||||
import { BticinoStorageSettings } from './storage-settings';
|
||||
import { BticinoSipPlugin } from './main';
|
||||
import { BticinoSipLock } from './bticino-lock';
|
||||
import { ffmpegLogInitialOutput, safeKillFFmpeg, safePrintFFmpegArguments } from '@scrypted/common/src/media-helpers';
|
||||
import { safePrintFFmpegArguments } from '@scrypted/common/src/media-helpers';
|
||||
import { PersistentSipManager } from './persistent-sip-manager';
|
||||
import { InviteHandler } from './bticino-inviteHandler';
|
||||
import { SipOptions, SipRequest } from '../../sip/src/sip-manager';
|
||||
import { startRtpForwarderProcess } from '../../webrtc/src/rtp-forwarders';
|
||||
import fs from "fs"
|
||||
import url from "url"
|
||||
import path from 'path';
|
||||
@@ -37,8 +37,7 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
|
||||
private session: SipCallSession
|
||||
private remoteRtpDescription: Promise<RtpDescription>
|
||||
private audioOutForwarder: dgram.Socket
|
||||
private audioOutProcess: ChildProcess
|
||||
private forwarder
|
||||
private refreshTimeout: NodeJS.Timeout
|
||||
public requestHandlers: CompositeSipMessageHandler = new CompositeSipMessageHandler()
|
||||
public incomingCallRequest : SipRequest
|
||||
@@ -276,21 +275,27 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
}
|
||||
|
||||
async takePicture(option?: PictureOptions): Promise<MediaObject> {
|
||||
const thumbnailCacheTime : number = parseInt( this.storage?.getItem('thumbnailCacheTime') ) * 1000 || 300000
|
||||
const now = new Date().getTime()
|
||||
if( !this.lastImageRefresh || this.lastImageRefresh + thumbnailCacheTime < now ) {
|
||||
// get a proxy object to make sure we pass prebuffer when already watching a stream
|
||||
let cam : VideoCamera = sdk.systemManager.getDeviceById<VideoCamera>(this.id)
|
||||
let vs : MediaObject = await cam.getVideoStream()
|
||||
let buf : Buffer = await mediaManager.convertMediaObjectToBuffer(vs, 'image/jpeg');
|
||||
this.cachedImage = buf
|
||||
this.lastImageRefresh = new Date().getTime()
|
||||
this.console.log(`Camera picture updated and cached: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
|
||||
let rebroadcastEnabled = this.interfaces?.includes( "mixin:@scrypted/prebuffer-mixin")
|
||||
if( rebroadcastEnabled ) {
|
||||
const thumbnailCacheTime : number = parseInt( this.storage?.getItem('thumbnailCacheTime') ) * 1000 || 300000
|
||||
const now = new Date().getTime()
|
||||
if( !this.lastImageRefresh || this.lastImageRefresh + thumbnailCacheTime < now ) {
|
||||
// get a proxy object to make sure we pass prebuffer when already watching a stream
|
||||
let cam : VideoCamera = sdk.systemManager.getDeviceById<VideoCamera>(this.id)
|
||||
let vs : MediaObject = await cam.getVideoStream()
|
||||
let buf : Buffer = await mediaManager.convertMediaObjectToBuffer(vs, 'image/jpeg');
|
||||
this.cachedImage = buf
|
||||
this.lastImageRefresh = new Date().getTime()
|
||||
this.console.log(`Camera picture updated and cached: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
|
||||
} else {
|
||||
this.console.log(`Not refreshing camera picture: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
}
|
||||
|
||||
return mediaManager.createMediaObject(this.cachedImage, 'image/jpeg')
|
||||
} else {
|
||||
this.console.log(`Not refreshing camera picture: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
|
||||
throw new Error("To enable snapshots, enable rebroadcast plugin or set a Snapshot URL in the Snapshot plugin to an external image.");
|
||||
}
|
||||
return mediaManager.createMediaObject(this.cachedImage, 'image/jpeg')
|
||||
}
|
||||
|
||||
async getPictureOptions(): Promise<PictureOptions[]> {
|
||||
@@ -317,52 +322,31 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
this.session = await this.callIntercom( cleanup )
|
||||
}
|
||||
|
||||
|
||||
this.stopIntercom();
|
||||
|
||||
const ffmpegInput: FFmpegInput = JSON.parse((await mediaManager.convertMediaObjectToBuffer(media, ScryptedMimeTypes.FFmpegInput)).toString());
|
||||
|
||||
const audioOutForwarder = await createBindZero()
|
||||
this.audioOutForwarder = audioOutForwarder.server
|
||||
const ffmpegInput = await sdk.mediaManager.convertMediaObjectToJSON<FFmpegInput>(media, ScryptedMimeTypes.FFmpegInput);
|
||||
let address = (await this.remoteRtpDescription).address
|
||||
audioOutForwarder.server.on('message', message => {
|
||||
if( this.session )
|
||||
this.session.audioSplitter.send(message, 40004, address)
|
||||
return null
|
||||
});
|
||||
|
||||
const args = ffmpegInput.inputArguments.slice();
|
||||
args.push(
|
||||
'-vn', '-dn', '-sn',
|
||||
'-acodec', 'speex',
|
||||
'-flags', '+global_header',
|
||||
'-ac', '1',
|
||||
'-ar', '8k',
|
||||
'-f', 'rtp',
|
||||
//'-srtp_out_suite', 'AES_CM_128_HMAC_SHA1_80',
|
||||
//'-srtp_out_params', encodeSrtpOptions(this.decodedSrtpOptions),
|
||||
`rtp://127.0.0.1:${audioOutForwarder.port}?pkt_size=188`,
|
||||
);
|
||||
|
||||
this.console.log("===========================================")
|
||||
safePrintFFmpegArguments( this.console, args )
|
||||
this.console.log("===========================================")
|
||||
|
||||
const cp = child_process.spawn(await mediaManager.getFFmpegPath(), args);
|
||||
ffmpegLogInitialOutput(this.console, cp)
|
||||
this.audioOutProcess = cp;
|
||||
cp.on('exit', () => this.console.log('two way audio ended'));
|
||||
this.session.onCallEnded.subscribe(() => {
|
||||
closeQuiet(audioOutForwarder.server);
|
||||
safeKillFFmpeg(cp)
|
||||
this.forwarder = await startRtpForwarderProcess(this.console, ffmpegInput, {
|
||||
audio: {
|
||||
codecCopy: 'speex',
|
||||
encoderArguments: [
|
||||
'-vn', '-sn', '-dn',
|
||||
'-acodec', 'speex',
|
||||
'-flags', '+global_header',
|
||||
'-ac', '1',
|
||||
'-ar', '8k',
|
||||
'-f', 'rtp',
|
||||
],
|
||||
onRtp: rtp => {
|
||||
this.session?.audioSplitter?.send(rtp, 40004, address)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async stopIntercom(): Promise<void> {
|
||||
closeQuiet(this.audioOutForwarder)
|
||||
this.audioOutProcess?.kill('SIGKILL')
|
||||
this.audioOutProcess = undefined
|
||||
this.audioOutForwarder = undefined
|
||||
this.forwarder?.kill()
|
||||
this.forwarder = undefined
|
||||
}
|
||||
|
||||
resetStreamTimeout() {
|
||||
@@ -572,12 +556,24 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor
|
||||
// Call the C300X
|
||||
this.remoteRtpDescription = sip.callOrAcceptInvite(
|
||||
( audio ) => {
|
||||
return [
|
||||
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
|
||||
`m=audio 65000 RTP/SAVP 110`,
|
||||
`a=rtpmap:110 speex/8000`,
|
||||
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
|
||||
]
|
||||
let audioSection = [
|
||||
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
|
||||
`m=audio 65000 RTP/SAVP 110`,
|
||||
`a=rtpmap:110 speex/8000`,
|
||||
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
|
||||
]
|
||||
if( !this.incomingCallRequest ) {
|
||||
let DEVADDR = this.storage.getItem('DEVADDR');
|
||||
if( DEVADDR ) {
|
||||
audioSection.unshift('a=DEVADDR:' + DEVADDR)
|
||||
} else {
|
||||
if( sipOptions.to.toLocaleLowerCase().indexOf('c300x') >= 0 || sipOptions.to.toLocaleLowerCase().indexOf('c100x') >= 0 ) {
|
||||
// Needed for bt_answering_machine (bticino specific), to check for c100X
|
||||
audioSection.unshift('a=DEVADDR:20')
|
||||
}
|
||||
}
|
||||
}
|
||||
return audioSection
|
||||
}, ( video ) => {
|
||||
return [
|
||||
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
|
||||
|
||||
@@ -15,6 +15,8 @@ Environment="SCRYPTED_PYTHON39_PATH=/usr/bin/python3.9"
|
||||
Environment="SCRYPTED_PYTHON310_PATH=/usr/bin/python3.10"
|
||||
Environment="SCRYPTED_FFMPEG_PATH=/usr/bin/ffmpeg"
|
||||
Environment="SCRYPTED_INSTALL_ENVIRONMENT=lxc"
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.23",
|
||||
"version": "0.3.25",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.23",
|
||||
"version": "0.3.25",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.3.23",
|
||||
"version": "0.3.25",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from 'fs';
|
||||
import child_process from 'child_process';
|
||||
import { once } from 'events';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import { stdout } from 'process';
|
||||
|
||||
export const SCRYPTED_INSTALL_ENVIRONMENT_LXC = 'lxc';
|
||||
|
||||
@@ -41,6 +42,31 @@ export async function checkLxcDependencies() {
|
||||
sdk.log.a('Failed to daemon-reload systemd.');
|
||||
}
|
||||
|
||||
try {
|
||||
// intel opencl icd is broken from their official apt repos on kernel versions 6.8, which ships with ubuntu 24.04 and proxmox 8.2.
|
||||
// the intel apt repo has not been updated yet.
|
||||
// the current workaround is to install the release manually.
|
||||
// https://github.com/intel/compute-runtime/releases/tag/24.13.29138.7
|
||||
const output = await new Promise<string>((r,f)=> child_process.exec("sh -c 'apt show versions intel-opencl-icd'", (err, stdout, stderr) => {
|
||||
if (err)
|
||||
f(err);
|
||||
else
|
||||
r(stdout + '\n' + stderr);
|
||||
}));
|
||||
|
||||
if (output.includes('Version: 23')) {
|
||||
const cp = child_process.spawn('sh', ['-c', 'curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash']);
|
||||
const [exitCode] = await once(cp, 'exit');
|
||||
if (exitCode !== 0)
|
||||
sdk.log.a('Failed to install intel-opencl-icd.');
|
||||
else
|
||||
needRestart = true;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
sdk.log.a('Failed to verify/install intel-opencl-icd version.');
|
||||
}
|
||||
|
||||
if (needRestart)
|
||||
sdk.log.a('A system update is pending. Please restart Scrypted to apply changes.');
|
||||
}
|
||||
|
||||
4
plugins/coreml/package-lock.json
generated
4
plugins/coreml/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.45",
|
||||
"version": "0.1.51",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.45",
|
||||
"version": "0.1.51",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.45"
|
||||
"version": "0.1.51"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,12 @@ from PIL import Image
|
||||
from scrypted_sdk import Setting, SettingValue
|
||||
|
||||
from common import yolo
|
||||
from coreml.recognition import CoreMLRecognition
|
||||
from coreml.face_recognition import CoreMLFaceRecognition
|
||||
|
||||
try:
|
||||
from coreml.text_recognition import CoreMLTextRecognition
|
||||
except:
|
||||
CoreMLTextRecognition = None
|
||||
from predict import Prediction, PredictPlugin
|
||||
from predict.rectangle import Rectangle
|
||||
|
||||
@@ -21,6 +26,7 @@ predictExecutor = concurrent.futures.ThreadPoolExecutor(1, "CoreML-Predict")
|
||||
|
||||
availableModels = [
|
||||
"Default",
|
||||
"scrypted_yolo_nas_s_320",
|
||||
"scrypted_yolov9c_320",
|
||||
"scrypted_yolov9c",
|
||||
"scrypted_yolov6n_320",
|
||||
@@ -72,6 +78,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
self.storage.setItem("model", "Default")
|
||||
model = "scrypted_yolov9c_320"
|
||||
self.yolo = "yolo" in model
|
||||
self.scrypted_yolo_nas = "scrypted_yolo_nas" in model
|
||||
self.scrypted_yolo = "scrypted_yolo" in model
|
||||
self.scrypted_model = "scrypted" in model
|
||||
model_version = "v7"
|
||||
@@ -127,29 +134,51 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.minThreshold = 0.2
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
devices = [
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "CoreML Face Recognition",
|
||||
},
|
||||
]
|
||||
|
||||
if CoreMLTextRecognition:
|
||||
devices.append(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "CoreML Text Recognition",
|
||||
},
|
||||
)
|
||||
|
||||
await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"nativeId": "recognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "CoreML Recognition",
|
||||
}
|
||||
]
|
||||
"devices": devices,
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
async def getDevice(self, nativeId: str) -> Any:
|
||||
return CoreMLRecognition(nativeId)
|
||||
if nativeId == "facerecognition":
|
||||
self.faceDevice = self.faceDevice or CoreMLFaceRecognition(nativeId)
|
||||
return self.faceDevice
|
||||
if nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or CoreMLTextRecognition(nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
@@ -174,7 +203,7 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
|
||||
def get_input_size(self) -> Tuple[float, float]:
|
||||
return (self.inputwidth, self.inputheight)
|
||||
|
||||
|
||||
async def detect_batch(self, inputs: List[Any]) -> List[Any]:
|
||||
out_dicts = await asyncio.get_event_loop().run_in_executor(
|
||||
predictExecutor, lambda: self.model.predict(inputs)
|
||||
@@ -188,7 +217,12 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.Settings, scrypted_sdk.DeviceProv
|
||||
if self.yolo:
|
||||
out_dict = await self.queue_batch({self.input_name: input})
|
||||
|
||||
if self.scrypted_yolo:
|
||||
if self.scrypted_yolo_nas:
|
||||
predictions = list(out_dict.values())
|
||||
objs = yolo.parse_yolo_nas(predictions)
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
elif self.scrypted_yolo:
|
||||
results = list(out_dict.values())[0][0]
|
||||
objs = yolo.parse_yolov9(results)
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
|
||||
@@ -3,13 +3,15 @@ from __future__ import annotations
|
||||
import concurrent.futures
|
||||
import os
|
||||
|
||||
import asyncio
|
||||
import coremltools as ct
|
||||
import numpy as np
|
||||
# import Quartz
|
||||
# from Foundation import NSData, NSMakeSize
|
||||
|
||||
# import Vision
|
||||
from predict.recognize import RecognizeDetection
|
||||
from predict.face_recognize import FaceRecognizeDetection
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def euclidean_distance(arr1, arr2):
|
||||
@@ -26,9 +28,11 @@ def cosine_similarity(vector_a, vector_b):
|
||||
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(8, "Vision-Predict")
|
||||
|
||||
class CoreMLRecognition(RecognizeDetection):
|
||||
class CoreMLFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-face")
|
||||
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-face")
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
model_version = "v7"
|
||||
@@ -51,23 +55,29 @@ class CoreMLRecognition(RecognizeDetection):
|
||||
inputName = model.get_spec().description.input[0].name
|
||||
return model, inputName
|
||||
|
||||
def predictDetectModel(self, input):
|
||||
model, inputName = self.detectModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
results = list(out_dict.values())[0][0]
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
def predict():
|
||||
model, inputName = self.detectModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
results = list(out_dict.values())[0][0]
|
||||
return results
|
||||
|
||||
results = await asyncio.get_event_loop().run_in_executor(
|
||||
self.detectExecutor, lambda: predict()
|
||||
)
|
||||
return results
|
||||
|
||||
def predictFaceModel(self, input):
|
||||
model, inputName = self.faceModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
return out_dict["var_2167"][0]
|
||||
async def predictFaceModel(self, input: np.ndarray):
|
||||
def predict():
|
||||
model, inputName = self.faceModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
results = out_dict["var_2167"][0]
|
||||
return results
|
||||
results = await asyncio.get_event_loop().run_in_executor(
|
||||
self.recogExecutor, lambda: predict()
|
||||
)
|
||||
return results
|
||||
|
||||
def predictTextModel(self, input):
|
||||
model, inputName = self.textModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
preds = out_dict["linear_2"]
|
||||
return preds
|
||||
|
||||
# def predictVision(self, input: Image.Image) -> asyncio.Future[list[Prediction]]:
|
||||
# buffer = input.tobytes()
|
||||
# myData = NSData.alloc().initWithBytes_length_(buffer, len(buffer))
|
||||
63
plugins/coreml/src/coreml/text_recognition.py
Normal file
63
plugins/coreml/src/coreml/text_recognition.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import os
|
||||
|
||||
import asyncio
|
||||
|
||||
import coremltools as ct
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from predict.text_recognize import TextRecognition
|
||||
|
||||
|
||||
class CoreMLTextRecognition(TextRecognition):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
self.detectExecutor = concurrent.futures.ThreadPoolExecutor(1, "detect-text")
|
||||
self.recogExecutor = concurrent.futures.ThreadPoolExecutor(1, "recog-text")
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
model_version = "v7"
|
||||
mlmodel = "model"
|
||||
|
||||
files = [
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
|
||||
f"{model}/{model}.mlpackage/Data/com.apple.CoreML/{mlmodel}.mlmodel",
|
||||
f"{model}/{model}.mlpackage/Manifest.json",
|
||||
]
|
||||
|
||||
for f in files:
|
||||
p = self.downloadFile(
|
||||
f"https://github.com/koush/coreml-models/raw/main/{f}",
|
||||
f"{model_version}/{f}",
|
||||
)
|
||||
modelFile = os.path.dirname(p)
|
||||
|
||||
model = ct.models.MLModel(modelFile)
|
||||
inputName = model.get_spec().description.input[0].name
|
||||
return model, inputName
|
||||
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
def predict():
|
||||
model, inputName = self.detectModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
results = list(out_dict.values())[0]
|
||||
return results
|
||||
results = await asyncio.get_event_loop().run_in_executor(
|
||||
self.detectExecutor, lambda: predict()
|
||||
)
|
||||
return results
|
||||
|
||||
async def predictTextModel(self, input: np.ndarray):
|
||||
def predict():
|
||||
model, inputName = self.textModel
|
||||
out_dict = model.predict({inputName: input})
|
||||
preds = out_dict["linear_2"]
|
||||
return preds
|
||||
preds = await asyncio.get_event_loop().run_in_executor(
|
||||
self.recogExecutor, lambda: predict()
|
||||
)
|
||||
return preds
|
||||
@@ -0,0 +1 @@
|
||||
opencv-python
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# 2024-04-23 - modify timestamp to force pip reinstall
|
||||
coremltools==7.1
|
||||
Pillow>=5.4.1
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Dlib Face Recognition for Scrypted
|
||||
|
||||
This plugin adds face recognition capabilities to any camera in Scrypted.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
@@ -1,252 +0,0 @@
|
||||
from __future__ import annotations
|
||||
import re
|
||||
import scrypted_sdk
|
||||
from typing import Any, Tuple
|
||||
from predict import PredictPlugin, Prediction, Rectangle
|
||||
import os
|
||||
from PIL import Image
|
||||
import face_recognition
|
||||
import numpy as np
|
||||
from typing import Any, List, Tuple, Mapping
|
||||
from scrypted_sdk.types import ObjectDetectionModel, ObjectDetectionResult, ObjectsDetected, Setting
|
||||
from predict import PredictSession
|
||||
import threading
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
from scrypted_sdk import RequestPictureOptions, MediaObject, Setting
|
||||
import os
|
||||
import json
|
||||
|
||||
def random_string():
|
||||
letters = string.ascii_lowercase
|
||||
return ''.join(random.choice(letters) for i in range(10))
|
||||
|
||||
|
||||
MIME_TYPE = 'x-scrypted-dlib/x-raw-image'
|
||||
|
||||
class DlibPlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(MIME_TYPE, nativeId=nativeId)
|
||||
|
||||
self.labels = {
|
||||
0: 'face'
|
||||
}
|
||||
|
||||
self.mutex = threading.Lock()
|
||||
self.known_faces = {}
|
||||
self.encoded_faces = {}
|
||||
self.load_known_faces()
|
||||
|
||||
def save_known_faces(self):
|
||||
j = json.dumps(self.known_faces)
|
||||
self.storage.setItem('known', j)
|
||||
|
||||
def load_known_faces(self):
|
||||
self.known_faces = {}
|
||||
self.encoded_faces = {}
|
||||
|
||||
try:
|
||||
self.known_faces = json.loads(self.storage.getItem('known'))
|
||||
except:
|
||||
pass
|
||||
|
||||
for known in self.known_faces:
|
||||
encoded = []
|
||||
self.encoded_faces[known] = encoded
|
||||
encodings = self.known_faces[known]
|
||||
for str in encodings:
|
||||
try:
|
||||
parsed = base64.decodebytes(bytes(str, 'utf-8'))
|
||||
encoding = np.frombuffer(parsed, dtype=np.float64)
|
||||
encoded.append(encoding)
|
||||
except:
|
||||
pass
|
||||
|
||||
# width, height, channels
|
||||
def get_input_details(self) -> Tuple[int, int, int]:
|
||||
pass
|
||||
|
||||
def get_input_size(self) -> Tuple[float, float]:
|
||||
pass
|
||||
|
||||
def getTriggerClasses(self) -> list[str]:
|
||||
return ['person']
|
||||
|
||||
def detect_once(self, input: Image.Image, settings: Any, src_size, cvss) -> ObjectsDetected:
|
||||
nparray = np.array(input.resize((int(input.width / 4), int(input.height / 4))))
|
||||
|
||||
with self.mutex:
|
||||
face_locations = face_recognition.face_locations(nparray)
|
||||
|
||||
for idx, face in enumerate(face_locations):
|
||||
t, r, b, l = face
|
||||
t *= 4
|
||||
r *= 4
|
||||
b *= 4
|
||||
l *= 4
|
||||
face_locations[idx] = (t, r, b, l)
|
||||
|
||||
nparray = np.array(input)
|
||||
|
||||
with self.mutex:
|
||||
face_encodings = face_recognition.face_encodings(nparray, face_locations)
|
||||
|
||||
all_ids = []
|
||||
all_faces = []
|
||||
for encoded in self.encoded_faces:
|
||||
all_ids += ([encoded] * len(self.encoded_faces[encoded]))
|
||||
all_faces += self.encoded_faces[encoded]
|
||||
|
||||
m = {}
|
||||
for idx, fe in enumerate(face_encodings):
|
||||
results = list(face_recognition.face_distance(all_faces, fe))
|
||||
|
||||
best = 1
|
||||
if len(results):
|
||||
best = min(results)
|
||||
minpos = results.index(best)
|
||||
|
||||
if best > .6:
|
||||
id = random_string() + '.jpg'
|
||||
print('top face %s' % best)
|
||||
print('new face %s' % id)
|
||||
encoded = [fe]
|
||||
self.encoded_faces[id] = encoded
|
||||
all_faces += encoded
|
||||
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
t, r, b, l = face_locations[idx]
|
||||
cropped = input.crop((l, t, r, b))
|
||||
fp = os.path.join(people, id)
|
||||
cropped.save(fp)
|
||||
else:
|
||||
id = all_ids[minpos]
|
||||
print('has face %s' % id)
|
||||
m[idx] = id
|
||||
|
||||
# return
|
||||
|
||||
objs = []
|
||||
|
||||
for face in face_locations:
|
||||
t, r, b, l = face
|
||||
obj = Prediction(0, 1, Rectangle(
|
||||
l,
|
||||
t,
|
||||
r,
|
||||
b
|
||||
))
|
||||
objs.append(obj)
|
||||
|
||||
ret = self.create_detection_result(objs, src_size, ['face'], cvss)
|
||||
|
||||
for idx, d in enumerate(ret['detections']):
|
||||
d['id'] = m.get(idx)
|
||||
d['name'] = m.get(idx)
|
||||
|
||||
return ret
|
||||
|
||||
def track(self, detection_session: PredictSession, ret: ObjectsDetected):
|
||||
pass
|
||||
|
||||
|
||||
async def takePicture(self, options: RequestPictureOptions = None) -> MediaObject:
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
for unknown in os.listdir(people):
|
||||
fp = os.path.join(people, unknown)
|
||||
ret = scrypted_sdk.mediaManager.createMediaObjectFromUrl('file:/' + fp)
|
||||
return await ret
|
||||
|
||||
black = os.path.join(volume, 'zip', 'unzipped', 'fs', 'black.jpg')
|
||||
ret = scrypted_sdk.mediaManager.createMediaObjectFromUrl('file:/' + black)
|
||||
return await ret
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
ret = []
|
||||
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
|
||||
choices = list(self.known_faces.keys())
|
||||
|
||||
for unknown in os.listdir(people):
|
||||
ret.append(
|
||||
{
|
||||
'key': unknown,
|
||||
'title': 'Name',
|
||||
'description': 'Associate this thumbnail with an existing person or identify a new person.',
|
||||
'choices': choices,
|
||||
'combobox': True,
|
||||
}
|
||||
)
|
||||
ret.append(
|
||||
{
|
||||
'key': 'delete',
|
||||
'title': 'Delete',
|
||||
'description': 'Delete this face.',
|
||||
'type': 'button',
|
||||
}
|
||||
)
|
||||
break
|
||||
|
||||
if not len(ret):
|
||||
ret.append(
|
||||
{
|
||||
'key': 'unknown',
|
||||
'title': 'Unknown People',
|
||||
'value': 'Waiting for unknown person...',
|
||||
'description': 'There are no more people that need to be identified.',
|
||||
'readonly': True,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
ret.append(
|
||||
{
|
||||
'key': 'known',
|
||||
'group': 'People',
|
||||
'title': 'Familiar People',
|
||||
'description': 'The people known to this plugin.',
|
||||
'choices': choices,
|
||||
'multiple': True,
|
||||
'value': choices,
|
||||
}
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
async def putSetting(self, key: str, value: str) -> None:
|
||||
if key == 'known':
|
||||
n = {}
|
||||
for k in value:
|
||||
n[k] = self.known_faces[k]
|
||||
self.known_faces = n
|
||||
self.save_known_faces()
|
||||
elif value or key == 'delete':
|
||||
volume = os.environ['SCRYPTED_PLUGIN_VOLUME']
|
||||
people = os.path.join(volume, 'unknown')
|
||||
os.makedirs(people, exist_ok=True)
|
||||
for unknown in os.listdir(people):
|
||||
fp = os.path.join(people, unknown)
|
||||
os.remove(fp)
|
||||
if key != 'delete':
|
||||
encoded = self.encoded_faces[key]
|
||||
strs = []
|
||||
for e in encoded:
|
||||
strs.append(base64.encodebytes(e.tobytes()).decode())
|
||||
if not self.known_faces.get(value):
|
||||
self.known_faces[value] = []
|
||||
self.known_faces[value] += strs
|
||||
self.save_known_faces()
|
||||
break
|
||||
|
||||
await self.onDeviceEvent(scrypted_sdk.ScryptedInterface.Settings.value, None)
|
||||
await self.onDeviceEvent(scrypted_sdk.ScryptedInterface.Camera.value, None)
|
||||
@@ -1,4 +0,0 @@
|
||||
from dlibplugin import DlibPlugin
|
||||
|
||||
def create_scrypted_plugin():
|
||||
return DlibPlugin()
|
||||
@@ -1 +0,0 @@
|
||||
../../tensorflow-lite/src/pipeline
|
||||
@@ -1,10 +0,0 @@
|
||||
# plugin
|
||||
Pillow>=5.4.1
|
||||
PyGObject>=3.30.4; sys_platform != 'win32'
|
||||
av>=10.0.0; sys_platform != 'linux' or platform_machine == 'x86_64' or platform_machine == 'aarch64'
|
||||
face-recognition
|
||||
|
||||
# sort_oh
|
||||
scipy
|
||||
filterpy
|
||||
numpy
|
||||
38
plugins/homekit/package-lock.json
generated
38
plugins/homekit/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.54",
|
||||
"version": "1.2.56",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.54",
|
||||
"version": "1.2.56",
|
||||
"dependencies": {
|
||||
"@koush/werift-src": "file:../../external/werift",
|
||||
"check-disk-space": "^3.4.0",
|
||||
@@ -47,26 +47,20 @@
|
||||
"examples/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.4.1",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"@types/node": "^20.10.6",
|
||||
"jest": "^29.7.0",
|
||||
"knip": "^3.7.0",
|
||||
"knip": "^3.9.0",
|
||||
"node-actionlint": "^1.2.2",
|
||||
"organize-imports-cli": "^0.10.0",
|
||||
"prettier": "^3.1.1",
|
||||
"process": "^0.11.10",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typedoc": "0.25.4",
|
||||
"typedoc": "0.25.5",
|
||||
"typedoc-plugin-markdown": "3.17.1",
|
||||
"typescript": "5.0.4"
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
@@ -127,7 +121,7 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.3.18",
|
||||
"version": "0.3.29",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -1306,26 +1300,20 @@
|
||||
"@koush/werift-src": {
|
||||
"version": "file:../../external/werift",
|
||||
"requires": {
|
||||
"@biomejs/biome": "^1.4.1",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"@types/node": "^20.10.6",
|
||||
"jest": "^29.7.0",
|
||||
"knip": "^3.7.0",
|
||||
"knip": "^3.9.0",
|
||||
"node-actionlint": "^1.2.2",
|
||||
"organize-imports-cli": "^0.10.0",
|
||||
"prettier": "^3.1.1",
|
||||
"process": "^0.11.10",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typedoc": "0.25.4",
|
||||
"typedoc": "0.25.5",
|
||||
"typedoc-plugin-markdown": "3.17.1",
|
||||
"typescript": "5.0.4"
|
||||
"typescript": "5.3.3"
|
||||
}
|
||||
},
|
||||
"@leichtgewicht/ip-codec": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.54",
|
||||
"version": "1.2.56",
|
||||
"description": "HomeKit Plugin for Scrypted",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
|
||||
@@ -24,8 +24,6 @@ export function createSnapshotHandler(device: ScryptedDevice & VideoCamera & Cam
|
||||
width: request.width,
|
||||
height: request.height,
|
||||
},
|
||||
// wait up to 2 seconds for the snapshot image, fallback to cached image
|
||||
timeout: 2000,
|
||||
})
|
||||
return await mediaManager.convertMediaObjectToBuffer(media, 'image/jpeg');
|
||||
}
|
||||
|
||||
@@ -354,15 +354,11 @@ export function createCameraStreamingDelegate(device: ScryptedDevice & VideoCame
|
||||
if (twoWayAudio) {
|
||||
let rtspServer: RtspServer;
|
||||
let track: string;
|
||||
let playing = false;
|
||||
session.audioReturn.once('message', async buffer => {
|
||||
let twoWayAudioState: 'stopped' | 'starting' | 'started' = 'stopped';
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
const decrypted = srtpSession.decrypt(buffer);
|
||||
const rtp = RtpPacket.deSerialize(decrypted);
|
||||
|
||||
if (rtp.header.payloadType !== session.startRequest.audio.pt)
|
||||
return;
|
||||
|
||||
twoWayAudioState = 'starting';
|
||||
const { clientPromise, url } = await listenZeroSingleClient();
|
||||
const rtspUrl = url.replace('tcp', 'rtsp');
|
||||
let sdp = createReturnAudioSdp(session.startRequest.audio);
|
||||
@@ -393,7 +389,7 @@ export function createCameraStreamingDelegate(device: ScryptedDevice & VideoCame
|
||||
device.stopIntercom();
|
||||
client.destroy();
|
||||
rtspServer = undefined;
|
||||
playing = false;
|
||||
twoWayAudioState = 'stopped';
|
||||
}
|
||||
// stop the intercom if the client dies for any reason.
|
||||
// allow the streaming session to continue however.
|
||||
@@ -402,16 +398,17 @@ export function createCameraStreamingDelegate(device: ScryptedDevice & VideoCame
|
||||
|
||||
rtspServer = new RtspServer(client, sdp);
|
||||
await rtspServer.handlePlayback();
|
||||
playing = true;
|
||||
twoWayAudioState = 'started';
|
||||
}
|
||||
catch (e) {
|
||||
console.error('two way audio failed', e);
|
||||
twoWayAudioState = 'stopped';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const srtpSession = new SrtpSession(session.aconfig);
|
||||
session.audioReturn.on('message', buffer => {
|
||||
if (!playing)
|
||||
if (twoWayAudioState === 'starting')
|
||||
return;
|
||||
|
||||
const decrypted = srtpSession.decrypt(buffer);
|
||||
@@ -420,6 +417,9 @@ export function createCameraStreamingDelegate(device: ScryptedDevice & VideoCame
|
||||
if (rtp.header.payloadType !== session.startRequest.audio.pt)
|
||||
return;
|
||||
|
||||
if (twoWayAudioState !== 'started')
|
||||
return start();
|
||||
|
||||
rtspServer.sendTrack(track, decrypted, false);
|
||||
});
|
||||
}
|
||||
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.39",
|
||||
"version": "0.1.42",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.39",
|
||||
"version": "0.1.42",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.1.39",
|
||||
"version": "0.1.42",
|
||||
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1159,7 +1159,7 @@ export class ObjectDetectionPlugin extends AutoenableMixinProvider implements Se
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
if (nativeId?.startsWith(SMART_MOTIONSENSOR_PREFIX)) {
|
||||
const smart = this.devices.get(nativeId) as SmartMotionSensor;
|
||||
smart?.listener?.removeListener();
|
||||
smart?.detectionListener?.removeListener();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
|
||||
},
|
||||
detectionTimeout: {
|
||||
title: 'Object Detection Timeout',
|
||||
description: 'Duration in seconds the sensor will report motion, before resetting.',
|
||||
description: 'Duration in seconds the sensor will report motion, before resetting. Setting this to 0 will reset the sensor when motion stops.',
|
||||
type: 'number',
|
||||
defaultValue: 60,
|
||||
},
|
||||
@@ -73,7 +73,8 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
|
||||
},
|
||||
});
|
||||
|
||||
listener: EventListenerRegister;
|
||||
detectionListener: EventListenerRegister;
|
||||
motionListener: EventListenerRegister;
|
||||
timeout: NodeJS.Timeout;
|
||||
lastPicture: Promise<MediaObject>;
|
||||
|
||||
@@ -143,8 +144,10 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
|
||||
|
||||
trigger() {
|
||||
this.resetTrigger();
|
||||
const duration: number = this.storageSettings.values.detectionTimeout;
|
||||
this.motionDetected = true;
|
||||
const duration: number = this.storageSettings.values.detectionTimeout;
|
||||
if (!duration)
|
||||
return;
|
||||
this.timeout = setTimeout(() => {
|
||||
this.motionDetected = false;
|
||||
}, duration * 1000);
|
||||
@@ -152,12 +155,14 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
|
||||
|
||||
rebind() {
|
||||
this.motionDetected = false;
|
||||
this.listener?.removeListener();
|
||||
this.listener = undefined;
|
||||
this.detectionListener?.removeListener();
|
||||
this.detectionListener = undefined;
|
||||
this.motionListener?.removeListener();
|
||||
this.motionListener = undefined;
|
||||
this.resetTrigger();
|
||||
|
||||
|
||||
const objectDetector: ObjectDetector & ScryptedDevice = this.storageSettings.values.objectDetector;
|
||||
const objectDetector: ObjectDetector & MotionSensor & ScryptedDevice = this.storageSettings.values.objectDetector;
|
||||
if (!objectDetector)
|
||||
return;
|
||||
|
||||
@@ -167,7 +172,19 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
|
||||
|
||||
const console = sdk.deviceManager.getMixinConsole(objectDetector.id, this.nativeId);
|
||||
|
||||
this.listener = objectDetector.listen(ScryptedInterface.ObjectDetector, (source, details, data) => {
|
||||
this.motionListener = objectDetector.listen({
|
||||
event: ScryptedInterface.MotionSensor,
|
||||
watch: true,
|
||||
}, (source, details, data) => {
|
||||
const duration: number = this.storageSettings.values.detectionTimeout;
|
||||
if (duration)
|
||||
return;
|
||||
|
||||
if (!objectDetector.motionDetected)
|
||||
this.motionDetected = false;
|
||||
});
|
||||
|
||||
this.detectionListener = objectDetector.listen(ScryptedInterface.ObjectDetector, (source, details, data) => {
|
||||
const detected: ObjectsDetected = data;
|
||||
|
||||
if (this.storageSettings.values.requireDetectionThumbnail && !detected.detectionId)
|
||||
@@ -214,7 +231,7 @@ export class SmartMotionSensor extends ScryptedDeviceBase implements Settings, R
|
||||
continue;
|
||||
if (levenshteinDistance(label, d.label) <= labelDistance)
|
||||
return true;
|
||||
this.console.log('No label does not match.', label, d.label);
|
||||
this.console.log('Label does not match.', label, d.label);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -9,6 +9,7 @@ dist/*.js
|
||||
dist/*.txt
|
||||
__pycache__
|
||||
all_models
|
||||
.venv
|
||||
sort_oh
|
||||
download_models.sh
|
||||
tsconfig.json
|
||||
.venv
|
||||
@@ -1,9 +1,11 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "koushik-thin",
|
||||
// "scrypted.debugHost": "koushik-ubuntuvm",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
|
||||
// "scrypted.debugHost": "koushik-ubuntuvm",
|
||||
// "scrypted.serverRoot": "/home/koush/.scrypted",
|
||||
|
||||
// pi local installation
|
||||
// "scrypted.debugHost": "192.168.2.119",
|
||||
// "scrypted.serverRoot": "/home/pi/.scrypted",
|
||||
@@ -11,6 +13,8 @@
|
||||
// local checkout
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "koushik-winvm",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
"python.analysis.extraPaths": [
|
||||
6
plugins/onnx/README.md
Normal file
6
plugins/onnx/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# ONNX Object Detection for Scrypted
|
||||
|
||||
This plugin adds object detection capabilities to any camera in Scrypted. Having a fast GPU and CPU is highly recommended.
|
||||
|
||||
The ONNX Plugin should only be used if you are a Scrypted NVR user. It will provide no
|
||||
benefits to HomeKit, which does its own detection processing.
|
||||
@@ -1,47 +1,48 @@
|
||||
{
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.18",
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.88",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.18",
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.88",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.39",
|
||||
"version": "0.3.29",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
"scrypted-deploy": "bin/scrypted-deploy.js",
|
||||
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
|
||||
"scrypted-package-json": "bin/scrypted-package-json.js",
|
||||
"scrypted-readme": "bin/scrypted-readme.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
@@ -60,12 +61,12 @@
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^18.11.9",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
@@ -73,10 +74,11 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@scrypted/tensorflow",
|
||||
"description": "Scrypted TensorFlow Object Detection",
|
||||
"name": "@scrypted/onnx",
|
||||
"description": "Scrypted ONNX Object Detection",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"coreml",
|
||||
"neural",
|
||||
"onnx",
|
||||
"motion",
|
||||
"object",
|
||||
"detect",
|
||||
"detection",
|
||||
@@ -26,14 +26,14 @@
|
||||
"scrypted-package-json": "scrypted-package-json"
|
||||
},
|
||||
"scrypted": {
|
||||
"name": "TensorFlow Object Detection",
|
||||
"name": "ONNX Object Detection",
|
||||
"pluginDependencies": [
|
||||
"@scrypted/objectdetector",
|
||||
"@scrypted/python-codecs"
|
||||
"@scrypted/objectdetector"
|
||||
],
|
||||
"runtime": "python",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"DeviceProvider",
|
||||
"Settings",
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview"
|
||||
@@ -42,5 +42,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.18"
|
||||
"version": "0.1.88"
|
||||
}
|
||||
1
plugins/onnx/src/common
Symbolic link
1
plugins/onnx/src/common
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openvino/src/common
|
||||
4
plugins/onnx/src/main.py
Normal file
4
plugins/onnx/src/main.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from ort import ONNXPlugin
|
||||
|
||||
def create_scrypted_plugin():
|
||||
return ONNXPlugin()
|
||||
251
plugins/onnx/src/ort/__init__.py
Normal file
251
plugins/onnx/src/ort/__init__.py
Normal file
@@ -0,0 +1,251 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import json
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from typing import Any, Tuple
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime
|
||||
import scrypted_sdk
|
||||
from PIL import Image
|
||||
from scrypted_sdk.other import SettingValue
|
||||
from scrypted_sdk.types import Setting
|
||||
|
||||
import common.yolo as yolo
|
||||
from predict import PredictPlugin
|
||||
|
||||
from .face_recognition import ONNXFaceRecognition
|
||||
|
||||
try:
|
||||
from .text_recognition import ONNXTextRecognition
|
||||
except:
|
||||
ONNXTextRecognition = None
|
||||
|
||||
availableModels = [
|
||||
"Default",
|
||||
"scrypted_yolo_nas_s_320",
|
||||
"scrypted_yolov6n_320",
|
||||
"scrypted_yolov6n",
|
||||
"scrypted_yolov6s_320",
|
||||
"scrypted_yolov6s",
|
||||
"scrypted_yolov9c_320",
|
||||
"scrypted_yolov9c",
|
||||
"scrypted_yolov8n_320",
|
||||
"scrypted_yolov8n",
|
||||
]
|
||||
|
||||
def parse_labels(names):
|
||||
j = ast.literal_eval(names)
|
||||
ret = {}
|
||||
for k, v in j.items():
|
||||
ret[int(k)] = v
|
||||
return ret
|
||||
|
||||
class ONNXPlugin(
|
||||
PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings, scrypted_sdk.DeviceProvider
|
||||
):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
if model == "Default" or model not in availableModels:
|
||||
if model != "Default":
|
||||
self.storage.setItem("model", "Default")
|
||||
model = "scrypted_yolov8n_320"
|
||||
self.yolo = "yolo" in model
|
||||
self.scrypted_yolo_nas = "scrypted_yolo_nas" in model
|
||||
self.scrypted_yolo = "scrypted_yolo" in model
|
||||
self.scrypted_model = "scrypted" in model
|
||||
|
||||
print(f"model {model}")
|
||||
|
||||
onnxmodel = model if self.scrypted_yolo_nas else "best" if self.scrypted_model else model
|
||||
|
||||
model_version = "v2"
|
||||
onnxfile = self.downloadFile(
|
||||
f"https://raw.githubusercontent.com/koush/onnx-models/main/{model}/{onnxmodel}.onnx",
|
||||
f"{model_version}/{model}/{onnxmodel}.onnx",
|
||||
)
|
||||
|
||||
print(onnxfile)
|
||||
|
||||
deviceIds = self.storage.getItem("deviceIds") or '["0"]'
|
||||
deviceIds = json.loads(deviceIds)
|
||||
if not len(deviceIds):
|
||||
deviceIds = ["0"]
|
||||
self.deviceIds = deviceIds
|
||||
|
||||
compiled_models = []
|
||||
self.compiled_models = {}
|
||||
|
||||
try:
|
||||
for deviceId in deviceIds:
|
||||
sess_options = onnxruntime.SessionOptions()
|
||||
|
||||
providers: list[str] = []
|
||||
if sys.platform == 'darwin':
|
||||
providers.append("CoreMLExecutionProvider")
|
||||
|
||||
if 'linux' in sys.platform and platform.machine() == 'x86_64':
|
||||
deviceId = int(deviceId)
|
||||
providers.append(("CUDAExecutionProvider", { "device_id": deviceId }))
|
||||
|
||||
providers.append('CPUExecutionProvider')
|
||||
|
||||
compiled_model = onnxruntime.InferenceSession(onnxfile, sess_options=sess_options, providers=providers)
|
||||
compiled_models.append(compiled_model)
|
||||
|
||||
input = compiled_model.get_inputs()[0]
|
||||
self.model_dim = input.shape[2]
|
||||
self.input_name = input.name
|
||||
self.labels = parse_labels(compiled_model.get_modelmeta().custom_metadata_map['names'])
|
||||
|
||||
except:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
print("Reverting all settings.")
|
||||
self.storage.removeItem("model")
|
||||
self.storage.removeItem("deviceIds")
|
||||
self.requestRestart()
|
||||
|
||||
def executor_initializer():
|
||||
thread_name = threading.current_thread().name
|
||||
interpreter = compiled_models.pop()
|
||||
self.compiled_models[thread_name] = interpreter
|
||||
print('Runtime initialized on thread {}'.format(thread_name))
|
||||
|
||||
self.executor = concurrent.futures.ThreadPoolExecutor(
|
||||
initializer=executor_initializer,
|
||||
max_workers=len(compiled_models),
|
||||
thread_name_prefix="onnx",
|
||||
)
|
||||
|
||||
self.prepareExecutor = concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=len(compiled_models),
|
||||
thread_name_prefix="onnx-prepare",
|
||||
)
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
devices = [
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "ONNX Face Recognition",
|
||||
},
|
||||
]
|
||||
|
||||
if ONNXTextRecognition:
|
||||
devices.append(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "ONNX Text Recognition",
|
||||
},
|
||||
)
|
||||
|
||||
await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
{
|
||||
"devices": devices,
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
async def getDevice(self, nativeId: str) -> Any:
|
||||
if nativeId == "facerecognition":
|
||||
self.faceDevice = self.faceDevice or ONNXFaceRecognition(self, nativeId)
|
||||
return self.faceDevice
|
||||
elif nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or ONNXTextRecognition(self, nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
model = self.storage.getItem("model") or "Default"
|
||||
deviceIds = self.storage.getItem("deviceIds") or '["0"]'
|
||||
deviceIds = json.loads(deviceIds)
|
||||
|
||||
return [
|
||||
{
|
||||
"key": "model",
|
||||
"title": "Model",
|
||||
"description": "The detection model used to find objects.",
|
||||
"choices": availableModels,
|
||||
"value": model,
|
||||
},
|
||||
{
|
||||
"key": "deviceIds",
|
||||
"title": "Device IDs",
|
||||
"description": "Optional: Assign multiple CUDA Device IDs to use for detection.",
|
||||
"choices": deviceIds,
|
||||
"combobox": True,
|
||||
"multiple": True,
|
||||
"value": deviceIds,
|
||||
},
|
||||
]
|
||||
|
||||
async def putSetting(self, key: str, value: SettingValue):
|
||||
if (key == 'deviceIds'):
|
||||
value = json.dumps(value)
|
||||
self.storage.setItem(key, value)
|
||||
await self.onDeviceEvent(scrypted_sdk.ScryptedInterface.Settings.value, None)
|
||||
self.requestRestart()
|
||||
|
||||
# width, height, channels
|
||||
def get_input_details(self) -> Tuple[int, int, int]:
|
||||
return [self.model_dim, self.model_dim, 3]
|
||||
|
||||
def get_input_size(self) -> Tuple[int, int]:
|
||||
return [self.model_dim, self.model_dim]
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
def prepare():
|
||||
im = np.array(input)
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
compiled_model = self.compiled_models[threading.current_thread().name]
|
||||
output_tensors = compiled_model.run(None, { self.input_name: input_tensor })
|
||||
if self.scrypted_yolo_nas:
|
||||
objs = yolo.parse_yolo_nas([output_tensors[1], output_tensors[0]])
|
||||
else:
|
||||
objs = yolo.parse_yolov9(output_tensors[0][0])
|
||||
return objs
|
||||
|
||||
try:
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
self.prepareExecutor, lambda: prepare()
|
||||
)
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
self.executor, lambda: predict(input_tensor)
|
||||
)
|
||||
|
||||
except:
|
||||
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
112
plugins/onnx/src/ort/face_recognition.py
Normal file
112
plugins/onnx/src/ort/face_recognition.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime
|
||||
from PIL import Image
|
||||
|
||||
from predict.face_recognize import FaceRecognizeDetection
|
||||
|
||||
|
||||
class ONNXFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, plugin, nativeId: str | None = None):
|
||||
self.plugin = plugin
|
||||
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
onnxmodel = "best" if "scrypted" in model else model
|
||||
model_version = "v1"
|
||||
onnxfile = self.downloadFile(
|
||||
f"https://raw.githubusercontent.com/koush/onnx-models/main/{model}/{onnxmodel}.onnx",
|
||||
f"{model_version}/{model}/{onnxmodel}.onnx",
|
||||
)
|
||||
print(onnxfile)
|
||||
|
||||
compiled_models_array = []
|
||||
compiled_models = {}
|
||||
deviceIds = self.plugin.deviceIds
|
||||
|
||||
for deviceId in deviceIds:
|
||||
sess_options = onnxruntime.SessionOptions()
|
||||
|
||||
providers: list[str] = []
|
||||
if sys.platform == "darwin":
|
||||
providers.append("CoreMLExecutionProvider")
|
||||
|
||||
if "linux" in sys.platform and platform.machine() == "x86_64":
|
||||
deviceId = int(deviceId)
|
||||
providers.append(("CUDAExecutionProvider", {"device_id": deviceId}))
|
||||
|
||||
providers.append("CPUExecutionProvider")
|
||||
|
||||
compiled_model = onnxruntime.InferenceSession(
|
||||
onnxfile, sess_options=sess_options, providers=providers
|
||||
)
|
||||
compiled_models_array.append(compiled_model)
|
||||
|
||||
input = compiled_model.get_inputs()[0]
|
||||
input_name = input.name
|
||||
|
||||
def executor_initializer():
|
||||
thread_name = threading.current_thread().name
|
||||
interpreter = compiled_models_array.pop()
|
||||
compiled_models[thread_name] = interpreter
|
||||
print("Runtime initialized on thread {}".format(thread_name))
|
||||
|
||||
executor = concurrent.futures.ThreadPoolExecutor(
|
||||
initializer=executor_initializer,
|
||||
max_workers=len(compiled_models_array),
|
||||
thread_name_prefix="face",
|
||||
)
|
||||
|
||||
prepareExecutor = concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=len(compiled_models_array),
|
||||
thread_name_prefix="face-prepare",
|
||||
)
|
||||
|
||||
return compiled_models, input_name, prepareExecutor, executor
|
||||
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
compiled_models, input_name, prepareExecutor, executor = self.detectModel
|
||||
|
||||
def prepare():
|
||||
im = np.array(input)
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
return im
|
||||
|
||||
def predict(input_tensor):
|
||||
compiled_model = compiled_models[threading.current_thread().name]
|
||||
output_tensors = compiled_model.run(None, {input_name: input_tensor})
|
||||
return output_tensors
|
||||
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
prepareExecutor, lambda: prepare()
|
||||
)
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, lambda: predict(input_tensor)
|
||||
)
|
||||
|
||||
return objs[0][0]
|
||||
|
||||
async def predictFaceModel(self, input: np.ndarray):
|
||||
compiled_models, input_name, prepareExecutor, executor = self.faceModel
|
||||
|
||||
def predict():
|
||||
compiled_model = compiled_models[threading.current_thread().name]
|
||||
output_tensors = compiled_model.run(None, {input_name: input})
|
||||
return output_tensors
|
||||
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, lambda: predict()
|
||||
)
|
||||
|
||||
return objs[0]
|
||||
102
plugins/onnx/src/ort/text_recognition.py
Normal file
102
plugins/onnx/src/ort/text_recognition.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime
|
||||
from PIL import Image
|
||||
|
||||
from predict.text_recognize import TextRecognition
|
||||
|
||||
|
||||
class ONNXTextRecognition(TextRecognition):
|
||||
def __init__(self, plugin, nativeId: str | None = None):
|
||||
self.plugin = plugin
|
||||
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
onnxmodel = model
|
||||
model_version = "v3"
|
||||
onnxfile = self.downloadFile(
|
||||
f"https://raw.githubusercontent.com/koush/onnx-models/main/{model}/{onnxmodel}.onnx",
|
||||
f"{model_version}/{model}/{onnxmodel}.onnx",
|
||||
)
|
||||
print(onnxfile)
|
||||
|
||||
compiled_models_array = []
|
||||
compiled_models = {}
|
||||
deviceIds = self.plugin.deviceIds
|
||||
|
||||
for deviceId in deviceIds:
|
||||
sess_options = onnxruntime.SessionOptions()
|
||||
|
||||
providers: list[str] = []
|
||||
if sys.platform == "darwin":
|
||||
providers.append("CoreMLExecutionProvider")
|
||||
|
||||
if "linux" in sys.platform and platform.machine() == "x86_64":
|
||||
deviceId = int(deviceId)
|
||||
providers.append(("CUDAExecutionProvider", {"device_id": deviceId}))
|
||||
|
||||
providers.append("CPUExecutionProvider")
|
||||
|
||||
compiled_model = onnxruntime.InferenceSession(
|
||||
onnxfile, sess_options=sess_options, providers=providers
|
||||
)
|
||||
compiled_models_array.append(compiled_model)
|
||||
|
||||
input = compiled_model.get_inputs()[0]
|
||||
input_name = input.name
|
||||
|
||||
def executor_initializer():
|
||||
thread_name = threading.current_thread().name
|
||||
interpreter = compiled_models_array.pop()
|
||||
compiled_models[thread_name] = interpreter
|
||||
print("Runtime initialized on thread {}".format(thread_name))
|
||||
|
||||
executor = concurrent.futures.ThreadPoolExecutor(
|
||||
initializer=executor_initializer,
|
||||
max_workers=len(compiled_models_array),
|
||||
thread_name_prefix="face",
|
||||
)
|
||||
|
||||
prepareExecutor = concurrent.futures.ThreadPoolExecutor(
|
||||
max_workers=len(compiled_models_array),
|
||||
thread_name_prefix="face-prepare",
|
||||
)
|
||||
|
||||
return compiled_models, input_name, prepareExecutor, executor
|
||||
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
compiled_models, input_name, prepareExecutor, executor = self.detectModel
|
||||
|
||||
def predict():
|
||||
compiled_model = compiled_models[threading.current_thread().name]
|
||||
output_tensors = compiled_model.run(None, {input_name: input})
|
||||
return output_tensors
|
||||
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, lambda: predict()
|
||||
)
|
||||
|
||||
return objs[0]
|
||||
|
||||
async def predictTextModel(self, input: np.ndarray):
|
||||
input = input.astype(np.float32)
|
||||
compiled_models, input_name, prepareExecutor, executor = self.textModel
|
||||
|
||||
def predict():
|
||||
compiled_model = compiled_models[threading.current_thread().name]
|
||||
output_tensors = compiled_model.run(None, {input_name: input})
|
||||
return output_tensors
|
||||
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
executor, lambda: predict()
|
||||
)
|
||||
|
||||
return objs[0]
|
||||
1
plugins/onnx/src/requirements.optional.txt
Normal file
1
plugins/onnx/src/requirements.optional.txt
Normal file
@@ -0,0 +1 @@
|
||||
opencv-python
|
||||
13
plugins/onnx/src/requirements.txt
Normal file
13
plugins/onnx/src/requirements.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# uncomment to require cuda 12, but most stuff is still targetting cuda 11.
|
||||
# however, stuff targetted for cuda 11 can still run on cuda 12.
|
||||
# --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
|
||||
onnxruntime-gpu; 'linux' in sys_platform and platform_machine == 'x86_64'
|
||||
# cpu and coreml execution provider
|
||||
onnxruntime; 'linux' not in sys_platform or platform_machine != 'x86_64'
|
||||
# nightly?
|
||||
# ort-nightly-gpu==1.17.3.dev20240409002
|
||||
|
||||
# pillow-simd is available on x64 linux
|
||||
# pillow-simd confirmed not building with arm64 linux or apple silicon
|
||||
Pillow>=5.4.1; 'linux' not in sys_platform or platform_machine != 'x86_64'
|
||||
pillow-simd; 'linux' in sys_platform and platform_machine == 'x86_64'
|
||||
@@ -1,9 +1,13 @@
|
||||
# plugin
|
||||
numpy>=1.16.2
|
||||
# pillow for anything not intel linux
|
||||
Pillow>=5.4.1; sys_platform != 'linux' or platform_machine != 'x86_64'
|
||||
pillow-simd; sys_platform == 'linux' and platform_machine == 'x86_64'
|
||||
|
||||
imutils>=0.5.0
|
||||
# opencv-python is not available on armhf
|
||||
|
||||
# locked to version because 4.8.0.76 is broken.
|
||||
opencv-python==4.8.0.74; sys_platform != 'linux' or platform_machine == 'x86_64' or platform_machine == 'aarch64'
|
||||
# todo: check newer versions.
|
||||
opencv-python==4.8.0.74
|
||||
|
||||
# pillow-simd is available on x64 linux
|
||||
# pillow-simd confirmed not building with arm64 linux or apple silicon
|
||||
Pillow>=5.4.1; 'linux' not in sys_platform or platform_machine != 'x86_64'
|
||||
pillow-simd; 'linux' in sys_platform and platform_machine == 'x86_64'
|
||||
|
||||
10
plugins/openvino/package-lock.json
generated
10
plugins/openvino/package-lock.json
generated
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.77",
|
||||
"version": "0.1.86",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.77",
|
||||
"version": "0.1.86",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.97",
|
||||
"version": "0.3.29",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
@@ -65,7 +65,7 @@
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.77"
|
||||
"version": "0.1.86"
|
||||
}
|
||||
|
||||
@@ -8,12 +8,26 @@ from common.softmax import softmax
|
||||
from common.colors import ensureRGBData
|
||||
import math
|
||||
|
||||
async def crop_text(d: ObjectDetectionResult, image: scrypted_sdk.Image, width: int, height: int):
|
||||
def skew_image(image: Image, skew_angle_rad: float):
|
||||
skew_matrix = [1, 0, 0, skew_angle_rad, 1, 0]
|
||||
|
||||
# Apply the transformation
|
||||
skewed_image = image.transform(
|
||||
image.size, Image.AFFINE, skew_matrix, resample=Image.BICUBIC
|
||||
)
|
||||
|
||||
return skewed_image
|
||||
|
||||
async def crop_text(d: ObjectDetectionResult, image: scrypted_sdk.Image):
|
||||
l, t, w, h = d["boundingBox"]
|
||||
l = math.floor(l)
|
||||
t = math.floor(t)
|
||||
l = max(0, math.floor(l))
|
||||
t = max(0, math.floor(t))
|
||||
w = math.floor(w)
|
||||
h = math.floor(h)
|
||||
if l + w > image.width:
|
||||
w = image.width - l
|
||||
if t + h > image.height:
|
||||
h = image.height - t
|
||||
format = image.format or 'rgb'
|
||||
cropped = await image.toBuffer(
|
||||
{
|
||||
@@ -27,14 +41,30 @@ async def crop_text(d: ObjectDetectionResult, image: scrypted_sdk.Image, width:
|
||||
}
|
||||
)
|
||||
pilImage = await ensureRGBData(cropped, (w, h), format)
|
||||
resized = pilImage.resize((width, height), resample=Image.LANCZOS).convert("L")
|
||||
pilImage.close()
|
||||
return resized
|
||||
return pilImage
|
||||
|
||||
def calculate_y_change(original_height, skew_angle_radians):
|
||||
# Calculate the change in y-position
|
||||
y_change = original_height * math.tan(skew_angle_radians)
|
||||
|
||||
return y_change
|
||||
|
||||
async def prepare_text_result(d: ObjectDetectionResult, image: scrypted_sdk.Image, skew_angle: float):
|
||||
textImage = await crop_text(d, image)
|
||||
|
||||
skew_height_change = calculate_y_change(d["boundingBox"][3], skew_angle)
|
||||
skew_height_change = math.floor(skew_height_change)
|
||||
textImage = skew_image(textImage, skew_angle)
|
||||
# crop skew_height_change from top
|
||||
if skew_height_change > 0:
|
||||
textImage = textImage.crop((0, 0, textImage.width, textImage.height - skew_height_change))
|
||||
elif skew_height_change < 0:
|
||||
textImage = textImage.crop((0, -skew_height_change, textImage.width, textImage.height))
|
||||
|
||||
async def prepare_text_result(d: ObjectDetectionResult, image: scrypted_sdk.Image):
|
||||
new_height = 64
|
||||
new_width = int(d["boundingBox"][2] * new_height / d["boundingBox"][3])
|
||||
textImage = await crop_text(d, image, new_width, new_height)
|
||||
new_width = int(textImage.width * new_height / textImage.height)
|
||||
textImage = textImage.resize((new_width, new_height), resample=Image.LANCZOS).convert("L")
|
||||
|
||||
new_width = 256
|
||||
# calculate padding dimensions
|
||||
padding = (0, 0, new_width - textImage.width, 0)
|
||||
@@ -50,7 +80,6 @@ async def prepare_text_result(d: ObjectDetectionResult, image: scrypted_sdk.Imag
|
||||
# test normalize contrast
|
||||
# image_tensor = (image_tensor - np.min(image_tensor)) / (np.max(image_tensor) - np.min(image_tensor))
|
||||
|
||||
|
||||
image_tensor = (image_tensor - 0.5) / 0.5
|
||||
|
||||
image_tensor = np.expand_dims(image_tensor, axis=0)
|
||||
|
||||
@@ -6,6 +6,20 @@ from predict.rectangle import Rectangle
|
||||
|
||||
defaultThreshold = .2
|
||||
|
||||
def parse_yolo_nas(predictions):
|
||||
objs = []
|
||||
for pred_scores, pred_bboxes in zip(*predictions):
|
||||
i, j = np.nonzero(pred_scores > .5)
|
||||
pred_bboxes = pred_bboxes[i]
|
||||
pred_cls_conf = pred_scores[i, j]
|
||||
pred_cls_label = j[:]
|
||||
for box, conf, label in zip(pred_bboxes, pred_cls_conf, pred_cls_label):
|
||||
obj = Prediction(
|
||||
int(label), conf.astype(float), Rectangle(box[0].astype(float), box[1].astype(float), box[2].astype(float), box[3].astype(float))
|
||||
)
|
||||
objs.append(obj)
|
||||
return objs
|
||||
|
||||
def parse_yolov9(results, threshold = defaultThreshold, scale = None, confidence_scale = None):
|
||||
objs = []
|
||||
keep = np.argwhere(results[4:] > threshold)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import json
|
||||
import re
|
||||
import traceback
|
||||
from typing import Any, Tuple
|
||||
|
||||
import numpy as np
|
||||
@@ -11,18 +13,24 @@ import scrypted_sdk
|
||||
from PIL import Image
|
||||
from scrypted_sdk.other import SettingValue
|
||||
from scrypted_sdk.types import Setting
|
||||
import concurrent.futures
|
||||
|
||||
import common.yolo as yolo
|
||||
from predict import Prediction, PredictPlugin
|
||||
from predict.rectangle import Rectangle
|
||||
|
||||
from .recognition import OpenVINORecognition
|
||||
from .face_recognition import OpenVINOFaceRecognition
|
||||
|
||||
try:
|
||||
from .text_recognition import OpenVINOTextRecognition
|
||||
except:
|
||||
OpenVINOTextRecognition = None
|
||||
|
||||
predictExecutor = concurrent.futures.ThreadPoolExecutor(1, "OpenVINO-Predict")
|
||||
prepareExecutor = concurrent.futures.ThreadPoolExecutor(1, "OpenVINO-Prepare")
|
||||
|
||||
availableModels = [
|
||||
"Default",
|
||||
"scrypted_yolo_nas_s_320",
|
||||
"scrypted_yolov6n_320",
|
||||
"scrypted_yolov6n",
|
||||
"scrypted_yolov6s_320",
|
||||
@@ -92,6 +100,22 @@ class OpenVINOPlugin(
|
||||
mode = self.storage.getItem("mode")
|
||||
if mode == "Default":
|
||||
mode = "AUTO"
|
||||
|
||||
dgpus = []
|
||||
# search for NVIDIA dGPU, as that is not preferred by AUTO for some reason?
|
||||
# todo: create separate core per NVIDIA dGPU as inference does not seem to
|
||||
# be distributed to multiple dGPU.
|
||||
for device in self.available_devices:
|
||||
try:
|
||||
full_device_name = self.core.get_property(device, "FULL_DEVICE_NAME")
|
||||
if "NVIDIA" in full_device_name and "dGPU" in full_device_name:
|
||||
dgpus.append(device)
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(dgpus):
|
||||
mode = f"AUTO:{','.join(dgpus)},CPU"
|
||||
|
||||
mode = mode or "AUTO"
|
||||
self.mode = mode
|
||||
|
||||
@@ -101,10 +125,10 @@ class OpenVINOPlugin(
|
||||
if using_mode == "AUTO":
|
||||
if "GPU" in available_devices:
|
||||
using_mode = "GPU"
|
||||
if using_mode == "GPU":
|
||||
precision = "FP16"
|
||||
else:
|
||||
precision = "FP32"
|
||||
|
||||
# FP16 is smaller and the default export. no tangible performance difference.
|
||||
# https://docs.openvino.ai/2023.3/openvino_docs_OV_Converter_UG_Conversion_Options.html
|
||||
precision = "FP16"
|
||||
|
||||
self.precision = precision
|
||||
|
||||
@@ -114,6 +138,7 @@ class OpenVINOPlugin(
|
||||
self.storage.setItem("model", "Default")
|
||||
model = "scrypted_yolov8n_320"
|
||||
self.yolo = "yolo" in model
|
||||
self.scrypted_yolo_nas = "scrypted_yolo_nas" in model
|
||||
self.scrypted_yolo = "scrypted_yolo" in model
|
||||
self.scrypted_model = "scrypted" in model
|
||||
self.sigmoid = model == "yolo-v4-tiny-tf"
|
||||
@@ -131,7 +156,12 @@ class OpenVINOPlugin(
|
||||
f"https://raw.githubusercontent.com/koush/openvino-models/main/{model}/{precision}/{ovmodel}.bin",
|
||||
f"{model_version}/{model}/{precision}/{ovmodel}.bin",
|
||||
)
|
||||
if self.scrypted_model:
|
||||
if self.scrypted_yolo_nas:
|
||||
labelsFile = self.downloadFile(
|
||||
"https://raw.githubusercontent.com/koush/openvino-models/main/scrypted_nas_labels.txt",
|
||||
"scrypted_nas_labels.txt",
|
||||
)
|
||||
elif self.scrypted_model:
|
||||
labelsFile = self.downloadFile(
|
||||
"https://raw.githubusercontent.com/koush/openvino-models/main/scrypted_labels.txt",
|
||||
"scrypted_labels.txt",
|
||||
@@ -174,6 +204,8 @@ class OpenVINOPlugin(
|
||||
labels_contents = open(labelsFile, "r").read()
|
||||
self.labels = parse_label_contents(labels_contents)
|
||||
|
||||
self.faceDevice = None
|
||||
self.textDevice = None
|
||||
asyncio.ensure_future(self.prepareRecognitionModels(), loop=self.loop)
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
@@ -242,7 +274,10 @@ class OpenVINOPlugin(
|
||||
objs = []
|
||||
|
||||
if self.scrypted_yolo:
|
||||
objs = yolo.parse_yolov9(output_tensors[0][0])
|
||||
if self.scrypted_yolo_nas:
|
||||
objs = yolo.parse_yolo_nas([output_tensors[1], output_tensors[0]])
|
||||
else:
|
||||
objs = yolo.parse_yolov9(output_tensors[0][0])
|
||||
return objs
|
||||
|
||||
if self.yolo:
|
||||
@@ -294,30 +329,34 @@ class OpenVINOPlugin(
|
||||
|
||||
return objs
|
||||
|
||||
# the input_tensor can be created with the shared_memory=True parameter,
|
||||
# but that seems to cause issues on some platforms.
|
||||
if self.scrypted_yolo:
|
||||
im = np.stack([input])
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
im = ov.Tensor(array=im)
|
||||
input_tensor = im
|
||||
elif self.yolo:
|
||||
input_tensor = ov.Tensor(
|
||||
array=np.expand_dims(np.array(input), axis=0).astype(np.float32)
|
||||
)
|
||||
else:
|
||||
input_tensor = ov.Tensor(array=np.expand_dims(np.array(input), axis=0))
|
||||
|
||||
def prepare():
|
||||
# the input_tensor can be created with the shared_memory=True parameter,
|
||||
# but that seems to cause issues on some platforms.
|
||||
if self.scrypted_yolo:
|
||||
im = np.array(input)
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
input_tensor = ov.Tensor(array=im)
|
||||
elif self.yolo:
|
||||
input_tensor = ov.Tensor(
|
||||
array=np.expand_dims(np.array(input), axis=0).astype(np.float32)
|
||||
)
|
||||
else:
|
||||
input_tensor = ov.Tensor(array=np.expand_dims(np.array(input), axis=0))
|
||||
return input_tensor
|
||||
|
||||
try:
|
||||
input_tensor = await asyncio.get_event_loop().run_in_executor(
|
||||
prepareExecutor, lambda: prepare()
|
||||
)
|
||||
objs = await asyncio.get_event_loop().run_in_executor(
|
||||
predictExecutor, lambda: predict(input_tensor)
|
||||
)
|
||||
|
||||
except:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
@@ -326,22 +365,42 @@ class OpenVINOPlugin(
|
||||
|
||||
async def prepareRecognitionModels(self):
|
||||
try:
|
||||
devices = [
|
||||
{
|
||||
"nativeId": "facerecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "OpenVINO Face Recognition",
|
||||
},
|
||||
]
|
||||
|
||||
if OpenVINOTextRecognition:
|
||||
devices.append(
|
||||
{
|
||||
"nativeId": "textrecognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "OpenVINO Text Recognition",
|
||||
},
|
||||
)
|
||||
|
||||
await scrypted_sdk.deviceManager.onDevicesChanged(
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"nativeId": "recognition",
|
||||
"type": scrypted_sdk.ScryptedDeviceType.Builtin.value,
|
||||
"interfaces": [
|
||||
scrypted_sdk.ScryptedInterface.ObjectDetection.value,
|
||||
],
|
||||
"name": "OpenVINO Recognition",
|
||||
}
|
||||
]
|
||||
"devices": devices,
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
async def getDevice(self, nativeId: str) -> Any:
|
||||
return OpenVINORecognition(self, nativeId)
|
||||
if nativeId == "facerecognition":
|
||||
self.faceDevice = self.faceDevice or OpenVINOFaceRecognition(self, nativeId)
|
||||
return self.faceDevice
|
||||
elif nativeId == "textrecognition":
|
||||
self.textDevice = self.textDevice or OpenVINOTextRecognition(self, nativeId)
|
||||
return self.textDevice
|
||||
raise Exception("unknown device")
|
||||
|
||||
9
plugins/openvino/src/ov/async_infer.py
Normal file
9
plugins/openvino/src/ov/async_infer.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import asyncio
|
||||
|
||||
async def start_async(infer_request):
|
||||
future = asyncio.Future(loop = asyncio.get_event_loop())
|
||||
def callback(status = None, result = None):
|
||||
future.set_result(None)
|
||||
infer_request.set_callback(callback, None)
|
||||
infer_request.start_async()
|
||||
await future
|
||||
@@ -1,25 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import openvino.runtime as ov
|
||||
from ov import async_infer
|
||||
from PIL import Image
|
||||
|
||||
import numpy as np
|
||||
|
||||
from predict.recognize import RecognizeDetection
|
||||
from predict.face_recognize import FaceRecognizeDetection
|
||||
|
||||
|
||||
def euclidean_distance(arr1, arr2):
|
||||
return np.linalg.norm(arr1 - arr2)
|
||||
|
||||
|
||||
def cosine_similarity(vector_a, vector_b):
|
||||
dot_product = np.dot(vector_a, vector_b)
|
||||
norm_a = np.linalg.norm(vector_a)
|
||||
norm_b = np.linalg.norm(vector_b)
|
||||
similarity = dot_product / (norm_a * norm_b)
|
||||
return similarity
|
||||
|
||||
class OpenVINORecognition(RecognizeDetection):
|
||||
class OpenVINOFaceRecognition(FaceRecognizeDetection):
|
||||
def __init__(self, plugin, nativeId: str | None = None):
|
||||
self.plugin = plugin
|
||||
|
||||
@@ -40,32 +29,20 @@ class OpenVINORecognition(RecognizeDetection):
|
||||
print(xmlFile, binFile)
|
||||
return self.plugin.core.compile_model(xmlFile, self.plugin.mode)
|
||||
|
||||
def predictDetectModel(self, input):
|
||||
async def predictDetectModel(self, input: Image.Image):
|
||||
infer_request = self.detectModel.create_infer_request()
|
||||
im = np.stack([input])
|
||||
im = np.expand_dims(input, axis=0)
|
||||
im = im.transpose((0, 3, 1, 2)) # BHWC to BCHW, (n, 3, h, w)
|
||||
im = im.astype(np.float32) / 255.0
|
||||
im = np.ascontiguousarray(im) # contiguous
|
||||
im = ov.Tensor(array=im)
|
||||
input_tensor = im
|
||||
infer_request.set_input_tensor(input_tensor)
|
||||
infer_request.start_async()
|
||||
infer_request.wait()
|
||||
infer_request.set_input_tensor(im)
|
||||
await async_infer.start_async(infer_request)
|
||||
return infer_request.output_tensors[0].data[0]
|
||||
|
||||
def predictFaceModel(self, input):
|
||||
async def predictFaceModel(self, input: np.ndarray):
|
||||
im = ov.Tensor(array=input)
|
||||
infer_request = self.faceModel.create_infer_request()
|
||||
infer_request.set_input_tensor(im)
|
||||
infer_request.start_async()
|
||||
infer_request.wait()
|
||||
await async_infer.start_async(infer_request)
|
||||
return infer_request.output_tensors[0].data[0]
|
||||
|
||||
def predictTextModel(self, input):
|
||||
input = input.astype(np.float32)
|
||||
im = ov.Tensor(array=input)
|
||||
infer_request = self.textModel.create_infer_request()
|
||||
infer_request.set_input_tensor(im)
|
||||
infer_request.start_async()
|
||||
infer_request.wait()
|
||||
return infer_request.output_tensors[0].data
|
||||
45
plugins/openvino/src/ov/text_recognition.py
Normal file
45
plugins/openvino/src/ov/text_recognition.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import openvino.runtime as ov
|
||||
from ov import async_infer
|
||||
|
||||
from predict.text_recognize import TextRecognition
|
||||
|
||||
|
||||
class OpenVINOTextRecognition(TextRecognition):
|
||||
def __init__(self, plugin, nativeId: str | None = None):
|
||||
self.plugin = plugin
|
||||
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
def downloadModel(self, model: str):
|
||||
ovmodel = "best"
|
||||
precision = self.plugin.precision
|
||||
model_version = "v5"
|
||||
xmlFile = self.downloadFile(
|
||||
f"https://raw.githubusercontent.com/koush/openvino-models/main/{model}/{precision}/{ovmodel}.xml",
|
||||
f"{model_version}/{model}/{precision}/{ovmodel}.xml",
|
||||
)
|
||||
binFile = self.downloadFile(
|
||||
f"https://raw.githubusercontent.com/koush/openvino-models/main/{model}/{precision}/{ovmodel}.bin",
|
||||
f"{model_version}/{model}/{precision}/{ovmodel}.bin",
|
||||
)
|
||||
print(xmlFile, binFile)
|
||||
return self.plugin.core.compile_model(xmlFile, self.plugin.mode)
|
||||
|
||||
async def predictDetectModel(self, input: np.ndarray):
|
||||
infer_request = self.detectModel.create_infer_request()
|
||||
im = ov.Tensor(array=input)
|
||||
input_tensor = im
|
||||
infer_request.set_input_tensor(input_tensor)
|
||||
await async_infer.start_async(infer_request)
|
||||
return infer_request.output_tensors[0].data
|
||||
|
||||
async def predictTextModel(self, input: np.ndarray):
|
||||
input = input.astype(np.float32)
|
||||
im = ov.Tensor(array=input)
|
||||
infer_request = self.textModel.create_infer_request()
|
||||
infer_request.set_input_tensor(im)
|
||||
await async_infer.start_async(infer_request)
|
||||
return infer_request.output_tensors[0].data
|
||||
1
plugins/openvino/src/requirements.optional.txt
Normal file
1
plugins/openvino/src/requirements.optional.txt
Normal file
@@ -0,0 +1 @@
|
||||
opencv-python
|
||||
@@ -1,6 +1,6 @@
|
||||
openvino==2024.0.0
|
||||
openvino==2024.1.0
|
||||
|
||||
# pillow-simd is available on x64 linux
|
||||
# pillow-simd confirmed not building with arm64 linux or apple silicon
|
||||
Pillow>=5.4.1; sys_platform != 'linux' or platform_machine != 'x86_64'
|
||||
pillow-simd; sys_platform == 'linux' and platform_machine == 'x86_64'
|
||||
Pillow>=5.4.1; 'linux' not in sys_platform or platform_machine != 'x86_64'
|
||||
pillow-simd; 'linux' in sys_platform and platform_machine == 'x86_64'
|
||||
|
||||
4
plugins/pam-diff/.gitignore
vendored
4
plugins/pam-diff/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/
|
||||
@@ -1,8 +0,0 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
*.map
|
||||
fs
|
||||
src
|
||||
.vscode
|
||||
dist/*.js
|
||||
22
plugins/pam-diff/.vscode/launch.json
vendored
22
plugins/pam-diff/.vscode/launch.json
vendored
@@ -1,22 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"address": "${config:scrypted.debugHost}",
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"sourceMaps": true,
|
||||
"localRoot": "${workspaceFolder}/out",
|
||||
"remoteRoot": "/plugin/",
|
||||
"type": "pwa-node"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugins/pam-diff/.vscode/settings.json
vendored
4
plugins/pam-diff/.vscode/settings.json
vendored
@@ -1,4 +0,0 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "koushik-ubuntu",
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# Motion Detection Plugin for Scrypted
|
||||
|
||||
The PAM Diff Motion Detection Plugin adds motion detection to any camera. This can also be used with cameras with built in motion detection.
|
||||
|
||||
Motion Detection should only be used if your camera does not have a plugin and does not provide motion
|
||||
events via email or webhooks.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
1. Enable the integration on a camera.
|
||||
2. Configure the motion percent and difference to change the sensitivity.
|
||||
201
plugins/pam-diff/package-lock.json
generated
201
plugins/pam-diff/package-lock.json
generated
@@ -1,201 +0,0 @@
|
||||
{
|
||||
"name": "@scrypted/pam-diff",
|
||||
"version": "0.0.24",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/pam-diff",
|
||||
"version": "0.0.24",
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"pipe2pam": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pam-diff": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.101",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
"scrypted-deploy": "bin/scrypted-deploy.js",
|
||||
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
|
||||
"scrypted-package-json": "bin/scrypted-package-json.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz",
|
||||
"integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz",
|
||||
"integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pam-diff": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pam-diff/-/pam-diff-1.1.0.tgz",
|
||||
"integrity": "sha512-4Xo6u4amQzhMcff372t7UfZBqmXd06av/GDVD6dQWyND7a4nW42ScJf5yr2WYf6JHTdPdVG82cDquuJkGI1FYA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"pixel-change": "1.1.0",
|
||||
"polygon-points": "^0.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pipe2pam": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pipe2pam/-/pipe2pam-0.6.2.tgz",
|
||||
"integrity": "sha512-gUWldPYgNjCp1q8qKpTsSalDqXWaLlaXVO+la1jgiJMbMlokMdOhzNyVAsRKJR23FVyPOAUHdi2YpDfneSOcbw=="
|
||||
},
|
||||
"node_modules/pixel-change": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pixel-change/-/pixel-change-1.1.0.tgz",
|
||||
"integrity": "sha512-p0J+CXVpeULyzlQTFzRnNcvQnbSn5kOw6qlMWPE09JNybicy/rr6ZC3AS6Z2gKhHINmo62KzynxQNlRIk6YJNQ==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-addon-api": "^4.2.0",
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/polygon-points": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/polygon-points/-/polygon-points-0.6.0.tgz",
|
||||
"integrity": "sha512-GiWcByVNyfbhGbBmiCfUXzXeDy+iMeYFUZ2Cc+ORWRpECcXi+AwyUH82ZT5zRDGIC8iU6jAcs0fFQGp03wbAFA==",
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz",
|
||||
"integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==",
|
||||
"optional": true
|
||||
},
|
||||
"node-gyp-build": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz",
|
||||
"integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==",
|
||||
"optional": true
|
||||
},
|
||||
"pam-diff": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pam-diff/-/pam-diff-1.1.0.tgz",
|
||||
"integrity": "sha512-4Xo6u4amQzhMcff372t7UfZBqmXd06av/GDVD6dQWyND7a4nW42ScJf5yr2WYf6JHTdPdVG82cDquuJkGI1FYA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"pixel-change": "1.1.0",
|
||||
"polygon-points": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"pipe2pam": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pipe2pam/-/pipe2pam-0.6.2.tgz",
|
||||
"integrity": "sha512-gUWldPYgNjCp1q8qKpTsSalDqXWaLlaXVO+la1jgiJMbMlokMdOhzNyVAsRKJR23FVyPOAUHdi2YpDfneSOcbw=="
|
||||
},
|
||||
"pixel-change": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pixel-change/-/pixel-change-1.1.0.tgz",
|
||||
"integrity": "sha512-p0J+CXVpeULyzlQTFzRnNcvQnbSn5kOw6qlMWPE09JNybicy/rr6ZC3AS6Z2gKhHINmo62KzynxQNlRIk6YJNQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"node-addon-api": "^4.2.0",
|
||||
"node-gyp-build": "^4.3.0"
|
||||
}
|
||||
},
|
||||
"polygon-points": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/polygon-points/-/polygon-points-0.6.0.tgz",
|
||||
"integrity": "sha512-GiWcByVNyfbhGbBmiCfUXzXeDy+iMeYFUZ2Cc+ORWRpECcXi+AwyUH82ZT5zRDGIC8iU6jAcs0fFQGp03wbAFA==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "@scrypted/pam-diff",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"motion",
|
||||
"detect",
|
||||
"detection",
|
||||
"pamdiff",
|
||||
"pam",
|
||||
"diff"
|
||||
],
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
"build": "scrypted-webpack",
|
||||
"prepublishOnly": "NODE_ENV=production scrypted-webpack",
|
||||
"prescrypted-vscode-launch": "scrypted-webpack",
|
||||
"scrypted-vscode-launch": "scrypted-deploy-debug",
|
||||
"scrypted-deploy-debug": "scrypted-deploy-debug",
|
||||
"scrypted-debug": "scrypted-debug",
|
||||
"scrypted-deploy": "scrypted-deploy",
|
||||
"scrypted-readme": "scrypted-readme",
|
||||
"scrypted-package-json": "scrypted-package-json"
|
||||
},
|
||||
"scrypted": {
|
||||
"name": "PAM Diff Motion Detection",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionGenerator"
|
||||
],
|
||||
"pluginDependencies": [
|
||||
"@scrypted/objectdetector"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"pipe2pam": "^0.6.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"pam-diff": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.24"
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import sdk, { MediaObject, ObjectDetection, ObjectDetectionGeneratorResult, ObjectDetectionGeneratorSession, ObjectDetectionModel, ObjectDetectionResult, ObjectDetectionSession, ObjectsDetected, ScryptedDeviceBase, VideoFrame } from '@scrypted/sdk';
|
||||
|
||||
import PD from 'pam-diff';
|
||||
import P2P from 'pipe2pam';
|
||||
import { PassThrough, Writable } from 'stream';
|
||||
|
||||
const defaultDifference = 9;
|
||||
const defaultPercentage = 2;
|
||||
|
||||
|
||||
class PamDiff extends ScryptedDeviceBase implements ObjectDetection {
|
||||
|
||||
|
||||
async * generateObjectDetectionsInternal(videoFrames: AsyncGenerator<VideoFrame, any, unknown>, session: ObjectDetectionGeneratorSession): AsyncGenerator<ObjectDetectionGeneratorResult, any, unknown> {
|
||||
videoFrames = await sdk.connectRPCObject(videoFrames);
|
||||
|
||||
const width = 640;
|
||||
const height = 360;
|
||||
const p2p: Writable = new P2P();
|
||||
const pt = new PassThrough();
|
||||
const pamDiff = new PD({
|
||||
difference: parseInt(session.settings?.difference) || defaultDifference,
|
||||
percent: parseInt(session.settings?.percent) || defaultPercentage,
|
||||
response: session?.settings?.motionAsObjects ? 'blobs' : 'percent',
|
||||
});
|
||||
pt.pipe(p2p).pipe(pamDiff);
|
||||
|
||||
const queued: ObjectsDetected[] = [];
|
||||
pamDiff.on('diff', async (data: any) => {
|
||||
const trigger = data.trigger[0];
|
||||
// console.log(trigger.blobs.length);
|
||||
const { blobs } = trigger;
|
||||
|
||||
const detections: ObjectDetectionResult[] = [];
|
||||
if (blobs?.length) {
|
||||
for (const blob of blobs) {
|
||||
detections.push(
|
||||
{
|
||||
className: 'motion',
|
||||
score: 1,
|
||||
boundingBox: [blob.minX, blob.minY, blob.maxX - blob.minX, blob.maxY - blob.minY],
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
detections.push(
|
||||
{
|
||||
className: 'motion',
|
||||
score: trigger.percent / 100,
|
||||
}
|
||||
)
|
||||
}
|
||||
const event: ObjectsDetected = {
|
||||
timestamp: Date.now(),
|
||||
inputDimensions: [width, height],
|
||||
detections,
|
||||
}
|
||||
queued.push(event);
|
||||
});
|
||||
|
||||
|
||||
for await (const videoFrame of videoFrames) {
|
||||
const header = `P7
|
||||
WIDTH ${width}
|
||||
HEIGHT ${height}
|
||||
DEPTH 3
|
||||
MAXVAL 255
|
||||
TUPLTYPE RGB
|
||||
ENDHDR
|
||||
`;
|
||||
|
||||
const { image } = videoFrame;
|
||||
|
||||
const buffer = await image.toBuffer({
|
||||
resize: (image.width !== width || image.height !== height) ? {
|
||||
width,
|
||||
height,
|
||||
} : undefined,
|
||||
format: 'rgb',
|
||||
});
|
||||
pt.write(Buffer.from(header));
|
||||
pt.write(buffer);
|
||||
|
||||
if (!queued.length) {
|
||||
yield {
|
||||
__json_copy_serialize_children: true,
|
||||
videoFrame,
|
||||
detected: {
|
||||
timestamp: Date.now(),
|
||||
detections: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
while (queued.length) {
|
||||
yield {
|
||||
__json_copy_serialize_children: true,
|
||||
detected: queued.pop(),
|
||||
videoFrame,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async generateObjectDetections(videoFrames: AsyncGenerator<VideoFrame, any, unknown>, session: ObjectDetectionGeneratorSession): Promise<AsyncGenerator<ObjectDetectionGeneratorResult, any, unknown>> {
|
||||
return this.generateObjectDetectionsInternal(videoFrames, session);
|
||||
}
|
||||
|
||||
async detectObjects(mediaObject: MediaObject, session?: ObjectDetectionSession): Promise<ObjectsDetected> {
|
||||
throw new Error('can not run motion detection on image')
|
||||
}
|
||||
|
||||
async getDetectionModel(): Promise<ObjectDetectionModel> {
|
||||
return {
|
||||
name: '@scrypted/pam-diff',
|
||||
classes: ['motion'],
|
||||
inputFormat: 'rgb',
|
||||
inputSize: [640, 360],
|
||||
settings: [
|
||||
{
|
||||
title: 'Motion Difference',
|
||||
description: 'The color difference required to trigger motion on a pixel.',
|
||||
key: 'difference',
|
||||
value: this.storage.getItem('difference') || defaultDifference,
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
title: 'Motion Percent',
|
||||
description: 'The percentage of pixels required to trigger motion',
|
||||
key: 'percent',
|
||||
value: this.storage.getItem('percent]') || defaultPercentage,
|
||||
type: 'number',
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PamDiff;
|
||||
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.17",
|
||||
"version": "0.10.18",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.17",
|
||||
"version": "0.10.18",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.10.17",
|
||||
"version": "0.10.18",
|
||||
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1596,10 +1596,11 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
|
||||
});
|
||||
|
||||
// schedule restarts at 2am
|
||||
const midnight = millisUntilMidnight();
|
||||
const twoAM = midnight + 2 * 60 * 60 * 1000;
|
||||
this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(twoAM / 1000 / 60)} minutes`)
|
||||
setTimeout(() => deviceManager.requestRestart(), twoAM);
|
||||
// removed as the mp4 containerization leak used way back when is defunct.
|
||||
// const midnight = millisUntilMidnight();
|
||||
// const twoAM = midnight + 2 * 60 * 60 * 1000;
|
||||
// this.log.i(`Rebroadcaster scheduled for restart at 2AM: ${Math.round(twoAM / 1000 / 60)} minutes`)
|
||||
// setTimeout(() => deviceManager.requestRestart(), twoAM);
|
||||
|
||||
process.nextTick(() => {
|
||||
deviceManager.onDeviceDiscovered({
|
||||
|
||||
@@ -5,5 +5,5 @@ av>=10.0.0
|
||||
|
||||
# in case pyvips fails to load, use a pillow fallback.
|
||||
# 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'
|
||||
pillow-simd; sys_platform == 'linux' and platform_machine == 'x86_64'
|
||||
Pillow>=5.4.1; 'linux' not in sys_platform or platform_machine != 'x86_64'
|
||||
pillow-simd; 'linux' in sys_platform and platform_machine == 'x86_64'
|
||||
|
||||
4
plugins/ring/package-lock.json
generated
4
plugins/ring/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/ring",
|
||||
"version": "0.0.137",
|
||||
"version": "0.0.138",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/ring",
|
||||
"version": "0.0.137",
|
||||
"version": "0.0.138",
|
||||
"dependencies": {
|
||||
"@koush/ring-client-api": "file:../../external/ring-client-api",
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -44,5 +44,5 @@
|
||||
"got": "11.8.6",
|
||||
"socket.io-client": "^2.5.0"
|
||||
},
|
||||
"version": "0.0.137"
|
||||
"version": "0.0.138"
|
||||
}
|
||||
|
||||
@@ -9,4 +9,7 @@ dist/*.js
|
||||
dist/*.txt
|
||||
__pycache__
|
||||
all_models
|
||||
sort_oh
|
||||
download_models.sh
|
||||
tsconfig.json
|
||||
.venv
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "koushik-thin",
|
||||
// "scrypted.debugHost": "koushik-ubuntu",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
|
||||
// pi local installation
|
||||
@@ -11,9 +11,9 @@
|
||||
// local checkout
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "koushik-windows",
|
||||
// "scrypted.debugHost": "koushik-winvm",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
5
plugins/rknn/README.md
Normal file
5
plugins/rknn/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Rockchip NPU Object Detection for Scrypted
|
||||
|
||||
This plugin adds object detection capabilities to any camera in Scrypted using the NPU accelerator on ARM64 Rockchip CPUs. Functionality has been tested on RK3588S, but should also work on RK3562, RK3576, and RK3588.
|
||||
|
||||
Using this plugin in Docker requires Docker to be run with the `--security-opt systempaths=unconfined` flag due to a dependency on the `/proc/device-tree/compatible` file. Additionally, use the Docker flag `--device /dev/dri:/dev/dri` to ensure that the `/dev/dri/renderD129` device is accessible. When using this plugin in a local install, ensure you have installed Rockchip's `librknnrt.so` as `/usr/lib/librknnrt.so`.
|
||||
@@ -1,47 +1,48 @@
|
||||
{
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.0.18",
|
||||
"name": "@scrypted/rknn",
|
||||
"version": "0.0.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.0.18",
|
||||
"name": "@scrypted/rknn",
|
||||
"version": "0.0.4",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.39",
|
||||
"version": "0.3.29",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
"scrypted-deploy": "bin/scrypted-deploy.js",
|
||||
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
|
||||
"scrypted-package-json": "bin/scrypted-package-json.js",
|
||||
"scrypted-readme": "bin/scrypted-readme.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
@@ -60,12 +61,12 @@
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^18.11.9",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"axios": "^1.6.5",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
@@ -73,10 +74,11 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"name": "@scrypted/dlib",
|
||||
"description": "Scrypted Face Recognition",
|
||||
"name": "@scrypted/rknn",
|
||||
"description": "Scrypted Rockchip NPU Object Detection",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"dlib",
|
||||
"face",
|
||||
"rknn",
|
||||
"rockchip",
|
||||
"npu",
|
||||
"motion",
|
||||
"object",
|
||||
"detect",
|
||||
"detection",
|
||||
"recognition",
|
||||
"people",
|
||||
"person"
|
||||
],
|
||||
@@ -26,21 +28,22 @@
|
||||
"scrypted-package-json": "scrypted-package-json"
|
||||
},
|
||||
"scrypted": {
|
||||
"name": "Dlib Face Recognition",
|
||||
"name": "Rockchip NPU Object Detection",
|
||||
"pluginDependencies": [
|
||||
"@scrypted/objectdetector"
|
||||
],
|
||||
"runtime": "python",
|
||||
"pythonVersion": {
|
||||
"default": "3.10"
|
||||
},
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"Camera",
|
||||
"Settings",
|
||||
"BufferConverter",
|
||||
"ObjectDetection"
|
||||
"ObjectDetection",
|
||||
"ObjectDetectionPreview"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.1"
|
||||
"version": "0.0.4"
|
||||
}
|
||||
1
plugins/rknn/src/common
Symbolic link
1
plugins/rknn/src/common
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openvino/src/common/
|
||||
1
plugins/rknn/src/detect
Symbolic link
1
plugins/rknn/src/detect
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openvino/src/detect
|
||||
4
plugins/rknn/src/main.py
Normal file
4
plugins/rknn/src/main.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from rknn import RKNNPlugin
|
||||
|
||||
def create_scrypted_plugin():
|
||||
return RKNNPlugin()
|
||||
1
plugins/rknn/src/predict
Symbolic link
1
plugins/rknn/src/predict
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openvino/src/predict
|
||||
2
plugins/rknn/src/requirements.txt
Normal file
2
plugins/rknn/src/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
https://github.com/airockchip/rknn-toolkit2/raw/v2.0.0-beta0/rknn-toolkit-lite2/packages/rknn_toolkit_lite2-2.0.0b0-cp310-cp310-linux_aarch64.whl
|
||||
pillow==10.3.0
|
||||
1
plugins/rknn/src/rknn/__init__.py
Normal file
1
plugins/rknn/src/rknn/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .plugin import RKNNPlugin
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user