Compare commits

..

52 Commits

Author SHA1 Message Date
Koushik Dutta
212883e84b server: probe one off discovered devices after creation 2023-04-28 07:34:21 -07:00
Koushik Dutta
1200537d62 cloud: support default login 2023-04-27 23:42:54 -07:00
Koushik Dutta
5f6adc9449 predict: publish 2023-04-27 21:53:50 -07:00
Koushik Dutta
7d17236ca7 server: fix prepublishOnly script 2023-04-27 10:31:13 -07:00
Koushik Dutta
028401362a postrelease 2023-04-27 10:30:59 -07:00
Koushik Dutta
69927be4f4 rebroadcast: publish beta 2023-04-26 22:51:34 -07:00
Koushik Dutta
ffee1c5cc2 predict: publish 2023-04-26 22:51:28 -07:00
Koushik Dutta
ebc3a03e2c postrelease 2023-04-26 22:47:50 -07:00
Koushik Dutta
4246e3c476 server: filter link local addresses 2023-04-26 22:47:33 -07:00
Koushik Dutta
3fce0838f1 Merge branch 'main' of github.com:koush/scrypted 2023-04-26 18:40:27 -07:00
Koushik Dutta
2609e301fe python-codecs: fix gray conversion 2023-04-26 18:40:22 -07:00
Koushik Dutta
f4737bf2ac docker: fix stupid bash/zsh issue 2023-04-26 10:22:55 -07:00
Koushik Dutta
fc102aa526 postbeta 2023-04-26 09:56:27 -07:00
Koushik Dutta
9ef33e156f docker: pass through /dev/dri in compose 2023-04-26 09:40:07 -07:00
Koushik Dutta
881865a0cb docker: add intel opencl driver 2023-04-26 09:22:16 -07:00
Koushik Dutta
be5643cc53 openvino: fix bufferconvertor 2023-04-25 22:35:41 -07:00
Koushik Dutta
7e6eba1596 openvino: initial release 2023-04-25 21:56:07 -07:00
Koushik Dutta
27dde776a6 rebroadcast: further settings cleanups 2023-04-25 18:46:38 -07:00
Koushik Dutta
b24159a22a rebroadcast: strip out legacy containers 2023-04-25 18:32:11 -07:00
Koushik Dutta
b6c242b9d5 postrelease 2023-04-25 14:11:58 -07:00
Koushik Dutta
2fbaa12caa core: support selecting interfaces 2023-04-25 14:10:04 -07:00
Koushik Dutta
eb5a497e82 prebeta 2023-04-25 14:04:56 -07:00
Koushik Dutta
66a0ea08ec server: support binding to interfaces 2023-04-25 14:04:50 -07:00
Koushik Dutta
0527baf14a webrtc: update werift, remove unnecessary disable ipv6 option. addresses can be filtered individually. 2023-04-25 13:37:16 -07:00
Koushik Dutta
c7c5c6eed5 server: electron app hooks 2023-04-25 13:34:14 -07:00
Koushik Dutta
143c950c19 core: add support for multiple local addresses 2023-04-25 13:28:00 -07:00
Koushik Dutta
8d0bb0fa97 prebeta 2023-04-24 23:26:53 -07:00
Koushik Dutta
964274e50c prebeta 2023-04-24 23:22:32 -07:00
Koushik Dutta
e9844528aa python-codecs: add timestamps 2023-04-24 18:32:43 -07:00
Koushik Dutta
0609fc8986 python-codecs: publish typings fix 2023-04-24 11:46:14 -07:00
Koushik Dutta
9331b71433 opencv/sdk: fix typing.Union missing 2023-04-24 09:26:21 -07:00
Koushik Dutta
21f8239db7 videoanalysis: publish 2023-04-24 09:26:03 -07:00
Koushik Dutta
86042ec3fe sdk/videoanalysis: add zone hints to detection generator 2023-04-23 21:25:39 -07:00
Koushik Dutta
cdb87fb268 dummy-switch: further settings tweaks 2023-04-22 21:57:15 -07:00
Koushik Dutta
63dcd35b17 dummy-switch: friendly names on extensions 2023-04-22 21:54:35 -07:00
Koushik Dutta
951c3b9be6 dummy-switch: add replace binary sensor extension 2023-04-22 21:52:06 -07:00
Koushik Dutta
ed642bb3fe homekit: dont sync notifier toggle buttons by default 2023-04-22 21:35:07 -07:00
Koushik Dutta
8093cdd3d9 homekit: remove linked motion sensor 2023-04-22 21:29:12 -07:00
Koushik Dutta
fcbfc3a73f Merge branch 'main' of github.com:koush/scrypted 2023-04-22 21:27:54 -07:00
Koushik Dutta
94945a48bd dummy-switch: create replace motion sensor extension 2023-04-22 21:27:48 -07:00
Brett Jia
e360ede5cb rebroadcast: prebuffer on charging battery (#751)
* rework battery prebuffer to take into account charger interface

* rename handler

* do not restart exited stream on low battery

* tweak battery prebuffer state + periodically poll battery prebuffer state
2023-04-22 16:54:15 -07:00
Roarrk
bc9ec73567 coreml: accomodate MultiArray (Float32 0 × 80) models (#749)
Hack to accomodate models that has an output of type Float32 instead of Double.
2023-04-22 16:54:02 -07:00
Sheng
cd7e570508 chromecast: fix stop casting issue (#753) 2023-04-22 16:53:42 -07:00
Koushik Dutta
1b06c9c11d videoanalyis: pause motion detection while motion is active and resume after timeout 2023-04-22 10:10:46 -07:00
Koushik Dutta
154ab42d15 videonalaysis: refactor to avoid holding onto generators 2023-04-22 08:16:34 -07:00
Koushik Dutta
1929f6e8ed python-codecs: simplify generator code 2023-04-21 09:20:04 -07:00
Koushik Dutta
58bfa17cfe postrelease 2023-04-20 21:55:22 -07:00
Koushik Dutta
38c7006055 server: fix runaway cluster sockets 2023-04-20 21:55:15 -07:00
Koushik Dutta
b5e16b45a9 python-codecs: fix potential leak 2023-04-20 20:05:17 -07:00
Koushik Dutta
9c13668812 doorbird: publish 2023-04-20 11:58:10 -07:00
Koushik Dutta
a1ca724d6b opencv: support reference frame interval setting 2023-04-20 11:57:48 -07:00
Koushik Dutta
1b032d669c postrelease 2023-04-19 21:37:44 -07:00
78 changed files with 1619 additions and 832 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
});
});
});

