mirror of
https://github.com/koush/scrypted.git
synced 2026-02-07 16:02:13 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fbaa12caa | ||
|
|
eb5a497e82 | ||
|
|
66a0ea08ec | ||
|
|
0527baf14a | ||
|
|
c7c5c6eed5 | ||
|
|
143c950c19 | ||
|
|
8d0bb0fa97 | ||
|
|
964274e50c | ||
|
|
e9844528aa | ||
|
|
0609fc8986 | ||
|
|
9331b71433 | ||
|
|
21f8239db7 | ||
|
|
86042ec3fe | ||
|
|
cdb87fb268 | ||
|
|
63dcd35b17 | ||
|
|
951c3b9be6 | ||
|
|
ed642bb3fe | ||
|
|
8093cdd3d9 | ||
|
|
fcbfc3a73f | ||
|
|
94945a48bd | ||
|
|
e360ede5cb | ||
|
|
bc9ec73567 | ||
|
|
cd7e570508 | ||
|
|
1b06c9c11d | ||
|
|
154ab42d15 | ||
|
|
1929f6e8ed | ||
|
|
58bfa17cfe |
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ class ChromecastViewCameraExample implements StartStop {
|
||||
}
|
||||
async stop() {
|
||||
device.running = false;
|
||||
return chromecast.stop();
|
||||
await chromecast.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.114",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.114",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/core",
|
||||
"version": "0.1.110",
|
||||
"version": "0.1.114",
|
||||
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -17,7 +17,13 @@ const { systemManager, deviceManager, endpointManager } = sdk;
|
||||
const indexHtml = fs.readFileSync('dist/index.html').toString();
|
||||
|
||||
export function getAddresses() {
|
||||
const addresses = Object.entries(os.networkInterfaces()).filter(([iface]) => iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')).map(([_, addr]) => addr).flat().map(info => info.address).filter(address => address);
|
||||
const addresses: string[] = [];
|
||||
for (const [iface, nif] of Object.entries(os.networkInterfaces())) {
|
||||
if (iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')) {
|
||||
addresses.push(iface);
|
||||
addresses.push(...nif.map(addr => addr.address));
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
@@ -37,17 +43,18 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
localAddresses: string[];
|
||||
storageSettings = new StorageSettings(this, {
|
||||
localAddresses: {
|
||||
title: 'Scrypted Server Address',
|
||||
description: 'The IP address used by the Scrypted server. Set this to the wired IP address to prevent usage of a wireless address.',
|
||||
title: 'Scrypted Server Addresses',
|
||||
description: 'The IP addresses used by the Scrypted server. Set this to the wired IP address to prevent usage of a wireless address.',
|
||||
combobox: true,
|
||||
multiple: true,
|
||||
async onGet() {
|
||||
return {
|
||||
choices: getAddresses(),
|
||||
};
|
||||
},
|
||||
mapGet: () => this.localAddresses?.[0],
|
||||
mapGet: () => this.localAddresses,
|
||||
onPut: async (oldValue, newValue) => {
|
||||
this.localAddresses = newValue ? [newValue] : undefined;
|
||||
this.localAddresses = newValue?.length ? newValue : undefined;
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
service.setLocalAddresses(this.localAddresses);
|
||||
},
|
||||
@@ -132,7 +139,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
try {
|
||||
const service = await sdk.systemManager.getComponent('addresses');
|
||||
this.localAddresses = await service.getLocalAddresses();
|
||||
this.localAddresses = await service.getLocalAddresses(true);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
@@ -58,11 +58,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]
|
||||
|
||||
250
plugins/dummy-switch/package-lock.json
generated
250
plugins/dummy-switch/package-lock.json
generated
@@ -1,61 +1,82 @@
|
||||
{
|
||||
"name": "@scrypted/dummy-switch",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.23",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/dummy-switch",
|
||||
"version": "0.0.15",
|
||||
"hasInstallScript": true,
|
||||
"version": "0.0.23",
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"axios": "^0.19.0"
|
||||
"axios": "^1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.0.199",
|
||||
"version": "0.2.97",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"tmp": "^0.2.1",
|
||||
"webpack": "^5.59.0"
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-changelog": "bin/scrypted-changelog.js",
|
||||
"scrypted-debug": "bin/scrypted-debug.js",
|
||||
"scrypted-deploy": "bin/scrypted-deploy.js",
|
||||
"scrypted-deploy-debug": "bin/scrypted-deploy-debug.js",
|
||||
"scrypted-package-json": "bin/scrypted-package-json.js",
|
||||
"scrypted-readme": "bin/scrypted-readme.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.1",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"../sdk": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
@@ -65,61 +86,130 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410",
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz",
|
||||
"integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "1.5.10"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"dependencies": {
|
||||
"debug": "=3.1.0"
|
||||
},
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects/node_modules/debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/common": {
|
||||
"version": "file:../../common",
|
||||
"requires": {
|
||||
"@scrypted/sdk": "file:../sdk",
|
||||
"@scrypted/server": "file:../server",
|
||||
"@types/node": "^16.9.0",
|
||||
"http-auth-utils": "^3.0.2",
|
||||
"node-fetch-commonjs": "^3.1.1",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
},
|
||||
"@scrypted/sdk": {
|
||||
"version": "file:../../sdk",
|
||||
"requires": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"babel-loader": "^9.1.0",
|
||||
"babel-plugin-const-enum": "^1.1.0",
|
||||
"esbuild": "^0.13.8",
|
||||
"esbuild": "^0.15.9",
|
||||
"ncp": "^2.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"stringify-object": "^3.3.0",
|
||||
"tmp": "^0.2.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.22.8",
|
||||
"typescript-json-schema": "^0.50.1",
|
||||
"webpack": "^5.59.0",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
@@ -128,36 +218,66 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.6.1.tgz",
|
||||
"integrity": "sha512-Sr7BhXEAer9xyGuCN3Ek9eg9xPviCF2gfu9kTfuU2HkTVAMYSDeX40fvpmo72n5nansg3nsBjuQBrsS28r+NUw=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz",
|
||||
"integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"requires": {
|
||||
"mime-db": "1.52.0"
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^16.6.1",
|
||||
"axios": "^0.19.0"
|
||||
"axios": "^1.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.15"
|
||||
"version": "0.0.23"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { BinarySensor, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Lock, LockState, MotionSensor, OccupancySensor, OnOff, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue, StartStop } from '@scrypted/sdk';
|
||||
import sdk from '@scrypted/sdk';
|
||||
import { ReplaceMotionSensor, ReplaceMotionSensorNativeId } from './replace-motion-sensor';
|
||||
import { ReplaceBinarySensor, ReplaceBinarySensorNativeId } from './replace-binary-sensor';
|
||||
|
||||
const { log, deviceManager } = sdk;
|
||||
|
||||
@@ -87,6 +89,27 @@ class DummyDeviceProvider extends ScryptedDeviceBase implements DeviceProvider,
|
||||
this.getDevice(camId);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
name: 'Custom Motion Sensor',
|
||||
nativeId: ReplaceMotionSensorNativeId,
|
||||
interfaces: [ScryptedInterface.MixinProvider],
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
},
|
||||
);
|
||||
})();
|
||||
|
||||
(async () => {
|
||||
await deviceManager.onDeviceDiscovered(
|
||||
{
|
||||
name: 'Custom Doorbell Button',
|
||||
nativeId: ReplaceBinarySensorNativeId,
|
||||
interfaces: [ScryptedInterface.MixinProvider],
|
||||
type: ScryptedDeviceType.Builtin,
|
||||
},
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
async getCreateDeviceSettings(): Promise<Setting[]> {
|
||||
@@ -127,6 +150,11 @@ class DummyDeviceProvider extends ScryptedDeviceBase implements DeviceProvider,
|
||||
}
|
||||
|
||||
async getDevice(nativeId: string) {
|
||||
if (nativeId === ReplaceMotionSensorNativeId)
|
||||
return new ReplaceMotionSensor(ReplaceMotionSensorNativeId);
|
||||
if (nativeId === ReplaceBinarySensorNativeId)
|
||||
return new ReplaceBinarySensor(ReplaceBinarySensorNativeId);
|
||||
|
||||
let ret = this.devices.get(nativeId);
|
||||
if (!ret) {
|
||||
ret = new DummyDevice(nativeId);
|
||||
@@ -143,7 +171,7 @@ class DummyDeviceProvider extends ScryptedDeviceBase implements DeviceProvider,
|
||||
}
|
||||
|
||||
async releaseDevice(id: string, nativeId: string): Promise<void> {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
80
plugins/dummy-switch/src/replace-binary-sensor.ts
Normal file
80
plugins/dummy-switch/src/replace-binary-sensor.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
||||
import { BinarySensor, DeviceState, EventListenerRegister, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
|
||||
export const ReplaceBinarySensorNativeId = 'replaceBinarySensor';
|
||||
|
||||
class ReplaceBinarySensorMixin extends SettingsMixinDeviceBase<any> implements Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
replaceBinarySensor: {
|
||||
title: 'Doorbell Button',
|
||||
description: 'The binary sensor to attach to this camera.',
|
||||
value: this.storage.getItem('replaceBinarySensor'),
|
||||
deviceFilter: `interfaces.includes('${ScryptedInterface.BinarySensor}') && !interfaces.includes('@scrypted/dummy-switch:ReplaceBinarySensor') && id !== '${this.id}'`,
|
||||
type: 'device',
|
||||
}
|
||||
});
|
||||
|
||||
listener: EventListenerRegister;
|
||||
|
||||
constructor(options: SettingsMixinDeviceOptions<any>) {
|
||||
super(options);
|
||||
this.binaryState = false;
|
||||
|
||||
this.register();
|
||||
}
|
||||
|
||||
register() {
|
||||
this.release();
|
||||
|
||||
const d = this.storageSettings.values.replaceBinarySensor as ScryptedDevice & BinarySensor;
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
this.listener = d.listen(ScryptedInterface.BinarySensor, () => {
|
||||
this.binaryState = d.binaryState;
|
||||
});
|
||||
}
|
||||
|
||||
getMixinSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
putMixinSetting(key: string, value: SettingValue) {
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
async release(): Promise<void> {
|
||||
this.listener?.removeListener();
|
||||
this.listener = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ReplaceBinarySensor extends ScryptedDeviceBase implements MixinProvider {
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
if (type !== ScryptedDeviceType.Camera && type !== ScryptedDeviceType.Doorbell)
|
||||
return;
|
||||
|
||||
return [
|
||||
ScryptedInterface.BinarySensor,
|
||||
ScryptedInterface.Settings,
|
||||
'@scrypted/dummy-switch:ReplaceBinarySensor',
|
||||
];
|
||||
}
|
||||
|
||||
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: DeviceState): Promise<any> {
|
||||
return new ReplaceBinarySensorMixin({
|
||||
group: 'Custom Doorbell Button',
|
||||
groupKey: 'replaceBinarySensor',
|
||||
mixinDevice,
|
||||
mixinDeviceInterfaces,
|
||||
mixinProviderNativeId: this.nativeId,
|
||||
mixinDeviceState,
|
||||
});
|
||||
}
|
||||
|
||||
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
||||
await mixinDevice.release();
|
||||
}
|
||||
}
|
||||
80
plugins/dummy-switch/src/replace-motion-sensor.ts
Normal file
80
plugins/dummy-switch/src/replace-motion-sensor.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
|
||||
import { DeviceState, EventListenerRegister, MixinProvider, MotionSensor, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from "@scrypted/sdk";
|
||||
import { StorageSettings } from "@scrypted/sdk/storage-settings";
|
||||
|
||||
export const ReplaceMotionSensorNativeId = 'replaceMotionSensor';
|
||||
|
||||
class ReplaceMotionSensorMixin extends SettingsMixinDeviceBase<any> implements Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
replaceMotionSensor: {
|
||||
title: 'Motion Sensor',
|
||||
description: 'The motion sensor to attach to this camera or doorbell.',
|
||||
value: this.storage.getItem('replaceMotionSensor'),
|
||||
deviceFilter: `interfaces.includes('${ScryptedInterface.MotionSensor}') && !interfaces.includes('@scrypted/dummy-switch:ReplaceMotionSensor') && id !== '${this.id}'`,
|
||||
type: 'device',
|
||||
}
|
||||
});
|
||||
|
||||
listener: EventListenerRegister;
|
||||
|
||||
constructor(options: SettingsMixinDeviceOptions<any>) {
|
||||
super(options);
|
||||
this.motionDetected = false;
|
||||
|
||||
this.register();
|
||||
}
|
||||
|
||||
register() {
|
||||
this.release();
|
||||
|
||||
const d = this.storageSettings.values.replaceMotionSensor as ScryptedDevice & MotionSensor;
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
this.listener = d.listen(ScryptedInterface.MotionSensor, () => {
|
||||
this.motionDetected = d.motionDetected;
|
||||
});
|
||||
}
|
||||
|
||||
getMixinSettings(): Promise<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
putMixinSetting(key: string, value: SettingValue) {
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
async release(): Promise<void> {
|
||||
this.listener?.removeListener();
|
||||
this.listener = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ReplaceMotionSensor extends ScryptedDeviceBase implements MixinProvider {
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
if (type !== ScryptedDeviceType.Camera && type !== ScryptedDeviceType.Doorbell)
|
||||
return;
|
||||
|
||||
return [
|
||||
ScryptedInterface.MotionSensor,
|
||||
ScryptedInterface.Settings,
|
||||
'@scrypted/dummy-switch:ReplaceMotionSensor',
|
||||
];
|
||||
}
|
||||
|
||||
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: DeviceState): Promise<any> {
|
||||
return new ReplaceMotionSensorMixin({
|
||||
group: 'Custom Motion Sensor',
|
||||
groupKey: 'replaceMotionSensor',
|
||||
mixinDevice,
|
||||
mixinDeviceInterfaces,
|
||||
mixinProviderNativeId: this.nativeId,
|
||||
mixinDeviceState,
|
||||
});
|
||||
}
|
||||
|
||||
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
||||
await mixinDevice.release();
|
||||
}
|
||||
}
|
||||
4
plugins/homekit/package-lock.json
generated
4
plugins/homekit/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.23",
|
||||
"version": "1.2.25",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.23",
|
||||
"version": "1.2.25",
|
||||
"dependencies": {
|
||||
"@koush/werift-src": "file:../../external/werift",
|
||||
"check-disk-space": "^3.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "1.2.23",
|
||||
"version": "1.2.25",
|
||||
"description": "HomeKit Plugin for Scrypted",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
|
||||
@@ -76,18 +76,20 @@ The latest troubleshooting guide for all known streaming or recording issues can
|
||||
const settings: Setting[] = [];
|
||||
const realDevice = systemManager.getDeviceById<ObjectDetector & VideoCamera>(this.id);
|
||||
|
||||
settings.push(
|
||||
{
|
||||
title: 'Linked Motion Sensor',
|
||||
key: 'linkedMotionSensor',
|
||||
type: 'device',
|
||||
deviceFilter: 'interfaces.includes("MotionSensor")',
|
||||
value: this.storage.getItem('linkedMotionSensor') || this.id,
|
||||
placeholder: this.interfaces.includes(ScryptedInterface.MotionSensor)
|
||||
? undefined : 'None',
|
||||
description: "Set the motion sensor used to trigger HomeKit Secure Video recordings. Defaults to the device provided motion sensor when available.",
|
||||
},
|
||||
);
|
||||
if (this.storage.getItem('linkedMotionSensor')) {
|
||||
settings.push(
|
||||
{
|
||||
title: 'Linked Motion Sensor',
|
||||
key: 'linkedMotionSensor',
|
||||
type: 'device',
|
||||
deviceFilter: 'interfaces.includes("MotionSensor")',
|
||||
value: this.storage.getItem('linkedMotionSensor') || this.id,
|
||||
placeholder: this.interfaces.includes(ScryptedInterface.MotionSensor)
|
||||
? undefined : 'None',
|
||||
description: "Set the motion sensor used to trigger HomeKit Secure Video recordings. Defaults to the device provided motion sensor when available.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// settings.push({
|
||||
// title: 'H265 Streams',
|
||||
|
||||
@@ -213,6 +213,9 @@ export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider,
|
||||
try {
|
||||
const mixins = (device.mixins || []).slice();
|
||||
if (!mixins.includes(this.id)) {
|
||||
// don't sync this by default, as it's solely for automations
|
||||
if (device.type === ScryptedDeviceType.Notifier)
|
||||
continue;
|
||||
if (defaultIncluded[device.id] === includeToken)
|
||||
continue;
|
||||
mixins.push(this.id);
|
||||
|
||||
4
plugins/objectdetector/package-lock.json
generated
4
plugins/objectdetector/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/objectdetector",
|
||||
"version": "0.0.132",
|
||||
"version": "0.0.133",
|
||||
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Deferred } from '@scrypted/common/src/deferred';
|
||||
import { sleep } from '@scrypted/common/src/sleep';
|
||||
import sdk, { Camera, DeviceProvider, DeviceState, EventListenerRegister, MediaObject, MediaStreamDestination, MixinDeviceBase, MixinProvider, MotionSensor, ObjectDetection, ObjectDetectionGeneratorResult, ObjectDetectionModel, ObjectDetectionTypes, ObjectDetector, ObjectsDetected, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, Settings, SettingValue, VideoCamera, VideoFrame, VideoFrameGenerator } from '@scrypted/sdk';
|
||||
import sdk, { Camera, DeviceProvider, DeviceState, EventListenerRegister, MediaObject, MediaStreamDestination, MixinDeviceBase, MixinProvider, MotionSensor, ObjectDetection, ObjectDetectionGeneratorResult, ObjectDetectionModel, ObjectDetectionTypes, ObjectDetectionZone, ObjectDetector, ObjectsDetected, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, ScryptedNativeId, Setting, Settings, SettingValue, VideoCamera, VideoFrame, VideoFrameGenerator } from '@scrypted/sdk';
|
||||
import { StorageSettings } from '@scrypted/sdk/storage-settings';
|
||||
import crypto from 'crypto';
|
||||
import { AutoenableMixinProvider } from "../../../common/src/autoenable-mixin-provider";
|
||||
@@ -17,7 +17,7 @@ const { systemManager } = sdk;
|
||||
const defaultDetectionDuration = 20;
|
||||
const defaultDetectionInterval = 60;
|
||||
const defaultDetectionTimeout = 60;
|
||||
const defaultMotionDuration = 10;
|
||||
const defaultMotionDuration = 30;
|
||||
|
||||
const BUILTIN_MOTION_SENSOR_ASSIST = 'Assist';
|
||||
const BUILTIN_MOTION_SENSOR_REPLACE = 'Replace';
|
||||
@@ -265,7 +265,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
snapshotPipeline: this.plugin.shouldUseSnapshotPipeline(),
|
||||
};
|
||||
|
||||
this.runPipelineAnalysis(signal, options)
|
||||
this.runPipelineAnalysisLoop(signal, options)
|
||||
.catch(e => {
|
||||
this.console.error('Video Analysis ended with error', e);
|
||||
}).finally(() => {
|
||||
@@ -277,28 +277,26 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
});
|
||||
}
|
||||
|
||||
async runPipelineAnalysis(signal: Deferred<void>, options: {
|
||||
async runPipelineAnalysisLoop(signal: Deferred<void>, options: {
|
||||
snapshotPipeline: boolean,
|
||||
suppress?: boolean,
|
||||
}) {
|
||||
const start = Date.now();
|
||||
this.analyzeStop = start + this.getDetectionDuration();
|
||||
|
||||
let lastStatusTime = Date.now();
|
||||
let lastStatus = 'starting';
|
||||
const updatePipelineStatus = (status: string) => {
|
||||
lastStatus = status;
|
||||
lastStatusTime = Date.now();
|
||||
while (!signal.finished) {
|
||||
const shouldSleep = await this.runPipelineAnalysis(signal, options);
|
||||
options.suppress = true;
|
||||
if (!shouldSleep || signal.finished)
|
||||
return;
|
||||
this.console.log('Suspending motion processing during active motion timeout.');
|
||||
// sleep until a moment before motion duration to start peeking again
|
||||
// to have an opporunity to reset the motion timeout.
|
||||
await sleep(this.storageSettings.values.motionDuration * 1000 - 4000);
|
||||
}
|
||||
}
|
||||
|
||||
let frameGenerator: AsyncGenerator<VideoFrame & MediaObject, void>;
|
||||
let detectionGenerator: AsyncGenerator<ObjectDetectionGeneratorResult, void>;
|
||||
const interval = setInterval(() => {
|
||||
if (Date.now() - lastStatusTime > 30000) {
|
||||
signal.resolve();
|
||||
this.console.error('VideoAnalysis is hung and will terminate:', lastStatus);
|
||||
}
|
||||
}, 30000);
|
||||
signal.promise.finally(() => clearInterval(interval));
|
||||
async createFrameGenerator(signal: Deferred<void>, options: {
|
||||
snapshotPipeline: boolean,
|
||||
suppress?: boolean,
|
||||
}, updatePipelineStatus: (status: string) => void) {
|
||||
|
||||
let newPipeline: string = this.newPipeline;
|
||||
if (!this.hasMotionType && (!newPipeline || newPipeline === 'Default')) {
|
||||
@@ -312,7 +310,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
options.snapshotPipeline = true;
|
||||
this.console.log('decoder:', 'Snapshot +', this.objectDetection.name);
|
||||
const self = this;
|
||||
frameGenerator = (async function* gen() {
|
||||
return (async function* gen() {
|
||||
try {
|
||||
while (!signal.finished) {
|
||||
const now = Date.now();
|
||||
@@ -353,7 +351,8 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
const videoFrameGenerator = systemManager.getDeviceById<VideoFrameGenerator>(newPipeline);
|
||||
if (!videoFrameGenerator)
|
||||
throw new Error('invalid VideoFrameGenerator');
|
||||
this.console.log(videoFrameGenerator.name, '+', this.objectDetection.name);
|
||||
if (!options?.suppress)
|
||||
this.console.log(videoFrameGenerator.name, '+', this.objectDetection.name);
|
||||
updatePipelineStatus('getVideoStream');
|
||||
const stream = await this.cameraDevice.getVideoStream({
|
||||
prebuffer: this.model.prebuffer,
|
||||
@@ -362,7 +361,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
audio: null,
|
||||
});
|
||||
|
||||
frameGenerator = await videoFrameGenerator.generateVideoFrames(stream, {
|
||||
return await videoFrameGenerator.generateVideoFrames(stream, {
|
||||
queue: 0,
|
||||
resize: this.model?.inputSize ? {
|
||||
width: this.model.inputSize[0],
|
||||
@@ -371,17 +370,63 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
format: this.model?.inputFormat,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async runPipelineAnalysis(signal: Deferred<void>, options: {
|
||||
snapshotPipeline: boolean,
|
||||
suppress?: boolean,
|
||||
}) {
|
||||
const start = Date.now();
|
||||
this.analyzeStop = start + this.getDetectionDuration();
|
||||
|
||||
let lastStatusTime = Date.now();
|
||||
let lastStatus = 'starting';
|
||||
const updatePipelineStatus = (status: string) => {
|
||||
lastStatus = status;
|
||||
lastStatusTime = Date.now();
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (Date.now() - lastStatusTime > 30000) {
|
||||
signal.resolve();
|
||||
this.console.error('VideoAnalysis is hung and will terminate:', lastStatus);
|
||||
}
|
||||
}, 30000);
|
||||
signal.promise.finally(() => clearInterval(interval));
|
||||
|
||||
const currentDetections = new Set<string>();
|
||||
let lastReport = 0;
|
||||
detectionGenerator = await sdk.connectRPCObject(await this.objectDetection.generateObjectDetections(frameGenerator, {
|
||||
settings: this.getCurrentSettings(),
|
||||
sourceId: this.id,
|
||||
}));
|
||||
|
||||
updatePipelineStatus('waiting result');
|
||||
|
||||
for await (const detected of detectionGenerator) {
|
||||
const zones: ObjectDetectionZone[] = [];
|
||||
for (const detectorMixin of this.plugin.currentMixins.values()) {
|
||||
for (const mixin of detectorMixin.currentMixins.values()) {
|
||||
if (mixin.id !== this.id)
|
||||
continue;
|
||||
for (const [key, zi] of Object.entries(mixin.zoneInfos)) {
|
||||
const zone = mixin.zones[key];
|
||||
if (!zone?.length || zone?.length < 3)
|
||||
continue;
|
||||
const odz: ObjectDetectionZone = {
|
||||
classes: mixin.hasMotionType ? ['motion'] : zi.classes,
|
||||
exclusion: zi.exclusion,
|
||||
path: zone,
|
||||
type: zi.type,
|
||||
}
|
||||
zones.push(odz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for await (const detected of
|
||||
await sdk.connectRPCObject(
|
||||
await this.objectDetection.generateObjectDetections(
|
||||
await this.createFrameGenerator(signal, options, updatePipelineStatus), {
|
||||
settings: this.getCurrentSettings(),
|
||||
sourceId: this.id,
|
||||
zones,
|
||||
}))) {
|
||||
if (signal.finished) {
|
||||
break;
|
||||
}
|
||||
@@ -426,11 +471,14 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
this.setDetection(detected.detected, mo);
|
||||
// this.console.log('image saved', detected.detected.detections);
|
||||
}
|
||||
const hadMotionDetected = this.motionDetected;
|
||||
this.reportObjectDetections(detected.detected);
|
||||
if (this.hasMotionType) {
|
||||
// const diff = Date.now() - when;
|
||||
// when = Date.now();
|
||||
// this.console.log('sleper', diff);
|
||||
if (!hadMotionDetected && this.motionDetected) {
|
||||
// if new motion is detected, stop processing and exit loop allowing it to sleep.
|
||||
clearInterval(interval);
|
||||
return true;
|
||||
}
|
||||
await sleep(250);
|
||||
}
|
||||
updatePipelineStatus('waiting result');
|
||||
@@ -695,6 +743,20 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
|
||||
],
|
||||
value: zi?.type || 'Intersect',
|
||||
});
|
||||
|
||||
if (!this.hasMotionType) {
|
||||
settings.push(
|
||||
{
|
||||
subgroup,
|
||||
key: `zoneinfo-classes-${name}`,
|
||||
title: `Detection Classes`,
|
||||
description: 'The detection classes to match inside this zone. An empty list will match all classes.',
|
||||
choices: (await this.getObjectTypes())?.classes || [],
|
||||
value: zi?.classes || [],
|
||||
multiple: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.hasMotionType) {
|
||||
|
||||
4
plugins/opencv/package-lock.json
generated
4
plugins/opencv/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.76",
|
||||
"version": "0.0.79",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/opencv",
|
||||
"version": "0.0.76",
|
||||
"version": "0.0.79",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
},
|
||||
"version": "0.0.76"
|
||||
"version": "0.0.79"
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -105,7 +105,10 @@ class PrebufferSession {
|
||||
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;
|
||||
@@ -129,6 +132,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() {
|
||||
@@ -206,6 +215,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() {
|
||||
@@ -934,6 +951,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,
|
||||
@@ -1497,8 +1554,6 @@ class PrebufferMixin extends SettingsMixinDeviceBase<VideoCamera> implements Vid
|
||||
}
|
||||
}
|
||||
|
||||
const isBatteryPowered = this.mixinDeviceInterfaces.includes(ScryptedInterface.Battery);
|
||||
|
||||
if (!enabledIds.length)
|
||||
this.online = true;
|
||||
|
||||
@@ -1524,22 +1579,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 {
|
||||
|
||||
4
plugins/python-codecs/package-lock.json
generated
4
plugins/python-codecs/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/python-codecs",
|
||||
"version": "0.1.37",
|
||||
"version": "0.1.46",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/python-codecs",
|
||||
"version": "0.1.37",
|
||||
"version": "0.1.46",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/python-codecs",
|
||||
"version": "0.1.37",
|
||||
"version": "0.1.46",
|
||||
"description": "Python Codecs for Scrypted",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
|
||||
@@ -14,13 +14,6 @@ try:
|
||||
except:
|
||||
pass
|
||||
|
||||
class Callback:
|
||||
def __init__(self, callback) -> None:
|
||||
if callback:
|
||||
self.callback = callback
|
||||
else:
|
||||
self.callback = None
|
||||
|
||||
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)
|
||||
@@ -32,10 +25,13 @@ async 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))
|
||||
@@ -49,8 +45,7 @@ async def createPipelineIterator(pipeline: str):
|
||||
def finish():
|
||||
nonlocal hasFinished
|
||||
hasFinished = True
|
||||
callback = Callback(None)
|
||||
callbackQueue.put(callback)
|
||||
yieldQueue.put(None)
|
||||
asyncio.run_coroutine_threadsafe(sampleQueue.put(None), loop = loop)
|
||||
if not finished.done():
|
||||
finished.set_result(None)
|
||||
@@ -63,27 +58,22 @@ async def createPipelineIterator(pipeline: str):
|
||||
hasFinished = False
|
||||
|
||||
appsink = gst.get_by_name('appsink')
|
||||
callbackQueue = Queue()
|
||||
yieldQueue = Queue()
|
||||
sampleQueue = asyncio.Queue()
|
||||
|
||||
async def gen():
|
||||
try:
|
||||
while True:
|
||||
yieldFuture = asyncio.Future()
|
||||
async def asyncCallback(sample):
|
||||
sampleQueue.put_nowait(sample)
|
||||
await yieldFuture
|
||||
callbackQueue.put(Callback(asyncCallback))
|
||||
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):
|
||||
@@ -91,16 +81,12 @@ async 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 = loop)
|
||||
if hasFinished:
|
||||
return Gst.FlowReturn.OK
|
||||
|
||||
future = asyncio.run_coroutine_threadsafe(callback.callback(sample), loop = loop)
|
||||
asyncio.run_coroutine_threadsafe(sampleQueue.put(sample), loop = loop)
|
||||
try:
|
||||
future.result()
|
||||
yieldQueue.get()
|
||||
except:
|
||||
pass
|
||||
return Gst.FlowReturn.OK
|
||||
|
||||
@@ -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
|
||||
@@ -81,6 +82,7 @@ def toPILImage(pilImageWrapper: PILImage, options: scrypted_sdk.ImageOptions = N
|
||||
|
||||
async def createPILMediaObject(image: PILImage):
|
||||
ret = await scrypted_sdk.mediaManager.createMediaObject(image, scrypted_sdk.ScryptedMimeTypes.Image.value, {
|
||||
'timestamp': time.time() * 1000,
|
||||
'format': None,
|
||||
'width': image.width,
|
||||
'height': image.height,
|
||||
|
||||
@@ -7,6 +7,7 @@ except:
|
||||
Image = None
|
||||
pyvips = None
|
||||
from thread import to_thread
|
||||
import time
|
||||
|
||||
class VipsImage(scrypted_sdk.VideoFrame):
|
||||
def __init__(self, vipsImage: Image) -> None:
|
||||
@@ -90,6 +91,7 @@ def toVipsImage(vipsImageWrapper: VipsImage, options: scrypted_sdk.ImageOptions
|
||||
|
||||
async def createVipsMediaObject(image: VipsImage):
|
||||
ret = await scrypted_sdk.mediaManager.createMediaObject(image, scrypted_sdk.ScryptedMimeTypes.Image.value, {
|
||||
'timestamp': time.time() * 1000,
|
||||
'format': None,
|
||||
'width': image.width,
|
||||
'height': image.height,
|
||||
|
||||
4
plugins/webrtc/package-lock.json
generated
4
plugins/webrtc/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.1.42",
|
||||
"version": "0.1.44",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.1.42",
|
||||
"version": "0.1.44",
|
||||
"dependencies": {
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/webrtc",
|
||||
"version": "0.1.42",
|
||||
"version": "0.1.44",
|
||||
"scripts": {
|
||||
"scrypted-setup-project": "scrypted-setup-project",
|
||||
"prescrypted-setup-project": "scrypted-package-json",
|
||||
|
||||
@@ -198,12 +198,6 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
},
|
||||
useIPv6: {
|
||||
title: 'Use IPv6',
|
||||
description: 'Use IPv6 addresses when connecting. This is disabled by default due to commonly misconfigured IPv6 local networks.',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
activeConnections: {
|
||||
readonly: true,
|
||||
title: "Current Open Connections",
|
||||
@@ -449,7 +443,6 @@ export class WebRTCPlugin extends AutoenableMixinProvider implements DeviceCreat
|
||||
}
|
||||
|
||||
return {
|
||||
iceUseIpv6: false,
|
||||
iceServers,
|
||||
iceInterfaceAddresses,
|
||||
...ret,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from __future__ import annotations
|
||||
from enum import Enum
|
||||
try:
|
||||
from typing import TypedDict, Union
|
||||
from typing import TypedDict
|
||||
except:
|
||||
from typing_extensions import TypedDict, Union
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing_extensions import TypedDict
|
||||
from typing import Union, Any, Callable
|
||||
|
||||
from .other import *
|
||||
|
||||
@@ -234,6 +233,9 @@ class Resource(TypedDict):
|
||||
href: str
|
||||
pass
|
||||
|
||||
class ClipPath(TypedDict):
|
||||
pass
|
||||
|
||||
class AudioStreamOptions(TypedDict):
|
||||
bitrate: float
|
||||
codec: str
|
||||
@@ -259,6 +261,13 @@ class ObjectDetectionResult(TypedDict):
|
||||
zones: list[str]
|
||||
pass
|
||||
|
||||
class ObjectDetectionZone(TypedDict):
|
||||
classes: list[str]
|
||||
exclusion: bool
|
||||
path: ClipPath
|
||||
type: Any | Any
|
||||
pass
|
||||
|
||||
class PictureDimensions(TypedDict):
|
||||
height: float
|
||||
width: float
|
||||
@@ -496,6 +505,7 @@ class ObjectDetectionGeneratorResult(TypedDict):
|
||||
class ObjectDetectionGeneratorSession(TypedDict):
|
||||
settings: Any
|
||||
sourceId: str
|
||||
zones: list[ObjectDetectionZone]
|
||||
pass
|
||||
|
||||
class ObjectDetectionModel(TypedDict):
|
||||
@@ -511,6 +521,7 @@ class ObjectDetectionModel(TypedDict):
|
||||
class ObjectDetectionSession(TypedDict):
|
||||
settings: Any
|
||||
sourceId: str
|
||||
zones: list[ObjectDetectionZone]
|
||||
pass
|
||||
|
||||
class ObjectDetectionTypes(TypedDict):
|
||||
|
||||
@@ -267,11 +267,10 @@ class ${td.name}(TypedDict):
|
||||
const pythonTypes = `from __future__ import annotations
|
||||
from enum import Enum
|
||||
try:
|
||||
from typing import TypedDict, Union
|
||||
from typing import TypedDict
|
||||
except:
|
||||
from typing_extensions import TypedDict, Union
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing_extensions import TypedDict
|
||||
from typing import Union, Any, Callable
|
||||
|
||||
from .other import *
|
||||
|
||||
|
||||
@@ -1296,6 +1296,7 @@ export interface ObjectDetector {
|
||||
getObjectTypes(): Promise<ObjectDetectionTypes>;
|
||||
}
|
||||
export interface ObjectDetectionGeneratorSession {
|
||||
zones?: ObjectDetectionZone[];
|
||||
settings?: { [key: string]: any };
|
||||
sourceId?: string;
|
||||
}
|
||||
@@ -1318,12 +1319,18 @@ 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>>;
|
||||
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>;
|
||||
}
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.7.85",
|
||||
"version": "0.7.88",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.7.85",
|
||||
"version": "0.7.88",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.7.85",
|
||||
"version": "0.7.89",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
|
||||
@@ -640,6 +640,8 @@ async function start(mainFilename: string, options?: {
|
||||
});
|
||||
|
||||
app.get('/', (_req, res) => res.redirect('/endpoint/@scrypted/core/public/'));
|
||||
|
||||
return scrypted;
|
||||
}
|
||||
|
||||
export default start;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Settings } from "../db-types";
|
||||
import { ScryptedRuntime } from "../runtime";
|
||||
import os from 'os';
|
||||
|
||||
export class AddressSettings {
|
||||
constructor(public scrypted: ScryptedRuntime) {
|
||||
@@ -12,10 +13,25 @@ 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) {
|
||||
ret.push(addr.address);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret.push(addressOrInterface);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user