mirror of
https://github.com/koush/scrypted.git
synced 2026-02-03 22:23:27 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
212883e84b | ||
|
|
1200537d62 | ||
|
|
5f6adc9449 | ||
|
|
7d17236ca7 | ||
|
|
028401362a | ||
|
|
69927be4f4 | ||
|
|
ffee1c5cc2 | ||
|
|
ebc3a03e2c | ||
|
|
4246e3c476 | ||
|
|
3fce0838f1 | ||
|
|
2609e301fe | ||
|
|
f4737bf2ac | ||
|
|
fc102aa526 | ||
|
|
9ef33e156f | ||
|
|
881865a0cb | ||
|
|
be5643cc53 | ||
|
|
7e6eba1596 | ||
|
|
27dde776a6 | ||
|
|
b24159a22a | ||
|
|
b6c242b9d5 | ||
|
|
2fbaa12caa | ||
|
|
eb5a497e82 | ||
|
|
66a0ea08ec | ||
|
|
0527baf14a | ||
|
|
c7c5c6eed5 | ||
|
|
143c950c19 | ||
|
|
8d0bb0fa97 | ||
|
|
964274e50c | ||
|
|
e9844528aa | ||
|
|
0609fc8986 | ||
|
|
9331b71433 | ||
|
|
21f8239db7 | ||
|
|
86042ec3fe | ||
|
|
cdb87fb268 | ||
|
|
63dcd35b17 | ||
|
|
951c3b9be6 | ||
|
|
ed642bb3fe | ||
|
|
8093cdd3d9 | ||
|
|
fcbfc3a73f | ||
|
|
94945a48bd | ||
|
|
e360ede5cb | ||
|
|
bc9ec73567 | ||
|
|
cd7e570508 | ||
|
|
1b06c9c11d | ||
|
|
154ab42d15 | ||
|
|
1929f6e8ed | ||
|
|
58bfa17cfe | ||
|
|
38c7006055 | ||
|
|
b5e16b45a9 | ||
|
|
9c13668812 | ||
|
|
a1ca724d6b | ||
|
|
1b032d669c | ||
|
|
c492c15081 | ||
|
|
ee7076384b | ||
|
|
717cac721a | ||
|
|
af41c853bc | ||
|
|
109b716753 | ||
|
|
07930508fe | ||
|
|
a291abe375 | ||
|
|
f4f34b2da8 | ||
|
|
3b4de526ba |
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -69,12 +69,18 @@ jobs:
|
||||
tags: |
|
||||
${{ format('koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.package_version || steps.package-version.outputs.current-version) }}
|
||||
${{ matrix.BASE == '18-bullseye-full' && matrix.SUPERVISOR == '.s6' && format('koush/scrypted:{0}', github.event.inputs.docker_tag) || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-full' && matrix.SUPERVISOR == '' && 'koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-lite' && matrix.SUPERVISOR == '' && 'koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-thin' && matrix.SUPERVISOR == '' && 'koush/scrypted:thin' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-lite' && matrix.SUPERVISOR == '.s6' && 'koush/scrypted:lite-s6' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-thin' && matrix.SUPERVISOR == '.s6' && 'koush/scrypted:thin-s6' || '' }}
|
||||
|
||||
${{ format('ghcr.io/koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.package_version || steps.package-version.outputs.current-version) }}
|
||||
${{ matrix.BASE == '18-bullseye-full' && matrix.SUPERVISOR == '.s6' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.docker_tag) || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-full' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-lite' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-thin' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:thin' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-lite' && matrix.SUPERVISOR == '.s6' && 'ghcr.io/koush/scrypted:lite-s6' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-thin' && matrix.SUPERVISOR == '.s6' && 'ghcr.io/koush/scrypted:thin-s6' || '' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -24,6 +24,13 @@ RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y install libedgetpu1-std
|
||||
|
||||
# intel opencl gpu for openvino
|
||||
RUN if [ "$(uname -m)" = "x86_64" ]; \
|
||||
then \
|
||||
apt-get -y install \
|
||||
intel-opencl-icd; \
|
||||
fi
|
||||
|
||||
RUN apt-get -y install software-properties-common apt-utils
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y upgrade
|
||||
|
||||
@@ -32,14 +32,14 @@ services:
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
|
||||
# uncomment this and a line below as needed.
|
||||
# devices:
|
||||
# zwave usb serial device
|
||||
# - /dev/ttyACM0:/dev/ttyACM0
|
||||
# all usb devices, such as coral tpu
|
||||
# - /dev/bus/usb:/dev/bus/usb
|
||||
# intel hardware accelerated video decoding
|
||||
# - /dev/dri:/dev/dri
|
||||
devices:
|
||||
# hardware accelerated video decoding, opencl, etc.
|
||||
- /dev/dri:/dev/dri
|
||||
# uncomment below as necessary.
|
||||
# zwave usb serial device
|
||||
# - /dev/ttyACM0:/dev/ttyACM0
|
||||
# all usb devices, such as coral tpu
|
||||
# - /dev/bus/usb:/dev/bus/usb
|
||||
|
||||
volumes:
|
||||
- ~/.scrypted/volume:/server/volume
|
||||
|
||||
@@ -21,6 +21,13 @@ RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y install libedgetpu1-std
|
||||
|
||||
# intel opencl gpu for openvino
|
||||
RUN if [ "$(uname -m)" = "x86_64" ]; \
|
||||
then \
|
||||
apt-get -y install \
|
||||
intel-opencl-icd; \
|
||||
fi
|
||||
|
||||
RUN apt-get -y install software-properties-common apt-utils
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y upgrade
|
||||
|
||||
@@ -88,7 +88,10 @@ class CastDevice extends ScryptedDeviceBase implements MediaPlayer, Refresh, Eng
|
||||
}
|
||||
|
||||
client.removeAllListeners();
|
||||
client.close();
|
||||
try {
|
||||
client.close();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
client.client.on('close', cleanup);
|
||||
client.on('error', err => {
|
||||
@@ -149,6 +152,14 @@ class CastDevice extends ScryptedDeviceBase implements MediaPlayer, Refresh, Eng
|
||||
}
|
||||
|
||||
async load(media: string | MediaObject, options: MediaPlayerOptions) {
|
||||
if (this.mediaPlayerPromise) {
|
||||
try {
|
||||
(await this.mediaPlayerPromise).close();
|
||||
} catch (e) {
|
||||
}
|
||||
this.mediaPlayerPromise = undefined;
|
||||
this.mediaPlayerStatus = undefined;
|
||||
}
|
||||
let url: string;
|
||||
let urlMimeType: string;
|
||||
|
||||
@@ -341,15 +352,7 @@ class CastDevice extends ScryptedDeviceBase implements MediaPlayer, Refresh, Eng
|
||||
});
|
||||
})
|
||||
|
||||
player.getStatus((err, status) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.mediaPlayerStatus = status;
|
||||
this.updateState();
|
||||
resolve(player);
|
||||
})
|
||||
resolve(player);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
6
plugins/cloud/package-lock.json
generated
6
plugins/cloud/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.14",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/cloud",
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.14",
|
||||
"dependencies": {
|
||||
"@eneris/push-receiver": "^3.1.4",
|
||||
"@scrypted/common": "file:../../common",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.82",
|
||||
"version": "0.2.97",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
"@types/nat-upnp": "^1.1.2",
|
||||
"@types/node": "^18.11.18"
|
||||
},
|
||||
"version": "0.1.13"
|
||||
"version": "0.1.14"
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ import { once } from 'events';
|
||||
import http from 'http';
|
||||
import HttpProxy from 'http-proxy';
|
||||
import https from 'https';
|
||||
import throttle from "lodash/throttle";
|
||||
import upnp from 'nat-upnp';
|
||||
import net, { AddressInfo } from 'net';
|
||||
import net from 'net';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import qs from 'query-string';
|
||||
@@ -210,6 +209,11 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
})
|
||||
|
||||
this.updateCors();
|
||||
|
||||
if (!this.storageSettings.values.token_info && process.env.SCRYPTED_CLOUD_TOKEN) {
|
||||
this.storageSettings.values.token_info = process.env.SCRYPTED_CLOUD_TOKEN;
|
||||
this.manager.registrationId.then(r => this.sendRegistrationId(r));
|
||||
}
|
||||
}
|
||||
|
||||
scheduleRefreshPortForward() {
|
||||
|
||||
@@ -29,7 +29,7 @@ class ChromecastViewCameraExample implements StartStop {
|
||||
}
|
||||
async stop() {
|
||||
device.running = false;
|
||||
return chromecast.stop();
|
||||
await chromecast.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.114",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.114",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.114",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -17,7 +17,13 @@ const { systemManager, deviceManager, endpointManager } = sdk;
|
||||
const indexHtml = fs.readFileSync('dist/index.html').toString();
|
||||
|
||||
export function getAddresses() {
|
||||
const addresses = Object.entries(os.networkInterfaces()).filter(([iface]) => iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')).map(([_, addr]) => addr).flat().map(info => info.address).filter(address => address);
|
||||
const addresses: string[] = [];
|
||||
for (const [iface, nif] of Object.entries(os.networkInterfaces())) {
|
||||
if (iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')) {
|
||||
addresses.push(iface);
|
||||
addresses.push(...nif.map(addr => addr.address));
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@@ -37,17 +43,18 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
localAddresses: string[];
|
||||
storageSettings = new StorageSettings(this, {
|
||||
localAddresses: {
|
||||
title: 'Scrypted Server Address',
|
||||
description: 'The IP address used by the Scrypted server. Set this to the wired IP address to prevent usage of a wireless address.',
|
||||
title: 'Scrypted Server Addresses',
|
||||
description: 'The IP addresses used by the Scrypted server. Set this to the wired IP address to prevent usage of a wireless address.',
|
||||
combobox: true,
|
||||
multiple: true,
|
||||
async onGet() {
|
||||
return {
|
||||
choices: getAddresses(),
|
||||
};
|
||||
},
|
||||
mapGet: () => this.localAddresses?.[0],
|
||||
mapGet: () => this.localAddresses,
|
||||
onPut: async (oldValue, newValue) => {
|
||||
this.localAddresses = newValue ? [newValue] : undefined;
|
||||
this.localAddresses = newValue?.length ? newValue : undefined;
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
service.setLocalAddresses(this.localAddresses);
|
||||
},
|
||||
@@ -132,7 +139,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
try {
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
this.localAddresses = await service.getLocalAddresses();
|
||||
this.localAddresses = await service.getLocalAddresses(true);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
4
plugins/coreml/package-lock.json
generated
4
plugins/coreml/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.14",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/coreml",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.14",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -34,12 +34,11 @@
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"Settings",
|
||||
"BufferConverter",
|
||||
"ObjectDetection"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.12"
|
||||
"version": "0.1.14"
|
||||
}
|
||||
|
||||
@@ -22,12 +22,9 @@ def parse_label_contents(contents: str):
|
||||
ret[row_number] = content.strip()
|
||||
return ret
|
||||
|
||||
|
||||
MIME_TYPE = 'x-scrypted-coreml/x-raw-image'
|
||||
|
||||
class CoreMLPlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(MIME_TYPE, nativeId=nativeId)
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
labelsFile = self.downloadFile('https://raw.githubusercontent.com/koush/coreml-survival-guide/master/MobileNetV2%2BSSDLite/coco_labels.txt', 'coco_labels.txt')
|
||||
modelFile = self.downloadFile('https://github.com/koush/coreml-survival-guide/raw/master/MobileNetV2%2BSSDLite/ObjectDetection/ObjectDetection/MobileNetV2_SSDLite.mlmodel', 'MobileNetV2_SSDLite.mlmodel')
|
||||
@@ -58,11 +55,11 @@ class CoreMLPlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Set
|
||||
else:
|
||||
out_dict = self.model.predict({'image': input, 'confidenceThreshold': self.minThreshold })
|
||||
|
||||
coordinatesList = out_dict['coordinates']
|
||||
coordinatesList = out_dict['coordinates'].astype(float)
|
||||
|
||||
objs = []
|
||||
|
||||
for index, confidenceList in enumerate(out_dict['confidence']):
|
||||
for index, confidenceList in enumerate(out_dict['confidence'].astype(float)):
|
||||
values = confidenceList
|
||||
maxConfidenceIndex = max(range(len(values)), key=values.__getitem__)
|
||||
maxConfidence = confidenceList[maxConfidenceIndex]
|
||||
|
||||
4
plugins/doorbird/.gitignore
vendored
Normal file
4
plugins/doorbird/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/
|
||||
11
plugins/doorbird/.npmignore
Normal file
11
plugins/doorbird/.npmignore
Normal file
@@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
*.map
|
||||
fs
|
||||
src
|
||||
.vscode
|
||||
dist/*.js
|
||||
dist/*.txt
|
||||
HAP-NodeJS
|
||||
.gitmodules
|
||||
23
plugins/doorbird/.vscode/launch.json
vendored
Normal file
23
plugins/doorbird/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Scrypted Debugger",
|
||||
"address": "${config:scrypted.debugHost}",
|
||||
"port": 10081,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"**/plugin-remote-worker.*",
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"sourceMaps": true,
|
||||
"localRoot": "${workspaceFolder}/out",
|
||||
"remoteRoot": "/plugin/",
|
||||
"type": "node"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
plugins/doorbird/.vscode/settings.json
vendored
Normal file
4
plugins/doorbird/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
}
|
||||
20
plugins/doorbird/.vscode/tasks.json
vendored
Normal file
20
plugins/doorbird/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "scrypted: deploy+debug",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "silent",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
|
||||
},
|
||||
]
|
||||
}
|
||||
9
plugins/doorbird/README.md
Normal file
9
plugins/doorbird/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Doorbird Plugin for Scrypted
|
||||
|
||||
The Doorbird Plugin bridges compatible Doorbird video doorbell cameras to Scrypted.
|
||||
|
||||
# Notes
|
||||
* Make sure that the user you want to use for the Doorbird plugin login has the API access rights.
|
||||
* Doorbrid cameras are quite limited in terms of maximum number of concurrent streams. Keep this in mind if you are also using other software with the Doorbird station. You have the possibility to override the internally used RTSP URL and provide another RTSP server which provides the video stream.
|
||||
* The doorbird mobile apps always have precedence over the public LAN API. So when somebody uses the Doorbird app to talk to the Doorbird station, the streams will be interrupted.
|
||||
* The doorbird camera just provides JPEG snapshots with VGA resolution. You can use the scrypted snapshot plugin to get a snapshot from the higher resolution video stream. Just set the option in the snapshot plugin to "enabled".
|
||||
349
plugins/doorbird/package-lock.json
generated
Normal file
349
plugins/doorbird/package-lock.json
generated
Normal file
@@ -0,0 +1,349 @@
|
||||
{
|
||||
"name": "@scrypted/doorbird",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/doorbird",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"doorbird": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^18.15.11",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.97",
|
||||
"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",
|
||||
"cross-env": "^7.0.3",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
|
||||
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
|
||||
"dependencies": {
|
||||
"auth-header": "^1.0.0",
|
||||
"axios": "^0.21.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@koush/axios-digest-auth/node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
|
||||
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz",
|
||||
"integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chacha-js": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/chacha-js/-/chacha-js-2.1.1.tgz",
|
||||
"integrity": "sha512-0ySdjUv/oUkr2cjCo00CNil8Y9f39nm5/3pCgc6hO3X7LvMLBnmugQ5WZ+3Z2SwP9jX7oMIjU3m6p23thtMnHA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.1",
|
||||
"readable-stream": "^1.0.33"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/doorbird": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/doorbird/-/doorbird-2.1.2.tgz",
|
||||
"integrity": "sha512-ivwwsS/nOslDnuLg3UB60Axo76w5LQuZ67mCPEeWFr5+HbGYRL7PCY3iLjWYaIakh5+IvZyFPHKR4yHAvAc1WQ==",
|
||||
"dependencies": {
|
||||
"axios": "^1.2.1",
|
||||
"chacha-js": "^2.1.1",
|
||||
"libsodium-wrappers-sumo": "^0.7.11"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/libsodium-sumo": {
|
||||
"version": "0.7.11",
|
||||
"resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.11.tgz",
|
||||
"integrity": "sha512-bY+7ph7xpk51Ez2GbE10lXAQ5sJma6NghcIDaSPbM/G9elfrjLa0COHl/7P6Wb/JizQzl5UQontOOP1z0VwbLA=="
|
||||
},
|
||||
"node_modules/libsodium-wrappers-sumo": {
|
||||
"version": "0.7.11",
|
||||
"resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.11.tgz",
|
||||
"integrity": "sha512-DGypHOmJbB1nZn89KIfGOAkDgfv5N6SBGC3Qvmy/On0P0WD1JQvNRS/e3UL3aFF+xC0m+MYz5M+MnRnK2HMrKQ==",
|
||||
"dependencies": {
|
||||
"libsodium-sumo": "^0.7.11"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
plugins/doorbird/package.json
Normal file
46
plugins/doorbird/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@scrypted/doorbird",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
"build": "scrypted-webpack",
|
||||
"prepublishOnly": "cross-env NODE_ENV=production scrypted-webpack",
|
||||
"prescrypted-vscode-launch": "scrypted-webpack",
|
||||
"scrypted-vscode-launch": "scrypted-deploy-debug",
|
||||
"scrypted-deploy-debug": "scrypted-deploy-debug",
|
||||
"scrypted-debug": "scrypted-debug",
|
||||
"scrypted-deploy": "scrypted-deploy",
|
||||
"scrypted-readme": "scrypted-readme",
|
||||
"scrypted-package-json": "scrypted-package-json"
|
||||
},
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"doorbird"
|
||||
],
|
||||
"scrypted": {
|
||||
"name": "Doorbird Plugin",
|
||||
"type": "DeviceProvider",
|
||||
"interfaces": [
|
||||
"DeviceProvider",
|
||||
"DeviceCreator",
|
||||
"Settings"
|
||||
],
|
||||
"pluginDependencies": [
|
||||
"@scrypted/prebuffer-mixin",
|
||||
"@scrypted/pam-diff",
|
||||
"@scrypted/snapshot"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"doorbird": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/node": "^18.15.11",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
92
plugins/doorbird/src/doorbird-api.ts
Normal file
92
plugins/doorbird/src/doorbird-api.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import Doorbird, { DoorbirdUdpSocket, Scheme, Response, DoorbirdInfoBHA } from 'doorbird';
|
||||
|
||||
export interface ApiRingEvent {
|
||||
event: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
export interface ApiMotionEvent {
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export type ApiRingCallback = (event: ApiRingEvent) => void;
|
||||
export type ApiMotionCallback = (event: ApiMotionEvent) => void;
|
||||
|
||||
export class DoorbirdAPI {
|
||||
|
||||
private console?: Console
|
||||
private doorbird: Doorbird;
|
||||
private doorbirdUdpSocket: DoorbirdUdpSocket;
|
||||
private ringCallback: ApiRingCallback;
|
||||
private motionCallback: ApiMotionCallback;
|
||||
private intercomId: String;
|
||||
|
||||
constructor(host: string, username: string, password: string, console?: Console) {
|
||||
this.console = console;
|
||||
this.doorbird = new Doorbird({
|
||||
scheme: Scheme.http,
|
||||
host: host,
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
this.intercomId = username.substring(0, 6);
|
||||
this.console?.log("Doorbird: Our intercomId is: ", this.intercomId);
|
||||
}
|
||||
|
||||
startEventSocket() {
|
||||
this.console?.log("Doorbird: starting event socket listening...");
|
||||
|
||||
// initialize dgram UDP socket where Doorbird stations broadcast their event info
|
||||
this.doorbirdUdpSocket = this.doorbird.startUdpSocket(6524); // 6524 or 35344 - both shall contain the same payload
|
||||
|
||||
// register a listener for ring events
|
||||
this.doorbirdUdpSocket.registerRingListener(ringEvent => {
|
||||
this.console?.log("Doorbird: Event from IntercomId:", ringEvent.intercomId);
|
||||
// Make sure that we only call this if the intercom ID matches our desired one
|
||||
if (ringEvent.intercomId === this.intercomId) {
|
||||
this.ringCallback({
|
||||
event: ringEvent.event,
|
||||
timestamp: ringEvent.timestamp
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// register a listener for motion events
|
||||
this.doorbirdUdpSocket.registerMotionListener(motionEvent => {
|
||||
this.console?.log("Doorbird: Event from IntercomId:", motionEvent.intercomId);
|
||||
// Make sure that we only call this if the intercom ID matches our desired one
|
||||
if (motionEvent.intercomId === this.intercomId) {
|
||||
this.motionCallback({
|
||||
timestamp: motionEvent.timestamp
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopEventSocket() {
|
||||
this.console?.log("Doorbird: stopping event socket listening...");
|
||||
this.doorbirdUdpSocket.close();
|
||||
}
|
||||
|
||||
registerRingCallback(ringCallback: ApiRingCallback) {
|
||||
this.ringCallback = ringCallback;
|
||||
}
|
||||
|
||||
registerMotionCallback(motionCallback: ApiMotionCallback) {
|
||||
this.motionCallback = motionCallback;
|
||||
}
|
||||
|
||||
async getImage(): Promise<Buffer> {
|
||||
this.console?.log("Doorbird: getting JPEG image...");
|
||||
return this.doorbird.getImage();
|
||||
}
|
||||
|
||||
async getInfo(): Promise<any> {
|
||||
const dbInfo = await this.doorbird.getInfo();
|
||||
return {
|
||||
deviceType: dbInfo.BHA.VERSION[0]['DEVICE-TYPE'],
|
||||
firmwareVersion: dbInfo.BHA.VERSION[0].FIRMWARE,
|
||||
buildNumber: dbInfo.BHA.VERSION[0].BUILD_NUMBER,
|
||||
serialNumber: dbInfo.BHA.VERSION[0].WIFI_MAC_ADDR,
|
||||
}
|
||||
}
|
||||
}
|
||||
565
plugins/doorbird/src/main.ts
Normal file
565
plugins/doorbird/src/main.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
import { listenZero } from '@scrypted/common/src/listen-cluster';
|
||||
import sdk, { BinarySensor, Camera, DeviceProvider, DeviceCreator, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, PictureOptions, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, VideoCamera, MotionSensor } from '@scrypted/sdk';
|
||||
import child_process, { ChildProcess } from 'child_process';
|
||||
import { ffmpegLogInitialOutput, safePrintFFmpegArguments } from "@scrypted/common/src/media-helpers";
|
||||
import net from 'net';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { PassThrough, Readable } from "stream";
|
||||
import AxiosDigestAuth from '@koush/axios-digest-auth';
|
||||
import { readLength } from "@scrypted/common/src/read-stream";
|
||||
import { ApiRingEvent, ApiMotionEvent, DoorbirdAPI } from "./doorbird-api";
|
||||
|
||||
const { deviceManager, mediaManager } = sdk;
|
||||
|
||||
class DoorbirdCamera extends ScryptedDeviceBase implements Intercom, Camera, VideoCamera, Settings, BinarySensor, MotionSensor {
|
||||
doorbirdApi: DoorbirdAPI | undefined;
|
||||
binarySensorTimeout: NodeJS.Timeout;
|
||||
motionSensorTimeout: NodeJS.Timeout;
|
||||
doorbellAudioActive: boolean;
|
||||
audioTXProcess: ChildProcess;
|
||||
audioRXProcess: ChildProcess;
|
||||
audioSilenceProcess: ChildProcess;
|
||||
audioRXClientSocket: net.Socket;
|
||||
pendingPicture: Promise<MediaObject>;
|
||||
|
||||
constructor(nativeId: string, public provider: DoorbirdCamProvider) {
|
||||
super(nativeId);
|
||||
this.binaryState = false;
|
||||
this.doorbellAudioActive = false;
|
||||
|
||||
this.updateDeviceInfo();
|
||||
}
|
||||
|
||||
getDoorbirdApi() {
|
||||
const ip = this.storage.getItem('ip');
|
||||
if (!ip)
|
||||
return undefined;
|
||||
|
||||
if (!this.doorbirdApi) {
|
||||
this.doorbirdApi = new DoorbirdAPI(this.getIPAddress(), this.getUsername(), this.getPassword(), this.console);
|
||||
|
||||
this.getDoorbirdApi()?.registerRingCallback((event: ApiRingEvent) => {
|
||||
this.console?.log("Ring event");
|
||||
this.console?.log("Event:", event.event);
|
||||
this.console?.log("Time:", event.timestamp);
|
||||
this.triggerBinarySensor();
|
||||
});
|
||||
this.getDoorbirdApi()?.registerMotionCallback((event: ApiMotionEvent) => {
|
||||
this.console?.log("Motion event");
|
||||
this.console?.log("Time:", event.timestamp);
|
||||
this.triggerMotionSensor();
|
||||
});
|
||||
this.getDoorbirdApi()?.startEventSocket();
|
||||
}
|
||||
return this.doorbirdApi;
|
||||
}
|
||||
|
||||
async updateDeviceInfo(): Promise<void> {
|
||||
const ip = this.storage.getItem('ip');
|
||||
if (!ip)
|
||||
return;
|
||||
|
||||
const deviceInfo: DeviceInformation = {
|
||||
...this.info,
|
||||
ip
|
||||
};
|
||||
|
||||
const response = await this.getDoorbirdApi()?.getInfo();
|
||||
|
||||
deviceInfo.firmware = response.firmwareVersion + '-' + response.buildNumber;
|
||||
|
||||
this.info = deviceInfo;
|
||||
}
|
||||
|
||||
async takePicture(option?: PictureOptions): Promise<MediaObject> {
|
||||
if (!this.pendingPicture) {
|
||||
this.pendingPicture = this.takePictureThrottled(option);
|
||||
this.pendingPicture.finally(() => this.pendingPicture = undefined);
|
||||
}
|
||||
|
||||
return this.pendingPicture;
|
||||
}
|
||||
|
||||
async takePictureThrottled(option?: PictureOptions): Promise<MediaObject> {
|
||||
return this.createMediaObject(await this.getDoorbirdApi().getImage(), 'image/jpeg');
|
||||
}
|
||||
|
||||
// Unfortunately, the Doorbird public API only offers JPEG snapshots with VGA resolution.
|
||||
// Recommendation: use the snapshot plugin to get snapshots with maximum resolution.
|
||||
public async getPictureOptions(): Promise<PictureOptions[]> {
|
||||
return [{
|
||||
id: 'VGA',
|
||||
picture: { width: 640, height: 480 }
|
||||
}];
|
||||
}
|
||||
|
||||
public async putSetting(key: string, value: string | number | boolean) {
|
||||
|
||||
this.doorbirdApi?.stopEventSocket();
|
||||
this.doorbirdApi = undefined;
|
||||
|
||||
this.storage.setItem(key, value.toString());
|
||||
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
|
||||
|
||||
this.provider.updateDevice(this.nativeId, this.name);
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'username',
|
||||
title: 'Username',
|
||||
value: this.storage.getItem('username'),
|
||||
description: 'Required: Username for Doorbird HTTP API.',
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
title: 'Password',
|
||||
value: this.storage.getItem('password'),
|
||||
type: 'password',
|
||||
description: 'Required: Password for Doorbird HTTP API.',
|
||||
},
|
||||
{
|
||||
key: 'ip',
|
||||
title: 'IP Address',
|
||||
placeholder: '192.168.1.100',
|
||||
value: this.storage.getItem('ip'),
|
||||
description: 'Required: IP address of the Doorbird station.',
|
||||
},
|
||||
{
|
||||
key: 'httpPort',
|
||||
subgroup: 'Advanced',
|
||||
title: 'HTTP Port Override',
|
||||
placeholder: '80',
|
||||
value: this.storage.getItem('httpPort'),
|
||||
description: 'Use this if you have some network firewall rules which change the HTTP port of the camera HTTP port.',
|
||||
},
|
||||
{
|
||||
key: 'rtspUrl',
|
||||
subgroup: 'Advanced',
|
||||
title: 'RTSP URL Override',
|
||||
placeholder: 'rtsp://192.168.2.100/my_doorbird_video_stream',
|
||||
value: this.storage.getItem('rtspUrl'),
|
||||
description: 'Use this in case you are already using another RTSP server/proxy (e.g. mediamtx, go2rtc, etc.) to limit the number of streams from the camera.',
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// When the intercom is started, we also start the audio receiver which receives audio fro the doorbird microphone.
|
||||
// This audio is then fed into ffmpeg instead of the silent audio from the silence generator.
|
||||
// We also start another process(audioTXProcess) which sends audio to the doorbird speaker.
|
||||
async startIntercom(media: MediaObject): Promise<void> {
|
||||
await this.startAudioReceiver();
|
||||
await this.startAudioTransmitter(media);
|
||||
}
|
||||
|
||||
async stopIntercom(): Promise<void> {
|
||||
this.stopAudioTransmitter();
|
||||
this.stopAudioReceiver();
|
||||
}
|
||||
|
||||
async startAudioTransmitter(media: MediaObject): Promise<void> {
|
||||
const ffmpegInput: FFmpegInput = JSON.parse((await mediaManager.convertMediaObjectToBuffer(media, ScryptedMimeTypes.FFmpegInput)).toString());
|
||||
|
||||
const ffmpegArgs = ffmpegInput.inputArguments.slice();
|
||||
ffmpegArgs.push(
|
||||
'-vn', '-dn', '-sn',
|
||||
'-acodec', 'pcm_mulaw',
|
||||
'-flags', '+global_header',
|
||||
'-ac', '1',
|
||||
'-ar', '8k',
|
||||
'-f', 'mulaw',
|
||||
'pipe:3'
|
||||
);
|
||||
|
||||
safePrintFFmpegArguments(console, ffmpegArgs);
|
||||
const cp = child_process.spawn(await mediaManager.getFFmpegPath(), ffmpegArgs, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
this.audioTXProcess = cp;
|
||||
ffmpegLogInitialOutput(console, cp);
|
||||
cp.on('exit', () => this.console.log('Doorbird: Audio transmitter ended.'));
|
||||
cp.stdout.on('data', data => this.console.log(data.toString()));
|
||||
cp.stderr.on('data', data => this.console.log(data.toString()));
|
||||
|
||||
const socket = cp.stdio[3] as Readable;
|
||||
|
||||
const username: string = this.getUsername();
|
||||
const password: string = this.getPassword();
|
||||
const audioTxUrl: string = `${this.getHttpBaseAddress()}/bha-api/audio-transmit.cgi`;
|
||||
|
||||
this.console.log('Doorbird: Starting audio transmitter...');
|
||||
|
||||
(async () => {
|
||||
this.console.log('Doorbird: audio transmitter started.');
|
||||
|
||||
const passthrough = new PassThrough();
|
||||
const digestAuth = new AxiosDigestAuth({
|
||||
username,
|
||||
password
|
||||
});
|
||||
digestAuth.request({
|
||||
method: 'POST',
|
||||
url: audioTxUrl,
|
||||
headers: {
|
||||
'Content-Type': 'audio/basic',
|
||||
'Content-Length': '9999999'
|
||||
},
|
||||
data: passthrough,
|
||||
});
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const data = await readLength(socket, 1024);
|
||||
passthrough.push(data);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
finally {
|
||||
this.console.log('Doorbird: audio transmitter finished.');
|
||||
passthrough.end();
|
||||
}
|
||||
|
||||
this.stopAudioTransmitter();
|
||||
})();
|
||||
}
|
||||
|
||||
stopAudioTransmitter() {
|
||||
this.audioTXProcess?.kill('SIGKILL');
|
||||
this.audioTXProcess = undefined;
|
||||
}
|
||||
|
||||
async startAudioReceiver(): Promise<void> {
|
||||
|
||||
const audioRxUrl = `${this.getHttpBaseAddress()}/bha-api/audio-receive.cgi`;
|
||||
|
||||
this.console.log('Doorbird: Starting audio receiver...');
|
||||
|
||||
const ffmpegPath = await mediaManager.getFFmpegPath();
|
||||
|
||||
const ffmpegArgs = [
|
||||
'-hide_banner',
|
||||
'-nostats',
|
||||
'-analyzeduration', '0',
|
||||
'-probesize', '32',
|
||||
'-re',
|
||||
'-ar', '8000',
|
||||
'-ac', '1',
|
||||
'-f', 'mulaw',
|
||||
'-i', `${audioRxUrl}`,
|
||||
'-acodec', 'copy',
|
||||
'-f', 'mulaw',
|
||||
'pipe:3'
|
||||
];
|
||||
|
||||
safePrintFFmpegArguments(console, ffmpegArgs);
|
||||
const cp = child_process.spawn(ffmpegPath, ffmpegArgs, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
this.audioRXProcess = cp;
|
||||
ffmpegLogInitialOutput(console, cp);
|
||||
|
||||
cp.on('exit', () => {
|
||||
this.console.log('Doorbird: audio receiver ended.')
|
||||
this.audioRXProcess = undefined;
|
||||
});
|
||||
cp.stdout.on('data', data => this.console.log(data.toString()));
|
||||
cp.stderr.on('data', data => this.console.log(data.toString()));
|
||||
|
||||
this.doorbellAudioActive = true;
|
||||
cp.stdio[3].on('data', data => {
|
||||
if (this.doorbellAudioActive && this.audioRXClientSocket) {
|
||||
this.audioRXClientSocket.write(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopAudioReceiver() {
|
||||
this.doorbellAudioActive = false;
|
||||
this.audioRXProcess?.kill('SIGKILL');
|
||||
this.audioRXProcess = undefined;
|
||||
}
|
||||
|
||||
async getVideoStreamOptions(): Promise<ResponseMediaStreamOptions[]> {
|
||||
return [{
|
||||
id: 'default',
|
||||
name: 'default',
|
||||
container: '', // must be empty to support prebuffering
|
||||
video: {
|
||||
codec: 'h264'
|
||||
},
|
||||
audio: { /*this.isAudioDisabled() ? null : {}, */
|
||||
// this is a hint to let homekit, et al, know that it's OPUS audio and does not need transcoding.
|
||||
codec: 'pcm_mulaw',
|
||||
}
|
||||
}]; }
|
||||
|
||||
async getVideoStream(options?: ResponseMediaStreamOptions): Promise<MediaObject> {
|
||||
|
||||
const port = await this.startAudioRXServer();
|
||||
|
||||
const ffmpegInput: FFmpegInput = {
|
||||
url: undefined,
|
||||
inputArguments: [
|
||||
'-analyzeduration', '0',
|
||||
'-probesize', '32',
|
||||
'-fflags', 'nobuffer',
|
||||
'-flags', 'low_delay',
|
||||
'-f', 'rtsp',
|
||||
'-rtsp_transport', 'tcp',
|
||||
'-i', `${this.getRtspAddress()}`,
|
||||
'-f', 'mulaw',
|
||||
'-ac', '1',
|
||||
'-ar', '8000',
|
||||
'-channel_layout', 'mono',
|
||||
'-use_wallclock_as_timestamps', 'true',
|
||||
'-i', `tcp://127.0.0.1:${port}?tcp_nodelay=1`,
|
||||
],
|
||||
mediaStreamOptions: options,
|
||||
};
|
||||
|
||||
return mediaManager.createFFmpegMediaObject(ffmpegInput);
|
||||
}
|
||||
|
||||
async startSilenceGenerator() {
|
||||
|
||||
if (this.audioSilenceProcess)
|
||||
return;
|
||||
|
||||
this.console.log('Doorbird: starting audio silence generator...')
|
||||
|
||||
const ffmpegPath = await mediaManager.getFFmpegPath();
|
||||
const ffmpegArgs = [
|
||||
'-hide_banner',
|
||||
'-nostats',
|
||||
'-re',
|
||||
'-f', 'lavfi',
|
||||
'-i', 'anullsrc=r=8000:cl=mono',
|
||||
'-f', 'mulaw',
|
||||
'pipe:3'
|
||||
];
|
||||
|
||||
safePrintFFmpegArguments(console, ffmpegArgs);
|
||||
const cp = child_process.spawn(ffmpegPath, ffmpegArgs, {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
this.audioSilenceProcess = cp;
|
||||
ffmpegLogInitialOutput(console, cp);
|
||||
|
||||
cp.on('exit', () => {
|
||||
this.console.log('Doorbird: audio silence generator ended.')
|
||||
this.audioSilenceProcess = undefined;
|
||||
});
|
||||
cp.stdout.on('data', data => this.console.log(data.toString()));
|
||||
cp.stderr.on('data', data => this.console.log(data.toString()));
|
||||
cp.stdio[3].on('data', data => {
|
||||
if (!this.doorbellAudioActive && this.audioRXClientSocket) {
|
||||
this.audioRXClientSocket.write(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopSilenceGenerator() {
|
||||
this.audioSilenceProcess?.kill();
|
||||
this.audioSilenceProcess = null;
|
||||
}
|
||||
|
||||
async startAudioRXServer(): Promise<number> {
|
||||
|
||||
const server = net.createServer(async (clientSocket) => {
|
||||
clearTimeout(serverTimeout);
|
||||
|
||||
this.audioRXClientSocket = clientSocket;
|
||||
|
||||
this.startSilenceGenerator();
|
||||
|
||||
this.audioRXClientSocket.on('close', () => {
|
||||
this.stopSilenceGenerator();
|
||||
this.audioRXClientSocket = null;
|
||||
});
|
||||
});
|
||||
const serverTimeout = setTimeout(() => {
|
||||
this.console.log('Doorbird: timed out waiting for tcp client from ffmpeg');
|
||||
server.close();
|
||||
}, 30000);
|
||||
const port = await listenZero(server);
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
triggerBinarySensor() {
|
||||
this.binaryState = true;
|
||||
clearTimeout(this.binarySensorTimeout);
|
||||
this.binarySensorTimeout = setTimeout(() => this.binaryState = false, 3000);
|
||||
}
|
||||
|
||||
triggerMotionSensor() {
|
||||
this.motionDetected = true;
|
||||
clearTimeout(this.motionSensorTimeout);
|
||||
this.motionSensorTimeout = setTimeout(() => this.motionDetected = false, 3000);
|
||||
}
|
||||
|
||||
setHttpPortOverride(port: string) {
|
||||
this.storage.setItem('httpPort', port || '');
|
||||
}
|
||||
|
||||
getHttpBaseAddress() {
|
||||
return `http://${this.getUsername()}:${this.getPassword()}@${this.getIPAddress()}:${this.storage.getItem('httpPort') || 80}`;
|
||||
}
|
||||
|
||||
getRtspAddress() {
|
||||
if (this.storage.getItem('rtspUrl') !== undefined) {
|
||||
return this.storage.getItem('rtspUrl');
|
||||
}
|
||||
else {
|
||||
return this.getRtspDefaultAddress();
|
||||
}
|
||||
}
|
||||
|
||||
getRtspDefaultAddress() {
|
||||
return `rtsp://${this.getUsername()}:${this.getPassword()}@${this.getIPAddress()}/mpeg/media.amp`;
|
||||
}
|
||||
|
||||
getIPAddress() {
|
||||
return this.storage.getItem('ip');
|
||||
}
|
||||
|
||||
setIPAddress(ip: string) {
|
||||
return this.storage.setItem('ip', ip);
|
||||
}
|
||||
|
||||
getUsername() {
|
||||
return this.storage.getItem('username');
|
||||
}
|
||||
|
||||
getPassword() {
|
||||
return this.storage.getItem('password');
|
||||
}
|
||||
}
|
||||
|
||||
export class DoorbirdCamProvider extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator {
|
||||
|
||||
devices = new Map<string, any>();
|
||||
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
|
||||
for (const camId of deviceManager.getNativeIds()) {
|
||||
if (camId)
|
||||
this.getDevice(camId);
|
||||
}
|
||||
}
|
||||
|
||||
async createDevice(settings: DeviceCreatorSettings, nativeId?: string): Promise<string> {
|
||||
|
||||
let info: DeviceInformation = {};
|
||||
|
||||
const host = settings.ip?.toString();
|
||||
const username = settings.username?.toString();
|
||||
const password = settings.password?.toString();
|
||||
const skipValidate = settings.skipValidate === 'true';
|
||||
|
||||
if (!skipValidate) {
|
||||
const api = new DoorbirdAPI(host, username, password, this.console);
|
||||
try {
|
||||
const deviceInfo = await api.getInfo();
|
||||
|
||||
settings.newCamera = deviceInfo.deviceType;
|
||||
info.model = deviceInfo.deviceType;
|
||||
info.serialNumber = deviceInfo.serialNumber;
|
||||
info.mac = deviceInfo.serialNumber;
|
||||
info.manufacturer = 'Bird Home Automation GmbH';
|
||||
info.managementUrl = 'https://webadmin.doorbird.com';
|
||||
}
|
||||
catch (e) {
|
||||
this.console.error('Error adding Doorbird camera', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
settings.newCamera ||= 'Doorbird Camera';
|
||||
|
||||
nativeId ||= randomBytes(4).toString('hex');
|
||||
const name = settings.newCamera?.toString();
|
||||
await this.updateDevice(nativeId, name);
|
||||
|
||||
const device = await this.getDevice(nativeId) as DoorbirdCamera;
|
||||
device.info = info;
|
||||
device.putSetting('username', username);
|
||||
device.putSetting('password', password);
|
||||
device.setIPAddress(settings.ip.toString());
|
||||
device.setHttpPortOverride(settings.httpPort?.toString());
|
||||
|
||||
return nativeId;
|
||||
}
|
||||
|
||||
async getCreateDeviceSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
{
|
||||
key: 'username',
|
||||
title: 'Username',
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
title: 'Password',
|
||||
type: 'password',
|
||||
},
|
||||
{
|
||||
key: 'ip',
|
||||
title: 'IP Address',
|
||||
placeholder: '192.168.2.222',
|
||||
},
|
||||
{
|
||||
key: 'httpPort',
|
||||
title: 'HTTP Port',
|
||||
description: 'Optional: Override the HTTP Port from the default value of 80',
|
||||
placeholder: '80',
|
||||
},
|
||||
{
|
||||
key: 'skipValidate',
|
||||
title: 'Skip Validation',
|
||||
description: 'Add the device without verifying the credentials and network settings.',
|
||||
type: 'boolean',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
updateDevice(nativeId: string, name: string) {
|
||||
return deviceManager.onDeviceDiscovered({
|
||||
nativeId,
|
||||
name,
|
||||
interfaces: [
|
||||
ScryptedInterface.Camera,
|
||||
ScryptedInterface.VideoCamera,
|
||||
ScryptedInterface.Settings,
|
||||
ScryptedInterface.Intercom,
|
||||
ScryptedInterface.BinarySensor,
|
||||
ScryptedInterface.MotionSensor
|
||||
],
|
||||
type: ScryptedDeviceType.Doorbell,
|
||||
info: deviceManager.getNativeIds().includes(nativeId) ? deviceManager.getDeviceState(nativeId)?.info : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
getDevice(nativeId: string) {
|
||||
let ret = this.devices.get(nativeId);
|
||||
if (!ret) {
|
||||
ret = this.createCamera(nativeId);
|
||||
if (ret)
|
||||
this.devices.set(nativeId, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
if( this.devices.delete( nativeId ) ) {
|
||||
this.console.log("Doorbird: Removed device from list: " + id + " / " + nativeId )
|
||||
}
|
||||
}
|
||||
|
||||
createCamera(nativeId: string): DoorbirdCamera {
|
||||
return new DoorbirdCamera(nativeId, this);
|
||||
}
|
||||
}
|
||||
|
||||
export default new DoorbirdCamProvider();
|
||||
11
plugins/doorbird/tsconfig.json
Normal file
11
plugins/doorbird/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"moduleResolution": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
250
plugins/dummy-switch/package-lock.json
generated
250
plugins/dummy-switch/package-lock.json
generated
@@ -1,61 +1,82 @@
|
||||
{
|
||||
"name": "@scrypted/dummy-switch",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.23",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/dummy-switch",
|
||||
"version": "0.0.15",
|
||||
"hasInstallScript": true,
|
||||
"version": "0.0.23",
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"axios": "^0.19.0"
|
||||
"axios": "^1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.0.199",
|
||||
"version": "0.2.97",
|
||||
"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",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"webpack": "^5.59.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": "^16.11.1",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
@@ -65,61 +86,130 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410",
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz",
|
||||
"integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "1.5.10"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"dependencies": {
|
||||
"debug": "=3.1.0"
|
||||
},
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects/node_modules/debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^16.9.0",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@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",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"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.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack": "^5.59.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
@@ -128,36 +218,66 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz",
|
||||
"integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"axios": "^0.19.0"
|
||||
"axios": "^1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.15"
|
||||
"version": "0.0.23"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { BinarySensor, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Lock, LockState, MotionSensor, OccupancySensor, OnOff, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue, StartStop } from '@scrypted/sdk';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import { ReplaceMotionSensor, ReplaceMotionSensorNativeId } from './replace-motion-sensor';
|
||||
import { ReplaceBinarySensor, ReplaceBinarySensorNativeId } from './replace-binary-sensor';
|
||||
|
||||
const { log, deviceManager } = sdk;
|
||||
|
||||
@@ -87,6 +89,27 @@ class DummyDeviceProvider extends ScryptedDeviceBase implements DeviceProvider,
|
||||
this.getDevice(camId);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
name: 'Custom Motion Sensor',
|
||||
nativeId: ReplaceMotionSensorNativeId,
|
||||
interfaces: [ScryptedInterface.MixinProvider],
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
},
|
||||
);
|
||||
})();
|
||||
|
||||
(async () => {
|
||||
await deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
name: 'Custom Doorbell Button',
|
||||
nativeId: ReplaceBinarySensorNativeId,
|
||||
interfaces: [ScryptedInterface.MixinProvider],
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
},
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
async getCreateDeviceSettings(): Promise<Setting[]> {
|
||||
@@ -127,6 +150,11 @@ class DummyDeviceProvider extends ScryptedDeviceBase implements DeviceProvider,
|
||||
}
|
||||
|
||||
async getDevice(nativeId: string) {
|
||||
if (nativeId === ReplaceMotionSensorNativeId)
|
||||
return new ReplaceMotionSensor(ReplaceMotionSensorNativeId);
|
||||
if (nativeId === ReplaceBinarySensorNativeId)
|
||||
return new ReplaceBinarySensor(ReplaceBinarySensorNativeId);
|
||||
|
||||
let ret = this.devices.get(nativeId);
|
||||
if (!ret) {
|
||||
ret = new DummyDevice(nativeId);
|
||||
@@ -143,7 +171,7 @@ class DummyDeviceProvider extends ScryptedDeviceBase implements DeviceProvider,
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
80
plugins/dummy-switch/src/replace-binary-sensor.ts
Normal file
80
plugins/dummy-switch/src/replace-binary-sensor.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
||||
import { BinarySensor, DeviceState, EventListenerRegister, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
|
||||
export const ReplaceBinarySensorNativeId = 'replaceBinarySensor';
|
||||
|
||||
class ReplaceBinarySensorMixin extends SettingsMixinDeviceBase<any> implements Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
replaceBinarySensor: {
|
||||
title: 'Doorbell Button',
|
||||
description: 'The binary sensor to attach to this camera.',
|
||||
value: this.storage.getItem('replaceBinarySensor'),
|
||||
deviceFilter: `interfaces.includes('${ScryptedInterface.BinarySensor}') && !interfaces.includes('@scrypted/dummy-switch:ReplaceBinarySensor') && id !== '${this.id}'`,
|
||||
type: 'device',
|
||||
}
|
||||
});
|
||||
|
||||
listener: EventListenerRegister;
|
||||
|
||||
constructor(options: SettingsMixinDeviceOptions<any>) {
|
||||
super(options);
|
||||
this.binaryState = false;
|
||||
|
||||
this.register();
|
||||
}
|
||||
|
||||
register() {
|
||||
this.release();
|
||||
|
||||
const d = this.storageSettings.values.replaceBinarySensor as ScryptedDevice & BinarySensor;
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
this.listener = d.listen(ScryptedInterface.BinarySensor, () => {
|
||||
this.binaryState = d.binaryState;
|
||||
});
|
||||
}
|
||||
|
||||
getMixinSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
putMixinSetting(key: string, value: SettingValue) {
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
async release(): Promise<void> {
|
||||
this.listener?.removeListener();
|
||||
this.listener = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ReplaceBinarySensor extends ScryptedDeviceBase implements MixinProvider {
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
if (type !== ScryptedDeviceType.Camera && type !== ScryptedDeviceType.Doorbell)
|
||||
return;
|
||||
|
||||
return [
|
||||
ScryptedInterface.BinarySensor,
|
||||
ScryptedInterface.Settings,
|
||||
'@scrypted/dummy-switch:ReplaceBinarySensor',
|
||||
];
|
||||
}
|
||||
|
||||
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: DeviceState): Promise<any> {
|
||||
return new ReplaceBinarySensorMixin({
|
||||
group: 'Custom Doorbell Button',
|
||||
groupKey: 'replaceBinarySensor',
|
||||
mixinDevice,
|
||||
mixinDeviceInterfaces,
|
||||
mixinProviderNativeId: this.nativeId,
|
||||
mixinDeviceState,
|
||||
});
|
||||
}
|
||||
|
||||
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
||||
await mixinDevice.release();
|
||||
}
|
||||
}
|
||||
80
plugins/dummy-switch/src/replace-motion-sensor.ts
Normal file
80
plugins/dummy-switch/src/replace-motion-sensor.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
||||
import { DeviceState, EventListenerRegister, MixinProvider, MotionSensor, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
|
||||
export const ReplaceMotionSensorNativeId = 'replaceMotionSensor';
|
||||
|
||||
class ReplaceMotionSensorMixin extends SettingsMixinDeviceBase<any> implements Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
replaceMotionSensor: {
|
||||
title: 'Motion Sensor',
|
||||
description: 'The motion sensor to attach to this camera or doorbell.',
|
||||
value: this.storage.getItem('replaceMotionSensor'),
|
||||
deviceFilter: `interfaces.includes('${ScryptedInterface.MotionSensor}') && !interfaces.includes('@scrypted/dummy-switch:ReplaceMotionSensor') && id !== '${this.id}'`,
|
||||
type: 'device',
|
||||
}
|
||||
});
|
||||
|
||||
listener: EventListenerRegister;
|
||||
|
||||
constructor(options: SettingsMixinDeviceOptions<any>) {
|
||||
super(options);
|
||||
this.motionDetected = false;
|
||||
|
||||
this.register();
|
||||
}
|
||||
|
||||
register() {
|
||||
this.release();
|
||||
|
||||
const d = this.storageSettings.values.replaceMotionSensor as ScryptedDevice & MotionSensor;
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
this.listener = d.listen(ScryptedInterface.MotionSensor, () => {
|
||||
this.motionDetected = d.motionDetected;
|
||||
});
|
||||
}
|
||||
|
||||
getMixinSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
putMixinSetting(key: string, value: SettingValue) {
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
async release(): Promise<void> {
|
||||
this.listener?.removeListener();
|
||||
this.listener = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ReplaceMotionSensor extends ScryptedDeviceBase implements MixinProvider {
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
if (type !== ScryptedDeviceType.Camera && type !== ScryptedDeviceType.Doorbell)
|
||||
return;
|
||||
|
||||
return [
|
||||
ScryptedInterface.MotionSensor,
|
||||
ScryptedInterface.Settings,
|
||||
'@scrypted/dummy-switch:ReplaceMotionSensor',
|
||||
];
|
||||
}
|
||||
|
||||
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: DeviceState): Promise<any> {
|
||||
return new ReplaceMotionSensorMixin({
|
||||
group: 'Custom Motion Sensor',
|
||||
groupKey: 'replaceMotionSensor',
|
||||
mixinDevice,
|
||||
mixinDeviceInterfaces,
|
||||
mixinProviderNativeId: this.nativeId,
|
||||
mixinDeviceState,
|
||||
});
|
||||
}
|
||||
|
||||
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
||||
await mixinDevice.release();
|
||||
}
|
||||
}
|
||||
4
plugins/homekit/package-lock.json
generated
4
plugins/homekit/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.23",
|
||||
"version": "1.2.25",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.23",
|
||||
"version": "1.2.25",
|
||||
"dependencies": {
|
||||
"@koush/werift-src": "file:../../external/werift",
|
||||
"check-disk-space": "^3.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.23",
|
||||
"version": "1.2.25",
|
||||
"description": "HomeKit Plugin for Scrypted",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
|
||||
@@ -76,18 +76,20 @@ The latest troubleshooting guide for all known streaming or recording issues can
|
||||
const settings: Setting[] = [];
|
||||
const realDevice = systemManager.getDeviceById<ObjectDetector & VideoCamera>(this.id);
|
||||
|
||||
settings.push(
|
||||
{
|
||||
title: 'Linked Motion Sensor',
|
||||
key: 'linkedMotionSensor',
|
||||
type: 'device',
|
||||
deviceFilter: 'interfaces.includes("MotionSensor")',
|
||||
value: this.storage.getItem('linkedMotionSensor') || this.id,
|
||||
placeholder: this.interfaces.includes(ScryptedInterface.MotionSensor)
|
||||
? undefined : 'None',
|
||||
description: "Set the motion sensor used to trigger HomeKit Secure Video recordings. Defaults to the device provided motion sensor when available.",
|
||||
},
|
||||
);
|
||||
if (this.storage.getItem('linkedMotionSensor')) {
|
||||
settings.push(
|
||||
{
|
||||
title: 'Linked Motion Sensor',
|
||||
key: 'linkedMotionSensor',
|
||||
type: 'device',
|
||||
deviceFilter: 'interfaces.includes("MotionSensor")',
|
||||
value: this.storage.getItem('linkedMotionSensor') || this.id,
|
||||
placeholder: this.interfaces.includes(ScryptedInterface.MotionSensor)
|
||||
? undefined : 'None',
|
||||
description: "Set the motion sensor used to trigger HomeKit Secure Video recordings. Defaults to the device provided motion sensor when available.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// settings.push({
|
||||
// title: 'H265 Streams',
|
||||
|
||||
@@ -213,6 +213,9 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
|
||||
try {
|
||||
const mixins = (device.mixins || []).slice();
|
||||
if (!mixins.includes(this.id)) {
|
||||
// don't sync this by default, as it's solely for automations
|
||||
if (device.type === ScryptedDeviceType.Notifier)
|
||||
continue;
|
||||
if (defaultIncluded[device.id] === includeToken)
|
||||
continue;
|
||||
mixins.push(this.id);
|
||||
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Deferred } from '@scrypted/common/src/deferred';
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
import sdk, { Camera, DeviceProvider, DeviceState, EventListenerRegister, MediaObject, MediaStreamDestination, MixinDeviceBase, MixinProvider, MotionSensor, ObjectDetection, ObjectDetectionGeneratorResult, ObjectDetectionModel, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, Settings, SettingValue, VideoCamera, VideoFrame, VideoFrameGenerator } from '@scrypted/sdk';
|
||||
import sdk, { Camera, DeviceProvider, DeviceState, EventListenerRegister, MediaObject, MediaStreamDestination, MixinDeviceBase, MixinProvider, MotionSensor, ObjectDetection, ObjectDetectionGeneratorResult, ObjectDetectionModel, ObjectDetectionTypes, ObjectDetectionZone, ObjectDetector, ObjectsDetected, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, Settings, SettingValue, VideoCamera, VideoFrame, VideoFrameGenerator } from '@scrypted/sdk';
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import crypto from 'crypto';
|
||||
import { AutoenableMixinProvider } from "../../../common/src/autoenable-mixin-provider";
|
||||
@@ -17,7 +17,7 @@ const { systemManager } = sdk;
|
||||
const defaultDetectionDuration = 20;
|
||||
const defaultDetectionInterval = 60;
|
||||
const defaultDetectionTimeout = 60;
|
||||
const defaultMotionDuration = 10;
|
||||
const defaultMotionDuration = 30;
|
||||
|
||||
const BUILTIN_MOTION_SENSOR_ASSIST = 'Assist';
|
||||
const BUILTIN_MOTION_SENSOR_REPLACE = 'Replace';
|
||||
@@ -265,7 +265,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
snapshotPipeline: this.plugin.shouldUseSnapshotPipeline(),
|
||||
};
|
||||
|
||||
this.runPipelineAnalysis(signal, options)
|
||||
this.runPipelineAnalysisLoop(signal, options)
|
||||
.catch(e => {
|
||||
this.console.error('Video Analysis ended with error', e);
|
||||
}).finally(() => {
|
||||
@@ -277,28 +277,26 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
});
|
||||
}
|
||||
|
||||
async runPipelineAnalysis(signal: Deferred<void>, options: {
|
||||
async runPipelineAnalysisLoop(signal: Deferred<void>, options: {
|
||||
snapshotPipeline: boolean,
|
||||
suppress?: boolean,
|
||||
}) {
|
||||
const start = Date.now();
|
||||
this.analyzeStop = start + this.getDetectionDuration();
|
||||
|
||||
let lastStatusTime = Date.now();
|
||||
let lastStatus = 'starting';
|
||||
const updatePipelineStatus = (status: string) => {
|
||||
lastStatus = status;
|
||||
lastStatusTime = Date.now();
|
||||
while (!signal.finished) {
|
||||
const shouldSleep = await this.runPipelineAnalysis(signal, options);
|
||||
options.suppress = true;
|
||||
if (!shouldSleep || signal.finished)
|
||||
return;
|
||||
this.console.log('Suspending motion processing during active motion timeout.');
|
||||
// sleep until a moment before motion duration to start peeking again
|
||||
// to have an opporunity to reset the motion timeout.
|
||||
await sleep(this.storageSettings.values.motionDuration * 1000 - 4000);
|
||||
}
|
||||
}
|
||||
|
||||
let frameGenerator: AsyncGenerator<VideoFrame & MediaObject, void>;
|
||||
let detectionGenerator: AsyncGenerator<ObjectDetectionGeneratorResult, void>;
|
||||
const interval = setInterval(() => {
|
||||
if (Date.now() - lastStatusTime > 30000) {
|
||||
signal.resolve();
|
||||
this.console.error('VideoAnalysis is hung and will terminate:', lastStatus);
|
||||
}
|
||||
}, 30000);
|
||||
signal.promise.finally(() => clearInterval(interval));
|
||||
async createFrameGenerator(signal: Deferred<void>, options: {
|
||||
snapshotPipeline: boolean,
|
||||
suppress?: boolean,
|
||||
}, updatePipelineStatus: (status: string) => void) {
|
||||
|
||||
let newPipeline: string = this.newPipeline;
|
||||
if (!this.hasMotionType && (!newPipeline || newPipeline === 'Default')) {
|
||||
@@ -312,7 +310,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
options.snapshotPipeline = true;
|
||||
this.console.log('decoder:', 'Snapshot +', this.objectDetection.name);
|
||||
const self = this;
|
||||
frameGenerator = (async function* gen() {
|
||||
return (async function* gen() {
|
||||
try {
|
||||
while (!signal.finished) {
|
||||
const now = Date.now();
|
||||
@@ -353,7 +351,8 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
const videoFrameGenerator = systemManager.getDeviceById<VideoFrameGenerator>(newPipeline);
|
||||
if (!videoFrameGenerator)
|
||||
throw new Error('invalid VideoFrameGenerator');
|
||||
this.console.log(videoFrameGenerator.name, '+', this.objectDetection.name);
|
||||
if (!options?.suppress)
|
||||
this.console.log(videoFrameGenerator.name, '+', this.objectDetection.name);
|
||||
updatePipelineStatus('getVideoStream');
|
||||
const stream = await this.cameraDevice.getVideoStream({
|
||||
prebuffer: this.model.prebuffer,
|
||||
@@ -362,7 +361,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
audio: null,
|
||||
});
|
||||
|
||||
frameGenerator = await videoFrameGenerator.generateVideoFrames(stream, {
|
||||
return await videoFrameGenerator.generateVideoFrames(stream, {
|
||||
queue: 0,
|
||||
resize: this.model?.inputSize ? {
|
||||
width: this.model.inputSize[0],
|
||||
@@ -371,17 +370,63 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
format: this.model?.inputFormat,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async runPipelineAnalysis(signal: Deferred<void>, options: {
|
||||
snapshotPipeline: boolean,
|
||||
suppress?: boolean,
|
||||
}) {
|
||||
const start = Date.now();
|
||||
this.analyzeStop = start + this.getDetectionDuration();
|
||||
|
||||
let lastStatusTime = Date.now();
|
||||
let lastStatus = 'starting';
|
||||
const updatePipelineStatus = (status: string) => {
|
||||
lastStatus = status;
|
||||
lastStatusTime = Date.now();
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (Date.now() - lastStatusTime > 30000) {
|
||||
signal.resolve();
|
||||
this.console.error('VideoAnalysis is hung and will terminate:', lastStatus);
|
||||
}
|
||||
}, 30000);
|
||||
signal.promise.finally(() => clearInterval(interval));
|
||||
|
||||
const currentDetections = new Set<string>();
|
||||
let lastReport = 0;
|
||||
detectionGenerator = await sdk.connectRPCObject(await this.objectDetection.generateObjectDetections(frameGenerator, {
|
||||
settings: this.getCurrentSettings(),
|
||||
sourceId: this.id,
|
||||
}));
|
||||
|
||||
updatePipelineStatus('waiting result');
|
||||
|
||||
for await (const detected of detectionGenerator) {
|
||||
const zones: ObjectDetectionZone[] = [];
|
||||
for (const detectorMixin of this.plugin.currentMixins.values()) {
|
||||
for (const mixin of detectorMixin.currentMixins.values()) {
|
||||
if (mixin.id !== this.id)
|
||||
continue;
|
||||
for (const [key, zi] of Object.entries(mixin.zoneInfos)) {
|
||||
const zone = mixin.zones[key];
|
||||
if (!zone?.length || zone?.length < 3)
|
||||
continue;
|
||||
const odz: ObjectDetectionZone = {
|
||||
classes: mixin.hasMotionType ? ['motion'] : zi.classes,
|
||||
exclusion: zi.exclusion,
|
||||
path: zone,
|
||||
type: zi.type,
|
||||
}
|
||||
zones.push(odz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for await (const detected of
|
||||
await sdk.connectRPCObject(
|
||||
await this.objectDetection.generateObjectDetections(
|
||||
await this.createFrameGenerator(signal, options, updatePipelineStatus), {
|
||||
settings: this.getCurrentSettings(),
|
||||
sourceId: this.id,
|
||||
zones,
|
||||
}))) {
|
||||
if (signal.finished) {
|
||||
break;
|
||||
}
|
||||
@@ -426,11 +471,14 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
this.setDetection(detected.detected, mo);
|
||||
// this.console.log('image saved', detected.detected.detections);
|
||||
}
|
||||
const hadMotionDetected = this.motionDetected;
|
||||
this.reportObjectDetections(detected.detected);
|
||||
if (this.hasMotionType) {
|
||||
// const diff = Date.now() - when;
|
||||
// when = Date.now();
|
||||
// this.console.log('sleper', diff);
|
||||
if (!hadMotionDetected && this.motionDetected) {
|
||||
// if new motion is detected, stop processing and exit loop allowing it to sleep.
|
||||
clearInterval(interval);
|
||||
return true;
|
||||
}
|
||||
await sleep(250);
|
||||
}
|
||||
updatePipelineStatus('waiting result');
|
||||
@@ -695,6 +743,20 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
],
|
||||
value: zi?.type || 'Intersect',
|
||||
});
|
||||
|
||||
if (!this.hasMotionType) {
|
||||
settings.push(
|
||||
{
|
||||
subgroup,
|
||||
key: `zoneinfo-classes-${name}`,
|
||||
title: `Detection Classes`,
|
||||
description: 'The detection classes to match inside this zone. An empty list will match all classes.',
|
||||
choices: (await this.getObjectTypes())?.classes || [],
|
||||
value: zi?.classes || [],
|
||||
multiple: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.hasMotionType) {
|
||||
|
||||
8
plugins/opencv/.vscode/settings.json
vendored
8
plugins/opencv/.vscode/settings.json
vendored
@@ -5,12 +5,12 @@
|
||||
// "scrypted.serverRoot": "/home/pi/.scrypted",
|
||||
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "192.168.2.109",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
"scrypted.debugHost": "koushik-ubuntu",
|
||||
"scrypted.serverRoot": "/server",
|
||||
|
||||
// local checkout
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "127.0.0.1",
|
||||
// "scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "koushik-windows",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
|
||||
6
plugins/opencv/package-lock.json
generated
6
plugins/opencv/package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.74",
|
||||
"version": "0.0.79",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.74",
|
||||
"version": "0.0.79",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.85",
|
||||
"version": "0.2.97",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.74"
|
||||
"version": "0.0.79"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
from typing import Any, List, Tuple
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import imutils
|
||||
@@ -49,11 +50,11 @@ class OpenCVDetectionSession:
|
||||
self.thresh = None
|
||||
self.gray = None
|
||||
self.gstsample = None
|
||||
self.lastFrame = 0
|
||||
|
||||
|
||||
defaultThreshold = 25
|
||||
defaultArea = 200
|
||||
defaultInterval = 250
|
||||
defaultBlur = 5
|
||||
|
||||
class OpenCVPlugin(DetectPlugin):
|
||||
@@ -89,14 +90,6 @@ class OpenCVPlugin(DetectPlugin):
|
||||
'placeholder': defaultBlur,
|
||||
'type': 'number',
|
||||
},
|
||||
{
|
||||
'title': "Frame Analysis Interval",
|
||||
'description': "The number of milliseconds to wait between motion analysis.",
|
||||
'value': defaultInterval,
|
||||
'key': 'interval',
|
||||
'placeholder': defaultInterval,
|
||||
'type': 'number',
|
||||
},
|
||||
]
|
||||
|
||||
return settings
|
||||
@@ -107,19 +100,19 @@ class OpenCVPlugin(DetectPlugin):
|
||||
def parse_settings(self, settings: Any):
|
||||
area = defaultArea
|
||||
threshold = defaultThreshold
|
||||
interval = defaultInterval
|
||||
blur = defaultBlur
|
||||
referenceFrameFrequency = 0
|
||||
if settings:
|
||||
area = float(settings.get('area', area))
|
||||
threshold = int(settings.get('threshold', threshold))
|
||||
interval = float(settings.get('interval', interval))
|
||||
blur = int(settings.get('blur', blur))
|
||||
return area, threshold, interval, blur
|
||||
referenceFrameFrequency = float(settings.get('referenceFrameFrequency', 0))
|
||||
return area, threshold, blur, referenceFrameFrequency
|
||||
|
||||
def detect(self, frame, detection_session: ObjectDetectionSession, src_size, convert_to_src_size) -> ObjectsDetected:
|
||||
session: OpenCVDetectionSession = detection_session['settings']['session']
|
||||
settings = detection_session and detection_session.get('settings', None)
|
||||
area, threshold, interval, blur = self.parse_settings(settings)
|
||||
area, threshold, blur, referenceFrameFrequency = self.parse_settings(settings)
|
||||
|
||||
gray = frame
|
||||
session.curFrame = cv2.GaussianBlur(
|
||||
@@ -127,6 +120,8 @@ class OpenCVPlugin(DetectPlugin):
|
||||
|
||||
detections: List[ObjectDetectionResult] = []
|
||||
detection_result: ObjectsDetected = {}
|
||||
now = round(time.time() * 1000)
|
||||
detection_result['timestamp'] = now
|
||||
detection_result['detections'] = detections
|
||||
detection_result['inputDimensions'] = src_size
|
||||
|
||||
@@ -137,9 +132,10 @@ class OpenCVPlugin(DetectPlugin):
|
||||
|
||||
session.frameDelta = cv2.absdiff(
|
||||
session.previous_frame, session.curFrame, dst=session.frameDelta)
|
||||
tmp = session.curFrame
|
||||
session.curFrame = session.previous_frame
|
||||
session.previous_frame = tmp
|
||||
if not referenceFrameFrequency or now - session.lastFrame > referenceFrameFrequency:
|
||||
tmp = session.curFrame
|
||||
session.curFrame = session.previous_frame
|
||||
session.previous_frame = tmp
|
||||
|
||||
_, session.thresh = cv2.threshold(
|
||||
session.frameDelta, threshold, 255, cv2.THRESH_BINARY, dst=session.thresh)
|
||||
|
||||
6
plugins/openvino/.gitignore
vendored
Normal file
6
plugins/openvino/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
dist/
|
||||
.venv
|
||||
all_models*
|
||||
15
plugins/openvino/.npmignore
Normal file
15
plugins/openvino/.npmignore
Normal file
@@ -0,0 +1,15 @@
|
||||
.DS_Store
|
||||
out/
|
||||
node_modules/
|
||||
*.map
|
||||
fs
|
||||
src
|
||||
.vscode
|
||||
dist/*.js
|
||||
dist/*.txt
|
||||
__pycache__
|
||||
all_models
|
||||
sort_oh
|
||||
download_models.sh
|
||||
tsconfig.json
|
||||
.venv
|
||||
30
plugins/openvino/.vscode/launch.json
vendored
Normal file
30
plugins/openvino/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
// 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",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "${config:scrypted.debugHost}",
|
||||
"port": 10081
|
||||
},
|
||||
"justMyCode": false,
|
||||
"preLaunchTask": "scrypted: deploy+debug",
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "/Volumes/Dev/scrypted/server/python/",
|
||||
"remoteRoot": "/Volumes/Dev/scrypted/server/python/",
|
||||
},
|
||||
{
|
||||
"localRoot": "${workspaceFolder}/src",
|
||||
"remoteRoot": "${config:scrypted.pythonRemoteRoot}"
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
21
plugins/openvino/.vscode/settings.json
vendored
Normal file
21
plugins/openvino/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
{
|
||||
// docker installation
|
||||
// "scrypted.debugHost": "koushik-ubuntu",
|
||||
// "scrypted.serverRoot": "/server",
|
||||
|
||||
// pi local installation
|
||||
// "scrypted.debugHost": "192.168.2.119",
|
||||
// "scrypted.serverRoot": "/home/pi/.scrypted",
|
||||
|
||||
// local checkout
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.serverRoot": "/Users/koush/.scrypted",
|
||||
// "scrypted.debugHost": "koushik-windows",
|
||||
// "scrypted.serverRoot": "C:\\Users\\koush\\.scrypted",
|
||||
|
||||
"scrypted.pythonRemoteRoot": "${config:scrypted.serverRoot}/volume/plugin.zip",
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
}
|
||||
20
plugins/openvino/.vscode/tasks.json
vendored
Normal file
20
plugins/openvino/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "scrypted: deploy+debug",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "silent",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": false
|
||||
},
|
||||
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
|
||||
},
|
||||
]
|
||||
}
|
||||
6
plugins/openvino/README.md
Normal file
6
plugins/openvino/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# OpenVINO 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 OpenVINO 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.
|
||||
86
plugins/openvino/package-lock.json
generated
Normal file
86
plugins/openvino/package-lock.json
generated
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.16",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/openvino",
|
||||
"version": "0.1.16",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.97",
|
||||
"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
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
plugins/openvino/package.json
Normal file
43
plugins/openvino/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@scrypted/openvino",
|
||||
"description": "Scrypted OpenVINO Object Detection",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
"plugin",
|
||||
"openvino",
|
||||
"motion",
|
||||
"object",
|
||||
"detect",
|
||||
"detection",
|
||||
"people",
|
||||
"person"
|
||||
],
|
||||
"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": "OpenVINO Object Detection",
|
||||
"pluginDependencies": [
|
||||
"@scrypted/objectdetector"
|
||||
],
|
||||
"runtime": "python",
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"ObjectDetection"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.16"
|
||||
}
|
||||
1
plugins/openvino/src/detect
Symbolic link
1
plugins/openvino/src/detect
Symbolic link
@@ -0,0 +1 @@
|
||||
../../tensorflow-lite/src/detect
|
||||
4
plugins/openvino/src/main.py
Normal file
4
plugins/openvino/src/main.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from ov import OpenVINOPlugin
|
||||
|
||||
def create_scrypted_plugin():
|
||||
return OpenVINOPlugin()
|
||||
102
plugins/openvino/src/ov/__init__.py
Normal file
102
plugins/openvino/src/ov/__init__.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Tuple
|
||||
|
||||
import openvino.runtime as ov
|
||||
import scrypted_sdk
|
||||
from PIL import Image
|
||||
from scrypted_sdk.types import Setting
|
||||
|
||||
from predict import PredictPlugin, Prediction, Rectangle
|
||||
import numpy as np
|
||||
|
||||
|
||||
def parse_label_contents(contents: str):
|
||||
lines = contents.splitlines()
|
||||
ret = {}
|
||||
for row_number, content in enumerate(lines):
|
||||
pair = re.split(r'[:\s]+', content.strip(), maxsplit=1)
|
||||
if len(pair) == 2 and pair[0].strip().isdigit():
|
||||
ret[int(pair[0])] = pair[1].strip()
|
||||
else:
|
||||
ret[row_number] = content.strip()
|
||||
return ret
|
||||
|
||||
|
||||
class OpenVINOPlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
self.core = ov.Core()
|
||||
available_devices = self.core.available_devices
|
||||
print('available devices: %s' % available_devices)
|
||||
|
||||
xmlFile = self.downloadFile('https://raw.githubusercontent.com/koush/openvino-models/main/ssd_mobilenet_v1_coco/FP16/ssd_mobilenet_v1_coco.xml', 'ssd_mobilenet_v1_coco.xml')
|
||||
mappingFile = self.downloadFile('https://raw.githubusercontent.com/koush/openvino-models/main/ssd_mobilenet_v1_coco/FP16/ssd_mobilenet_v1_coco.mapping', 'ssd_mobilenet_v1_coco.mapping')
|
||||
labelsFile = self.downloadFile('https://raw.githubusercontent.com/koush/openvino-models/main/ssd_mobilenet_v1_coco/FP16/ssd_mobilenet_v1_coco.bin', 'ssd_mobilenet_v1_coco.bin')
|
||||
|
||||
self.compiled_model = self.core.compile_model(xmlFile, "AUTO")
|
||||
|
||||
labelsFile = self.downloadFile('https://raw.githubusercontent.com/google-coral/test_data/master/coco_labels.txt', 'coco_labels.txt')
|
||||
labels_contents = open(labelsFile, 'r').read()
|
||||
self.labels = parse_label_contents(labels_contents)
|
||||
|
||||
self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1, thread_name_prefix="openvino", )
|
||||
|
||||
async def getSettings(self) -> list[Setting]:
|
||||
return []
|
||||
|
||||
# width, height, channels
|
||||
def get_input_details(self) -> Tuple[int, int, int]:
|
||||
return [300, 300, 3]
|
||||
|
||||
def get_input_size(self) -> Tuple[int, int]:
|
||||
return [300, 300]
|
||||
|
||||
async def detect_once(self, input: Image.Image, settings: Any, src_size, cvss):
|
||||
def predict():
|
||||
infer_request = self.compiled_model.create_infer_request()
|
||||
input_tensor = ov.Tensor(array=np.expand_dims(np.array(input), axis=0), shared_memory=True)
|
||||
# Set input tensor for model with one input
|
||||
infer_request.set_input_tensor(input_tensor)
|
||||
infer_request.start_async()
|
||||
infer_request.wait()
|
||||
output = infer_request.get_output_tensor()
|
||||
|
||||
objs = []
|
||||
for values in output.data[0][0].astype(float):
|
||||
valid, index, confidence, l, t, r, b = values
|
||||
if valid == -1:
|
||||
break
|
||||
|
||||
def torelative(value: float):
|
||||
return value * 300
|
||||
|
||||
l = torelative(l)
|
||||
t = torelative(t)
|
||||
r = torelative(r)
|
||||
b = torelative(b)
|
||||
|
||||
obj = Prediction(index - 1, confidence, Rectangle(
|
||||
l,
|
||||
t,
|
||||
r,
|
||||
b
|
||||
))
|
||||
objs.append(obj)
|
||||
|
||||
return objs
|
||||
|
||||
try:
|
||||
objs = await asyncio.get_event_loop().run_in_executor(self.executor, predict)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise
|
||||
|
||||
ret = self.create_detection_result(objs, src_size, cvss)
|
||||
return ret
|
||||
1
plugins/openvino/src/predict
Symbolic link
1
plugins/openvino/src/predict
Symbolic link
@@ -0,0 +1 @@
|
||||
../../tensorflow-lite/src/predict
|
||||
5
plugins/openvino/src/requirements.txt
Normal file
5
plugins/openvino/src/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
openvino==2022.3.0
|
||||
|
||||
# pillow for anything not intel linux, pillow-simd is available on x64 linux
|
||||
Pillow>=5.4.1; sys_platform != 'linux' or platform_machine != 'x86_64'
|
||||
pillow-simd; sys_platform == 'linux' and platform_machine == 'x86_64'
|
||||
13
plugins/openvino/tsconfig.json
Normal file
13
plugins/openvino/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2021",
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Node16",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
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.9.81",
|
||||
"version": "0.9.82",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.9.81",
|
||||
"version": "0.9.82",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/prebuffer-mixin",
|
||||
"version": "0.9.81",
|
||||
"version": "0.9.82",
|
||||
"description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -10,7 +10,7 @@ import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
|
||||
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
import { createFragmentedMp4Parser, createMpegTsParser, StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
|
||||
import sdk, { BufferConverter, DeviceProvider, DeviceState, EventListenerRegister, FFmpegInput, H264Info, MediaObject, MediaStreamDestination, MediaStreamOptions, MixinProvider, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
|
||||
import sdk, { BufferConverter, ChargeState, DeviceBase, DeviceProvider, DeviceState, EventListenerRegister, FFmpegInput, H264Info, MediaObject, MediaStreamDestination, MediaStreamOptions, MixinProvider, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import crypto from 'crypto';
|
||||
import { once } from 'events';
|
||||
@@ -59,8 +59,8 @@ type Prebuffers<T extends string> = {
|
||||
[key in T]: PrebufferStreamChunk[];
|
||||
}
|
||||
|
||||
type PrebufferParsers = 'mpegts' | 'mp4' | 'rtsp';
|
||||
const PrebufferParserValues: PrebufferParsers[] = ['mpegts', 'mp4', 'rtsp'];
|
||||
type PrebufferParsers = 'rtsp';
|
||||
const PrebufferParserValues: PrebufferParsers[] = ['rtsp'];
|
||||
|
||||
function hasOddities(h264Info: H264Info) {
|
||||
const h264Oddities = h264Info.fuab
|
||||
@@ -79,8 +79,6 @@ class PrebufferSession {
|
||||
parserSessionPromise: Promise<ParserSession<PrebufferParsers>>;
|
||||
parserSession: ParserSession<PrebufferParsers>;
|
||||
prebuffers: Prebuffers<PrebufferParsers> = {
|
||||
mp4: [],
|
||||
mpegts: [],
|
||||
rtsp: [],
|
||||
};
|
||||
parsers: { [container: string]: StreamParser };
|
||||
@@ -100,18 +98,19 @@ class PrebufferSession {
|
||||
ffmpegInputArgumentsKey: string;
|
||||
lastDetectedAudioCodecKey: string;
|
||||
lastH264ProbeKey: string;
|
||||
rebroadcastModeKey: string;
|
||||
rtspParserKey: string;
|
||||
rtspServerPath: string;
|
||||
rtspServerMutedPath: string;
|
||||
|
||||
constructor(public mixin: PrebufferMixin, public advertisedMediaStreamOptions: ResponseMediaStreamOptions, public stopInactive: boolean) {
|
||||
batteryListener: EventListenerRegister;
|
||||
chargerListener: EventListenerRegister;
|
||||
|
||||
constructor(public mixin: PrebufferMixin, public advertisedMediaStreamOptions: ResponseMediaStreamOptions, public enabled: boolean, public forceBatteryPrebuffer: boolean) {
|
||||
this.storage = mixin.storage;
|
||||
this.console = mixin.console;
|
||||
this.mixinDevice = mixin.mixinDevice;
|
||||
this.audioConfigurationKey = 'audioConfiguration-' + this.streamId;
|
||||
this.ffmpegInputArgumentsKey = 'ffmpegInputArguments-' + this.streamId;
|
||||
this.rebroadcastModeKey = 'rebroadcastMode-' + this.streamId;
|
||||
this.lastDetectedAudioCodecKey = 'lastDetectedAudioCodec-' + this.streamId;
|
||||
this.lastH264ProbeKey = 'lastH264Probe-' + this.streamId;
|
||||
this.rtspParserKey = 'rtspParser-' + this.streamId;
|
||||
@@ -129,6 +128,12 @@ class PrebufferSession {
|
||||
this.rtspServerMutedPath = crypto.randomBytes(8).toString('hex');
|
||||
this.storage.setItem(rtspServerMutedPathKey, this.rtspServerMutedPath);
|
||||
}
|
||||
|
||||
this.handleChargingBatteryEvents();
|
||||
}
|
||||
|
||||
get stopInactive() {
|
||||
return !this.enabled || this.shouldDisableBatteryPrebuffer();
|
||||
}
|
||||
|
||||
get canPrebuffer() {
|
||||
@@ -155,18 +160,7 @@ class PrebufferSession {
|
||||
|
||||
getDetectedIdrInterval() {
|
||||
const durations: number[] = [];
|
||||
if (this.prebuffers.mp4.length) {
|
||||
let last: number;
|
||||
|
||||
for (const chunk of this.prebuffers.mp4) {
|
||||
if (chunk.type === 'mdat') {
|
||||
if (last)
|
||||
durations.push(chunk.time - last);
|
||||
last = chunk.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this.prebuffers.rtsp.length) {
|
||||
if (this.prebuffers.rtsp.length) {
|
||||
let last: number;
|
||||
|
||||
for (const chunk of this.prebuffers.rtsp) {
|
||||
@@ -206,6 +200,14 @@ class PrebufferSession {
|
||||
parserSession.kill(new Error('rebroadcast disabled'));
|
||||
this.clearPrebuffers();
|
||||
});
|
||||
if (this.batteryListener) {
|
||||
this.batteryListener.removeListener();
|
||||
this.batteryListener = null;
|
||||
}
|
||||
if (this.chargerListener) {
|
||||
this.chargerListener.removeListener();
|
||||
this.chargerListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
ensurePrebufferSession() {
|
||||
@@ -242,7 +244,7 @@ class PrebufferSession {
|
||||
return mediaStreamOptions?.container?.startsWith('rtsp');
|
||||
}
|
||||
|
||||
getParser(rtspMode: boolean, mediaStreamOptions: MediaStreamOptions) {
|
||||
getParser(mediaStreamOptions: MediaStreamOptions) {
|
||||
let parser: string;
|
||||
const rtspParser = this.storage.getItem(this.rtspParserKey);
|
||||
|
||||
@@ -250,25 +252,17 @@ class PrebufferSession {
|
||||
parser = STRING_DEFAULT;
|
||||
}
|
||||
else {
|
||||
|
||||
if (rtspParser === FFMPEG_PARSER_TCP)
|
||||
parser = FFMPEG_PARSER_TCP;
|
||||
if (rtspParser === FFMPEG_PARSER_UDP)
|
||||
parser = FFMPEG_PARSER_UDP;
|
||||
|
||||
// scrypted parser can only be used in rtsp mode.
|
||||
if (rtspMode && !parser) {
|
||||
if (!rtspParser || rtspParser === STRING_DEFAULT)
|
||||
switch (rtspParser) {
|
||||
case FFMPEG_PARSER_TCP:
|
||||
case FFMPEG_PARSER_UDP:
|
||||
case SCRYPTED_PARSER_TCP:
|
||||
case SCRYPTED_PARSER_UDP:
|
||||
parser = rtspParser;
|
||||
break;
|
||||
default:
|
||||
parser = SCRYPTED_PARSER_TCP;
|
||||
if (rtspParser === SCRYPTED_PARSER_TCP)
|
||||
parser = SCRYPTED_PARSER_TCP;
|
||||
if (rtspParser === SCRYPTED_PARSER_UDP)
|
||||
parser = SCRYPTED_PARSER_UDP;
|
||||
break;
|
||||
}
|
||||
|
||||
// bad config, fall back to ffmpeg tcp parsing.
|
||||
if (!parser)
|
||||
parser = FFMPEG_PARSER_TCP;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -277,18 +271,6 @@ class PrebufferSession {
|
||||
}
|
||||
}
|
||||
|
||||
getRebroadcastContainer() {
|
||||
let mode = this.storage.getItem(this.rebroadcastModeKey) || 'Default';
|
||||
if (mode === 'Default')
|
||||
mode = 'RTSP';
|
||||
const rtspMode = mode?.startsWith('RTSP');
|
||||
|
||||
return {
|
||||
rtspMode: mode?.startsWith('RTSP'),
|
||||
muxingMp4: !rtspMode,
|
||||
};
|
||||
}
|
||||
|
||||
async getMixinSettings(): Promise<Setting[]> {
|
||||
const settings: Setting[] = [];
|
||||
|
||||
@@ -296,8 +278,7 @@ class PrebufferSession {
|
||||
|
||||
let total = 0;
|
||||
let start = 0;
|
||||
const { muxingMp4, rtspMode } = this.getRebroadcastContainer();
|
||||
for (const prebuffer of (muxingMp4 ? this.prebuffers.mp4 : this.prebuffers.rtsp)) {
|
||||
for (const prebuffer of this.prebuffers.rtsp) {
|
||||
start = start || prebuffer.time;
|
||||
for (const chunk of prebuffer.chunks) {
|
||||
total += chunk.byteLength;
|
||||
@@ -309,23 +290,6 @@ class PrebufferSession {
|
||||
const group = "Streams";
|
||||
const subgroup = `Stream: ${this.streamName}`;
|
||||
|
||||
settings.push(
|
||||
{
|
||||
title: 'Rebroadcast Container',
|
||||
group,
|
||||
subgroup,
|
||||
description: `The container format to use when rebroadcasting. The default mode for this camera is RTSP.`,
|
||||
placeholder: 'RTSP',
|
||||
choices: [
|
||||
STRING_DEFAULT,
|
||||
'MPEG-TS',
|
||||
'RTSP',
|
||||
],
|
||||
key: this.rebroadcastModeKey,
|
||||
value: this.storage.getItem(this.rebroadcastModeKey) || STRING_DEFAULT,
|
||||
}
|
||||
);
|
||||
|
||||
const addFFmpegAudioSettings = () => {
|
||||
settings.push(
|
||||
{
|
||||
@@ -366,19 +330,18 @@ class PrebufferSession {
|
||||
)
|
||||
}
|
||||
|
||||
let usingFFmpeg = muxingMp4;
|
||||
let usingFFmpeg = false;
|
||||
|
||||
if (this.canUseRtspParser(this.advertisedMediaStreamOptions)) {
|
||||
const canUseScryptedParser = rtspMode;
|
||||
const defaultValue = canUseScryptedParser && !this.getLastH264Oddities() ?
|
||||
SCRYPTED_PARSER_TCP : FFMPEG_PARSER_TCP;
|
||||
const parser = this.getParser(this.advertisedMediaStreamOptions);
|
||||
const defaultValue = parser.parser;
|
||||
|
||||
const scryptedOptions = canUseScryptedParser ? [
|
||||
const scryptedOptions = [
|
||||
SCRYPTED_PARSER_TCP,
|
||||
SCRYPTED_PARSER_UDP,
|
||||
] : [];
|
||||
];
|
||||
|
||||
const currentParser = this.storage.getItem(this.rtspParserKey) || STRING_DEFAULT;
|
||||
const currentParser = parser.isDefault ? STRING_DEFAULT : parser.parser;
|
||||
|
||||
settings.push(
|
||||
{
|
||||
@@ -397,14 +360,9 @@ class PrebufferSession {
|
||||
}
|
||||
);
|
||||
|
||||
if (!(currentParser === STRING_DEFAULT ? defaultValue : currentParser).includes('Scrypted')) {
|
||||
usingFFmpeg = true;
|
||||
}
|
||||
usingFFmpeg = !parser.parser.includes('Scrypted');
|
||||
}
|
||||
|
||||
if (muxingMp4) {
|
||||
addFFmpegAudioSettings();
|
||||
}
|
||||
if (usingFFmpeg) {
|
||||
addFFmpegInputSettings();
|
||||
}
|
||||
@@ -475,26 +433,24 @@ class PrebufferSession {
|
||||
addOddities();
|
||||
}
|
||||
|
||||
if (rtspMode) {
|
||||
settings.push({
|
||||
group,
|
||||
subgroup,
|
||||
key: 'rtspRebroadcastUrl',
|
||||
title: 'RTSP Rebroadcast Url',
|
||||
description: 'The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.',
|
||||
readonly: true,
|
||||
value: `rtsp://localhost:${this.mixin.streamSettings.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`,
|
||||
});
|
||||
settings.push({
|
||||
group,
|
||||
subgroup,
|
||||
key: 'rtspRebroadcastMutedUrl',
|
||||
title: 'RTSP Rebroadcast Url (Muted)',
|
||||
description: 'The RTSP URL of the muted rebroadcast stream. Substitute localhost as appropriate.',
|
||||
readonly: true,
|
||||
value: `rtsp://localhost:${this.mixin.streamSettings.storageSettings.values.rebroadcastPort}/${this.rtspServerMutedPath}`,
|
||||
});
|
||||
}
|
||||
settings.push({
|
||||
group,
|
||||
subgroup,
|
||||
key: 'rtspRebroadcastUrl',
|
||||
title: 'RTSP Rebroadcast Url',
|
||||
description: 'The RTSP URL of the rebroadcast stream. Substitute localhost as appropriate.',
|
||||
readonly: true,
|
||||
value: `rtsp://localhost:${this.mixin.streamSettings.storageSettings.values.rebroadcastPort}/${this.rtspServerPath}`,
|
||||
});
|
||||
settings.push({
|
||||
group,
|
||||
subgroup,
|
||||
key: 'rtspRebroadcastMutedUrl',
|
||||
title: 'RTSP Rebroadcast Url (Muted)',
|
||||
description: 'The RTSP URL of the muted rebroadcast stream. Substitute localhost as appropriate.',
|
||||
readonly: true,
|
||||
value: `rtsp://localhost:${this.mixin.streamSettings.storageSettings.values.rebroadcastPort}/${this.rtspServerMutedPath}`,
|
||||
});
|
||||
|
||||
return settings;
|
||||
}
|
||||
@@ -519,24 +475,10 @@ class PrebufferSession {
|
||||
|
||||
const { isUsingDefaultAudioConfig, aacAudio, compatibleAudio, reencodeAudio } = this.getAudioConfig();
|
||||
|
||||
const { rtspMode, muxingMp4 } = this.getRebroadcastContainer();
|
||||
|
||||
let detectedAudioCodec = this.storage.getItem(this.lastDetectedAudioCodecKey) || undefined;
|
||||
if (detectedAudioCodec === 'null')
|
||||
detectedAudioCodec = null;
|
||||
|
||||
// only need to probe the audio under specific circumstances.
|
||||
// rtsp only mode (ie, no mp4 mux) does not need probing.
|
||||
let probingAudioCodec = false;
|
||||
if (muxingMp4
|
||||
&& !audioSoftMuted
|
||||
&& !advertisedAudioCodec
|
||||
&& isUsingDefaultAudioConfig
|
||||
&& detectedAudioCodec === undefined) {
|
||||
this.console.warn('Camera did not report an audio codec, muting the audio stream and probing the codec.');
|
||||
probingAudioCodec = true;
|
||||
}
|
||||
|
||||
// the assumed audio codec is the detected codec first and the reported codec otherwise.
|
||||
const assumedAudioCodec = detectedAudioCodec === undefined
|
||||
? advertisedAudioCodec?.toLowerCase()
|
||||
@@ -546,24 +488,7 @@ class PrebufferSession {
|
||||
// after probing the audio codec is complete, alert the user with appropriate instructions.
|
||||
// assume the codec is user configurable unless the camera explictly reports otherwise.
|
||||
const audioIncompatible = !COMPATIBLE_AUDIO_CODECS.includes(assumedAudioCodec);
|
||||
if (muxingMp4 && !probingAudioCodec && mso?.userConfigurable !== false && !audioSoftMuted) {
|
||||
if (audioIncompatible) {
|
||||
// show an alert that rebroadcast needs an explicit setting by the user.
|
||||
if (isUsingDefaultAudioConfig) {
|
||||
log.a(`${this.mixin.name} is using the ${assumedAudioCodec} audio codec. Configuring your Camera to use Opus, PCM, or AAC audio is recommended. If this is not possible, Select 'Transcode Audio' in the camera stream's Rebroadcast settings to suppress this alert.`);
|
||||
}
|
||||
this.console.warn('Configure your camera to output Opus, PCM, or AAC audio. Suboptimal audio codec in use:', assumedAudioCodec);
|
||||
}
|
||||
else if (!audioSoftMuted && isUsingDefaultAudioConfig && advertisedAudioCodec === undefined && detectedAudioCodec !== undefined) {
|
||||
// handling compatible codecs that were unspecified...
|
||||
// if (detectedAudioCodec === 'aac') {
|
||||
// log.a(`${this.mixin.name} did not report a codec and ${detectedAudioCodec} was found during probe. Select '${AAC_AUDIO}' in the camera stream's Rebroadcast settings to suppress this alert and improve startup time.`);
|
||||
// }
|
||||
// else {
|
||||
// log.a(`${this.mixin.name} did not report a codec and ${detectedAudioCodec} was found during probe. Select '${COMPATIBLE_AUDIO}' in the camera stream's Rebroadcast settings to suppress this alert and improve startup time.`);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// aac needs to have the adts header stripped for mpegts and mp4.
|
||||
// use this filter sparingly as it prevents ffmpeg from starting on a mismatch.
|
||||
@@ -582,15 +507,9 @@ class PrebufferSession {
|
||||
// enable transcoding by default. however, still allow the user to change the settings
|
||||
// in case something changed.
|
||||
let mustTranscode = false;
|
||||
if (muxingMp4 && !probingAudioCodec && isUsingDefaultAudioConfig && audioIncompatible) {
|
||||
if (mso?.userConfigurable === false)
|
||||
this.console.log('camera reports it is not user configurable. transcoding due to incompatible codec', assumedAudioCodec);
|
||||
else
|
||||
this.console.log('camera audio transcoding due to incompatible codec. configure the camera to use a compatible codec if possible.');
|
||||
mustTranscode = true;
|
||||
}
|
||||
|
||||
if (audioSoftMuted || probingAudioCodec) {
|
||||
|
||||
if (audioSoftMuted) {
|
||||
// no audio? explicitly disable it.
|
||||
acodec = ['-an'];
|
||||
this.audioDisabled = true;
|
||||
@@ -663,29 +582,14 @@ class PrebufferSession {
|
||||
};
|
||||
this.parsers = rbo.parsers;
|
||||
|
||||
this.console.log('rebroadcast mode:', rtspMode ? 'rtsp' : 'mpegts');
|
||||
if (!rtspMode) {
|
||||
rbo.parsers.mpegts = createMpegTsParser({
|
||||
vcodec,
|
||||
acodec,
|
||||
});
|
||||
}
|
||||
else {
|
||||
const parser = createRtspParser({
|
||||
vcodec,
|
||||
// the rtsp parser should always stream copy unless audio is soft muted.
|
||||
acodec: audioSoftMuted ? acodec : ['-acodec', 'copy'],
|
||||
});
|
||||
this.sdp = parser.sdp;
|
||||
rbo.parsers.rtsp = parser;
|
||||
}
|
||||
|
||||
if (muxingMp4) {
|
||||
rbo.parsers.mp4 = createFragmentedMp4Parser({
|
||||
vcodec,
|
||||
acodec,
|
||||
});
|
||||
}
|
||||
const parser = createRtspParser({
|
||||
vcodec,
|
||||
// the rtsp parser should always stream copy unless audio is soft muted.
|
||||
acodec: audioSoftMuted ? acodec : ['-acodec', 'copy'],
|
||||
});
|
||||
this.sdp = parser.sdp;
|
||||
rbo.parsers.rtsp = parser;
|
||||
|
||||
const mo = await this.mixinDevice.getVideoStream(mso);
|
||||
const isRfc4571 = mo.mimeType === 'x-scrypted/x-rfc4571';
|
||||
@@ -700,7 +604,7 @@ class PrebufferSession {
|
||||
|
||||
const h264Oddities = this.getLastH264Oddities();
|
||||
|
||||
if (rtspMode && isRfc4571) {
|
||||
if (isRfc4571) {
|
||||
this.usingScryptedParser = true;
|
||||
this.console.log('bypassing ffmpeg: using scrypted rfc4571 parser')
|
||||
const json = await mediaManager.convertMediaObjectToJSON<any>(mo, 'x-scrypted/x-rfc4571');
|
||||
@@ -714,7 +618,7 @@ class PrebufferSession {
|
||||
const ffmpegInput = JSON.parse(moBuffer.toString()) as FFmpegInput;
|
||||
sessionMso = ffmpegInput.mediaStreamOptions || this.advertisedMediaStreamOptions;
|
||||
|
||||
let { parser, isDefault } = this.getParser(rtspMode, sessionMso);
|
||||
let { parser, isDefault } = this.getParser(sessionMso);
|
||||
this.usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP;
|
||||
this.usingScryptedUdpParser = parser === SCRYPTED_PARSER_UDP;
|
||||
|
||||
@@ -771,7 +675,7 @@ class PrebufferSession {
|
||||
const oddity = hasOddities(h264Probe);
|
||||
if (oddity && !reportedOddity) {
|
||||
reportedOddity = true;
|
||||
let { isDefault } = this.getParser(rtspMode, sessionMso);
|
||||
let { isDefault } = this.getParser(sessionMso);
|
||||
this.console.warn('H264 oddity detected.');
|
||||
if (!isDefault) {
|
||||
this.console.warn('If there are issues streaming, consider using the Default parser.');
|
||||
@@ -837,12 +741,6 @@ class PrebufferSession {
|
||||
this.console.error(`Video codec is not h264. If there are errors, try changing your camera's encoder output.`);
|
||||
}
|
||||
|
||||
if (probingAudioCodec) {
|
||||
this.console.warn('Audio probe complete, ending rebroadcast session and restarting with detected codecs.');
|
||||
session.kill(new Error('audio probe completed, restarting'));
|
||||
return this.startPrebufferSession();
|
||||
}
|
||||
|
||||
this.parserSession = session;
|
||||
session.killed.finally(() => {
|
||||
if (this.parserSession === session)
|
||||
@@ -934,6 +832,46 @@ class PrebufferSession {
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
handleChargingBatteryEvents() {
|
||||
if (!this.mixin.interfaces.includes(ScryptedInterface.Charger) ||
|
||||
!this.mixin.interfaces.includes(ScryptedInterface.Battery)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const checkDisablePrebuffer = async () => {
|
||||
if (this.stopInactive) {
|
||||
this.console.log(this.streamName, 'low battery or not charging, prebuffering and rebroadcasting will only work on demand')
|
||||
if (!this.activeClients && this.parserSessionPromise) {
|
||||
this.console.log(this.streamName, 'terminating rebroadcast due to low battery or not charging')
|
||||
const session = await this.parserSessionPromise;
|
||||
session.kill(new Error('low battery or not charging'));
|
||||
}
|
||||
} else {
|
||||
this.ensurePrebufferSession();
|
||||
}
|
||||
}
|
||||
|
||||
const id = this.mixin.id;
|
||||
if (!this.batteryListener) {
|
||||
this.batteryListener = systemManager.listenDevice(id, ScryptedInterface.Battery, () => checkDisablePrebuffer());
|
||||
}
|
||||
if (!this.chargerListener) {
|
||||
this.chargerListener = systemManager.listenDevice(id, ScryptedInterface.Charger, () => checkDisablePrebuffer());
|
||||
}
|
||||
}
|
||||
|
||||
shouldDisableBatteryPrebuffer(): boolean | null {
|
||||
if (!this.mixin.interfaces.includes(ScryptedInterface.Battery)) {
|
||||
return null;
|
||||
}
|
||||
if (this.forceBatteryPrebuffer) {
|
||||
return false;
|
||||
}
|
||||
const lowBattery = this.mixin.batteryLevel == null || this.mixin.batteryLevel < 20;
|
||||
const hasCharger = this.mixin.interfaces.includes(ScryptedInterface.Charger);
|
||||
return !hasCharger || lowBattery || this.mixin.chargeState !== ChargeState.Charging;
|
||||
}
|
||||
|
||||
async handleRebroadcasterClient(options: {
|
||||
findSyncFrame: boolean,
|
||||
isActiveClient: boolean,
|
||||
@@ -1062,11 +1000,6 @@ class PrebufferSession {
|
||||
requestedPrebuffer = Math.min(defaultPrebuffer, this.getDetectedIdrInterval() || defaultPrebuffer);;
|
||||
}
|
||||
|
||||
const { rtspMode, muxingMp4 } = this.getRebroadcastContainer();
|
||||
const defaultContainer = rtspMode ? 'rtsp' : 'mpegts';
|
||||
|
||||
let container: PrebufferParsers = this.parsers[options?.container] ? options?.container as PrebufferParsers : defaultContainer;
|
||||
|
||||
const mediaStreamOptions: ResponseMediaStreamOptions = session.negotiateMediaStream(options);
|
||||
let sdp = await this.sdp;
|
||||
if (!mediaStreamOptions.video?.h264Info && this.usingScryptedParser) {
|
||||
@@ -1082,100 +1015,94 @@ class PrebufferSession {
|
||||
const interleavedMap = new Map<string, number>();
|
||||
const serverPortMap = new Map<string, RtspTrack>();
|
||||
let server: FileRtspServer;
|
||||
const parsedSdp = parseSdp(sdp);
|
||||
const videoSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.video?.codec) || parsedSdp.msections.find(msection => msection.type === 'video');
|
||||
let audioSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.audio?.codec) || parsedSdp.msections.find(msection => msection.type === 'audio');
|
||||
if (mediaStreamOptions.audio === null)
|
||||
audioSection = undefined;
|
||||
parsedSdp.msections = parsedSdp.msections.filter(msection => msection === videoSection || msection === audioSection);
|
||||
const filterPrebufferAudio = options?.prebuffer === undefined;
|
||||
const videoCodec = parsedSdp.msections.find(msection => msection.type === 'video')?.codec;
|
||||
sdp = parsedSdp.toSdp();
|
||||
filter = (chunk, prebuffer) => {
|
||||
// if no prebuffer is explicitly requested, don't send prebuffer audio
|
||||
if (prebuffer && filterPrebufferAudio && chunk.type !== videoCodec)
|
||||
return;
|
||||
|
||||
if (container === 'rtsp') {
|
||||
const parsedSdp = parseSdp(sdp);
|
||||
const videoSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.video?.codec) || parsedSdp.msections.find(msection => msection.type === 'video');
|
||||
let audioSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.audio?.codec) || parsedSdp.msections.find(msection => msection.type === 'audio');
|
||||
if (mediaStreamOptions.audio === null)
|
||||
audioSection = undefined;
|
||||
parsedSdp.msections = parsedSdp.msections.filter(msection => msection === videoSection || msection === audioSection);
|
||||
const filterPrebufferAudio = options?.prebuffer === undefined;
|
||||
const videoCodec = parsedSdp.msections.find(msection => msection.type === 'video')?.codec;
|
||||
sdp = parsedSdp.toSdp();
|
||||
filter = (chunk, prebuffer) => {
|
||||
// if no prebuffer is explicitly requested, don't send prebuffer audio
|
||||
if (prebuffer && filterPrebufferAudio && chunk.type !== videoCodec)
|
||||
return;
|
||||
|
||||
const channel = interleavedMap.get(chunk.type);
|
||||
if (!interleavePassthrough) {
|
||||
if (channel == undefined) {
|
||||
const udp = serverPortMap.get(chunk.type);
|
||||
if (udp)
|
||||
server.sendTrack(udp.control, chunk.chunks[1], chunk.type.startsWith('rtcp-'));
|
||||
return;
|
||||
}
|
||||
|
||||
const chunks = chunk.chunks.slice();
|
||||
const header = Buffer.from(chunks[0]);
|
||||
header.writeUInt8(channel, 1);
|
||||
chunks[0] = header;
|
||||
chunk = {
|
||||
startStream: chunk.startStream,
|
||||
chunks,
|
||||
}
|
||||
}
|
||||
else if (channel === undefined) {
|
||||
const channel = interleavedMap.get(chunk.type);
|
||||
if (!interleavePassthrough) {
|
||||
if (channel == undefined) {
|
||||
const udp = serverPortMap.get(chunk.type);
|
||||
if (udp)
|
||||
server.sendTrack(udp.control, chunk.chunks[1], chunk.type.startsWith('rtcp-'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.writeStream) {
|
||||
server.writeRtpPayload(chunk.chunks[0], chunk.chunks[1]);
|
||||
return;
|
||||
const chunks = chunk.chunks.slice();
|
||||
const header = Buffer.from(chunks[0]);
|
||||
header.writeUInt8(channel, 1);
|
||||
chunks[0] = header;
|
||||
chunk = {
|
||||
startStream: chunk.startStream,
|
||||
chunks,
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
else if (channel === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hostname = options?.route === 'internal' ? undefined : '0.0.0.0';
|
||||
|
||||
const clientPromise = await listenSingleRtspClient({
|
||||
hostname,
|
||||
createServer: duplex => {
|
||||
sdp = addTrackControls(sdp);
|
||||
server = new FileRtspServer(duplex, sdp);
|
||||
server.writeConsole = this.console;
|
||||
return server;
|
||||
}
|
||||
});
|
||||
|
||||
socketPromise = clientPromise.rtspServerPromise.then(async server => {
|
||||
if (session.parserSpecific) {
|
||||
const parserSpecific = session.parserSpecific as RtspSessionParserSpecific;
|
||||
server.resolveInterleaved = msection => {
|
||||
const channel = parserSpecific.interleaved.get(msection.codec);
|
||||
return [channel, channel + 1];
|
||||
}
|
||||
}
|
||||
// server.console = this.console;
|
||||
await server.handlePlayback();
|
||||
server.handleTeardown().catch(() => {}).finally(() => server.client.destroy());
|
||||
for (const track of Object.values(server.setupTracks)) {
|
||||
if (track.protocol === 'udp') {
|
||||
serverPortMap.set(track.codec, track);
|
||||
serverPortMap.set(`rtcp-${track.codec}`, track);
|
||||
continue;
|
||||
}
|
||||
interleavedMap.set(track.codec, track.destination);
|
||||
interleavedMap.set(`rtcp-${track.codec}`, track.destination + 1);
|
||||
}
|
||||
|
||||
interleavePassthrough = session.parserSpecific && serverPortMap.size === 0;
|
||||
return server.client;
|
||||
})
|
||||
|
||||
url = clientPromise.url;
|
||||
if (hostname) {
|
||||
urls = await getUrlLocalAdresses(this.console, url);
|
||||
if (server.writeStream) {
|
||||
server.writeRtpPayload(chunk.chunks[0], chunk.chunks[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
else {
|
||||
const client = await listenZeroSingleClient();
|
||||
socketPromise = client.clientPromise;
|
||||
url = client.url;
|
||||
|
||||
const hostname = options?.route === 'internal' ? undefined : '0.0.0.0';
|
||||
|
||||
const clientPromise = await listenSingleRtspClient({
|
||||
hostname,
|
||||
createServer: duplex => {
|
||||
sdp = addTrackControls(sdp);
|
||||
server = new FileRtspServer(duplex, sdp);
|
||||
server.writeConsole = this.console;
|
||||
return server;
|
||||
}
|
||||
});
|
||||
|
||||
socketPromise = clientPromise.rtspServerPromise.then(async server => {
|
||||
if (session.parserSpecific) {
|
||||
const parserSpecific = session.parserSpecific as RtspSessionParserSpecific;
|
||||
server.resolveInterleaved = msection => {
|
||||
const channel = parserSpecific.interleaved.get(msection.codec);
|
||||
return [channel, channel + 1];
|
||||
}
|
||||
}
|
||||
// server.console = this.console;
|
||||
await server.handlePlayback();
|
||||
server.handleTeardown().catch(() => { }).finally(() => server.client.destroy());
|
||||
for (const track of Object.values(server.setupTracks)) {
|
||||
if (track.protocol === 'udp') {
|
||||
serverPortMap.set(track.codec, track);
|
||||
serverPortMap.set(`rtcp-${track.codec}`, track);
|
||||
continue;
|
||||
}
|
||||
interleavedMap.set(track.codec, track.destination);
|
||||
interleavedMap.set(`rtcp-${track.codec}`, track.destination + 1);
|
||||
}
|
||||
|
||||
interleavePassthrough = session.parserSpecific && serverPortMap.size === 0;
|
||||
return server.client;
|
||||
})
|
||||
|
||||
url = clientPromise.url;
|
||||
if (hostname) {
|
||||
urls = await getUrlLocalAdresses(this.console, url);
|
||||
}
|
||||
|
||||
const container = 'rtsp';
|
||||
|
||||
mediaStreamOptions.sdp = sdp;
|
||||
|
||||
const isActiveClient = options?.refresh !== false;
|
||||
@@ -1197,7 +1124,7 @@ class PrebufferSession {
|
||||
if (this.audioDisabled) {
|
||||
mediaStreamOptions.audio = null;
|
||||
}
|
||||
else if (reencodeAudio && muxingMp4) {
|
||||
else if (reencodeAudio) {
|
||||
mediaStreamOptions.audio = {
|
||||
codec: 'aac',
|
||||
encoder: 'aac',
|
||||
@@ -1497,8 +1424,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
||||
}
|
||||
}
|
||||
|
||||
const isBatteryPowered = this.mixinDeviceInterfaces.includes(ScryptedInterface.Battery);
|
||||
|
||||
if (!enabledIds.length)
|
||||
this.online = true;
|
||||
|
||||
@@ -1524,22 +1449,27 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
||||
}
|
||||
const name = mso?.name;
|
||||
const enabled = enabledIds.includes(id);
|
||||
const stopInactive = (isBatteryPowered && !mso.allowBatteryPrebuffer) || !enabled;
|
||||
session = new PrebufferSession(this, mso, stopInactive);
|
||||
session = new PrebufferSession(this, mso, enabled, mso.allowBatteryPrebuffer);
|
||||
this.sessions.set(id, session);
|
||||
|
||||
if (isBatteryPowered && !mso.allowBatteryPrebuffer) {
|
||||
this.console.log('camera is battery powered, prebuffering and rebroadcasting will only work on demand.');
|
||||
if (!enabled) {
|
||||
this.console.log('stream', name, 'is not enabled and will be rebroadcast on demand.');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
this.console.log('stream', name, 'will be rebroadcast on demand.');
|
||||
continue;
|
||||
if (session.shouldDisableBatteryPrebuffer()) {
|
||||
this.console.log('camera is battery powered and either not charging or on low battery, prebuffering and rebroadcasting will only work on demand.');
|
||||
}
|
||||
|
||||
(async () => {
|
||||
while (this.sessions.get(id) === session && !this.released) {
|
||||
if (session.shouldDisableBatteryPrebuffer()) {
|
||||
// since battery devices could be eligible for prebuffer, check periodically
|
||||
// in the event the battery device becomes eligible again
|
||||
await new Promise(resolve => setTimeout(resolve, 60000));
|
||||
continue;
|
||||
}
|
||||
|
||||
session.ensurePrebufferSession();
|
||||
let wasActive = false;
|
||||
try {
|
||||
|
||||
@@ -121,7 +121,7 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
|
||||
console.log('parsed sdp sps', parsedSps);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('sdp sps parsing failed');
|
||||
console.warn('sdp sps parsing failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
plugins/python-codecs/package-lock.json
generated
4
plugins/python-codecs/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/python-codecs",
|
||||
"version": "0.1.35",
|
||||
"version": "0.1.47",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/python-codecs",
|
||||
"version": "0.1.35",
|
||||
"version": "0.1.47",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/python-codecs",
|
||||
"version": "0.1.35",
|
||||
"version": "0.1.47",
|
||||
"description": "Python Codecs for Scrypted",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
|
||||
@@ -14,16 +14,8 @@ try:
|
||||
except:
|
||||
pass
|
||||
|
||||
class Callback:
|
||||
def __init__(self, callback) -> None:
|
||||
if callback:
|
||||
self.loop = asyncio.get_running_loop()
|
||||
self.callback = callback
|
||||
else:
|
||||
self.loop = None
|
||||
self.callback = None
|
||||
|
||||
def createPipelineIterator(pipeline: str):
|
||||
async def createPipelineIterator(pipeline: str):
|
||||
loop = asyncio.get_running_loop()
|
||||
pipeline = '{pipeline} ! queue leaky=downstream max-size-buffers=0 ! appsink name=appsink emit-signals=true sync=false max-buffers=-1 drop=true'.format(pipeline=pipeline)
|
||||
print(pipeline)
|
||||
gst = Gst.parse_launch(pipeline)
|
||||
@@ -33,10 +25,13 @@ def createPipelineIterator(pipeline: str):
|
||||
t = str(message.type)
|
||||
# print(t)
|
||||
if t == str(Gst.MessageType.EOS):
|
||||
print('EOS: Stream ended.')
|
||||
finish()
|
||||
elif t == str(Gst.MessageType.WARNING):
|
||||
err, debug = message.parse_warning()
|
||||
print('Warning: %s: %s\n' % (err, debug))
|
||||
print('Ending stream due to warning. If this camera is causing errors, switch to the libav decoder.');
|
||||
finish();
|
||||
elif t == str(Gst.MessageType.ERROR):
|
||||
err, debug = message.parse_error()
|
||||
print('Error: %s: %s\n' % (err, debug))
|
||||
@@ -50,10 +45,8 @@ def createPipelineIterator(pipeline: str):
|
||||
def finish():
|
||||
nonlocal hasFinished
|
||||
hasFinished = True
|
||||
callback = Callback(None)
|
||||
callbackQueue.put(callback)
|
||||
if not asyncFuture.done():
|
||||
asyncFuture.set_result(None)
|
||||
yieldQueue.put(None)
|
||||
asyncio.run_coroutine_threadsafe(sampleQueue.put(None), loop = loop)
|
||||
if not finished.done():
|
||||
finished.set_result(None)
|
||||
|
||||
@@ -65,30 +58,22 @@ def createPipelineIterator(pipeline: str):
|
||||
hasFinished = False
|
||||
|
||||
appsink = gst.get_by_name('appsink')
|
||||
callbackQueue = Queue()
|
||||
asyncFuture = asyncio.Future()
|
||||
yieldQueue = Queue()
|
||||
sampleQueue = asyncio.Queue()
|
||||
|
||||
async def gen():
|
||||
try:
|
||||
while True:
|
||||
nonlocal asyncFuture
|
||||
asyncFuture = asyncio.Future()
|
||||
yieldFuture = asyncio.Future()
|
||||
async def asyncCallback(sample):
|
||||
asyncFuture.set_result(sample)
|
||||
await yieldFuture
|
||||
callbackQueue.put(Callback(asyncCallback))
|
||||
sample = await asyncFuture
|
||||
if not sample:
|
||||
yieldFuture.set_result(None)
|
||||
break
|
||||
try:
|
||||
sample = await sampleQueue.get()
|
||||
if not sample:
|
||||
break
|
||||
yield sample
|
||||
finally:
|
||||
yieldFuture.set_result(None)
|
||||
yieldQueue.put(None)
|
||||
finally:
|
||||
finish()
|
||||
print('gstreamer finished')
|
||||
finish()
|
||||
|
||||
|
||||
def on_new_sample(sink, preroll):
|
||||
@@ -96,16 +81,12 @@ def createPipelineIterator(pipeline: str):
|
||||
|
||||
sample = sink.emit('pull-preroll' if preroll else 'pull-sample')
|
||||
|
||||
callback: Callback = callbackQueue.get()
|
||||
if not callback.callback or hasFinished:
|
||||
hasFinished = True
|
||||
if callback.callback:
|
||||
asyncio.run_coroutine_threadsafe(callback.callback(None), loop = callback.loop)
|
||||
if hasFinished:
|
||||
return Gst.FlowReturn.OK
|
||||
|
||||
future = asyncio.run_coroutine_threadsafe(callback.callback(sample), loop = callback.loop)
|
||||
asyncio.run_coroutine_threadsafe(sampleQueue.put(sample), loop = loop)
|
||||
try:
|
||||
future.result()
|
||||
yieldQueue.get()
|
||||
except:
|
||||
pass
|
||||
return Gst.FlowReturn.OK
|
||||
|
||||
@@ -80,7 +80,7 @@ async def generateVideoFramesGstreamer(mediaObject: scrypted_sdk.MediaObject, op
|
||||
|
||||
videosrc += ' ! {decoder} ! queue leaky=downstream max-size-buffers=0 ! videoconvert ! {videocaps}'.format(decoder=decoder, videocaps=videocaps)
|
||||
|
||||
gst, gen = createPipelineIterator(videosrc)
|
||||
gst, gen = await createPipelineIterator(videosrc)
|
||||
async for gstsample in gen():
|
||||
caps = gstsample.get_caps()
|
||||
height = caps.get_structure(0).get_value('height')
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import time
|
||||
from gst_generator import createPipelineIterator
|
||||
import scrypted_sdk
|
||||
from typing import Any
|
||||
import vipsimage
|
||||
|
||||
@@ -2,6 +2,7 @@ import scrypted_sdk
|
||||
from typing import Any
|
||||
from thread import to_thread
|
||||
import io
|
||||
import time
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -34,7 +35,16 @@ class PILImage(scrypted_sdk.VideoFrame):
|
||||
finally:
|
||||
rgb.close()
|
||||
return await to_thread(format)
|
||||
# TODO: gray...
|
||||
elif options['format'] == 'gray':
|
||||
def format():
|
||||
if pilImage.pilImage.mode == 'L':
|
||||
return pilImage.pilImage.tobytes()
|
||||
l = pilImage.pilImage.convert('L')
|
||||
try:
|
||||
return l.tobytes()
|
||||
finally:
|
||||
l.close()
|
||||
return await to_thread(format)
|
||||
|
||||
def save():
|
||||
bytesArray = io.BytesIO()
|
||||
@@ -81,6 +91,7 @@ def toPILImage(pilImageWrapper: PILImage, options: scrypted_sdk.ImageOptions = N
|
||||
|
||||
async def createPILMediaObject(image: PILImage):
|
||||
ret = await scrypted_sdk.mediaManager.createMediaObject(image, scrypted_sdk.ScryptedMimeTypes.Image.value, {
|
||||
'timestamp': time.time() * 1000,
|
||||
'format': None,
|
||||
'width': image.width,
|
||||
'height': image.height,
|
||||
|
||||
@@ -7,6 +7,7 @@ except:
|
||||
Image = None
|
||||
pyvips = None
|
||||
from thread import to_thread
|
||||
import time
|
||||
|
||||
class VipsImage(scrypted_sdk.VideoFrame):
|
||||
def __init__(self, vipsImage: Image) -> None:
|
||||
@@ -90,6 +91,7 @@ def toVipsImage(vipsImageWrapper: VipsImage, options: scrypted_sdk.ImageOptions
|
||||
|
||||
async def createVipsMediaObject(image: VipsImage):
|
||||
ret = await scrypted_sdk.mediaManager.createMediaObject(image, scrypted_sdk.ScryptedMimeTypes.Image.value, {
|
||||
'timestamp': time.time() * 1000,
|
||||
'format': None,
|
||||
'width': image.width,
|
||||
'height': image.height,
|
||||
|
||||
4
plugins/tensorflow-lite/package-lock.json
generated
4
plugins/tensorflow-lite/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.14",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.14",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -37,12 +37,11 @@
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"Settings",
|
||||
"BufferConverter",
|
||||
"ObjectDetection"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.12"
|
||||
"version": "0.1.14"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ from typing import Any, Tuple
|
||||
|
||||
import scrypted_sdk
|
||||
from scrypted_sdk.types import (MediaObject, ObjectDetection,
|
||||
ObjectDetectionCallbacks,
|
||||
ObjectDetectionGeneratorSession,
|
||||
ObjectDetectionModel, ObjectDetectionSession,
|
||||
ObjectsDetected, ScryptedMimeTypes, Setting)
|
||||
@@ -68,10 +67,10 @@ class DetectPlugin(scrypted_sdk.ScryptedDeviceBase, ObjectDetection):
|
||||
except:
|
||||
pass
|
||||
|
||||
async def detectObjects(self, mediaObject: MediaObject, session: ObjectDetectionSession = None, callbacks: ObjectDetectionCallbacks = None) -> ObjectsDetected:
|
||||
async def detectObjects(self, mediaObject: MediaObject, session: ObjectDetectionSession = None) -> ObjectsDetected:
|
||||
vf: scrypted_sdk.VideoFrame
|
||||
if mediaObject and mediaObject.mimeType == ScryptedMimeTypes.Image.value:
|
||||
vf = mediaObject
|
||||
vf = await scrypted_sdk.sdk.connectRPCObject(mediaObject)
|
||||
else:
|
||||
vf = await scrypted_sdk.mediaManager.convertMediaObjectToBuffer(mediaObject, ScryptedMimeTypes.Image.value)
|
||||
|
||||
|
||||
@@ -110,14 +110,9 @@ class Prediction:
|
||||
class PredictPlugin(DetectPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
labels: dict
|
||||
|
||||
def __init__(self, PLUGIN_MIME_TYPE: str, nativeId: str | None = None):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
self.fromMimeType = PLUGIN_MIME_TYPE
|
||||
self.toMimeType = scrypted_sdk.ScryptedMimeTypes.MediaObject.value
|
||||
|
||||
self.crop = False
|
||||
|
||||
# periodic restart because there seems to be leaks in tflite or coral API.
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.call_later(4 * 60 * 60, lambda: self.requestRestart())
|
||||
@@ -205,7 +200,7 @@ class PredictPlugin(DetectPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Set
|
||||
# image is already correct aspect ratio, so it can be processed in a single pass.
|
||||
if input_aspect_ratio == src_aspect_ratio:
|
||||
def cvss(point):
|
||||
return point[0], point[1]
|
||||
return point[0] / s, point[1] / s
|
||||
|
||||
# aspect ratio matches, but image must be scaled.
|
||||
resize = None
|
||||
|
||||
@@ -32,12 +32,9 @@ def parse_label_contents(contents: str):
|
||||
ret[row_number] = content.strip()
|
||||
return ret
|
||||
|
||||
|
||||
MIME_TYPE = 'x-scrypted-tensorflow-lite/x-raw-image'
|
||||
|
||||
class TensorFlowLitePlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(MIME_TYPE, nativeId=nativeId)
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
tfliteFile = self.downloadFile('https://raw.githubusercontent.com/google-coral/test_data/master/ssd_mobilenet_v2_coco_quant_postprocess.tflite', 'ssd_mobilenet_v2_coco_quant_postprocess.tflite')
|
||||
edgetpuFile = self.downloadFile('https://raw.githubusercontent.com/google-coral/test_data/master/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite', 'ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite')
|
||||
|
||||
4
plugins/tensorflow/package-lock.json
generated
4
plugins/tensorflow/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.14",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/tensorflow-lite",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.14",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -34,12 +34,11 @@
|
||||
"type": "API",
|
||||
"interfaces": [
|
||||
"Settings",
|
||||
"BufferConverter",
|
||||
"ObjectDetection"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.1.12"
|
||||
"version": "0.1.14"
|
||||
}
|
||||
|
||||
@@ -21,12 +21,9 @@ def parse_label_contents(contents: str):
|
||||
ret[row_number] = content.strip()
|
||||
return ret
|
||||
|
||||
|
||||
MIME_TYPE = 'x-scrypted-tensorflow/x-raw-image'
|
||||
|
||||
class TensorFlowPlugin(PredictPlugin, scrypted_sdk.BufferConverter, scrypted_sdk.Settings):
|
||||
def __init__(self, nativeId: str | None = None):
|
||||
super().__init__(MIME_TYPE, nativeId=nativeId)
|
||||
super().__init__(nativeId=nativeId)
|
||||
|
||||
modelPath = os.path.join(os.environ['SCRYPTED_PLUGIN_VOLUME'], 'zip', 'unzipped', 'fs')
|
||||
self.model = tf.saved_model.load(modelPath)
|
||||
|
||||
4
plugins/webrtc/package-lock.json
generated
4
plugins/webrtc/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.1.42",
|
||||
"version": "0.1.44",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.1.42",
|
||||
"version": "0.1.44",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.1.42",
|
||||
"version": "0.1.44",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
|
||||
@@ -198,12 +198,6 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
useIPv6: {
|
||||
title: 'Use IPv6',
|
||||
description: 'Use IPv6 addresses when connecting. This is disabled by default due to commonly misconfigured IPv6 local networks.',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
activeConnections: {
|
||||
readonly: true,
|
||||
title: "Current Open Connections",
|
||||
@@ -449,7 +443,6 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
|
||||
}
|
||||
|
||||
return {
|
||||
iceUseIpv6: false,
|
||||
iceServers,
|
||||
iceInterfaceAddresses,
|
||||
...ret,
|
||||
|
||||
521
sdk/package-lock.json
generated
521
sdk/package-lock.json
generated
@@ -562,9 +562,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
|
||||
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
@@ -576,12 +576,12 @@
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
|
||||
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
|
||||
"version": "0.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
|
||||
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
"@jridgewell/resolve-uri": "3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@polka/url": {
|
||||
@@ -632,9 +632,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "0.0.51",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
|
||||
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ=="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
|
||||
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.9",
|
||||
@@ -653,133 +653,133 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||
"integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz",
|
||||
"integrity": "sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1"
|
||||
"@webassemblyjs/helper-numbers": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
|
||||
"integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz",
|
||||
"integrity": "sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
|
||||
"integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz",
|
||||
"integrity": "sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
|
||||
"integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz",
|
||||
"integrity": "sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
|
||||
"integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz",
|
||||
"integrity": "sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.1",
|
||||
"@webassemblyjs/helper-api-error": "1.11.1",
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.5",
|
||||
"@webassemblyjs/helper-api-error": "1.11.5",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
|
||||
"integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz",
|
||||
"integrity": "sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
|
||||
"integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz",
|
||||
"integrity": "sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-buffer": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/wasm-gen": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-buffer": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/wasm-gen": "1.11.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ieee754": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
|
||||
"integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz",
|
||||
"integrity": "sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==",
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/leb128": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
|
||||
"integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz",
|
||||
"integrity": "sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==",
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/utf8": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
|
||||
"integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz",
|
||||
"integrity": "sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
|
||||
"integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz",
|
||||
"integrity": "sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-buffer": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-section": "1.11.1",
|
||||
"@webassemblyjs/wasm-gen": "1.11.1",
|
||||
"@webassemblyjs/wasm-opt": "1.11.1",
|
||||
"@webassemblyjs/wasm-parser": "1.11.1",
|
||||
"@webassemblyjs/wast-printer": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-buffer": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-section": "1.11.5",
|
||||
"@webassemblyjs/wasm-gen": "1.11.5",
|
||||
"@webassemblyjs/wasm-opt": "1.11.5",
|
||||
"@webassemblyjs/wasm-parser": "1.11.5",
|
||||
"@webassemblyjs/wast-printer": "1.11.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-gen": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
|
||||
"integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz",
|
||||
"integrity": "sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/ieee754": "1.11.1",
|
||||
"@webassemblyjs/leb128": "1.11.1",
|
||||
"@webassemblyjs/utf8": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/ieee754": "1.11.5",
|
||||
"@webassemblyjs/leb128": "1.11.5",
|
||||
"@webassemblyjs/utf8": "1.11.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-opt": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
|
||||
"integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz",
|
||||
"integrity": "sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-buffer": "1.11.1",
|
||||
"@webassemblyjs/wasm-gen": "1.11.1",
|
||||
"@webassemblyjs/wasm-parser": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-buffer": "1.11.5",
|
||||
"@webassemblyjs/wasm-gen": "1.11.5",
|
||||
"@webassemblyjs/wasm-parser": "1.11.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-parser": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
|
||||
"integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz",
|
||||
"integrity": "sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-api-error": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/ieee754": "1.11.1",
|
||||
"@webassemblyjs/leb128": "1.11.1",
|
||||
"@webassemblyjs/utf8": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-api-error": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/ieee754": "1.11.5",
|
||||
"@webassemblyjs/leb128": "1.11.5",
|
||||
"@webassemblyjs/utf8": "1.11.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wast-printer": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
|
||||
"integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz",
|
||||
"integrity": "sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
@@ -1124,9 +1124,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
|
||||
"integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz",
|
||||
"integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@@ -1136,9 +1136,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
|
||||
"integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
|
||||
"integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg=="
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.15.9",
|
||||
@@ -1727,9 +1727,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.4.6",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz",
|
||||
"integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==",
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -2197,9 +2197,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
|
||||
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
|
||||
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
@@ -2288,9 +2288,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
|
||||
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz",
|
||||
"integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
@@ -2305,15 +2305,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
|
||||
"integrity": "sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ==",
|
||||
"version": "5.3.7",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz",
|
||||
"integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==",
|
||||
"dependencies": {
|
||||
"jest-worker": "^27.4.1",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^3.1.1",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^5.7.2"
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"terser": "^5.16.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
@@ -2338,9 +2338,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
|
||||
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
|
||||
"integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
@@ -2354,14 +2354,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
|
||||
@@ -2632,21 +2624,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.75.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
|
||||
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
|
||||
"version": "5.80.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz",
|
||||
"integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
"@types/estree": "^0.0.51",
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/wasm-edit": "1.11.1",
|
||||
"@webassemblyjs/wasm-parser": "1.11.1",
|
||||
"@types/estree": "^1.0.0",
|
||||
"@webassemblyjs/ast": "^1.11.5",
|
||||
"@webassemblyjs/wasm-edit": "^1.11.5",
|
||||
"@webassemblyjs/wasm-parser": "^1.11.5",
|
||||
"acorn": "^8.7.1",
|
||||
"acorn-import-assertions": "^1.7.6",
|
||||
"browserslist": "^4.14.5",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.10.0",
|
||||
"es-module-lexer": "^0.9.0",
|
||||
"enhanced-resolve": "^5.13.0",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
@@ -2655,9 +2647,9 @@
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^3.1.0",
|
||||
"schema-utils": "^3.1.2",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.1.3",
|
||||
"terser-webpack-plugin": "^5.3.7",
|
||||
"watchpack": "^2.4.0",
|
||||
"webpack-sources": "^3.2.3"
|
||||
},
|
||||
@@ -2780,9 +2772,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/schema-utils": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
|
||||
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
|
||||
"integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
@@ -3176,9 +3168,9 @@
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
|
||||
},
|
||||
"@jridgewell/source-map": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
|
||||
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
|
||||
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
|
||||
"requires": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
@@ -3190,12 +3182,12 @@
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
|
||||
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
|
||||
"version": "0.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
|
||||
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
"@jridgewell/resolve-uri": "3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"@polka/url": {
|
||||
@@ -3246,9 +3238,9 @@
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.51",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
|
||||
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ=="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
|
||||
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.9",
|
||||
@@ -3267,133 +3259,133 @@
|
||||
"dev": true
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||
"integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz",
|
||||
"integrity": "sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==",
|
||||
"requires": {
|
||||
"@webassemblyjs/helper-numbers": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1"
|
||||
"@webassemblyjs/helper-numbers": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz",
|
||||
"integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz",
|
||||
"integrity": "sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ=="
|
||||
},
|
||||
"@webassemblyjs/helper-api-error": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz",
|
||||
"integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz",
|
||||
"integrity": "sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA=="
|
||||
},
|
||||
"@webassemblyjs/helper-buffer": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz",
|
||||
"integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz",
|
||||
"integrity": "sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg=="
|
||||
},
|
||||
"@webassemblyjs/helper-numbers": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz",
|
||||
"integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz",
|
||||
"integrity": "sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==",
|
||||
"requires": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.1",
|
||||
"@webassemblyjs/helper-api-error": "1.11.1",
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.5",
|
||||
"@webassemblyjs/helper-api-error": "1.11.5",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz",
|
||||
"integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz",
|
||||
"integrity": "sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA=="
|
||||
},
|
||||
"@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz",
|
||||
"integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz",
|
||||
"integrity": "sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==",
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-buffer": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/wasm-gen": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-buffer": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/wasm-gen": "1.11.5"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/ieee754": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz",
|
||||
"integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz",
|
||||
"integrity": "sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==",
|
||||
"requires": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/leb128": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz",
|
||||
"integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz",
|
||||
"integrity": "sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==",
|
||||
"requires": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/utf8": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz",
|
||||
"integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ=="
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz",
|
||||
"integrity": "sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ=="
|
||||
},
|
||||
"@webassemblyjs/wasm-edit": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz",
|
||||
"integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz",
|
||||
"integrity": "sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==",
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-buffer": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-section": "1.11.1",
|
||||
"@webassemblyjs/wasm-gen": "1.11.1",
|
||||
"@webassemblyjs/wasm-opt": "1.11.1",
|
||||
"@webassemblyjs/wasm-parser": "1.11.1",
|
||||
"@webassemblyjs/wast-printer": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-buffer": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-section": "1.11.5",
|
||||
"@webassemblyjs/wasm-gen": "1.11.5",
|
||||
"@webassemblyjs/wasm-opt": "1.11.5",
|
||||
"@webassemblyjs/wasm-parser": "1.11.5",
|
||||
"@webassemblyjs/wast-printer": "1.11.5"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/wasm-gen": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz",
|
||||
"integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz",
|
||||
"integrity": "sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==",
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/ieee754": "1.11.1",
|
||||
"@webassemblyjs/leb128": "1.11.1",
|
||||
"@webassemblyjs/utf8": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/ieee754": "1.11.5",
|
||||
"@webassemblyjs/leb128": "1.11.5",
|
||||
"@webassemblyjs/utf8": "1.11.5"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/wasm-opt": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz",
|
||||
"integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz",
|
||||
"integrity": "sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==",
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-buffer": "1.11.1",
|
||||
"@webassemblyjs/wasm-gen": "1.11.1",
|
||||
"@webassemblyjs/wasm-parser": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-buffer": "1.11.5",
|
||||
"@webassemblyjs/wasm-gen": "1.11.5",
|
||||
"@webassemblyjs/wasm-parser": "1.11.5"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/wasm-parser": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz",
|
||||
"integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz",
|
||||
"integrity": "sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==",
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/helper-api-error": "1.11.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.1",
|
||||
"@webassemblyjs/ieee754": "1.11.1",
|
||||
"@webassemblyjs/leb128": "1.11.1",
|
||||
"@webassemblyjs/utf8": "1.11.1"
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@webassemblyjs/helper-api-error": "1.11.5",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
|
||||
"@webassemblyjs/ieee754": "1.11.5",
|
||||
"@webassemblyjs/leb128": "1.11.5",
|
||||
"@webassemblyjs/utf8": "1.11.5"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/wast-printer": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz",
|
||||
"integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz",
|
||||
"integrity": "sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==",
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/ast": "1.11.5",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
@@ -3649,18 +3641,18 @@
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
|
||||
"integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz",
|
||||
"integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"es-module-lexer": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
|
||||
"integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
|
||||
"integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg=="
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.15.9",
|
||||
@@ -3987,9 +3979,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "27.4.6",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz",
|
||||
"integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==",
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -4321,9 +4313,9 @@
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
|
||||
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
|
||||
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
@@ -4396,9 +4388,9 @@
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
|
||||
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz",
|
||||
"integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==",
|
||||
"requires": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
@@ -4407,31 +4399,26 @@
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
|
||||
"integrity": "sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ==",
|
||||
"version": "5.3.7",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz",
|
||||
"integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==",
|
||||
"requires": {
|
||||
"jest-worker": "^27.4.1",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^3.1.1",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^5.7.2"
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"terser": "^5.16.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"schema-utils": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
|
||||
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
|
||||
"integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4619,21 +4606,21 @@
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.75.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
|
||||
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
|
||||
"version": "5.80.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz",
|
||||
"integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==",
|
||||
"requires": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
"@types/estree": "^0.0.51",
|
||||
"@webassemblyjs/ast": "1.11.1",
|
||||
"@webassemblyjs/wasm-edit": "1.11.1",
|
||||
"@webassemblyjs/wasm-parser": "1.11.1",
|
||||
"@types/estree": "^1.0.0",
|
||||
"@webassemblyjs/ast": "^1.11.5",
|
||||
"@webassemblyjs/wasm-edit": "^1.11.5",
|
||||
"@webassemblyjs/wasm-parser": "^1.11.5",
|
||||
"acorn": "^8.7.1",
|
||||
"acorn-import-assertions": "^1.7.6",
|
||||
"browserslist": "^4.14.5",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.10.0",
|
||||
"es-module-lexer": "^0.9.0",
|
||||
"enhanced-resolve": "^5.13.0",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
@@ -4642,17 +4629,17 @@
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^3.1.0",
|
||||
"schema-utils": "^3.1.2",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.1.3",
|
||||
"terser-webpack-plugin": "^5.3.7",
|
||||
"watchpack": "^2.4.0",
|
||||
"webpack-sources": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"schema-utils": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
|
||||
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
|
||||
"integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
|
||||
@@ -4,8 +4,7 @@ try:
|
||||
from typing import TypedDict
|
||||
except:
|
||||
from typing_extensions import TypedDict
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Union, Any, Callable
|
||||
|
||||
from .other import *
|
||||
|
||||
@@ -218,6 +217,12 @@ class H264Info(TypedDict):
|
||||
stapb: bool
|
||||
pass
|
||||
|
||||
class ImageOptions(TypedDict):
|
||||
crop: Any
|
||||
format: ImageFormat
|
||||
resize: Any
|
||||
pass
|
||||
|
||||
class ObjectDetectionHistory(TypedDict):
|
||||
firstSeen: float
|
||||
lastSeen: float
|
||||
@@ -228,6 +233,9 @@ class Resource(TypedDict):
|
||||
href: str
|
||||
pass
|
||||
|
||||
class ClipPath(TypedDict):
|
||||
pass
|
||||
|
||||
class AudioStreamOptions(TypedDict):
|
||||
bitrate: float
|
||||
codec: str
|
||||
@@ -240,18 +248,12 @@ class HttpResponseOptions(TypedDict):
|
||||
headers: object
|
||||
pass
|
||||
|
||||
class ImageOptions(TypedDict):
|
||||
crop: Any
|
||||
format: ImageFormat
|
||||
resize: Any
|
||||
pass
|
||||
|
||||
class ObjectDetectionResult(TypedDict):
|
||||
boundingBox: tuple[float, float, float, float]
|
||||
className: str
|
||||
history: ObjectDetectionHistory
|
||||
id: str
|
||||
movement: Any
|
||||
movement: Union[ObjectDetectionHistory, Any]
|
||||
name: str
|
||||
resources: VideoResource
|
||||
score: float
|
||||
@@ -259,6 +261,13 @@ class ObjectDetectionResult(TypedDict):
|
||||
zones: list[str]
|
||||
pass
|
||||
|
||||
class ObjectDetectionZone(TypedDict):
|
||||
classes: list[str]
|
||||
exclusion: bool
|
||||
path: ClipPath
|
||||
type: Any | Any
|
||||
pass
|
||||
|
||||
class PictureDimensions(TypedDict):
|
||||
height: float
|
||||
width: float
|
||||
@@ -490,12 +499,13 @@ class NotifierOptions(TypedDict):
|
||||
class ObjectDetectionGeneratorResult(TypedDict):
|
||||
__json_copy_serialize_children: Any
|
||||
detected: ObjectsDetected
|
||||
videoFrame: VideoFrame
|
||||
videoFrame: Union[VideoFrame, MediaObject]
|
||||
pass
|
||||
|
||||
class ObjectDetectionGeneratorSession(TypedDict):
|
||||
settings: Any
|
||||
sourceId: str
|
||||
zones: list[ObjectDetectionZone]
|
||||
pass
|
||||
|
||||
class ObjectDetectionModel(TypedDict):
|
||||
@@ -509,10 +519,9 @@ class ObjectDetectionModel(TypedDict):
|
||||
pass
|
||||
|
||||
class ObjectDetectionSession(TypedDict):
|
||||
detectionId: str
|
||||
duration: float
|
||||
settings: Any
|
||||
sourceId: str
|
||||
zones: list[ObjectDetectionZone]
|
||||
pass
|
||||
|
||||
class ObjectDetectionTypes(TypedDict):
|
||||
@@ -709,6 +718,8 @@ class VideoClipOptions(TypedDict):
|
||||
class VideoFrameGeneratorOptions(TypedDict):
|
||||
crop: Any
|
||||
format: ImageFormat
|
||||
fps: float
|
||||
queue: float
|
||||
resize: Any
|
||||
pass
|
||||
|
||||
@@ -936,9 +947,9 @@ class OauthClient:
|
||||
pass
|
||||
|
||||
class ObjectDetection:
|
||||
async def detectObjects(self, mediaObject: MediaObject, session: ObjectDetectionSession = None, callbacks: ObjectDetectionCallbacks = None) -> ObjectsDetected:
|
||||
async def detectObjects(self, mediaObject: MediaObject, session: ObjectDetectionSession = None) -> ObjectsDetected:
|
||||
pass
|
||||
async def generateObjectDetections(self, videoFrames: VideoFrame, session: ObjectDetectionGeneratorSession) -> ObjectDetectionGeneratorResult:
|
||||
async def generateObjectDetections(self, videoFrames: AsyncGenerator, session: ObjectDetectionGeneratorSession) -> ObjectDetectionGeneratorResult:
|
||||
pass
|
||||
async def getDetectionModel(self, settings: Any = None) -> ObjectDetectionModel:
|
||||
pass
|
||||
@@ -1281,7 +1292,7 @@ class MediaManager:
|
||||
pass
|
||||
async def createFFmpegMediaObject(self, ffmpegInput: FFmpegInput, options: MediaObjectOptions = None) -> MediaObject:
|
||||
pass
|
||||
async def createMediaObject(self, data: Any, mimeType: str, options: Any = None) -> Any:
|
||||
async def createMediaObject(self, data: Any, mimeType: str, options: Any = None) -> Union[MediaObject, Any]:
|
||||
pass
|
||||
async def createMediaObjectFromUrl(self, data: str, options: Any = None) -> MediaObject:
|
||||
pass
|
||||
@@ -2554,21 +2565,27 @@ class HttpResponse:
|
||||
pass
|
||||
pass
|
||||
|
||||
class ObjectDetectionCallbacks:
|
||||
async def onDetection(self, detection: ObjectsDetected, redetect: Any = None, mediaObject: MediaObject = None) -> bool:
|
||||
pass
|
||||
async def onDetectionEnded(self, detection: ObjectsDetected) -> None:
|
||||
pass
|
||||
pass
|
||||
|
||||
class VideoFrame:
|
||||
format: ImageFormat
|
||||
height: float
|
||||
queued: float
|
||||
timestamp: float
|
||||
width: float
|
||||
async def flush(self, count: float = None) -> None:
|
||||
pass
|
||||
async def toBuffer(self, options: ImageOptions = None) -> bytearray:
|
||||
pass
|
||||
async def toImage(self, options: ImageOptions = None) -> Any:
|
||||
async def toImage(self, options: ImageOptions = None) -> Union[Image, MediaObject]:
|
||||
pass
|
||||
pass
|
||||
|
||||
class Image:
|
||||
format: ImageFormat
|
||||
height: float
|
||||
width: float
|
||||
async def toBuffer(self, options: ImageOptions = None) -> bytearray:
|
||||
pass
|
||||
async def toImage(self, options: ImageOptions = None) -> Union[Image, MediaObject]:
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ discoveredTypes.add('EventDetails');
|
||||
function toPythonType(type: any): string {
|
||||
if (type.type === 'array')
|
||||
return `list[${toPythonType(type.elementType)}]`;
|
||||
if (type.type === 'intersection')
|
||||
return `Union[${type.types.map((et: any) => toPythonType(et)).join(', ')}]`
|
||||
if (type.type === 'tuple')
|
||||
return `tuple[${type.elements.map((et: any) => toPythonType(et)).join(', ')}]`;
|
||||
if (type.type === 'union')
|
||||
@@ -268,8 +270,7 @@ try:
|
||||
from typing import TypedDict
|
||||
except:
|
||||
from typing_extensions import TypedDict
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Union, Any, Callable
|
||||
|
||||
from .other import *
|
||||
|
||||
|
||||
@@ -1296,12 +1296,11 @@ export interface ObjectDetector {
|
||||
getObjectTypes(): Promise<ObjectDetectionTypes>;
|
||||
}
|
||||
export interface ObjectDetectionGeneratorSession {
|
||||
zones?: ObjectDetectionZone[];
|
||||
settings?: { [key: string]: any };
|
||||
sourceId?: string;
|
||||
}
|
||||
export interface ObjectDetectionSession extends ObjectDetectionGeneratorSession {
|
||||
detectionId?: string;
|
||||
duration?: number;
|
||||
}
|
||||
export interface ObjectDetectionModel extends ObjectDetectionTypes {
|
||||
name: string;
|
||||
@@ -1317,16 +1316,22 @@ export interface ObjectDetectionCallbacks {
|
||||
}
|
||||
export interface ObjectDetectionGeneratorResult {
|
||||
__json_copy_serialize_children: true,
|
||||
videoFrame: VideoFrame;
|
||||
videoFrame: VideoFrame & MediaObject;
|
||||
detected: ObjectsDetected;
|
||||
}
|
||||
export interface ObjectDetectionZone {
|
||||
exclusion?: boolean;
|
||||
type?: 'Intersect' | 'Contain';
|
||||
classes?: string[];
|
||||
path?: ClipPath;
|
||||
}
|
||||
/**
|
||||
* ObjectDetection can run classifications or analysis on arbitrary media sources.
|
||||
* E.g. TensorFlow, OpenCV, or a Coral TPU.
|
||||
*/
|
||||
export interface ObjectDetection {
|
||||
generateObjectDetections(videoFrames: AsyncGenerator<VideoFrame>, session: ObjectDetectionGeneratorSession): Promise<AsyncGenerator<ObjectDetectionGeneratorResult>>;
|
||||
detectObjects(mediaObject: MediaObject, session?: ObjectDetectionSession, callbacks?: ObjectDetectionCallbacks): Promise<ObjectsDetected>;
|
||||
generateObjectDetections(videoFrames: AsyncGenerator<VideoFrame & MediaObject, void>, session: ObjectDetectionGeneratorSession): Promise<AsyncGenerator<ObjectDetectionGeneratorResult, void>>;
|
||||
detectObjects(mediaObject: MediaObject, session?: ObjectDetectionSession): Promise<ObjectsDetected>;
|
||||
getDetectionModel(settings?: { [key: string]: any }): Promise<ObjectDetectionModel>;
|
||||
}
|
||||
export type ImageFormat = 'gray' | 'rgba' | 'rgb' | 'jpg';
|
||||
@@ -1357,8 +1362,12 @@ export interface Image {
|
||||
}
|
||||
export interface VideoFrame extends Image {
|
||||
timestamp: number;
|
||||
queued: number;
|
||||
flush(count?: number): Promise<void>;
|
||||
}
|
||||
export interface VideoFrameGeneratorOptions extends ImageOptions {
|
||||
queue?: number;
|
||||
fps?: number;
|
||||
}
|
||||
export interface VideoFrameGenerator {
|
||||
generateVideoFrames(mediaObject: MediaObject, options?: VideoFrameGeneratorOptions, filter?: (videoFrame: VideoFrame & MediaObject) => Promise<boolean>): Promise<AsyncGenerator<VideoFrame & MediaObject>>;
|
||||
@@ -1834,6 +1843,8 @@ export enum MediaPlayerState {
|
||||
Buffering = "Buffering",
|
||||
}
|
||||
export type SettingValue = undefined | null | string | number | boolean | string[] | number[];
|
||||
export type Point = [number, number];
|
||||
export type ClipPath = Point[];
|
||||
export interface Setting {
|
||||
key?: string;
|
||||
title?: string;
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.7.81",
|
||||
"version": "0.7.95",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.7.81",
|
||||
"version": "0.7.95",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.7.81",
|
||||
"version": "0.7.95",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
@@ -69,10 +69,10 @@
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc --outDir dist",
|
||||
"postbuild": "node test/check-build-output.js",
|
||||
"prebeta": "npm version patch && git add package.json && npm run build && git commit -m prebeta",
|
||||
"beta": "npm publish --tag beta",
|
||||
"postbeta": "npm version patch && git add package.json && npm run build && git commit -m postbeta",
|
||||
"release": "npm publish",
|
||||
"prepublish": "npm run build",
|
||||
"prepublishOnly": "npm run build",
|
||||
"postrelease": "git tag v$npm_package_version && git push origin v$npm_package_version && npm version patch && git add package.json && git commit -m postrelease",
|
||||
"docker": "scripts/github-workflow-publish-docker.sh"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
async function main() {
|
||||
const response = await fetch('https://registry.npmjs.org/@scrypted/server');
|
||||
const json = await response.json();
|
||||
// console.log(json['dist-tags'][process.argv[2]]);
|
||||
const packageJson = require('../package.json');
|
||||
console.log(packageJson.version);
|
||||
console.log(json['dist-tags'][process.argv[2]]);
|
||||
// const packageJson = require('../package.json');
|
||||
// console.log(packageJson.version);
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -147,7 +147,9 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP
|
||||
}
|
||||
|
||||
async onDeviceDiscovered(device: Device) {
|
||||
return this.pluginHost.upsertDevice(device);
|
||||
const id = await this.pluginHost.upsertDevice(device);
|
||||
this.scrypted.getDevice(id)?.probe().catch(() => { });
|
||||
return id;
|
||||
}
|
||||
|
||||
async onDeviceRemoved(nativeId: string) {
|
||||
|
||||
@@ -162,6 +162,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
|
||||
throw e;
|
||||
}
|
||||
})();
|
||||
clusterPeers.set(port, clusterPeerPromise);
|
||||
}
|
||||
return clusterPeerPromise;
|
||||
};
|
||||
|
||||
@@ -320,6 +320,7 @@ export class RpcPeer {
|
||||
killed: Promise<string>;
|
||||
killedDeferred: Deferred;
|
||||
tags: any = {};
|
||||
yieldedAsyncIterators = new Set<AsyncGenerator>();
|
||||
|
||||
static readonly finalizerIdSymbol = Symbol('rpcFinalizerId');
|
||||
static remotesCollected = 0;
|
||||
@@ -445,6 +446,10 @@ export class RpcPeer {
|
||||
for (const result of Object.values(this.pendingResults)) {
|
||||
result.reject(error);
|
||||
}
|
||||
for (const y of this.yieldedAsyncIterators) {
|
||||
y.throw(error);
|
||||
}
|
||||
this.yieldedAsyncIterators.clear();
|
||||
this.pendingResults = Object.freeze({});
|
||||
this.params = Object.freeze({});
|
||||
this.remoteWeakProxies = Object.freeze({});
|
||||
@@ -735,9 +740,13 @@ export class RpcPeer {
|
||||
const method = target[rpcApply.method];
|
||||
if (!method)
|
||||
throw new Error(`target ${target?.constructor?.name} does not have method ${rpcApply.method}`);
|
||||
|
||||
const isIteratorNext = RpcPeer.getIteratorNext(target) === rpcApply.method;
|
||||
if (isIteratorNext)
|
||||
this.yieldedAsyncIterators.delete(target);
|
||||
value = await target[rpcApply.method](...args);
|
||||
|
||||
if (RpcPeer.getIteratorNext(target) === rpcApply.method) {
|
||||
if (isIteratorNext) {
|
||||
if (value.done) {
|
||||
const errorType: ErrorType = {
|
||||
name: 'StopAsyncIteration',
|
||||
@@ -746,6 +755,12 @@ export class RpcPeer {
|
||||
throw errorType;
|
||||
}
|
||||
else {
|
||||
if (Object.isFrozen(this.pendingResults)) {
|
||||
(target as AsyncGenerator).throw(new RPCResultError(this, 'RpcPeer has been killed (yield)'));
|
||||
}
|
||||
else {
|
||||
this.yieldedAsyncIterators.add(target);
|
||||
}
|
||||
value = value.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,6 +640,8 @@ async function start(mainFilename: string, options?: {
|
||||
});
|
||||
|
||||
app.get('/', (_req, res) => res.redirect('/endpoint/@scrypted/core/public/'));
|
||||
|
||||
return scrypted;
|
||||
}
|
||||
|
||||
export default start;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Settings } from "../db-types";
|
||||
import { ScryptedRuntime } from "../runtime";
|
||||
import os from 'os';
|
||||
|
||||
export class AddressSettings {
|
||||
constructor(public scrypted: ScryptedRuntime) {
|
||||
@@ -12,10 +13,27 @@ export class AddressSettings {
|
||||
await this.scrypted.datastore.upsert(localAddresses);
|
||||
}
|
||||
|
||||
async getLocalAddresses(): Promise<string[]> {
|
||||
async getLocalAddresses(raw?: boolean): Promise<string[]> {
|
||||
const settings = await this.scrypted.datastore.tryGet(Settings, 'localAddresses');
|
||||
|
||||
if (!settings?.value?.[0])
|
||||
return;
|
||||
return settings.value as string[];
|
||||
|
||||
const ret: string[] = [];
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
for (const addressOrInterface of settings.value) {
|
||||
const nif = networkInterfaces[addressOrInterface];
|
||||
if (!raw && nif) {
|
||||
for (const addr of nif) {
|
||||
if (!addr.address || addr.address.startsWith('169.254.') || addr.address.toLowerCase().startsWith('fe80:'))
|
||||
continue;
|
||||
ret.push(addr.address);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret.push(addressOrInterface);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user