View File

@@ -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",

View File

@@ -55,5 +55,5 @@
"@types/nat-upnp": "^1.1.2",
"@types/node": "^18.11.18"
},
"version": "0.1.13"
"version": "0.1.14"
}

View File

@@ -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() {

View File

@@ -29,7 +29,7 @@ class ChromecastViewCameraExample implements StartStop {
}
async stop() {
device.running = false;
return chromecast.stop();
await chromecast.stop();
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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) {
}

View File

@@ -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"
}

View File

@@ -34,12 +34,11 @@
"type": "API",
"interfaces": [
"Settings",
"BufferConverter",
"ObjectDetection"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.12"
"version": "0.1.14"
}

View File

@@ -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]

View File

@@ -14,7 +14,8 @@
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^18.15.11"
"@types/node": "^18.15.11",
"cross-env": "^7.0.3"
}
},
"../../common": {
@@ -44,6 +45,7 @@
"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",
@@ -147,6 +149,38 @@
"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",
@@ -207,6 +241,12 @@
"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",
@@ -239,6 +279,15 @@
"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",
@@ -255,10 +304,46 @@
"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"
}
}
}
}

View File

@@ -40,6 +40,7 @@
"devDependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/node": "^18.15.11"
"@types/node": "^18.15.11",
"cross-env": "^7.0.3"
}
}

View File

@@ -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=="
}
}
}

View File

@@ -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"
}

View File

@@ -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> {
}
}

View 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();
}
}

View 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();
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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',

View File

@@ -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);

View File

@@ -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",

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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": {

View File

@@ -36,5 +36,5 @@
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.0.74"
"version": "0.0.79"
}

View File

@@ -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
View File

@@ -0,0 +1,6 @@
.DS_Store
out/
node_modules/
dist/
.venv
all_models*

View 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
View 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
View 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
View 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}",
},
]
}

View 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
View 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"
}
}
}
}

View 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
View File

@@ -0,0 +1 @@
../../tensorflow-lite/src/detect

View File

@@ -0,0 +1,4 @@
from ov import OpenVINOPlugin
def create_scrypted_plugin():
return OpenVINOPlugin()

View 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

View File

@@ -0,0 +1 @@
../../tensorflow-lite/src/predict

View 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'

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2021",
"resolveJsonModule": true,
"moduleResolution": "Node16",
"esModuleInterop": true,
"sourceMap": true
},
"include": [
"src/**/*"
]
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/python-codecs",
"version": "0.1.35",
"version": "0.1.47",
"description": "Python Codecs for Scrypted",
"keywords": [
"scrypted",

View File

@@ -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

View File

@@ -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')

View File

@@ -1,5 +1,4 @@
import time
from gst_generator import createPipelineIterator
import scrypted_sdk
from typing import Any
import vipsimage

View File

@@ -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,

View File

@@ -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,

View File

@@ -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"
}

View File

@@ -37,12 +37,11 @@
"type": "API",
"interfaces": [
"Settings",
"BufferConverter",
"ObjectDetection"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.12"
"version": "0.1.14"
}

View File

@@ -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,7 +67,7 @@ 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 = await scrypted_sdk.sdk.connectRPCObject(mediaObject)

View File

@@ -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

View File

@@ -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')

View File

@@ -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"
}

View File

@@ -34,12 +34,11 @@
"type": "API",
"interfaces": [
"Settings",
"BufferConverter",
"ObjectDetection"
]
},
"devDependencies": {
"@scrypted/sdk": "file:../../sdk"
},
"version": "0.1.12"
"version": "0.1.14"
}

View File

@@ -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)

View File

@@ -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",

View File

@@ -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",

View File

@@ -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
View File

@@ -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",

View File

@@ -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
@@ -245,7 +253,7 @@ class ObjectDetectionResult(TypedDict):
className: str
history: ObjectDetectionHistory
id: str
movement: Any
movement: Union[ObjectDetectionHistory, Any]
name: str
resources: VideoResource
score: float
@@ -253,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
@@ -484,12 +499,13 @@ class NotifierOptions(TypedDict):
class ObjectDetectionGeneratorResult(TypedDict):
__json_copy_serialize_children: Any
detected: ObjectsDetected
videoFrame: Any
videoFrame: Union[VideoFrame, MediaObject]
pass
class ObjectDetectionGeneratorSession(TypedDict):
settings: Any
sourceId: str
zones: list[ObjectDetectionZone]
pass
class ObjectDetectionModel(TypedDict):
@@ -503,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):
@@ -932,7 +947,7 @@ 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: AsyncGenerator, session: ObjectDetectionGeneratorSession) -> ObjectDetectionGeneratorResult:
pass
@@ -1277,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
@@ -2550,10 +2565,27 @@ class HttpResponse:
pass
pass
class ObjectDetectionCallbacks:
async def onDetection(self, detection: ObjectsDetected, redetect: Any = None, mediaObject: MediaObject = None) -> bool:
class VideoFrame:
format: ImageFormat
height: float
queued: float
timestamp: float
width: float
async def flush(self, count: float = None) -> None:
pass
async def onDetectionEnded(self, detection: ObjectsDetected) -> None:
async def toBuffer(self, options: ImageOptions = None) -> bytearray:
pass
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

View File

@@ -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 *

View File

@@ -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;
@@ -1320,13 +1319,19 @@ export interface ObjectDetectionGeneratorResult {
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 & MediaObject>, 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';
@@ -1838,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;

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/server",
"version": "0.7.83",
"version": "0.7.95",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/server",
"version": "0.7.83",
"version": "0.7.95",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.10",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/server",
"version": "0.7.83",
"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"
},

View File

@@ -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) {

View File

@@ -162,6 +162,7 @@ export function startPluginRemote(mainFilename: string, pluginId: string, peerSe
throw e;
}
})();
clusterPeers.set(port, clusterPeerPromise);
}
return clusterPeerPromise;
};

View File

@@ -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;

View File

@@ -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;
}
}